diff --git a/index.html b/index.html index 99049f6e4..2e3d3847e 100644 --- a/index.html +++ b/index.html @@ -2358,7 +2358,8 @@

only items selectable (please note structure of treebranch)

- + + diff --git a/index.js b/index.js index 4c4173dcf..4108254d0 100644 --- a/index.js +++ b/index.js @@ -992,6 +992,10 @@ myTreeInit(); myTreeInit(); }); + $('#btnTreeClearSelected').click(function () { + log('Items/folders cleared: ', $('#myTree1').tree('deselectAll') ); + }); + $('#btnTreeDiscloseVisible').click(function () { $('#myTree1').tree('discloseVisible'); }); diff --git a/js/tree.js b/js/tree.js index 99ccbeb95..d73a7368a 100644 --- a/js/tree.js +++ b/js/tree.js @@ -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 @@ -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 }); }, @@ -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() { @@ -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) { diff --git a/test/markup/tree-markup.html b/test/markup/tree-markup.html index d8330cb3c..cdfaaaf60 100644 --- a/test/markup/tree-markup.html +++ b/test/markup/tree-markup.html @@ -66,6 +66,13 @@ + +
diff --git a/test/tree-test.js b/test/tree-test.js index bd4d1740e..e51e8c619 100644 --- a/test/tree-test.js +++ b/test/tree-test.js @@ -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 }); @@ -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 () {