fuelux.tree.js 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401
  1. /*
  2. * Fuel UX Tree
  3. * https://github.com/ExactTarget/fuelux
  4. *
  5. * Copyright (c) 2014 ExactTarget
  6. * Licensed under the BSD New license.
  7. */
  8. // -- BEGIN UMD WRAPPER PREFACE --
  9. // For more information on UMD visit:
  10. // https://github.com/umdjs/umd/blob/master/jqueryPlugin.js
  11. (function (factory) {
  12. if (typeof define === 'function' && define.amd) {
  13. // if AMD loader is available, register as an anonymous module.
  14. define(['jquery'], factory);
  15. } else {
  16. // OR use browser globals if AMD is not present
  17. factory(jQuery);
  18. }
  19. }(function ($) {
  20. // -- END UMD WRAPPER PREFACE --
  21. // -- BEGIN MODULE CODE HERE --
  22. var old = $.fn.tree;
  23. // TREE CONSTRUCTOR AND PROTOTYPE
  24. var Tree = function (element, options) {
  25. this.$element = $(element);
  26. this.options = $.extend({}, $.fn.tree.defaults, options);
  27. this.$element.on('click.fu.tree', '.tree-item', $.proxy( function(ev) { this.selectItem(ev.currentTarget); } ,this));
  28. //this.$element.on('click.fu.tree', '.tree-branch-name', $.proxy( function(ev) { this.openFolder(ev.currentTarget); }, this));
  29. this.$element.on('click.fu.tree', '.tree-branch-header', $.proxy( function(ev) { this.openFolder(ev.currentTarget); }, this));
  30. //ACE
  31. //ACE
  32. /**
  33. if( this.options.folderSelect ){
  34. this.$element.off('click.fu.tree', '.tree-branch-header');
  35. this.$element.on('click.fu.tree', '.icon-caret', $.proxy( function(ev) { this.openFolder($(ev.currentTarget).parent()); }, this));
  36. this.$element.on('click.fu.tree', '.tree-branch-header', $.proxy( function(ev) { this.selectFolder($(ev.currentTarget)); }, this));
  37. }
  38. */
  39. this.render();
  40. };
  41. Tree.prototype = {
  42. constructor: Tree,
  43. destroy: function() {
  44. // any external bindings [none]
  45. // empty elements to return to original markup
  46. this.$element.find("li:not([data-template])").remove();
  47. this.$element.remove();
  48. // returns string of markup
  49. return this.$element[0].outerHTML;
  50. },
  51. render: function () {
  52. this.populate(this.$element);
  53. },
  54. populate: function ($el) {
  55. var self = this;
  56. var $parent = ($el.hasClass('tree')) ? $el : $el.parent();
  57. var loader = $parent.find('.tree-loader:eq(0)');
  58. var treeData = $parent.data();
  59. loader.removeClass('hide');
  60. this.options.dataSource( treeData ? treeData : {} , function (items) {
  61. loader.addClass('hide');
  62. $.each( items.data, function(index, value) {
  63. var $entity;
  64. if(value.type === 'folder') {
  65. $entity = self.$element.find('[data-template=treebranch]:eq(0)').clone().removeClass('hide').removeAttr('data-template');
  66. $entity.data(value);
  67. $entity.find('.tree-branch-name > .tree-label').html(value.text || value.name);
  68. //ACE
  69. var header = $entity.find('.tree-branch-header');
  70. if('icon-class' in value)
  71. header.find('i').addClass(value['icon-class']);
  72. if('additionalParameters' in value
  73. && 'item-selected' in value.additionalParameters
  74. && value.additionalParameters['item-selected'] == true) {
  75. setTimeout(function(){header.trigger('click')}, 0);
  76. }
  77. } else if (value.type === 'item') {
  78. $entity = self.$element.find('[data-template=treeitem]:eq(0)').clone().removeClass('hide').removeAttr('data-template');
  79. $entity.find('.tree-item-name > .tree-label').html(value.text || value.name);
  80. $entity.data(value);
  81. //ACE
  82. if('additionalParameters' in value
  83. && 'item-selected' in value.additionalParameters
  84. && value.additionalParameters['item-selected'] == true) {
  85. $entity.addClass ('tree-selected');
  86. $entity.find('i').removeClass(self.options['unselected-icon']).addClass(self.options['selected-icon']);
  87. //$entity.closest('.tree-folder-content').show();
  88. }
  89. }
  90. // Decorate $entity with data or other attributes making the
  91. // element easily accessable with libraries like jQuery.
  92. //
  93. // Values are contained within the object returned
  94. // for folders and items as attr:
  95. //
  96. // {
  97. // name: "An Item",
  98. // type: 'item',
  99. // attr = {
  100. // 'classes': 'required-item red-text',
  101. // 'data-parent': parentId,
  102. // 'guid': guid,
  103. // 'id': guid
  104. // }
  105. // };
  106. //
  107. // the "name" attribute is also supported but is deprecated for "text"
  108. // add attributes to tree-branch or tree-item
  109. var attr = value['attr'] || value.dataAttributes || [];
  110. $.each(attr, function(key, value) {
  111. switch (key) {
  112. case 'cssClass':
  113. case 'class':
  114. case 'className':
  115. $entity.addClass(value);
  116. break;
  117. // allow custom icons
  118. case 'data-icon':
  119. $entity.find('.icon-item').removeClass().addClass('icon-item ' + value);
  120. $entity.attr(key, value);
  121. break;
  122. // ARIA support
  123. case 'id':
  124. $entity.attr(key, value);
  125. $entity.attr('aria-labelledby', value + '-label');
  126. $entity.find('.tree-branch-name > .tree-label').attr('id', value + '-label');
  127. break;
  128. // id, style, data-*
  129. default:
  130. $entity.attr(key, value);
  131. break;
  132. }
  133. });
  134. // add child nodes
  135. if($el.hasClass('tree-branch-header')) {
  136. $parent.find('.tree-branch-children:eq(0)').append($entity);
  137. } else {
  138. $el.append($entity);
  139. }
  140. });
  141. // return newly populated folder
  142. self.$element.trigger('loaded.fu.tree', $parent);
  143. });
  144. },
  145. selectItem: function (el) {
  146. if(this.options['selectable'] == false) return;//ACE
  147. var $el = $(el);
  148. var selData = $el.data();
  149. var $all = this.$element.find('.tree-selected');
  150. var data = [];
  151. var $icon = $el.find('.icon-item');
  152. if (this.options.multiSelect) {
  153. $.each($all, function(index, value) {
  154. var $val = $(value);
  155. if($val[0] !== $el[0]) {
  156. data.push( $(value).data() );
  157. }
  158. });
  159. } else if ($all[0] !== $el[0]) {
  160. //
  161. $all.removeClass('tree-selected')
  162. .find('i').removeClass(this.options['selected-icon']).addClass(this.options['unselected-icon']);//ACE
  163. data.push(selData);
  164. }
  165. var eventType = 'selected';
  166. if($el.hasClass('tree-selected')) {
  167. eventType = 'deselected';
  168. $el.removeClass('tree-selected');
  169. if($icon.hasClass(this.options['selected-icon']) || $icon.hasClass(this.options['unselected-icon']) ) {
  170. $icon.removeClass(this.options['selected-icon']).addClass(this.options['unselected-icon']);//ACE
  171. }
  172. } else {
  173. $el.addClass ('tree-selected');
  174. // add tree dot back in
  175. if($icon.hasClass(this.options['selected-icon']) || $icon.hasClass(this.options['unselected-icon']) ) {
  176. $icon.removeClass(this.options['unselected-icon']).addClass(this.options['selected-icon']);//ACE
  177. }
  178. if (this.options.multiSelect) {
  179. data.push( selData );
  180. }
  181. }
  182. this.$element.trigger(eventType + '.fu.tree', {target: selData, selected: data});
  183. // Return new list of selected items, the item
  184. // clicked, and the type of event:
  185. $el.trigger('updated.fu.tree', {
  186. selected: data,
  187. item: $el,
  188. eventType: eventType
  189. });
  190. },
  191. openFolder: function (el) {
  192. var $el = $(el); // tree-branch-name
  193. var $branch;
  194. var $treeFolderContent;
  195. var $treeFolderContentFirstChild;
  196. // if item select only
  197. //ACE
  198. /**
  199. if (!this.options.folderSelect) {
  200. $el = $(el).parent(); // tree-branch, if tree-branch-name clicked
  201. }
  202. */
  203. $branch = $el.closest('.tree-branch'); // tree branch
  204. $treeFolderContent = $branch.find('.tree-branch-children');
  205. $treeFolderContentFirstChild = $treeFolderContent.eq(0);
  206. // manipulate branch/folder
  207. var eventType, classToTarget, classToAdd;
  208. if ($el.find('.'+$.trim(this.options['close-icon'].replace(/(\s+)/g, '.'))).length) {//ACE
  209. eventType = 'opened';
  210. classToTarget = this.options['close-icon'];//ACE
  211. classToAdd = this.options['open-icon'];//ACE
  212. $branch.addClass('tree-open');
  213. $branch.attr('aria-expanded', 'true');
  214. $treeFolderContentFirstChild.removeClass('hide');
  215. if (!$treeFolderContent.children().length) {
  216. this.populate($treeFolderContent);
  217. }
  218. } else if($el.find('.'+$.trim(this.options['open-icon'].replace(/(\s+)/g, '.')))) {
  219. eventType = 'closed';
  220. classToTarget = this.options['open-icon'];//ACE
  221. classToAdd = this.options['close-icon'];//ACE
  222. $branch.removeClass('tree-open');
  223. $branch.attr('aria-expanded', 'false');
  224. $treeFolderContentFirstChild.addClass('hide');
  225. // remove if no cache
  226. if (!this.options.cacheItems) {
  227. $treeFolderContentFirstChild.empty();
  228. }
  229. }
  230. $branch.find('> .tree-branch-header .icon-folder').eq(0)
  231. //.removeClass(this.options['close-icon'] + ' ' + this.options['open-icon'])
  232. .removeClass(classToTarget)//ACE
  233. .addClass(classToAdd);
  234. this.$element.trigger(eventType + '.fu.tree', $branch.data());
  235. },
  236. selectFolder: function (clickedElement) {
  237. var $clickedElement = $(clickedElement);
  238. var $clickedBranch = $clickedElement.closest('.tree-branch');
  239. var $selectedBranch = this.$element.find('.tree-branch.tree-selected');
  240. var clickedData = $clickedBranch.data();
  241. var selectedData = [];
  242. var eventType = 'selected';
  243. // select clicked item
  244. if($clickedBranch.hasClass('tree-selected')) {
  245. eventType = 'deselected';
  246. $clickedBranch.removeClass('tree-selected');
  247. } else {
  248. $clickedBranch.addClass('tree-selected');
  249. }
  250. if (this.options.multiSelect) {
  251. // get currently selected
  252. $selectedBranch = this.$element.find('.tree-branch.tree-selected');
  253. $.each($selectedBranch, function(index, value) {
  254. var $value = $(value);
  255. if($value[0] !== $clickedElement[0]) {
  256. selectedData.push( $(value).data() );
  257. }
  258. });
  259. } else if ($selectedBranch[0] !== $clickedElement[0]) {
  260. $selectedBranch.removeClass('tree-selected');
  261. selectedData.push(clickedData);
  262. }
  263. this.$element.trigger(eventType + '.fu.tree', {target: clickedData, selected: selectedData});
  264. // Return new list of selected items, the item
  265. // clicked, and the type of event:
  266. $clickedElement.trigger('updated.fu.tree', {
  267. selected: selectedData,
  268. item: $clickedElement,
  269. eventType: eventType
  270. });
  271. },
  272. selectedItems: function () {
  273. var $sel = this.$element.find('.tree-selected');
  274. var data = [];
  275. $.each($sel, function (index, value) {
  276. data.push($(value).data());
  277. });
  278. return data;
  279. },
  280. // collapses open folders
  281. collapse: function () {
  282. var cacheItems = this.options.cacheItems;
  283. // find open folders
  284. this.$element.find('.'+$.trim(this.options['open-icon'].replace(/(\s+)/g, '.'))).each(function () {//ACE
  285. // update icon class
  286. var $this = $(this)
  287. .removeClass(this.options['open-icon'] + ' ' + this.options['close-icon'])
  288. .addClass(this.options['close-icon']);
  289. // "close" or empty folder contents
  290. var $parent = $this.parent().parent();
  291. var $folder = $parent.children('.tree-branch-children');
  292. $folder.addClass('hide');
  293. if (!cacheItems) {
  294. $folder.empty();
  295. }
  296. });
  297. }
  298. };
  299. // TREE PLUGIN DEFINITION
  300. $.fn.tree = function (option) {
  301. var args = Array.prototype.slice.call( arguments, 1 );
  302. var methodReturn;
  303. var $set = this.each(function () {
  304. var $this = $( this );
  305. var data = $this.data( 'fu.tree' );
  306. var options = typeof option === 'object' && option;
  307. if( !data ) $this.data('fu.tree', (data = new Tree( this, options ) ) );
  308. if( typeof option === 'string' ) methodReturn = data[ option ].apply( data, args );
  309. });
  310. return ( methodReturn === undefined ) ? $set : methodReturn;
  311. };
  312. $.fn.tree.defaults = {
  313. dataSource: function(options, callback){},
  314. multiSelect: false,
  315. cacheItems: true,
  316. folderSelect: false//ACE
  317. };
  318. $.fn.tree.Constructor = Tree;
  319. $.fn.tree.noConflict = function () {
  320. $.fn.tree = old;
  321. return this;
  322. };
  323. // NO DATA-API DUE TO NEED OF DATA-SOURCE
  324. // -- BEGIN UMD WRAPPER AFTERWORD --
  325. }));
  326. // -- END UMD WRAPPER AFTERWORD --