Subversion-Projekte lars-tiefland.ci

Revision

Details | Letzte Änderung | Log anzeigen | RSS feed

Revision Autor Zeilennr. Zeile
875 lars 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
 
22
	if($.jstree.plugins.checkbox) { return; }
23
 
24
	var _i = document.createElement('I');
25
	_i.className = 'jstree-icon jstree-checkbox';
26
	_i.setAttribute('role', 'presentation');
27
	/**
28
	 * stores all defaults for the checkbox plugin
29
	 * @name $.jstree.defaults.checkbox
30
	 * @plugin checkbox
31
	 */
32
	$.jstree.defaults.checkbox = {
33
		/**
34
		 * a boolean indicating if checkboxes should be visible (can be changed at a later time using `show_checkboxes()` and `hide_checkboxes`). Defaults to `true`.
35
		 * @name $.jstree.defaults.checkbox.visible
36
		 * @plugin checkbox
37
		 */
38
		visible				: true,
39
		/**
40
		 * a boolean indicating if checkboxes should cascade down and have an undetermined state. Defaults to `true`.
41
		 * @name $.jstree.defaults.checkbox.three_state
42
		 * @plugin checkbox
43
		 */
44
		three_state			: true,
45
		/**
46
		 * a boolean indicating if clicking anywhere on the node should act as clicking on the checkbox. Defaults to `true`.
47
		 * @name $.jstree.defaults.checkbox.whole_node
48
		 * @plugin checkbox
49
		 */
50
		whole_node			: true,
51
		/**
52
		 * a boolean indicating if the selected style of a node should be kept, or removed. Defaults to `true`.
53
		 * @name $.jstree.defaults.checkbox.keep_selected_style
54
		 * @plugin checkbox
55
		 */
56
		keep_selected_style	: true,
57
		/**
58
		 * This setting controls how cascading and undetermined nodes are applied.
59
		 * 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.
60
		 * If `three_state` is set to `true` this setting is automatically set to 'up+down+undetermined'. Defaults to ''.
61
		 * @name $.jstree.defaults.checkbox.cascade
62
		 * @plugin checkbox
63
		 */
64
		cascade				: '',
65
		/**
66
		 * 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.
67
		 * @name $.jstree.defaults.checkbox.tie_selection
68
		 * @plugin checkbox
69
		 */
70
		tie_selection		: true
71
	};
72
	$.jstree.plugins.checkbox = function (options, parent) {
73
		this.bind = function () {
74
			parent.bind.call(this);
75
			this._data.checkbox.uto = false;
76
			this._data.checkbox.selected = [];
77
			if(this.settings.checkbox.three_state) {
78
				this.settings.checkbox.cascade = 'up+down+undetermined';
79
			}
80
			this.element
81
				.on("init.jstree", $.proxy(function () {
82
						this._data.checkbox.visible = this.settings.checkbox.visible;
83
						if(!this.settings.checkbox.keep_selected_style) {
84
							this.element.addClass('jstree-checkbox-no-clicked');
85
						}
86
						if(this.settings.checkbox.tie_selection) {
87
							this.element.addClass('jstree-checkbox-selection');
88
						}
89
					}, this))
90
				.on("loading.jstree", $.proxy(function () {
91
						this[ this._data.checkbox.visible ? 'show_checkboxes' : 'hide_checkboxes' ]();
92
					}, this));
93
			if(this.settings.checkbox.cascade.indexOf('undetermined') !== -1) {
94
				this.element
95
					.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 () {
96
							// only if undetermined is in setting
97
							if(this._data.checkbox.uto) { clearTimeout(this._data.checkbox.uto); }
98
							this._data.checkbox.uto = setTimeout($.proxy(this._undetermined, this), 50);
99
						}, this));
100
			}
101
			if(!this.settings.checkbox.tie_selection) {
102
				this.element
103
					.on('model.jstree', $.proxy(function (e, data) {
104
						var m = this._model.data,
105
							p = m[data.parent],
106
							dpc = data.nodes,
107
							i, j;
108
						for(i = 0, j = dpc.length; i < j; i++) {
109
							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);
110
							if(m[dpc[i]].state.checked) {
111
								this._data.checkbox.selected.push(dpc[i]);
112
							}
113
						}
114
					}, this));
