Skip to content

Commit c7bc460

Browse files
committed
implementing multi-selection
1 parent 1a4cf72 commit c7bc460

File tree

2 files changed

+122
-10
lines changed

2 files changed

+122
-10
lines changed

angular-tree-control.js

Lines changed: 47 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
scope: {
3131
treeModel: "=",
3232
selectedNode: "=?",
33+
selectedNodes: "=?",
3334
expandedNodes: "=?",
3435
onSelection: "&",
3536
onNodeToggle: "&",
@@ -56,6 +57,7 @@
5657
}
5758

5859
$scope.options = $scope.options || {};
60+
ensureDefault($scope.options, "multiSelection", false);
5961
ensureDefault($scope.options, "nodeChildren", "children");
6062
ensureDefault($scope.options, "dirSelectable", "true");
6163
ensureDefault($scope.options, "injectClasses", {});
@@ -70,6 +72,7 @@
7072
ensureDefault($scope.options, "equality", defaultEquality);
7173
ensureDefault($scope.options, "isLeaf", defaultIsLeaf);
7274

75+
$scope.selectedNodes = $scope.selectedNodes || [];
7376
$scope.expandedNodes = $scope.expandedNodes || [];
7477
$scope.expandedNodesMap = {};
7578
for (var i=0; i < $scope.expandedNodes.length; i++) {
@@ -78,10 +81,23 @@
7881
$scope.parentScopeOfTree = $scope.$parent;
7982

8083

84+
function isSelectedNode(node) {
85+
if (!$scope.options.multiSelection && ($scope.options.equality(node, $scope.selectedNode)))
86+
return true;
87+
else if ($scope.options.multiSelection && $scope.selectedNodes) {
88+
for (var i = 0; (i < $scope.selectedNodes.length); i++) {
89+
if ($scope.options.equality(node, $scope.selectedNodes[i])) {
90+
return true;
91+
}
92+
}
93+
return false;
94+
}
95+
}
96+
8197
$scope.headClass = function(node) {
8298
var liSelectionClass = classIfDefined($scope.options.injectClasses.liSelected, false);
8399
var injectSelectionClass = "";
84-
if (liSelectionClass && ($scope.options.equality(this.node, $scope.selectedNode)))
100+
if (liSelectionClass && isSelectedNode(node))
85101
injectSelectionClass = " " + liSelectionClass;
86102
if ($scope.options.isLeaf(node))
87103
return "tree-leaf" + injectSelectionClass;
@@ -128,24 +144,37 @@
128144
this.selectNodeHead();
129145
}
130146
else {
131-
if ($scope.selectedNode != selectedNode) {
132-
$scope.selectedNode = selectedNode;
133-
}
134-
else {
135-
$scope.selectedNode = undefined;
147+
var selected = false;
148+
if ($scope.options.multiSelection) {
149+
var pos = $scope.selectedNodes.indexOf(selectedNode);
150+
if (pos === -1) {
151+
$scope.selectedNodes.push(selectedNode);
152+
selected = true;
153+
} else {
154+
$scope.selectedNodes.splice(pos, 1);
155+
}
156+
} else {
157+
if ($scope.selectedNode != selectedNode) {
158+
$scope.selectedNode = selectedNode;
159+
selected = true;
160+
}
161+
else {
162+
$scope.selectedNode = undefined;
163+
}
136164
}
137165
if ($scope.onSelection)
138-
$scope.onSelection({node: $scope.selectedNode});
166+
$scope.onSelection({node: selected? selectedNode : undefined});
139167
}
140168
};
141169

142170
$scope.selectedClass = function() {
171+
var isThisNodeSelected = isSelectedNode(this.node);
143172
var labelSelectionClass = classIfDefined($scope.options.injectClasses.labelSelected, false);
144173
var injectSelectionClass = "";
145-
if (labelSelectionClass && (this.node == $scope.selectedNode))
174+
if (labelSelectionClass && isThisNodeSelected)
146175
injectSelectionClass = " " + labelSelectionClass;
147176

148-
return (this.node == $scope.selectedNode)?"tree-selected" + injectSelectionClass:"";
177+
return isThisNodeSelected?"tree-selected" + injectSelectionClass:"";
149178
};
150179

151180
//tree template
@@ -248,8 +277,16 @@
248277
}
249278
});
250279
}
251-
if (scope.options.equality(scope.node, scope.selectedNode)) {
280+
if (!scope.options.multiSelection && scope.options.equality(scope.node, scope.selectedNode)) {
252281
scope.selectedNode = scope.node;
282+
} else if (scope.options.multiSelection) {
283+
var newSelectedNodes = [];
284+
for (var i = 0; (i < scope.selectedNodes.length); i++) {
285+
if (scope.options.equality(scope.node, scope.selectedNodes[i])) {
286+
newSelectedNodes.push(scope.node);
287+
}
288+
}
289+
scope.selectedNodes = newSelectedNodes;
253290
}
254291

255292
// create a scope for the transclusion, whos parent is the parent of the tree control

test/angular-tree-control-test.js

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -261,6 +261,81 @@ describe('treeControl', function() {
261261
});
262262
});
263263

