Skip to content
This repository has been archived by the owner on Feb 12, 2022. It is now read-only.

Commit

Permalink
Merge pull request #1312 from swilliamset/cherry-pick-pr-1283
Browse files Browse the repository at this point in the history
Tree select bug for 3.7.x branch
  • Loading branch information
interactivellama committed May 22, 2015
2 parents f32e292 + e54a9dc commit f9e6964
Show file tree
Hide file tree
Showing 5 changed files with 141 additions and 96 deletions.
3 changes: 2 additions & 1 deletion index.html
Original file line number Diff line number Diff line change
Expand Up @@ -2354,7 +2354,8 @@ <h3>only items selectable (please note structure of treebranch)</h3>

</div>
<div class="btn-panel">
<button type="button" class="btn btn-default" id="btnTreeDestroy">destroy and append</button>
<button type="button" class="btn btn-default" id="btnTreeDestroy">destroy and append</button>
<button type="button" class="btn btn-default" id="btnTreeClearSelected">clear all selected</button>
<button type="button" class="btn btn-default" id="btnTreeDiscloseVisible">disclose visible</button>
<button type="button" class="btn btn-default" id="btnTreeDiscloseAll">disclose all</button>
<button type="button" class="btn btn-default" id="btnTreeCloseAll">close all</button>
Expand Down
4 changes: 4 additions & 0 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -976,6 +976,10 @@ myTreeInit();
myTreeInit();
});

$('#btnTreeClearSelected').click(function () {
log('Items/folders cleared: ', $('#myTree1').tree('deselectAll') );
});

$('#btnTreeDiscloseVisible').click(function () {
$('#myTree1').tree('discloseVisible');
});
Expand Down
198 changes: 108 additions & 90 deletions js/tree.js
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,16 @@
Tree.prototype = {
constructor: Tree,

deselectAll: function deselectAll(nodes) {
// clear all child tree nodes and style as deselected
nodes = nodes || this.$element;
var $selectedElements = $(nodes).find('.tree-selected');
$selectedElements.each(function (index, element) {
styleNodeDeselected( $(element), $(element).find( '.glyphicon' ) );
});
return $selectedElements;
},

destroy: function destroy() {
// any external bindings [none]
// empty elements to return to original markup
Expand Down Expand Up @@ -159,59 +169,43 @@
});
},

