jstree.checkbox.js 36 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977
  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.js'], factory);
  12. }
  13. else if(typeof exports === 'object') {
  14. factory(require('jquery'), require('./jstree.js'));
  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", 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. }.bind(this))
  100. .on("loading.jstree", function () {
  101. this[ this._data.checkbox.visible ? 'show_checkboxes' : 'hide_checkboxes' ]();
  102. }.bind(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', 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(this._undetermined.bind(this), 50);
  109. }.bind(this));
  110. }
  111. if(!this.settings.checkbox.tie_selection) {
  112. this.element
  113. .on('model.jstree', 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. }.bind(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', 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. }.bind(this))
  185. .on(this.settings.checkbox.tie_selection ? 'select_node.jstree' : 'check_node.jstree', 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. }.bind(this))
  239. .on(this.settings.checkbox.tie_selection ? 'deselect_all.jstree' : 'uncheck_all.jstree', 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. }.bind(this))
  250. .on(this.settings.checkbox.tie_selection ? 'deselect_node.jstree' : 'uncheck_node.jstree', 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. }.bind(this));
  285. }
  286. if(this.settings.checkbox.cascade.indexOf('up') !== -1) {
  287. this.element
  288. .on('delete_node.jstree', 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. }.bind(this))
  312. .on('move_node.jstree', 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. }.bind(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(this._undetermined.bind(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($.vakata.is_array(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($.vakata.is_array(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. * @name _cascade_new_checked_state(id, checkedState)
  621. * @param {string} id the node ID
  622. * @param {bool} checkedState should the nodes be checked or not
  623. * @returns {Array} Array of all node id's (in this tree branch) that are checked.
  624. */
  625. this._cascade_new_checked_state = function (id, checkedState) {
  626. var self = this;
  627. var t = this.settings.checkbox.tie_selection;
  628. var node = this._model.data[id];
  629. var selectedNodeIds = [];
  630. var selectedChildrenIds = [], i, j, selectedChildIds;
  631. if (
  632. (this.settings.checkbox.cascade_to_disabled || !node.state.disabled) &&
  633. (this.settings.checkbox.cascade_to_hidden || !node.state.hidden)
  634. ) {
  635. //First try and check/uncheck the children
  636. if (node.children) {
  637. for (i = 0, j = node.children.length; i < j; i++) {
  638. var childId = node.children[i];
  639. selectedChildIds = self._cascade_new_checked_state(childId, checkedState);
  640. selectedNodeIds = selectedNodeIds.concat(selectedChildIds);
  641. if (selectedChildIds.indexOf(childId) > -1) {
  642. selectedChildrenIds.push(childId);
  643. }
  644. }
  645. }
  646. var dom = self.get_node(node, true);
  647. //A node's state is undetermined if some but not all of it's children are checked/selected .
  648. var undetermined = selectedChildrenIds.length > 0 && selectedChildrenIds.length < node.children.length;
  649. if(node.original && node.original.state && node.original.state.undetermined) {
  650. node.original.state.undetermined = undetermined;
  651. }
  652. //If a node is undetermined then remove selected class
  653. if (undetermined) {
  654. node.state[ t ? 'selected' : 'checked' ] = false;
  655. dom.attr('aria-selected', false).children('.jstree-anchor').removeClass(t ? 'jstree-clicked' : 'jstree-checked');
  656. }
  657. //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),
  658. //check the node and style it correctly.
  659. else if (checkedState && selectedChildrenIds.length === node.children.length) {
  660. node.state[ t ? 'selected' : 'checked' ] = checkedState;
  661. selectedNodeIds.push(node.id);
  662. dom.attr('aria-selected', true).children('.jstree-anchor').addClass(t ? 'jstree-clicked' : 'jstree-checked');
  663. }
  664. else {
  665. node.state[ t ? 'selected' : 'checked' ] = false;
  666. dom.attr('aria-selected', false).children('.jstree-anchor').removeClass(t ? 'jstree-clicked' : 'jstree-checked');
  667. }
  668. }
  669. else {
  670. selectedChildIds = this.get_checked_descendants(id);
  671. if (node.state[ t ? 'selected' : 'checked' ]) {
  672. selectedChildIds.push(node.id);
  673. }
  674. selectedNodeIds = selectedNodeIds.concat(selectedChildIds);
  675. }
  676. return selectedNodeIds;
  677. };
  678. /**
  679. * Gets ids of nodes selected in branch (of tree) specified by id (does not include the node specified by id)
  680. * @name get_checked_descendants(obj)
  681. * @param {string} id the node ID
  682. * @return {Array} array of IDs
  683. * @plugin checkbox
  684. */
  685. this.get_checked_descendants = function (id) {
  686. var self = this;
  687. var t = self.settings.checkbox.tie_selection;
  688. var node = self._model.data[id];
  689. return $.vakata.array_filter(node.children_d, function(_id) {
  690. return self._model.data[_id].state[ t ? 'selected' : 'checked' ];
  691. });
  692. };
  693. /**
  694. * check a node (only if tie_selection in checkbox settings is false, otherwise select_node will be called internally)
  695. * @name check_node(obj)
  696. * @param {mixed} obj an array can be used to check multiple nodes
  697. * @trigger check_node.jstree
  698. * @plugin checkbox
  699. */
  700. this.check_node = function (obj, e) {
  701. if(this.settings.checkbox.tie_selection) { return this.select_node(obj, false, true, e); }
  702. var dom, t1, t2, th;
  703. if($.vakata.is_array(obj)) {
  704. obj = obj.slice();
  705. for(t1 = 0, t2 = obj.length; t1 < t2; t1++) {
  706. this.check_node(obj[t1], e);
  707. }
  708. return true;
  709. }
  710. obj = this.get_node(obj);
  711. if(!obj || obj.id === $.jstree.root) {
  712. return false;
  713. }
  714. dom = this.get_node(obj, true);
  715. if(!obj.state.checked) {
  716. obj.state.checked = true;
  717. this._data.checkbox.selected.push(obj.id);
  718. if(dom && dom.length) {
  719. dom.children('.jstree-anchor').addClass('jstree-checked');
  720. }
  721. /**
  722. * triggered when an node is checked (only if tie_selection in checkbox settings is false)
  723. * @event
  724. * @name check_node.jstree
  725. * @param {Object} node
  726. * @param {Array} selected the current selection
  727. * @param {Object} event the event (if any) that triggered this check_node
  728. * @plugin checkbox
  729. */
  730. this.trigger('check_node', { 'node' : obj, 'selected' : this._data.checkbox.selected, 'event' : e });
  731. }
  732. };
  733. /**
  734. * uncheck a node (only if tie_selection in checkbox settings is false, otherwise deselect_node will be called internally)
  735. * @name uncheck_node(obj)
  736. * @param {mixed} obj an array can be used to uncheck multiple nodes
  737. * @trigger uncheck_node.jstree
  738. * @plugin checkbox
  739. */
  740. this.uncheck_node = function (obj, e) {
  741. if(this.settings.checkbox.tie_selection) { return this.deselect_node(obj, false, e); }
  742. var t1, t2, dom;
  743. if($.vakata.is_array(obj)) {
  744. obj = obj.slice();
  745. for(t1 = 0, t2 = obj.length; t1 < t2; t1++) {
  746. this.uncheck_node(obj[t1], e);
  747. }
  748. return true;
  749. }
  750. obj = this.get_node(obj);
  751. if(!obj || obj.id === $.jstree.root) {
  752. return false;
  753. }
  754. dom = this.get_node(obj, true);
  755. if(obj.state.checked) {
  756. obj.state.checked = false;
  757. this._data.checkbox.selected = $.vakata.array_remove_item(this._data.checkbox.selected, obj.id);
  758. if(dom.length) {
  759. dom.children('.jstree-anchor').removeClass('jstree-checked');
  760. }
  761. /**
  762. * triggered when an node is unchecked (only if tie_selection in checkbox settings is false)
  763. * @event
  764. * @name uncheck_node.jstree
  765. * @param {Object} node
  766. * @param {Array} selected the current selection
  767. * @param {Object} event the event (if any) that triggered this uncheck_node
  768. * @plugin checkbox
  769. */
  770. this.trigger('uncheck_node', { 'node' : obj, 'selected' : this._data.checkbox.selected, 'event' : e });
  771. }
  772. };
  773. /**
  774. * checks all nodes in the tree (only if tie_selection in checkbox settings is false, otherwise select_all will be called internally)
  775. * @name check_all()
  776. * @trigger check_all.jstree, changed.jstree
  777. * @plugin checkbox
  778. */
  779. this.check_all = function () {
  780. if(this.settings.checkbox.tie_selection) { return this.select_all(); }
  781. var tmp = this._data.checkbox.selected.concat([]), i, j;
  782. this._data.checkbox.selected = this._model.data[$.jstree.root].children_d.concat();
  783. for(i = 0, j = this._data.checkbox.selected.length; i < j; i++) {
  784. if(this._model.data[this._data.checkbox.selected[i]]) {
  785. this._model.data[this._data.checkbox.selected[i]].state.checked = true;
  786. }
  787. }
  788. this.redraw(true);
  789. /**
  790. * triggered when all nodes are checked (only if tie_selection in checkbox settings is false)
  791. * @event
  792. * @name check_all.jstree
  793. * @param {Array} selected the current selection
  794. * @plugin checkbox
  795. */
  796. this.trigger('check_all', { 'selected' : this._data.checkbox.selected });
  797. };
  798. /**
  799. * uncheck all checked nodes (only if tie_selection in checkbox settings is false, otherwise deselect_all will be called internally)
  800. * @name uncheck_all()
  801. * @trigger uncheck_all.jstree
  802. * @plugin checkbox
  803. */
  804. this.uncheck_all = function () {
  805. if(this.settings.checkbox.tie_selection) { return this.deselect_all(); }
  806. var tmp = this._data.checkbox.selected.concat([]), i, j;
  807. for(i = 0, j = this._data.checkbox.selected.length; i < j; i++) {
  808. if(this._model.data[this._data.checkbox.selected[i]]) {
  809. this._model.data[this._data.checkbox.selected[i]].state.checked = false;
  810. }
  811. }
  812. this._data.checkbox.selected = [];
  813. this.element.find('.jstree-checked').removeClass('jstree-checked');
  814. /**
  815. * triggered when all nodes are unchecked (only if tie_selection in checkbox settings is false)
  816. * @event
  817. * @name uncheck_all.jstree
  818. * @param {Object} node the previous selection
  819. * @param {Array} selected the current selection
  820. * @plugin checkbox
  821. */
  822. this.trigger('uncheck_all', { 'selected' : this._data.checkbox.selected, 'node' : tmp });
  823. };
  824. /**
  825. * checks if a node is checked (if tie_selection is on in the settings this function will return the same as is_selected)
  826. * @name is_checked(obj)
  827. * @param {mixed} obj
  828. * @return {Boolean}
  829. * @plugin checkbox
  830. */
  831. this.is_checked = function (obj) {
  832. if(this.settings.checkbox.tie_selection) { return this.is_selected(obj); }
  833. obj = this.get_node(obj);
  834. if(!obj || obj.id === $.jstree.root) { return false; }
  835. return obj.state.checked;
  836. };
  837. /**
  838. * get an array of all checked nodes (if tie_selection is on in the settings this function will return the same as get_selected)
  839. * @name get_checked([full])
  840. * @param {mixed} full if set to `true` the returned array will consist of the full node objects, otherwise - only IDs will be returned
  841. * @return {Array}
  842. * @plugin checkbox
  843. */
  844. this.get_checked = function (full) {
  845. if(this.settings.checkbox.tie_selection) { return this.get_selected(full); }
  846. return full ? $.map(this._data.checkbox.selected, function (i) { return this.get_node(i); }.bind(this)) : this._data.checkbox.selected.slice();
  847. };
  848. /**
  849. * 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)
  850. * @name get_top_checked([full])
  851. * @param {mixed} full if set to `true` the returned array will consist of the full node objects, otherwise - only IDs will be returned
  852. * @return {Array}
  853. * @plugin checkbox
  854. */
  855. this.get_top_checked = function (full) {
  856. if(this.settings.checkbox.tie_selection) { return this.get_top_selected(full); }
  857. var tmp = this.get_checked(true),
  858. obj = {}, i, j, k, l;
  859. for(i = 0, j = tmp.length; i < j; i++) {
  860. obj[tmp[i].id] = tmp[i];
  861. }
  862. for(i = 0, j = tmp.length; i < j; i++) {
  863. for(k = 0, l = tmp[i].children_d.length; k < l; k++) {
  864. if(obj[tmp[i].children_d[k]]) {
  865. delete obj[tmp[i].children_d[k]];
  866. }
  867. }
  868. }
  869. tmp = [];
  870. for(i in obj) {
  871. if(obj.hasOwnProperty(i)) {
  872. tmp.push(i);
  873. }
  874. }
  875. return full ? $.map(tmp, function (i) { return this.get_node(i); }.bind(this)) : tmp;
  876. };
  877. /**
  878. * 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)
  879. * @name get_bottom_checked([full])
  880. * @param {mixed} full if set to `true` the returned array will consist of the full node objects, otherwise - only IDs will be returned
  881. * @return {Array}
  882. * @plugin checkbox
  883. */
  884. this.get_bottom_checked = function (full) {
  885. if(this.settings.checkbox.tie_selection) { return this.get_bottom_selected(full); }
  886. var tmp = this.get_checked(true),
  887. obj = [], i, j;
  888. for(i = 0, j = tmp.length; i < j; i++) {
  889. if(!tmp[i].children.length) {
  890. obj.push(tmp[i].id);
  891. }
  892. }
  893. return full ? $.map(obj, function (i) { return this.get_node(i); }.bind(this)) : obj;
  894. };
  895. this.load_node = function (obj, callback) {
  896. var k, l, i, j, c, tmp;
  897. if(!$.vakata.is_array(obj) && !this.settings.checkbox.tie_selection) {
  898. tmp = this.get_node(obj);
  899. if(tmp && tmp.state.loaded) {
  900. for(k = 0, l = tmp.children_d.length; k < l; k++) {
  901. if(this._model.data[tmp.children_d[k]].state.checked) {
  902. c = true;
  903. this._data.checkbox.selected = $.vakata.array_remove_item(this._data.checkbox.selected, tmp.children_d[k]);
  904. }
  905. }
  906. }
  907. }
  908. return parent.load_node.apply(this, arguments);
  909. };
  910. this.get_state = function () {
  911. var state = parent.get_state.apply(this, arguments);
  912. if(this.settings.checkbox.tie_selection) { return state; }
  913. state.checkbox = this._data.checkbox.selected.slice();
  914. return state;
  915. };
  916. this.set_state = function (state, callback) {
  917. var res = parent.set_state.apply(this, arguments);
  918. if(res && state.checkbox) {
  919. if(!this.settings.checkbox.tie_selection) {
  920. this.uncheck_all();
  921. var _this = this;
  922. $.each(state.checkbox, function (i, v) {
  923. _this.check_node(v);
  924. });
  925. }
  926. delete state.checkbox;
  927. this.set_state(state, callback);
  928. return false;
  929. }
  930. return res;
  931. };
  932. this.refresh = function (skip_loading, forget_state) {
  933. if(this.settings.checkbox.tie_selection) {
  934. this._data.checkbox.selected = [];
  935. }
  936. return parent.refresh.apply(this, arguments);
  937. };
  938. };
  939. // include the checkbox plugin by default
  940. // $.jstree.defaults.plugins.push("checkbox");
  941. }));