(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 || []; 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 === undefined || b === undefined) return false; a = shallowCopy(a); a[$scope.options.nodeChildren] = []; b = shallowCopy(b); b[$scope.options.nodeChildren] = []; return angular.equals(a, b); } $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); $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; 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; } } 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; }; $scope.iBranchClass = function() { if ($scope.expandedNodesMap[this.$id]) return classIfDefined($scope.options.injectClasses.iExpanded); else return classIfDefined($scope.options.injectClasses.iCollapsed); }; $scope.nodeExpanded = function() { return !!$scope.expandedNodesMap[this.$id]; }; $scope.selectNodeHead = function() { var expanding = $scope.expandedNodesMap[this.$id] === undefined; $scope.expandedNodesMap[this.$id] = (expanding ? this.node : undefined); if (expanding) { $scope.expandedNodes.push(this.node); } else { var index; for (var i=0; (i < $scope.expandedNodes.length) && !index; i++) { if ($scope.options.equality($scope.expandedNodes[i], this.node)) { index = i; } } if (index != undefined) $scope.expandedNodes.splice(index, 1); } if ($scope.onNodeToggle) $scope.onNodeToggle({node: this.node, expanded: expanding}); }; $scope.selectNodeLabel = function( selectedNode ){ if (selectedNode[$scope.options.nodeChildren] && selectedNode[$scope.options.nodeChildren].length > 0 && !$scope.options.dirSelectable) { this.selectNodeHead(); } else { var selected = false; if ($scope.options.multiSelection) { var pos = $scope.selectedNodes.indexOf(selectedNode); if (pos === -1) { $scope.selectedNodes.push(selectedNode); selected = true; } else { $scope.selectedNodes.splice(pos, 1); } } else { if ($scope.selectedNode != selectedNode) { $scope.selectedNode = selectedNode; selected = true; } else { $scope.selectedNode = undefined; } } if ($scope.onSelection) $scope.onSelection({node: selectedNode, selected: selected}); } }; $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:""; }; //tree template var orderBy = $scope.orderBy ? ' | orderBy:orderBy:reverseOrder' : ''; var template = '