selectItem: function selectItem(el) {
if (!this.options.itemSelect) return;
var $el = $(el);
var selData = $el.data();
var $all = this.$element.find('.tree-selected');
var data = [];
var $icon = $el.find('.icon-item');

if (this.options.multiSelect) {
$.each($all, function (index, value) {
var $val = $(value);
if ($val[0] !== $el[0]) {
data.push($(value).data());
}
});
} else if ($all[0] !== $el[0]) {
$all.removeClass('tree-selected')
.find('.glyphicon').removeClass('glyphicon-ok').addClass('fueluxicon-bullet');
data.push(selData);
}

var eventType = 'selected';
if ($el.hasClass('tree-selected')) {
eventType = 'deselected';
$el.removeClass('tree-selected');
if ($icon.hasClass('glyphicon-ok') || $icon.hasClass('fueluxicon-bullet')) {
$icon.removeClass('glyphicon-ok').addClass('fueluxicon-bullet');
}

} else {
$el.addClass ('tree-selected');
// add tree dot back in
if ($icon.hasClass('glyphicon-ok') || $icon.hasClass('fueluxicon-bullet')) {
$icon.removeClass('fueluxicon-bullet').addClass('glyphicon-ok');
}
selectTreeNode: function selectItem(clickedElement, nodeType) {
var clicked = {}; // object for clicked element
clicked.$element = $(clickedElement);

if (this.options.multiSelect) {
data.push(selData);
}
var selected = {}; // object for selected elements
selected.$elements = this.$element.find('.tree-selected');
selected.dataForEvent = [];

// determine clicked element and it's icon
if (nodeType === 'folder') {
// make the clicked.$element the container branch
clicked.$element = clicked.$element.closest('.tree-branch');
clicked.$icon = clicked.$element.find('.icon-folder');
}
else {
clicked.$icon = clicked.$element.find('.icon-item');
}
clicked.elementData = clicked.$element.data();

this.$element.trigger(eventType + '.fu.tree', {
target: selData,
selected: data
// the below functions pass objects by copy/reference and use modified object in this function
if ( this.options.multiSelect ) {
multiSelectSyncNodes(this, clicked, selected);
}
else {
singleSelectSyncNodes(this, clicked, selected);
}

// all done with the DOM, now fire events
this.$element.trigger(selected.eventType + '.fu.tree', {
target: clicked.elementData,
selected: selected.dataForEvent
});

// Return new list of selected items, the item
// clicked, and the type of event:
$el.trigger('updated.fu.tree', {
selected: data,
item: $el,
eventType: eventType
clicked.$element.trigger('updated.fu.tree', {
selected: selected.dataForEvent,
item: clicked.$element,
eventType: selected.eventType
});
},

Expand Down Expand Up @@ -270,52 +264,16 @@
}
},

selectFolder: function selectFolder(clickedElement) {
if (!this.options.folderSelect) return;
var $clickedElement = $(clickedElement);
var $clickedBranch = $clickedElement.closest('.tree-branch');
var $selectedBranch = this.$element.find('.tree-branch.tree-selected');
var clickedData = $clickedBranch.data();
var selectedData = [];
var eventType = 'selected';

// select clicked item
if ($clickedBranch.hasClass('tree-selected')) {
eventType = 'deselected';
$clickedBranch.removeClass('tree-selected');
} else {
$clickedBranch.addClass('tree-selected');
selectFolder: function selectFolder(el) {
if (this.options.folderSelect) {
this.selectTreeNode(el, 'folder');
}
},

if (this.options.multiSelect) {
// get currently selected
$selectedBranch = this.$element.find('.tree-branch.tree-selected');

$.each($selectedBranch, function (index, value) {
var $value = $(value);
if ($value[0] !== $clickedElement[0]) {
selectedData.push($(value).data());
}
});

} else if ($selectedBranch[0] !== $clickedElement[0]) {
$selectedBranch.removeClass('tree-selected');

selectedData.push(clickedData);
selectItem: function selectItem(el) {
if (this.options.itemSelect) {
this.selectTreeNode(el, 'item');
}

this.$element.trigger(eventType + '.fu.tree', {
target: clickedData,
selected: selectedData
});

// Return new list of selected items, the item
// clicked, and the type of event:
$clickedElement.trigger('updated.fu.tree', {
selected: selectedData,
item: $clickedElement,
eventType: eventType
});
},

selectedItems: function selectedItems() {
Expand Down Expand Up @@ -465,11 +423,71 @@
}
};


// ALIASES

//alias for collapse for consistency. "Collapse" is an ambiguous term (collapse what? All? One specific branch?)
Tree.prototype.closeAll = Tree.prototype.collapse;
//alias for backwards compatibility because there's no reason not to.
Tree.prototype.openFolder = Tree.prototype.discloseFolder;


// PRIVATE FUNCTIONS

function styleNodeSelected ($element, $icon) {
$element.addClass('tree-selected');
if ( $element.data('type') === 'item' && $icon.hasClass('fueluxicon-bullet') ) {
$icon.removeClass('fueluxicon-bullet').addClass('glyphicon-ok'); // make checkmark
}
}

function styleNodeDeselected ($element, $icon) {
$element.removeClass('tree-selected');
if ( $element.data('type') === 'item' && $icon.hasClass('glyphicon-ok') ) {
$icon.removeClass('glyphicon-ok').addClass('fueluxicon-bullet'); // make bullet
}
}

function multiSelectSyncNodes (self, clicked, selected) {
// search for currently selected and add to selected data list if needed
$.each(selected.$elements, function (index, element) {
var $element = $(element);
if ($element[0] !== clicked.$element[0]) {
selected.dataForEvent.push( $($element).data() );
}
});

if (clicked.$element.hasClass('tree-selected')) {
styleNodeDeselected (clicked.$element, clicked.$icon);
// set event data
selected.eventType = 'deselected';
}
else {
styleNodeSelected(clicked.$element, clicked.$icon);
// set event data
selected.eventType = 'selected';
selected.dataForEvent.push(clicked.elementData);
}
}

function singleSelectSyncNodes(self, clicked, selected) {
// element is not currently selected
if (selected.$elements[0] !== clicked.$element[0]) {
var clearedElements = self.deselectAll(self.$element);
styleNodeSelected(clicked.$element, clicked.$icon);
// set event data
selected.eventType = 'selected';
selected.dataForEvent = [clicked.elementData];
}
else {
styleNodeDeselected(clicked.$element, clicked.$icon);
// set event data
selected.eventType = 'deselected';
selected.dataForEvent = [];
}
}


// TREE PLUGIN DEFINITION

$.fn.tree = function tree(option) {
Expand Down
7 changes: 7 additions & 0 deletions test/markup/tree-markup.html
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,13 @@
<ul class="tree-branch-children"></ul>
<div class="tree-loader" role="alert">Loading...</div>
</li>
<!-- item template -->
<li class="tree-item hidden" data-template="treeitem" role="treeitem">
<button class="tree-item-name">
<span class="glyphicon icon-item fueluxicon-bullet"></span>
<span class="tree-label">Example Item</span>
</button>
</li>
</ul>

</div>
Expand Down
25 changes: 20 additions & 5 deletions test/tree-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -182,12 +182,13 @@ define(function (require) {

$selNode = $tree.find('.tree-branch:eq(1)');
$tree.tree('discloseFolder', $selNode.find('.tree-branch-header'));
equal($selNode.find('.tree-branch-children > li').length, 4, 'Folder has been populated with sub-folders');
equal($selNode.find('.tree-branch-children > li').length, 8, 'Folder has been populated with sub-folders and items');
});

test("Single item/folder selection works as designed", function () {
var $tree = $(html).find('#MyTree');

// multiSelect: false is the default
$tree.tree({
dataSource: this.dataSource
});
Expand All @@ -204,10 +205,24 @@ define(function (require) {
folderSelect: true
});

$tree.tree('selectItem', $tree.find('.tree-branch-name:eq(1)'));
equal($tree.tree('selectedItems').length, 1, 'Return single selected value');
$tree.tree('selectItem', $tree.find('.tree-branch-name:eq(2)'));
equal($tree.tree('selectedItems').length, 1, 'Return new single selected value');
$tree.tree('selectItem', $tree.find('.tree-item:eq(1)'));
equal($tree.tree('selectedItems').length, 1, 'Return single selected item (none previously selected, 1st programatic selection)');

$tree.tree('selectFolder', $tree.find('.tree-branch-name:eq(1)'));
equal($tree.tree('selectedItems').length, 1, 'Return single selected folder (item previously selected, 2nd programatic selection)');

$tree.tree('selectItem', $tree.find('.tree-item:eq(2)'));
equal($tree.tree('selectedItems').length, 1, 'Return single selected item (folder previously selected, 3rd programatic selection)');

$tree.find('.tree-item:eq(1)').click();
equal($tree.tree('selectedItems').length, 1, 'Return single selected item (item previously selected, 1st click selection)');

$tree.find('.tree-branch-name:eq(1)').click();
equal($tree.tree('selectedItems').length, 1, 'Return single selected folder (item previously selected, 2nd click selection)');

$tree.find('.tree-item:eq(2)').click();
equal($tree.tree('selectedItems').length, 1, 'Return single selected item (folder previously selected, 3rd click selection)');

});

test("Multiple item/folder selection works as designed", function () {
Expand Down

0 comments on commit f9e6964

Please sign in to comment.