diff --git a/angular-tree-control.js b/angular-tree-control.js index b138572..92d07c3 100644 --- a/angular-tree-control.js +++ b/angular-tree-control.js @@ -1,321 +1,337 @@ -(function ( angular ) { +(function(angular) { 'use strict'; - - angular.module( 'treeControl', [] ) - .directive( 'treecontrol', ['$compile', function( $compile ) { - /** - * @param cssClass - the css class - * @param addClassProperty - should we wrap the class name with class="" - */ - function classIfDefined(cssClass, addClassProperty) { - if (cssClass) { - if (addClassProperty) - return 'class="' + cssClass + '"'; - else - return cssClass; - } - else - return ""; - } - - function ensureDefault(obj, prop, value) { - if (!obj.hasOwnProperty(prop)) - obj[prop] = value; - } - - return { - restrict: 'EA', - require: "treecontrol", - transclude: true, - scope: { - treeModel: "=", - selectedNode: "=?", - selectedNodes: "=?", - expandedNodes: "=?", - onSelection: "&", - onNodeToggle: "&", - options: "=?", - orderBy: "@", - reverseOrder: "@", - filterExpression: "=?", - filterComparator: "=?" - }, - controller: ['$scope', function( $scope ) { - function defaultIsLeaf(node) { - return !node[$scope.options.nodeChildren] || node[$scope.options.nodeChildren].length === 0; - } - - function shallowCopy(src, dst) { - if (angular.isArray(src)) { - dst = dst || []; + angular.module('treeControl') + .directive('treecontrol', [ + '$compile', function($compile) { + return { + restrict: 'EA', + require: "treecontrol", + transclude: true, + scope: { + treeModel: "=", + selectedNode: "=?", + selectedNodes: "=?", + expandedNodes: "=?", + onSelection: "&", + onNodeToggle: "&", + options: "=?", + orderBy: "@", + reverseOrder: "@", + filterExpression: "=?", + filterComparator: "=?" + }, + controller: [ + '$scope', function ($scope) { + var ctrl = this; - for ( var i = 0; i < src.length; i++) { - dst[i] = src[i]; + ctrl.recompileTemplate = function(template) { + return $compile(template); } - } 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]; - } + ctrl.classIfDefined = function(cssClass, addClassProperty) { + if (cssClass) { + if (addClassProperty) + return 'class="' + cssClass + '"'; + else + return cssClass; + } else + return ""; } - } - return dst || src; - } - function defaultEquality(a, b) { - if (a === undefined || b === undefined) - return false; - a = shallowCopy(a); - a[$scope.options.nodeChildren] = []; - b = shallowCopy(b); - b[$scope.options.nodeChildren] = []; - return angular.equals(a, b); - } + ctrl.ensureDefault = function(obj, prop, value) { + if (!obj.hasOwnProperty(prop)) + obj[prop] = value; + } - function defaultIsSelectable() { - return true; - } + ctrl.defaultIsLeaf = function(node) { + return !node[$scope.options.nodeChildren] || node[$scope.options.nodeChildren].length === 0; + } - $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); - - $scope.selectedNodes = $scope.selectedNodes || []; - $scope.expandedNodes = $scope.expandedNodes || []; - $scope.expandedNodesMap = {}; - for (var i=0; i < $scope.expandedNodes.length; i++) { - $scope.expandedNodesMap[""+i] = $scope.expandedNodes[i]; - } - $scope.parentScopeOfTree = $scope.$parent; + ctrl.shallowCopy = function(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 || {}; - function isSelectedNode(node) { - if (!$scope.options.multiSelection && ($scope.options.equality(node, $scope.selectedNode))) - 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])) { - return true; + for (var key in src) { + if (hasOwnProperty.call(src, key) && !(key.charAt(0) === '$' && key.charAt(1) === '$')) { + dst[key] = src[key]; + } + } } - } - return false; - } - } - $scope.headClass = function(node) { - var liSelectionClass = classIfDefined($scope.options.injectClasses.liSelected, false); - var injectSelectionClass = ""; - if (liSelectionClass && isSelectedNode(node)) - injectSelectionClass = " " + liSelectionClass; - if ($scope.options.isLeaf(node)) - return "tree-leaf" + injectSelectionClass; - if ($scope.expandedNodesMap[this.$id]) - return "tree-expanded" + injectSelectionClass; - else - return "tree-collapsed" + injectSelectionClass; - }; + return dst || src; + } + ctrl.defaultEquality = function(a, b) { + if (a === undefined || b === undefined) + return false; + a = ctrl.shallowCopy(a); + a[$scope.options.nodeChildren] = []; + b = ctrl.shallowCopy(b); + b[$scope.options.nodeChildren] = []; + return angular.equals(a, b); + } - $scope.iBranchClass = function() { - if ($scope.expandedNodesMap[this.$id]) - return classIfDefined($scope.options.injectClasses.iExpanded); - else - return classIfDefined($scope.options.injectClasses.iCollapsed); - }; + ctrl.defaultIsSelectable = function() { + return true; + } - $scope.nodeExpanded = function() { - return !!$scope.expandedNodesMap[this.$id]; - }; + $scope.options = $scope.options || {}; + ctrl.ensureDefault($scope.options, "multiSelection", false); + ctrl.ensureDefault($scope.options, "nodeChildren", "children"); + ctrl.ensureDefault($scope.options, "dirSelectable", "true"); + ctrl.ensureDefault($scope.options, "injectClasses", {}); + ctrl.ensureDefault($scope.options.injectClasses, "ul", ""); + ctrl.ensureDefault($scope.options.injectClasses, "li", ""); + ctrl.ensureDefault($scope.options.injectClasses, "liSelected", ""); + ctrl.ensureDefault($scope.options.injectClasses, "iExpanded", ""); + ctrl.ensureDefault($scope.options.injectClasses, "iCollapsed", ""); + ctrl.ensureDefault($scope.options.injectClasses, "iLeaf", ""); + ctrl.ensureDefault($scope.options.injectClasses, "label", ""); + ctrl.ensureDefault($scope.options.injectClasses, "labelSelected", ""); + ctrl.ensureDefault($scope.options, "equality", ctrl.defaultEquality); + ctrl.ensureDefault($scope.options, "isLeaf", ctrl.defaultIsLeaf); + ctrl.ensureDefault($scope.options, "allowDeselect", true); + ctrl.ensureDefault($scope.options, "isSelectable", ctrl.defaultIsSelectable); - $scope.selectNodeHead = function() { - var transcludedScope = this; - var expanding = $scope.expandedNodesMap[transcludedScope.$id] === undefined; - $scope.expandedNodesMap[transcludedScope.$id] = (expanding ? transcludedScope.node : undefined); - if (expanding) { - $scope.expandedNodes.push(transcludedScope.node); - } - else { - var index; - for (var i=0; (i < $scope.expandedNodes.length) && !index; i++) { - if ($scope.options.equality($scope.expandedNodes[i], transcludedScope.node)) { - index = i; - } + $scope.selectedNodes = $scope.selectedNodes || []; + $scope.expandedNodes = $scope.expandedNodes || []; + $scope.expandedNodesMap = {}; + for (var i = 0; i < $scope.expandedNodes.length; i++) { + $scope.expandedNodesMap["" + i] = $scope.expandedNodes[i]; } - if (index != undefined) - $scope.expandedNodes.splice(index, 1); - } - if ($scope.onNodeToggle) { - var parentNode = (transcludedScope.$parent.node === transcludedScope.synteticRoot)?null:transcludedScope.$parent.node; - $scope.onNodeToggle({node: transcludedScope.node, $parentNode: parentNode, - $index: transcludedScope.$index, $first: transcludedScope.$first, $middle: transcludedScope.$middle, - $last: transcludedScope.$last, $odd: transcludedScope.$odd, $even: transcludedScope.$even, expanded: expanding}); + $scope.parentScopeOfTree = $scope.$parent; - } - }; - $scope.selectNodeLabel = function( selectedNode){ - var transcludedScope = this; - if(!$scope.options.isLeaf(selectedNode) && (!$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))) { - // Leaf node is not selectable - return; - } - else { - var selected = false; - if ($scope.options.multiSelection) { - var pos = -1; - for (var i=0; i < $scope.selectedNodes.length; i++) { - if($scope.options.equality(selectedNode, $scope.selectedNodes[i])) { - pos = i; - break; + ctrl.isSelectedNode = function(node) { + if (!$scope.options.multiSelection && ($scope.options.equality(node, $scope.selectedNode))) + 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])) { + return true; + } } + return false; } - if (pos === -1) { - $scope.selectedNodes.push(selectedNode); - selected = true; + return false; + } + + $scope.headClass = function(node) { + var liSelectionClass = ctrl.classIfDefined($scope.options.injectClasses.liSelected, false); + var injectSelectionClass = ""; + if (liSelectionClass && ctrl.isSelectedNode(node)) + injectSelectionClass = " " + liSelectionClass; + if ($scope.options.isLeaf(node)) + return "tree-leaf" + injectSelectionClass; + if ($scope.expandedNodesMap[this.$id]) + return "tree-expanded" + injectSelectionClass; + else + return "tree-collapsed" + injectSelectionClass; + }; + + $scope.iBranchClass = function() { + if ($scope.expandedNodesMap[this.$id]) + return ctrl.classIfDefined($scope.options.injectClasses.iExpanded); + else + return ctrl.classIfDefined($scope.options.injectClasses.iCollapsed); + }; + + $scope.nodeExpanded = function() { + return !!$scope.expandedNodesMap[this.$id]; + }; + + $scope.selectNodeHead = function() { + var transcludedScope = this; + var expanding = $scope.expandedNodesMap[transcludedScope.$id] === undefined; + $scope.expandedNodesMap[transcludedScope.$id] = (expanding ? transcludedScope.node : undefined); + if (expanding) { + $scope.expandedNodes.push(transcludedScope.node); } else { - $scope.selectedNodes.splice(pos, 1); + var index; + for (var i = 0; (i < $scope.expandedNodes.length) && !index; i++) { + if ($scope.options.equality($scope.expandedNodes[i], transcludedScope.node)) { + index = i; + } + } + if (index != undefined) + $scope.expandedNodes.splice(index, 1); } - } else { - if (!$scope.options.equality(selectedNode, $scope.selectedNode)) { - $scope.selectedNode = selectedNode; - selected = true; + if ($scope.onNodeToggle) { + var parentNode = (transcludedScope.$parent.node === transcludedScope.synteticRoot) ? null : transcludedScope.$parent.node; + $scope.onNodeToggle({ + node: transcludedScope.node, + $parentNode: parentNode, + $index: transcludedScope.$index, + $first: transcludedScope.$first, + $middle: transcludedScope.$middle, + $last: transcludedScope.$last, + $odd: transcludedScope.$odd, + $even: transcludedScope.$even, + expanded: expanding + }); + } - else { - if ($scope.options.allowDeselect) { - $scope.selectedNode = undefined; + }; + + $scope.selectNodeLabel = function(selectedNode) { + var transcludedScope = this; + if (!$scope.options.isLeaf(selectedNode) && (!$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))) { + // Leaf node is not selectable + return; + } else { + var selected = false; + if ($scope.options.multiSelection) { + var pos = -1; + for (var i = 0; i < $scope.selectedNodes.length; i++) { + if ($scope.options.equality(selectedNode, $scope.selectedNodes[i])) { + pos = i; + break; + } + } + if (pos === -1) { + $scope.selectedNodes.push(selectedNode); + selected = true; + } else { + $scope.selectedNodes.splice(pos, 1); + } } else { - $scope.selectedNode = selectedNode; - selected = true; + if (!$scope.options.equality(selectedNode, $scope.selectedNode)) { + $scope.selectedNode = selectedNode; + selected = true; + } else { + if ($scope.options.allowDeselect) { + $scope.selectedNode = undefined; + } else { + $scope.selectedNode = selectedNode; + selected = true; + } + } + } + if ($scope.onSelection) { + var parentNode = (transcludedScope.$parent.node === transcludedScope.synteticRoot) ? null : transcludedScope.$parent.node; + $scope.onSelection({ + node: selectedNode, + selected: selected, + $parentNode: parentNode, + $index: transcludedScope.$index, + $first: transcludedScope.$first, + $middle: transcludedScope.$middle, + $last: transcludedScope.$last, + $odd: transcludedScope.$odd, + $even: transcludedScope.$even + }); } } - } - if ($scope.onSelection) { - var parentNode = (transcludedScope.$parent.node === transcludedScope.synteticRoot)?null:transcludedScope.$parent.node; - $scope.onSelection({node: selectedNode, selected: selected, $parentNode: parentNode, - $index: transcludedScope.$index, $first: transcludedScope.$first, $middle: transcludedScope.$middle, - $last: transcludedScope.$last, $odd: transcludedScope.$odd, $even: transcludedScope.$even}); - } - } - }; - - $scope.selectedClass = function() { - var isThisNodeSelected = isSelectedNode(this.node); - var labelSelectionClass = classIfDefined($scope.options.injectClasses.labelSelected, false); - var injectSelectionClass = ""; - if (labelSelectionClass && isThisNodeSelected) - injectSelectionClass = " " + labelSelectionClass; + }; - return isThisNodeSelected ? "tree-selected" + injectSelectionClass : ""; - }; + $scope.selectedClass = function() { + var isThisNodeSelected = ctrl.isSelectedNode(this.node); + var labelSelectionClass = ctrl.classIfDefined($scope.options.injectClasses.labelSelected, false); + var injectSelectionClass = ""; + if (labelSelectionClass && isThisNodeSelected) + injectSelectionClass = " " + labelSelectionClass; - $scope.unselectableClass = function() { - var isThisNodeUnselectable = !$scope.options.isSelectable(this.node); - var labelUnselectableClass = classIfDefined($scope.options.injectClasses.labelUnselectable, false); - return isThisNodeUnselectable ? "tree-unselectable " + labelUnselectableClass : ""; - }; + return isThisNodeSelected ? "tree-selected" + injectSelectionClass : ""; + }; - //tree template - var orderBy = $scope.orderBy ? ' | orderBy:orderBy:reverseOrder' : ''; - var template = - '