25'ten fazla konu seçemezsiniz Konular bir harf veya rakamla başlamalı, kısa çizgiler ('-') içerebilir ve en fazla 35 karakter uzunluğunda olabilir.
 
 
 
 
 
 

977 satır
36 KiB

  1. /**
  2. * ### Checkbox plugin
  3. *
  4. * This plugin renders checkbox icons in front of each node, making multiple selection much easier.
  5. * It also supports tri-state behavior, meaning that if a node has a few of its children checked it will be rendered as undetermined, and state will be propagated up.
  6. */
  7. /*globals jQuery, define, exports, require, document */
  8. (function (factory) {
  9. "use strict";
  10. if (typeof define === 'function' && define.amd) {
  11. define('jstree.checkbox', ['jquery','jstree'], factory);
  12. }
  13. else if(typeof exports === 'object') {
  14. factory(require('jquery'), require('jstree'));
  15. }
  16. else {
  17. factory(jQuery, jQuery.jstree);
  18. }
  19. }(function ($, jstree, undefined) {
  20. "use strict";
  21. if($.jstree.plugins.checkbox) { return; }
  22. var _i = document.createElement('I');
  23. _i.className = 'jstree-icon jstree-checkbox';
  24. _i.setAttribute('role', 'presentation');
  25. /**
  26. * stores all defaults for the checkbox plugin
  27. * @name $.jstree.defaults.checkbox
  28. * @plugin checkbox
  29. */
  30. $.jstree.defaults.checkbox = {
  31. /**
  32. * a boolean indicating if checkboxes should be visible (can be changed at a later time using `show_checkboxes()` and `hide_checkboxes`). Defaults to `true`.
  33. * @name $.jstree.defaults.checkbox.visible
  34. * @plugin checkbox
  35. */
  36. visible : true,
  37. /**
  38. * a boolean indicating if checkboxes should cascade down and have an undetermined state. Defaults to `true`.
  39. * @name $.jstree.defaults.checkbox.three_state
  40. * @plugin checkbox
  41. */
  42. three_state : true,
  43. /**
  44. * a boolean indicating if clicking anywhere on the node should act as clicking on the checkbox. Defaults to `true`.
  45. * @name $.jstree.defaults.checkbox.whole_node
  46. * @plugin checkbox
  47. */
  48. whole_node : true,
  49. /**
  50. * a boolean indicating if the selected style of a node should be kept, or removed. Defaults to `true`.
  51. * @name $.jstree.defaults.checkbox.keep_selected_style
  52. * @plugin checkbox
  53. */
  54. keep_selected_style : true,
  55. /**
  56. * This setting controls how cascading and undetermined nodes are applied.
  57. * If 'up' is in the string - cascading up is enabled, if 'down' is in the string - cascading down is enabled, if 'undetermined' is in the string - undetermined nodes will be used.
  58. * If `three_state` is set to `true` this setting is automatically set to 'up+down+undetermined'. Defaults to ''.
  59. * @name $.jstree.defaults.checkbox.cascade
  60. * @plugin checkbox
  61. */
  62. cascade : '',
  63. /**
  64. * This setting controls if checkbox are bound to the general tree selection or to an internal array maintained by the checkbox plugin. Defaults to `true`, only set to `false` if you know exactly what you are doing.
  65. * @name $.jstree.defaults.checkbox.tie_selection
  66. * @plugin checkbox
  67. */
  68. tie_selection : true,
  69. /**
  70. * This setting controls if cascading down affects disabled checkboxes
  71. * @name $.jstree.defaults.checkbox.cascade_to_disabled
  72. * @plugin checkbox
  73. */
  74. cascade_to_disabled : true,
  75. /**
  76. * This setting controls if cascading down affects hidden checkboxes
  77. * @name $.jstree.defaults.checkbox.cascade_to_hidden
  78. * @plugin checkbox
  79. */
  80. cascade_to_hidden : true
  81. };
  82. $.jstree.plugins.checkbox = function (options, parent) {
  83. this.bind = function () {
  84. parent.bind.call(this);
  85. this._data.checkbox.uto = false;
  86. this._data.checkbox.selected = [];
  87. if(this.settings.checkbox.three_state) {
  88. this.settings.checkbox.cascade = 'up+down+undetermined';
  89. }
  90. this.element
  91. .on("init.jstree", $.proxy(function () {
  92. this._data.checkbox.visible = this.settings.checkbox.visible;
  93. if(!this.settings.checkbox.keep_selected_style) {
  94. this.element.addClass('jstree-checkbox-no-clicked');
  95. }
  96. if(this.settings.checkbox.tie_selection) {
  97. this.element.addClass('jstree-checkbox-selection');
  98. }
  99. }, this))
  100. .on("loading.jstree", $.proxy(function () {
  101. this[ this._data.checkbox.visible ? 'show_checkboxes' : 'hide_checkboxes' ]();
  102. }, this));
  103. if(this.settings.checkbox.cascade.indexOf('undetermined') !== -1) {
  104. this.element
  105. .on('changed.jstree uncheck_node.jstree check_node.jstree uncheck_all.jstree check_all.jstree move_node.jstree copy_node.jstree redraw.jstree open_node.jstree', $.proxy(function () {
  106. // only if undetermined is in setting
  107. if(this._data.checkbox.uto) { clearTimeout(this._data.checkbox.uto); }
  108. this._data.checkbox.uto = setTimeout($.proxy(this._undetermined, this), 50);
  109. }, this));
  110. }
  111. if(!this.settings.checkbox.tie_selection) {
  112. this.element
  113. .on('model.jstree', $.proxy(function (e, data) {
  114. var m = this._model.data,
  115. p = m[data.parent],
  116. dpc = data.nodes,
  117. i, j;
  118. for(i = 0, j = dpc.length; i < j; i++) {
  119. m[dpc[i]].state.checked = m[dpc[i]].state.checked || (m[dpc[i]].original && m[dpc[i]].original.state && m[dpc[i]].original.state.checked);
  120. if(m[dpc[i]].state.checked) {
  121. this._data.checkbox.selected.push(dpc[i]);
  122. }
  123. }
  124. }, this));
  125. }
  126. if(this.settings.checkbox.cascade.indexOf('up') !== -1 || this.settings.checkbox.cascade.indexOf('down') !== -1) {
  127. this.element
  128. .on('model.jstree', $.proxy(function (e, data) {
  129. var m = this._model.data,
  130. p = m[data.parent],
  131. dpc = data.nodes,
  132. chd = [],
  133. c, i, j, k, l, tmp, s = this.settings.checkbox.cascade, t = this.settings.checkbox.tie_selection;
  134. if(s.indexOf('down') !== -1) {
  135. // apply down
  136. if(p.state[ t ? 'selected' : 'checked' ]) {
  137. for(i = 0, j = dpc.length; i < j; i++) {
  138. m[dpc[i]].state[ t ? 'selected' : 'checked' ] = true;
  139. }
  140. this._data[ t ? 'core' : 'checkbox' ].selected = this._data[ t ? 'core' : 'checkbox' ].selected.concat(dpc);
  141. }
  142. else {
  143. for(i = 0, j = dpc.length; i < j; i++) {
  144. if(m[dpc[i]].state[ t ? 'selected' : 'checked' ]) {
  145. for(k = 0, l = m[dpc[i]].children_d.length; k < l; k++) {
  146. m[m[dpc[i]].children_d[k]].state[ t ? 'selected' : 'checked' ] = true;
  147. }
  148. this._data[ t ? 'core' : 'checkbox' ].selected = this._data[ t ? 'core' : 'checkbox' ].selected.concat(m[dpc[i]].children_d);
  149. }
  150. }
  151. }
  152. }
  153. if(s.indexOf('up') !== -1) {
  154. // apply up
  155. for(i = 0, j = p.children_d.length; i < j; i++) {
  156. if(!m[p.children_d[i]].children.length) {
  157. chd.push(m[p.children_d[i]].parent);
  158. }
  159. }
  160. chd = $.vakata.array_unique(chd);
  161. for(k = 0, l = chd.length; k < l; k++) {
  162. p = m[chd[k]];
  163. while(p && p.id !== $.jstree.root) {
  164. c = 0;
  165. for(i = 0, j = p.children.length; i < j; i++) {
  166. c += m[p.children[i]].state[ t ? 'selected' : 'checked' ];
  167. }
  168. if(c === j) {
  169. p.state[ t ? 'selected' : 'checked' ] = true;
  170. this._data[ t ? 'core' : 'checkbox' ].selected.push(p.id);
  171. tmp = this.get_node(p, true);
  172. if(tmp && tmp.length) {
  173. tmp.attr('aria-selected', true).children('.jstree-anchor').addClass( t ? 'jstree-clicked' : 'jstree-checked');
  174. }
  175. }
  176. else {
  177. break;
  178. }
  179. p = this.get_node(p.parent);
  180. }
  181. }
  182. }
  183. this._data[ t ? 'core' : 'checkbox' ].selected = $.vakata.array_unique(this._data[ t ? 'core' : 'checkbox' ].selected);
  184. }, this))
  185. .on(this.settings.checkbox.tie_selection ? 'select_node.jstree' : 'check_node.jstree', $.proxy(function (e, data) {
  186. var self = this,
  187. obj = data.node,
  188. m = this._model.data,
  189. par = this.get_node(obj.parent),
  190. i, j, c, tmp, s = this.settings.checkbox.cascade, t = this.settings.checkbox.tie_selection,
  191. sel = {}, cur = this._data[ t ? 'core' : 'checkbox' ].selected;
  192. for (i = 0, j = cur.length; i < j; i++) {
  193. sel[cur[i]] = true;
  194. }
  195. // apply down
  196. if(s.indexOf('down') !== -1) {
  197. //this._data[ t ? 'core' : 'checkbox' ].selected = $.vakata.array_unique(this._data[ t ? 'core' : 'checkbox' ].selected.concat(obj.children_d));
  198. var selectedIds = this._cascade_new_checked_state(obj.id, true);
  199. var temp = obj.children_d.concat(obj.id);
  200. for (i = 0, j = temp.length; i < j; i++) {
  201. if (selectedIds.indexOf(temp[i]) > -1) {
  202. sel[temp[i]] = true;
  203. }
  204. else {
  205. delete sel[temp[i]];
  206. }
  207. }
  208. }
  209. // apply up
  210. if(s.indexOf('up') !== -1) {
  211. while(par && par.id !== $.jstree.root) {
  212. c = 0;
  213. for(i = 0, j = par.children.length; i < j; i++) {
  214. c += m[par.children[i]].state[ t ? 'selected' : 'checked' ];
  215. }
  216. if(c === j) {
  217. par.state[ t ? 'selected' : 'checked' ] = true;
  218. sel[par.id] = true;
  219. //this._data[ t ? 'core' : 'checkbox' ].selected.push(par.id);
  220. tmp = this.get_node(par, true);
  221. if(tmp && tmp.length) {
  222. tmp.attr('aria-selected', true).children('.jstree-anchor').addClass(t ? 'jstree-clicked' : 'jstree-checked');
  223. }
  224. }
  225. else {
  226. break;
  227. }
  228. par = this.get_node(par.parent);
  229. }
  230. }
  231. cur = [];
  232. for (i in sel) {
  233. if (sel.hasOwnProperty(i)) {
  234. cur.push(i);
  235. }
  236. }
  237. this._data[ t ? 'core' : 'checkbox' ].selected = cur;
  238. }, this))
  239. .on(this.settings.checkbox.tie_selection ? 'deselect_all.jstree' : 'uncheck_all.jstree', $.proxy(function (e, data) {
  240. var obj = this.get_node($.jstree.root),
  241. m = this._model.data,
  242. i, j, tmp;
  243. for(i = 0, j = obj.children_d.length; i < j; i++) {
  244. tmp = m[obj.children_d[i]];
  245. if(tmp && tmp.original && tmp.original.state && tmp.original.state.undetermined) {
  246. tmp.original.state.undetermined = false;
  247. }
  248. }
  249. }, this))
  250. .on(this.settings.checkbox.tie_selection ? 'deselect_node.jstree' : 'uncheck_node.jstree', $.proxy(function (e, data) {
  251. var self = this,
  252. obj = data.node,
  253. dom = this.get_node(obj, true),
  254. i, j, tmp, s = this.settings.checkbox.cascade, t = this.settings.checkbox.tie_selection,
  255. cur = this._data[ t ? 'core' : 'checkbox' ].selected, sel = {},
  256. stillSelectedIds = [],
  257. allIds = obj.children_d.concat(obj.id);
  258. // apply down
  259. if(s.indexOf('down') !== -1) {
  260. var selectedIds = this._cascade_new_checked_state(obj.id, false);
  261. cur = $.vakata.array_filter(cur, function(id) {
  262. return allIds.indexOf(id) === -1 || selectedIds.indexOf(id) > -1;
  263. });
  264. }
  265. // only apply up if cascade up is enabled and if this node is not selected
  266. // (if all child nodes are disabled and cascade_to_disabled === false then this node will till be selected).
  267. if(s.indexOf('up') !== -1 && cur.indexOf(obj.id) === -1) {
  268. for(i = 0, j = obj.parents.length; i < j; i++) {
  269. tmp = this._model.data[obj.parents[i]];
  270. tmp.state[ t ? 'selected' : 'checked' ] = false;
  271. if(tmp && tmp.original && tmp.original.state && tmp.original.state.undetermined) {
  272. tmp.original.state.undetermined = false;
  273. }
  274. tmp = this.get_node(obj.parents[i], true);
  275. if(tmp && tmp.length) {
  276. tmp.attr('aria-selected', false).children('.jstree-anchor').removeClass(t ? 'jstree-clicked' : 'jstree-checked');
  277. }
  278. }
  279. cur = $.vakata.array_filter(cur, function(id) {
  280. return obj.parents.indexOf(id) === -1;
  281. });
  282. }
  283. this._data[ t ? 'core' : 'checkbox' ].selected = cur;
  284. }, this));
  285. }
  286. if(this.settings.checkbox.cascade.indexOf('up') !== -1) {
  287. this.element
  288. .on('delete_node.jstree', $.proxy(function (e, data) {
  289. // apply up (whole handler)
  290. var p = this.get_node(data.parent),
  291. m = this._model.data,
  292. i, j, c, tmp, t = this.settings.checkbox.tie_selection;
  293. while(p && p.id !== $.jstree.root && !p.state[ t ? 'selected' : 'checked' ]) {
  294. c = 0;
  295. for(i = 0, j = p.children.length; i < j; i++) {
  296. c += m[p.children[i]].state[ t ? 'selected' : 'checked' ];
  297. }
  298. if(j > 0 && c === j) {
  299. p.state[ t ? 'selected' : 'checked' ] = true;
  300. this._data[ t ? 'core' : 'checkbox' ].selected.push(p.id);
  301. tmp = this.get_node(p, true);
  302. if(tmp && tmp.length) {
  303. tmp.attr('aria-selected', true).children('.jstree-anchor').addClass(t ? 'jstree-clicked' : 'jstree-checked');
  304. }
  305. }
  306. else {
  307. break;
  308. }
  309. p = this.get_node(p.parent);
  310. }
  311. }, this))
  312. .on('move_node.jstree', $.proxy(function (e, data) {
  313. // apply up (whole handler)
  314. var is_multi = data.is_multi,
  315. old_par = data.old_parent,
  316. new_par = this.get_node(data.parent),
  317. m = this._model.data,
  318. p, c, i, j, tmp, t = this.settings.checkbox.tie_selection;
  319. if(!is_multi) {
  320. p = this.get_node(old_par);
  321. while(p && p.id !== $.jstree.root && !p.state[ t ? 'selected' : 'checked' ]) {
  322. c = 0;
  323. for(i = 0, j = p.children.length; i < j; i++) {
  324. c += m[p.children[i]].state[ t ? 'selected' : 'checked' ];
  325. }
  326. if(j > 0 && c === j) {
  327. p.state[ t ? 'selected' : 'checked' ] = true;
  328. this._data[ t ? 'core' : 'checkbox' ].selected.push(p.id);
  329. tmp = this.get_node(p, true);
  330. if(tmp && tmp.length) {
  331. tmp.attr('aria-selected', true).children('.jstree-anchor').addClass(t ? 'jstree-clicked' : 'jstree-checked');
  332. }
  333. }
  334. else {
  335. break;
  336. }
  337. p = this.get_node(p.parent);
  338. }
  339. }
  340. p = new_par;
  341. while(p && p.id !== $.jstree.root) {
  342. c = 0;
  343. for(i = 0, j = p.children.length; i < j; i++) {
  344. c += m[p.children[i]].state[ t ? 'selected' : 'checked' ];
  345. }
  346. if(c === j) {
  347. if(!p.state[ t ? 'selected' : 'checked' ]) {
  348. p.state[ t ? 'selected' : 'checked' ] = true;
  349. this._data[ t ? 'core' : 'checkbox' ].selected.push(p.id);
  350. tmp = this.get_node(p, true);
  351. if(tmp && tmp.length) {
  352. tmp.attr('aria-selected', true).children('.jstree-anchor').addClass(t ? 'jstree-clicked' : 'jstree-checked');
  353. }
  354. }
  355. }
  356. else {
  357. if(p.state[ t ? 'selected' : 'checked' ]) {
  358. p.state[ t ? 'selected' : 'checked' ] = false;
  359. this._data[ t ? 'core' : 'checkbox' ].selected = $.vakata.array_remove_item(this._data[ t ? 'core' : 'checkbox' ].selected, p.id);
  360. tmp = this.get_node(p, true);
  361. if(tmp && tmp.length) {
  362. tmp.attr('aria-selected', false).children('.jstree-anchor').removeClass(t ? 'jstree-clicked' : 'jstree-checked');
  363. }
  364. }
  365. else {
  366. break;
  367. }
  368. }
  369. p = this.get_node(p.parent);
  370. }
  371. }, this));
  372. }
  373. };
  374. /**
  375. * get an array of all nodes whose state is "undetermined"
  376. * @name get_undetermined([full])
  377. * @param {boolean} full: if set to `true` the returned array will consist of the full node objects, otherwise - only IDs will be returned
  378. * @return {Array}
  379. * @plugin checkbox
  380. */
  381. this.get_undetermined = function (full) {
  382. if (this.settings.checkbox.cascade.indexOf('undetermined') === -1) {
  383. return [];
  384. }
  385. var i, j, k, l, o = {}, m = this._model.data, t = this.settings.checkbox.tie_selection, s = this._data[ t ? 'core' : 'checkbox' ].selected, p = [], tt = this, r = [];
  386. for(i = 0, j = s.length; i < j; i++) {
  387. if(m[s[i]] && m[s[i]].parents) {
  388. for(k = 0, l = m[s[i]].parents.length; k < l; k++) {
  389. if(o[m[s[i]].parents[k]] !== undefined) {
  390. break;
  391. }
  392. if(m[s[i]].parents[k] !== $.jstree.root) {
  393. o[m[s[i]].parents[k]] = true;
  394. p.push(m[s[i]].parents[k]);
  395. }
  396. }
  397. }
  398. }
  399. // attempt for server side undetermined state
  400. this.element.find('.jstree-closed').not(':has(.jstree-children)')
  401. .each(function () {
  402. var tmp = tt.get_node(this), tmp2;
  403. if(!tmp) { return; }
  404. if(!tmp.state.loaded) {
  405. if(tmp.original && tmp.original.state && tmp.original.state.undetermined && tmp.original.state.undetermined === true) {
  406. if(o[tmp.id] === undefined && tmp.id !== $.jstree.root) {
  407. o[tmp.id] = true;
  408. p.push(tmp.id);
  409. }
  410. for(k = 0, l = tmp.parents.length; k < l; k++) {
  411. if(o[tmp.parents[k]] === undefined && tmp.parents[k] !== $.jstree.root) {
  412. o[tmp.parents[k]] = true;
  413. p.push(tmp.parents[k]);
  414. }
  415. }
  416. }
  417. }
  418. else {
  419. for(i = 0, j = tmp.children_d.length; i < j; i++) {
  420. tmp2 = m[tmp.children_d[i]];
  421. if(!tmp2.state.loaded && tmp2.original && tmp2.original.state && tmp2.original.state.undetermined && tmp2.original.state.undetermined === true) {
  422. if(o[tmp2.id] === undefined && tmp2.id !== $.jstree.root) {
  423. o[tmp2.id] = true;
  424. p.push(tmp2.id);
  425. }
  426. for(k = 0, l = tmp2.parents.length; k < l; k++) {
  427. if(o[tmp2.parents[k]] === undefined && tmp2.parents[k] !== $.jstree.root) {
  428. o[tmp2.parents[k]] = true;
  429. p.push(tmp2.parents[k]);
  430. }
  431. }
  432. }
  433. }
  434. }
  435. });
  436. for (i = 0, j = p.length; i < j; i++) {
  437. if(!m[p[i]].state[ t ? 'selected' : 'checked' ]) {
  438. r.push(full ? m[p[i]] : p[i]);
  439. }
  440. }
  441. return r;
  442. };
  443. /**
  444. * set the undetermined state where and if necessary. Used internally.
  445. * @private
  446. * @name _undetermined()
  447. * @plugin checkbox
  448. */
  449. this._undetermined = function () {
  450. if(this.element === null) { return; }
  451. var p = this.get_undetermined(false), i, j, s;
  452. this.element.find('.jstree-undetermined').removeClass('jstree-undetermined');
  453. for (i = 0, j = p.length; i < j; i++) {
  454. s = this.get_node(p[i], true);
  455. if(s && s.length) {
  456. s.children('.jstree-anchor').children('.jstree-checkbox').addClass('jstree-undetermined');
  457. }
  458. }
  459. };
  460. this.redraw_node = function(obj, deep, is_callback, force_render) {
  461. obj = parent.redraw_node.apply(this, arguments);
  462. if(obj) {
  463. var i, j, tmp = null, icon = null;
  464. for(i = 0, j = obj.childNodes.length; i < j; i++) {
  465. if(obj.childNodes[i] && obj.childNodes[i].className && obj.childNodes[i].className.indexOf("jstree-anchor") !== -1) {
  466. tmp = obj.childNodes[i];
  467. break;
  468. }
  469. }
  470. if(tmp) {
  471. if(!this.settings.checkbox.tie_selection && this._model.data[obj.id].state.checked) { tmp.className += ' jstree-checked'; }
  472. icon = _i.cloneNode(false);
  473. if(this._model.data[obj.id].state.checkbox_disabled) { icon.className += ' jstree-checkbox-disabled'; }
  474. tmp.insertBefore(icon, tmp.childNodes[0]);
  475. }
  476. }
  477. if(!is_callback && this.settings.checkbox.cascade.indexOf('undetermined') !== -1) {
  478. if(this._data.checkbox.uto) { clearTimeout(this._data.checkbox.uto); }
  479. this._data.checkbox.uto = setTimeout($.proxy(this._undetermined, this), 50);
  480. }
  481. return obj;
  482. };
  483. /**
  484. * show the node checkbox icons
  485. * @name show_checkboxes()
  486. * @plugin checkbox
  487. */
  488. this.show_checkboxes = function () { this._data.core.themes.checkboxes = true; this.get_container_ul().removeClass("jstree-no-checkboxes"); };
  489. /**
  490. * hide the node checkbox icons
  491. * @name hide_checkboxes()
  492. * @plugin checkbox
  493. */
  494. this.hide_checkboxes = function () { this._data.core.themes.checkboxes = false; this.get_container_ul().addClass("jstree-no-checkboxes"); };
  495. /**
  496. * toggle the node icons
  497. * @name toggle_checkboxes()
  498. * @plugin checkbox
  499. */
  500. this.toggle_checkboxes = function () { if(this._data.core.themes.checkboxes) { this.hide_checkboxes(); } else { this.show_checkboxes(); } };
  501. /**
  502. * checks if a node is in an undetermined state
  503. * @name is_undetermined(obj)
  504. * @param {mixed} obj
  505. * @return {Boolean}
  506. */
  507. this.is_undetermined = function (obj) {
  508. obj = this.get_node(obj);
  509. var s = this.settings.checkbox.cascade, i, j, t = this.settings.checkbox.tie_selection, d = this._data[ t ? 'core' : 'checkbox' ].selected, m = this._model.data;
  510. if(!obj || obj.state[ t ? 'selected' : 'checked' ] === true || s.indexOf('undetermined') === -1 || (s.indexOf('down') === -1 && s.indexOf('up') === -1)) {
  511. return false;
  512. }
  513. if(!obj.state.loaded && obj.original.state.undetermined === true) {
  514. return true;
  515. }
  516. for(i = 0, j = obj.children_d.length; i < j; i++) {
  517. if($.inArray(obj.children_d[i], d) !== -1 || (!m[obj.children_d[i]].state.loaded && m[obj.children_d[i]].original.state.undetermined)) {
  518. return true;
  519. }
  520. }
  521. return false;
  522. };
  523. /**
  524. * disable a node's checkbox
  525. * @name disable_checkbox(obj)
  526. * @param {mixed} obj an array can be used too
  527. * @trigger disable_checkbox.jstree
  528. * @plugin checkbox
  529. */
  530. this.disable_checkbox = function (obj) {
  531. var t1, t2, dom;
  532. if($.isArray(obj)) {
  533. obj = obj.slice();
  534. for(t1 = 0, t2 = obj.length; t1 < t2; t1++) {
  535. this.disable_checkbox(obj[t1]);
  536. }
  537. return true;
  538. }
  539. obj = this.get_node(obj);
  540. if(!obj || obj.id === $.jstree.root) {
  541. return false;
  542. }
  543. dom = this.get_node(obj, true);
  544. if(!obj.state.checkbox_disabled) {
  545. obj.state.checkbox_disabled = true;
  546. if(dom && dom.length) {
  547. dom.children('.jstree-anchor').children('.jstree-checkbox').addClass('jstree-checkbox-disabled');
  548. }
  549. /**
  550. * triggered when an node's checkbox is disabled
  551. * @event
  552. * @name disable_checkbox.jstree
  553. * @param {Object} node
  554. * @plugin checkbox
  555. */
  556. this.trigger('disable_checkbox', { 'node' : obj });
  557. }
  558. };
  559. /**
  560. * enable a node's checkbox
  561. * @name enable_checkbox(obj)
  562. * @param {mixed} obj an array can be used too
  563. * @trigger enable_checkbox.jstree
  564. * @plugin checkbox
  565. */
  566. this.enable_checkbox = function (obj) {
  567. var t1, t2, dom;
  568. if($.isArray(obj)) {
  569. obj = obj.slice();
  570. for(t1 = 0, t2 = obj.length; t1 < t2; t1++) {
  571. this.enable_checkbox(obj[t1]);
  572. }
  573. return true;
  574. }
  575. obj = this.get_node(obj);
  576. if(!obj || obj.id === $.jstree.root) {
  577. return false;
  578. }
  579. dom = this.get_node(obj, true);
  580. if(obj.state.checkbox_disabled) {
  581. obj.state.checkbox_disabled = false;
  582. if(dom && dom.length) {
  583. dom.children('.jstree-anchor').children('.jstree-checkbox').removeClass('jstree-checkbox-disabled');
  584. }
  585. /**
  586. * triggered when an node's checkbox is enabled
  587. * @event
  588. * @name enable_checkbox.jstree
  589. * @param {Object} node
  590. * @plugin checkbox
  591. */
  592. this.trigger('enable_checkbox', { 'node' : obj });
  593. }
  594. };
  595. this.activate_node = function (obj, e) {
  596. if($(e.target).hasClass('jstree-checkbox-disabled')) {
  597. return false;
  598. }
  599. if(this.settings.checkbox.tie_selection && (this.settings.checkbox.whole_node || $(e.target).hasClass('jstree-checkbox'))) {
  600. e.ctrlKey = true;
  601. }
  602. if(this.settings.checkbox.tie_selection || (!this.settings.checkbox.whole_node && !$(e.target).hasClass('jstree-checkbox'))) {
  603. return parent.activate_node.call(this, obj, e);
  604. }
  605. if(this.is_disabled(obj)) {
  606. return false;
  607. }
  608. if(this.is_checked(obj)) {
  609. this.uncheck_node(obj, e);
  610. }
  611. else {
  612. this.check_node(obj, e);
  613. }
  614. this.trigger('activate_node', { 'node' : this.get_node(obj) });
  615. };
  616. /**
  617. * Cascades checked state to a node and all its descendants. This function does NOT affect hidden and disabled nodes (or their descendants).
  618. * However if these unaffected nodes are already selected their ids will be included in the returned array.
  619. * @private
  620. * @param {string} id the node ID
  621. * @param {bool} checkedState should the nodes be checked or not
  622. * @returns {Array} Array of all node id's (in this tree branch) that are checked.
  623. */
  624. this._cascade_new_checked_state = function (id, checkedState) {
  625. var self = this;
  626. var t = this.settings.checkbox.tie_selection;
  627. var node = this._model.data[id];
  628. var selectedNodeIds = [];
  629. var selectedChildrenIds = [], i, j, selectedChildIds;
  630. if (
  631. (this.settings.checkbox.cascade_to_disabled || !node.state.disabled) &&
  632. (this.settings.checkbox.cascade_to_hidden || !node.state.hidden)
  633. ) {
  634. //First try and check/uncheck the children
  635. if (node.children) {
  636. for (i = 0, j = node.children.length; i < j; i++) {
  637. var childId = node.children[i];
  638. selectedChildIds = self._cascade_new_checked_state(childId, checkedState);
  639. selectedNodeIds = selectedNodeIds.concat(selectedChildIds);
  640. if (selectedChildIds.indexOf(childId) > -1) {
  641. selectedChildrenIds.push(childId);
  642. }
  643. }
  644. }
  645. var dom = self.get_node(node, true);
  646. //A node's state is undetermined if some but not all of it's children are checked/selected .
  647. var undetermined = selectedChildrenIds.length > 0 && selectedChildrenIds.length < node.children.length;
  648. if(node.original && node.original.state && node.original.state.undetermined) {
  649. node.original.state.undetermined = undetermined;
  650. }
  651. //If a node is undetermined then remove selected class
  652. if (undetermined) {
  653. node.state[ t ? 'selected' : 'checked' ] = false;
  654. dom.attr('aria-selected', false).children('.jstree-anchor').removeClass(t ? 'jstree-clicked' : 'jstree-checked');
  655. }
  656. //Otherwise, if the checkedState === true (i.e. the node is being checked now) and all of the node's children are checked (if it has any children),
  657. //check the node and style it correctly.
  658. else if (checkedState && selectedChildrenIds.length === node.children.length) {
  659. node.state[ t ? 'selected' : 'checked' ] = checkedState;
  660. selectedNodeIds.push(node.id);
  661. dom.attr('aria-selected', true).children('.jstree-anchor').addClass(t ? 'jstree-clicked' : 'jstree-checked');
  662. }
  663. else {
  664. node.state[ t ? 'selected' : 'checked' ] = false;
  665. dom.attr('aria-selected', false).children('.jstree-anchor').removeClass(t ? 'jstree-clicked' : 'jstree-checked');
  666. }
  667. }
  668. else {
  669. selectedChildIds = this.get_checked_descendants(id);
  670. if (node.state[ t ? 'selected' : 'checked' ]) {
  671. selectedChildIds.push(node.id);
  672. }
  673. selectedNodeIds = selectedNodeIds.concat(selectedChildIds);
  674. }
  675. return selectedNodeIds;
  676. };
  677. /**
  678. * Gets ids of nodes selected in branch (of tree) specified by id (does not include the node specified by id)
  679. * @name get_checked_descendants(obj)
  680. * @param {string} id the node ID
  681. * @return {Array} array of IDs
  682. * @plugin checkbox
  683. */
  684. this.get_checked_descendants = function (id) {
  685. var self = this;
  686. var t = self.settings.checkbox.tie_selection;
  687. var node = self._model.data[id];
  688. return $.vakata.array_filter(node.children_d, function(_id) {
  689. return self._model.data[_id].state[ t ? 'selected' : 'checked' ];
  690. });
  691. };
  692. /**
  693. * check a node (only if tie_selection in checkbox settings is false, otherwise select_node will be called internally)
  694. * @name check_node(obj)
  695. * @param {mixed} obj an array can be used to check multiple nodes
  696. * @trigger check_node.jstree
  697. * @plugin checkbox
  698. */
  699. this.check_node = function (obj, e) {
  700. if(this.settings.checkbox.tie_selection) { return this.select_node(obj, false, true, e); }
  701. var dom, t1, t2, th;
  702. if($.isArray(obj)) {
  703. obj = obj.slice();
  704. for(t1 = 0, t2 = obj.length; t1 < t2; t1++) {
  705. this.check_node(obj[t1], e);
  706. }
  707. return true;
  708. }
  709. obj = this.get_node(obj);
  710. if(!obj || obj.id === $.jstree.root) {
  711. return false;
  712. }
  713. dom = this.get_node(obj, true);
  714. if(!obj.state.checked) {
  715. obj.state.checked = true;
  716. this._data.checkbox.selected.push(obj.id);
  717. if(dom && dom.length) {
  718. dom.children('.jstree-anchor').addClass('jstree-checked');
  719. }
  720. /**
  721. * triggered when an node is checked (only if tie_selection in checkbox settings is false)
  722. * @event
  723. * @name check_node.jstree
  724. * @param {Object} node
  725. * @param {Array} selected the current selection
  726. * @param {Object} event the event (if any) that triggered this check_node
  727. * @plugin checkbox
  728. */
  729. this.trigger('check_node', { 'node' : obj, 'selected' : this._data.checkbox.selected, 'event' : e });
  730. }
  731. };
  732. /**
  733. * uncheck a node (only if tie_selection in checkbox settings is false, otherwise deselect_node will be called internally)
  734. * @name uncheck_node(obj)
  735. * @param {mixed} obj an array can be used to uncheck multiple nodes
  736. * @trigger uncheck_node.jstree
  737. * @plugin checkbox
  738. */
  739. this.uncheck_node = function (obj, e) {
  740. if(this.settings.checkbox.tie_selection) { return this.deselect_node(obj, false, e); }
  741. var t1, t2, dom;
  742. if($.isArray(obj)) {
  743. obj = obj.slice();
  744. for(t1 = 0, t2 = obj.length; t1 < t2; t1++) {
  745. this.uncheck_node(obj[t1], e);
  746. }
  747. return true;
  748. }
  749. obj = this.get_node(obj);
  750. if(!obj || obj.id === $.jstree.root) {
  751. return false;
  752. }
  753. dom = this.get_node(obj, true);
  754. if(obj.state.checked) {
  755. obj.state.checked = false;
  756. this._data.checkbox.selected = $.vakata.array_remove_item(this._data.checkbox.selected, obj.id);
  757. if(dom.length) {
  758. dom.children('.jstree-anchor').removeClass('jstree-checked');
  759. }
  760. /**
  761. * triggered when an node is unchecked (only if tie_selection in checkbox settings is false)
  762. * @event
  763. * @name uncheck_node.jstree
  764. * @param {Object} node
  765. * @param {Array} selected the current selection
  766. * @param {Object} event the event (if any) that triggered this uncheck_node
  767. * @plugin checkbox
  768. */
  769. this.trigger('uncheck_node', { 'node' : obj, 'selected' : this._data.checkbox.selected, 'event' : e });
  770. }
  771. };
  772. /**
  773. * checks all nodes in the tree (only if tie_selection in checkbox settings is false, otherwise select_all will be called internally)
  774. * @name check_all()
  775. * @trigger check_all.jstree, changed.jstree
  776. * @plugin checkbox
  777. */
  778. this.check_all = function () {
  779. if(this.settings.checkbox.tie_selection) { return this.select_all(); }
  780. var tmp = this._data.checkbox.selected.concat([]), i, j;
  781. this._data.checkbox.selected = this._model.data[$.jstree.root].children_d.concat();
  782. for(i = 0, j = this._data.checkbox.selected.length; i < j; i++) {
  783. if(this._model.data[this._data.checkbox.selected[i]]) {
  784. this._model.data[this._data.checkbox.selected[i]].state.checked = true;
  785. }
  786. }
  787. this.redraw(true);
  788. /**
  789. * triggered when all nodes are checked (only if tie_selection in checkbox settings is false)
  790. * @event
  791. * @name check_all.jstree
  792. * @param {Array} selected the current selection
  793. * @plugin checkbox
  794. */
  795. this.trigger('check_all', { 'selected' : this._data.checkbox.selected });
  796. };
  797. /**
  798. * uncheck all checked nodes (only if tie_selection in checkbox settings is false, otherwise deselect_all will be called internally)
  799. * @name uncheck_all()
  800. * @trigger uncheck_all.jstree
  801. * @plugin checkbox
  802. */
  803. this.uncheck_all = function () {
  804. if(this.settings.checkbox.tie_selection) { return this.deselect_all(); }
  805. var tmp = this._data.checkbox.selected.concat([]), i, j;
  806. for(i = 0, j = this._data.checkbox.selected.length; i < j; i++) {
  807. if(this._model.data[this._data.checkbox.selected[i]]) {
  808. this._model.data[this._data.checkbox.selected[i]].state.checked = false;
  809. }
  810. }
  811. this._data.checkbox.selected = [];
  812. this.element.find('.jstree-checked').removeClass('jstree-checked');
  813. /**
  814. * triggered when all nodes are unchecked (only if tie_selection in checkbox settings is false)
  815. * @event
  816. * @name uncheck_all.jstree
  817. * @param {Object} node the previous selection
  818. * @param {Array} selected the current selection
  819. * @plugin checkbox
  820. */
  821. this.trigger('uncheck_all', { 'selected' : this._data.checkbox.selected, 'node' : tmp });
  822. };
  823. /**
  824. * checks if a node is checked (if tie_selection is on in the settings this function will return the same as is_selected)
  825. * @name is_checked(obj)
  826. * @param {mixed} obj
  827. * @return {Boolean}
  828. * @plugin checkbox
  829. */
  830. this.is_checked = function (obj) {
  831. if(this.settings.checkbox.tie_selection) { return this.is_selected(obj); }
  832. obj = this.get_node(obj);
  833. if(!obj || obj.id === $.jstree.root) { return false; }
  834. return obj.state.checked;
  835. };
  836. /**
  837. * get an array of all checked nodes (if tie_selection is on in the settings this function will return the same as get_selected)
  838. * @name get_checked([full])
  839. * @param {mixed} full if set to `true` the returned array will consist of the full node objects, otherwise - only IDs will be returned
  840. * @return {Array}
  841. * @plugin checkbox
  842. */
  843. this.get_checked = function (full) {
  844. if(this.settings.checkbox.tie_selection) { return this.get_selected(full); }
  845. return full ? $.map(this._data.checkbox.selected, $.proxy(function (i) { return this.get_node(i); }, this)) : this._data.checkbox.selected.slice();
  846. };
  847. /**
  848. * get an array of all top level checked nodes (ignoring children of checked nodes) (if tie_selection is on in the settings this function will return the same as get_top_selected)
  849. * @name get_top_checked([full])
  850. * @param {mixed} full if set to `true` the returned array will consist of the full node objects, otherwise - only IDs will be returned
  851. * @return {Array}
  852. * @plugin checkbox
  853. */
  854. this.get_top_checked = function (full) {
  855. if(this.settings.checkbox.tie_selection) { return this.get_top_selected(full); }
  856. var tmp = this.get_checked(true),
  857. obj = {}, i, j, k, l;
  858. for(i = 0, j = tmp.length; i < j; i++) {
  859. obj[tmp[i].id] = tmp[i];
  860. }
  861. for(i = 0, j = tmp.length; i < j; i++) {
  862. for(k = 0, l = tmp[i].children_d.length; k < l; k++) {
  863. if(obj[tmp[i].children_d[k]]) {
  864. delete obj[tmp[i].children_d[k]];
  865. }
  866. }
  867. }
  868. tmp = [];
  869. for(i in obj) {
  870. if(obj.hasOwnProperty(i)) {
  871. tmp.push(i);
  872. }
  873. }
  874. return full ? $.map(tmp, $.proxy(function (i) { return this.get_node(i); }, this)) : tmp;
  875. };
  876. /**
  877. * get an array of all bottom level checked nodes (ignoring selected parents) (if tie_selection is on in the settings this function will return the same as get_bottom_selected)
  878. * @name get_bottom_checked([full])
  879. * @param {mixed} full if set to `true` the returned array will consist of the full node objects, otherwise - only IDs will be returned
  880. * @return {Array}
  881. * @plugin checkbox
  882. */
  883. this.get_bottom_checked = function (full) {
  884. if(this.settings.checkbox.tie_selection) { return this.get_bottom_selected(full); }
  885. var tmp = this.get_checked(true),
  886. obj = [], i, j;
  887. for(i = 0, j = tmp.length; i < j; i++) {
  888. if(!tmp[i].children.length) {
  889. obj.push(tmp[i].id);
  890. }
  891. }
  892. return full ? $.map(obj, $.proxy(function (i) { return this.get_node(i); }, this)) : obj;
  893. };
  894. this.load_node = function (obj, callback) {
  895. var k, l, i, j, c, tmp;
  896. if(!$.isArray(obj) && !this.settings.checkbox.tie_selection) {
  897. tmp = this.get_node(obj);
  898. if(tmp && tmp.state.loaded) {
  899. for(k = 0, l = tmp.children_d.length; k < l; k++) {
  900. if(this._model.data[tmp.children_d[k]].state.checked) {
  901. c = true;
  902. this._data.checkbox.selected = $.vakata.array_remove_item(this._data.checkbox.selected, tmp.children_d[k]);
  903. }
  904. }
  905. }
  906. }
  907. return parent.load_node.apply(this, arguments);
  908. };
  909. this.get_state = function () {
  910. var state = parent.get_state.apply(this, arguments);
  911. if(this.settings.checkbox.tie_selection) { return state; }
  912. state.checkbox = this._data.checkbox.selected.slice();
  913. return state;
  914. };
  915. this.set_state = function (state, callback) {
  916. var res = parent.set_state.apply(this, arguments);
  917. if(res && state.checkbox) {
  918. if(!this.settings.checkbox.tie_selection) {
  919. this.uncheck_all();
  920. var _this = this;
  921. $.each(state.checkbox, function (i, v) {
  922. _this.check_node(v);
  923. });
  924. }
  925. delete state.checkbox;
  926. this.set_state(state, callback);
  927. return false;
  928. }
  929. return res;
  930. };
  931. this.refresh = function (skip_loading, forget_state) {
  932. if(this.settings.checkbox.tie_selection) {
  933. this._data.checkbox.selected = [];
  934. }
  935. return parent.refresh.apply(this, arguments);
  936. };
  937. };
  938. // include the checkbox plugin by default
  939. // $.jstree.defaults.plugins.push("checkbox");
  940. }));