264+
describe('multi-selection', function() {
265+
it('should publish the currently selected nodes on scope', function () {
266+
$rootScope.treeOptions = {multiSelection: true};
267+
$rootScope.treedata = createSubTree(2, 2);
268+
element = $compile('<treecontrol tree-model="treedata" selected-nodes="selectedItems" options="treeOptions">{{node.label}}</treecontrol>')($rootScope);
269+
$rootScope.$digest();
270+
271+
element.find('li:eq(0) div').click();
272+
expect($rootScope.selectedItems.length).toBe(1);
273+
expect($rootScope.selectedItems[0].label).toBe('node 1');
274+
element.find('li:eq(1) div').click();
275+
expect($rootScope.selectedItems.length).toBe(2);
276+
});
277+
278+
it('should update the tree selection if the external scope selected-node changes', function() {
279+
$rootScope.treeOptions = {multiSelection: true};
280+
$rootScope.treedata = createSubTree(2, 2);
281+
element = $compile('<treecontrol tree-model="treedata" selected-nodes="selectedItems" options="treeOptions">{{node.label}}</treecontrol>')($rootScope);
282+
$rootScope.selectedItems = [$rootScope.treedata[0], $rootScope.treedata[1].children[0]];
283+
$rootScope.$digest();
284+
285+
expect(element.find('li:eq(0) div.tree-selected').length).toBe(1);
286+
element.find('li:eq(1) .tree-branch-head').click();
287+
expect(element.find('li:eq(1) li:eq(0) div.tree-selected').length).toBe(1);
288+
});
289+
290+
it('should invoke on-selection callback when item is selected', function () {
291+
$rootScope.treeOptions = {multiSelection: true};
292+
$rootScope.treedata = createSubTree(2, 2);
293+
element = $compile('<treecontrol tree-model="treedata" on-selection="itemSelected(node.label)" options="treeOptions">{{node.label}}</treecontrol>')($rootScope);
294+
$rootScope.$digest();
295+
296+
$rootScope.itemSelected = jasmine.createSpy('itemSelected');
297+
element.find('li:eq(0) div').click();
298+
expect($rootScope.itemSelected).toHaveBeenCalledWith('node 1');
299+
});
300+
301+
xit('should call on-selection callback on item unselection with undefined node', function () {
302+
$rootScope.treedata = createSubTree(2, 2);
303+
element = $compile('<treecontrol tree-model="treedata" on-selection="itemSelected(node)">{{node.label}}</treecontrol>')($rootScope);
304+
$rootScope.$digest();
305+
306+
$rootScope.itemSelected = jasmine.createSpy('itemSelected');
307+
element.find('li:eq(0) div').click();
308+
element.find('li:eq(0) div').click();
309+
expect($rootScope.itemSelected).toHaveBeenCalledWith($rootScope.treedata[0]);
310+
expect($rootScope.itemSelected).toHaveBeenCalledWith(undefined);
311+
expect($rootScope.itemSelected.calls.length).toBe(2);
312+
});
313+
314+
xit('should un-select a node after second click', function () {
315+
$rootScope.treedata = createSubTree(2, 2);
316+
$rootScope.selectedItem = $rootScope.treedata[0];
317+
element = $compile('<treecontrol tree-model="treedata" selected-node="selectedItem">{{node.label}}</treecontrol>')($rootScope);
318+
$rootScope.$digest();
319+
320+
element.find('li:eq(0) div').click();
321+
expect($rootScope.selectedItem).toBeUndefined()
322+
});
323+
324+
xit('should retain selection after full model refresh', function () {
325+
var testTree = createSubTree(2, 2);
326+
$rootScope.treedata = angular.copy(testTree);
327+
element = $compile('<treecontrol tree-model="treedata">{{node.label}}</treecontrol>')($rootScope);
328+
$rootScope.$digest();
329+
330+
element.find('li:eq(0) div').click();
331+
expect(element.find('.tree-selected').length).toBe(1);
332+
333+
$rootScope.treedata = angular.copy(testTree);
334+
$rootScope.$digest();
335+
expect(element.find('.tree-selected').length).toBe(1);
336+
});
337+
});
338+
264339
describe('options usage', function () {
265340

266341
it('should not reorder nodes if no order-by is provided', function() {

0 commit comments

Comments
 (0)