diff --git a/.gitignore b/.gitignore index 94783d5..5e22086 100644 --- a/.gitignore +++ b/.gitignore @@ -4,3 +4,4 @@ bower_components coverage treecontrol.iml .DS_Store +Chromium 49.0.2623 (Ubuntu) diff --git a/Gruntfile.js b/Gruntfile.js index 1d3351c..4851758 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -30,7 +30,7 @@ module.exports = function(grunt) { background: true, options: { files: [ - 'bower_components/jquery/jquery.js', + 'bower_components/jquery/dist/jquery.js', 'demo/angular.1.3.12.js', 'demo/angular-mocks.1.3.12.js', 'angular-tree-control.js', diff --git a/angular-tree-control.js b/angular-tree-control.js index 3c492a8..418a916 100644 --- a/angular-tree-control.js +++ b/angular-tree-control.js @@ -19,6 +19,67 @@ if (typeof module !== "undefined" && typeof exports !== "undefined" && module.ex return _path; } } + + function ensureDefault(obj, prop, value) { + if (!obj.hasOwnProperty(prop)) + obj[prop] = value; + } + + function defaultIsLeaf(node, $scope) { + return !node[$scope.options.nodeChildren] || node[$scope.options.nodeChildren].length === 0; + } + + function shallowCopy(src, dst) { + if (angular.isArray(src)) { + dst = dst || []; + + for (var i = 0; i < src.length; i++) { + dst[i] = src[i]; + } + } else if (angular.isObject(src)) { + dst = dst || {}; + + for (var key in src) { + if (hasOwnProperty.call(src, key) && !(key.charAt(0) === '$' && key.charAt(1) === '$')) { + dst[key] = src[key]; + } + } + } + + return dst || src; + } + function defaultEquality(a, b,$scope) { + if (!a || !b) + return false; + a = shallowCopy(a); + a[$scope.options.nodeChildren] = []; + b = shallowCopy(b); + b[$scope.options.nodeChildren] = []; + return angular.equals(a, b); + } + + function defaultIsSelectable() { + return true; + } + + function ensureAllDefaultOptions($scope) { + ensureDefault($scope.options, "multiSelection", false); + ensureDefault($scope.options, "nodeChildren", "children"); + ensureDefault($scope.options, "dirSelectable", "true"); + ensureDefault($scope.options, "injectClasses", {}); + ensureDefault($scope.options.injectClasses, "ul", ""); + ensureDefault($scope.options.injectClasses, "li", ""); + ensureDefault($scope.options.injectClasses, "liSelected", ""); + ensureDefault($scope.options.injectClasses, "iExpanded", ""); + ensureDefault($scope.options.injectClasses, "iCollapsed", ""); + ensureDefault($scope.options.injectClasses, "iLeaf", ""); + ensureDefault($scope.options.injectClasses, "label", ""); + ensureDefault($scope.options.injectClasses, "labelSelected", ""); + ensureDefault($scope.options, "equality", defaultEquality); + ensureDefault($scope.options, "isLeaf", defaultIsLeaf); + ensureDefault($scope.options, "allowDeselect", true); + ensureDefault($scope.options, "isSelectable", defaultIsSelectable); + } angular.module( 'treeControl', [] ) .constant('treeConfig', { @@ -40,10 +101,7 @@ if (typeof module !== "undefined" && typeof exports !== "undefined" && module.ex return ""; } - function ensureDefault(obj, prop, value) { - if (!obj.hasOwnProperty(prop)) - obj[prop] = value; - } + return { restrict: 'EA', @@ -62,62 +120,11 @@ if (typeof module !== "undefined" && typeof exports !== "undefined" && module.ex filterExpression: "=?", filterComparator: "=?" }, - controller: ['$scope', '$templateCache', '$interpolate', 'treeConfig', function( $scope, $templateCache, $interpolate, treeConfig ) { - - function defaultIsLeaf(node) { - return !node[$scope.options.nodeChildren] || node[$scope.options.nodeChildren].length === 0; - } - - function shallowCopy(src, dst) { - if (angular.isArray(src)) { - dst = dst || []; - - for ( var i = 0; i < src.length; i++) { - dst[i] = src[i]; - } - } else if (angular.isObject(src)) { - dst = dst || {}; - - for (var key in src) { - if (hasOwnProperty.call(src, key) && !(key.charAt(0) === '$' && key.charAt(1) === '$')) { - dst[key] = src[key]; - } - } - } - - return dst || src; - } - function defaultEquality(a, b) { - if (!a || !b ) - return false; - a = shallowCopy(a); - a[$scope.options.nodeChildren] = []; - b = shallowCopy(b); - b[$scope.options.nodeChildren] = []; - return angular.equals(a, b); - } - - function defaultIsSelectable() { - return true; - } - + controller: ['$scope', '$templateCache', '$interpolate', 'treeConfig', function ($scope, $templateCache, $interpolate, treeConfig) { + $scope.options = $scope.options || {}; - ensureDefault($scope.options, "multiSelection", false); - ensureDefault($scope.options, "nodeChildren", "children"); - ensureDefault($scope.options, "dirSelectable", "true"); - ensureDefault($scope.options, "injectClasses", {}); - ensureDefault($scope.options.injectClasses, "ul", ""); - ensureDefault($scope.options.injectClasses, "li", ""); - ensureDefault($scope.options.injectClasses, "liSelected", ""); - ensureDefault($scope.options.injectClasses, "iExpanded", ""); - ensureDefault($scope.options.injectClasses, "iCollapsed", ""); - ensureDefault($scope.options.injectClasses, "iLeaf", ""); - ensureDefault($scope.options.injectClasses, "label", ""); - ensureDefault($scope.options.injectClasses, "labelSelected", ""); - ensureDefault($scope.options, "equality", defaultEquality); - ensureDefault($scope.options, "isLeaf", defaultIsLeaf); - ensureDefault($scope.options, "allowDeselect", true); - ensureDefault($scope.options, "isSelectable", defaultIsSelectable); + + ensureAllDefaultOptions($scope); $scope.selectedNodes = $scope.selectedNodes || []; $scope.expandedNodes = $scope.expandedNodes || []; @@ -129,11 +136,11 @@ if (typeof module !== "undefined" && typeof exports !== "undefined" && module.ex function isSelectedNode(node) { - if (!$scope.options.multiSelection && ($scope.options.equality(node, $scope.selectedNode))) + if (!$scope.options.multiSelection && ($scope.options.equality(node, $scope.selectedNode , $scope))) return true; else if ($scope.options.multiSelection && $scope.selectedNodes) { for (var i = 0; (i < $scope.selectedNodes.length); i++) { - if ($scope.options.equality(node, $scope.selectedNodes[i])) { + if ($scope.options.equality(node, $scope.selectedNodes[i] , $scope)) { return true; } } @@ -146,7 +153,7 @@ if (typeof module !== "undefined" && typeof exports !== "undefined" && module.ex var injectSelectionClass = ""; if (liSelectionClass && isSelectedNode(node)) injectSelectionClass = " " + liSelectionClass; - if ($scope.options.isLeaf(node)) + if ($scope.options.isLeaf(node, $scope)) return "tree-leaf" + injectSelectionClass; if ($scope.expandedNodesMap[this.$id]) return "tree-expanded" + injectSelectionClass; @@ -175,7 +182,7 @@ if (typeof module !== "undefined" && typeof exports !== "undefined" && module.ex else { var index; for (var i=0; (i < $scope.expandedNodes.length) && !index; i++) { - if ($scope.options.equality($scope.expandedNodes[i], transcludedScope.node)) { + if ($scope.options.equality($scope.expandedNodes[i], transcludedScope.node , $scope)) { index = i; } } @@ -194,11 +201,11 @@ if (typeof module !== "undefined" && typeof exports !== "undefined" && module.ex $scope.selectNodeLabel = function( selectedNode){ var transcludedScope = this; - if(!$scope.options.isLeaf(selectedNode) && (!$scope.options.dirSelectable || !$scope.options.isSelectable(selectedNode))) { + if(!$scope.options.isLeaf(selectedNode, $scope) && (!$scope.options.dirSelectable || !$scope.options.isSelectable(selectedNode))) { // Branch node is not selectable, expand this.selectNodeHead(); } - else if($scope.options.isLeaf(selectedNode) && (!$scope.options.isSelectable(selectedNode))) { + else if($scope.options.isLeaf(selectedNode, $scope) && (!$scope.options.isSelectable(selectedNode))) { // Leaf node is not selectable return; } @@ -207,7 +214,7 @@ if (typeof module !== "undefined" && typeof exports !== "undefined" && module.ex if ($scope.options.multiSelection) { var pos = -1; for (var i=0; i < $scope.selectedNodes.length; i++) { - if($scope.options.equality(selectedNode, $scope.selectedNodes[i])) { + if($scope.options.equality(selectedNode, $scope.selectedNodes[i] , $scope)) { pos = i; break; } @@ -219,7 +226,7 @@ if (typeof module !== "undefined" && typeof exports !== "undefined" && module.ex $scope.selectedNodes.splice(pos, 1); } } else { - if (!$scope.options.equality(selectedNode, $scope.selectedNode)) { + if (!$scope.options.equality(selectedNode, $scope.selectedNode , $scope)) { $scope.selectedNode = selectedNode; selected = true; } @@ -338,7 +345,7 @@ if (typeof module !== "undefined" && typeof exports !== "undefined" && module.ex var found = false; for (var i=0; (i < existingScopes.length) && !found; i++) { var existingScope = existingScopes[i]; - if (scope.options.equality(newExNode, existingScope.node)) { + if (scope.options.equality(newExNode, existingScope.node , scope)) { newExpandedNodesMap[existingScope.$id] = existingScope.node; found = true; } @@ -386,23 +393,27 @@ if (typeof module !== "undefined" && typeof exports !== "undefined" && module.ex } }; }) - .directive("treeTransclude", function() { + .directive("treeTransclude", function () { return { + controller: function ($scope) { + ensureAllDefaultOptions($scope); + }, + link: function(scope, element, attrs, controller) { - if (!scope.options.isLeaf(scope.node)) { + if (!scope.options.isLeaf(scope.node, scope)) { angular.forEach(scope.expandedNodesMap, function (node, id) { - if (scope.options.equality(node, scope.node)) { + if (scope.options.equality(node, scope.node , scope)) { scope.expandedNodesMap[scope.$id] = scope.node; scope.expandedNodesMap[id] = undefined; } }); } - if (!scope.options.multiSelection && scope.options.equality(scope.node, scope.selectedNode)) { + if (!scope.options.multiSelection && scope.options.equality(scope.node, scope.selectedNode , scope)) { scope.selectedNode = scope.node; } else if (scope.options.multiSelection) { var newSelectedNodes = []; for (var i = 0; (i < scope.selectedNodes.length); i++) { - if (scope.options.equality(scope.node, scope.selectedNodes[i])) { + if (scope.options.equality(scope.node, scope.selectedNodes[i] , scope)) { newSelectedNodes.push(scope.node); } } diff --git a/karma.conf.js b/karma.conf.js index 2fbb4ce..936c672 100644 --- a/karma.conf.js +++ b/karma.conf.js @@ -12,7 +12,7 @@ module.exports = function(config) { // list of files / patterns to load in the browser files: [ - 'bower_components/jquery/jquery.js', + 'bower_components/jquery/dist/jquery.js', 'bower_components/angular/angular.js', 'bower_components/angular-mocks/angular-mocks.js', 'angular-tree-control.js', diff --git a/test/angular-tree-control-test.js b/test/angular-tree-control-test.js index 5827415..ceddf5f 100644 --- a/test/angular-tree-control-test.js +++ b/test/angular-tree-control-test.js @@ -98,6 +98,28 @@ describe('treeControl', function() { expect(element.find('li.tree-leaf').length).toBe(3); }); + it('should display first level parents as leafs, based on condition', function () { + $rootScope.treedata = createSubTree(2, 2); + // reverse which is leaf and which is branch - now we have 2 leafs that are not expanded + $rootScope.treeOptions = {isLeaf: function(node) {return node.children.length > 0;}}; + element = $compile('{{node.label}}')($rootScope); + $rootScope.$digest(); + expect(element.find('li.tree-collapsed').length).toBe(0); + expect(element.find('li.tree-leaf').length).toBe(2); + }); + + it('should display second level as branches, based on condition', function () { + $rootScope.treedata = createSubTree(2, 2); + // reverse which is leaf and which is branch - now we have 2 leafs that are not expanded + $rootScope.treeOptions = {isLeaf: function(node) {return node.children.length > 0;}}; + element = $compile('{{node.label}}')($rootScope); + $rootScope.$digest(); + element.find('li:eq(1) .tree-branch-head').click(); + element.find('li:eq(0) .tree-branch-head').click(); + // now the first level "leafs" are expanded, and we have 4 second level "branches" + expect(element.find('li.tree-collapsed').length).toBe(4); + expect(element.find('li.tree-leaf').length).toBe(2); + }); }); describe('rendering using external scope data', function () {