115
			}
116
			if(this.settings.checkbox.cascade.indexOf('up') !== -1 || this.settings.checkbox.cascade.indexOf('down') !== -1) {
117
				this.element
118
					.on('model.jstree', $.proxy(function (e, data) {
119
							var m = this._model.data,
120
								p = m[data.parent],
121
								dpc = data.nodes,
122
								chd = [],
123
								c, i, j, k, l, tmp, s = this.settings.checkbox.cascade, t = this.settings.checkbox.tie_selection;
124
 
125
							if(s.indexOf('down') !== -1) {
126
								// apply down
127
								if(p.state[ t ? 'selected' : 'checked' ]) {
128
									for(i = 0, j = dpc.length; i < j; i++) {
129
										m[dpc[i]].state[ t ? 'selected' : 'checked' ] = true;
130
									}
131
									this._data[ t ? 'core' : 'checkbox' ].selected = this._data[ t ? 'core' : 'checkbox' ].selected.concat(dpc);
132
								}
133
								else {
134
									for(i = 0, j = dpc.length; i < j; i++) {
135
										if(m[dpc[i]].state[ t ? 'selected' : 'checked' ]) {
136
											for(k = 0, l = m[dpc[i]].children_d.length; k < l; k++) {
137
												m[m[dpc[i]].children_d[k]].state[ t ? 'selected' : 'checked' ] = true;
138
											}
139
											this._data[ t ? 'core' : 'checkbox' ].selected = this._data[ t ? 'core' : 'checkbox' ].selected.concat(m[dpc[i]].children_d);
140
										}
141
									}
142
								}
143
							}
144
 
145
							if(s.indexOf('up') !== -1) {
146
								// apply up
147
								for(i = 0, j = p.children_d.length; i < j; i++) {
148
									if(!m[p.children_d[i]].children.length) {
149
										chd.push(m[p.children_d[i]].parent);
150
									}
151
								}
152
								chd = $.vakata.array_unique(chd);
153
								for(k = 0, l = chd.length; k < l; k++) {
154
									p = m[chd[k]];
155
									while(p && p.id !== $.jstree.root) {
156
										c = 0;
157
										for(i = 0, j = p.children.length; i < j; i++) {
158
											c += m[p.children[i]].state[ t ? 'selected' : 'checked' ];
159
										}
160
										if(c === j) {
161
											p.state[ t ? 'selected' : 'checked' ] = true;
162
											this._data[ t ? 'core' : 'checkbox' ].selected.push(p.id);
163
											tmp = this.get_node(p, true);
164
											if(tmp && tmp.length) {
165
												tmp.attr('aria-selected', true).children('.jstree-anchor').addClass( t ? 'jstree-clicked' : 'jstree-checked');
166
											}
167
										}
168
										else {
169
											break;
170
										}
171
										p = this.get_node(p.parent);
172
									}
173
								}
174
							}
175
 
176
							this._data[ t ? 'core' : 'checkbox' ].selected = $.vakata.array_unique(this._data[ t ? 'core' : 'checkbox' ].selected);
177
						}, this))
178
					.on(this.settings.checkbox.tie_selection ? 'select_node.jstree' : 'check_node.jstree', $.proxy(function (e, data) {
179
							var obj = data.node,
180
								m = this._model.data,
181
								par = this.get_node(obj.parent),
182
								dom = this.get_node(obj, true),
183
								i, j, c, tmp, s = this.settings.checkbox.cascade, t = this.settings.checkbox.tie_selection;
184
 
185
							// apply down
186
							if(s.indexOf('down') !== -1) {
187
								this._data[ t ? 'core' : 'checkbox' ].selected = $.vakata.array_unique(this._data[ t ? 'core' : 'checkbox' ].selected.concat(obj.children_d));
188
								for(i = 0, j = obj.children_d.length; i < j; i++) {
189
									tmp = m[obj.children_d[i]];
190
									tmp.state[ t ? 'selected' : 'checked' ] = true;
191
									if(tmp && tmp.original && tmp.original.state && tmp.original.state.undetermined) {
192
										tmp.original.state.undetermined = false;
193
									}
194
								}
195
							}
196
 
197
							// apply up
198
							if(s.indexOf('up') !== -1) {
199
								while(par && par.id !== $.jstree.root) {
200
									c = 0;
201
									for(i = 0, j = par.children.length; i < j; i++) {
202
										c += m[par.children[i]].state[ t ? 'selected' : 'checked' ];
203
									}
204
									if(c === j) {
205
										par.state[ t ? 'selected' : 'checked' ] = true;
206
										this._data[ t ? 'core' : 'checkbox' ].selected.push(par.id);
207
										tmp = this.get_node(par, true);
208
										if(tmp && tmp.length) {
209
											tmp.attr('aria-selected', true).children('.jstree-anchor').addClass(t ? 'jstree-clicked' : 'jstree-checked');
210
										}
211
									}
212
									else {
213
										break;
214
									}
215
									par = this.get_node(par.parent);
216
								}
217
							}
218
 
219
							// apply down (process .children separately?)
220
							if(s.indexOf('down') !== -1 && dom.length) {
221
								dom.find('.jstree-anchor').addClass(t ? 'jstree-clicked' : 'jstree-checked').parent().attr('aria-selected', true);
222
							}
223
						}, this))
224
					.on(this.settings.checkbox.tie_selection ? 'deselect_all.jstree' : 'uncheck_all.jstree', $.proxy(function (e, data) {
225
							var obj = this.get_node($.jstree.root),
226
								m = this._model.data,
227
								i, j, tmp;
228
							for(i = 0, j = obj.children_d.length; i < j; i++) {
229
								tmp = m[obj.children_d[i]];
230
								if(tmp && tmp.original && tmp.original.state && tmp.original.state.undetermined) {
231
									tmp.original.state.undetermined = false;
232
								}
233
							}
234
						}, this))
235
					.on(this.settings.checkbox.tie_selection ? 'deselect_node.jstree' : 'uncheck_node.jstree', $.proxy(function (e, data) {
236
							var obj = data.node,
237
								dom = this.get_node(obj, true),
238
								i, j, tmp, s = this.settings.checkbox.cascade, t = this.settings.checkbox.tie_selection;
239
							if(obj && obj.original && obj.original.state && obj.original.state.undetermined) {
240
								obj.original.state.undetermined = false;
241
							}
242
 
243
							// apply down
244
							if(s.indexOf('down') !== -1) {
245
								for(i = 0, j = obj.children_d.length; i < j; i++) {
246
									tmp = this._model.data[obj.children_d[i]];
247
									tmp.state[ t ? 'selected' : 'checked' ] = false;
248
									if(tmp && tmp.original && tmp.original.state && tmp.original.state.undetermined) {
249
										tmp.original.state.undetermined = false;
250
									}
251
								}
252
							}
253
 
254
							// apply up
255
							if(s.indexOf('up') !== -1) {
256
								for(i = 0, j = obj.parents.length; i < j; i++) {
257
									tmp = this._model.data[obj.parents[i]];
258
									tmp.state[ t ? 'selected' : 'checked' ] = false;
259
									if(tmp && tmp.original && tmp.original.state && tmp.original.state.undetermined) {
260
										tmp.original.state.undetermined = false;
261
									}
262
									tmp = this.get_node(obj.parents[i], true);
263
									if(tmp && tmp.length) {
264
										tmp.attr('aria-selected', false).children('.jstree-anchor').removeClass(t ? 'jstree-clicked' : 'jstree-checked');
265
									}
266
								}
267
							}
268
							tmp = [];
269
							for(i = 0, j = this._data[ t ? 'core' : 'checkbox' ].selected.length; i < j; i++) {
270
								// apply down + apply up
271
								if(
272
									(s.indexOf('down') === -1 || $.inArray(this._data[ t ? 'core' : 'checkbox' ].selected[i], obj.children_d) === -1) &&
273
									(s.indexOf('up') === -1 || $.inArray(this._data[ t ? 'core' : 'checkbox' ].selected[i], obj.parents) === -1)
274
								) {
275
									tmp.push(this._data[ t ? 'core' : 'checkbox' ].selected[i]);
276
								}
277
							}
278
							this._data[ t ? 'core' : 'checkbox' ].selected = $.vakata.array_unique(tmp);
279
 
280
							// apply down (process .children separately?)
281
							if(s.indexOf('down') !== -1 && dom.length) {
282
								dom.find('.jstree-anchor').removeClass(t ? 'jstree-clicked' : 'jstree-checked').parent().attr('aria-selected', false);
283
							}
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
		 * set the undetermined state where and if necessary. Used internally.
376
		 * @private
377
		 * @name _undetermined()
378
		 * @plugin checkbox
379
		 */
380
		this._undetermined = function () {
381
			if(this.element === null) { return; }
382
			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;
383
			for(i = 0, j = s.length; i < j; i++) {
384
				if(m[s[i]] && m[s[i]].parents) {
385
					for(k = 0, l = m[s[i]].parents.length; k < l; k++) {
386
						if(o[m[s[i]].parents[k]] === undefined && m[s[i]].parents[k] !== $.jstree.root) {
387
							o[m[s[i]].parents[k]] = true;
388
							p.push(m[s[i]].parents[k]);
389
						}
390
					}
391
				}
392
			}
393
			// attempt for server side undetermined state
394
			this.element.find('.jstree-closed').not(':has(.jstree-children)')
395
				.each(function () {
396
					var tmp = tt.get_node(this), tmp2;
397
					if(!tmp.state.loaded) {
398
						if(tmp.original && tmp.original.state && tmp.original.state.undetermined && tmp.original.state.undetermined === true) {
399
							if(o[tmp.id] === undefined && tmp.id !== $.jstree.root) {
400
								o[tmp.id] = true;
401
								p.push(tmp.id);
402
							}
403
							for(k = 0, l = tmp.parents.length; k < l; k++) {
404
								if(o[tmp.parents[k]] === undefined && tmp.parents[k] !== $.jstree.root) {
405
									o[tmp.parents[k]] = true;
406
									p.push(tmp.parents[k]);
407
								}
408
							}
409
						}
410
					}
411
					else {
412
						for(i = 0, j = tmp.children_d.length; i < j; i++) {
413
							tmp2 = m[tmp.children_d[i]];
414
							if(!tmp2.state.loaded && tmp2.original && tmp2.original.state && tmp2.original.state.undetermined && tmp2.original.state.undetermined === true) {
415
								if(o[tmp2.id] === undefined && tmp2.id !== $.jstree.root) {
416
									o[tmp2.id] = true;
417
									p.push(tmp2.id);
418
								}
419
								for(k = 0, l = tmp2.parents.length; k < l; k++) {
420
									if(o[tmp2.parents[k]] === undefined && tmp2.parents[k] !== $.jstree.root) {
421
										o[tmp2.parents[k]] = true;
422
										p.push(tmp2.parents[k]);
423
									}
424
								}
425
							}
426
						}
427
					}
428
				});
429
 
430
			this.element.find('.jstree-undetermined').removeClass('jstree-undetermined');
431
			for(i = 0, j = p.length; i < j; i++) {
432
				if(!m[p[i]].state[ t ? 'selected' : 'checked' ]) {
433
					s = this.get_node(p[i], true);
434
					if(s && s.length) {
435
						s.children('.jstree-anchor').children('.jstree-checkbox').addClass('jstree-undetermined');
436
					}
437
				}
438
			}
439
		};
440
		this.redraw_node = function(obj, deep, is_callback, force_render) {
441
			obj = parent.redraw_node.apply(this, arguments);
442
			if(obj) {
443
				var i, j, tmp = null, icon = null;
444
				for(i = 0, j = obj.childNodes.length; i < j; i++) {
445
					if(obj.childNodes[i] && obj.childNodes[i].className && obj.childNodes[i].className.indexOf("jstree-anchor") !== -1) {
446
						tmp = obj.childNodes[i];
447
						break;
448
					}
449
				}
450
				if(tmp) {
451
					if(!this.settings.checkbox.tie_selection && this._model.data[obj.id].state.checked) { tmp.className += ' jstree-checked'; }
452
					icon = _i.cloneNode(false);
453
					if(this._model.data[obj.id].state.checkbox_disabled) { icon.className += ' jstree-checkbox-disabled'; }
454
					tmp.insertBefore(icon, tmp.childNodes[0]);
455
				}
456
			}
457
			if(!is_callback && this.settings.checkbox.cascade.indexOf('undetermined') !== -1) {
458
				if(this._data.checkbox.uto) { clearTimeout(this._data.checkbox.uto); }
459
				this._data.checkbox.uto = setTimeout($.proxy(this._undetermined, this), 50);
460
			}
461
			return obj;
462
		};
463
		/**
464
		 * show the node checkbox icons
465
		 * @name show_checkboxes()
466
		 * @plugin checkbox
467
		 */
468
		this.show_checkboxes = function () { this._data.core.themes.checkboxes = true; this.get_container_ul().removeClass("jstree-no-checkboxes"); };
469
		/**
470
		 * hide the node checkbox icons
471
		 * @name hide_checkboxes()
472
		 * @plugin checkbox
473
		 */
474
		this.hide_checkboxes = function () { this._data.core.themes.checkboxes = false; this.get_container_ul().addClass("jstree-no-checkboxes"); };
475
		/**
476
		 * toggle the node icons
477
		 * @name toggle_checkboxes()
478
		 * @plugin checkbox
479
		 */
480
		this.toggle_checkboxes = function () { if(this._data.core.themes.checkboxes) { this.hide_checkboxes(); } else { this.show_checkboxes(); } };
481
		/**
482
		 * checks if a node is in an undetermined state
483
		 * @name is_undetermined(obj)
484
		 * @param  {mixed} obj
485
		 * @return {Boolean}
486
		 */
487
		this.is_undetermined = function (obj) {
488
			obj = this.get_node(obj);
489
			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;
490
			if(!obj || obj.state[ t ? 'selected' : 'checked' ] === true || s.indexOf('undetermined') === -1 || (s.indexOf('down') === -1 && s.indexOf('up') === -1)) {
491
				return false;
492
			}
493
			if(!obj.state.loaded && obj.original.state.undetermined === true) {
494
				return true;
495
			}
496
			for(i = 0, j = obj.children_d.length; i < j; i++) {
497
				if($.inArray(obj.children_d[i], d) !== -1 || (!m[obj.children_d[i]].state.loaded && m[obj.children_d[i]].original.state.undetermined)) {
498
					return true;
499
				}
500
			}
501
			return false;
502
		};
503
		/**
504
		 * disable a node's checkbox
505
		 * @name disable_checkbox(obj)
506
		 * @param {mixed} obj an array can be used too
507
		 * @trigger disable_checkbox.jstree
508
		 * @plugin checkbox
509
		 */
510
		this.disable_checkbox = function (obj) {
511
			var t1, t2, dom;
512
			if($.isArray(obj)) {
513
				obj = obj.slice();
514
				for(t1 = 0, t2 = obj.length; t1 < t2; t1++) {
515
					this.disable_checkbox(obj[t1]);
516
				}
517
				return true;
518
			}
519
			obj = this.get_node(obj);
520
			if(!obj || obj.id === $.jstree.root) {
521
				return false;
522
			}
523
			dom = this.get_node(obj, true);
524
			if(!obj.state.checkbox_disabled) {
525
				obj.state.checkbox_disabled = true;
526
				if(dom && dom.length) {
527
					dom.children('.jstree-anchor').children('.jstree-checkbox').addClass('jstree-checkbox-disabled');
528
				}
529
				/**
530
				 * triggered when an node's checkbox is disabled
531
				 * @event
532
				 * @name disable_checkbox.jstree
533
				 * @param {Object} node
534
				 * @plugin checkbox
535
				 */
536
				this.trigger('disable_checkbox', { 'node' : obj });
537
			}
538
		};
539
		/**
540
		 * enable a node's checkbox
541
		 * @name disable_checkbox(obj)
542
		 * @param {mixed} obj an array can be used too
543
		 * @trigger enable_checkbox.jstree
544
		 * @plugin checkbox
545
		 */
546
		this.enable_checkbox = function (obj) {
547
			var t1, t2, dom;
548
			if($.isArray(obj)) {
549
				obj = obj.slice();
550
				for(t1 = 0, t2 = obj.length; t1 < t2; t1++) {
551
					this.enable_checkbox(obj[t1]);
552
				}
553
				return true;
554
			}
555
			obj = this.get_node(obj);
556
			if(!obj || obj.id === $.jstree.root) {
557
				return false;
558
			}
559
			dom = this.get_node(obj, true);
560
			if(obj.state.checkbox_disabled) {
561
				obj.state.checkbox_disabled = false;
562
				if(dom && dom.length) {
563
					dom.children('.jstree-anchor').children('.jstree-checkbox').removeClass('jstree-checkbox-disabled');
564
				}
565
				/**
566
				 * triggered when an node's checkbox is enabled
567
				 * @event
568
				 * @name enable_checkbox.jstree
569
				 * @param {Object} node
570
				 * @plugin checkbox
571
				 */
572
				this.trigger('enable_checkbox', { 'node' : obj });
573
			}
574
		};
575
 
576
		this.activate_node = function (obj, e) {
577
			if($(e.target).hasClass('jstree-checkbox-disabled')) {
578
				return false;
579
			}
580
			if(this.settings.checkbox.tie_selection && (this.settings.checkbox.whole_node || $(e.target).hasClass('jstree-checkbox'))) {
581
				e.ctrlKey = true;
582
			}
583
			if(this.settings.checkbox.tie_selection || (!this.settings.checkbox.whole_node && !$(e.target).hasClass('jstree-checkbox'))) {
584
				return parent.activate_node.call(this, obj, e);
585
			}
586
			if(this.is_disabled(obj)) {
587
				return false;
588
			}
589
			if(this.is_checked(obj)) {
590
				this.uncheck_node(obj, e);
591
			}
592
			else {
593
				this.check_node(obj, e);
594
			}
595
			this.trigger('activate_node', { 'node' : this.get_node(obj) });
596
		};
597
 
598
		/**
599
		 * check a node (only if tie_selection in checkbox settings is false, otherwise select_node will be called internally)
600
		 * @name check_node(obj)
601
		 * @param {mixed} obj an array can be used to check multiple nodes
602
		 * @trigger check_node.jstree
603
		 * @plugin checkbox
604
		 */
605
		this.check_node = function (obj, e) {
606
			if(this.settings.checkbox.tie_selection) { return this.select_node(obj, false, true, e); }
607
			var dom, t1, t2, th;
608
			if($.isArray(obj)) {
609
				obj = obj.slice();
610
				for(t1 = 0, t2 = obj.length; t1 < t2; t1++) {
611
					this.check_node(obj[t1], e);
612
				}
613
				return true;
614
			}
615
			obj = this.get_node(obj);
616
			if(!obj || obj.id === $.jstree.root) {
617
				return false;
618
			}
619
			dom = this.get_node(obj, true);
620
			if(!obj.state.checked) {
621
				obj.state.checked = true;
622
				this._data.checkbox.selected.push(obj.id);
623
				if(dom && dom.length) {
624
					dom.children('.jstree-anchor').addClass('jstree-checked');
625
				}
626
				/**
627
				 * triggered when an node is checked (only if tie_selection in checkbox settings is false)
628
				 * @event
629
				 * @name check_node.jstree
630
				 * @param {Object} node
631
				 * @param {Array} selected the current selection
632
				 * @param {Object} event the event (if any) that triggered this check_node
633
				 * @plugin checkbox
634
				 */
635
				this.trigger('check_node', { 'node' : obj, 'selected' : this._data.checkbox.selected, 'event' : e });
636
			}
637
		};
638
		/**
639
		 * uncheck a node (only if tie_selection in checkbox settings is false, otherwise deselect_node will be called internally)
640
		 * @name uncheck_node(obj)
641
		 * @param {mixed} obj an array can be used to uncheck multiple nodes
642
		 * @trigger uncheck_node.jstree
643
		 * @plugin checkbox
644
		 */
645
		this.uncheck_node = function (obj, e) {
646
			if(this.settings.checkbox.tie_selection) { return this.deselect_node(obj, false, e); }
647
			var t1, t2, dom;
648
			if($.isArray(obj)) {
649
				obj = obj.slice();
650
				for(t1 = 0, t2 = obj.length; t1 < t2; t1++) {
651
					this.uncheck_node(obj[t1], e);
652
				}
653
				return true;
654
			}
655
			obj = this.get_node(obj);
656
			if(!obj || obj.id === $.jstree.root) {
657
				return false;
658
			}
659
			dom = this.get_node(obj, true);
660
			if(obj.state.checked) {
661
				obj.state.checked = false;
662
				this._data.checkbox.selected = $.vakata.array_remove_item(this._data.checkbox.selected, obj.id);
663
				if(dom.length) {
664
					dom.children('.jstree-anchor').removeClass('jstree-checked');
665
				}
666
				/**
667
				 * triggered when an node is unchecked (only if tie_selection in checkbox settings is false)
668
				 * @event
669
				 * @name uncheck_node.jstree
670
				 * @param {Object} node
671
				 * @param {Array} selected the current selection
672
				 * @param {Object} event the event (if any) that triggered this uncheck_node
673
				 * @plugin checkbox
674
				 */
675
				this.trigger('uncheck_node', { 'node' : obj, 'selected' : this._data.checkbox.selected, 'event' : e });
676
			}
677
		};
678
		/**
679
		 * checks all nodes in the tree (only if tie_selection in checkbox settings is false, otherwise select_all will be called internally)
680
		 * @name check_all()
681
		 * @trigger check_all.jstree, changed.jstree
682
		 * @plugin checkbox
683
		 */
684
		this.check_all = function () {
685
			if(this.settings.checkbox.tie_selection) { return this.select_all(); }
686
			var tmp = this._data.checkbox.selected.concat([]), i, j;
687
			this._data.checkbox.selected = this._model.data[$.jstree.root].children_d.concat();
688
			for(i = 0, j = this._data.checkbox.selected.length; i < j; i++) {
689
				if(this._model.data[this._data.checkbox.selected[i]]) {
690
					this._model.data[this._data.checkbox.selected[i]].state.checked = true;
691
				}
692
			}
693
			this.redraw(true);
694
			/**
695
			 * triggered when all nodes are checked (only if tie_selection in checkbox settings is false)
696
			 * @event
697
			 * @name check_all.jstree
698
			 * @param {Array} selected the current selection
699
			 * @plugin checkbox
700
			 */
701
			this.trigger('check_all', { 'selected' : this._data.checkbox.selected });
702
		};
703
		/**
704
		 * uncheck all checked nodes (only if tie_selection in checkbox settings is false, otherwise deselect_all will be called internally)
705
		 * @name uncheck_all()
706
		 * @trigger uncheck_all.jstree
707
		 * @plugin checkbox
708
		 */
709
		this.uncheck_all = function () {
710
			if(this.settings.checkbox.tie_selection) { return this.deselect_all(); }
711
			var tmp = this._data.checkbox.selected.concat([]), i, j;
712
			for(i = 0, j = this._data.checkbox.selected.length; i < j; i++) {
713
				if(this._model.data[this._data.checkbox.selected[i]]) {
714
					this._model.data[this._data.checkbox.selected[i]].state.checked = false;
715
				}
716
			}
717
			this._data.checkbox.selected = [];
718
			this.element.find('.jstree-checked').removeClass('jstree-checked');
719
			/**
720
			 * triggered when all nodes are unchecked (only if tie_selection in checkbox settings is false)
721
			 * @event
722
			 * @name uncheck_all.jstree
723
			 * @param {Object} node the previous selection
724
			 * @param {Array} selected the current selection
725
			 * @plugin checkbox
726
			 */
727
			this.trigger('uncheck_all', { 'selected' : this._data.checkbox.selected, 'node' : tmp });
728
		};
729
		/**
730
		 * checks if a node is checked (if tie_selection is on in the settings this function will return the same as is_selected)
731
		 * @name is_checked(obj)
732
		 * @param  {mixed}  obj
733
		 * @return {Boolean}
734
		 * @plugin checkbox
735
		 */
736
		this.is_checked = function (obj) {
737
			if(this.settings.checkbox.tie_selection) { return this.is_selected(obj); }
738
			obj = this.get_node(obj);
739
			if(!obj || obj.id === $.jstree.root) { return false; }
740
			return obj.state.checked;
741
		};
742
		/**
743
		 * get an array of all checked nodes (if tie_selection is on in the settings this function will return the same as get_selected)
744
		 * @name get_checked([full])
745
		 * @param  {mixed}  full if set to `true` the returned array will consist of the full node objects, otherwise - only IDs will be returned
746
		 * @return {Array}
747
		 * @plugin checkbox
748
		 */
749
		this.get_checked = function (full) {
750
			if(this.settings.checkbox.tie_selection) { return this.get_selected(full); }
751
			return full ? $.map(this._data.checkbox.selected, $.proxy(function (i) { return this.get_node(i); }, this)) : this._data.checkbox.selected;
752
		};
753
		/**
754
		 * 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)
755
		 * @name get_top_checked([full])
756
		 * @param  {mixed}  full if set to `true` the returned array will consist of the full node objects, otherwise - only IDs will be returned
757
		 * @return {Array}
758
		 * @plugin checkbox
759
		 */
760
		this.get_top_checked = function (full) {
761
			if(this.settings.checkbox.tie_selection) { return this.get_top_selected(full); }
762
			var tmp = this.get_checked(true),
763
				obj = {}, i, j, k, l;
764
			for(i = 0, j = tmp.length; i < j; i++) {
765
				obj[tmp[i].id] = tmp[i];
766
			}
767
			for(i = 0, j = tmp.length; i < j; i++) {
768
				for(k = 0, l = tmp[i].children_d.length; k < l; k++) {
769
					if(obj[tmp[i].children_d[k]]) {
770
						delete obj[tmp[i].children_d[k]];
771
					}
772
				}
773
			}
774
			tmp = [];
775
			for(i in obj) {
776
				if(obj.hasOwnProperty(i)) {
777
					tmp.push(i);
778
				}
779
			}
780
			return full ? $.map(tmp, $.proxy(function (i) { return this.get_node(i); }, this)) : tmp;
781
		};
782
		/**
783
		 * 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)
784
		 * @name get_bottom_checked([full])
785
		 * @param  {mixed}  full if set to `true` the returned array will consist of the full node objects, otherwise - only IDs will be returned
786
		 * @return {Array}
787
		 * @plugin checkbox
788
		 */
789
		this.get_bottom_checked = function (full) {
790
			if(this.settings.checkbox.tie_selection) { return this.get_bottom_selected(full); }
791
			var tmp = this.get_checked(true),
792
				obj = [], i, j;
793
			for(i = 0, j = tmp.length; i < j; i++) {
794
				if(!tmp[i].children.length) {
795
					obj.push(tmp[i].id);
796
				}
797
			}
798
			return full ? $.map(obj, $.proxy(function (i) { return this.get_node(i); }, this)) : obj;
799
		};
800
		this.load_node = function (obj, callback) {
801
			var k, l, i, j, c, tmp;
802
			if(!$.isArray(obj) && !this.settings.checkbox.tie_selection) {
803
				tmp = this.get_node(obj);
804
				if(tmp && tmp.state.loaded) {
805
					for(k = 0, l = tmp.children_d.length; k < l; k++) {
806
						if(this._model.data[tmp.children_d[k]].state.checked) {
807
							c = true;
808
							this._data.checkbox.selected = $.vakata.array_remove_item(this._data.checkbox.selected, tmp.children_d[k]);
809
						}
810
					}
811
				}
812
			}
813
			return parent.load_node.apply(this, arguments);
814
		};
815
		this.get_state = function () {
816
			var state = parent.get_state.apply(this, arguments);
817
			if(this.settings.checkbox.tie_selection) { return state; }
818
			state.checkbox = this._data.checkbox.selected.slice();
819
			return state;
820
		};
821
		this.set_state = function (state, callback) {
822
			var res = parent.set_state.apply(this, arguments);
823
			if(res && state.checkbox) {
824
				if(!this.settings.checkbox.tie_selection) {
825
					this.uncheck_all();
826
					var _this = this;
827
					$.each(state.checkbox, function (i, v) {
828
						_this.check_node(v);
829
					});
830
				}
831
				delete state.checkbox;
832
				this.set_state(state, callback);
833
				return false;
834
			}
835
			return res;
836
		};
837
		this.refresh = function (skip_loading, forget_state) {
838
			if(!this.settings.checkbox.tie_selection) {
839
				this._data.checkbox.selected = [];
840
			}
841
			return parent.refresh.apply(this, arguments);
842
		};
843
	};
844
 
845
	// include the checkbox plugin by default
846
	// $.jstree.defaults.plugins.push("checkbox");
847
}));