forked from alalonde/angular-lazy-tree
-
Notifications
You must be signed in to change notification settings - Fork 0
/
angular-lazy-tree.js
141 lines (136 loc) · 5.67 KB
/
angular-lazy-tree.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
// A recursively-defined tree. Tree nodes with children create sub-trees.
// Inspired by: http://plnkr.co/edit/T0BgQR?p=preview
//
// Attributes:
// ng-model An array of root nodes for the tree
// load-fn A function that will be called to load child nodes. This function takes
// a nodeId and optional callback. It can also optionally return a promise.
// It is assumed that the callback is called with an array parameter which
// represents the node's children.
(function(angular) {
'use strict';
angular.module('lazy-tree', [])
.directive('uiTree', function() {
return {
template: '<ul class="uiTree"><ui-tree-node ng-repeat="node in tree"></ui-tree-node></ul>',
replace: true,
transclude: true,
restrict: 'E',
scope: {
tree: '=ngModel',
attrNodeId: "@",
loadFn: '=',
expandTo: '=',
selectedId: '='
},
controller: function($scope, $element, $attrs) {
$scope.loadFnName = $attrs.loadFn;
// this seems like an egregious hack, but it is necessary for recursively-generated
// trees to have access to the loader function
if($scope.$parent.loadFn)
$scope.loadFn = $scope.$parent.loadFn;
// TODO expandTo shouldn't be two-way, currently we're copying it
if($scope.expandTo && $scope.expandTo.length) {
$scope.expansionNodes = angular.copy($scope.expandTo);
var arrExpandTo = $scope.expansionNodes.split(",");
$scope.nextExpandTo = arrExpandTo.shift();
$scope.expansionNodes = arrExpandTo.join(",");
}
}
};
})
.directive('uiTreeNode', ['$compile', '$timeout', function($compile, $timeout) {
return {
restrict: 'E',
replace: true,
template: '<li>' +
'<div class="node" data-node-id="{{ nodeId() }}">' +
'<a class="icon" ng-click="toggleNode(nodeId())""></a>' +
'<a ng-hide="selectedId" ng-href="#/assets/{{ nodeId() }}">{{ node.name }}</a>' +
'<span ng-show="selectedId" ng-class="css()" ng-click="setSelected(node)">' +
'{{ node.name }}</span>' +
'</div>' +
'</li>',
link: function(scope, elm, attrs) {
scope.nodeId = function(node) {
var localNode = node || scope.node;
return localNode[scope.attrNodeId];
};
scope.toggleNode = function(nodeId) {
var isVisible = elm.children(".uiTree:visible").length > 0;
var childrenTree = elm.children(".uiTree");
if(isVisible) {
scope.$emit('nodeCollapsed', nodeId);
} else if(nodeId) {
scope.$emit('nodeExpanded', nodeId);
}
if(!isVisible && scope.loadFn && childrenTree.length === 0) {
// load the children asynchronously
var callback = function(arrChildren) {
scope.node.children = arrChildren;
scope.appendChildren();
elm.find("a.icon i").show();
elm.find("a.icon img").remove();
scope.toggleNode(); // show it
};
var promiseOrNodes = scope.loadFn(nodeId, callback);
if(promiseOrNodes && promiseOrNodes.then) {
promiseOrNodes.then(callback);
} else {
$timeout(function() {
callback(promiseOrNodes);
});
}
elm.find("a.icon i").hide();
var imgUrl = "http://www.efsa.europa.eu/efsa_rep/repository/images/ajax-loader.gif";
elm.find("a.icon").append('<img src="' + imgUrl + '" width="18" height="18">');
} else {
childrenTree.toggle(!isVisible);
elm.find("a.icon i").toggleClass("icon-chevron-right");
elm.find("a.icon i").toggleClass("icon-chevron-down");
}
};
scope.appendChildren = function() {
// Add children by $compiling and doing a new ui-tree directive
// We need the load-fn attribute in there if it has been provided
var childrenHtml = '<ui-tree ng-model="node.children" attr-node-id="' +
scope.attrNodeId + '"';
if(scope.loadFn) {
childrenHtml += ' load-fn="' + scope.loadFnName + '"';
}
// pass along all the variables
if(scope.expansionNodes) {
childrenHtml += ' expand-to="expansionNodes"';
}
if(scope.selectedId) {
childrenHtml += ' selected-id="selectedId"';
}
childrenHtml += ' style="display: none"></ui-tree>';
return elm.append($compile(childrenHtml)(scope));
};
scope.css = function() {
return {
nodeLabel: true,
selected: scope.selectedId && scope.nodeId() === scope.selectedId
};
};
// emit an event up the scope. Then, from the scope above this tree, a "selectNode"
// event is expected to be broadcasted downwards to each node in the tree.
// TODO this needs to be re-thought such that the controller doesn't need to manually
// broadcast "selectNode" from outside of the directive scope.
scope.setSelected = function(node) {
scope.$emit("nodeSelected", node);
};
scope.$on("selectNode", function(event, node) {
scope.selectedId = scope.nodeId(node);
});
if(scope.node.hasChildren) {
elm.find("a.icon").append('<i class="icon-chevron-right"></i>');
}
if(scope.nextExpandTo && scope.nodeId() == parseInt(scope.nextExpandTo, 10)) {
scope.toggleNode(scope.nodeId());
}
}
};
}]);
})(angular);