Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,4 @@ bower_components
coverage
treecontrol.iml
.DS_Store
Chromium 49.0.2623 (Ubuntu)
2 changes: 1 addition & 1 deletion Gruntfile.js
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand Down
157 changes: 84 additions & 73 deletions angular-tree-control.js
Original file line number Diff line number Diff line change
Expand Up @@ -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', {
Expand All @@ -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',
Expand All @@ -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 || [];
Expand All @@ -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;
}
}
Expand All @@ -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;
Expand Down Expand Up @@ -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;
}
}
Expand All @@ -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;
}
Expand All @@ -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;
}
Expand All @@ -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;
}
Expand Down Expand Up @@ -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;
}
Expand Down Expand Up @@ -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);
}
}
Expand Down
2 changes: 1 addition & 1 deletion karma.conf.js
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand Down
22 changes: 22 additions & 0 deletions test/angular-tree-control-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -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('<treecontrol tree-model="treedata" options="treeOptions">{{node.label}}</treecontrol>')($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('<treecontrol tree-model="treedata" options="treeOptions">{{node.label}}</treecontrol>')($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 () {
Expand Down