bootstrap-treeview.js 37 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352
  1. /* =========================================================
  2. * bootstrap-treeview.js v1.2.0
  3. * =========================================================
  4. * Copyright 2013 Jonathan Miles
  5. * Project URL : http://www.jondmiles.com/bootstrap-treeview
  6. *
  7. * Licensed under the Apache License, Version 2.0 (the "License");
  8. * you may not use this file except in compliance with the License.
  9. * You may obtain a copy of the License at
  10. *
  11. * http://www.apache.org/licenses/LICENSE-2.0
  12. *
  13. * Unless required by applicable law or agreed to in writing, software
  14. * distributed under the License is distributed on an "AS IS" BASIS,
  15. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  16. * See the License for the specific language governing permissions and
  17. * limitations under the License.
  18. * ========================================================= */
  19. ;(function ($, window, document, undefined) {
  20. /*global jQuery, console*/
  21. 'use strict';
  22. var pluginName = 'treeview';
  23. var _default = {};
  24. _default.settings = {
  25. injectStyle: true,
  26. levels: 5,
  27. expandIcon: 'glyphicon glyphicon-plus',
  28. collapseIcon: 'glyphicon glyphicon-minus',
  29. emptyIcon: '',
  30. nodeIcon: '',
  31. selectedIcon: '',
  32. checkedIcon: 'glyphicon glyphicon-check',
  33. uncheckedIcon: 'glyphicon glyphicon-unchecked',
  34. color: undefined, // '#000000',
  35. backColor: undefined, // '#FFFFFF',
  36. borderColor: undefined, // '#dddddd',
  37. onhoverColor: '#F5F5F5',
  38. selectedColor: '#FFFFFF',
  39. selectedBackColor: '#428bca',
  40. searchResultColor: '#D9534F',
  41. searchResultBackColor: undefined, //'#FFFFFF',
  42. enableLinks: false,
  43. highlightSelected: true,
  44. highlightSearchResults: true,
  45. showBorder: true,
  46. showIcon: true,
  47. showCheckbox: false,
  48. showTags: false,
  49. multiSelect: false,
  50. showDel : true,
  51. // Event handlers
  52. onNodeChecked: undefined,
  53. onNodeCollapsed: undefined,
  54. onNodeDisabled: undefined,
  55. onNodeEnabled: undefined,
  56. onNodeExpanded: undefined,
  57. onNodeSelected: undefined,
  58. onNodeUnchecked: undefined,
  59. onNodeUnselected: undefined,
  60. onSearchComplete: undefined,
  61. onSearchCleared: undefined,
  62. onNodeRemove : undefined
  63. };
  64. _default.options = {
  65. silent: false,
  66. ignoreChildren: false
  67. };
  68. _default.searchOptions = {
  69. ignoreCase: true,
  70. exactMatch: false,
  71. revealResults: true
  72. };
  73. var Tree = function (element, options) {
  74. this.$element = $(element);
  75. this.elementId = element.id;
  76. this.styleId = this.elementId + '-style';
  77. this.init(options);
  78. return {
  79. // Options (public access)
  80. options: this.options,
  81. // Initialize / destroy methods
  82. init: $.proxy(this.init, this),
  83. remove: $.proxy(this.remove, this),
  84. // Get methods
  85. getNode: $.proxy(this.getNode, this),
  86. getParent: $.proxy(this.getParent, this),
  87. getSiblings: $.proxy(this.getSiblings, this),
  88. getSelected: $.proxy(this.getSelected, this),
  89. getUnselected: $.proxy(this.getUnselected, this),
  90. getExpanded: $.proxy(this.getExpanded, this),
  91. getCollapsed: $.proxy(this.getCollapsed, this),
  92. getChecked: $.proxy(this.getChecked, this),
  93. getUnchecked: $.proxy(this.getUnchecked, this),
  94. getDisabled: $.proxy(this.getDisabled, this),
  95. getEnabled: $.proxy(this.getEnabled, this),
  96. // Select methods
  97. selectNode: $.proxy(this.selectNode, this),
  98. unselectNode: $.proxy(this.unselectNode, this),
  99. toggleNodeSelected: $.proxy(this.toggleNodeSelected, this),
  100. // Expand / collapse methods
  101. collapseAll: $.proxy(this.collapseAll, this),
  102. collapseNode: $.proxy(this.collapseNode, this),
  103. expandAll: $.proxy(this.expandAll, this),
  104. expandNode: $.proxy(this.expandNode, this),
  105. toggleNodeExpanded: $.proxy(this.toggleNodeExpanded, this),
  106. revealNode: $.proxy(this.revealNode, this),
  107. // Expand / collapse methods
  108. checkAll: $.proxy(this.checkAll, this),
  109. checkNode: $.proxy(this.checkNode, this),
  110. uncheckAll: $.proxy(this.uncheckAll, this),
  111. uncheckNode: $.proxy(this.uncheckNode, this),
  112. toggleNodeChecked: $.proxy(this.toggleNodeChecked, this),
  113. // Disable / enable methods
  114. disableAll: $.proxy(this.disableAll, this),
  115. disableNode: $.proxy(this.disableNode, this),
  116. enableAll: $.proxy(this.enableAll, this),
  117. enableNode: $.proxy(this.enableNode, this),
  118. toggleNodeDisabled: $.proxy(this.toggleNodeDisabled, this),
  119. // Search methods
  120. search: $.proxy(this.search, this),
  121. clearSearch: $.proxy(this.clearSearch, this),
  122. addNode: $.proxy(this.addNode, this),
  123. removeNode: $.proxy(this.removeNode, this)
  124. };
  125. };
  126. Tree.prototype.init = function (options) {
  127. this.tree = [];
  128. this.nodes = [];
  129. if (options.data) {
  130. if (typeof options.data === 'string') {
  131. options.data = $.parseJSON(options.data);
  132. }
  133. this.tree = $.extend(true, [], options.data);
  134. delete options.data;
  135. }
  136. this.options = $.extend({}, _default.settings, options);
  137. this.destroy();
  138. this.subscribeEvents();
  139. this.setInitialStates({ nodes: this.tree }, 0);
  140. this.render();
  141. };
  142. Tree.prototype.remove = function () {
  143. this.destroy();
  144. $.removeData(this, pluginName);
  145. $('#' + this.styleId).remove();
  146. };
  147. Tree.prototype.destroy = function () {
  148. if (!this.initialized) return;
  149. this.$wrapper.remove();
  150. this.$wrapper = null;
  151. // Switch off events
  152. this.unsubscribeEvents();
  153. // Reset this.initialized flag
  154. this.initialized = false;
  155. };
  156. Tree.prototype.unsubscribeEvents = function () {
  157. this.$element.off('click');
  158. this.$element.off('nodeChecked');
  159. this.$element.off('nodeCollapsed');
  160. this.$element.off('nodeDisabled');
  161. this.$element.off('nodeEnabled');
  162. this.$element.off('nodeExpanded');
  163. this.$element.off('nodeSelected');
  164. this.$element.off('nodeUnchecked');
  165. this.$element.off('nodeUnselected');
  166. this.$element.off('searchComplete');
  167. this.$element.off('searchCleared');
  168. this.$element.off('nodeRemove');
  169. };
  170. Tree.prototype.subscribeEvents = function () {
  171. this.unsubscribeEvents();
  172. this.$element.on('click', $.proxy(this.clickHandler, this));
  173. if (typeof (this.options.onNodeChecked) === 'function') {
  174. this.$element.on('nodeChecked', this.options.onNodeChecked);
  175. }
  176. if (typeof (this.options.onNodeCollapsed) === 'function') {
  177. this.$element.on('nodeCollapsed', this.options.onNodeCollapsed);
  178. }
  179. if (typeof (this.options.onNodeDisabled) === 'function') {
  180. this.$element.on('nodeDisabled', this.options.onNodeDisabled);
  181. }
  182. if (typeof (this.options.onNodeEnabled) === 'function') {
  183. this.$element.on('nodeEnabled', this.options.onNodeEnabled);
  184. }
  185. if (typeof (this.options.onNodeExpanded) === 'function') {
  186. this.$element.on('nodeExpanded', this.options.onNodeExpanded);
  187. }
  188. if (typeof (this.options.onNodeSelected) === 'function') {
  189. this.$element.on('nodeSelected', this.options.onNodeSelected);
  190. }
  191. if (typeof (this.options.onNodeUnchecked) === 'function') {
  192. this.$element.on('nodeUnchecked', this.options.onNodeUnchecked);
  193. }
  194. if (typeof (this.options.onNodeUnselected) === 'function') {
  195. this.$element.on('nodeUnselected', this.options.onNodeUnselected);
  196. }
  197. if (typeof (this.options.onSearchComplete) === 'function') {
  198. this.$element.on('searchComplete', this.options.onSearchComplete);
  199. }
  200. if (typeof (this.options.onSearchCleared) === 'function') {
  201. this.$element.on('searchCleared', this.options.onSearchCleared);
  202. }
  203. if(typeof (this.options.onNodeRemove) === 'function') {
  204. this.$element.on('nodeRemove',this.options.onNodeRemove);
  205. }
  206. if(typeof (this.options.onNodeDbClk) === 'function') {
  207. this.$element.on('nodeDbClk',this.options.onNodeDbClk);
  208. }
  209. };
  210. /*
  211. Recurse the tree structure and ensure all nodes have
  212. valid initial states. User defined states will be preserved.
  213. For performance we also take this opportunity to
  214. index nodes in a flattened structure
  215. */
  216. Tree.prototype.setInitialStates = function (node, level) {
  217. if (!node.nodes) return;
  218. level += 1;
  219. var parent = node;
  220. var _this = this;
  221. $.each(node.nodes, function checkStates(index, node) {
  222. // nodeId : unique, incremental identifier
  223. node.nodeId = _this.nodes.length;
  224. // parentId : transversing up the tree
  225. node.parentId = parent.nodeId;
  226. // if not provided set selectable default value
  227. if (!node.hasOwnProperty('selectable')) {
  228. node.selectable = true;
  229. }
  230. // where provided we should preserve states
  231. node.state = node.state || {};
  232. // set checked state; unless set always false
  233. if (!node.state.hasOwnProperty('checked')) {
  234. node.state.checked = true;
  235. }
  236. // set enabled state; unless set always false
  237. if (!node.state.hasOwnProperty('disabled')) {
  238. node.state.disabled = false;
  239. }
  240. // set expanded state; if not provided based on levels
  241. if (!node.state.hasOwnProperty('expanded')) {
  242. if (!node.state.disabled &&
  243. (level < _this.options.levels) &&
  244. (node.nodes && node.nodes.length > 0)) {
  245. node.state.expanded = true;
  246. }
  247. else {
  248. node.state.expanded = false;
  249. }
  250. }
  251. else{
  252. if (!node.state.disabled &&
  253. (level < _this.options.levels) &&
  254. (node.nodes && node.nodes.length > 0)) {
  255. node.state.expanded = true;
  256. }
  257. else {
  258. node.state.expanded = false;
  259. }
  260. }
  261. // set selected state; unless set always false
  262. if (!node.state.hasOwnProperty('selected')) {
  263. node.state.selected = false;
  264. }
  265. // index nodes in a flattened structure for use later
  266. _this.nodes.push(node);
  267. // recurse child nodes and transverse the tree
  268. if (node.nodes && node.nodes.length > 0) {
  269. _this.setInitialStates(node, level);
  270. }
  271. });
  272. };
  273. Tree.prototype.clickHandler = function (event) {
  274. if (!this.options.enableLinks) event.preventDefault();
  275. var target = $(event.target);
  276. var node = this.findNode(target);
  277. if (!node || node.state.disabled) return;
  278. //checkbox
  279. var classList = target.attr('class') ? target.attr('class').split(' ') : [];
  280. if ((classList.indexOf('expand-icon') !== -1)) {
  281. this.toggleExpandedState(node, _default.options);
  282. this.render();
  283. }
  284. else if ((classList.indexOf('check-icon') !== -1)) {
  285. //target.find('input').prop('checked',false);
  286. var chk = target.prev();
  287. if(chk && chk[0]){
  288. chk[0].checked = !chk[0].checked;
  289. }
  290. this.toggleCheckedState(node, _default.options);
  291. //this.render();
  292. }
  293. else if(classList.indexOf('fui-cross') !== -1){
  294. this.$element.trigger('nodeRemove', $.extend(true, {}, node));
  295. }
  296. else {
  297. if (node.selectable) {
  298. this.toggleSelectedState(node, _default.options);
  299. } else {
  300. this.toggleExpandedState(node, _default.options);
  301. }
  302. this.render();
  303. }
  304. };
  305. // Looks up the DOM for the closest parent list item to retrieve the
  306. // data attribute nodeid, which is used to lookup the node in the flattened structure.
  307. Tree.prototype.findNode = function (target) {
  308. var nodeId = target.closest('li.list-group-item').attr('data-nodeid');
  309. var node = this.nodes[nodeId];
  310. if (!node) {
  311. console.log('Error: node does not exist');
  312. }
  313. return node;
  314. };
  315. Tree.prototype.toggleExpandedState = function (node, options) {
  316. if (!node) return;
  317. this.setExpandedState(node, !node.state.expanded, options);
  318. };
  319. Tree.prototype.setExpandedState = function (node, state, options) {
  320. if (state === node.state.expanded) return;
  321. if (state && node.nodes) {
  322. // Expand a node
  323. node.state.expanded = true;
  324. if (!options.silent) {
  325. this.$element.trigger('nodeExpanded', $.extend(true, {}, node));
  326. }
  327. }
  328. else if (!state) {
  329. // Collapse a node
  330. node.state.expanded = false;
  331. if (!options.silent) {
  332. this.$element.trigger('nodeCollapsed', $.extend(true, {}, node));
  333. }
  334. // Collapse child nodes
  335. if (node.nodes && !options.ignoreChildren) {
  336. $.each(node.nodes, $.proxy(function (index, node) {
  337. this.setExpandedState(node, false, options);
  338. }, this));
  339. }
  340. }
  341. };
  342. Tree.prototype.toggleSelectedState = function (node, options) {
  343. if (!node) return;
  344. this.setSelectedState(node, !node.state.selected, options);
  345. };
  346. Tree.prototype.setSelectedState = function (node, state, options) {
  347. if (state === node.state.selected) return;
  348. if (state) {
  349. // If multiSelect false, unselect previously selected
  350. if (!this.options.multiSelect) {
  351. $.each(this.findNodes('true', 'g', 'state.selected'), $.proxy(function (index, node) {
  352. this.setSelectedState(node, false, options);
  353. }, this));
  354. }
  355. // Continue selecting node
  356. node.state.selected = true;
  357. if (!options.silent) {
  358. this.$element.trigger('nodeSelected', $.extend(true, {}, node));
  359. }
  360. }
  361. else {
  362. // Unselect node
  363. node.state.selected = false;
  364. if (!options.silent) {
  365. this.$element.trigger('nodeUnselected', $.extend(true, {}, node));
  366. }
  367. }
  368. };
  369. Tree.prototype.toggleCheckedState = function (node, options) {
  370. if (!node) return;
  371. this.setCheckedState(node, !node.state.checked, options);
  372. };
  373. Tree.prototype.setCheckedState = function (node, state, options) {
  374. if (state === node.state.checked) return;
  375. if (state) {
  376. // Check node
  377. node.state.checked = true;
  378. this.render();
  379. if (!options.silent) {
  380. this.$element.trigger('nodeChecked', $.extend(true, {}, node));
  381. }
  382. }
  383. else {
  384. // Uncheck node
  385. node.state.checked = false;
  386. this.render();
  387. if (!options.silent) {
  388. this.$element.trigger('nodeUnchecked', $.extend(true, {}, node));
  389. }
  390. }
  391. };
  392. Tree.prototype.setDisabledState = function (node, state, options) {
  393. if (state === node.state.disabled) return;
  394. if (state) {
  395. // Disable node
  396. node.state.disabled = true;
  397. // Disable all other states
  398. this.setExpandedState(node, false, options);
  399. this.setSelectedState(node, false, options);
  400. this.setCheckedState(node, false, options);
  401. if (!options.silent) {
  402. this.$element.trigger('nodeDisabled', $.extend(true, {}, node));
  403. }
  404. }
  405. else {
  406. // Enabled node
  407. node.state.disabled = false;
  408. if (!options.silent) {
  409. this.$element.trigger('nodeEnabled', $.extend(true, {}, node));
  410. }
  411. }
  412. };
  413. Tree.prototype.render = function () {
  414. if (!this.initialized) {
  415. // Setup first time only components
  416. this.$element.addClass(pluginName);
  417. this.$wrapper = $(this.template.list);
  418. this.injectStyle();
  419. this.initialized = true;
  420. }
  421. this.$element.empty().append(this.$wrapper.empty());
  422. // Build tree
  423. this.buildTree(this.tree, 0);
  424. };
  425. // Starting from the root node, and recursing down the
  426. // structure we build the tree one node at a time
  427. Tree.prototype.buildTree = function (nodes, level) {
  428. if (!nodes) return;
  429. level += 1;
  430. var _this = this;
  431. $.each(nodes, function addNodes(id, node) {
  432. node.level = level;
  433. var treeItem = $(_this.template.item)
  434. .addClass('node-' + _this.elementId)
  435. .addClass(node.state.checked ? 'node-checked' : 'node-unchecked')
  436. .addClass(node.state.disabled ? 'node-disabled': '')
  437. .addClass(node.state.selected ? 'node-selected' : '')
  438. .addClass(node.searchResult ? 'search-result' : '')
  439. .attr('data-nodeid', node.nodeId)
  440. .attr('style', _this.buildStyleOverride(node));
  441. // Add indent/spacer to mimic tree structure
  442. for (var i = 0; i < (level - 1); i++) {
  443. treeItem.append(_this.template.indent);
  444. }
  445. // Add expand, collapse or empty spacer icons
  446. var classList = [];
  447. if (node.nodes && node.nodes.length > 0) {
  448. classList.push('expand-icon');
  449. if (node.state.expanded) {
  450. classList.push(_this.options.collapseIcon);
  451. }
  452. else {
  453. classList.push(_this.options.expandIcon);
  454. }
  455. }
  456. else {
  457. classList.push(_this.options.emptyIcon);
  458. }
  459. treeItem
  460. .append($(_this.template.icon)
  461. .addClass(classList.join(' '))
  462. );
  463. // Add node icon
  464. if (_this.options.showIcon) {
  465. var classList = ['node-icon'];
  466. classList.push(node.icon || _this.options.nodeIcon);
  467. if (node.state.selected) {
  468. classList.pop();
  469. classList.push(node.selectedIcon || _this.options.selectedIcon ||
  470. node.icon || _this.options.nodeIcon);
  471. }
  472. treeItem
  473. .append($(_this.template.icon)
  474. .addClass(classList.join(' '))
  475. );
  476. }
  477. // Add check / unchecked icon
  478. if (_this.options.showCheckbox) {
  479. var classList = ['check-icon'];
  480. if (node.state.checked) {
  481. classList.push(_this.options.checkedIcon);
  482. }
  483. else {
  484. classList.push(_this.options.uncheckedIcon);
  485. }
  486. /*treeItem
  487. .append($(_this.template.icon)
  488. .addClass(classList.join(' '))
  489. );*/
  490. treeItem.append($(_this.template.chk));
  491. }
  492. // Add text
  493. if (_this.options.enableLinks) {
  494. // Add hyperlink
  495. treeItem
  496. .append($(_this.template.link)
  497. .attr('href', node.href)
  498. .append(node.text)
  499. );
  500. }
  501. else {
  502. // otherwise just text
  503. //treeItem.append(node.text);
  504. treeItem.append($('<span style="white-space:normal;display:inline-block;width:160px;word-break:break-word;">' + node.text + '</span>'));
  505. }
  506. // Add tags as badges
  507. if (_this.options.showTags && node.tags) {
  508. $.each(node.tags, function addTag(id, tag) {
  509. treeItem
  510. .append($(_this.template.badge)
  511. .append(tag)
  512. );
  513. });
  514. }
  515. /* // 范例不提供删除图层功能
  516. if(_this.options.showDel && node.showDel){
  517. treeItem.append($(_this.template.del));
  518. }*/
  519. // Add item to the tree
  520. _this.$wrapper.append(treeItem);
  521. // Recursively add child ndoes
  522. if (node.nodes && node.state.expanded && !node.state.disabled) {
  523. return _this.buildTree(node.nodes, level);
  524. }
  525. });
  526. };
  527. Tree.prototype.addNode = function (parentNode, childNode) {
  528. if(!parentNode.nodes){
  529. parentNode.nodes = [];
  530. }
  531. parentNode.nodes.push(childNode);
  532. this.nodes.length = 0;
  533. this.setInitialStates({ nodes: this.tree }, 0);
  534. this.render();
  535. return childNode;
  536. };
  537. Tree.prototype.removeNode2 = function (node) {
  538. var pId = node.parentId;
  539. if(pId){
  540. var pNode = this.getNode(pId);
  541. if(pNode){
  542. var index = -1;
  543. var i = pNode.nodes.length;
  544. while(i--){
  545. if(pNode.nodes[i].nodeId === node.nodeId){
  546. index = i;
  547. break;
  548. }
  549. }
  550. if(index !== -1){
  551. pNode.nodes.splice(index,1);
  552. this.setInitialStates({ nodes: this.tree }, 0);
  553. this.render();
  554. }
  555. }
  556. }
  557. };
  558. Tree.prototype.removeNode = function (node) {
  559. var pId = node.parentId;
  560. if(pId !== undefined){
  561. var pNode = this.getNode(pId);
  562. if(pNode){
  563. var index = -1;
  564. var i = pNode.nodes.length;
  565. while(i--){
  566. if(pNode.nodes[i].nodeId === node.nodeId){
  567. index = i;
  568. break;
  569. }
  570. }
  571. if(index !== -1){
  572. pNode.nodes.splice(index,1);
  573. if(pNode.nodes.length == 0 && pNode.level != 1){
  574. this.removeNode(pNode);
  575. }
  576. else{
  577. this.nodes.length = 0;
  578. this.setInitialStates({ nodes: this.tree }, 0);
  579. this.render();
  580. }
  581. }
  582. }
  583. }
  584. };
  585. // Define any node level style override for
  586. // 1. selectedNode
  587. // 2. node|data assigned color overrides
  588. Tree.prototype.buildStyleOverride = function (node) {
  589. if (node.state.disabled) return '';
  590. var color = node.color;
  591. var backColor = node.backColor;
  592. var fontSize = node.fontSize;
  593. if (this.options.highlightSelected && node.state.selected) {
  594. if (this.options.selectedColor) {
  595. color = this.options.selectedColor;
  596. }
  597. if (this.options.selectedBackColor) {
  598. backColor = this.options.selectedBackColor;
  599. }
  600. }
  601. if (this.options.highlightSearchResults && node.searchResult && !node.state.disabled) {
  602. if (this.options.searchResultColor) {
  603. color = this.options.searchResultColor;
  604. }
  605. if (this.options.searchResultBackColor) {
  606. backColor = this.options.searchResultBackColor;
  607. }
  608. }
  609. return 'color:' + color +
  610. ';background-color:' + backColor + ';' + 'font-size:' + fontSize + ';';
  611. };
  612. // Add inline style into head
  613. Tree.prototype.injectStyle = function () {
  614. if (this.options.injectStyle && !document.getElementById(this.styleId)) {
  615. $('<style type="text/css" id="' + this.styleId + '"> ' + this.buildStyle() + ' </style>').appendTo('head');
  616. }
  617. };
  618. // Construct trees style based on user options
  619. Tree.prototype.buildStyle = function () {
  620. var style = '.node-' + this.elementId + '{';
  621. if (this.options.color) {
  622. style += 'color:' + this.options.color + ';';
  623. }
  624. if (this.options.backColor) {
  625. style += 'background-color:' + this.options.backColor + ';';
  626. }
  627. if (!this.options.showBorder) {
  628. style += 'border:none;';
  629. }
  630. else if (this.options.borderColor) {
  631. style += 'border:1px solid ' + this.options.borderColor + ';';
  632. }
  633. style += '}';
  634. if (this.options.onhoverColor) {
  635. style += '.node-' + this.elementId + ':not(.node-disabled):hover{' +
  636. 'background-color:' + this.options.onhoverColor + ';' +
  637. '}';
  638. }
  639. return this.css + style;
  640. };
  641. Tree.prototype.template = {
  642. list: '<ul class="list-group"></ul>',
  643. item: '<li class="list-group-item"></li>',
  644. indent: '<span class="indent"></span>',
  645. icon: '<span class="icon"></span>',
  646. link: '<a href="#" style="color:inherit;"></a>',
  647. badge: '<span class="badge"></span>',
  648. chk : '<div class="squaredTwo"><input type="checkbox" checked=""><label class="check-icon"></label></div>',
  649. del : '<span class="fui-cross"></span>'
  650. };
  651. Tree.prototype.css = '.treeview .list-group-item{cursor:pointer}.treeview span.indent{margin-left:10px;margin-right:10px}.treeview span.icon{width:12px;margin-right:5px}.treeview .node-disabled{color:silver;cursor:not-allowed}'
  652. /**
  653. Returns a single node object that matches the given node id.
  654. @param {Number} nodeId - A node's unique identifier
  655. @return {Object} node - Matching node
  656. */
  657. Tree.prototype.getNode = function (nodeId) {
  658. return this.nodes[nodeId];
  659. };
  660. /**
  661. Returns the parent node of a given node, if valid otherwise returns undefined.
  662. @param {Object|Number} identifier - A valid node or node id
  663. @returns {Object} node - The parent node
  664. */
  665. Tree.prototype.getParent = function (identifier) {
  666. var node = this.identifyNode(identifier);
  667. return this.nodes[node.parentId];
  668. };
  669. /**
  670. Returns an array of sibling nodes for a given node, if valid otherwise returns undefined.
  671. @param {Object|Number} identifier - A valid node or node id
  672. @returns {Array} nodes - Sibling nodes
  673. */
  674. Tree.prototype.getSiblings = function (identifier) {
  675. var node = this.identifyNode(identifier);
  676. var parent = this.getParent(node);
  677. var nodes = parent ? parent.nodes : this.tree;
  678. return nodes.filter(function (obj) {
  679. return obj.nodeId !== node.nodeId;
  680. });
  681. };
  682. /**
  683. Returns an array of selected nodes.
  684. @returns {Array} nodes - Selected nodes
  685. */
  686. Tree.prototype.getSelected = function () {
  687. return this.findNodes('true', 'g', 'state.selected');
  688. };
  689. /**
  690. Returns an array of unselected nodes.
  691. @returns {Array} nodes - Unselected nodes
  692. */
  693. Tree.prototype.getUnselected = function () {
  694. return this.findNodes('false', 'g', 'state.selected');
  695. };
  696. /**
  697. Returns an array of expanded nodes.
  698. @returns {Array} nodes - Expanded nodes
  699. */
  700. Tree.prototype.getExpanded = function () {
  701. return this.findNodes('true', 'g', 'state.expanded');
  702. };
  703. /**
  704. Returns an array of collapsed nodes.
  705. @returns {Array} nodes - Collapsed nodes
  706. */
  707. Tree.prototype.getCollapsed = function () {
  708. return this.findNodes('false', 'g', 'state.expanded');
  709. };
  710. /**
  711. Returns an array of checked nodes.
  712. @returns {Array} nodes - Checked nodes
  713. */
  714. Tree.prototype.getChecked = function () {
  715. return this.findNodes('true', 'g', 'state.checked');
  716. };
  717. /**
  718. Returns an array of unchecked nodes.
  719. @returns {Array} nodes - Unchecked nodes
  720. */
  721. Tree.prototype.getUnchecked = function () {
  722. return this.findNodes('false', 'g', 'state.checked');
  723. };
  724. /**
  725. Returns an array of disabled nodes.
  726. @returns {Array} nodes - Disabled nodes
  727. */
  728. Tree.prototype.getDisabled = function () {
  729. return this.findNodes('true', 'g', 'state.disabled');
  730. };
  731. /**
  732. Returns an array of enabled nodes.
  733. @returns {Array} nodes - Enabled nodes
  734. */
  735. Tree.prototype.getEnabled = function () {
  736. return this.findNodes('false', 'g', 'state.disabled');
  737. };
  738. /**
  739. Set a node state to selected
  740. @param {Object|Number} identifiers - A valid node, node id or array of node identifiers
  741. @param {optional Object} options
  742. */
  743. Tree.prototype.selectNode = function (identifiers, options) {
  744. this.forEachIdentifier(identifiers, options, $.proxy(function (node, options) {
  745. this.setSelectedState(node, true, options);
  746. }, this));
  747. this.render();
  748. };
  749. /**
  750. Set a node state to unselected
  751. @param {Object|Number} identifiers - A valid node, node id or array of node identifiers
  752. @param {optional Object} options
  753. */
  754. Tree.prototype.unselectNode = function (identifiers, options) {
  755. this.forEachIdentifier(identifiers, options, $.proxy(function (node, options) {
  756. this.setSelectedState(node, false, options);
  757. }, this));
  758. this.render();
  759. };
  760. /**
  761. Toggles a node selected state; selecting if unselected, unselecting if selected.
  762. @param {Object|Number} identifiers - A valid node, node id or array of node identifiers
  763. @param {optional Object} options
  764. */
  765. Tree.prototype.toggleNodeSelected = function (identifiers, options) {
  766. this.forEachIdentifier(identifiers, options, $.proxy(function (node, options) {
  767. this.toggleSelectedState(node, options);
  768. }, this));
  769. this.render();
  770. };
  771. /**
  772. Collapse all tree nodes
  773. @param {optional Object} options
  774. */
  775. Tree.prototype.collapseAll = function (options) {
  776. var identifiers = this.findNodes('true', 'g', 'state.expanded');
  777. this.forEachIdentifier(identifiers, options, $.proxy(function (node, options) {
  778. this.setExpandedState(node, false, options);
  779. }, this));
  780. this.render();
  781. };
  782. /**
  783. Collapse a given tree node
  784. @param {Object|Number} identifiers - A valid node, node id or array of node identifiers
  785. @param {optional Object} options
  786. */
  787. Tree.prototype.collapseNode = function (identifiers, options) {
  788. this.forEachIdentifier(identifiers, options, $.proxy(function (node, options) {
  789. this.setExpandedState(node, false, options);
  790. }, this));
  791. this.render();
  792. };
  793. /**
  794. Expand all tree nodes
  795. @param {optional Object} options
  796. */
  797. Tree.prototype.expandAll = function (options) {
  798. options = $.extend({}, _default.options, options);
  799. if (options && options.levels) {
  800. this.expandLevels(this.tree, options.levels, options);
  801. }
  802. else {
  803. var identifiers = this.findNodes('false', 'g', 'state.expanded');
  804. this.forEachIdentifier(identifiers, options, $.proxy(function (node, options) {
  805. this.setExpandedState(node, true, options);
  806. }, this));
  807. }
  808. this.render();
  809. };
  810. /**
  811. Expand a given tree node
  812. @param {Object|Number} identifiers - A valid node, node id or array of node identifiers
  813. @param {optional Object} options
  814. */
  815. Tree.prototype.expandNode = function (identifiers, options) {
  816. this.forEachIdentifier(identifiers, options, $.proxy(function (node, options) {
  817. this.setExpandedState(node, true, options);
  818. if (node.nodes && (options && options.levels)) {
  819. this.expandLevels(node.nodes, options.levels-1, options);
  820. }
  821. }, this));
  822. this.render();
  823. };
  824. Tree.prototype.expandLevels = function (nodes, level, options) {
  825. options = $.extend({}, _default.options, options);
  826. $.each(nodes, $.proxy(function (index, node) {
  827. this.setExpandedState(node, (level > 0) ? true : false, options);
  828. if (node.nodes) {
  829. this.expandLevels(node.nodes, level-1, options);
  830. }
  831. }, this));
  832. };
  833. /**
  834. Reveals a given tree node, expanding the tree from node to root.
  835. @param {Object|Number|Array} identifiers - A valid node, node id or array of node identifiers
  836. @param {optional Object} options
  837. */
  838. Tree.prototype.revealNode = function (identifiers, options) {
  839. this.forEachIdentifier(identifiers, options, $.proxy(function (node, options) {
  840. var parentNode = this.getParent(node);
  841. while (parentNode) {
  842. this.setExpandedState(parentNode, true, options);
  843. parentNode = this.getParent(parentNode);
  844. };
  845. }, this));
  846. this.render();
  847. };
  848. /**
  849. Toggles a nodes expanded state; collapsing if expanded, expanding if collapsed.
  850. @param {Object|Number} identifiers - A valid node, node id or array of node identifiers
  851. @param {optional Object} options
  852. */
  853. Tree.prototype.toggleNodeExpanded = function (identifiers, options) {
  854. this.forEachIdentifier(identifiers, options, $.proxy(function (node, options) {
  855. this.toggleExpandedState(node, options);
  856. }, this));
  857. this.render();
  858. };
  859. /**
  860. Check all tree nodes
  861. @param {optional Object} options
  862. */
  863. Tree.prototype.checkAll = function (options) {
  864. var identifiers = this.findNodes('false', 'g', 'state.checked');
  865. this.forEachIdentifier(identifiers, options, $.proxy(function (node, options) {
  866. this.setCheckedState(node, true, options);
  867. }, this));
  868. this.render();
  869. };
  870. /**
  871. Check a given tree node
  872. @param {Object|Number} identifiers - A valid node, node id or array of node identifiers
  873. @param {optional Object} options
  874. */
  875. Tree.prototype.checkNode = function (identifiers, options) {
  876. this.forEachIdentifier(identifiers, options, $.proxy(function (node, options) {
  877. this.setCheckedState(node, true, options);
  878. }, this));
  879. this.render();
  880. };
  881. /**
  882. Uncheck all tree nodes
  883. @param {optional Object} options
  884. */
  885. Tree.prototype.uncheckAll = function (options) {
  886. var identifiers = this.findNodes('true', 'g', 'state.checked');
  887. this.forEachIdentifier(identifiers, options, $.proxy(function (node, options) {
  888. this.setCheckedState(node, false, options);
  889. }, this));
  890. this.render();
  891. };
  892. /**
  893. Uncheck a given tree node
  894. @param {Object|Number} identifiers - A valid node, node id or array of node identifiers
  895. @param {optional Object} options
  896. */
  897. Tree.prototype.uncheckNode = function (identifiers, options) {
  898. this.forEachIdentifier(identifiers, options, $.proxy(function (node, options) {
  899. this.setCheckedState(node, false, options);
  900. }, this));
  901. this.render();
  902. };
  903. /**
  904. Toggles a nodes checked state; checking if unchecked, unchecking if checked.
  905. @param {Object|Number} identifiers - A valid node, node id or array of node identifiers
  906. @param {optional Object} options
  907. */
  908. Tree.prototype.toggleNodeChecked = function (identifiers, options) {
  909. this.forEachIdentifier(identifiers, options, $.proxy(function (node, options) {
  910. this.toggleCheckedState(node, options);
  911. }, this));
  912. this.render();
  913. };
  914. /**
  915. Disable all tree nodes
  916. @param {optional Object} options
  917. */
  918. Tree.prototype.disableAll = function (options) {
  919. var identifiers = this.findNodes('false', 'g', 'state.disabled');
  920. this.forEachIdentifier(identifiers, options, $.proxy(function (node, options) {
  921. this.setDisabledState(node, true, options);
  922. }, this));
  923. this.render();
  924. };
  925. /**
  926. Disable a given tree node
  927. @param {Object|Number} identifiers - A valid node, node id or array of node identifiers
  928. @param {optional Object} options
  929. */
  930. Tree.prototype.disableNode = function (identifiers, options) {
  931. this.forEachIdentifier(identifiers, options, $.proxy(function (node, options) {
  932. this.setDisabledState(node, true, options);
  933. }, this));
  934. this.render();
  935. };
  936. /**
  937. Enable all tree nodes
  938. @param {optional Object} options
  939. */
  940. Tree.prototype.enableAll = function (options) {
  941. var identifiers = this.findNodes('true', 'g', 'state.disabled');
  942. this.forEachIdentifier(identifiers, options, $.proxy(function (node, options) {
  943. this.setDisabledState(node, false, options);
  944. }, this));
  945. this.render();
  946. };
  947. /**
  948. Enable a given tree node
  949. @param {Object|Number} identifiers - A valid node, node id or array of node identifiers
  950. @param {optional Object} options
  951. */
  952. Tree.prototype.enableNode = function (identifiers, options) {
  953. this.forEachIdentifier(identifiers, options, $.proxy(function (node, options) {
  954. this.setDisabledState(node, false, options);
  955. }, this));
  956. this.render();
  957. };
  958. /**
  959. Toggles a nodes disabled state; disabling is enabled, enabling if disabled.
  960. @param {Object|Number} identifiers - A valid node, node id or array of node identifiers
  961. @param {optional Object} options
  962. */
  963. Tree.prototype.toggleNodeDisabled = function (identifiers, options) {
  964. this.forEachIdentifier(identifiers, options, $.proxy(function (node, options) {
  965. this.setDisabledState(node, !node.state.disabled, options);
  966. }, this));
  967. this.render();
  968. };
  969. /**
  970. Common code for processing multiple identifiers
  971. */
  972. Tree.prototype.forEachIdentifier = function (identifiers, options, callback) {
  973. options = $.extend({}, _default.options, options);
  974. if (!(identifiers instanceof Array)) {
  975. identifiers = [identifiers];
  976. }
  977. $.each(identifiers, $.proxy(function (index, identifier) {
  978. callback(this.identifyNode(identifier), options);
  979. }, this));
  980. };
  981. /*
  982. Identifies a node from either a node id or object
  983. */
  984. Tree.prototype.identifyNode = function (identifier) {
  985. return ((typeof identifier) === 'number') ?
  986. this.nodes[identifier] :
  987. identifier;
  988. };
  989. /**
  990. Searches the tree for nodes (text) that match given criteria
  991. @param {String} pattern - A given string to match against
  992. @param {optional Object} options - Search criteria options
  993. @return {Array} nodes - Matching nodes
  994. */
  995. Tree.prototype.search = function (pattern, options) {
  996. options = $.extend({}, _default.searchOptions, options);
  997. this.clearSearch({ render: false });
  998. var results = [];
  999. if (pattern && pattern.length > 0) {
  1000. if (options.exactMatch) {
  1001. pattern = '^' + pattern + '$';
  1002. }
  1003. var modifier = 'g';
  1004. if (options.ignoreCase) {
  1005. modifier += 'i';
  1006. }
  1007. results = this.findNodes(pattern, modifier);
  1008. // Add searchResult property to all matching nodes
  1009. // This will be used to apply custom styles
  1010. // and when identifying result to be cleared
  1011. $.each(results, function (index, node) {
  1012. node.searchResult = true;
  1013. })
  1014. }
  1015. // If revealResults, then render is triggered from revealNode
  1016. // otherwise we just call render.
  1017. if (options.revealResults) {
  1018. this.revealNode(results);
  1019. }
  1020. else {
  1021. this.render();
  1022. }
  1023. this.$element.trigger('searchComplete', $.extend(true, {}, results));
  1024. return results;
  1025. };
  1026. /**
  1027. Clears previous search results
  1028. */
  1029. Tree.prototype.clearSearch = function (options) {
  1030. options = $.extend({}, { render: true }, options);
  1031. var results = $.each(this.findNodes('true', 'g', 'searchResult'), function (index, node) {
  1032. node.searchResult = false;
  1033. });
  1034. if (options.render) {
  1035. this.render();
  1036. }
  1037. this.$element.trigger('searchCleared', $.extend(true, {}, results));
  1038. };
  1039. /**
  1040. Find nodes that match a given criteria
  1041. @param {String} pattern - A given string to match against
  1042. @param {optional String} modifier - Valid RegEx modifiers
  1043. @param {optional String} attribute - Attribute to compare pattern against
  1044. @return {Array} nodes - Nodes that match your criteria
  1045. */
  1046. Tree.prototype.findNodes = function (pattern, modifier, attribute) {
  1047. modifier = modifier || 'g';
  1048. attribute = attribute || 'text';
  1049. var _this = this;
  1050. return $.grep(this.nodes, function (node) {
  1051. var val = _this.getNodeValue(node, attribute);
  1052. if (typeof val === 'string') {
  1053. return val.match(new RegExp(pattern, modifier));
  1054. }
  1055. });
  1056. };
  1057. /**
  1058. Recursive find for retrieving nested attributes values
  1059. All values are return as strings, unless invalid
  1060. @param {Object} obj - Typically a node, could be any object
  1061. @param {String} attr - Identifies an object property using dot notation
  1062. @return {String} value - Matching attributes string representation
  1063. */
  1064. Tree.prototype.getNodeValue = function (obj, attr) {
  1065. var index = attr.indexOf('.');
  1066. if (index > 0) {
  1067. var _obj = obj[attr.substring(0, index)];
  1068. var _attr = attr.substring(index + 1, attr.length);
  1069. return this.getNodeValue(_obj, _attr);
  1070. }
  1071. else {
  1072. if (obj.hasOwnProperty(attr)) {
  1073. return obj[attr].toString();
  1074. }
  1075. else {
  1076. return undefined;
  1077. }
  1078. }
  1079. };
  1080. var logError = function (message) {
  1081. if (window.console) {
  1082. window.console.error(message);
  1083. }
  1084. };
  1085. // Prevent against multiple instantiations,
  1086. // handle updates and method calls
  1087. $.fn[pluginName] = function (options, args) {
  1088. var result;
  1089. this.each(function () {
  1090. var _this = $.data(this, pluginName);
  1091. if (typeof options === 'string') {
  1092. if (!_this) {
  1093. logError('Not initialized, can not call method : ' + options);
  1094. }
  1095. else if (!$.isFunction(_this[options]) || options.charAt(0) === '_') {
  1096. logError('No such method : ' + options);
  1097. }
  1098. else {
  1099. if (!(args instanceof Array)) {
  1100. args = [ args ];
  1101. }
  1102. result = _this[options].apply(_this, args);
  1103. }
  1104. }
  1105. else if (typeof options === 'boolean') {
  1106. result = _this;
  1107. }
  1108. else {
  1109. $.data(this, pluginName, new Tree(this, $.extend(true, {}, options)));
  1110. }
  1111. });
  1112. return result || this;
  1113. };
  1114. })(jQuery, window, document);