Subversion-Projekte lars-tiefland.webanos.marine-sales.de

Revision

Details | Letzte Änderung | Log anzeigen | RSS feed

Revision Autor Zeilennr. Zeile
2 lars 1
/*globals jQuery, define, module, exports, require, window, document, postMessage */
2
(function (factory) {
3
	"use strict";
4
	if (typeof define === 'function' && define.amd) {
5
		define(['jquery'], factory);
6
	}
7
	else if(typeof module !== 'undefined' && module.exports) {
8
		module.exports = factory(require('jquery'));
9
	}
10
	else {
11
		factory(jQuery);
12
	}
13
}(function ($, undefined) {
14
	"use strict";
15
/*!
16
 * jsTree 3.3.12
17
 * http://jstree.com/
18
 *
19
 * Copyright (c) 2014 Ivan Bozhanov (http://vakata.com)
20
 *
21
 * Licensed same as jquery - under the terms of the MIT License
22
 *   http://www.opensource.org/licenses/mit-license.php
23
 */
24
/*!
25
 * if using jslint please allow for the jQuery global and use following options:
26
 * jslint: loopfunc: true, browser: true, ass: true, bitwise: true, continue: true, nomen: true, plusplus: true, regexp: true, unparam: true, todo: true, white: true
27
 */
28
/*jshint -W083 */
29
 
30
	// prevent another load? maybe there is a better way?
31
	if($.jstree) {
32
		return;
33
	}
34
 
35
	/**
36
	 * ### jsTree core functionality
37
	 */
38
 
39
	// internal variables
40
	var instance_counter = 0,
41
		ccp_node = false,
42
		ccp_mode = false,
43
		ccp_inst = false,
44
		themes_loaded = [],
45
		src = $('script:last').attr('src'),
46
		document = window.document; // local variable is always faster to access then a global
47
 
48
	var setImmediate = window.setImmediate;
49
	var Promise = window.Promise;
50
	if (!setImmediate && Promise) {
51
		// Good enough approximation of setImmediate
52
		setImmediate = function (cb, arg) {
53
			Promise.resolve(arg).then(cb);
54
		};
55
	}
56
 
57
	/**
58
	 * holds all jstree related functions and variables, including the actual class and methods to create, access and manipulate instances.
59
	 * @name $.jstree
60
	 */
61
	$.jstree = {
62
		/**
63
		 * specifies the jstree version in use
64
		 * @name $.jstree.version
65
		 */
66
		version : '3.3.12',
67
		/**
68
		 * holds all the default options used when creating new instances
69
		 * @name $.jstree.defaults
70
		 */
71
		defaults : {
72
			/**
73
			 * configure which plugins will be active on an instance. Should be an array of strings, where each element is a plugin name. The default is `[]`
74
			 * @name $.jstree.defaults.plugins
75
			 */
76
			plugins : []
77
		},
78
		/**
79
		 * stores all loaded jstree plugins (used internally)
80
		 * @name $.jstree.plugins
81
		 */
82
		plugins : {},
83
		path : src && src.indexOf('/') !== -1 ? src.replace(/\/[^\/]+$/,'') : '',
84
		idregex : /[\\:&!^|()\[\]<>@*'+~#";.,=\- \/${}%?`]/g,
85
		root : '#'
86
	};
87
 
88
	/**
89
	 * creates a jstree instance
90
	 * @name $.jstree.create(el [, options])
91
	 * @param {DOMElement|jQuery|String} el the element to create the instance on, can be jQuery extended or a selector
92
	 * @param {Object} options options for this instance (extends `$.jstree.defaults`)
93
	 * @return {jsTree} the new instance
94
	 */
95
	$.jstree.create = function (el, options) {
96
		var tmp = new $.jstree.core(++instance_counter),
97
			opt = options;
98
		options = $.extend(true, {}, $.jstree.defaults, options);
99
		if(opt && opt.plugins) {
100
			options.plugins = opt.plugins;
101
		}
102
		$.each(options.plugins, function (i, k) {
103
			if(i !== 'core') {
104
				tmp = tmp.plugin(k, options[k]);
105
			}
106
		});
107
		$(el).data('jstree', tmp);
108
		tmp.init(el, options);
109
		return tmp;
110
	};
111
	/**
112
	 * remove all traces of jstree from the DOM and destroy all instances
113
	 * @name $.jstree.destroy()
114
	 */
115
	$.jstree.destroy = function () {
116
		$('.jstree:jstree').jstree('destroy');
117
		$(document).off('.jstree');
118
	};
119
	/**
120
	 * the jstree class constructor, used only internally
121
	 * @private
122
	 * @name $.jstree.core(id)
123
	 * @param {Number} id this instance's index
124
	 */
125
	$.jstree.core = function (id) {
126
		this._id = id;
127
		this._cnt = 0;
128
		this._wrk = null;
129
		this._data = {
130
			core : {
131
				themes : {
132
					name : false,
133
					dots : false,
134
					icons : false,
135
					ellipsis : false
136
				},
137
				selected : [],
138
				last_error : {},
139
				working : false,
140
				worker_queue : [],
141
				focused : null
142
			}
143
		};
144
	};
145
	/**
146
	 * get a reference to an existing instance
147
	 *
148
	 * __Examples__
149
	 *
150
	 *	// provided a container with an ID of "tree", and a nested node with an ID of "branch"
151
	 *	// all of there will return the same instance
152
	 *	$.jstree.reference('tree');
153
	 *	$.jstree.reference('#tree');
154
	 *	$.jstree.reference($('#tree'));
155
	 *	$.jstree.reference(document.getElementByID('tree'));
156
	 *	$.jstree.reference('branch');
157
	 *	$.jstree.reference('#branch');
158
	 *	$.jstree.reference($('#branch'));
159
	 *	$.jstree.reference(document.getElementByID('branch'));
160
	 *
161
	 * @name $.jstree.reference(needle)
162
	 * @param {DOMElement|jQuery|String} needle
163
	 * @return {jsTree|null} the instance or `null` if not found
164
	 */
165
	$.jstree.reference = function (needle) {
166
		var tmp = null,
167
			obj = null;
168
		if(needle && needle.id && (!needle.tagName || !needle.nodeType)) { needle = needle.id; }
169
 
170
		if(!obj || !obj.length) {
171
			try { obj = $(needle); } catch (ignore) { }
172
		}
173
		if(!obj || !obj.length) {
174
			try { obj = $('#' + needle.replace($.jstree.idregex,'\\$&')); } catch (ignore) { }
175
		}
176
		if(obj && obj.length && (obj = obj.closest('.jstree')).length && (obj = obj.data('jstree'))) {
177
			tmp = obj;
178
		}
179
		else {
180
			$('.jstree').each(function () {
181
				var inst = $(this).data('jstree');
182
				if(inst && inst._model.data[needle]) {
183
					tmp = inst;
184
					return false;
185
				}
186
			});
187
		}
188
		return tmp;
189
	};
190
	/**
191
	 * Create an instance, get an instance or invoke a command on a instance.
192
	 *
193
	 * If there is no instance associated with the current node a new one is created and `arg` is used to extend `$.jstree.defaults` for this new instance. There would be no return value (chaining is not broken).
194
	 *
195
	 * If there is an existing instance and `arg` is a string the command specified by `arg` is executed on the instance, with any additional arguments passed to the function. If the function returns a value it will be returned (chaining could break depending on function).
196
	 *
197
	 * If there is an existing instance and `arg` is not a string the instance itself is returned (similar to `$.jstree.reference`).
198
	 *
199
	 * In any other case - nothing is returned and chaining is not broken.
200
	 *
201
	 * __Examples__
202
	 *
203
	 *	$('#tree1').jstree(); // creates an instance
204
	 *	$('#tree2').jstree({ plugins : [] }); // create an instance with some options
205
	 *	$('#tree1').jstree('open_node', '#branch_1'); // call a method on an existing instance, passing additional arguments
206
	 *	$('#tree2').jstree(); // get an existing instance (or create an instance)
207
	 *	$('#tree2').jstree(true); // get an existing instance (will not create new instance)
208
	 *	$('#branch_1').jstree().select_node('#branch_1'); // get an instance (using a nested element and call a method)
209
	 *
210
	 * @name $().jstree([arg])
211
	 * @param {String|Object} arg
212
	 * @return {Mixed}
213
	 */
214
	$.fn.jstree = function (arg) {
215
		// check for string argument
216
		var is_method	= (typeof arg === 'string'),
217
			args		= Array.prototype.slice.call(arguments, 1),
218
			result		= null;
219
		if(arg === true && !this.length) { return false; }
220
		this.each(function () {
221
			// get the instance (if there is one) and method (if it exists)
222
			var instance = $.jstree.reference(this),
223
				method = is_method && instance ? instance[arg] : null;
224
			// if calling a method, and method is available - execute on the instance
225
			result = is_method && method ?
226
				method.apply(instance, args) :
227
				null;
228
			// if there is no instance and no method is being called - create one
229
			if(!instance && !is_method && (arg === undefined || $.isPlainObject(arg))) {
230
				$.jstree.create(this, arg);
231
			}
232
			// if there is an instance and no method is called - return the instance
233
			if( (instance && !is_method) || arg === true ) {
234
				result = instance || false;
235
			}
236
			// if there was a method call which returned a result - break and return the value
237
			if(result !== null && result !== undefined) {
238
				return false;
239
			}
240
		});
241
		// if there was a method call with a valid return value - return that, otherwise continue the chain
242
		return result !== null && result !== undefined ?
243
			result : this;
244
	};
245
	/**
246
	 * used to find elements containing an instance
247
	 *
248
	 * __Examples__
249
	 *
250
	 *	$('div:jstree').each(function () {
251
	 *		$(this).jstree('destroy');
252
	 *	});
253
	 *
254
	 * @name $(':jstree')
255
	 * @return {jQuery}
256
	 */
257
	$.expr.pseudos.jstree = $.expr.createPseudo(function(search) {
258
		return function(a) {
259
			return $(a).hasClass('jstree') &&
260
				$(a).data('jstree') !== undefined;
261
		};
262
	});
263
 
264
	/**
265
	 * stores all defaults for the core
266
	 * @name $.jstree.defaults.core
267
	 */
268
	$.jstree.defaults.core = {
269
		/**
270
		 * data configuration
271
		 *
272
		 * If left as `false` the HTML inside the jstree container element is used to populate the tree (that should be an unordered list with list items).
273
		 *
274
		 * You can also pass in a HTML string or a JSON array here.
275
		 *
276
		 * It is possible to pass in a standard jQuery-like AJAX config and jstree will automatically determine if the response is JSON or HTML and use that to populate the tree.
277
		 * In addition to the standard jQuery ajax options here you can supply functions for `data` and `url`, the functions will be run in the current instance's scope and a param will be passed indicating which node is being loaded, the return value of those functions will be used.
278
		 *
279
		 * The last option is to specify a function, that function will receive the node being loaded as argument and a second param which is a function which should be called with the result.
280
		 *
281
		 * __Examples__
282
		 *
283
		 *	// AJAX
284
		 *	$('#tree').jstree({
285
		 *		'core' : {
286
		 *			'data' : {
287
		 *				'url' : '/get/children/',
288
		 *				'data' : function (node) {
289
		 *					return { 'id' : node.id };
290
		 *				}
291
		 *			}
292
		 *		});
293
		 *
294
		 *	// direct data
295
		 *	$('#tree').jstree({
296
		 *		'core' : {
297
		 *			'data' : [
298
		 *				'Simple root node',
299
		 *				{
300
		 *					'id' : 'node_2',
301
		 *					'text' : 'Root node with options',
302
		 *					'state' : { 'opened' : true, 'selected' : true },
303
		 *					'children' : [ { 'text' : 'Child 1' }, 'Child 2']
304
		 *				}
305
		 *			]
306
		 *		}
307
		 *	});
308
		 *
309
		 *	// function
310
		 *	$('#tree').jstree({
311
		 *		'core' : {
312
		 *			'data' : function (obj, callback) {
313
		 *				callback.call(this, ['Root 1', 'Root 2']);
314
		 *			}
315
		 *		});
316
		 *
317
		 * @name $.jstree.defaults.core.data
318
		 */
319
		data			: false,
320
		/**
321
		 * configure the various strings used throughout the tree
322
		 *
323
		 * You can use an object where the key is the string you need to replace and the value is your replacement.
324
		 * Another option is to specify a function which will be called with an argument of the needed string and should return the replacement.
325
		 * If left as `false` no replacement is made.
326
		 *
327
		 * __Examples__
328
		 *
329
		 *	$('#tree').jstree({
330
		 *		'core' : {
331
		 *			'strings' : {
332
		 *				'Loading ...' : 'Please wait ...'
333
		 *			}
334
		 *		}
335
		 *	});
336
		 *
337
		 * @name $.jstree.defaults.core.strings
338
		 */
339
		strings			: false,
340
		/**
341
		 * determines what happens when a user tries to modify the structure of the tree
342
		 * If left as `false` all operations like create, rename, delete, move or copy are prevented.
343
		 * You can set this to `true` to allow all interactions or use a function to have better control.
344
		 *
345
		 * __Examples__
346
		 *
347
		 *	$('#tree').jstree({
348
		 *		'core' : {
349
		 *			'check_callback' : function (operation, node, node_parent, node_position, more) {
350
		 *				// operation can be 'create_node', 'rename_node', 'delete_node', 'move_node', 'copy_node' or 'edit'
351
		 *				// in case of 'rename_node' node_position is filled with the new node name
352
		 *				return operation === 'rename_node' ? true : false;
353
		 *			}
354
		 *		}
355
		 *	});
356
		 *
357
		 * @name $.jstree.defaults.core.check_callback
358
		 */
359
		check_callback	: false,
360
		/**
361
		 * a callback called with a single object parameter in the instance's scope when something goes wrong (operation prevented, ajax failed, etc)
362
		 * @name $.jstree.defaults.core.error
363
		 */
364
		error			: $.noop,
365
		/**
366
		 * the open / close animation duration in milliseconds - set this to `false` to disable the animation (default is `200`)
367
		 * @name $.jstree.defaults.core.animation
368
		 */
369
		animation		: 200,
370
		/**
371
		 * a boolean indicating if multiple nodes can be selected
372
		 * @name $.jstree.defaults.core.multiple
373
		 */
374
		multiple		: true,
375
		/**
376
		 * theme configuration object
377
		 * @name $.jstree.defaults.core.themes
378
		 */
379
		themes			: {
380
			/**
381
			 * the name of the theme to use (if left as `false` the default theme is used)
382
			 * @name $.jstree.defaults.core.themes.name
383
			 */
384
			name			: false,
385
			/**
386
			 * the URL of the theme's CSS file, leave this as `false` if you have manually included the theme CSS (recommended). You can set this to `true` too which will try to autoload the theme.
387
			 * @name $.jstree.defaults.core.themes.url
388
			 */
389
			url				: false,
390
			/**
391
			 * the location of all jstree themes - only used if `url` is set to `true`
392
			 * @name $.jstree.defaults.core.themes.dir
393
			 */
394
			dir				: false,
395
			/**
396
			 * a boolean indicating if connecting dots are shown
397
			 * @name $.jstree.defaults.core.themes.dots
398
			 */
399
			dots			: true,
400
			/**
401
			 * a boolean indicating if node icons are shown
402
			 * @name $.jstree.defaults.core.themes.icons
403
			 */
404
			icons			: true,
405
			/**
406
			 * a boolean indicating if node ellipsis should be shown - this only works with a fixed with on the container
407
			 * @name $.jstree.defaults.core.themes.ellipsis
408
			 */
409
			ellipsis		: false,
410
			/**
411
			 * a boolean indicating if the tree background is striped
412
			 * @name $.jstree.defaults.core.themes.stripes
413
			 */
414
			stripes			: false,
415
			/**
416
			 * a string (or boolean `false`) specifying the theme variant to use (if the theme supports variants)
417
			 * @name $.jstree.defaults.core.themes.variant
418
			 */
419
			variant			: false,
420
			/**
421
			 * a boolean specifying if a reponsive version of the theme should kick in on smaller screens (if the theme supports it). Defaults to `false`.
422
			 * @name $.jstree.defaults.core.themes.responsive
423
			 */
424
			responsive		: false
425
		},
426
		/**
427
		 * if left as `true` all parents of all selected nodes will be opened once the tree loads (so that all selected nodes are visible to the user)
428
		 * @name $.jstree.defaults.core.expand_selected_onload
429
		 */
430
		expand_selected_onload : true,
431
		/**
432
		 * if left as `true` web workers will be used to parse incoming JSON data where possible, so that the UI will not be blocked by large requests. Workers are however about 30% slower. Defaults to `true`
433
		 * @name $.jstree.defaults.core.worker
434
		 */
435
		worker : true,
436
		/**
437
		 * Force node text to plain text (and escape HTML). Defaults to `false`
438
		 * @name $.jstree.defaults.core.force_text
439
		 */
440
		force_text : false,
441
		/**
442
		 * Should the node be toggled if the text is double clicked. Defaults to `true`
443
		 * @name $.jstree.defaults.core.dblclick_toggle
444
		 */
445
		dblclick_toggle : true,
446
		/**
447
		 * Should the loaded nodes be part of the state. Defaults to `false`
448
		 * @name $.jstree.defaults.core.loaded_state
449
		 */
450
		loaded_state : false,
451
		/**
452
		 * Should the last active node be focused when the tree container is blurred and the focused again. This helps working with screen readers. Defaults to `true`
453
		 * @name $.jstree.defaults.core.restore_focus
454
		 */
455
		restore_focus : true,
456
		/**
457
		 * Force to compute and set "aria-setsize" and "aria-posinset" explicitly for each treeitem.
458
		 * Some browsers may compute incorrect elements position and produce wrong announcements for screen readers. Defaults to `false`
459
		 * @name $.jstree.defaults.core.compute_elements_positions
460
		 */
461
		compute_elements_positions : false,
462
		/**
463
		 * Default keyboard shortcuts (an object where each key is the button name or combo - like 'enter', 'ctrl-space', 'p', etc and the value is the function to execute in the instance's scope)
464
		 * @name $.jstree.defaults.core.keyboard
465
		 */
466
		keyboard : {
467
			'ctrl-space': function (e) {
468
				// aria defines space only with Ctrl
469
				e.type = "click";
470
				$(e.currentTarget).trigger(e);
471
			},
472
			'enter': function (e) {
473
				// enter
474
				e.type = "click";
475
				$(e.currentTarget).trigger(e);
476
			},
477
			'left': function (e) {
478
				// left
479
				e.preventDefault();
480
				if(this.is_open(e.currentTarget)) {
481
					this.close_node(e.currentTarget);
482
				}
483
				else {
484
					var o = this.get_parent(e.currentTarget);
485
					if(o && o.id !== $.jstree.root) { this.get_node(o, true).children('.jstree-anchor').trigger('focus'); }
486
				}
487
			},
488
			'up': function (e) {
489
				// up
490
				e.preventDefault();
491
				var o = this.get_prev_dom(e.currentTarget);
492
				if(o && o.length) { o.children('.jstree-anchor').trigger('focus'); }
493
			},
494
			'right': function (e) {
495
				// right
496
				e.preventDefault();
497
				if(this.is_closed(e.currentTarget)) {
498
					this.open_node(e.currentTarget, function (o) { this.get_node(o, true).children('.jstree-anchor').trigger('focus'); });
499
				}
500
				else if (this.is_open(e.currentTarget)) {
501
					var o = this.get_node(e.currentTarget, true).children('.jstree-children')[0];
502
					if(o) { $(this._firstChild(o)).children('.jstree-anchor').trigger('focus'); }
503
				}
504
			},
505
			'down': function (e) {
506
				// down
507
				e.preventDefault();
508
				var o = this.get_next_dom(e.currentTarget);
509
				if(o && o.length) { o.children('.jstree-anchor').trigger('focus'); }
510
			},
511
			'*': function (e) {
512
				// aria defines * on numpad as open_all - not very common
513
				this.open_all();
514
			},
515
			'home': function (e) {
516
				// home
517
				e.preventDefault();
518
				var o = this._firstChild(this.get_container_ul()[0]);
519
				if(o) { $(o).children('.jstree-anchor').filter(':visible').trigger('focus'); }
520
			},
521
			'end': function (e) {
522
				// end
523
				e.preventDefault();
524
				this.element.find('.jstree-anchor').filter(':visible').last().trigger('focus');
525
			},
526
			'f2': function (e) {
527
				// f2 - safe to include - if check_callback is false it will fail
528
				e.preventDefault();
529
				this.edit(e.currentTarget);
530
			}
531
		}
532
	};
533
	$.jstree.core.prototype = {
534
		/**
535
		 * used to decorate an instance with a plugin. Used internally.
536
		 * @private
537
		 * @name plugin(deco [, opts])
538
		 * @param  {String} deco the plugin to decorate with
539
		 * @param  {Object} opts options for the plugin
540
		 * @return {jsTree}
541
		 */
542
		plugin : function (deco, opts) {
543
			var Child = $.jstree.plugins[deco];
544
			if(Child) {
545
				this._data[deco] = {};
546
				Child.prototype = this;
547
				return new Child(opts, this);
548
			}
549
			return this;
550
		},
551
		/**
552
		 * initialize the instance. Used internally.
553
		 * @private
554
		 * @name init(el, optons)
555
		 * @param {DOMElement|jQuery|String} el the element we are transforming
556
		 * @param {Object} options options for this instance
557
		 * @trigger init.jstree, loading.jstree, loaded.jstree, ready.jstree, changed.jstree
558
		 */
559
		init : function (el, options) {
560
			this._model = {
561
				data : {},
562
				changed : [],
563
				force_full_redraw : false,
564
				redraw_timeout : false,
565
				default_state : {
566
					loaded : true,
567
					opened : false,
568
					selected : false,
569
					disabled : false
570
				}
571
			};
572
			this._model.data[$.jstree.root] = {
573
				id : $.jstree.root,
574
				parent : null,
575
				parents : [],
576
				children : [],
577
				children_d : [],
578
				state : { loaded : false }
579
			};
580
 
581
			this.element = $(el).addClass('jstree jstree-' + this._id);
582
			this.settings = options;
583
 
584
			this._data.core.ready = false;
585
			this._data.core.loaded = false;
586
			this._data.core.rtl = (this.element.css("direction") === "rtl");
587
			this.element[this._data.core.rtl ? 'addClass' : 'removeClass']("jstree-rtl");
588
			this.element.attr('role','tree');
589
			if(this.settings.core.multiple) {
590
				this.element.attr('aria-multiselectable', true);
591
			}
592
			if(!this.element.attr('tabindex')) {
593
				this.element.attr('tabindex','0');
594
			}
595
 
596
			this.bind();
597
			/**
598
			 * triggered after all events are bound
599
			 * @event
600
			 * @name init.jstree
601
			 */
602
			this.trigger("init");
603
 
604
			this._data.core.original_container_html = this.element.find(" > ul > li").clone(true);
605
			this._data.core.original_container_html
606
				.find("li").addBack()
607
				.contents().filter(function() {
608
					return this.nodeType === 3 && (!this.nodeValue || /^\s+$/.test(this.nodeValue));
609
				})
610
				.remove();
611
			this.element.html("<"+"ul class='jstree-container-ul jstree-children' role='group'><"+"li id='j"+this._id+"_loading' class='jstree-initial-node jstree-loading jstree-leaf jstree-last' role='none'><i class='jstree-icon jstree-ocl'></i><"+"a class='jstree-anchor' role='treeitem' href='#'><i class='jstree-icon jstree-themeicon-hidden'></i>" + this.get_string("Loading ...") + "</a></li></ul>");
612
			this.element.attr('aria-activedescendant','j' + this._id + '_loading');
613
			this._data.core.li_height = this.get_container_ul().children("li").first().outerHeight() || 24;
614
			this._data.core.node = this._create_prototype_node();
615
			/**
616
			 * triggered after the loading text is shown and before loading starts
617
			 * @event
618
			 * @name loading.jstree
619
			 */
620
			this.trigger("loading");
621
			this.load_node($.jstree.root);
622
		},
623
		/**
624
		 * destroy an instance
625
		 * @name destroy()
626
		 * @param  {Boolean} keep_html if not set to `true` the container will be emptied, otherwise the current DOM elements will be kept intact
627
		 */
628
		destroy : function (keep_html) {
629
			/**
630
			 * triggered before the tree is destroyed
631
			 * @event
632
			 * @name destroy.jstree
633
			 */
634
			this.trigger("destroy");
635
			if(this._wrk) {
636
				try {
637
					window.URL.revokeObjectURL(this._wrk);
638
					this._wrk = null;
639
				}
640
				catch (ignore) { }
641
			}
642
			if(!keep_html) { this.element.empty(); }
643
			this.teardown();
644
		},
645
		/**
646
		 * Create a prototype node
647
		 * @name _create_prototype_node()
648
		 * @return {DOMElement}
649
		 */
650
		_create_prototype_node : function () {
651
			var _node = document.createElement('LI'), _temp1, _temp2;
652
			_node.setAttribute('role', 'none');
653
			_temp1 = document.createElement('I');
654
			_temp1.className = 'jstree-icon jstree-ocl';
655
			_temp1.setAttribute('role', 'presentation');
656
			_node.appendChild(_temp1);
657
			_temp1 = document.createElement('A');
658
			_temp1.className = 'jstree-anchor';
659
			_temp1.setAttribute('href','#');
660
			_temp1.setAttribute('tabindex','-1');
661
			_temp1.setAttribute('role', 'treeitem');
662
			_temp2 = document.createElement('I');
663
			_temp2.className = 'jstree-icon jstree-themeicon';
664
			_temp2.setAttribute('role', 'presentation');
665
			_temp1.appendChild(_temp2);
666
			_node.appendChild(_temp1);
667
			_temp1 = _temp2 = null;
668
 
669
			return _node;
670
		},
671
		_kbevent_to_func : function (e) {
672
			var keys = {
673
				8: "Backspace", 9: "Tab", 13: "Enter", 19: "Pause", 27: "Esc",
674
				32: "Space", 33: "PageUp", 34: "PageDown", 35: "End", 36: "Home",
675
				37: "Left", 38: "Up", 39: "Right", 40: "Down", 44: "Print", 45: "Insert",
676
				46: "Delete", 96: "Numpad0", 97: "Numpad1", 98: "Numpad2", 99 : "Numpad3",
677
				100: "Numpad4", 101: "Numpad5", 102: "Numpad6", 103: "Numpad7",
678
				104: "Numpad8", 105: "Numpad9", '-13': "NumpadEnter", 112: "F1",
679
				113: "F2", 114: "F3", 115: "F4", 116: "F5", 117: "F6", 118: "F7",
680
				119: "F8", 120: "F9", 121: "F10", 122: "F11", 123: "F12", 144: "Numlock",
681
				145: "Scrolllock", 16: 'Shift', 17: 'Ctrl', 18: 'Alt',
682
				48: '0',  49: '1',  50: '2',  51: '3',  52: '4', 53:  '5',
683
				54: '6',  55: '7',  56: '8',  57: '9',  59: ';',  61: '=', 65:  'a',
684
				66: 'b',  67: 'c',  68: 'd',  69: 'e',  70: 'f',  71: 'g', 72:  'h',
685
				73: 'i',  74: 'j',  75: 'k',  76: 'l',  77: 'm',  78: 'n', 79:  'o',
686
				80: 'p',  81: 'q',  82: 'r',  83: 's',  84: 't',  85: 'u', 86:  'v',
687
				87: 'w',  88: 'x',  89: 'y',  90: 'z', 107: '+', 109: '-', 110: '.',
688
				186: ';', 187: '=', 188: ',', 189: '-', 190: '.', 191: '/', 192: '`',
689
				219: '[', 220: '\\',221: ']', 222: "'", 111: '/', 106: '*', 173: '-'
690
			};
691
			var parts = [];
692
			if (e.ctrlKey) { parts.push('ctrl'); }
693
			if (e.altKey) { parts.push('alt'); }
694
            if (e.shiftKey) { parts.push('shift'); }
695
			parts.push(keys[e.which] || e.which);
696
            parts = parts.sort().join('-').toLowerCase();
697
            if (parts === 'shift-shift' || parts === 'ctrl-ctrl' || parts === 'alt-alt') {
698
                return null;
699
            }
700
 
701
			var kb = this.settings.core.keyboard, i, tmp;
702
			for (i in kb) {
703
				if (kb.hasOwnProperty(i)) {
704
					tmp = i;
705
					if (tmp !== '-' && tmp !== '+') {
706
						tmp = tmp.replace('--', '-MINUS').replace('+-', '-MINUS').replace('++', '-PLUS').replace('-+', '-PLUS');
707
						tmp = tmp.split(/-|\+/).sort().join('-').replace('MINUS', '-').replace('PLUS', '+').toLowerCase();
708
					}
709
					if (tmp === parts) {
710
						return kb[i];
711
					}
712
				}
713
			}
714
			return null;
715
		},
716
		/**
717
		 * part of the destroying of an instance. Used internally.
718
		 * @private
719
		 * @name teardown()
720
		 */
721
		teardown : function () {
722
			this.unbind();
723
			this.element
724
				.removeClass('jstree')
725
				.removeData('jstree')
726
				.find("[class^='jstree']")
727
					.addBack()
728
					.attr("class", function () { return this.className.replace(/jstree[^ ]*|$/ig,''); });
729
			this.element = null;
730
		},
731
		/**
732
		 * bind all events. Used internally.
733
		 * @private
734
		 * @name bind()
735
		 */
736
		bind : function () {
737
			var word = '',
738
				tout = null,
739
				was_click = 0;
740
			this.element
741
				.on("dblclick.jstree", function (e) {
742
						if(e.target.tagName && e.target.tagName.toLowerCase() === "input") { return true; }
743
						if(document.selection && document.selection.empty) {
744
							document.selection.empty();
745
						}
746
						else {
747
							if(window.getSelection) {
748
								var sel = window.getSelection();
749
								try {
750
									sel.removeAllRanges();
751
									sel.collapse();
752
								} catch (ignore) { }
753
							}
754
						}
755
					})
756
				.on("mousedown.jstree", function (e) {
757
						if(e.target === this.element[0]) {
758
							e.preventDefault(); // prevent losing focus when clicking scroll arrows (FF, Chrome)
759
							was_click = +(new Date()); // ie does not allow to prevent losing focus
760
						}
761
					}.bind(this))
762
				.on("mousedown.jstree", ".jstree-ocl", function (e) {
763
						e.preventDefault(); // prevent any node inside from losing focus when clicking the open/close icon
764
					})
765
				.on("click.jstree", ".jstree-ocl", function (e) {
766
						this.toggle_node(e.target);
767
					}.bind(this))
768
				.on("dblclick.jstree", ".jstree-anchor", function (e) {
769
						if(e.target.tagName && e.target.tagName.toLowerCase() === "input") { return true; }
770
						if(this.settings.core.dblclick_toggle) {
771
							this.toggle_node(e.target);
772
						}
773
					}.bind(this))
774
				.on("click.jstree", ".jstree-anchor", function (e) {
775
						e.preventDefault();
776
						if(e.currentTarget !== document.activeElement) { $(e.currentTarget).trigger('focus'); }
777
						this.activate_node(e.currentTarget, e);
778
					}.bind(this))
779
				.on('keydown.jstree', '.jstree-anchor', function (e) {
780
						if(e.target.tagName && e.target.tagName.toLowerCase() === "input") { return true; }
781
						if(this._data.core.rtl) {
782
							if(e.which === 37) { e.which = 39; }
783
							else if(e.which === 39) { e.which = 37; }
784
						}
785
						var f = this._kbevent_to_func(e);
786
						if (f) {
787
							var r = f.call(this, e);
788
							if (r === false || r === true) {
789
								return r;
790
							}
791
						}
792
					}.bind(this))
793
				.on("load_node.jstree", function (e, data) {
794
						if(data.status) {
795
							if(data.node.id === $.jstree.root && !this._data.core.loaded) {
796
								this._data.core.loaded = true;
797
								if(this._firstChild(this.get_container_ul()[0])) {
798
									this.element.attr('aria-activedescendant',this._firstChild(this.get_container_ul()[0]).id);
799
								}
800
								/**
801
								 * triggered after the root node is loaded for the first time
802
								 * @event
803
								 * @name loaded.jstree
804
								 */
805
								this.trigger("loaded");
806
							}
807
							if(!this._data.core.ready) {
808
								setTimeout(function() {
809
									if(this.element && !this.get_container_ul().find('.jstree-loading').length) {
810
										this._data.core.ready = true;
811
										if(this._data.core.selected.length) {
812
											if(this.settings.core.expand_selected_onload) {
813
												var tmp = [], i, j;
814
												for(i = 0, j = this._data.core.selected.length; i < j; i++) {
815
													tmp = tmp.concat(this._model.data[this._data.core.selected[i]].parents);
816
												}
817
												tmp = $.vakata.array_unique(tmp);
818
												for(i = 0, j = tmp.length; i < j; i++) {
819
													this.open_node(tmp[i], false, 0);
820
												}
821
											}
822
											this.trigger('changed', { 'action' : 'ready', 'selected' : this._data.core.selected });
823
										}
824
										/**
825
										 * triggered after all nodes are finished loading
826
										 * @event
827
										 * @name ready.jstree
828
										 */
829
										this.trigger("ready");
830
									}
831
								}.bind(this), 0);
832
							}
833
						}
834
					}.bind(this))
835
				// quick searching when the tree is focused
836
				.on('keypress.jstree', function (e) {
837
						if(e.target.tagName && e.target.tagName.toLowerCase() === "input") { return true; }
838
						if(tout) { clearTimeout(tout); }
839
						tout = setTimeout(function () {
840
							word = '';
841
						}, 500);
842
 
843
						var chr = String.fromCharCode(e.which).toLowerCase(),
844
							col = this.element.find('.jstree-anchor').filter(':visible'),
845
							ind = col.index(document.activeElement) || 0,
846
							end = false;
847
						word += chr;
848
 
849
						// match for whole word from current node down (including the current node)
850
						if(word.length > 1) {
851
							col.slice(ind).each(function (i, v) {
852
								if($(v).text().toLowerCase().indexOf(word) === 0) {
853
									$(v).trigger('focus');
854
									end = true;
855
									return false;
856
								}
857
							}.bind(this));
858
							if(end) { return; }
859
 
860
							// match for whole word from the beginning of the tree
861
							col.slice(0, ind).each(function (i, v) {
862
								if($(v).text().toLowerCase().indexOf(word) === 0) {
863
									$(v).trigger('focus');
864
									end = true;
865
									return false;
866
								}
867
							}.bind(this));
868
							if(end) { return; }
869
						}
870
						// list nodes that start with that letter (only if word consists of a single char)
871
						if(new RegExp('^' + chr.replace(/[-\/\\^$*+?.()|[\]{}]/g, '\\$&') + '+$').test(word)) {
872
							// search for the next node starting with that letter
873
							col.slice(ind + 1).each(function (i, v) {
874
								if($(v).text().toLowerCase().charAt(0) === chr) {
875
									$(v).trigger('focus');
876
									end = true;
877
									return false;
878
								}
879
							}.bind(this));
880
							if(end) { return; }
881
 
882
							// search from the beginning
883
							col.slice(0, ind + 1).each(function (i, v) {
884
								if($(v).text().toLowerCase().charAt(0) === chr) {
885
									$(v).trigger('focus');
886
									end = true;
887
									return false;
888
								}
889
							}.bind(this));
890
							if(end) { return; }
891
						}
892
					}.bind(this))
893
				// THEME RELATED
894
				.on("init.jstree", function () {
895
						var s = this.settings.core.themes;
896
						this._data.core.themes.dots			= s.dots;
897
						this._data.core.themes.stripes		= s.stripes;
898
						this._data.core.themes.icons		= s.icons;
899
						this._data.core.themes.ellipsis		= s.ellipsis;
900
						this.set_theme(s.name || "default", s.url);
901
						this.set_theme_variant(s.variant);
902
					}.bind(this))
903
				.on("loading.jstree", function () {
904
						this[ this._data.core.themes.dots ? "show_dots" : "hide_dots" ]();
905
						this[ this._data.core.themes.icons ? "show_icons" : "hide_icons" ]();
906
						this[ this._data.core.themes.stripes ? "show_stripes" : "hide_stripes" ]();
907
						this[ this._data.core.themes.ellipsis ? "show_ellipsis" : "hide_ellipsis" ]();
908
					}.bind(this))
909
				.on('blur.jstree', '.jstree-anchor', function (e) {
910
						this._data.core.focused = null;
911
						$(e.currentTarget).filter('.jstree-hovered').trigger('mouseleave');
912
						this.element.attr('tabindex', '0');
913
					}.bind(this))
914
				.on('focus.jstree', '.jstree-anchor', function (e) {
915
						var tmp = this.get_node(e.currentTarget);
916
						if(tmp && tmp.id) {
917
							this._data.core.focused = tmp.id;
918
						}
919
						this.element.find('.jstree-hovered').not(e.currentTarget).trigger('mouseleave');
920
						$(e.currentTarget).trigger('mouseenter');
921
						this.element.attr('tabindex', '-1');
922
					}.bind(this))
923
				.on('focus.jstree', function () {
924
						if(+(new Date()) - was_click > 500 && !this._data.core.focused && this.settings.core.restore_focus) {
925
							was_click = 0;
926
							var act = this.get_node(this.element.attr('aria-activedescendant'), true);
927
							if(act) {
928
								act.find('> .jstree-anchor').trigger('focus');
929
							}
930
						}
931
					}.bind(this))
932
				.on('mouseenter.jstree', '.jstree-anchor', function (e) {
933
						this.hover_node(e.currentTarget);
934
					}.bind(this))
935
				.on('mouseleave.jstree', '.jstree-anchor', function (e) {
936
						this.dehover_node(e.currentTarget);
937
					}.bind(this));
938
		},
939
		/**
940
		 * part of the destroying of an instance. Used internally.
941
		 * @private
942
		 * @name unbind()
943
		 */
944
		unbind : function () {
945
			this.element.off('.jstree');
946
			$(document).off('.jstree-' + this._id);
947
		},
948
		/**
949
		 * trigger an event. Used internally.
950
		 * @private
951
		 * @name trigger(ev [, data])
952
		 * @param  {String} ev the name of the event to trigger
953
		 * @param  {Object} data additional data to pass with the event
954
		 */
955
		trigger : function (ev, data) {
956
			if(!data) {
957
				data = {};
958
			}
959
			data.instance = this;
960
			this.element.triggerHandler(ev.replace('.jstree','') + '.jstree', data);
961
		},
962
		/**
963
		 * returns the jQuery extended instance container
964
		 * @name get_container()
965
		 * @return {jQuery}
966
		 */
967
		get_container : function () {
968
			return this.element;
969
		},
970
		/**
971
		 * returns the jQuery extended main UL node inside the instance container. Used internally.
972
		 * @private
973
		 * @name get_container_ul()
974
		 * @return {jQuery}
975
		 */
976
		get_container_ul : function () {
977
			return this.element.children(".jstree-children").first();
978
		},
979
		/**
980
		 * gets string replacements (localization). Used internally.
981
		 * @private
982
		 * @name get_string(key)
983
		 * @param  {String} key
984
		 * @return {String}
985
		 */
986
		get_string : function (key) {
987
			var a = this.settings.core.strings;
988
			if($.vakata.is_function(a)) { return a.call(this, key); }
989
			if(a && a[key]) { return a[key]; }
990
			return key;
991
		},
992
		/**
993
		 * gets the first child of a DOM node. Used internally.
994
		 * @private
995
		 * @name _firstChild(dom)
996
		 * @param  {DOMElement} dom
997
		 * @return {DOMElement}
998
		 */
999
		_firstChild : function (dom) {
1000
			dom = dom ? dom.firstChild : null;
1001
			while(dom !== null && dom.nodeType !== 1) {
1002
				dom = dom.nextSibling;
1003
			}
1004
			return dom;
1005
		},
1006
		/**
1007
		 * gets the next sibling of a DOM node. Used internally.
1008
		 * @private
1009
		 * @name _nextSibling(dom)
1010
		 * @param  {DOMElement} dom
1011
		 * @return {DOMElement}
1012
		 */
1013
		_nextSibling : function (dom) {
1014
			dom = dom ? dom.nextSibling : null;
1015
			while(dom !== null && dom.nodeType !== 1) {
1016
				dom = dom.nextSibling;
1017
			}
1018
			return dom;
1019
		},
1020
		/**
1021
		 * gets the previous sibling of a DOM node. Used internally.
1022
		 * @private
1023
		 * @name _previousSibling(dom)
1024
		 * @param  {DOMElement} dom
1025
		 * @return {DOMElement}
1026
		 */
1027
		_previousSibling : function (dom) {
1028
			dom = dom ? dom.previousSibling : null;
1029
			while(dom !== null && dom.nodeType !== 1) {
1030
				dom = dom.previousSibling;
1031
			}
1032
			return dom;
1033
		},
1034
		/**
1035
		 * get the JSON representation of a node (or the actual jQuery extended DOM node) by using any input (child DOM element, ID string, selector, etc)
1036
		 * @name get_node(obj [, as_dom])
1037
		 * @param  {mixed} obj
1038
		 * @param  {Boolean} as_dom
1039
		 * @return {Object|jQuery}
1040
		 */
1041
		get_node : function (obj, as_dom) {
1042
			if(obj && obj.id) {
1043
				obj = obj.id;
1044
			}
1045
			if (obj instanceof $ && obj.length && obj[0].id) {
1046
				obj = obj[0].id;
1047
			}
1048
			var dom;
1049
			try {
1050
				if(this._model.data[obj]) {
1051
					obj = this._model.data[obj];
1052
				}
1053
				else if(typeof obj === "string" && this._model.data[obj.replace(/^#/, '')]) {
1054
					obj = this._model.data[obj.replace(/^#/, '')];
1055
				}
1056
				else if(typeof obj === "string" && (dom = $('#' + obj.replace($.jstree.idregex,'\\$&'), this.element)).length && this._model.data[dom.closest('.jstree-node').attr('id')]) {
1057
					obj = this._model.data[dom.closest('.jstree-node').attr('id')];
1058
				}
1059
				else if((dom = this.element.find(obj)).length && this._model.data[dom.closest('.jstree-node').attr('id')]) {
1060
					obj = this._model.data[dom.closest('.jstree-node').attr('id')];
1061
				}
1062
				else if((dom = this.element.find(obj)).length && dom.hasClass('jstree')) {
1063
					obj = this._model.data[$.jstree.root];
1064
				}
1065
				else {
1066
					return false;
1067
				}
1068
 
1069
				if(as_dom) {
1070
					obj = obj.id === $.jstree.root ? this.element : $('#' + obj.id.replace($.jstree.idregex,'\\$&'), this.element);
1071
				}
1072
				return obj;
1073
			} catch (ex) { return false; }
1074
		},
1075
		/**
1076
		 * get the path to a node, either consisting of node texts, or of node IDs, optionally glued together (otherwise an array)
1077
		 * @name get_path(obj [, glue, ids])
1078
		 * @param  {mixed} obj the node
1079
		 * @param  {String} glue if you want the path as a string - pass the glue here (for example '/'), if a falsy value is supplied here, an array is returned
1080
		 * @param  {Boolean} ids if set to true build the path using ID, otherwise node text is used
1081
		 * @return {mixed}
1082
		 */
1083
		get_path : function (obj, glue, ids) {
1084
			obj = obj.parents ? obj : this.get_node(obj);
1085
			if(!obj || obj.id === $.jstree.root || !obj.parents) {
1086
				return false;
1087
			}
1088
			var i, j, p = [];
1089
			p.push(ids ? obj.id : obj.text);
1090
			for(i = 0, j = obj.parents.length; i < j; i++) {
1091
				p.push(ids ? obj.parents[i] : this.get_text(obj.parents[i]));
1092
			}
1093
			p = p.reverse().slice(1);
1094
			return glue ? p.join(glue) : p;
1095
		},
1096
		/**
1097
		 * get the next visible node that is below the `obj` node. If `strict` is set to `true` only sibling nodes are returned.
1098
		 * @name get_next_dom(obj [, strict])
1099
		 * @param  {mixed} obj
1100
		 * @param  {Boolean} strict
1101
		 * @return {jQuery}
1102
		 */
1103
		get_next_dom : function (obj, strict) {
1104
			var tmp;
1105
			obj = this.get_node(obj, true);
1106
			if(obj[0] === this.element[0]) {
1107
				tmp = this._firstChild(this.get_container_ul()[0]);
1108
				while (tmp && tmp.offsetHeight === 0) {
1109
					tmp = this._nextSibling(tmp);
1110
				}
1111
				return tmp ? $(tmp) : false;
1112
			}
1113
			if(!obj || !obj.length) {
1114
				return false;
1115
			}
1116
			if(strict) {
1117
				tmp = obj[0];
1118
				do {
1119
					tmp = this._nextSibling(tmp);
1120
				} while (tmp && tmp.offsetHeight === 0);
1121
				return tmp ? $(tmp) : false;
1122
			}
1123
			if(obj.hasClass("jstree-open")) {
1124
				tmp = this._firstChild(obj.children('.jstree-children')[0]);
1125
				while (tmp && tmp.offsetHeight === 0) {
1126
					tmp = this._nextSibling(tmp);
1127
				}
1128
				if(tmp !== null) {
1129
					return $(tmp);
1130
				}
1131
			}
1132
			tmp = obj[0];
1133
			do {
1134
				tmp = this._nextSibling(tmp);
1135
			} while (tmp && tmp.offsetHeight === 0);
1136
			if(tmp !== null) {
1137
				return $(tmp);
1138
			}
1139
			return obj.parentsUntil(".jstree",".jstree-node").nextAll(".jstree-node:visible").first();
1140
		},
1141
		/**
1142
		 * get the previous visible node that is above the `obj` node. If `strict` is set to `true` only sibling nodes are returned.
1143
		 * @name get_prev_dom(obj [, strict])
1144
		 * @param  {mixed} obj
1145
		 * @param  {Boolean} strict
1146
		 * @return {jQuery}
1147
		 */
1148
		get_prev_dom : function (obj, strict) {
1149
			var tmp;
1150
			obj = this.get_node(obj, true);
1151
			if(obj[0] === this.element[0]) {
1152
				tmp = this.get_container_ul()[0].lastChild;
1153
				while (tmp && tmp.offsetHeight === 0) {
1154
					tmp = this._previousSibling(tmp);
1155
				}
1156
				return tmp ? $(tmp) : false;
1157
			}
1158
			if(!obj || !obj.length) {
1159
				return false;
1160
			}
1161
			if(strict) {
1162
				tmp = obj[0];
1163
				do {
1164
					tmp = this._previousSibling(tmp);
1165
				} while (tmp && tmp.offsetHeight === 0);
1166
				return tmp ? $(tmp) : false;
1167
			}
1168
			tmp = obj[0];
1169
			do {
1170
				tmp = this._previousSibling(tmp);
1171
			} while (tmp && tmp.offsetHeight === 0);
1172
			if(tmp !== null) {
1173
				obj = $(tmp);
1174
				while(obj.hasClass("jstree-open")) {
1175
					obj = obj.children(".jstree-children").first().children(".jstree-node:visible:last");
1176
				}
1177
				return obj;
1178
			}
1179
			tmp = obj[0].parentNode.parentNode;
1180
			return tmp && tmp.className && tmp.className.indexOf('jstree-node') !== -1 ? $(tmp) : false;
1181
		},
1182
		/**
1183
		 * get the parent ID of a node
1184
		 * @name get_parent(obj)
1185
		 * @param  {mixed} obj
1186
		 * @return {String}
1187
		 */
1188
		get_parent : function (obj) {
1189
			obj = this.get_node(obj);
1190
			if(!obj || obj.id === $.jstree.root) {
1191
				return false;
1192
			}
1193
			return obj.parent;
1194
		},
1195
		/**
1196
		 * get a jQuery collection of all the children of a node (node must be rendered), returns false on error
1197
		 * @name get_children_dom(obj)
1198
		 * @param  {mixed} obj
1199
		 * @return {jQuery}
1200
		 */
1201
		get_children_dom : function (obj) {
1202
			obj = this.get_node(obj, true);
1203
			if(obj[0] === this.element[0]) {
1204
				return this.get_container_ul().children(".jstree-node");
1205
			}
1206
			if(!obj || !obj.length) {
1207
				return false;
1208
			}
1209
			return obj.children(".jstree-children").children(".jstree-node");
1210
		},
1211
		/**
1212
		 * checks if a node has children
1213
		 * @name is_parent(obj)
1214
		 * @param  {mixed} obj
1215
		 * @return {Boolean}
1216
		 */
1217
		is_parent : function (obj) {
1218
			obj = this.get_node(obj);
1219
			return obj && (obj.state.loaded === false || obj.children.length > 0);
1220
		},
1221
		/**
1222
		 * checks if a node is loaded (its children are available)
1223
		 * @name is_loaded(obj)
1224
		 * @param  {mixed} obj
1225
		 * @return {Boolean}
1226
		 */
1227
		is_loaded : function (obj) {
1228
			obj = this.get_node(obj);
1229
			return obj && obj.state.loaded;
1230
		},
1231
		/**
1232
		 * check if a node is currently loading (fetching children)
1233
		 * @name is_loading(obj)
1234
		 * @param  {mixed} obj
1235
		 * @return {Boolean}
1236
		 */
1237
		is_loading : function (obj) {
1238
			obj = this.get_node(obj);
1239
			return obj && obj.state && obj.state.loading;
1240
		},
1241
		/**
1242
		 * check if a node is opened
1243
		 * @name is_open(obj)
1244
		 * @param  {mixed} obj
1245
		 * @return {Boolean}
1246
		 */
1247
		is_open : function (obj) {
1248
			obj = this.get_node(obj);
1249
			return obj && obj.state.opened;
1250
		},
1251
		/**
1252
		 * check if a node is in a closed state
1253
		 * @name is_closed(obj)
1254
		 * @param  {mixed} obj
1255
		 * @return {Boolean}
1256
		 */
1257
		is_closed : function (obj) {
1258
			obj = this.get_node(obj);
1259
			return obj && this.is_parent(obj) && !obj.state.opened;
1260
		},
1261
		/**
1262
		 * check if a node has no children
1263
		 * @name is_leaf(obj)
1264
		 * @param  {mixed} obj
1265
		 * @return {Boolean}
1266
		 */
1267
		is_leaf : function (obj) {
1268
			return !this.is_parent(obj);
1269
		},
1270
		/**
1271
		 * loads a node (fetches its children using the `core.data` setting). Multiple nodes can be passed to by using an array.
1272
		 * @name load_node(obj [, callback])
1273
		 * @param  {mixed} obj
1274
		 * @param  {function} callback a function to be executed once loading is complete, the function is executed in the instance's scope and receives two arguments - the node and a boolean status
1275
		 * @return {Boolean}
1276
		 * @trigger load_node.jstree
1277
		 */
1278
		load_node : function (obj, callback) {
1279
			var k, l, i, j, c;
1280
			if($.vakata.is_array(obj)) {
1281
				this._load_nodes(obj.slice(), callback);
1282
				return true;
1283
			}
1284
			obj = this.get_node(obj);
1285
			if(!obj) {
1286
				if(callback) { callback.call(this, obj, false); }
1287
				return false;
1288
			}
1289
			// if(obj.state.loading) { } // the node is already loading - just wait for it to load and invoke callback? but if called implicitly it should be loaded again?
1290
			if(obj.state.loaded) {
1291
				obj.state.loaded = false;
1292
				for(i = 0, j = obj.parents.length; i < j; i++) {
1293
					this._model.data[obj.parents[i]].children_d = $.vakata.array_filter(this._model.data[obj.parents[i]].children_d, function (v) {
1294
						return $.inArray(v, obj.children_d) === -1;
1295
					});
1296
				}
1297
				for(k = 0, l = obj.children_d.length; k < l; k++) {
1298
					if(this._model.data[obj.children_d[k]].state.selected) {
1299
						c = true;
1300
					}
1301
					delete this._model.data[obj.children_d[k]];
1302
				}
1303
				if (c) {
1304
					this._data.core.selected = $.vakata.array_filter(this._data.core.selected, function (v) {
1305
						return $.inArray(v, obj.children_d) === -1;
1306
					});
1307
				}
1308
				obj.children = [];
1309
				obj.children_d = [];
1310
				if(c) {
1311
					this.trigger('changed', { 'action' : 'load_node', 'node' : obj, 'selected' : this._data.core.selected });
1312
				}
1313
			}
1314
			obj.state.failed = false;
1315
			obj.state.loading = true;
1316
			this.get_node(obj, true).addClass("jstree-loading").attr('aria-busy',true);
1317
			this._load_node(obj, function (status) {
1318
				obj = this._model.data[obj.id];
1319
				obj.state.loading = false;
1320
				obj.state.loaded = status;
1321
				obj.state.failed = !obj.state.loaded;
1322
				var dom = this.get_node(obj, true), i = 0, j = 0, m = this._model.data, has_children = false;
1323
				for(i = 0, j = obj.children.length; i < j; i++) {
1324
					if(m[obj.children[i]] && !m[obj.children[i]].state.hidden) {
1325
						has_children = true;
1326
						break;
1327
					}
1328
				}
1329
				if(obj.state.loaded && dom && dom.length) {
1330
					dom.removeClass('jstree-closed jstree-open jstree-leaf');
1331
					if (!has_children) {
1332
						dom.addClass('jstree-leaf');
1333
					}
1334
					else {
1335
						if (obj.id !== '#') {
1336
							dom.addClass(obj.state.opened ? 'jstree-open' : 'jstree-closed');
1337
						}
1338
					}
1339
				}
1340
				dom.removeClass("jstree-loading").attr('aria-busy',false);
1341
				/**
1342
				 * triggered after a node is loaded
1343
				 * @event
1344
				 * @name load_node.jstree
1345
				 * @param {Object} node the node that was loading
1346
				 * @param {Boolean} status was the node loaded successfully
1347
				 */
1348
				this.trigger('load_node', { "node" : obj, "status" : status });
1349
				if(callback) {
1350
					callback.call(this, obj, status);
1351
				}
1352
			}.bind(this));
1353
			return true;
1354
		},
1355
		/**
1356
		 * load an array of nodes (will also load unavailable nodes as soon as they appear in the structure). Used internally.
1357
		 * @private
1358
		 * @name _load_nodes(nodes [, callback])
1359
		 * @param  {array} nodes
1360
		 * @param  {function} callback a function to be executed once loading is complete, the function is executed in the instance's scope and receives one argument - the array passed to _load_nodes
1361
		 */
1362
		_load_nodes : function (nodes, callback, is_callback, force_reload) {
1363
			var r = true,
1364
				c = function () { this._load_nodes(nodes, callback, true); },
1365
				m = this._model.data, i, j, tmp = [];
1366
			for(i = 0, j = nodes.length; i < j; i++) {
1367
				if(m[nodes[i]] && ( (!m[nodes[i]].state.loaded && !m[nodes[i]].state.failed) || (!is_callback && force_reload) )) {
1368
					if(!this.is_loading(nodes[i])) {
1369
						this.load_node(nodes[i], c);
1370
					}
1371
					r = false;
1372
				}
1373
			}
1374
			if(r) {
1375
				for(i = 0, j = nodes.length; i < j; i++) {
1376
					if(m[nodes[i]] && m[nodes[i]].state.loaded) {
1377
						tmp.push(nodes[i]);
1378
					}
1379
				}
1380
				if(callback && !callback.done) {
1381
					callback.call(this, tmp);
1382
					callback.done = true;
1383
				}
1384
			}
1385
		},
1386
		/**
1387
		 * loads all unloaded nodes
1388
		 * @name load_all([obj, callback])
1389
		 * @param {mixed} obj the node to load recursively, omit to load all nodes in the tree
1390
		 * @param {function} callback a function to be executed once loading all the nodes is complete,
1391
		 * @trigger load_all.jstree
1392
		 */
1393
		load_all : function (obj, callback) {
1394
			if(!obj) { obj = $.jstree.root; }
1395
			obj = this.get_node(obj);
1396
			if(!obj) { return false; }
1397
			var to_load = [],
1398
				m = this._model.data,
1399
				c = m[obj.id].children_d,
1400
				i, j;
1401
			if(obj.state && !obj.state.loaded) {
1402
				to_load.push(obj.id);
1403
			}
1404
			for(i = 0, j = c.length; i < j; i++) {
1405
				if(m[c[i]] && m[c[i]].state && !m[c[i]].state.loaded) {
1406
					to_load.push(c[i]);
1407
				}
1408
			}
1409
			if(to_load.length) {
1410
				this._load_nodes(to_load, function () {
1411
					this.load_all(obj, callback);
1412
				});
1413
			}
1414
			else {
1415
				/**
1416
				 * triggered after a load_all call completes
1417
				 * @event
1418
				 * @name load_all.jstree
1419
				 * @param {Object} node the recursively loaded node
1420
				 */
1421
				if(callback) { callback.call(this, obj); }
1422
				this.trigger('load_all', { "node" : obj });
1423
			}
1424
		},
1425
		/**
1426
		 * handles the actual loading of a node. Used only internally.
1427
		 * @private
1428
		 * @name _load_node(obj [, callback])
1429
		 * @param  {mixed} obj
1430
		 * @param  {function} callback a function to be executed once loading is complete, the function is executed in the instance's scope and receives one argument - a boolean status
1431
		 * @return {Boolean}
1432
		 */
1433
		_load_node : function (obj, callback) {
1434
			var s = this.settings.core.data, t;
1435
			var notTextOrCommentNode = function notTextOrCommentNode () {
1436
				return this.nodeType !== 3 && this.nodeType !== 8;
1437
			};
1438
			// use original HTML
1439
			if(!s) {
1440
				if(obj.id === $.jstree.root) {
1441
					return this._append_html_data(obj, this._data.core.original_container_html.clone(true), function (status) {
1442
						callback.call(this, status);
1443
					});
1444
				}
1445
				else {
1446
					return callback.call(this, false);
1447
				}
1448
				// return callback.call(this, obj.id === $.jstree.root ? this._append_html_data(obj, this._data.core.original_container_html.clone(true)) : false);
1449
			}
1450
			if($.vakata.is_function(s)) {
1451
				return s.call(this, obj, function (d) {
1452
					if(d === false) {
1453
						callback.call(this, false);
1454
					}
1455
					else {
1456
						this[typeof d === 'string' ? '_append_html_data' : '_append_json_data'](obj, typeof d === 'string' ? $($.parseHTML(d)).filter(notTextOrCommentNode) : d, function (status) {
1457
							callback.call(this, status);
1458
						});
1459
					}
1460
					// return d === false ? callback.call(this, false) : callback.call(this, this[typeof d === 'string' ? '_append_html_data' : '_append_json_data'](obj, typeof d === 'string' ? $(d) : d));
1461
				}.bind(this));
1462
			}
1463
			if(typeof s === 'object') {
1464
				if(s.url) {
1465
					s = $.extend(true, {}, s);
1466
					if($.vakata.is_function(s.url)) {
1467
						s.url = s.url.call(this, obj);
1468
					}
1469
					if($.vakata.is_function(s.data)) {
1470
						s.data = s.data.call(this, obj);
1471
					}
1472
					return $.ajax(s)
1473
						.done(function (d,t,x) {
1474
								var type = x.getResponseHeader('Content-Type');
1475
								if((type && type.indexOf('json') !== -1) || typeof d === "object") {
1476
									return this._append_json_data(obj, d, function (status) { callback.call(this, status); });
1477
									//return callback.call(this, this._append_json_data(obj, d));
1478
								}
1479
								if((type && type.indexOf('html') !== -1) || typeof d === "string") {
1480
									return this._append_html_data(obj, $($.parseHTML(d)).filter(notTextOrCommentNode), function (status) { callback.call(this, status); });
1481
									// return callback.call(this, this._append_html_data(obj, $(d)));
1482
								}
1483
								this._data.core.last_error = { 'error' : 'ajax', 'plugin' : 'core', 'id' : 'core_04', 'reason' : 'Could not load node', 'data' : JSON.stringify({ 'id' : obj.id, 'xhr' : x }) };
1484
								this.settings.core.error.call(this, this._data.core.last_error);
1485
								return callback.call(this, false);
1486
							}.bind(this))
1487
						.fail(function (f) {
1488
								this._data.core.last_error = { 'error' : 'ajax', 'plugin' : 'core', 'id' : 'core_04', 'reason' : 'Could not load node', 'data' : JSON.stringify({ 'id' : obj.id, 'xhr' : f }) };
1489
								callback.call(this, false);
1490
								this.settings.core.error.call(this, this._data.core.last_error);
1491
							}.bind(this));
1492
				}
1493
				if ($.vakata.is_array(s)) {
1494
					t = $.extend(true, [], s);
1495
				} else if ($.isPlainObject(s)) {
1496
					t = $.extend(true, {}, s);
1497
				} else {
1498
					t = s;
1499
				}
1500
				if(obj.id === $.jstree.root) {
1501
					return this._append_json_data(obj, t, function (status) {
1502
						callback.call(this, status);
1503
					});
1504
				}
1505
				else {
1506
					this._data.core.last_error = { 'error' : 'nodata', 'plugin' : 'core', 'id' : 'core_05', 'reason' : 'Could not load node', 'data' : JSON.stringify({ 'id' : obj.id }) };
1507
					this.settings.core.error.call(this, this._data.core.last_error);
1508
					return callback.call(this, false);
1509
				}
1510
				//return callback.call(this, (obj.id === $.jstree.root ? this._append_json_data(obj, t) : false) );
1511
			}
1512
			if(typeof s === 'string') {
1513
				if(obj.id === $.jstree.root) {
1514
					return this._append_html_data(obj, $($.parseHTML(s)).filter(notTextOrCommentNode), function (status) {
1515
						callback.call(this, status);
1516
					});
1517
				}
1518
				else {
1519
					this._data.core.last_error = { 'error' : 'nodata', 'plugin' : 'core', 'id' : 'core_06', 'reason' : 'Could not load node', 'data' : JSON.stringify({ 'id' : obj.id }) };
1520
					this.settings.core.error.call(this, this._data.core.last_error);
1521
					return callback.call(this, false);
1522
				}
1523
				//return callback.call(this, (obj.id === $.jstree.root ? this._append_html_data(obj, $(s)) : false) );
1524
			}
1525
			return callback.call(this, false);
1526
		},
1527
		/**
1528
		 * adds a node to the list of nodes to redraw. Used only internally.
1529
		 * @private
1530
		 * @name _node_changed(obj [, callback])
1531
		 * @param  {mixed} obj
1532
		 */
1533
		_node_changed : function (obj) {
1534
			obj = this.get_node(obj);
1535
      if (obj && $.inArray(obj.id, this._model.changed) === -1) {
1536
				this._model.changed.push(obj.id);
1537
			}
1538
		},
1539
		/**
1540
		 * appends HTML content to the tree. Used internally.
1541
		 * @private
1542
		 * @name _append_html_data(obj, data)
1543
		 * @param  {mixed} obj the node to append to
1544
		 * @param  {String} data the HTML string to parse and append
1545
		 * @trigger model.jstree, changed.jstree
1546
		 */
1547
		_append_html_data : function (dom, data, cb) {
1548
			dom = this.get_node(dom);
1549
			dom.children = [];
1550
			dom.children_d = [];
1551
			var dat = data.is('ul') ? data.children() : data,
1552
				par = dom.id,
1553
				chd = [],
1554
				dpc = [],
1555
				m = this._model.data,
1556
				p = m[par],
1557
				s = this._data.core.selected.length,
1558
				tmp, i, j;
1559
			dat.each(function (i, v) {
1560
				tmp = this._parse_model_from_html($(v), par, p.parents.concat());
1561
				if(tmp) {
1562
					chd.push(tmp);
1563
					dpc.push(tmp);
1564
					if(m[tmp].children_d.length) {
1565
						dpc = dpc.concat(m[tmp].children_d);
1566
					}
1567
				}
1568
			}.bind(this));
1569
			p.children = chd;
1570
			p.children_d = dpc;
1571
			for(i = 0, j = p.parents.length; i < j; i++) {
1572
				m[p.parents[i]].children_d = m[p.parents[i]].children_d.concat(dpc);
1573
			}
1574
			/**
1575
			 * triggered when new data is inserted to the tree model
1576
			 * @event
1577
			 * @name model.jstree
1578
			 * @param {Array} nodes an array of node IDs
1579
			 * @param {String} parent the parent ID of the nodes
1580
			 */
1581
			this.trigger('model', { "nodes" : dpc, 'parent' : par });
1582
			if(par !== $.jstree.root) {
1583
				this._node_changed(par);
1584
				this.redraw();
1585
			}
1586
			else {
1587
				this.get_container_ul().children('.jstree-initial-node').remove();
1588
				this.redraw(true);
1589
			}
1590
			if(this._data.core.selected.length !== s) {
1591
				this.trigger('changed', { 'action' : 'model', 'selected' : this._data.core.selected });
1592
			}
1593
			cb.call(this, true);
1594
		},
1595
		/**
1596
		 * appends JSON content to the tree. Used internally.
1597
		 * @private
1598
		 * @name _append_json_data(obj, data)
1599
		 * @param  {mixed} obj the node to append to
1600
		 * @param  {String} data the JSON object to parse and append
1601
		 * @param  {Boolean} force_processing internal param - do not set
1602
		 * @trigger model.jstree, changed.jstree
1603
		 */
1604
		_append_json_data : function (dom, data, cb, force_processing) {
1605
			if(this.element === null) { return; }
1606
			dom = this.get_node(dom);
1607
			dom.children = [];
1608
			dom.children_d = [];
1609
			// *%$@!!!
1610
			if(data.d) {
1611
				data = data.d;
1612
				if(typeof data === "string") {
1613
					data = JSON.parse(data);
1614
				}
1615
			}
1616
			if(!$.vakata.is_array(data)) { data = [data]; }
1617
			var w = null,
1618
				args = {
1619
					'df'	: this._model.default_state,
1620
					'dat'	: data,
1621
					'par'	: dom.id,
1622
					'm'		: this._model.data,
1623
					't_id'	: this._id,
1624
					't_cnt'	: this._cnt,
1625
					'sel'	: this._data.core.selected
1626
				},
1627
				inst = this,
1628
				func = function (data, undefined) {
1629
					if(data.data) { data = data.data; }
1630
					var dat = data.dat,
1631
						par = data.par,
1632
						chd = [],
1633
						dpc = [],
1634
						add = [],
1635
						df = data.df,
1636
						t_id = data.t_id,
1637
						t_cnt = data.t_cnt,
1638
						m = data.m,
1639
						p = m[par],
1640
						sel = data.sel,
1641
						tmp, i, j, rslt,
1642
						parse_flat = function (d, p, ps) {
1643
							if(!ps) { ps = []; }
1644
							else { ps = ps.concat(); }
1645
							if(p) { ps.unshift(p); }
1646
							var tid = d.id.toString(),
1647
								i, j, c, e,
1648
								tmp = {
1649
									id			: tid,
1650
									text		: d.text || '',
1651
									icon		: d.icon !== undefined ? d.icon : true,
1652
									parent		: p,
1653
									parents		: ps,
1654
									children	: d.children || [],
1655
									children_d	: d.children_d || [],
1656
									data		: d.data,
1657
									state		: { },
1658
									li_attr		: { id : false },
1659
									a_attr		: { href : '#' },
1660
									original	: false
1661
								};
1662
							for(i in df) {
1663
								if(df.hasOwnProperty(i)) {
1664
									tmp.state[i] = df[i];
1665
								}
1666
							}
1667
							if(d && d.data && d.data.jstree && d.data.jstree.icon) {
1668
								tmp.icon = d.data.jstree.icon;
1669
							}
1670
							if(tmp.icon === undefined || tmp.icon === null || tmp.icon === "") {
1671
								tmp.icon = true;
1672
							}
1673
							if(d && d.data) {
1674
								tmp.data = d.data;
1675
								if(d.data.jstree) {
1676
									for(i in d.data.jstree) {
1677
										if(d.data.jstree.hasOwnProperty(i)) {
1678
											tmp.state[i] = d.data.jstree[i];
1679
										}
1680
									}
1681
								}
1682
							}
1683
							if(d && typeof d.state === 'object') {
1684
								for (i in d.state) {
1685
									if(d.state.hasOwnProperty(i)) {
1686
										tmp.state[i] = d.state[i];
1687
									}
1688
								}
1689
							}
1690
							if(d && typeof d.li_attr === 'object') {
1691
								for (i in d.li_attr) {
1692
									if(d.li_attr.hasOwnProperty(i)) {
1693
										tmp.li_attr[i] = d.li_attr[i];
1694
									}
1695
								}
1696
							}
1697
							if(!tmp.li_attr.id) {
1698
								tmp.li_attr.id = tid;
1699
							}
1700
							if(d && typeof d.a_attr === 'object') {
1701
								for (i in d.a_attr) {
1702
									if(d.a_attr.hasOwnProperty(i)) {
1703
										tmp.a_attr[i] = d.a_attr[i];
1704
									}
1705
								}
1706
							}
1707
							if(d && d.children && d.children === true) {
1708
								tmp.state.loaded = false;
1709
								tmp.children = [];
1710
								tmp.children_d = [];
1711
							}
1712
							m[tmp.id] = tmp;
1713
							for(i = 0, j = tmp.children.length; i < j; i++) {
1714
								c = parse_flat(m[tmp.children[i]], tmp.id, ps);
1715
								e = m[c];
1716
								tmp.children_d.push(c);
1717
								if(e.children_d.length) {
1718
									tmp.children_d = tmp.children_d.concat(e.children_d);
1719
								}
1720
							}
1721
							delete d.data;
1722
							delete d.children;
1723
							m[tmp.id].original = d;
1724
							if(tmp.state.selected) {
1725
								add.push(tmp.id);
1726
							}
1727
							return tmp.id;
1728
						},
1729
						parse_nest = function (d, p, ps) {
1730
							if(!ps) { ps = []; }
1731
							else { ps = ps.concat(); }
1732
							if(p) { ps.unshift(p); }
1733
							var tid = false, i, j, c, e, tmp;
1734
							do {
1735
								tid = 'j' + t_id + '_' + (++t_cnt);
1736
							} while(m[tid]);
1737
 
1738
							tmp = {
1739
								id			: false,
1740
								text		: typeof d === 'string' ? d : '',
1741
								icon		: typeof d === 'object' && d.icon !== undefined ? d.icon : true,
1742
								parent		: p,
1743
								parents		: ps,
1744
								children	: [],
1745
								children_d	: [],
1746
								data		: null,
1747
								state		: { },
1748
								li_attr		: { id : false },
1749
								a_attr		: { href : '#' },
1750
								original	: false
1751
							};
1752
							for(i in df) {
1753
								if(df.hasOwnProperty(i)) {
1754
									tmp.state[i] = df[i];
1755
								}
1756
							}
1757
							if(d && d.id) { tmp.id = d.id.toString(); }
1758
							if(d && d.text) { tmp.text = d.text; }
1759
							if(d && d.data && d.data.jstree && d.data.jstree.icon) {
1760
								tmp.icon = d.data.jstree.icon;
1761
							}
1762
							if(tmp.icon === undefined || tmp.icon === null || tmp.icon === "") {
1763
								tmp.icon = true;
1764
							}
1765
							if(d && d.data) {
1766
								tmp.data = d.data;
1767
								if(d.data.jstree) {
1768
									for(i in d.data.jstree) {
1769
										if(d.data.jstree.hasOwnProperty(i)) {
1770
											tmp.state[i] = d.data.jstree[i];
1771
										}
1772
									}
1773
								}
1774
							}
1775
							if(d && typeof d.state === 'object') {
1776
								for (i in d.state) {
1777
									if(d.state.hasOwnProperty(i)) {
1778
										tmp.state[i] = d.state[i];
1779
									}
1780
								}
1781
							}
1782
							if(d && typeof d.li_attr === 'object') {
1783
								for (i in d.li_attr) {
1784
									if(d.li_attr.hasOwnProperty(i)) {
1785
										tmp.li_attr[i] = d.li_attr[i];
1786
									}
1787
								}
1788
							}
1789
							if(tmp.li_attr.id && !tmp.id) {
1790
								tmp.id = tmp.li_attr.id.toString();
1791
							}
1792
							if(!tmp.id) {
1793
								tmp.id = tid;
1794
							}
1795
							if(!tmp.li_attr.id) {
1796
								tmp.li_attr.id = tmp.id;
1797
							}
1798
							if(d && typeof d.a_attr === 'object') {
1799
								for (i in d.a_attr) {
1800
									if(d.a_attr.hasOwnProperty(i)) {
1801
										tmp.a_attr[i] = d.a_attr[i];
1802
									}
1803
								}
1804
							}
1805
							if(d && d.children && d.children.length) {
1806
								for(i = 0, j = d.children.length; i < j; i++) {
1807
									c = parse_nest(d.children[i], tmp.id, ps);
1808
									e = m[c];
1809
									tmp.children.push(c);
1810
									if(e.children_d.length) {
1811
										tmp.children_d = tmp.children_d.concat(e.children_d);
1812
									}
1813
								}
1814
								tmp.children_d = tmp.children_d.concat(tmp.children);
1815
							}
1816
							if(d && d.children && d.children === true) {
1817
								tmp.state.loaded = false;
1818
								tmp.children = [];
1819
								tmp.children_d = [];
1820
							}
1821
							delete d.data;
1822
							delete d.children;
1823
							tmp.original = d;
1824
							m[tmp.id] = tmp;
1825
							if(tmp.state.selected) {
1826
								add.push(tmp.id);
1827
							}
1828
							return tmp.id;
1829
						};
1830
 
1831
					if(dat.length && dat[0].id !== undefined && dat[0].parent !== undefined) {
1832
						// Flat JSON support (for easy import from DB):
1833
						// 1) convert to object (foreach)
1834
						for(i = 0, j = dat.length; i < j; i++) {
1835
							if(!dat[i].children) {
1836
								dat[i].children = [];
1837
							}
1838
							if(!dat[i].state) {
1839
								dat[i].state = {};
1840
							}
1841
							m[dat[i].id.toString()] = dat[i];
1842
						}
1843
						// 2) populate children (foreach)
1844
						for(i = 0, j = dat.length; i < j; i++) {
1845
							if (!m[dat[i].parent.toString()]) {
1846
								if (typeof inst !== "undefined") {
1847
									inst._data.core.last_error = { 'error' : 'parse', 'plugin' : 'core', 'id' : 'core_07', 'reason' : 'Node with invalid parent', 'data' : JSON.stringify({ 'id' : dat[i].id.toString(), 'parent' : dat[i].parent.toString() }) };
1848
									inst.settings.core.error.call(inst, inst._data.core.last_error);
1849
								}
1850
								continue;
1851
							}
1852
 
1853
							m[dat[i].parent.toString()].children.push(dat[i].id.toString());
1854
							// populate parent.children_d
1855
							p.children_d.push(dat[i].id.toString());
1856
						}
1857
						// 3) normalize && populate parents and children_d with recursion
1858
						for(i = 0, j = p.children.length; i < j; i++) {
1859
							tmp = parse_flat(m[p.children[i]], par, p.parents.concat());
1860
							dpc.push(tmp);
1861
							if(m[tmp].children_d.length) {
1862
								dpc = dpc.concat(m[tmp].children_d);
1863
							}
1864
						}
1865
						for(i = 0, j = p.parents.length; i < j; i++) {
1866
							m[p.parents[i]].children_d = m[p.parents[i]].children_d.concat(dpc);
1867
						}
1868
						// ?) three_state selection - p.state.selected && t - (if three_state foreach(dat => ch) -> foreach(parents) if(parent.selected) child.selected = true;
1869
						rslt = {
1870
							'cnt' : t_cnt,
1871
							'mod' : m,
1872
							'sel' : sel,
1873
							'par' : par,
1874
							'dpc' : dpc,
1875
							'add' : add
1876
						};
1877
					}
1878
					else {
1879
						for(i = 0, j = dat.length; i < j; i++) {
1880
							tmp = parse_nest(dat[i], par, p.parents.concat());
1881
							if(tmp) {
1882
								chd.push(tmp);
1883
								dpc.push(tmp);
1884
								if(m[tmp].children_d.length) {
1885
									dpc = dpc.concat(m[tmp].children_d);
1886
								}
1887
							}
1888
						}
1889
						p.children = chd;
1890
						p.children_d = dpc;
1891
						for(i = 0, j = p.parents.length; i < j; i++) {
1892
							m[p.parents[i]].children_d = m[p.parents[i]].children_d.concat(dpc);
1893
						}
1894
						rslt = {
1895
							'cnt' : t_cnt,
1896
							'mod' : m,
1897
							'sel' : sel,
1898
							'par' : par,
1899
							'dpc' : dpc,
1900
							'add' : add
1901
						};
1902
					}
1903
					if(typeof window === 'undefined' || typeof window.document === 'undefined') {
1904
						postMessage(rslt);
1905
					}
1906
					else {
1907
						return rslt;
1908
					}
1909
				},
1910
				rslt = function (rslt, worker) {
1911
					if(this.element === null) { return; }
1912
					this._cnt = rslt.cnt;
1913
					var i, m = this._model.data;
1914
					for (i in m) {
1915
						if (m.hasOwnProperty(i) && m[i].state && m[i].state.loading && rslt.mod[i]) {
1916
							rslt.mod[i].state.loading = true;
1917
						}
1918
					}
1919
					this._model.data = rslt.mod; // breaks the reference in load_node - careful
1920
 
1921
					if(worker) {
1922
						var j, a = rslt.add, r = rslt.sel, s = this._data.core.selected.slice();
1923
						m = this._model.data;
1924
						// if selection was changed while calculating in worker
1925
						if(r.length !== s.length || $.vakata.array_unique(r.concat(s)).length !== r.length) {
1926
							// deselect nodes that are no longer selected
1927
							for(i = 0, j = r.length; i < j; i++) {
1928
								if($.inArray(r[i], a) === -1 && $.inArray(r[i], s) === -1) {
1929
									m[r[i]].state.selected = false;
1930
								}
1931
							}
1932
							// select nodes that were selected in the mean time
1933
							for(i = 0, j = s.length; i < j; i++) {
1934
								if($.inArray(s[i], r) === -1) {
1935
									m[s[i]].state.selected = true;
1936
								}
1937
							}
1938
						}
1939
					}
1940
					if(rslt.add.length) {
1941
						this._data.core.selected = this._data.core.selected.concat(rslt.add);
1942
					}
1943
 
1944
					this.trigger('model', { "nodes" : rslt.dpc, 'parent' : rslt.par });
1945
 
1946
					if(rslt.par !== $.jstree.root) {
1947
						this._node_changed(rslt.par);
1948
						this.redraw();
1949
					}
1950
					else {
1951
						// this.get_container_ul().children('.jstree-initial-node').remove();
1952
						this.redraw(true);
1953
					}
1954
					if(rslt.add.length) {
1955
						this.trigger('changed', { 'action' : 'model', 'selected' : this._data.core.selected });
1956
					}
1957
 
1958
					// If no worker, try to mimic worker behavioour, by invoking cb asynchronously
1959
					if (!worker && setImmediate) {
1960
						setImmediate(function(){
1961
							cb.call(inst, true);
1962
						});
1963
					}
1964
					else {
1965
						cb.call(inst, true);
1966
					}
1967
				};
1968
			if(this.settings.core.worker && window.Blob && window.URL && window.Worker) {
1969
				try {
1970
					if(this._wrk === null) {
1971
						this._wrk = window.URL.createObjectURL(
1972
							new window.Blob(
1973
								['self.onmessage = ' + func.toString()],
1974
								{type:"text/javascript"}
1975
							)
1976
						);
1977
					}
1978
					if(!this._data.core.working || force_processing) {
1979
						this._data.core.working = true;
1980
						w = new window.Worker(this._wrk);
1981
						w.onmessage = function (e) {
1982
							rslt.call(this, e.data, true);
1983
							try { w.terminate(); w = null; } catch(ignore) { }
1984
							if(this._data.core.worker_queue.length) {
1985
								this._append_json_data.apply(this, this._data.core.worker_queue.shift());
1986
							}
1987
							else {
1988
								this._data.core.working = false;
1989
							}
1990
						}.bind(this);
1991
						if(!args.par) {
1992
							if(this._data.core.worker_queue.length) {
1993
								this._append_json_data.apply(this, this._data.core.worker_queue.shift());
1994
							}
1995
							else {
1996
								this._data.core.working = false;
1997
							}
1998
						}
1999
						else {
2000
							w.postMessage(args);
2001
						}
2002
					}
2003
					else {
2004
						this._data.core.worker_queue.push([dom, data, cb, true]);
2005
					}
2006
				}
2007
				catch(e) {
2008
					rslt.call(this, func(args), false);
2009
					if(this._data.core.worker_queue.length) {
2010
						this._append_json_data.apply(this, this._data.core.worker_queue.shift());
2011
					}
2012
					else {
2013
						this._data.core.working = false;
2014
					}
2015
				}
2016
			}
2017
			else {
2018
				rslt.call(this, func(args), false);
2019
			}
2020
		},
2021
		/**
2022
		 * parses a node from a jQuery object and appends them to the in memory tree model. Used internally.
2023
		 * @private
2024
		 * @name _parse_model_from_html(d [, p, ps])
2025
		 * @param  {jQuery} d the jQuery object to parse
2026
		 * @param  {String} p the parent ID
2027
		 * @param  {Array} ps list of all parents
2028
		 * @return {String} the ID of the object added to the model
2029
		 */
2030
		_parse_model_from_html : function (d, p, ps) {
2031
			if(!ps) { ps = []; }
2032
			else { ps = [].concat(ps); }
2033
			if(p) { ps.unshift(p); }
2034
			var c, e, m = this._model.data,
2035
				data = {
2036
					id			: false,
2037
					text		: false,
2038
					icon		: true,
2039
					parent		: p,
2040
					parents		: ps,
2041
					children	: [],
2042
					children_d	: [],
2043
					data		: null,
2044
					state		: { },
2045
					li_attr		: { id : false },
2046
					a_attr		: { href : '#' },
2047
					original	: false
2048
				}, i, tmp, tid;
2049
			for(i in this._model.default_state) {
2050
				if(this._model.default_state.hasOwnProperty(i)) {
2051
					data.state[i] = this._model.default_state[i];
2052
				}
2053
			}
2054
			tmp = $.vakata.attributes(d, true);
2055
			$.each(tmp, function (i, v) {
2056
				v = $.vakata.trim(v);
2057
				if(!v.length) { return true; }
2058
				data.li_attr[i] = v;
2059
				if(i === 'id') {
2060
					data.id = v.toString();
2061
				}
2062
			});
2063
			tmp = d.children('a').first();
2064
			if(tmp.length) {
2065
				tmp = $.vakata.attributes(tmp, true);
2066
				$.each(tmp, function (i, v) {
2067
					v = $.vakata.trim(v);
2068
					if(v.length) {
2069
						data.a_attr[i] = v;
2070
					}
2071
				});
2072
			}
2073
			tmp = d.children("a").first().length ? d.children("a").first().clone() : d.clone();
2074
			tmp.children("ins, i, ul").remove();
2075
			tmp = tmp.html();
2076
			tmp = $('<div></div>').html(tmp);
2077
			data.text = this.settings.core.force_text ? tmp.text() : tmp.html();
2078
			tmp = d.data();
2079
			data.data = tmp ? $.extend(true, {}, tmp) : null;
2080
			data.state.opened = d.hasClass('jstree-open');
2081
			data.state.selected = d.children('a').hasClass('jstree-clicked');
2082
			data.state.disabled = d.children('a').hasClass('jstree-disabled');
2083
			if(data.data && data.data.jstree) {
2084
				for(i in data.data.jstree) {
2085
					if(data.data.jstree.hasOwnProperty(i)) {
2086
						data.state[i] = data.data.jstree[i];
2087
					}
2088
				}
2089
			}
2090
			tmp = d.children("a").children(".jstree-themeicon");
2091
			if(tmp.length) {
2092
				data.icon = tmp.hasClass('jstree-themeicon-hidden') ? false : tmp.attr('rel');
2093
			}
2094
			if(data.state.icon !== undefined) {
2095
				data.icon = data.state.icon;
2096
			}
2097
			if(data.icon === undefined || data.icon === null || data.icon === "") {
2098
				data.icon = true;
2099
			}
2100
			tmp = d.children("ul").children("li");
2101
			do {
2102
				tid = 'j' + this._id + '_' + (++this._cnt);
2103
			} while(m[tid]);
2104
			data.id = data.li_attr.id ? data.li_attr.id.toString() : tid;
2105
			if(tmp.length) {
2106
				tmp.each(function (i, v) {
2107
					c = this._parse_model_from_html($(v), data.id, ps);
2108
					e = this._model.data[c];
2109
					data.children.push(c);
2110
					if(e.children_d.length) {
2111
						data.children_d = data.children_d.concat(e.children_d);
2112
					}
2113
				}.bind(this));
2114
				data.children_d = data.children_d.concat(data.children);
2115
			}
2116
			else {
2117
				if(d.hasClass('jstree-closed')) {
2118
					data.state.loaded = false;
2119
				}
2120
			}
2121
			if(data.li_attr['class']) {
2122
				data.li_attr['class'] = data.li_attr['class'].replace('jstree-closed','').replace('jstree-open','');
2123
			}
2124
			if(data.a_attr['class']) {
2125
				data.a_attr['class'] = data.a_attr['class'].replace('jstree-clicked','').replace('jstree-disabled','');
2126
			}
2127
			m[data.id] = data;
2128
			if(data.state.selected) {
2129
				this._data.core.selected.push(data.id);
2130
			}
2131
			return data.id;
2132
		},
2133
		/**
2134
		 * parses a node from a JSON object (used when dealing with flat data, which has no nesting of children, but has id and parent properties) and appends it to the in memory tree model. Used internally.
2135
		 * @private
2136
		 * @name _parse_model_from_flat_json(d [, p, ps])
2137
		 * @param  {Object} d the JSON object to parse
2138
		 * @param  {String} p the parent ID
2139
		 * @param  {Array} ps list of all parents
2140
		 * @return {String} the ID of the object added to the model
2141
		 */
2142
		_parse_model_from_flat_json : function (d, p, ps) {
2143
			if(!ps) { ps = []; }
2144
			else { ps = ps.concat(); }
2145
			if(p) { ps.unshift(p); }
2146
			var tid = d.id.toString(),
2147
				m = this._model.data,
2148
				df = this._model.default_state,
2149
				i, j, c, e,
2150
				tmp = {
2151
					id			: tid,
2152
					text		: d.text || '',
2153
					icon		: d.icon !== undefined ? d.icon : true,
2154
					parent		: p,
2155
					parents		: ps,
2156
					children	: d.children || [],
2157
					children_d	: d.children_d || [],
2158
					data		: d.data,
2159
					state		: { },
2160
					li_attr		: { id : false },
2161
					a_attr		: { href : '#' },
2162
					original	: false
2163
				};
2164
			for(i in df) {
2165
				if(df.hasOwnProperty(i)) {
2166
					tmp.state[i] = df[i];
2167
				}
2168
			}
2169
			if(d && d.data && d.data.jstree && d.data.jstree.icon) {
2170
				tmp.icon = d.data.jstree.icon;
2171
			}
2172
			if(tmp.icon === undefined || tmp.icon === null || tmp.icon === "") {
2173
				tmp.icon = true;
2174
			}
2175
			if(d && d.data) {
2176
				tmp.data = d.data;
2177
				if(d.data.jstree) {
2178
					for(i in d.data.jstree) {
2179
						if(d.data.jstree.hasOwnProperty(i)) {
2180
							tmp.state[i] = d.data.jstree[i];
2181
						}
2182
					}
2183
				}
2184
			}
2185
			if(d && typeof d.state === 'object') {
2186
				for (i in d.state) {
2187
					if(d.state.hasOwnProperty(i)) {
2188
						tmp.state[i] = d.state[i];
2189
					}
2190
				}
2191
			}
2192
			if(d && typeof d.li_attr === 'object') {
2193
				for (i in d.li_attr) {
2194
					if(d.li_attr.hasOwnProperty(i)) {
2195
						tmp.li_attr[i] = d.li_attr[i];
2196
					}
2197
				}
2198
			}
2199
			if(!tmp.li_attr.id) {
2200
				tmp.li_attr.id = tid;
2201
			}
2202
			if(d && typeof d.a_attr === 'object') {
2203
				for (i in d.a_attr) {
2204
					if(d.a_attr.hasOwnProperty(i)) {
2205
						tmp.a_attr[i] = d.a_attr[i];
2206
					}
2207
				}
2208
			}
2209
			if(d && d.children && d.children === true) {
2210
				tmp.state.loaded = false;
2211
				tmp.children = [];
2212
				tmp.children_d = [];
2213
			}
2214
			m[tmp.id] = tmp;
2215
			for(i = 0, j = tmp.children.length; i < j; i++) {
2216
				c = this._parse_model_from_flat_json(m[tmp.children[i]], tmp.id, ps);
2217
				e = m[c];
2218
				tmp.children_d.push(c);
2219
				if(e.children_d.length) {
2220
					tmp.children_d = tmp.children_d.concat(e.children_d);
2221
				}
2222
			}
2223
			delete d.data;
2224
			delete d.children;
2225
			m[tmp.id].original = d;
2226
			if(tmp.state.selected) {
2227
				this._data.core.selected.push(tmp.id);
2228
			}
2229
			return tmp.id;
2230
		},
2231
		/**
2232
		 * parses a node from a JSON object and appends it to the in memory tree model. Used internally.
2233
		 * @private
2234
		 * @name _parse_model_from_json(d [, p, ps])
2235
		 * @param  {Object} d the JSON object to parse
2236
		 * @param  {String} p the parent ID
2237
		 * @param  {Array} ps list of all parents
2238
		 * @return {String} the ID of the object added to the model
2239
		 */
2240
		_parse_model_from_json : function (d, p, ps) {
2241
			if(!ps) { ps = []; }
2242
			else { ps = ps.concat(); }
2243
			if(p) { ps.unshift(p); }
2244
			var tid = false, i, j, c, e, m = this._model.data, df = this._model.default_state, tmp;
2245
			do {
2246
				tid = 'j' + this._id + '_' + (++this._cnt);
2247
			} while(m[tid]);
2248
 
2249
			tmp = {
2250
				id			: false,
2251
				text		: typeof d === 'string' ? d : '',
2252
				icon		: typeof d === 'object' && d.icon !== undefined ? d.icon : true,
2253
				parent		: p,
2254
				parents		: ps,
2255
				children	: [],
2256
				children_d	: [],
2257
				data		: null,
2258
				state		: { },
2259
				li_attr		: { id : false },
2260
				a_attr		: { href : '#' },
2261
				original	: false
2262
			};
2263
			for(i in df) {
2264
				if(df.hasOwnProperty(i)) {
2265
					tmp.state[i] = df[i];
2266
				}
2267
			}
2268
			if(d && d.id) { tmp.id = d.id.toString(); }
2269
			if(d && d.text) { tmp.text = d.text; }
2270
			if(d && d.data && d.data.jstree && d.data.jstree.icon) {
2271
				tmp.icon = d.data.jstree.icon;
2272
			}
2273
			if(tmp.icon === undefined || tmp.icon === null || tmp.icon === "") {
2274
				tmp.icon = true;
2275
			}
2276
			if(d && d.data) {
2277
				tmp.data = d.data;
2278
				if(d.data.jstree) {
2279
					for(i in d.data.jstree) {
2280
						if(d.data.jstree.hasOwnProperty(i)) {
2281
							tmp.state[i] = d.data.jstree[i];
2282
						}
2283
					}
2284
				}
2285
			}
2286
			if(d && typeof d.state === 'object') {
2287
				for (i in d.state) {
2288
					if(d.state.hasOwnProperty(i)) {
2289
						tmp.state[i] = d.state[i];
2290
					}
2291
				}
2292
			}
2293
			if(d && typeof d.li_attr === 'object') {
2294
				for (i in d.li_attr) {
2295
					if(d.li_attr.hasOwnProperty(i)) {
2296
						tmp.li_attr[i] = d.li_attr[i];
2297
					}
2298
				}
2299
			}
2300
			if(tmp.li_attr.id && !tmp.id) {
2301
				tmp.id = tmp.li_attr.id.toString();
2302
			}
2303
			if(!tmp.id) {
2304
				tmp.id = tid;
2305
			}
2306
			if(!tmp.li_attr.id) {
2307
				tmp.li_attr.id = tmp.id;
2308
			}
2309
			if(d && typeof d.a_attr === 'object') {
2310
				for (i in d.a_attr) {
2311
					if(d.a_attr.hasOwnProperty(i)) {
2312
						tmp.a_attr[i] = d.a_attr[i];
2313
					}
2314
				}
2315
			}
2316
			if(d && d.children && d.children.length) {
2317
				for(i = 0, j = d.children.length; i < j; i++) {
2318
					c = this._parse_model_from_json(d.children[i], tmp.id, ps);
2319
					e = m[c];
2320
					tmp.children.push(c);
2321
					if(e.children_d.length) {
2322
						tmp.children_d = tmp.children_d.concat(e.children_d);
2323
					}
2324
				}
2325
				tmp.children_d = tmp.children.concat(tmp.children_d);
2326
			}
2327
			if(d && d.children && d.children === true) {
2328
				tmp.state.loaded = false;
2329
				tmp.children = [];
2330
				tmp.children_d = [];
2331
			}
2332
			delete d.data;
2333
			delete d.children;
2334
			tmp.original = d;
2335
			m[tmp.id] = tmp;
2336
			if(tmp.state.selected) {
2337
				this._data.core.selected.push(tmp.id);
2338
			}
2339
			return tmp.id;
2340
		},
2341
		/**
2342
		 * redraws all nodes that need to be redrawn. Used internally.
2343
		 * @private
2344
		 * @name _redraw()
2345
		 * @trigger redraw.jstree
2346
		 */
2347
		_redraw : function () {
2348
			var nodes = this._model.force_full_redraw ? this._model.data[$.jstree.root].children.concat([]) : this._model.changed.concat([]),
2349
				f = document.createElement('UL'), tmp, i, j, fe = this._data.core.focused;
2350
			for(i = 0, j = nodes.length; i < j; i++) {
2351
				tmp = this.redraw_node(nodes[i], true, this._model.force_full_redraw);
2352
				if(tmp && this._model.force_full_redraw) {
2353
					f.appendChild(tmp);
2354
				}
2355
			}
2356
			if(this._model.force_full_redraw) {
2357
				f.className = this.get_container_ul()[0].className;
2358
				f.setAttribute('role','group');
2359
				this.element.empty().append(f);
2360
				//this.get_container_ul()[0].appendChild(f);
2361
			}
2362
			if(fe !== null && this.settings.core.restore_focus) {
2363
				tmp = this.get_node(fe, true);
2364
				if(tmp && tmp.length && tmp.children('.jstree-anchor')[0] !== document.activeElement) {
2365
					tmp.children('.jstree-anchor').trigger('focus');
2366
				}
2367
				else {
2368
					this._data.core.focused = null;
2369
				}
2370
			}
2371
			this._model.force_full_redraw = false;
2372
			this._model.changed = [];
2373
			/**
2374
			 * triggered after nodes are redrawn
2375
			 * @event
2376
			 * @name redraw.jstree
2377
			 * @param {array} nodes the redrawn nodes
2378
			 */
2379
			this.trigger('redraw', { "nodes" : nodes });
2380
		},
2381
		/**
2382
		 * redraws all nodes that need to be redrawn or optionally - the whole tree
2383
		 * @name redraw([full])
2384
		 * @param {Boolean} full if set to `true` all nodes are redrawn.
2385
		 */
2386
		redraw : function (full) {
2387
			if(full) {
2388
				this._model.force_full_redraw = true;
2389
			}
2390
			//if(this._model.redraw_timeout) {
2391
			//	clearTimeout(this._model.redraw_timeout);
2392
			//}
2393
			//this._model.redraw_timeout = setTimeout($.proxy(this._redraw, this),0);
2394
			this._redraw();
2395
		},
2396
		/**
2397
		 * redraws a single node's children. Used internally.
2398
		 * @private
2399
		 * @name draw_children(node)
2400
		 * @param {mixed} node the node whose children will be redrawn
2401
		 */
2402
		draw_children : function (node) {
2403
			var obj = this.get_node(node),
2404
				i = false,
2405
				j = false,
2406
				k = false,
2407
				d = document;
2408
			if(!obj) { return false; }
2409
			if(obj.id === $.jstree.root) { return this.redraw(true); }
2410
			node = this.get_node(node, true);
2411
			if(!node || !node.length) { return false; } // TODO: quick toggle
2412
 
2413
			node.children('.jstree-children').remove();
2414
			node = node[0];
2415
			if(obj.children.length && obj.state.loaded) {
2416
				k = d.createElement('UL');
2417
				k.setAttribute('role', 'group');
2418
				k.className = 'jstree-children';
2419
				for(i = 0, j = obj.children.length; i < j; i++) {
2420
					k.appendChild(this.redraw_node(obj.children[i], true, true));
2421
				}
2422
				node.appendChild(k);
2423
			}
2424
		},
2425
		/**
2426
		 * redraws a single node. Used internally.
2427
		 * @private
2428
		 * @name redraw_node(node, deep, is_callback, force_render)
2429
		 * @param {mixed} node the node to redraw
2430
		 * @param {Boolean} deep should child nodes be redrawn too
2431
		 * @param {Boolean} is_callback is this a recursion call
2432
		 * @param {Boolean} force_render should children of closed parents be drawn anyway
2433
		 */
2434
		redraw_node : function (node, deep, is_callback, force_render) {
2435
			var obj = this.get_node(node),
2436
				par = false,
2437
				ind = false,
2438
				old = false,
2439
				i = false,
2440
				j = false,
2441
				k = false,
2442
				c = '',
2443
				d = document,
2444
				m = this._model.data,
2445
				f = false,
2446
				s = false,
2447
				tmp = null,
2448
				t = 0,
2449
				l = 0,
2450
				has_children = false,
2451
				last_sibling = false;
2452
			if(!obj) { return false; }
2453
			if(obj.id === $.jstree.root) {  return this.redraw(true); }
2454
			deep = deep || obj.children.length === 0;
2455
			node = !document.querySelector ? document.getElementById(obj.id) : this.element[0].querySelector('#' + ("0123456789".indexOf(obj.id[0]) !== -1 ? '\\3' + obj.id[0] + ' ' + obj.id.substr(1).replace($.jstree.idregex,'\\$&') : obj.id.replace($.jstree.idregex,'\\$&')) ); //, this.element);
2456
			if(!node) {
2457
				deep = true;
2458
				//node = d.createElement('LI');
2459
				if(!is_callback) {
2460
					par = obj.parent !== $.jstree.root ? $('#' + obj.parent.replace($.jstree.idregex,'\\$&'), this.element)[0] : null;
2461
					if(par !== null && (!par || !m[obj.parent].state.opened)) {
2462
						return false;
2463
					}
2464
					ind = $.inArray(obj.id, par === null ? m[$.jstree.root].children : m[obj.parent].children);
2465
				}
2466
			}
2467
			else {
2468
				node = $(node);
2469
				if(!is_callback) {
2470
					par = node.parent().parent()[0];
2471
					if(par === this.element[0]) {
2472
						par = null;
2473
					}
2474
					ind = node.index();
2475
				}
2476
				// m[obj.id].data = node.data(); // use only node's data, no need to touch jquery storage
2477
				if(!deep && obj.children.length && !node.children('.jstree-children').length) {
2478
					deep = true;
2479
				}
2480
				if(!deep) {
2481
					old = node.children('.jstree-children')[0];
2482
				}
2483
				f = node.children('.jstree-anchor')[0] === document.activeElement;
2484
				node.remove();
2485
				//node = d.createElement('LI');
2486
				//node = node[0];
2487
			}
2488
			node = this._data.core.node.cloneNode(true);
2489
			// node is DOM, deep is boolean
2490
 
2491
			c = 'jstree-node ';
2492
			for(i in obj.li_attr) {
2493
				if(obj.li_attr.hasOwnProperty(i)) {
2494
					if(i === 'id') { continue; }
2495
					if(i !== 'class') {
2496
						node.setAttribute(i, obj.li_attr[i]);
2497
					}
2498
					else {
2499
						c += obj.li_attr[i];
2500
					}
2501
				}
2502
			}
2503
			if(!obj.a_attr.id) {
2504
				obj.a_attr.id = obj.id + '_anchor';
2505
			}
2506
			node.childNodes[1].setAttribute('aria-selected', !!obj.state.selected);
2507
			node.childNodes[1].setAttribute('aria-level', obj.parents.length);
2508
			if(this.settings.core.compute_elements_positions) {
2509
				node.childNodes[1].setAttribute('aria-setsize', m[obj.parent].children.length);
2510
				node.childNodes[1].setAttribute('aria-posinset', m[obj.parent].children.indexOf(obj.id) + 1);
2511
			}
2512
			if(obj.state.disabled) {
2513
				node.childNodes[1].setAttribute('aria-disabled', true);
2514
			}
2515
 
2516
			for(i = 0, j = obj.children.length; i < j; i++) {
2517
				if(!m[obj.children[i]].state.hidden) {
2518
					has_children = true;
2519
					break;
2520
				}
2521
			}
2522
			if(obj.parent !== null && m[obj.parent] && !obj.state.hidden) {
2523
				i = $.inArray(obj.id, m[obj.parent].children);
2524
				last_sibling = obj.id;
2525
				if(i !== -1) {
2526
					i++;
2527
					for(j = m[obj.parent].children.length; i < j; i++) {
2528
						if(!m[m[obj.parent].children[i]].state.hidden) {
2529
							last_sibling = m[obj.parent].children[i];
2530
						}
2531
						if(last_sibling !== obj.id) {
2532
							break;
2533
						}
2534
					}
2535
				}
2536
			}
2537
 
2538
			if(obj.state.hidden) {
2539
				c += ' jstree-hidden';
2540
			}
2541
			if (obj.state.loading) {
2542
				c += ' jstree-loading';
2543
			}
2544
			if(obj.state.loaded && !has_children) {
2545
				c += ' jstree-leaf';
2546
			}
2547
			else {
2548
				c += obj.state.opened && obj.state.loaded ? ' jstree-open' : ' jstree-closed';
2549
				node.childNodes[1].setAttribute('aria-expanded', (obj.state.opened && obj.state.loaded) );
2550
			}
2551
			if(last_sibling === obj.id) {
2552
				c += ' jstree-last';
2553
			}
2554
			node.id = obj.id;
2555
			node.className = c;
2556
			c = ( obj.state.selected ? ' jstree-clicked' : '') + ( obj.state.disabled ? ' jstree-disabled' : '');
2557
			for(j in obj.a_attr) {
2558
				if(obj.a_attr.hasOwnProperty(j)) {
2559
					if(j === 'href' && obj.a_attr[j] === '#') { continue; }
2560
					if(j !== 'class') {
2561
						node.childNodes[1].setAttribute(j, obj.a_attr[j]);
2562
					}
2563
					else {
2564
						c += ' ' + obj.a_attr[j];
2565
					}
2566
				}
2567
			}
2568
			if(c.length) {
2569
				node.childNodes[1].className = 'jstree-anchor ' + c;
2570
			}
2571
			if((obj.icon && obj.icon !== true) || obj.icon === false) {
2572
				if(obj.icon === false) {
2573
					node.childNodes[1].childNodes[0].className += ' jstree-themeicon-hidden';
2574
				}
2575
				else if(obj.icon.indexOf('/') === -1 && obj.icon.indexOf('.') === -1) {
2576
					node.childNodes[1].childNodes[0].className += ' ' + obj.icon + ' jstree-themeicon-custom';
2577
				}
2578
				else {
2579
					node.childNodes[1].childNodes[0].style.backgroundImage = 'url("'+obj.icon+'")';
2580
					node.childNodes[1].childNodes[0].style.backgroundPosition = 'center center';
2581
					node.childNodes[1].childNodes[0].style.backgroundSize = 'auto';
2582
					node.childNodes[1].childNodes[0].className += ' jstree-themeicon-custom';
2583
				}
2584
			}
2585
 
2586
			if(this.settings.core.force_text) {
2587
				node.childNodes[1].appendChild(d.createTextNode(obj.text));
2588
			}
2589
			else {
2590
				node.childNodes[1].innerHTML += obj.text;
2591
			}
2592
 
2593
 
2594
			if(deep && obj.children.length && (obj.state.opened || force_render) && obj.state.loaded) {
2595
				k = d.createElement('UL');
2596
				k.setAttribute('role', 'group');
2597
				k.className = 'jstree-children';
2598
				for(i = 0, j = obj.children.length; i < j; i++) {
2599
					k.appendChild(this.redraw_node(obj.children[i], deep, true));
2600
				}
2601
				node.appendChild(k);
2602
			}
2603
			if(old) {
2604
				node.appendChild(old);
2605
			}
2606
			if(!is_callback) {
2607
				// append back using par / ind
2608
				if(!par) {
2609
					par = this.element[0];
2610
				}
2611
				for(i = 0, j = par.childNodes.length; i < j; i++) {
2612
					if(par.childNodes[i] && par.childNodes[i].className && par.childNodes[i].className.indexOf('jstree-children') !== -1) {
2613
						tmp = par.childNodes[i];
2614
						break;
2615
					}
2616
				}
2617
				if(!tmp) {
2618
					tmp = d.createElement('UL');
2619
					tmp.setAttribute('role', 'group');
2620
					tmp.className = 'jstree-children';
2621
					par.appendChild(tmp);
2622
				}
2623
				par = tmp;
2624
 
2625
				if(ind < par.childNodes.length) {
2626
					par.insertBefore(node, par.childNodes[ind]);
2627
				}
2628
				else {
2629
					par.appendChild(node);
2630
				}
2631
				if(f) {
2632
					t = this.element[0].scrollTop;
2633
					l = this.element[0].scrollLeft;
2634
					node.childNodes[1].focus();
2635
					this.element[0].scrollTop = t;
2636
					this.element[0].scrollLeft = l;
2637
				}
2638
			}
2639
			if(obj.state.opened && !obj.state.loaded) {
2640
				obj.state.opened = false;
2641
				setTimeout(function () {
2642
					this.open_node(obj.id, false, 0);
2643
				}.bind(this), 0);
2644
			}
2645
			return node;
2646
		},
2647
		/**
2648
		 * opens a node, revealing its children. If the node is not loaded it will be loaded and opened once ready.
2649
		 * @name open_node(obj [, callback, animation])
2650
		 * @param {mixed} obj the node to open
2651
		 * @param {Function} callback a function to execute once the node is opened
2652
		 * @param {Number} animation the animation duration in milliseconds when opening the node (overrides the `core.animation` setting). Use `false` for no animation.
2653
		 * @trigger open_node.jstree, after_open.jstree, before_open.jstree
2654
		 */
2655
		open_node : function (obj, callback, animation) {
2656
			var t1, t2, d, t;
2657
			if($.vakata.is_array(obj)) {
2658
				obj = obj.slice();
2659
				for(t1 = 0, t2 = obj.length; t1 < t2; t1++) {
2660
					this.open_node(obj[t1], callback, animation);
2661
				}
2662
				return true;
2663
			}
2664
			obj = this.get_node(obj);
2665
			if(!obj || obj.id === $.jstree.root) {
2666
				return false;
2667
			}
2668
			animation = animation === undefined ? this.settings.core.animation : animation;
2669
			if(!this.is_closed(obj)) {
2670
				if(callback) {
2671
					callback.call(this, obj, false);
2672
				}
2673
				return false;
2674
			}
2675
			if(!this.is_loaded(obj)) {
2676
				if(this.is_loading(obj)) {
2677
					return setTimeout(function () {
2678
						this.open_node(obj, callback, animation);
2679
					}.bind(this), 500);
2680
				}
2681
				this.load_node(obj, function (o, ok) {
2682
					return ok ? this.open_node(o, callback, animation) : (callback ? callback.call(this, o, false) : false);
2683
				});
2684
			}
2685
			else {
2686
				d = this.get_node(obj, true);
2687
				t = this;
2688
				if(d.length) {
2689
					if(animation && d.children(".jstree-children").length) {
2690
						d.children(".jstree-children").stop(true, true);
2691
					}
2692
					if(obj.children.length && !this._firstChild(d.children('.jstree-children')[0])) {
2693
						this.draw_children(obj);
2694
						//d = this.get_node(obj, true);
2695
					}
2696
					if(!animation) {
2697
						this.trigger('before_open', { "node" : obj });
2698
						d[0].className = d[0].className.replace('jstree-closed', 'jstree-open');
2699
						d[0].childNodes[1].setAttribute("aria-expanded", true);
2700
					}
2701
					else {
2702
						this.trigger('before_open', { "node" : obj });
2703
						d
2704
							.children(".jstree-children").css("display","none").end()
2705
							.removeClass("jstree-closed").addClass("jstree-open")
2706
								.children('.jstree-anchor').attr("aria-expanded", true).end()
2707
							.children(".jstree-children").stop(true, true)
2708
								.slideDown(animation, function () {
2709
									this.style.display = "";
2710
									if (t.element) {
2711
										t.trigger("after_open", { "node" : obj });
2712
									}
2713
								});
2714
					}
2715
				}
2716
				obj.state.opened = true;
2717
				if(callback) {
2718
					callback.call(this, obj, true);
2719
				}
2720
				if(!d.length) {
2721
					/**
2722
					 * triggered when a node is about to be opened (if the node is supposed to be in the DOM, it will be, but it won't be visible yet)
2723
					 * @event
2724
					 * @name before_open.jstree
2725
					 * @param {Object} node the opened node
2726
					 */
2727
					this.trigger('before_open', { "node" : obj });
2728
				}
2729
				/**
2730
				 * triggered when a node is opened (if there is an animation it will not be completed yet)
2731
				 * @event
2732
				 * @name open_node.jstree
2733
				 * @param {Object} node the opened node
2734
				 */
2735
				this.trigger('open_node', { "node" : obj });
2736
				if(!animation || !d.length) {
2737
					/**
2738
					 * triggered when a node is opened and the animation is complete
2739
					 * @event
2740
					 * @name after_open.jstree
2741
					 * @param {Object} node the opened node
2742
					 */
2743
					this.trigger("after_open", { "node" : obj });
2744
				}
2745
				return true;
2746
			}
2747
		},
2748
		/**
2749
		 * opens every parent of a node (node should be loaded)
2750
		 * @name _open_to(obj)
2751
		 * @param {mixed} obj the node to reveal
2752
		 * @private
2753
		 */
2754
		_open_to : function (obj) {
2755
			obj = this.get_node(obj);
2756
			if(!obj || obj.id === $.jstree.root) {
2757
				return false;
2758
			}
2759
			var i, j, p = obj.parents;
2760
			for(i = 0, j = p.length; i < j; i+=1) {
2761
				if(i !== $.jstree.root) {
2762
					this.open_node(p[i], false, 0);
2763
				}
2764
			}
2765
			return $('#' + obj.id.replace($.jstree.idregex,'\\$&'), this.element);
2766
		},
2767
		/**
2768
		 * closes a node, hiding its children
2769
		 * @name close_node(obj [, animation])
2770
		 * @param {mixed} obj the node to close
2771
		 * @param {Number} animation the animation duration in milliseconds when closing the node (overrides the `core.animation` setting). Use `false` for no animation.
2772
		 * @trigger close_node.jstree, after_close.jstree
2773
		 */
2774
		close_node : function (obj, animation) {
2775
			var t1, t2, t, d;
2776
			if($.vakata.is_array(obj)) {
2777
				obj = obj.slice();
2778
				for(t1 = 0, t2 = obj.length; t1 < t2; t1++) {
2779
					this.close_node(obj[t1], animation);
2780
				}
2781
				return true;
2782
			}
2783
			obj = this.get_node(obj);
2784
			if(!obj || obj.id === $.jstree.root) {
2785
				return false;
2786
			}
2787
			if(this.is_closed(obj)) {
2788
				return false;
2789
			}
2790
			animation = animation === undefined ? this.settings.core.animation : animation;
2791
			t = this;
2792
			d = this.get_node(obj, true);
2793
 
2794
			obj.state.opened = false;
2795
			/**
2796
			 * triggered when a node is closed (if there is an animation it will not be complete yet)
2797
			 * @event
2798
			 * @name close_node.jstree
2799
			 * @param {Object} node the closed node
2800
			 */
2801
			this.trigger('close_node',{ "node" : obj });
2802
			if(!d.length) {
2803
				/**
2804
				 * triggered when a node is closed and the animation is complete
2805
				 * @event
2806
				 * @name after_close.jstree
2807
				 * @param {Object} node the closed node
2808
				 */
2809
				this.trigger("after_close", { "node" : obj });
2810
			}
2811
			else {
2812
				if(!animation) {
2813
					d[0].className = d[0].className.replace('jstree-open', 'jstree-closed');
2814
					d.children('.jstree-anchor').attr("aria-expanded", false);
2815
					d.children('.jstree-children').remove();
2816
					this.trigger("after_close", { "node" : obj });
2817
				}
2818
				else {
2819
					d
2820
						.children(".jstree-children").attr("style","display:block !important").end()
2821
						.removeClass("jstree-open").addClass("jstree-closed")
2822
							.children('.jstree-anchor').attr("aria-expanded", false).end()
2823
						.children(".jstree-children").stop(true, true).slideUp(animation, function () {
2824
							this.style.display = "";
2825
							d.children('.jstree-children').remove();
2826
							if (t.element) {
2827
								t.trigger("after_close", { "node" : obj });
2828
							}
2829
						});
2830
				}
2831
			}
2832
		},
2833
		/**
2834
		 * toggles a node - closing it if it is open, opening it if it is closed
2835
		 * @name toggle_node(obj)
2836
		 * @param {mixed} obj the node to toggle
2837
		 */
2838
		toggle_node : function (obj) {
2839
			var t1, t2;
2840
			if($.vakata.is_array(obj)) {
2841
				obj = obj.slice();
2842
				for(t1 = 0, t2 = obj.length; t1 < t2; t1++) {
2843
					this.toggle_node(obj[t1]);
2844
				}
2845
				return true;
2846
			}
2847
			if(this.is_closed(obj)) {
2848
				return this.open_node(obj);
2849
			}
2850
			if(this.is_open(obj)) {
2851
				return this.close_node(obj);
2852
			}
2853
		},
2854
		/**
2855
		 * opens all nodes within a node (or the tree), revealing their children. If the node is not loaded it will be loaded and opened once ready.
2856
		 * @name open_all([obj, animation, original_obj])
2857
		 * @param {mixed} obj the node to open recursively, omit to open all nodes in the tree
2858
		 * @param {Number} animation the animation duration in milliseconds when opening the nodes, the default is no animation
2859
		 * @param {jQuery} reference to the node that started the process (internal use)
2860
		 * @trigger open_all.jstree
2861
		 */
2862
		open_all : function (obj, animation, original_obj) {
2863
			if(!obj) { obj = $.jstree.root; }
2864
			obj = this.get_node(obj);
2865
			if(!obj) { return false; }
2866
			var dom = obj.id === $.jstree.root ? this.get_container_ul() : this.get_node(obj, true), i, j, _this;
2867
			if(!dom.length) {
2868
				for(i = 0, j = obj.children_d.length; i < j; i++) {
2869
					if(this.is_closed(this._model.data[obj.children_d[i]])) {
2870
						this._model.data[obj.children_d[i]].state.opened = true;
2871
					}
2872
				}
2873
				return this.trigger('open_all', { "node" : obj });
2874
			}
2875
			original_obj = original_obj || dom;
2876
			_this = this;
2877
			dom = this.is_closed(obj) ? dom.find('.jstree-closed').addBack() : dom.find('.jstree-closed');
2878
			dom.each(function () {
2879
				_this.open_node(
2880
					this,
2881
					function(node, status) { if(status && this.is_parent(node)) { this.open_all(node, animation, original_obj); } },
2882
					animation || 0
2883
				);
2884
			});
2885
			if(original_obj.find('.jstree-closed').length === 0) {
2886
				/**
2887
				 * triggered when an `open_all` call completes
2888
				 * @event
2889
				 * @name open_all.jstree
2890
				 * @param {Object} node the opened node
2891
				 */
2892
				this.trigger('open_all', { "node" : this.get_node(original_obj) });
2893
			}
2894
		},
2895
		/**
2896
		 * closes all nodes within a node (or the tree), revealing their children
2897
		 * @name close_all([obj, animation])
2898
		 * @param {mixed} obj the node to close recursively, omit to close all nodes in the tree
2899
		 * @param {Number} animation the animation duration in milliseconds when closing the nodes, the default is no animation
2900
		 * @trigger close_all.jstree
2901
		 */
2902
		close_all : function (obj, animation) {
2903
			if(!obj) { obj = $.jstree.root; }
2904
			obj = this.get_node(obj);
2905
			if(!obj) { return false; }
2906
			var dom = obj.id === $.jstree.root ? this.get_container_ul() : this.get_node(obj, true),
2907
				_this = this, i, j;
2908
			if(dom.length) {
2909
				dom = this.is_open(obj) ? dom.find('.jstree-open').addBack() : dom.find('.jstree-open');
2910
				$(dom.get().reverse()).each(function () { _this.close_node(this, animation || 0); });
2911
			}
2912
			for(i = 0, j = obj.children_d.length; i < j; i++) {
2913
				this._model.data[obj.children_d[i]].state.opened = false;
2914
			}
2915
			/**
2916
			 * triggered when an `close_all` call completes
2917
			 * @event
2918
			 * @name close_all.jstree
2919
			 * @param {Object} node the closed node
2920
			 */
2921
			this.trigger('close_all', { "node" : obj });
2922
		},
2923
		/**
2924
		 * checks if a node is disabled (not selectable)
2925
		 * @name is_disabled(obj)
2926
		 * @param  {mixed} obj
2927
		 * @return {Boolean}
2928
		 */
2929
		is_disabled : function (obj) {
2930
			obj = this.get_node(obj);
2931
			return obj && obj.state && obj.state.disabled;
2932
		},
2933
		/**
2934
		 * enables a node - so that it can be selected
2935
		 * @name enable_node(obj)
2936
		 * @param {mixed} obj the node to enable
2937
		 * @trigger enable_node.jstree
2938
		 */
2939
		enable_node : function (obj) {
2940
			var t1, t2;
2941
			if($.vakata.is_array(obj)) {
2942
				obj = obj.slice();
2943
				for(t1 = 0, t2 = obj.length; t1 < t2; t1++) {
2944
					this.enable_node(obj[t1]);
2945
				}
2946
				return true;
2947
			}
2948
			obj = this.get_node(obj);
2949
			if(!obj || obj.id === $.jstree.root) {
2950
				return false;
2951
			}
2952
			obj.state.disabled = false;
2953
			this.get_node(obj,true).children('.jstree-anchor').removeClass('jstree-disabled').attr('aria-disabled', false);
2954
			/**
2955
			 * triggered when an node is enabled
2956
			 * @event
2957
			 * @name enable_node.jstree
2958
			 * @param {Object} node the enabled node
2959
			 */
2960
			this.trigger('enable_node', { 'node' : obj });
2961
		},
2962
		/**
2963
		 * disables a node - so that it can not be selected
2964
		 * @name disable_node(obj)
2965
		 * @param {mixed} obj the node to disable
2966
		 * @trigger disable_node.jstree
2967
		 */
2968
		disable_node : function (obj) {
2969
			var t1, t2;
2970
			if($.vakata.is_array(obj)) {
2971
				obj = obj.slice();
2972
				for(t1 = 0, t2 = obj.length; t1 < t2; t1++) {
2973
					this.disable_node(obj[t1]);
2974
				}
2975
				return true;
2976
			}
2977
			obj = this.get_node(obj);
2978
			if(!obj || obj.id === $.jstree.root) {
2979
				return false;
2980
			}
2981
			obj.state.disabled = true;
2982
			this.get_node(obj,true).children('.jstree-anchor').addClass('jstree-disabled').attr('aria-disabled', true);
2983
			/**
2984
			 * triggered when an node is disabled
2985
			 * @event
2986
			 * @name disable_node.jstree
2987
			 * @param {Object} node the disabled node
2988
			 */
2989
			this.trigger('disable_node', { 'node' : obj });
2990
		},
2991
		/**
2992
		 * determines if a node is hidden
2993
		 * @name is_hidden(obj)
2994
		 * @param {mixed} obj the node
2995
		 */
2996
		is_hidden : function (obj) {
2997
			obj = this.get_node(obj);
2998
			return obj.state.hidden === true;
2999
		},
3000
		/**
3001
		 * hides a node - it is still in the structure but will not be visible
3002
		 * @name hide_node(obj)
3003
		 * @param {mixed} obj the node to hide
3004
		 * @param {Boolean} skip_redraw internal parameter controlling if redraw is called
3005
		 * @trigger hide_node.jstree
3006
		 */
3007
		hide_node : function (obj, skip_redraw) {
3008
			var t1, t2;
3009
			if($.vakata.is_array(obj)) {
3010
				obj = obj.slice();
3011
				for(t1 = 0, t2 = obj.length; t1 < t2; t1++) {
3012
					this.hide_node(obj[t1], true);
3013
				}
3014
				if (!skip_redraw) {
3015
					this.redraw();
3016
				}
3017
				return true;
3018
			}
3019
			obj = this.get_node(obj);
3020
			if(!obj || obj.id === $.jstree.root) {
3021
				return false;
3022
			}
3023
			if(!obj.state.hidden) {
3024
				obj.state.hidden = true;
3025
				this._node_changed(obj.parent);
3026
				if(!skip_redraw) {
3027
					this.redraw();
3028
				}
3029
				/**
3030
				 * triggered when an node is hidden
3031
				 * @event
3032
				 * @name hide_node.jstree
3033
				 * @param {Object} node the hidden node
3034
				 */
3035
				this.trigger('hide_node', { 'node' : obj });
3036
			}
3037
		},
3038
		/**
3039
		 * shows a node
3040
		 * @name show_node(obj)
3041
		 * @param {mixed} obj the node to show
3042
		 * @param {Boolean} skip_redraw internal parameter controlling if redraw is called
3043
		 * @trigger show_node.jstree
3044
		 */
3045
		show_node : function (obj, skip_redraw) {
3046
			var t1, t2;
3047
			if($.vakata.is_array(obj)) {
3048
				obj = obj.slice();
3049
				for(t1 = 0, t2 = obj.length; t1 < t2; t1++) {
3050
					this.show_node(obj[t1], true);
3051
				}
3052
				if (!skip_redraw) {
3053
					this.redraw();
3054
				}
3055
				return true;
3056
			}
3057
			obj = this.get_node(obj);
3058
			if(!obj || obj.id === $.jstree.root) {
3059
				return false;
3060
			}
3061
			if(obj.state.hidden) {
3062
				obj.state.hidden = false;
3063
				this._node_changed(obj.parent);
3064
				if(!skip_redraw) {
3065
					this.redraw();
3066
				}
3067
				/**
3068
				 * triggered when an node is shown
3069
				 * @event
3070
				 * @name show_node.jstree
3071
				 * @param {Object} node the shown node
3072
				 */
3073
				this.trigger('show_node', { 'node' : obj });
3074
			}
3075
		},
3076
		/**
3077
		 * hides all nodes
3078
		 * @name hide_all()
3079
		 * @trigger hide_all.jstree
3080
		 */
3081
		hide_all : function (skip_redraw) {
3082
			var i, m = this._model.data, ids = [];
3083
			for(i in m) {
3084
				if(m.hasOwnProperty(i) && i !== $.jstree.root && !m[i].state.hidden) {
3085
					m[i].state.hidden = true;
3086
					ids.push(i);
3087
				}
3088
			}
3089
			this._model.force_full_redraw = true;
3090
			if(!skip_redraw) {
3091
				this.redraw();
3092
			}
3093
			/**
3094
			 * triggered when all nodes are hidden
3095
			 * @event
3096
			 * @name hide_all.jstree
3097
			 * @param {Array} nodes the IDs of all hidden nodes
3098
			 */
3099
			this.trigger('hide_all', { 'nodes' : ids });
3100
			return ids;
3101
		},
3102
		/**
3103
		 * shows all nodes
3104
		 * @name show_all()
3105
		 * @trigger show_all.jstree
3106
		 */
3107
		show_all : function (skip_redraw) {
3108
			var i, m = this._model.data, ids = [];
3109
			for(i in m) {
3110
				if(m.hasOwnProperty(i) && i !== $.jstree.root && m[i].state.hidden) {
3111
					m[i].state.hidden = false;
3112
					ids.push(i);
3113
				}
3114
			}
3115
			this._model.force_full_redraw = true;
3116
			if(!skip_redraw) {
3117
				this.redraw();
3118
			}
3119
			/**
3120
			 * triggered when all nodes are shown
3121
			 * @event
3122
			 * @name show_all.jstree
3123
			 * @param {Array} nodes the IDs of all shown nodes
3124
			 */
3125
			this.trigger('show_all', { 'nodes' : ids });
3126
			return ids;
3127
		},
3128
		/**
3129
		 * called when a node is selected by the user. Used internally.
3130
		 * @private
3131
		 * @name activate_node(obj, e)
3132
		 * @param {mixed} obj the node
3133
		 * @param {Object} e the related event
3134
		 * @trigger activate_node.jstree, changed.jstree
3135
		 */
3136
		activate_node : function (obj, e) {
3137
			if(this.is_disabled(obj)) {
3138
				return false;
3139
			}
3140
			if(!e || typeof e !== 'object') {
3141
				e = {};
3142
			}
3143
 
3144
			// ensure last_clicked is still in the DOM, make it fresh (maybe it was moved?) and make sure it is still selected, if not - make last_clicked the last selected node
3145
			this._data.core.last_clicked = this._data.core.last_clicked && this._data.core.last_clicked.id !== undefined ? this.get_node(this._data.core.last_clicked.id) : null;
3146
			if(this._data.core.last_clicked && !this._data.core.last_clicked.state.selected) { this._data.core.last_clicked = null; }
3147
			if(!this._data.core.last_clicked && this._data.core.selected.length) { this._data.core.last_clicked = this.get_node(this._data.core.selected[this._data.core.selected.length - 1]); }
3148
 
3149
			if(!this.settings.core.multiple || (!e.metaKey && !e.ctrlKey && !e.shiftKey) || (e.shiftKey && (!this._data.core.last_clicked || !this.get_parent(obj) || this.get_parent(obj) !== this._data.core.last_clicked.parent ) )) {
3150
				if(!this.settings.core.multiple && (e.metaKey || e.ctrlKey || e.shiftKey) && this.is_selected(obj)) {
3151
					this.deselect_node(obj, false, e);
3152
				}
3153
				else {
3154
					this.deselect_all(true);
3155
					this.select_node(obj, false, false, e);
3156
					this._data.core.last_clicked = this.get_node(obj);
3157
				}
3158
			}
3159
			else {
3160
				if(e.shiftKey) {
3161
					var o = this.get_node(obj).id,
3162
						l = this._data.core.last_clicked.id,
3163
						p = this.get_node(this._data.core.last_clicked.parent).children,
3164
						c = false,
3165
						i, j;
3166
					for(i = 0, j = p.length; i < j; i += 1) {
3167
						// separate IFs work whem o and l are the same
3168
						if(p[i] === o) {
3169
							c = !c;
3170
						}
3171
						if(p[i] === l) {
3172
							c = !c;
3173
						}
3174
						if(!this.is_disabled(p[i]) && (c || p[i] === o || p[i] === l)) {
3175
							if (!this.is_hidden(p[i])) {
3176
								this.select_node(p[i], true, false, e);
3177
							}
3178
						}
3179
						else {
3180
							this.deselect_node(p[i], true, e);
3181
						}
3182
					}
3183
					this.trigger('changed', { 'action' : 'select_node', 'node' : this.get_node(obj), 'selected' : this._data.core.selected, 'event' : e });
3184
				}
3185
				else {
3186
					if(!this.is_selected(obj)) {
3187
						this.select_node(obj, false, false, e);
3188
					}
3189
					else {
3190
						this.deselect_node(obj, false, e);
3191
					}
3192
				}
3193
			}
3194
			/**
3195
			 * triggered when an node is clicked or intercated with by the user
3196
			 * @event
3197
			 * @name activate_node.jstree
3198
			 * @param {Object} node
3199
			 * @param {Object} event the ooriginal event (if any) which triggered the call (may be an empty object)
3200
			 */
3201
			this.trigger('activate_node', { 'node' : this.get_node(obj), 'event' : e });
3202
		},
3203
		/**
3204
		 * applies the hover state on a node, called when a node is hovered by the user. Used internally.
3205
		 * @private
3206
		 * @name hover_node(obj)
3207
		 * @param {mixed} obj
3208
		 * @trigger hover_node.jstree
3209
		 */
3210
		hover_node : function (obj) {
3211
			obj = this.get_node(obj, true);
3212
			if(!obj || !obj.length || obj.children('.jstree-hovered').length) {
3213
				return false;
3214
			}
3215
			var o = this.element.find('.jstree-hovered'), t = this.element;
3216
			if(o && o.length) { this.dehover_node(o); }
3217
 
3218
			obj.children('.jstree-anchor').addClass('jstree-hovered');
3219
			/**
3220
			 * triggered when an node is hovered
3221
			 * @event
3222
			 * @name hover_node.jstree
3223
			 * @param {Object} node
3224
			 */
3225
			this.trigger('hover_node', { 'node' : this.get_node(obj) });
3226
			setTimeout(function () { t.attr('aria-activedescendant', obj[0].id); }, 0);
3227
		},
3228
		/**
3229
		 * removes the hover state from a nodecalled when a node is no longer hovered by the user. Used internally.
3230
		 * @private
3231
		 * @name dehover_node(obj)
3232
		 * @param {mixed} obj
3233
		 * @trigger dehover_node.jstree
3234
		 */
3235
		dehover_node : function (obj) {
3236
			obj = this.get_node(obj, true);
3237
			if(!obj || !obj.length || !obj.children('.jstree-hovered').length) {
3238
				return false;
3239
			}
3240
			obj.children('.jstree-anchor').removeClass('jstree-hovered');
3241
			/**
3242
			 * triggered when an node is no longer hovered
3243
			 * @event
3244
			 * @name dehover_node.jstree
3245
			 * @param {Object} node
3246
			 */
3247
			this.trigger('dehover_node', { 'node' : this.get_node(obj) });
3248
		},
3249
		/**
3250
		 * select a node
3251
		 * @name select_node(obj [, supress_event, prevent_open])
3252
		 * @param {mixed} obj an array can be used to select multiple nodes
3253
		 * @param {Boolean} supress_event if set to `true` the `changed.jstree` event won't be triggered
3254
		 * @param {Boolean} prevent_open if set to `true` parents of the selected node won't be opened
3255
		 * @trigger select_node.jstree, changed.jstree
3256
		 */
3257
		select_node : function (obj, supress_event, prevent_open, e) {
3258
			var dom, t1, t2, th;
3259
			if($.vakata.is_array(obj)) {
3260
				obj = obj.slice();
3261
				for(t1 = 0, t2 = obj.length; t1 < t2; t1++) {
3262
					this.select_node(obj[t1], supress_event, prevent_open, e);
3263
				}
3264
				return true;
3265
			}
3266
			obj = this.get_node(obj);
3267
			if(!obj || obj.id === $.jstree.root) {
3268
				return false;
3269
			}
3270
			dom = this.get_node(obj, true);
3271
			if(!obj.state.selected) {
3272
				obj.state.selected = true;
3273
				this._data.core.selected.push(obj.id);
3274
				if(!prevent_open) {
3275
					dom = this._open_to(obj);
3276
				}
3277
				if(dom && dom.length) {
3278
					dom.children('.jstree-anchor').addClass('jstree-clicked').attr('aria-selected', true);
3279
				}
3280
				/**
3281
				 * triggered when an node is selected
3282
				 * @event
3283
				 * @name select_node.jstree
3284
				 * @param {Object} node
3285
				 * @param {Array} selected the current selection
3286
				 * @param {Object} event the event (if any) that triggered this select_node
3287
				 */
3288
				this.trigger('select_node', { 'node' : obj, 'selected' : this._data.core.selected, 'event' : e });
3289
				if(!supress_event) {
3290
					/**
3291
					 * triggered when selection changes
3292
					 * @event
3293
					 * @name changed.jstree
3294
					 * @param {Object} node
3295
					 * @param {Object} action the action that caused the selection to change
3296
					 * @param {Array} selected the current selection
3297
					 * @param {Object} event the event (if any) that triggered this changed event
3298
					 */
3299
					this.trigger('changed', { 'action' : 'select_node', 'node' : obj, 'selected' : this._data.core.selected, 'event' : e });
3300
				}
3301
			}
3302
		},
3303
		/**
3304
		 * deselect a node
3305
		 * @name deselect_node(obj [, supress_event])
3306
		 * @param {mixed} obj an array can be used to deselect multiple nodes
3307
		 * @param {Boolean} supress_event if set to `true` the `changed.jstree` event won't be triggered
3308
		 * @trigger deselect_node.jstree, changed.jstree
3309
		 */
3310
		deselect_node : function (obj, supress_event, e) {
3311
			var t1, t2, dom;
3312
			if($.vakata.is_array(obj)) {
3313
				obj = obj.slice();
3314
				for(t1 = 0, t2 = obj.length; t1 < t2; t1++) {
3315
					this.deselect_node(obj[t1], supress_event, e);
3316
				}
3317
				return true;
3318
			}
3319
			obj = this.get_node(obj);
3320
			if(!obj || obj.id === $.jstree.root) {
3321
				return false;
3322
			}
3323
			dom = this.get_node(obj, true);
3324
			if(obj.state.selected) {
3325
				obj.state.selected = false;
3326
				this._data.core.selected = $.vakata.array_remove_item(this._data.core.selected, obj.id);
3327
				if(dom.length) {
3328
					dom.children('.jstree-anchor').removeClass('jstree-clicked').attr('aria-selected', false);
3329
				}
3330
				/**
3331
				 * triggered when an node is deselected
3332
				 * @event
3333
				 * @name deselect_node.jstree
3334
				 * @param {Object} node
3335
				 * @param {Array} selected the current selection
3336
				 * @param {Object} event the event (if any) that triggered this deselect_node
3337
				 */
3338
				this.trigger('deselect_node', { 'node' : obj, 'selected' : this._data.core.selected, 'event' : e });
3339
				if(!supress_event) {
3340
					this.trigger('changed', { 'action' : 'deselect_node', 'node' : obj, 'selected' : this._data.core.selected, 'event' : e });
3341
				}
3342
			}
3343
		},
3344
		/**
3345
		 * select all nodes in the tree
3346
		 * @name select_all([supress_event])
3347
		 * @param {Boolean} supress_event if set to `true` the `changed.jstree` event won't be triggered
3348
		 * @trigger select_all.jstree, changed.jstree
3349
		 */
3350
		select_all : function (supress_event) {
3351
			var tmp = this._data.core.selected.concat([]), i, j;
3352
			this._data.core.selected = this._model.data[$.jstree.root].children_d.concat();
3353
			for(i = 0, j = this._data.core.selected.length; i < j; i++) {
3354
				if(this._model.data[this._data.core.selected[i]]) {
3355
					this._model.data[this._data.core.selected[i]].state.selected = true;
3356
				}
3357
			}
3358
			this.redraw(true);
3359
			/**
3360
			 * triggered when all nodes are selected
3361
			 * @event
3362
			 * @name select_all.jstree
3363
			 * @param {Array} selected the current selection
3364
			 */
3365
			this.trigger('select_all', { 'selected' : this._data.core.selected });
3366
			if(!supress_event) {
3367
				this.trigger('changed', { 'action' : 'select_all', 'selected' : this._data.core.selected, 'old_selection' : tmp });
3368
			}
3369
		},
3370
		/**
3371
		 * deselect all selected nodes
3372
		 * @name deselect_all([supress_event])
3373
		 * @param {Boolean} supress_event if set to `true` the `changed.jstree` event won't be triggered
3374
		 * @trigger deselect_all.jstree, changed.jstree
3375
		 */
3376
		deselect_all : function (supress_event) {
3377
			var tmp = this._data.core.selected.concat([]), i, j;
3378
			for(i = 0, j = this._data.core.selected.length; i < j; i++) {
3379
				if(this._model.data[this._data.core.selected[i]]) {
3380
					this._model.data[this._data.core.selected[i]].state.selected = false;
3381
				}
3382
			}
3383
			this._data.core.selected = [];
3384
			this.element.find('.jstree-clicked').removeClass('jstree-clicked').attr('aria-selected', false);
3385
			/**
3386
			 * triggered when all nodes are deselected
3387
			 * @event
3388
			 * @name deselect_all.jstree
3389
			 * @param {Object} node the previous selection
3390
			 * @param {Array} selected the current selection
3391
			 */
3392
			this.trigger('deselect_all', { 'selected' : this._data.core.selected, 'node' : tmp });
3393
			if(!supress_event) {
3394
				this.trigger('changed', { 'action' : 'deselect_all', 'selected' : this._data.core.selected, 'old_selection' : tmp });
3395
			}
3396
		},
3397
		/**
3398
		 * checks if a node is selected
3399
		 * @name is_selected(obj)
3400
		 * @param  {mixed}  obj
3401
		 * @return {Boolean}
3402
		 */
3403
		is_selected : function (obj) {
3404
			obj = this.get_node(obj);
3405
			if(!obj || obj.id === $.jstree.root) {
3406
				return false;
3407
			}
3408
			return obj.state.selected;
3409
		},
3410
		/**
3411
		 * get an array of all selected nodes
3412
		 * @name get_selected([full])
3413
		 * @param  {mixed}  full if set to `true` the returned array will consist of the full node objects, otherwise - only IDs will be returned
3414
		 * @return {Array}
3415
		 */
3416
		get_selected : function (full) {
3417
			return full ? $.map(this._data.core.selected, function (i) { return this.get_node(i); }.bind(this)) : this._data.core.selected.slice();
3418
		},
3419
		/**
3420
		 * get an array of all top level selected nodes (ignoring children of selected nodes)
3421
		 * @name get_top_selected([full])
3422
		 * @param  {mixed}  full if set to `true` the returned array will consist of the full node objects, otherwise - only IDs will be returned
3423
		 * @return {Array}
3424
		 */
3425
		get_top_selected : function (full) {
3426
			var tmp = this.get_selected(true),
3427
				obj = {}, i, j, k, l;
3428
			for(i = 0, j = tmp.length; i < j; i++) {
3429
				obj[tmp[i].id] = tmp[i];
3430
			}
3431
			for(i = 0, j = tmp.length; i < j; i++) {
3432
				for(k = 0, l = tmp[i].children_d.length; k < l; k++) {
3433
					if(obj[tmp[i].children_d[k]]) {
3434
						delete obj[tmp[i].children_d[k]];
3435
					}
3436
				}
3437
			}
3438
			tmp = [];
3439
			for(i in obj) {
3440
				if(obj.hasOwnProperty(i)) {
3441
					tmp.push(i);
3442
				}
3443
			}
3444
			return full ? $.map(tmp, function (i) { return this.get_node(i); }.bind(this)) : tmp;
3445
		},
3446
		/**
3447
		 * get an array of all bottom level selected nodes (ignoring selected parents)
3448
		 * @name get_bottom_selected([full])
3449
		 * @param  {mixed}  full if set to `true` the returned array will consist of the full node objects, otherwise - only IDs will be returned
3450
		 * @return {Array}
3451
		 */
3452
		get_bottom_selected : function (full) {
3453
			var tmp = this.get_selected(true),
3454
				obj = [], i, j;
3455
			for(i = 0, j = tmp.length; i < j; i++) {
3456
				if(!tmp[i].children.length) {
3457
					obj.push(tmp[i].id);
3458
				}
3459
			}
3460
			return full ? $.map(obj, function (i) { return this.get_node(i); }.bind(this)) : obj;
3461
		},
3462
		/**
3463
		 * gets the current state of the tree so that it can be restored later with `set_state(state)`. Used internally.
3464
		 * @name get_state()
3465
		 * @private
3466
		 * @return {Object}
3467
		 */
3468
		get_state : function () {
3469
			var state	= {
3470
				'core' : {
3471
					'open' : [],
3472
					'loaded' : [],
3473
					'scroll' : {
3474
						'left' : this.element.scrollLeft(),
3475
						'top' : this.element.scrollTop()
3476
					},
3477
					/*!
3478
					'themes' : {
3479
						'name' : this.get_theme(),
3480
						'icons' : this._data.core.themes.icons,
3481
						'dots' : this._data.core.themes.dots
3482
					},
3483
					*/
3484
					'selected' : []
3485
				}
3486
			}, i;
3487
			for(i in this._model.data) {
3488
				if(this._model.data.hasOwnProperty(i)) {
3489
					if(i !== $.jstree.root) {
3490
						if(this._model.data[i].state.loaded && this.settings.core.loaded_state) {
3491
							state.core.loaded.push(i);
3492
						}
3493
						if(this._model.data[i].state.opened) {
3494
							state.core.open.push(i);
3495
						}
3496
						if(this._model.data[i].state.selected) {
3497
							state.core.selected.push(i);
3498
						}
3499
					}
3500
				}
3501
			}
3502
			return state;
3503
		},
3504
		/**
3505
		 * sets the state of the tree. Used internally.
3506
		 * @name set_state(state [, callback])
3507
		 * @private
3508
		 * @param {Object} state the state to restore. Keep in mind this object is passed by reference and jstree will modify it.
3509
		 * @param {Function} callback an optional function to execute once the state is restored.
3510
		 * @trigger set_state.jstree
3511
		 */
3512
		set_state : function (state, callback) {
3513
			if(state) {
3514
				if(state.core && state.core.selected && state.core.initial_selection === undefined) {
3515
					state.core.initial_selection = this._data.core.selected.concat([]).sort().join(',');
3516
				}
3517
				if(state.core) {
3518
					var res, n, t, _this, i;
3519
					if(state.core.loaded) {
3520
						if(!this.settings.core.loaded_state || !$.vakata.is_array(state.core.loaded) || !state.core.loaded.length) {
3521
							delete state.core.loaded;
3522
							this.set_state(state, callback);
3523
						}
3524
						else {
3525
							this._load_nodes(state.core.loaded, function (nodes) {
3526
								delete state.core.loaded;
3527
								this.set_state(state, callback);
3528
							});
3529
						}
3530
						return false;
3531
					}
3532
					if(state.core.open) {
3533
						if(!$.vakata.is_array(state.core.open) || !state.core.open.length) {
3534
							delete state.core.open;
3535
							this.set_state(state, callback);
3536
						}
3537
						else {
3538
							this._load_nodes(state.core.open, function (nodes) {
3539
								this.open_node(nodes, false, 0);
3540
								delete state.core.open;
3541
								this.set_state(state, callback);
3542
							});
3543
						}
3544
						return false;
3545
					}
3546
					if(state.core.scroll) {
3547
						if(state.core.scroll && state.core.scroll.left !== undefined) {
3548
							this.element.scrollLeft(state.core.scroll.left);
3549
						}
3550
						if(state.core.scroll && state.core.scroll.top !== undefined) {
3551
							this.element.scrollTop(state.core.scroll.top);
3552
						}
3553
						delete state.core.scroll;
3554
						this.set_state(state, callback);
3555
						return false;
3556
					}
3557
					if(state.core.selected) {
3558
						_this = this;
3559
						if (state.core.initial_selection === undefined ||
3560
							state.core.initial_selection === this._data.core.selected.concat([]).sort().join(',')
3561
						) {
3562
							this.deselect_all();
3563
							$.each(state.core.selected, function (i, v) {
3564
								_this.select_node(v, false, true);
3565
							});
3566
						}
3567
						delete state.core.initial_selection;
3568
						delete state.core.selected;
3569
						this.set_state(state, callback);
3570
						return false;
3571
					}
3572
					for(i in state) {
3573
						if(state.hasOwnProperty(i) && i !== "core" && $.inArray(i, this.settings.plugins) === -1) {
3574
							delete state[i];
3575
						}
3576
					}
3577
					if($.isEmptyObject(state.core)) {
3578
						delete state.core;
3579
						this.set_state(state, callback);
3580
						return false;
3581
					}
3582
				}
3583
				if($.isEmptyObject(state)) {
3584
					state = null;
3585
					if(callback) { callback.call(this); }
3586
					/**
3587
					 * triggered when a `set_state` call completes
3588
					 * @event
3589
					 * @name set_state.jstree
3590
					 */
3591
					this.trigger('set_state');
3592
					return false;
3593
				}
3594
				return true;
3595
			}
3596
			return false;
3597
		},
3598
		/**
3599
		 * refreshes the tree - all nodes are reloaded with calls to `load_node`.
3600
		 * @name refresh()
3601
		 * @param {Boolean} skip_loading an option to skip showing the loading indicator
3602
		 * @param {Mixed} forget_state if set to `true` state will not be reapplied, if set to a function (receiving the current state as argument) the result of that function will be used as state
3603
		 * @trigger refresh.jstree
3604
		 */
3605
		refresh : function (skip_loading, forget_state) {
3606
			this._data.core.state = forget_state === true ? {} : this.get_state();
3607
			if(forget_state && $.vakata.is_function(forget_state)) { this._data.core.state = forget_state.call(this, this._data.core.state); }
3608
			this._cnt = 0;
3609
			this._model.data = {};
3610
			this._model.data[$.jstree.root] = {
3611
				id : $.jstree.root,
3612
				parent : null,
3613
				parents : [],
3614
				children : [],
3615
				children_d : [],
3616
				state : { loaded : false }
3617
			};
3618
			this._data.core.selected = [];
3619
			this._data.core.last_clicked = null;
3620
			this._data.core.focused = null;
3621
 
3622
			var c = this.get_container_ul()[0].className;
3623
			if(!skip_loading) {
3624
				this.element.html("<"+"ul class='"+c+"' role='group'><"+"li class='jstree-initial-node jstree-loading jstree-leaf jstree-last' role='none' id='j"+this._id+"_loading'><i class='jstree-icon jstree-ocl'></i><"+"a class='jstree-anchor' role='treeitem' href='#'><i class='jstree-icon jstree-themeicon-hidden'></i>" + this.get_string("Loading ...") + "</a></li></ul>");
3625
				this.element.attr('aria-activedescendant','j'+this._id+'_loading');
3626
			}
3627
			this.load_node($.jstree.root, function (o, s) {
3628
				if(s) {
3629
					this.get_container_ul()[0].className = c;
3630
					if(this._firstChild(this.get_container_ul()[0])) {
3631
						this.element.attr('aria-activedescendant',this._firstChild(this.get_container_ul()[0]).id);
3632
					}
3633
					this.set_state($.extend(true, {}, this._data.core.state), function () {
3634
						/**
3635
						 * triggered when a `refresh` call completes
3636
						 * @event
3637
						 * @name refresh.jstree
3638
						 */
3639
						this.trigger('refresh');
3640
					});
3641
				}
3642
				this._data.core.state = null;
3643
			});
3644
		},
3645
		/**
3646
		 * refreshes a node in the tree (reload its children) all opened nodes inside that node are reloaded with calls to `load_node`.
3647
		 * @name refresh_node(obj)
3648
		 * @param  {mixed} obj the node
3649
		 * @trigger refresh_node.jstree
3650
		 */
3651
		refresh_node : function (obj) {
3652
			obj = this.get_node(obj);
3653
			if(!obj || obj.id === $.jstree.root) { return false; }
3654
			var opened = [], to_load = [], s = this._data.core.selected.concat([]);
3655
			to_load.push(obj.id);
3656
			if(obj.state.opened === true) { opened.push(obj.id); }
3657
			this.get_node(obj, true).find('.jstree-open').each(function() { to_load.push(this.id); opened.push(this.id); });
3658
			this._load_nodes(to_load, function (nodes) {
3659
				this.open_node(opened, false, 0);
3660
				this.select_node(s);
3661
				/**
3662
				 * triggered when a node is refreshed
3663
				 * @event
3664
				 * @name refresh_node.jstree
3665
				 * @param {Object} node - the refreshed node
3666
				 * @param {Array} nodes - an array of the IDs of the nodes that were reloaded
3667
				 */
3668
				this.trigger('refresh_node', { 'node' : obj, 'nodes' : nodes });
3669
			}.bind(this), false, true);
3670
		},
3671
		/**
3672
		 * set (change) the ID of a node
3673
		 * @name set_id(obj, id)
3674
		 * @param  {mixed} obj the node
3675
		 * @param  {String} id the new ID
3676
		 * @return {Boolean}
3677
		 * @trigger set_id.jstree
3678
		 */
3679
		set_id : function (obj, id) {
3680
			obj = this.get_node(obj);
3681
			if(!obj || obj.id === $.jstree.root) { return false; }
3682
			var i, j, m = this._model.data, old = obj.id;
3683
			id = id.toString();
3684
			// update parents (replace current ID with new one in children and children_d)
3685
			m[obj.parent].children[$.inArray(obj.id, m[obj.parent].children)] = id;
3686
			for(i = 0, j = obj.parents.length; i < j; i++) {
3687
				m[obj.parents[i]].children_d[$.inArray(obj.id, m[obj.parents[i]].children_d)] = id;
3688
			}
3689
			// update children (replace current ID with new one in parent and parents)
3690
			for(i = 0, j = obj.children.length; i < j; i++) {
3691
				m[obj.children[i]].parent = id;
3692
			}
3693
			for(i = 0, j = obj.children_d.length; i < j; i++) {
3694
				m[obj.children_d[i]].parents[$.inArray(obj.id, m[obj.children_d[i]].parents)] = id;
3695
			}
3696
			i = $.inArray(obj.id, this._data.core.selected);
3697
			if(i !== -1) { this._data.core.selected[i] = id; }
3698
			// update model and obj itself (obj.id, this._model.data[KEY])
3699
			i = this.get_node(obj.id, true);
3700
			if(i) {
3701
				i.attr('id', id); //.children('.jstree-anchor').attr('id', id + '_anchor').end().attr('aria-labelledby', id + '_anchor');
3702
				if(this.element.attr('aria-activedescendant') === obj.id) {
3703
					this.element.attr('aria-activedescendant', id);
3704
				}
3705
			}
3706
			delete m[obj.id];
3707
			obj.id = id;
3708
			obj.li_attr.id = id;
3709
			m[id] = obj;
3710
			/**
3711
			 * triggered when a node id value is changed
3712
			 * @event
3713
			 * @name set_id.jstree
3714
			 * @param {Object} node
3715
			 * @param {String} old the old id
3716
			 */
3717
			this.trigger('set_id',{ "node" : obj, "new" : obj.id, "old" : old });
3718
			return true;
3719
		},
3720
		/**
3721
		 * get the text value of a node
3722
		 * @name get_text(obj)
3723
		 * @param  {mixed} obj the node
3724
		 * @return {String}
3725
		 */
3726
		get_text : function (obj) {
3727
			obj = this.get_node(obj);
3728
			return (!obj || obj.id === $.jstree.root) ? false : obj.text;
3729
		},
3730
		/**
3731
		 * set the text value of a node. Used internally, please use `rename_node(obj, val)`.
3732
		 * @private
3733
		 * @name set_text(obj, val)
3734
		 * @param  {mixed} obj the node, you can pass an array to set the text on multiple nodes
3735
		 * @param  {String} val the new text value
3736
		 * @return {Boolean}
3737
		 * @trigger set_text.jstree
3738
		 */
3739
		set_text : function (obj, val) {
3740
			var t1, t2;
3741
			if($.vakata.is_array(obj)) {
3742
				obj = obj.slice();
3743
				for(t1 = 0, t2 = obj.length; t1 < t2; t1++) {
3744
					this.set_text(obj[t1], val);
3745
				}
3746
				return true;
3747
			}
3748
			obj = this.get_node(obj);
3749
			if(!obj || obj.id === $.jstree.root) { return false; }
3750
			obj.text = val;
3751
			if(this.get_node(obj, true).length) {
3752
				this.redraw_node(obj.id);
3753
			}
3754
			/**
3755
			 * triggered when a node text value is changed
3756
			 * @event
3757
			 * @name set_text.jstree
3758
			 * @param {Object} obj
3759
			 * @param {String} text the new value
3760
			 */
3761
			this.trigger('set_text',{ "obj" : obj, "text" : val });
3762
			return true;
3763
		},
3764
		/**
3765
		 * gets a JSON representation of a node (or the whole tree)
3766
		 * @name get_json([obj, options])
3767
		 * @param  {mixed} obj
3768
		 * @param  {Object} options
3769
		 * @param  {Boolean} options.no_state do not return state information
3770
		 * @param  {Boolean} options.no_id do not return ID
3771
		 * @param  {Boolean} options.no_children do not include children
3772
		 * @param  {Boolean} options.no_data do not include node data
3773
		 * @param  {Boolean} options.no_li_attr do not include LI attributes
3774
		 * @param  {Boolean} options.no_a_attr do not include A attributes
3775
		 * @param  {Boolean} options.flat return flat JSON instead of nested
3776
		 * @return {Object}
3777
		 */
3778
		get_json : function (obj, options, flat) {
3779
			obj = this.get_node(obj || $.jstree.root);
3780
			if(!obj) { return false; }
3781
			if(options && options.flat && !flat) { flat = []; }
3782
			var tmp = {
3783
				'id' : obj.id,
3784
				'text' : obj.text,
3785
				'icon' : this.get_icon(obj),
3786
				'li_attr' : $.extend(true, {}, obj.li_attr),
3787
				'a_attr' : $.extend(true, {}, obj.a_attr),
3788
				'state' : {},
3789
				'data' : options && options.no_data ? false : $.extend(true, $.vakata.is_array(obj.data)?[]:{}, obj.data)
3790
				//( this.get_node(obj, true).length ? this.get_node(obj, true).data() : obj.data ),
3791
			}, i, j;
3792
			if(options && options.flat) {
3793
				tmp.parent = obj.parent;
3794
			}
3795
			else {
3796
				tmp.children = [];
3797
			}
3798
			if(!options || !options.no_state) {
3799
				for(i in obj.state) {
3800
					if(obj.state.hasOwnProperty(i)) {
3801
						tmp.state[i] = obj.state[i];
3802
					}
3803
				}
3804
			} else {
3805
				delete tmp.state;
3806
			}
3807
			if(options && options.no_li_attr) {
3808
				delete tmp.li_attr;
3809
			}
3810
			if(options && options.no_a_attr) {
3811
				delete tmp.a_attr;
3812
			}
3813
			if(options && options.no_id) {
3814
				delete tmp.id;
3815
				if(tmp.li_attr && tmp.li_attr.id) {
3816
					delete tmp.li_attr.id;
3817
				}
3818
				if(tmp.a_attr && tmp.a_attr.id) {
3819
					delete tmp.a_attr.id;
3820
				}
3821
			}
3822
			if(options && options.flat && obj.id !== $.jstree.root) {
3823
				flat.push(tmp);
3824
			}
3825
			if(!options || !options.no_children) {
3826
				for(i = 0, j = obj.children.length; i < j; i++) {
3827
					if(options && options.flat) {
3828
						this.get_json(obj.children[i], options, flat);
3829
					}
3830
					else {
3831
						tmp.children.push(this.get_json(obj.children[i], options));
3832
					}
3833
				}
3834
			}
3835
			return options && options.flat ? flat : (obj.id === $.jstree.root ? tmp.children : tmp);
3836
		},
3837
		/**
3838
		 * create a new node (do not confuse with load_node)
3839
		 * @name create_node([par, node, pos, callback, is_loaded])
3840
		 * @param  {mixed}   par       the parent node (to create a root node use either "#" (string) or `null`)
3841
		 * @param  {mixed}   node      the data for the new node (a valid JSON object, or a simple string with the name)
3842
		 * @param  {mixed}   pos       the index at which to insert the node, "first" and "last" are also supported, default is "last"
3843
		 * @param  {Function} callback a function to be called once the node is created
3844
		 * @param  {Boolean} is_loaded internal argument indicating if the parent node was succesfully loaded
3845
		 * @return {String}            the ID of the newly create node
3846
		 * @trigger model.jstree, create_node.jstree
3847
		 */
3848
		create_node : function (par, node, pos, callback, is_loaded) {
3849
			if(par === null) { par = $.jstree.root; }
3850
			par = this.get_node(par);
3851
			if(!par) { return false; }
3852
			pos = pos === undefined ? "last" : pos;
3853
			if(!pos.toString().match(/^(before|after)$/) && !is_loaded && !this.is_loaded(par)) {
3854
				return this.load_node(par, function () { this.create_node(par, node, pos, callback, true); });
3855
			}
3856
			if(!node) { node = { "text" : this.get_string('New node') }; }
3857
			if(typeof node === "string") {
3858
				node = { "text" : node };
3859
			} else {
3860
				node = $.extend(true, {}, node);
3861
			}
3862
			if(node.text === undefined) { node.text = this.get_string('New node'); }
3863
			var tmp, dpc, i, j;
3864
 
3865
			if(par.id === $.jstree.root) {
3866
				if(pos === "before") { pos = "first"; }
3867
				if(pos === "after") { pos = "last"; }
3868
			}
3869
			switch(pos) {
3870
				case "before":
3871
					tmp = this.get_node(par.parent);
3872
					pos = $.inArray(par.id, tmp.children);
3873
					par = tmp;
3874
					break;
3875
				case "after" :
3876
					tmp = this.get_node(par.parent);
3877
					pos = $.inArray(par.id, tmp.children) + 1;
3878
					par = tmp;
3879
					break;
3880
				case "inside":
3881
				case "first":
3882
					pos = 0;
3883
					break;
3884
				case "last":
3885
					pos = par.children.length;
3886
					break;
3887
				default:
3888
					if(!pos) { pos = 0; }
3889
					break;
3890
			}
3891
			if(pos > par.children.length) { pos = par.children.length; }
3892
			if(!node.id) { node.id = true; }
3893
			if(!this.check("create_node", node, par, pos)) {
3894
				this.settings.core.error.call(this, this._data.core.last_error);
3895
				return false;
3896
			}
3897
			if(node.id === true) { delete node.id; }
3898
			node = this._parse_model_from_json(node, par.id, par.parents.concat());
3899
			if(!node) { return false; }
3900
			tmp = this.get_node(node);
3901
			dpc = [];
3902
			dpc.push(node);
3903
			dpc = dpc.concat(tmp.children_d);
3904
			this.trigger('model', { "nodes" : dpc, "parent" : par.id });
3905
 
3906
			par.children_d = par.children_d.concat(dpc);
3907
			for(i = 0, j = par.parents.length; i < j; i++) {
3908
				this._model.data[par.parents[i]].children_d = this._model.data[par.parents[i]].children_d.concat(dpc);
3909
			}
3910
			node = tmp;
3911
			tmp = [];
3912
			for(i = 0, j = par.children.length; i < j; i++) {
3913
				tmp[i >= pos ? i+1 : i] = par.children[i];
3914
			}
3915
			tmp[pos] = node.id;
3916
			par.children = tmp;
3917
 
3918
			this.redraw_node(par, true);
3919
			/**
3920
			 * triggered when a node is created
3921
			 * @event
3922
			 * @name create_node.jstree
3923
			 * @param {Object} node
3924
			 * @param {String} parent the parent's ID
3925
			 * @param {Number} position the position of the new node among the parent's children
3926
			 */
3927
			this.trigger('create_node', { "node" : this.get_node(node), "parent" : par.id, "position" : pos });
3928
			if(callback) { callback.call(this, this.get_node(node)); }
3929
			return node.id;
3930
		},
3931
		/**
3932
		 * set the text value of a node
3933
		 * @name rename_node(obj, val)
3934
		 * @param  {mixed} obj the node, you can pass an array to rename multiple nodes to the same name
3935
		 * @param  {String} val the new text value
3936
		 * @return {Boolean}
3937
		 * @trigger rename_node.jstree
3938
		 */
3939
		rename_node : function (obj, val) {
3940
			var t1, t2, old;
3941
			if($.vakata.is_array(obj)) {
3942
				obj = obj.slice();
3943
				for(t1 = 0, t2 = obj.length; t1 < t2; t1++) {
3944
					this.rename_node(obj[t1], val);
3945
				}
3946
				return true;
3947
			}
3948
			obj = this.get_node(obj);
3949
			if(!obj || obj.id === $.jstree.root) { return false; }
3950
			old = obj.text;
3951
			if(!this.check("rename_node", obj, this.get_parent(obj), val)) {
3952
				this.settings.core.error.call(this, this._data.core.last_error);
3953
				return false;
3954
			}
3955
			this.set_text(obj, val); // .apply(this, Array.prototype.slice.call(arguments))
3956
			/**
3957
			 * triggered when a node is renamed
3958
			 * @event
3959
			 * @name rename_node.jstree
3960
			 * @param {Object} node
3961
			 * @param {String} text the new value
3962
			 * @param {String} old the old value
3963
			 */
3964
			this.trigger('rename_node', { "node" : obj, "text" : val, "old" : old });
3965
			return true;
3966
		},
3967
		/**
3968
		 * remove a node
3969
		 * @name delete_node(obj)
3970
		 * @param  {mixed} obj the node, you can pass an array to delete multiple nodes
3971
		 * @return {Boolean}
3972
		 * @trigger delete_node.jstree, changed.jstree
3973
		 */
3974
		delete_node : function (obj) {
3975
			var t1, t2, par, pos, tmp, i, j, k, l, c, top, lft;
3976
			if($.vakata.is_array(obj)) {
3977
				obj = obj.slice();
3978
				for(t1 = 0, t2 = obj.length; t1 < t2; t1++) {
3979
					this.delete_node(obj[t1]);
3980
				}
3981
				return true;
3982
			}
3983
			obj = this.get_node(obj);
3984
			if(!obj || obj.id === $.jstree.root) { return false; }
3985
			par = this.get_node(obj.parent);
3986
			pos = $.inArray(obj.id, par.children);
3987
			c = false;
3988
			if(!this.check("delete_node", obj, par, pos)) {
3989
				this.settings.core.error.call(this, this._data.core.last_error);
3990
				return false;
3991
			}
3992
			if(pos !== -1) {
3993
				par.children = $.vakata.array_remove(par.children, pos);
3994
			}
3995
			tmp = obj.children_d.concat([]);
3996
			tmp.push(obj.id);
3997
			for(i = 0, j = obj.parents.length; i < j; i++) {
3998
				this._model.data[obj.parents[i]].children_d = $.vakata.array_filter(this._model.data[obj.parents[i]].children_d, function (v) {
3999
					return $.inArray(v, tmp) === -1;
4000
				});
4001
			}
4002
			for(k = 0, l = tmp.length; k < l; k++) {
4003
				if(this._model.data[tmp[k]].state.selected) {
4004
					c = true;
4005
					break;
4006
				}
4007
			}
4008
			if (c) {
4009
				this._data.core.selected = $.vakata.array_filter(this._data.core.selected, function (v) {
4010
					return $.inArray(v, tmp) === -1;
4011
				});
4012
			}
4013
			/**
4014
			 * triggered when a node is deleted
4015
			 * @event
4016
			 * @name delete_node.jstree
4017
			 * @param {Object} node
4018
			 * @param {String} parent the parent's ID
4019
			 */
4020
			this.trigger('delete_node', { "node" : obj, "parent" : par.id });
4021
			if(c) {
4022
				this.trigger('changed', { 'action' : 'delete_node', 'node' : obj, 'selected' : this._data.core.selected, 'parent' : par.id });
4023
			}
4024
			for(k = 0, l = tmp.length; k < l; k++) {
4025
				delete this._model.data[tmp[k]];
4026
			}
4027
			if($.inArray(this._data.core.focused, tmp) !== -1) {
4028
				this._data.core.focused = null;
4029
				top = this.element[0].scrollTop;
4030
				lft = this.element[0].scrollLeft;
4031
				if(par.id === $.jstree.root) {
4032
					if (this._model.data[$.jstree.root].children[0]) {
4033
						this.get_node(this._model.data[$.jstree.root].children[0], true).children('.jstree-anchor').triger('focus');
4034
					}
4035
				}
4036
				else {
4037
					this.get_node(par, true).children('.jstree-anchor').trigger('focus');
4038
				}
4039
				this.element[0].scrollTop  = top;
4040
				this.element[0].scrollLeft = lft;
4041
			}
4042
			this.redraw_node(par, true);
4043
			return true;
4044
		},
4045
		/**
4046
		 * check if an operation is premitted on the tree. Used internally.
4047
		 * @private
4048
		 * @name check(chk, obj, par, pos)
4049
		 * @param  {String} chk the operation to check, can be "create_node", "rename_node", "delete_node", "copy_node" or "move_node"
4050
		 * @param  {mixed} obj the node
4051
		 * @param  {mixed} par the parent
4052
		 * @param  {mixed} pos the position to insert at, or if "rename_node" - the new name
4053
		 * @param  {mixed} more some various additional information, for example if a "move_node" operations is triggered by DND this will be the hovered node
4054
		 * @return {Boolean}
4055
		 */
4056
		check : function (chk, obj, par, pos, more) {
4057
			obj = obj && obj.id ? obj : this.get_node(obj);
4058
			par = par && par.id ? par : this.get_node(par);
4059
			var tmp = chk.match(/^move_node|copy_node|create_node$/i) ? par : obj,
4060
				chc = this.settings.core.check_callback;
4061
			if(chk === "move_node" || chk === "copy_node") {
4062
				if((!more || !more.is_multi) && (chk === "move_node" && $.inArray(obj.id, par.children) === pos)) {
4063
					this._data.core.last_error = { 'error' : 'check', 'plugin' : 'core', 'id' : 'core_08', 'reason' : 'Moving node to its current position', 'data' : JSON.stringify({ 'chk' : chk, 'pos' : pos, 'obj' : obj && obj.id ? obj.id : false, 'par' : par && par.id ? par.id : false }) };
4064
					return false;
4065
				}
4066
				if((!more || !more.is_multi) && (obj.id === par.id || (chk === "move_node" && $.inArray(obj.id, par.children) === pos) || $.inArray(par.id, obj.children_d) !== -1)) {
4067
					this._data.core.last_error = { 'error' : 'check', 'plugin' : 'core', 'id' : 'core_01', 'reason' : 'Moving parent inside child', 'data' : JSON.stringify({ 'chk' : chk, 'pos' : pos, 'obj' : obj && obj.id ? obj.id : false, 'par' : par && par.id ? par.id : false }) };
4068
					return false;
4069
				}
4070
			}
4071
			if(tmp && tmp.data) { tmp = tmp.data; }
4072
			if(tmp && tmp.functions && (tmp.functions[chk] === false || tmp.functions[chk] === true)) {
4073
				if(tmp.functions[chk] === false) {
4074
					this._data.core.last_error = { 'error' : 'check', 'plugin' : 'core', 'id' : 'core_02', 'reason' : 'Node data prevents function: ' + chk, 'data' : JSON.stringify({ 'chk' : chk, 'pos' : pos, 'obj' : obj && obj.id ? obj.id : false, 'par' : par && par.id ? par.id : false }) };
4075
				}
4076
				return tmp.functions[chk];
4077
			}
4078
			if(chc === false || ($.vakata.is_function(chc) && chc.call(this, chk, obj, par, pos, more) === false) || (chc && chc[chk] === false)) {
4079
				this._data.core.last_error = { 'error' : 'check', 'plugin' : 'core', 'id' : 'core_03', 'reason' : 'User config for core.check_callback prevents function: ' + chk, 'data' : JSON.stringify({ 'chk' : chk, 'pos' : pos, 'obj' : obj && obj.id ? obj.id : false, 'par' : par && par.id ? par.id : false }) };
4080
				return false;
4081
			}
4082
			return true;
4083
		},
4084
		/**
4085
		 * get the last error
4086
		 * @name last_error()
4087
		 * @return {Object}
4088
		 */
4089
		last_error : function () {
4090
			return this._data.core.last_error;
4091
		},
4092
		/**
4093
		 * move a node to a new parent
4094
		 * @name move_node(obj, par [, pos, callback, is_loaded])
4095
		 * @param  {mixed} obj the node to move, pass an array to move multiple nodes
4096
		 * @param  {mixed} par the new parent
4097
		 * @param  {mixed} pos the position to insert at (besides integer values, "first" and "last" are supported, as well as "before" and "after"), defaults to integer `0`
4098
		 * @param  {function} callback a function to call once the move is completed, receives 3 arguments - the node, the new parent and the position
4099
		 * @param  {Boolean} is_loaded internal parameter indicating if the parent node has been loaded
4100
		 * @param  {Boolean} skip_redraw internal parameter indicating if the tree should be redrawn
4101
		 * @param  {Boolean} instance internal parameter indicating if the node comes from another instance
4102
		 * @trigger move_node.jstree
4103
		 */
4104
		move_node : function (obj, par, pos, callback, is_loaded, skip_redraw, origin) {
4105
			var t1, t2, old_par, old_pos, new_par, old_ins, is_multi, dpc, tmp, i, j, k, l, p;
4106
 
4107
			par = this.get_node(par);
4108
			pos = pos === undefined ? 0 : pos;
4109
			if(!par) { return false; }
4110
			if(!pos.toString().match(/^(before|after)$/) && !is_loaded && !this.is_loaded(par)) {
4111
				return this.load_node(par, function () { this.move_node(obj, par, pos, callback, true, false, origin); });
4112
			}
4113
 
4114
			if($.vakata.is_array(obj)) {
4115
				if(obj.length === 1) {
4116
					obj = obj[0];
4117
				}
4118
				else {
4119
					//obj = obj.slice();
4120
					for(t1 = 0, t2 = obj.length; t1 < t2; t1++) {
4121
						if((tmp = this.move_node(obj[t1], par, pos, callback, is_loaded, false, origin))) {
4122
							par = tmp;
4123
							pos = "after";
4124
						}
4125
					}
4126
					this.redraw();
4127
					return true;
4128
				}
4129
			}
4130
			obj = obj && obj.id ? obj : this.get_node(obj);
4131
 
4132
			if(!obj || obj.id === $.jstree.root) { return false; }
4133
 
4134
			old_par = (obj.parent || $.jstree.root).toString();
4135
			new_par = (!pos.toString().match(/^(before|after)$/) || par.id === $.jstree.root) ? par : this.get_node(par.parent);
4136
			old_ins = origin ? origin : (this._model.data[obj.id] ? this : $.jstree.reference(obj.id));
4137
			is_multi = !old_ins || !old_ins._id || (this._id !== old_ins._id);
4138
			old_pos = old_ins && old_ins._id && old_par && old_ins._model.data[old_par] && old_ins._model.data[old_par].children ? $.inArray(obj.id, old_ins._model.data[old_par].children) : -1;
4139
			if(old_ins && old_ins._id) {
4140
				obj = old_ins._model.data[obj.id];
4141
			}
4142
 
4143
			if(is_multi) {
4144
				if((tmp = this.copy_node(obj, par, pos, callback, is_loaded, false, origin))) {
4145
					if(old_ins) { old_ins.delete_node(obj); }
4146
					return tmp;
4147
				}
4148
				return false;
4149
			}
4150
			//var m = this._model.data;
4151
			if(par.id === $.jstree.root) {
4152
				if(pos === "before") { pos = "first"; }
4153
				if(pos === "after") { pos = "last"; }
4154
			}
4155
			switch(pos) {
4156
				case "before":
4157
					pos = $.inArray(par.id, new_par.children);
4158
					break;
4159
				case "after" :
4160
					pos = $.inArray(par.id, new_par.children) + 1;
4161
					break;
4162
				case "inside":
4163
				case "first":
4164
					pos = 0;
4165
					break;
4166
				case "last":
4167
					pos = new_par.children.length;
4168
					break;
4169
				default:
4170
					if(!pos) { pos = 0; }
4171
					break;
4172
			}
4173
			if(pos > new_par.children.length) { pos = new_par.children.length; }
4174
			if(!this.check("move_node", obj, new_par, pos, { 'core' : true, 'origin' : origin, 'is_multi' : (old_ins && old_ins._id && old_ins._id !== this._id), 'is_foreign' : (!old_ins || !old_ins._id) })) {
4175
				this.settings.core.error.call(this, this._data.core.last_error);
4176
				return false;
4177
			}
4178
			if(obj.parent === new_par.id) {
4179
				dpc = new_par.children.concat();
4180
				tmp = $.inArray(obj.id, dpc);
4181
				if(tmp !== -1) {
4182
					dpc = $.vakata.array_remove(dpc, tmp);
4183
					if(pos > tmp) { pos--; }
4184
				}
4185
				tmp = [];
4186
				for(i = 0, j = dpc.length; i < j; i++) {
4187
					tmp[i >= pos ? i+1 : i] = dpc[i];
4188
				}
4189
				tmp[pos] = obj.id;
4190
				new_par.children = tmp;
4191
				this._node_changed(new_par.id);
4192
				this.redraw(new_par.id === $.jstree.root);
4193
			}
4194
			else {
4195
				// clean old parent and up
4196
				tmp = obj.children_d.concat();
4197
				tmp.push(obj.id);
4198
				for(i = 0, j = obj.parents.length; i < j; i++) {
4199
					dpc = [];
4200
					p = old_ins._model.data[obj.parents[i]].children_d;
4201
					for(k = 0, l = p.length; k < l; k++) {
4202
						if($.inArray(p[k], tmp) === -1) {
4203
							dpc.push(p[k]);
4204
						}
4205
					}
4206
					old_ins._model.data[obj.parents[i]].children_d = dpc;
4207
				}
4208
				old_ins._model.data[old_par].children = $.vakata.array_remove_item(old_ins._model.data[old_par].children, obj.id);
4209
 
4210
				// insert into new parent and up
4211
				for(i = 0, j = new_par.parents.length; i < j; i++) {
4212
					this._model.data[new_par.parents[i]].children_d = this._model.data[new_par.parents[i]].children_d.concat(tmp);
4213
				}
4214
				dpc = [];
4215
				for(i = 0, j = new_par.children.length; i < j; i++) {
4216
					dpc[i >= pos ? i+1 : i] = new_par.children[i];
4217
				}
4218
				dpc[pos] = obj.id;
4219
				new_par.children = dpc;
4220
				new_par.children_d.push(obj.id);
4221
				new_par.children_d = new_par.children_d.concat(obj.children_d);
4222
 
4223
				// update object
4224
				obj.parent = new_par.id;
4225
				tmp = new_par.parents.concat();
4226
				tmp.unshift(new_par.id);
4227
				p = obj.parents.length;
4228
				obj.parents = tmp;
4229
 
4230
				// update object children
4231
				tmp = tmp.concat();
4232
				for(i = 0, j = obj.children_d.length; i < j; i++) {
4233
					this._model.data[obj.children_d[i]].parents = this._model.data[obj.children_d[i]].parents.slice(0,p*-1);
4234
					Array.prototype.push.apply(this._model.data[obj.children_d[i]].parents, tmp);
4235
				}
4236
 
4237
				if(old_par === $.jstree.root || new_par.id === $.jstree.root) {
4238
					this._model.force_full_redraw = true;
4239
				}
4240
				if(!this._model.force_full_redraw) {
4241
					this._node_changed(old_par);
4242
					this._node_changed(new_par.id);
4243
				}
4244
				if(!skip_redraw) {
4245
					this.redraw();
4246
				}
4247
			}
4248
			if(callback) { callback.call(this, obj, new_par, pos); }
4249
			/**
4250
			 * triggered when a node is moved
4251
			 * @event
4252
			 * @name move_node.jstree
4253
			 * @param {Object} node
4254
			 * @param {String} parent the parent's ID
4255
			 * @param {Number} position the position of the node among the parent's children
4256
			 * @param {String} old_parent the old parent of the node
4257
			 * @param {Number} old_position the old position of the node
4258
			 * @param {Boolean} is_multi do the node and new parent belong to different instances
4259
			 * @param {jsTree} old_instance the instance the node came from
4260
			 * @param {jsTree} new_instance the instance of the new parent
4261
			 */
4262
			this.trigger('move_node', { "node" : obj, "parent" : new_par.id, "position" : pos, "old_parent" : old_par, "old_position" : old_pos, 'is_multi' : (old_ins && old_ins._id && old_ins._id !== this._id), 'is_foreign' : (!old_ins || !old_ins._id), 'old_instance' : old_ins, 'new_instance' : this });
4263
			return obj.id;
4264
		},
4265
		/**
4266
		 * copy a node to a new parent
4267
		 * @name copy_node(obj, par [, pos, callback, is_loaded])
4268
		 * @param  {mixed} obj the node to copy, pass an array to copy multiple nodes
4269
		 * @param  {mixed} par the new parent
4270
		 * @param  {mixed} pos the position to insert at (besides integer values, "first" and "last" are supported, as well as "before" and "after"), defaults to integer `0`
4271
		 * @param  {function} callback a function to call once the move is completed, receives 3 arguments - the node, the new parent and the position
4272
		 * @param  {Boolean} is_loaded internal parameter indicating if the parent node has been loaded
4273
		 * @param  {Boolean} skip_redraw internal parameter indicating if the tree should be redrawn
4274
		 * @param  {Boolean} instance internal parameter indicating if the node comes from another instance
4275
		 * @trigger model.jstree copy_node.jstree
4276
		 */
4277
		copy_node : function (obj, par, pos, callback, is_loaded, skip_redraw, origin) {
4278
			var t1, t2, dpc, tmp, i, j, node, old_par, new_par, old_ins, is_multi;
4279
 
4280
			par = this.get_node(par);
4281
			pos = pos === undefined ? 0 : pos;
4282
			if(!par) { return false; }
4283
			if(!pos.toString().match(/^(before|after)$/) && !is_loaded && !this.is_loaded(par)) {
4284
				return this.load_node(par, function () { this.copy_node(obj, par, pos, callback, true, false, origin); });
4285
			}
4286
 
4287
			if($.vakata.is_array(obj)) {
4288
				if(obj.length === 1) {
4289
					obj = obj[0];
4290
				}
4291
				else {
4292
					//obj = obj.slice();
4293
					for(t1 = 0, t2 = obj.length; t1 < t2; t1++) {
4294
						if((tmp = this.copy_node(obj[t1], par, pos, callback, is_loaded, true, origin))) {
4295
							par = tmp;
4296
							pos = "after";
4297
						}
4298
					}
4299
					this.redraw();
4300
					return true;
4301
				}
4302
			}
4303
			obj = obj && obj.id ? obj : this.get_node(obj);
4304
			if(!obj || obj.id === $.jstree.root) { return false; }
4305
 
4306
			old_par = (obj.parent || $.jstree.root).toString();
4307
			new_par = (!pos.toString().match(/^(before|after)$/) || par.id === $.jstree.root) ? par : this.get_node(par.parent);
4308
			old_ins = origin ? origin : (this._model.data[obj.id] ? this : $.jstree.reference(obj.id));
4309
			is_multi = !old_ins || !old_ins._id || (this._id !== old_ins._id);
4310
 
4311
			if(old_ins && old_ins._id) {
4312
				obj = old_ins._model.data[obj.id];
4313
			}
4314
 
4315
			if(par.id === $.jstree.root) {
4316
				if(pos === "before") { pos = "first"; }
4317
				if(pos === "after") { pos = "last"; }
4318
			}
4319
			switch(pos) {
4320
				case "before":
4321
					pos = $.inArray(par.id, new_par.children);
4322
					break;
4323
				case "after" :
4324
					pos = $.inArray(par.id, new_par.children) + 1;
4325
					break;
4326
				case "inside":
4327
				case "first":
4328
					pos = 0;
4329
					break;
4330
				case "last":
4331
					pos = new_par.children.length;
4332
					break;
4333
				default:
4334
					if(!pos) { pos = 0; }
4335
					break;
4336
			}
4337
			if(pos > new_par.children.length) { pos = new_par.children.length; }
4338
			if(!this.check("copy_node", obj, new_par, pos, { 'core' : true, 'origin' : origin, 'is_multi' : (old_ins && old_ins._id && old_ins._id !== this._id), 'is_foreign' : (!old_ins || !old_ins._id) })) {
4339
				this.settings.core.error.call(this, this._data.core.last_error);
4340
				return false;
4341
			}
4342
			node = old_ins ? old_ins.get_json(obj, { no_id : true, no_data : true, no_state : true }) : obj;
4343
			if(!node) { return false; }
4344
			if(node.id === true) { delete node.id; }
4345
			node = this._parse_model_from_json(node, new_par.id, new_par.parents.concat());
4346
			if(!node) { return false; }
4347
			tmp = this.get_node(node);
4348
			if(obj && obj.state && obj.state.loaded === false) { tmp.state.loaded = false; }
4349
			dpc = [];
4350
			dpc.push(node);
4351
			dpc = dpc.concat(tmp.children_d);
4352
			this.trigger('model', { "nodes" : dpc, "parent" : new_par.id });
4353
 
4354
			// insert into new parent and up
4355
			for(i = 0, j = new_par.parents.length; i < j; i++) {
4356
				this._model.data[new_par.parents[i]].children_d = this._model.data[new_par.parents[i]].children_d.concat(dpc);
4357
			}
4358
			dpc = [];
4359
			for(i = 0, j = new_par.children.length; i < j; i++) {
4360
				dpc[i >= pos ? i+1 : i] = new_par.children[i];
4361
			}
4362
			dpc[pos] = tmp.id;
4363
			new_par.children = dpc;
4364
			new_par.children_d.push(tmp.id);
4365
			new_par.children_d = new_par.children_d.concat(tmp.children_d);
4366
 
4367
			if(new_par.id === $.jstree.root) {
4368
				this._model.force_full_redraw = true;
4369
			}
4370
			if(!this._model.force_full_redraw) {
4371
				this._node_changed(new_par.id);
4372
			}
4373
			if(!skip_redraw) {
4374
				this.redraw(new_par.id === $.jstree.root);
4375
			}
4376
			if(callback) { callback.call(this, tmp, new_par, pos); }
4377
			/**
4378
			 * triggered when a node is copied
4379
			 * @event
4380
			 * @name copy_node.jstree
4381
			 * @param {Object} node the copied node
4382
			 * @param {Object} original the original node
4383
			 * @param {String} parent the parent's ID
4384
			 * @param {Number} position the position of the node among the parent's children
4385
			 * @param {String} old_parent the old parent of the node
4386
			 * @param {Number} old_position the position of the original node
4387
			 * @param {Boolean} is_multi do the node and new parent belong to different instances
4388
			 * @param {jsTree} old_instance the instance the node came from
4389
			 * @param {jsTree} new_instance the instance of the new parent
4390
			 */
4391
			this.trigger('copy_node', { "node" : tmp, "original" : obj, "parent" : new_par.id, "position" : pos, "old_parent" : old_par, "old_position" : old_ins && old_ins._id && old_par && old_ins._model.data[old_par] && old_ins._model.data[old_par].children ? $.inArray(obj.id, old_ins._model.data[old_par].children) : -1,'is_multi' : (old_ins && old_ins._id && old_ins._id !== this._id), 'is_foreign' : (!old_ins || !old_ins._id), 'old_instance' : old_ins, 'new_instance' : this });
4392
			return tmp.id;
4393
		},
4394
		/**
4395
		 * cut a node (a later call to `paste(obj)` would move the node)
4396
		 * @name cut(obj)
4397
		 * @param  {mixed} obj multiple objects can be passed using an array
4398
		 * @trigger cut.jstree
4399
		 */
4400
		cut : function (obj) {
4401
			if(!obj) { obj = this._data.core.selected.concat(); }
4402
			if(!$.vakata.is_array(obj)) { obj = [obj]; }
4403
			if(!obj.length) { return false; }
4404
			var tmp = [], o, t1, t2;
4405
			for(t1 = 0, t2 = obj.length; t1 < t2; t1++) {
4406
				o = this.get_node(obj[t1]);
4407
				if(o && o.id && o.id !== $.jstree.root) { tmp.push(o); }
4408
			}
4409
			if(!tmp.length) { return false; }
4410
			ccp_node = tmp;
4411
			ccp_inst = this;
4412
			ccp_mode = 'move_node';
4413
			/**
4414
			 * triggered when nodes are added to the buffer for moving
4415
			 * @event
4416
			 * @name cut.jstree
4417
			 * @param {Array} node
4418
			 */
4419
			this.trigger('cut', { "node" : obj });
4420
		},
4421
		/**
4422
		 * copy a node (a later call to `paste(obj)` would copy the node)
4423
		 * @name copy(obj)
4424
		 * @param  {mixed} obj multiple objects can be passed using an array
4425
		 * @trigger copy.jstree
4426
		 */
4427
		copy : function (obj) {
4428
			if(!obj) { obj = this._data.core.selected.concat(); }
4429
			if(!$.vakata.is_array(obj)) { obj = [obj]; }
4430
			if(!obj.length) { return false; }
4431
			var tmp = [], o, t1, t2;
4432
			for(t1 = 0, t2 = obj.length; t1 < t2; t1++) {
4433
				o = this.get_node(obj[t1]);
4434
				if(o && o.id && o.id !== $.jstree.root) { tmp.push(o); }
4435
			}
4436
			if(!tmp.length) { return false; }
4437
			ccp_node = tmp;
4438
			ccp_inst = this;
4439
			ccp_mode = 'copy_node';
4440
			/**
4441
			 * triggered when nodes are added to the buffer for copying
4442
			 * @event
4443
			 * @name copy.jstree
4444
			 * @param {Array} node
4445
			 */
4446
			this.trigger('copy', { "node" : obj });
4447
		},
4448
		/**
4449
		 * get the current buffer (any nodes that are waiting for a paste operation)
4450
		 * @name get_buffer()
4451
		 * @return {Object} an object consisting of `mode` ("copy_node" or "move_node"), `node` (an array of objects) and `inst` (the instance)
4452
		 */
4453
		get_buffer : function () {
4454
			return { 'mode' : ccp_mode, 'node' : ccp_node, 'inst' : ccp_inst };
4455
		},
4456
		/**
4457
		 * check if there is something in the buffer to paste
4458
		 * @name can_paste()
4459
		 * @return {Boolean}
4460
		 */
4461
		can_paste : function () {
4462
			return ccp_mode !== false && ccp_node !== false; // && ccp_inst._model.data[ccp_node];
4463
		},
4464
		/**
4465
		 * copy or move the previously cut or copied nodes to a new parent
4466
		 * @name paste(obj [, pos])
4467
		 * @param  {mixed} obj the new parent
4468
		 * @param  {mixed} pos the position to insert at (besides integer, "first" and "last" are supported), defaults to integer `0`
4469
		 * @trigger paste.jstree
4470
		 */
4471
		paste : function (obj, pos) {
4472
			obj = this.get_node(obj);
4473
			if(!obj || !ccp_mode || !ccp_mode.match(/^(copy_node|move_node)$/) || !ccp_node) { return false; }
4474
			if(this[ccp_mode](ccp_node, obj, pos, false, false, false, ccp_inst)) {
4475
				/**
4476
				 * triggered when paste is invoked
4477
				 * @event
4478
				 * @name paste.jstree
4479
				 * @param {String} parent the ID of the receiving node
4480
				 * @param {Array} node the nodes in the buffer
4481
				 * @param {String} mode the performed operation - "copy_node" or "move_node"
4482
				 */
4483
				this.trigger('paste', { "parent" : obj.id, "node" : ccp_node, "mode" : ccp_mode });
4484
			}
4485
			ccp_node = false;
4486
			ccp_mode = false;
4487
			ccp_inst = false;
4488
		},
4489
		/**
4490
		 * clear the buffer of previously copied or cut nodes
4491
		 * @name clear_buffer()
4492
		 * @trigger clear_buffer.jstree
4493
		 */
4494
		clear_buffer : function () {
4495
			ccp_node = false;
4496
			ccp_mode = false;
4497
			ccp_inst = false;
4498
			/**
4499
			 * triggered when the copy / cut buffer is cleared
4500
			 * @event
4501
			 * @name clear_buffer.jstree
4502
			 */
4503
			this.trigger('clear_buffer');
4504
		},
4505
		/**
4506
		 * put a node in edit mode (input field to rename the node)
4507
		 * @name edit(obj [, default_text, callback])
4508
		 * @param  {mixed} obj
4509
		 * @param  {String} default_text the text to populate the input with (if omitted or set to a non-string value the node's text value is used)
4510
		 * @param  {Function} callback a function to be called once the text box is blurred, it is called in the instance's scope and receives the node, a status parameter (true if the rename is successful, false otherwise), a boolean indicating if the user cancelled the edit and the original unescaped value provided by the user. You can also access the node's title using .text
4511
		 */
4512
		edit : function (obj, default_text, callback) {
4513
			var rtl, w, a, s, t, h1, h2, fn, tmp, cancel = false;
4514
			obj = this.get_node(obj);
4515
			if(!obj) { return false; }
4516
			if(!this.check("edit", obj, this.get_parent(obj))) {
4517
				this.settings.core.error.call(this, this._data.core.last_error);
4518
				return false;
4519
			}
4520
			tmp = obj;
4521
			default_text = typeof default_text === 'string' ? default_text : obj.text;
4522
			this.set_text(obj, "");
4523
			obj = this._open_to(obj);
4524
			tmp.text = default_text;
4525
 
4526
			rtl = this._data.core.rtl;
4527
			w  = this.element.width();
4528
			this._data.core.focused = tmp.id;
4529
			a  = obj.children('.jstree-anchor').trigger('focus');
4530
			s  = $('<span></span>');
4531
			/*!
4532
			oi = obj.children("i:visible"),
4533
			ai = a.children("i:visible"),
4534
			w1 = oi.width() * oi.length,
4535
			w2 = ai.width() * ai.length,
4536
			*/
4537
			t  = default_text;
4538
			h1 = $("<"+"div></div>", { css : { "position" : "absolute", "top" : "-200px", "left" : (rtl ? "0px" : "-1000px"), "visibility" : "hidden" } }).appendTo(document.body);
4539
			h2 = $("<"+"input />", {
4540
						"value" : t,
4541
						"class" : "jstree-rename-input",
4542
						// "size" : t.length,
4543
						"css" : {
4544
							"padding" : "0",
4545
							"border" : "1px solid silver",
4546
							"box-sizing" : "border-box",
4547
							"display" : "inline-block",
4548
							"height" : (this._data.core.li_height) + "px",
4549
							"lineHeight" : (this._data.core.li_height) + "px",
4550
							"width" : "150px" // will be set a bit further down
4551
						},
4552
						"blur" : function (e) {
4553
							e.stopImmediatePropagation();
4554
							e.preventDefault();
4555
							var i = s.children(".jstree-rename-input"),
4556
								v = i.val(),
4557
								f = this.settings.core.force_text,
4558
								nv;
4559
							if(v === "") { v = t; }
4560
							h1.remove();
4561
							s.replaceWith(a);
4562
							s.remove();
4563
							t = f ? t : $('<div></div>').append($.parseHTML(t)).html();
4564
							obj = this.get_node(obj);
4565
							this.set_text(obj, t);
4566
							nv = !!this.rename_node(obj, f ? $('<div></div>').text(v).text() : $('<div></div>').append($.parseHTML(v)).html());
4567
							if(!nv) {
4568
								this.set_text(obj, t); // move this up? and fix #483
4569
							}
4570
							this._data.core.focused = tmp.id;
4571
							setTimeout(function () {
4572
								var node = this.get_node(tmp.id, true);
4573
								if(node.length) {
4574
									this._data.core.focused = tmp.id;
4575
									node.children('.jstree-anchor').trigger('focus');
4576
								}
4577
							}.bind(this), 0);
4578
							if(callback) {
4579
								callback.call(this, tmp, nv, cancel, v);
4580
							}
4581
							h2 = null;
4582
						}.bind(this),
4583
						"keydown" : function (e) {
4584
							var key = e.which;
4585
							if(key === 27) {
4586
								cancel = true;
4587
								this.value = t;
4588
							}
4589
							if(key === 27 || key === 13 || key === 37 || key === 38 || key === 39 || key === 40 || key === 32) {
4590
								e.stopImmediatePropagation();
4591
							}
4592
							if(key === 27 || key === 13) {
4593
								e.preventDefault();
4594
								this.blur();
4595
							}
4596
						},
4597
						"click" : function (e) { e.stopImmediatePropagation(); },
4598
						"mousedown" : function (e) { e.stopImmediatePropagation(); },
4599
						"keyup" : function (e) {
4600
							h2.width(Math.min(h1.text("pW" + this.value).width(),w));
4601
						},
4602
						"keypress" : function(e) {
4603
							if(e.which === 13) { return false; }
4604
						}
4605
					});
4606
				fn = {
4607
						fontFamily		: a.css('fontFamily')		|| '',
4608
						fontSize		: a.css('fontSize')			|| '',
4609
						fontWeight		: a.css('fontWeight')		|| '',
4610
						fontStyle		: a.css('fontStyle')		|| '',
4611
						fontStretch		: a.css('fontStretch')		|| '',
4612
						fontVariant		: a.css('fontVariant')		|| '',
4613
						letterSpacing	: a.css('letterSpacing')	|| '',
4614
						wordSpacing		: a.css('wordSpacing')		|| ''
4615
				};
4616
			s.attr('class', a.attr('class')).append(a.contents().clone()).append(h2);
4617
			a.replaceWith(s);
4618
			h1.css(fn);
4619
			h2.css(fn).width(Math.min(h1.text("pW" + h2[0].value).width(),w))[0].select();
4620
			$(document).one('mousedown.jstree touchstart.jstree dnd_start.vakata', function (e) {
4621
				if (h2 && e.target !== h2) {
4622
					$(h2).trigger('blur');
4623
				}
4624
			});
4625
		},
4626
 
4627
 
4628
		/**
4629
		 * changes the theme
4630
		 * @name set_theme(theme_name [, theme_url])
4631
		 * @param {String} theme_name the name of the new theme to apply
4632
		 * @param {mixed} theme_url  the location of the CSS file for this theme. Omit or set to `false` if you manually included the file. Set to `true` to autoload from the `core.themes.dir` directory.
4633
		 * @trigger set_theme.jstree
4634
		 */
4635
		set_theme : function (theme_name, theme_url) {
4636
			if(!theme_name) { return false; }
4637
			if(theme_url === true) {
4638
				var dir = this.settings.core.themes.dir;
4639
				if(!dir) { dir = $.jstree.path + '/themes'; }
4640
				theme_url = dir + '/' + theme_name + '/style.css';
4641
			}
4642
			if(theme_url && $.inArray(theme_url, themes_loaded) === -1) {
4643
				$('head').append('<'+'link rel="stylesheet" href="' + theme_url + '" type="text/css" />');
4644
				themes_loaded.push(theme_url);
4645
			}
4646
			if(this._data.core.themes.name) {
4647
				this.element.removeClass('jstree-' + this._data.core.themes.name);
4648
			}
4649
			this._data.core.themes.name = theme_name;
4650
			this.element.addClass('jstree-' + theme_name);
4651
			this.element[this.settings.core.themes.responsive ? 'addClass' : 'removeClass' ]('jstree-' + theme_name + '-responsive');
4652
			/**
4653
			 * triggered when a theme is set
4654
			 * @event
4655
			 * @name set_theme.jstree
4656
			 * @param {String} theme the new theme
4657
			 */
4658
			this.trigger('set_theme', { 'theme' : theme_name });
4659
		},
4660
		/**
4661
		 * gets the name of the currently applied theme name
4662
		 * @name get_theme()
4663
		 * @return {String}
4664
		 */
4665
		get_theme : function () { return this._data.core.themes.name; },
4666
		/**
4667
		 * changes the theme variant (if the theme has variants)
4668
		 * @name set_theme_variant(variant_name)
4669
		 * @param {String|Boolean} variant_name the variant to apply (if `false` is used the current variant is removed)
4670
		 */
4671
		set_theme_variant : function (variant_name) {
4672
			if(this._data.core.themes.variant) {
4673
				this.element.removeClass('jstree-' + this._data.core.themes.name + '-' + this._data.core.themes.variant);
4674
			}
4675
			this._data.core.themes.variant = variant_name;
4676
			if(variant_name) {
4677
				this.element.addClass('jstree-' + this._data.core.themes.name + '-' + this._data.core.themes.variant);
4678
			}
4679
		},
4680
		/**
4681
		 * gets the name of the currently applied theme variant
4682
		 * @name get_theme()
4683
		 * @return {String}
4684
		 */
4685
		get_theme_variant : function () { return this._data.core.themes.variant; },
4686
		/**
4687
		 * shows a striped background on the container (if the theme supports it)
4688
		 * @name show_stripes()
4689
		 */
4690
		show_stripes : function () {
4691
			this._data.core.themes.stripes = true;
4692
			this.get_container_ul().addClass("jstree-striped");
4693
			/**
4694
			 * triggered when stripes are shown
4695
			 * @event
4696
			 * @name show_stripes.jstree
4697
			 */
4698
			this.trigger('show_stripes');
4699
		},
4700
		/**
4701
		 * hides the striped background on the container
4702
		 * @name hide_stripes()
4703
		 */
4704
		hide_stripes : function () {
4705
			this._data.core.themes.stripes = false;
4706
			this.get_container_ul().removeClass("jstree-striped");
4707
			/**
4708
			 * triggered when stripes are hidden
4709
			 * @event
4710
			 * @name hide_stripes.jstree
4711
			 */
4712
			this.trigger('hide_stripes');
4713
		},
4714
		/**
4715
		 * toggles the striped background on the container
4716
		 * @name toggle_stripes()
4717
		 */
4718
		toggle_stripes : function () { if(this._data.core.themes.stripes) { this.hide_stripes(); } else { this.show_stripes(); } },
4719
		/**
4720
		 * shows the connecting dots (if the theme supports it)
4721
		 * @name show_dots()
4722
		 */
4723
		show_dots : function () {
4724
			this._data.core.themes.dots = true;
4725
			this.get_container_ul().removeClass("jstree-no-dots");
4726
			/**
4727
			 * triggered when dots are shown
4728
			 * @event
4729
			 * @name show_dots.jstree
4730
			 */
4731
			this.trigger('show_dots');
4732
		},
4733
		/**
4734
		 * hides the connecting dots
4735
		 * @name hide_dots()
4736
		 */
4737
		hide_dots : function () {
4738
			this._data.core.themes.dots = false;
4739
			this.get_container_ul().addClass("jstree-no-dots");
4740
			/**
4741
			 * triggered when dots are hidden
4742
			 * @event
4743
			 * @name hide_dots.jstree
4744
			 */
4745
			this.trigger('hide_dots');
4746
		},
4747
		/**
4748
		 * toggles the connecting dots
4749
		 * @name toggle_dots()
4750
		 */
4751
		toggle_dots : function () { if(this._data.core.themes.dots) { this.hide_dots(); } else { this.show_dots(); } },
4752
		/**
4753
		 * show the node icons
4754
		 * @name show_icons()
4755
		 */
4756
		show_icons : function () {
4757
			this._data.core.themes.icons = true;
4758
			this.get_container_ul().removeClass("jstree-no-icons");
4759
			/**
4760
			 * triggered when icons are shown
4761
			 * @event
4762
			 * @name show_icons.jstree
4763
			 */
4764
			this.trigger('show_icons');
4765
		},
4766
		/**
4767
		 * hide the node icons
4768
		 * @name hide_icons()
4769
		 */
4770
		hide_icons : function () {
4771
			this._data.core.themes.icons = false;
4772
			this.get_container_ul().addClass("jstree-no-icons");
4773
			/**
4774
			 * triggered when icons are hidden
4775
			 * @event
4776
			 * @name hide_icons.jstree
4777
			 */
4778
			this.trigger('hide_icons');
4779
		},
4780
		/**
4781
		 * toggle the node icons
4782
		 * @name toggle_icons()
4783
		 */
4784
		toggle_icons : function () { if(this._data.core.themes.icons) { this.hide_icons(); } else { this.show_icons(); } },
4785
		/**
4786
		 * show the node ellipsis
4787
		 * @name show_icons()
4788
		 */
4789
		show_ellipsis : function () {
4790
			this._data.core.themes.ellipsis = true;
4791
			this.get_container_ul().addClass("jstree-ellipsis");
4792
			/**
4793
			 * triggered when ellisis is shown
4794
			 * @event
4795
			 * @name show_ellipsis.jstree
4796
			 */
4797
			this.trigger('show_ellipsis');
4798
		},
4799
		/**
4800
		 * hide the node ellipsis
4801
		 * @name hide_ellipsis()
4802
		 */
4803
		hide_ellipsis : function () {
4804
			this._data.core.themes.ellipsis = false;
4805
			this.get_container_ul().removeClass("jstree-ellipsis");
4806
			/**
4807
			 * triggered when ellisis is hidden
4808
			 * @event
4809
			 * @name hide_ellipsis.jstree
4810
			 */
4811
			this.trigger('hide_ellipsis');
4812
		},
4813
		/**
4814
		 * toggle the node ellipsis
4815
		 * @name toggle_icons()
4816
		 */
4817
		toggle_ellipsis : function () { if(this._data.core.themes.ellipsis) { this.hide_ellipsis(); } else { this.show_ellipsis(); } },
4818
		/**
4819
		 * set the node icon for a node
4820
		 * @name set_icon(obj, icon)
4821
		 * @param {mixed} obj
4822
		 * @param {String} icon the new icon - can be a path to an icon or a className, if using an image that is in the current directory use a `./` prefix, otherwise it will be detected as a class
4823
		 */
4824
		set_icon : function (obj, icon) {
4825
			var t1, t2, dom, old;
4826
			if($.vakata.is_array(obj)) {
4827
				obj = obj.slice();
4828
				for(t1 = 0, t2 = obj.length; t1 < t2; t1++) {
4829
					this.set_icon(obj[t1], icon);
4830
				}
4831
				return true;
4832
			}
4833
			obj = this.get_node(obj);
4834
			if(!obj || obj.id === $.jstree.root) { return false; }
4835
			old = obj.icon;
4836
			obj.icon = icon === true || icon === null || icon === undefined || icon === '' ? true : icon;
4837
			dom = this.get_node(obj, true).children(".jstree-anchor").children(".jstree-themeicon");
4838
			if(icon === false) {
4839
				dom.removeClass('jstree-themeicon-custom ' + old).css("background","").removeAttr("rel");
4840
				this.hide_icon(obj);
4841
			}
4842
			else if(icon === true || icon === null || icon === undefined || icon === '') {
4843
				dom.removeClass('jstree-themeicon-custom ' + old).css("background","").removeAttr("rel");
4844
				if(old === false) { this.show_icon(obj); }
4845
			}
4846
			else if(icon.indexOf("/") === -1 && icon.indexOf(".") === -1) {
4847
				dom.removeClass(old).css("background","");
4848
				dom.addClass(icon + ' jstree-themeicon-custom').attr("rel",icon);
4849
				if(old === false) { this.show_icon(obj); }
4850
			}
4851
			else {
4852
				dom.removeClass(old).css("background","");
4853
				dom.addClass('jstree-themeicon-custom').css("background", "url('" + icon + "') center center no-repeat").attr("rel",icon);
4854
				if(old === false) { this.show_icon(obj); }
4855
			}
4856
			return true;
4857
		},
4858
		/**
4859
		 * get the node icon for a node
4860
		 * @name get_icon(obj)
4861
		 * @param {mixed} obj
4862
		 * @return {String}
4863
		 */
4864
		get_icon : function (obj) {
4865
			obj = this.get_node(obj);
4866
			return (!obj || obj.id === $.jstree.root) ? false : obj.icon;
4867
		},
4868
		/**
4869
		 * hide the icon on an individual node
4870
		 * @name hide_icon(obj)
4871
		 * @param {mixed} obj
4872
		 */
4873
		hide_icon : function (obj) {
4874
			var t1, t2;
4875
			if($.vakata.is_array(obj)) {
4876
				obj = obj.slice();
4877
				for(t1 = 0, t2 = obj.length; t1 < t2; t1++) {
4878
					this.hide_icon(obj[t1]);
4879
				}
4880
				return true;
4881
			}
4882
			obj = this.get_node(obj);
4883
			if(!obj || obj === $.jstree.root) { return false; }
4884
			obj.icon = false;
4885
			this.get_node(obj, true).children(".jstree-anchor").children(".jstree-themeicon").addClass('jstree-themeicon-hidden');
4886
			return true;
4887
		},
4888
		/**
4889
		 * show the icon on an individual node
4890
		 * @name show_icon(obj)
4891
		 * @param {mixed} obj
4892
		 */
4893
		show_icon : function (obj) {
4894
			var t1, t2, dom;
4895
			if($.vakata.is_array(obj)) {
4896
				obj = obj.slice();
4897
				for(t1 = 0, t2 = obj.length; t1 < t2; t1++) {
4898
					this.show_icon(obj[t1]);
4899
				}
4900
				return true;
4901
			}
4902
			obj = this.get_node(obj);
4903
			if(!obj || obj === $.jstree.root) { return false; }
4904
			dom = this.get_node(obj, true);
4905
			obj.icon = dom.length ? dom.children(".jstree-anchor").children(".jstree-themeicon").attr('rel') : true;
4906
			if(!obj.icon) { obj.icon = true; }
4907
			dom.children(".jstree-anchor").children(".jstree-themeicon").removeClass('jstree-themeicon-hidden');
4908
			return true;
4909
		}
4910
	};
4911
 
4912
	// helpers
4913
	$.vakata = {};
4914
	// collect attributes
4915
	$.vakata.attributes = function(node, with_values) {
4916
		node = $(node)[0];
4917
		var attr = with_values ? {} : [];
4918
		if(node && node.attributes) {
4919
			$.each(node.attributes, function (i, v) {
4920
				if($.inArray(v.name.toLowerCase(),['style','contenteditable','hasfocus','tabindex']) !== -1) { return; }
4921
				if(v.value !== null && $.vakata.trim(v.value) !== '') {
4922
					if(with_values) { attr[v.name] = v.value; }
4923
					else { attr.push(v.name); }
4924
				}
4925
			});
4926
		}
4927
		return attr;
4928
	};
4929
	$.vakata.array_unique = function(array) {
4930
		var a = [], i, j, l, o = {};
4931
		for(i = 0, l = array.length; i < l; i++) {
4932
			if(o[array[i]] === undefined) {
4933
				a.push(array[i]);
4934
				o[array[i]] = true;
4935
			}
4936
		}
4937
		return a;
4938
	};
4939
	// remove item from array
4940
	$.vakata.array_remove = function(array, from) {
4941
		array.splice(from, 1);
4942
		return array;
4943
		//var rest = array.slice((to || from) + 1 || array.length);
4944
		//array.length = from < 0 ? array.length + from : from;
4945
		//array.push.apply(array, rest);
4946
		//return array;
4947
	};
4948
	// remove item from array
4949
	$.vakata.array_remove_item = function(array, item) {
4950
		var tmp = $.inArray(item, array);
4951
		return tmp !== -1 ? $.vakata.array_remove(array, tmp) : array;
4952
	};
4953
	$.vakata.array_filter = function(c,a,b,d,e) {
4954
		if (c.filter) {
4955
			return c.filter(a, b);
4956
		}
4957
		d=[];
4958
		for (e in c) {
4959
			if (~~e+''===e+'' && e>=0 && a.call(b,c[e],+e,c)) {
4960
				d.push(c[e]);
4961
			}
4962
		}
4963
		return d;
4964
	};
4965
	$.vakata.trim = function (text) {
4966
		return String.prototype.trim ?
4967
			String.prototype.trim.call(text.toString()) :
4968
			text.toString().replace(/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g, '');
4969
	};
4970
	$.vakata.is_function = function(obj) {
4971
		return typeof obj === "function" && typeof obj.nodeType !== "number";
4972
	};
4973
	$.vakata.is_array = Array.isArray || function (obj) {
4974
		return Object.prototype.toString.call(obj) === "[object Array]";
4975
	};
4976
 
4977
	// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_objects/Function/bind#polyfill
4978
	if (!Function.prototype.bind) {
4979
		Function.prototype.bind = function () {
4980
			var thatFunc = this, thatArg = arguments[0];
4981
			var args = Array.prototype.slice.call(arguments, 1);
4982
			if (typeof thatFunc !== 'function') {
4983
				// closest thing possible to the ECMAScript 5
4984
				// internal IsCallable function
4985
				throw new TypeError('Function.prototype.bind - what is trying to be bound is not callable');
4986
			}
4987
			return function(){
4988
				var funcArgs = args.concat(Array.prototype.slice.call(arguments));
4989
				return thatFunc.apply(thatArg, funcArgs);
4990
			};
4991
		};
4992
	}
4993
 
4994
 
4995
/**
4996
 * ### Changed plugin
4997
 *
4998
 * This plugin adds more information to the `changed.jstree` event. The new data is contained in the `changed` event data property, and contains a lists of `selected` and `deselected` nodes.
4999
 */
5000
 
5001
	$.jstree.plugins.changed = function (options, parent) {
5002
		var last = [];
5003
		this.trigger = function (ev, data) {
5004
			var i, j;
5005
			if(!data) {
5006
				data = {};
5007
			}
5008
			if(ev.replace('.jstree','') === 'changed') {
5009
				data.changed = { selected : [], deselected : [] };
5010
				var tmp = {};
5011
				for(i = 0, j = last.length; i < j; i++) {
5012
					tmp[last[i]] = 1;
5013
				}
5014
				for(i = 0, j = data.selected.length; i < j; i++) {
5015
					if(!tmp[data.selected[i]]) {
5016
						data.changed.selected.push(data.selected[i]);
5017
					}
5018
					else {
5019
						tmp[data.selected[i]] = 2;
5020
					}
5021
				}
5022
				for(i = 0, j = last.length; i < j; i++) {
5023
					if(tmp[last[i]] === 1) {
5024
						data.changed.deselected.push(last[i]);
5025
					}
5026
				}
5027
				last = data.selected.slice();
5028
			}
5029
			/**
5030
			 * triggered when selection changes (the "changed" plugin enhances the original event with more data)
5031
			 * @event
5032
			 * @name changed.jstree
5033
			 * @param {Object} node
5034
			 * @param {Object} action the action that caused the selection to change
5035
			 * @param {Array} selected the current selection
5036
			 * @param {Object} changed an object containing two properties `selected` and `deselected` - both arrays of node IDs, which were selected or deselected since the last changed event
5037
			 * @param {Object} event the event (if any) that triggered this changed event
5038
			 * @plugin changed
5039
			 */
5040
			parent.trigger.call(this, ev, data);
5041
		};
5042
		this.refresh = function (skip_loading, forget_state) {
5043
			last = [];
5044
			return parent.refresh.apply(this, arguments);
5045
		};
5046
	};
5047
 
5048
/**
5049
 * ### Checkbox plugin
5050
 *
5051
 * This plugin renders checkbox icons in front of each node, making multiple selection much easier.
5052
 * 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.
5053
 */
5054
 
5055
	var _i = document.createElement('I');
5056
	_i.className = 'jstree-icon jstree-checkbox';
5057
	_i.setAttribute('role', 'presentation');
5058
	/**
5059
	 * stores all defaults for the checkbox plugin
5060
	 * @name $.jstree.defaults.checkbox
5061
	 * @plugin checkbox
5062
	 */
5063
	$.jstree.defaults.checkbox = {
5064
		/**
5065
		 * a boolean indicating if checkboxes should be visible (can be changed at a later time using `show_checkboxes()` and `hide_checkboxes`). Defaults to `true`.
5066
		 * @name $.jstree.defaults.checkbox.visible
5067
		 * @plugin checkbox
5068
		 */
5069
		visible				: true,
5070
		/**
5071
		 * a boolean indicating if checkboxes should cascade down and have an undetermined state. Defaults to `true`.
5072
		 * @name $.jstree.defaults.checkbox.three_state
5073
		 * @plugin checkbox
5074
		 */
5075
		three_state			: true,
5076
		/**
5077
		 * a boolean indicating if clicking anywhere on the node should act as clicking on the checkbox. Defaults to `true`.
5078
		 * @name $.jstree.defaults.checkbox.whole_node
5079
		 * @plugin checkbox
5080
		 */
5081
		whole_node			: true,
5082
		/**
5083
		 * a boolean indicating if the selected style of a node should be kept, or removed. Defaults to `true`.
5084
		 * @name $.jstree.defaults.checkbox.keep_selected_style
5085
		 * @plugin checkbox
5086
		 */
5087
		keep_selected_style	: true,
5088
		/**
5089
		 * This setting controls how cascading and undetermined nodes are applied.
5090
		 * 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.
5091
		 * If `three_state` is set to `true` this setting is automatically set to 'up+down+undetermined'. Defaults to ''.
5092
		 * @name $.jstree.defaults.checkbox.cascade
5093
		 * @plugin checkbox
5094
		 */
5095
		cascade				: '',
5096
		/**
5097
		 * 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.
5098
		 * @name $.jstree.defaults.checkbox.tie_selection
5099
		 * @plugin checkbox
5100
		 */
5101
		tie_selection		: true,
5102
 
5103
		/**
5104
		 * This setting controls if cascading down affects disabled checkboxes
5105
		 * @name $.jstree.defaults.checkbox.cascade_to_disabled
5106
		 * @plugin checkbox
5107
		 */
5108
		cascade_to_disabled : true,
5109
 
5110
		/**
5111
		 * This setting controls if cascading down affects hidden checkboxes
5112
		 * @name $.jstree.defaults.checkbox.cascade_to_hidden
5113
		 * @plugin checkbox
5114
		 */
5115
		cascade_to_hidden : true
5116
	};
5117
	$.jstree.plugins.checkbox = function (options, parent) {
5118
		this.bind = function () {
5119
			parent.bind.call(this);
5120
			this._data.checkbox.uto = false;
5121
			this._data.checkbox.selected = [];
5122
			if(this.settings.checkbox.three_state) {
5123
				this.settings.checkbox.cascade = 'up+down+undetermined';
5124
			}
5125
			this.element
5126
				.on("init.jstree", function () {
5127
						this._data.checkbox.visible = this.settings.checkbox.visible;
5128
						if(!this.settings.checkbox.keep_selected_style) {
5129
							this.element.addClass('jstree-checkbox-no-clicked');
5130
						}
5131
						if(this.settings.checkbox.tie_selection) {
5132
							this.element.addClass('jstree-checkbox-selection');
5133
						}
5134
					}.bind(this))
5135
				.on("loading.jstree", function () {
5136
						this[ this._data.checkbox.visible ? 'show_checkboxes' : 'hide_checkboxes' ]();
5137
					}.bind(this));
5138
			if(this.settings.checkbox.cascade.indexOf('undetermined') !== -1) {
5139
				this.element
5140
					.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 () {
5141
							// only if undetermined is in setting
5142
							if(this._data.checkbox.uto) { clearTimeout(this._data.checkbox.uto); }
5143
							this._data.checkbox.uto = setTimeout(this._undetermined.bind(this), 50);
5144
						}.bind(this));
5145
			}
5146
			if(!this.settings.checkbox.tie_selection) {
5147
				this.element
5148
					.on('model.jstree', function (e, data) {
5149
						var m = this._model.data,
5150
							p = m[data.parent],
5151
							dpc = data.nodes,
5152
							i, j;
5153
						for(i = 0, j = dpc.length; i < j; i++) {
5154
							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);
5155
							if(m[dpc[i]].state.checked) {
5156
								this._data.checkbox.selected.push(dpc[i]);
5157
							}
5158
						}
5159
					}.bind(this));
5160
			}
5161
			if(this.settings.checkbox.cascade.indexOf('up') !== -1 || this.settings.checkbox.cascade.indexOf('down') !== -1) {
5162
				this.element
5163
					.on('model.jstree', function (e, data) {
5164
							var m = this._model.data,
5165
								p = m[data.parent],
5166
								dpc = data.nodes,
5167
								chd = [],
5168
								c, i, j, k, l, tmp, s = this.settings.checkbox.cascade, t = this.settings.checkbox.tie_selection;
5169
 
5170
							if(s.indexOf('down') !== -1) {
5171
								// apply down
5172
								if(p.state[ t ? 'selected' : 'checked' ]) {
5173
									for(i = 0, j = dpc.length; i < j; i++) {
5174
										m[dpc[i]].state[ t ? 'selected' : 'checked' ] = true;
5175
									}
5176
 
5177
									this._data[ t ? 'core' : 'checkbox' ].selected = this._data[ t ? 'core' : 'checkbox' ].selected.concat(dpc);
5178
								}
5179
								else {
5180
									for(i = 0, j = dpc.length; i < j; i++) {
5181
										if(m[dpc[i]].state[ t ? 'selected' : 'checked' ]) {
5182
											for(k = 0, l = m[dpc[i]].children_d.length; k < l; k++) {
5183
												m[m[dpc[i]].children_d[k]].state[ t ? 'selected' : 'checked' ] = true;
5184
											}
5185
											this._data[ t ? 'core' : 'checkbox' ].selected = this._data[ t ? 'core' : 'checkbox' ].selected.concat(m[dpc[i]].children_d);
5186
										}
5187
									}
5188
								}
5189
							}
5190
 
5191
							if(s.indexOf('up') !== -1) {
5192
								// apply up
5193
								for(i = 0, j = p.children_d.length; i < j; i++) {
5194
									if(!m[p.children_d[i]].children.length) {
5195
										chd.push(m[p.children_d[i]].parent);
5196
									}
5197
								}
5198
								chd = $.vakata.array_unique(chd);
5199
								for(k = 0, l = chd.length; k < l; k++) {
5200
									p = m[chd[k]];
5201
									while(p && p.id !== $.jstree.root) {
5202
										c = 0;
5203
										for(i = 0, j = p.children.length; i < j; i++) {
5204
											c += m[p.children[i]].state[ t ? 'selected' : 'checked' ];
5205
										}
5206
										if(c === j) {
5207
											p.state[ t ? 'selected' : 'checked' ] = true;
5208
											this._data[ t ? 'core' : 'checkbox' ].selected.push(p.id);
5209
											tmp = this.get_node(p, true);
5210
											if(tmp && tmp.length) {
5211
												tmp.attr('aria-selected', true).children('.jstree-anchor').addClass( t ? 'jstree-clicked' : 'jstree-checked');
5212
											}
5213
										}
5214
										else {
5215
											break;
5216
										}
5217
										p = this.get_node(p.parent);
5218
									}
5219
								}
5220
							}
5221
 
5222
							this._data[ t ? 'core' : 'checkbox' ].selected = $.vakata.array_unique(this._data[ t ? 'core' : 'checkbox' ].selected);
5223
						}.bind(this))
5224
					.on(this.settings.checkbox.tie_selection ? 'select_node.jstree' : 'check_node.jstree', function (e, data) {
5225
							var self = this,
5226
								obj = data.node,
5227
								m = this._model.data,
5228
								par = this.get_node(obj.parent),
5229
								i, j, c, tmp, s = this.settings.checkbox.cascade, t = this.settings.checkbox.tie_selection,
5230
								sel = {}, cur = this._data[ t ? 'core' : 'checkbox' ].selected;
5231
 
5232
							for (i = 0, j = cur.length; i < j; i++) {
5233
								sel[cur[i]] = true;
5234
							}
5235
 
5236
							// apply down
5237
							if(s.indexOf('down') !== -1) {
5238
								//this._data[ t ? 'core' : 'checkbox' ].selected = $.vakata.array_unique(this._data[ t ? 'core' : 'checkbox' ].selected.concat(obj.children_d));
5239
								var selectedIds = this._cascade_new_checked_state(obj.id, true);
5240
								var temp = obj.children_d.concat(obj.id);
5241
								for (i = 0, j = temp.length; i < j; i++) {
5242
									if (selectedIds.indexOf(temp[i]) > -1) {
5243
										sel[temp[i]] = true;
5244
									}
5245
									else {
5246
										delete sel[temp[i]];
5247
									}
5248
								}
5249
							}
5250
 
5251
							// apply up
5252
							if(s.indexOf('up') !== -1) {
5253
								while(par && par.id !== $.jstree.root) {
5254
									c = 0;
5255
									for(i = 0, j = par.children.length; i < j; i++) {
5256
										c += m[par.children[i]].state[ t ? 'selected' : 'checked' ];
5257
									}
5258
									if(c === j) {
5259
										par.state[ t ? 'selected' : 'checked' ] = true;
5260
										sel[par.id] = true;
5261
										//this._data[ t ? 'core' : 'checkbox' ].selected.push(par.id);
5262
										tmp = this.get_node(par, true);
5263
										if(tmp && tmp.length) {
5264
											tmp.attr('aria-selected', true).children('.jstree-anchor').addClass(t ? 'jstree-clicked' : 'jstree-checked');
5265
										}
5266
									}
5267
									else {
5268
										break;
5269
									}
5270
									par = this.get_node(par.parent);
5271
								}
5272
							}
5273
 
5274
							cur = [];
5275
							for (i in sel) {
5276
								if (sel.hasOwnProperty(i)) {
5277
									cur.push(i);
5278
								}
5279
							}
5280
							this._data[ t ? 'core' : 'checkbox' ].selected = cur;
5281
						}.bind(this))
5282
					.on(this.settings.checkbox.tie_selection ? 'deselect_all.jstree' : 'uncheck_all.jstree', function (e, data) {
5283
							var obj = this.get_node($.jstree.root),
5284
								m = this._model.data,
5285
								i, j, tmp;
5286
							for(i = 0, j = obj.children_d.length; i < j; i++) {
5287
								tmp = m[obj.children_d[i]];
5288
								if(tmp && tmp.original && tmp.original.state && tmp.original.state.undetermined) {
5289
									tmp.original.state.undetermined = false;
5290
								}
5291
							}
5292
						}.bind(this))
5293
					.on(this.settings.checkbox.tie_selection ? 'deselect_node.jstree' : 'uncheck_node.jstree', function (e, data) {
5294
							var self = this,
5295
								obj = data.node,
5296
								dom = this.get_node(obj, true),
5297
								i, j, tmp, s = this.settings.checkbox.cascade, t = this.settings.checkbox.tie_selection,
5298
								cur = this._data[ t ? 'core' : 'checkbox' ].selected, sel = {},
5299
								stillSelectedIds = [],
5300
								allIds = obj.children_d.concat(obj.id);
5301
 
5302
							// apply down
5303
							if(s.indexOf('down') !== -1) {
5304
								var selectedIds = this._cascade_new_checked_state(obj.id, false);
5305
 
5306
								cur = $.vakata.array_filter(cur, function(id) {
5307
									return allIds.indexOf(id) === -1 || selectedIds.indexOf(id) > -1;
5308
								});
5309
							}
5310
 
5311
							// only apply up if cascade up is enabled and if this node is not selected
5312
							// (if all child nodes are disabled and cascade_to_disabled === false then this node will till be selected).
5313
							if(s.indexOf('up') !== -1 && cur.indexOf(obj.id) === -1) {
5314
								for(i = 0, j = obj.parents.length; i < j; i++) {
5315
									tmp = this._model.data[obj.parents[i]];
5316
									tmp.state[ t ? 'selected' : 'checked' ] = false;
5317
									if(tmp && tmp.original && tmp.original.state && tmp.original.state.undetermined) {
5318
										tmp.original.state.undetermined = false;
5319
									}
5320
									tmp = this.get_node(obj.parents[i], true);
5321
									if(tmp && tmp.length) {
5322
										tmp.attr('aria-selected', false).children('.jstree-anchor').removeClass(t ? 'jstree-clicked' : 'jstree-checked');
5323
									}
5324
								}
5325
 
5326
								cur = $.vakata.array_filter(cur, function(id) {
5327
									return obj.parents.indexOf(id) === -1;
5328
								});
5329
							}
5330
 
5331
							this._data[ t ? 'core' : 'checkbox' ].selected = cur;
5332
						}.bind(this));
5333
			}
5334
			if(this.settings.checkbox.cascade.indexOf('up') !== -1) {
5335
				this.element
5336
					.on('delete_node.jstree', function (e, data) {
5337
							// apply up (whole handler)
5338
							var p = this.get_node(data.parent),
5339
								m = this._model.data,
5340
								i, j, c, tmp, t = this.settings.checkbox.tie_selection;
5341
							while(p && p.id !== $.jstree.root && !p.state[ t ? 'selected' : 'checked' ]) {
5342
								c = 0;
5343
								for(i = 0, j = p.children.length; i < j; i++) {
5344
									c += m[p.children[i]].state[ t ? 'selected' : 'checked' ];
5345
								}
5346
								if(j > 0 && c === j) {
5347
									p.state[ t ? 'selected' : 'checked' ] = true;
5348
									this._data[ t ? 'core' : 'checkbox' ].selected.push(p.id);
5349
									tmp = this.get_node(p, true);
5350
									if(tmp && tmp.length) {
5351
										tmp.attr('aria-selected', true).children('.jstree-anchor').addClass(t ? 'jstree-clicked' : 'jstree-checked');
5352
									}
5353
								}
5354
								else {
5355
									break;
5356
								}
5357
								p = this.get_node(p.parent);
5358
							}
5359
						}.bind(this))
5360
					.on('move_node.jstree', function (e, data) {
5361
							// apply up (whole handler)
5362
							var is_multi = data.is_multi,
5363
								old_par = data.old_parent,
5364
								new_par = this.get_node(data.parent),
5365
								m = this._model.data,
5366
								p, c, i, j, tmp, t = this.settings.checkbox.tie_selection;
5367
							if(!is_multi) {
5368
								p = this.get_node(old_par);
5369
								while(p && p.id !== $.jstree.root && !p.state[ t ? 'selected' : 'checked' ]) {
5370
									c = 0;
5371
									for(i = 0, j = p.children.length; i < j; i++) {
5372
										c += m[p.children[i]].state[ t ? 'selected' : 'checked' ];
5373
									}
5374
									if(j > 0 && c === j) {
5375
										p.state[ t ? 'selected' : 'checked' ] = true;
5376
										this._data[ t ? 'core' : 'checkbox' ].selected.push(p.id);
5377
										tmp = this.get_node(p, true);
5378
										if(tmp && tmp.length) {
5379
											tmp.attr('aria-selected', true).children('.jstree-anchor').addClass(t ? 'jstree-clicked' : 'jstree-checked');
5380
										}
5381
									}
5382
									else {
5383
										break;
5384
									}
5385
									p = this.get_node(p.parent);
5386
								}
5387
							}
5388
							p = new_par;
5389
							while(p && p.id !== $.jstree.root) {
5390
								c = 0;
5391
								for(i = 0, j = p.children.length; i < j; i++) {
5392
									c += m[p.children[i]].state[ t ? 'selected' : 'checked' ];
5393
								}
5394
								if(c === j) {
5395
									if(!p.state[ t ? 'selected' : 'checked' ]) {
5396
										p.state[ t ? 'selected' : 'checked' ] = true;
5397
										this._data[ t ? 'core' : 'checkbox' ].selected.push(p.id);
5398
										tmp = this.get_node(p, true);
5399
										if(tmp && tmp.length) {
5400
											tmp.attr('aria-selected', true).children('.jstree-anchor').addClass(t ? 'jstree-clicked' : 'jstree-checked');
5401
										}
5402
									}
5403
								}
5404
								else {
5405
									if(p.state[ t ? 'selected' : 'checked' ]) {
5406
										p.state[ t ? 'selected' : 'checked' ] = false;
5407
										this._data[ t ? 'core' : 'checkbox' ].selected = $.vakata.array_remove_item(this._data[ t ? 'core' : 'checkbox' ].selected, p.id);
5408
										tmp = this.get_node(p, true);
5409
										if(tmp && tmp.length) {
5410
											tmp.attr('aria-selected', false).children('.jstree-anchor').removeClass(t ? 'jstree-clicked' : 'jstree-checked');
5411
										}
5412
									}
5413
									else {
5414
										break;
5415
									}
5416
								}
5417
								p = this.get_node(p.parent);
5418
							}
5419
						}.bind(this));
5420
			}
5421
		};
5422
		/**
5423
		 * get an array of all nodes whose state is "undetermined"
5424
		 * @name get_undetermined([full])
5425
		 * @param  {boolean} full: if set to `true` the returned array will consist of the full node objects, otherwise - only IDs will be returned
5426
		 * @return {Array}
5427
		 * @plugin checkbox
5428
		 */
5429
		this.get_undetermined = function (full) {
5430
			if (this.settings.checkbox.cascade.indexOf('undetermined') === -1) {
5431
				return [];
5432
			}
5433
			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 = [];
5434
			for(i = 0, j = s.length; i < j; i++) {
5435
				if(m[s[i]] && m[s[i]].parents) {
5436
					for(k = 0, l = m[s[i]].parents.length; k < l; k++) {
5437
						if(o[m[s[i]].parents[k]] !== undefined) {
5438
							break;
5439
						}
5440
						if(m[s[i]].parents[k] !== $.jstree.root) {
5441
							o[m[s[i]].parents[k]] = true;
5442
							p.push(m[s[i]].parents[k]);
5443
						}
5444
					}
5445
				}
5446
			}
5447
			// attempt for server side undetermined state
5448
			this.element.find('.jstree-closed').not(':has(.jstree-children)')
5449
				.each(function () {
5450
					var tmp = tt.get_node(this), tmp2;
5451
 
5452
					if(!tmp) { return; }
5453
 
5454
					if(!tmp.state.loaded) {
5455
						if(tmp.original && tmp.original.state && tmp.original.state.undetermined && tmp.original.state.undetermined === true) {
5456
							if(o[tmp.id] === undefined && tmp.id !== $.jstree.root) {
5457
								o[tmp.id] = true;
5458
								p.push(tmp.id);
5459
							}
5460
							for(k = 0, l = tmp.parents.length; k < l; k++) {
5461
								if(o[tmp.parents[k]] === undefined && tmp.parents[k] !== $.jstree.root) {
5462
									o[tmp.parents[k]] = true;
5463
									p.push(tmp.parents[k]);
5464
								}
5465
							}
5466
						}
5467
					}
5468
					else {
5469
						for(i = 0, j = tmp.children_d.length; i < j; i++) {
5470
							tmp2 = m[tmp.children_d[i]];
5471
							if(!tmp2.state.loaded && tmp2.original && tmp2.original.state && tmp2.original.state.undetermined && tmp2.original.state.undetermined === true) {
5472
								if(o[tmp2.id] === undefined && tmp2.id !== $.jstree.root) {
5473
									o[tmp2.id] = true;
5474
									p.push(tmp2.id);
5475
								}
5476
								for(k = 0, l = tmp2.parents.length; k < l; k++) {
5477
									if(o[tmp2.parents[k]] === undefined && tmp2.parents[k] !== $.jstree.root) {
5478
										o[tmp2.parents[k]] = true;
5479
										p.push(tmp2.parents[k]);
5480
									}
5481
								}
5482
							}
5483
						}
5484
					}
5485
				});
5486
			for (i = 0, j = p.length; i < j; i++) {
5487
				if(!m[p[i]].state[ t ? 'selected' : 'checked' ]) {
5488
					r.push(full ? m[p[i]] : p[i]);
5489
				}
5490
			}
5491
			return r;
5492
		};
5493
		/**
5494
		 * set the undetermined state where and if necessary. Used internally.
5495
		 * @private
5496
		 * @name _undetermined()
5497
		 * @plugin checkbox
5498
		 */
5499
		this._undetermined = function () {
5500
			if(this.element === null) { return; }
5501
			var p = this.get_undetermined(false), i, j, s;
5502
 
5503
			this.element.find('.jstree-undetermined').removeClass('jstree-undetermined');
5504
			for (i = 0, j = p.length; i < j; i++) {
5505
				s = this.get_node(p[i], true);
5506
				if(s && s.length) {
5507
					s.children('.jstree-anchor').children('.jstree-checkbox').addClass('jstree-undetermined');
5508
				}
5509
			}
5510
		};
5511
		this.redraw_node = function(obj, deep, is_callback, force_render) {
5512
			obj = parent.redraw_node.apply(this, arguments);
5513
			if(obj) {
5514
				var i, j, tmp = null, icon = null;
5515
				for(i = 0, j = obj.childNodes.length; i < j; i++) {
5516
					if(obj.childNodes[i] && obj.childNodes[i].className && obj.childNodes[i].className.indexOf("jstree-anchor") !== -1) {
5517
						tmp = obj.childNodes[i];
5518
						break;
5519
					}
5520
				}
5521
				if(tmp) {
5522
					if(!this.settings.checkbox.tie_selection && this._model.data[obj.id].state.checked) { tmp.className += ' jstree-checked'; }
5523
					icon = _i.cloneNode(false);
5524
					if(this._model.data[obj.id].state.checkbox_disabled) { icon.className += ' jstree-checkbox-disabled'; }
5525
					tmp.insertBefore(icon, tmp.childNodes[0]);
5526
				}
5527
			}
5528
			if(!is_callback && this.settings.checkbox.cascade.indexOf('undetermined') !== -1) {
5529
				if(this._data.checkbox.uto) { clearTimeout(this._data.checkbox.uto); }
5530
				this._data.checkbox.uto = setTimeout(this._undetermined.bind(this), 50);
5531
			}
5532
			return obj;
5533
		};
5534
		/**
5535
		 * show the node checkbox icons
5536
		 * @name show_checkboxes()
5537
		 * @plugin checkbox
5538
		 */
5539
		this.show_checkboxes = function () { this._data.core.themes.checkboxes = true; this.get_container_ul().removeClass("jstree-no-checkboxes"); };
5540
		/**
5541
		 * hide the node checkbox icons
5542
		 * @name hide_checkboxes()
5543
		 * @plugin checkbox
5544
		 */
5545
		this.hide_checkboxes = function () { this._data.core.themes.checkboxes = false; this.get_container_ul().addClass("jstree-no-checkboxes"); };
5546
		/**
5547
		 * toggle the node icons
5548
		 * @name toggle_checkboxes()
5549
		 * @plugin checkbox
5550
		 */
5551
		this.toggle_checkboxes = function () { if(this._data.core.themes.checkboxes) { this.hide_checkboxes(); } else { this.show_checkboxes(); } };
5552
		/**
5553
		 * checks if a node is in an undetermined state
5554
		 * @name is_undetermined(obj)
5555
		 * @param  {mixed} obj
5556
		 * @return {Boolean}
5557
		 */
5558
		this.is_undetermined = function (obj) {
5559
			obj = this.get_node(obj);
5560
			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;
5561
			if(!obj || obj.state[ t ? 'selected' : 'checked' ] === true || s.indexOf('undetermined') === -1 || (s.indexOf('down') === -1 && s.indexOf('up') === -1)) {
5562
				return false;
5563
			}
5564
			if(!obj.state.loaded && obj.original.state.undetermined === true) {
5565
				return true;
5566
			}
5567
			for(i = 0, j = obj.children_d.length; i < j; i++) {
5568
				if($.inArray(obj.children_d[i], d) !== -1 || (!m[obj.children_d[i]].state.loaded && m[obj.children_d[i]].original.state.undetermined)) {
5569
					return true;
5570
				}
5571
			}
5572
			return false;
5573
		};
5574
		/**
5575
		 * disable a node's checkbox
5576
		 * @name disable_checkbox(obj)
5577
		 * @param {mixed} obj an array can be used too
5578
		 * @trigger disable_checkbox.jstree
5579
		 * @plugin checkbox
5580
		 */
5581
		this.disable_checkbox = function (obj) {
5582
			var t1, t2, dom;
5583
			if($.vakata.is_array(obj)) {
5584
				obj = obj.slice();
5585
				for(t1 = 0, t2 = obj.length; t1 < t2; t1++) {
5586
					this.disable_checkbox(obj[t1]);
5587
				}
5588
				return true;
5589
			}
5590
			obj = this.get_node(obj);
5591
			if(!obj || obj.id === $.jstree.root) {
5592
				return false;
5593
			}
5594
			dom = this.get_node(obj, true);
5595
			if(!obj.state.checkbox_disabled) {
5596
				obj.state.checkbox_disabled = true;
5597
				if(dom && dom.length) {
5598
					dom.children('.jstree-anchor').children('.jstree-checkbox').addClass('jstree-checkbox-disabled');
5599
				}
5600
				/**
5601
				 * triggered when an node's checkbox is disabled
5602
				 * @event
5603
				 * @name disable_checkbox.jstree
5604
				 * @param {Object} node
5605
				 * @plugin checkbox
5606
				 */
5607
				this.trigger('disable_checkbox', { 'node' : obj });
5608
			}
5609
		};
5610
		/**
5611
		 * enable a node's checkbox
5612
		 * @name enable_checkbox(obj)
5613
		 * @param {mixed} obj an array can be used too
5614
		 * @trigger enable_checkbox.jstree
5615
		 * @plugin checkbox
5616
		 */
5617
		this.enable_checkbox = function (obj) {
5618
			var t1, t2, dom;
5619
			if($.vakata.is_array(obj)) {
5620
				obj = obj.slice();
5621
				for(t1 = 0, t2 = obj.length; t1 < t2; t1++) {
5622
					this.enable_checkbox(obj[t1]);
5623
				}
5624
				return true;
5625
			}
5626
			obj = this.get_node(obj);
5627
			if(!obj || obj.id === $.jstree.root) {
5628
				return false;
5629
			}
5630
			dom = this.get_node(obj, true);
5631
			if(obj.state.checkbox_disabled) {
5632
				obj.state.checkbox_disabled = false;
5633
				if(dom && dom.length) {
5634
					dom.children('.jstree-anchor').children('.jstree-checkbox').removeClass('jstree-checkbox-disabled');
5635
				}
5636
				/**
5637
				 * triggered when an node's checkbox is enabled
5638
				 * @event
5639
				 * @name enable_checkbox.jstree
5640
				 * @param {Object} node
5641
				 * @plugin checkbox
5642
				 */
5643
				this.trigger('enable_checkbox', { 'node' : obj });
5644
			}
5645
		};
5646
 
5647
		this.activate_node = function (obj, e) {
5648
			if($(e.target).hasClass('jstree-checkbox-disabled')) {
5649
				return false;
5650
			}
5651
			if(this.settings.checkbox.tie_selection && (this.settings.checkbox.whole_node || $(e.target).hasClass('jstree-checkbox'))) {
5652
				e.ctrlKey = true;
5653
			}
5654
			if(this.settings.checkbox.tie_selection || (!this.settings.checkbox.whole_node && !$(e.target).hasClass('jstree-checkbox'))) {
5655
				return parent.activate_node.call(this, obj, e);
5656
			}
5657
			if(this.is_disabled(obj)) {
5658
				return false;
5659
			}
5660
			if(this.is_checked(obj)) {
5661
				this.uncheck_node(obj, e);
5662
			}
5663
			else {
5664
				this.check_node(obj, e);
5665
			}
5666
			this.trigger('activate_node', { 'node' : this.get_node(obj) });
5667
		};
5668
 
5669
		/**
5670
		 * Cascades checked state to a node and all its descendants. This function does NOT affect hidden and disabled nodes (or their descendants).
5671
		 * However if these unaffected nodes are already selected their ids will be included in the returned array.
5672
		 * @private
5673
		 * @name _cascade_new_checked_state(id, checkedState)
5674
		 * @param {string} id the node ID
5675
		 * @param {bool} checkedState should the nodes be checked or not
5676
		 * @returns {Array} Array of all node id's (in this tree branch) that are checked.
5677
		 */
5678
		this._cascade_new_checked_state = function (id, checkedState) {
5679
			var self = this;
5680
			var t = this.settings.checkbox.tie_selection;
5681
			var node = this._model.data[id];
5682
			var selectedNodeIds = [];
5683
			var selectedChildrenIds = [], i, j, selectedChildIds;
5684
 
5685
			if (
5686
				(this.settings.checkbox.cascade_to_disabled || !node.state.disabled) &&
5687
				(this.settings.checkbox.cascade_to_hidden || !node.state.hidden)
5688
			) {
5689
				//First try and check/uncheck the children
5690
				if (node.children) {
5691
					for (i = 0, j = node.children.length; i < j; i++) {
5692
						var childId = node.children[i];
5693
						selectedChildIds = self._cascade_new_checked_state(childId, checkedState);
5694
						selectedNodeIds = selectedNodeIds.concat(selectedChildIds);
5695
						if (selectedChildIds.indexOf(childId) > -1) {
5696
							selectedChildrenIds.push(childId);
5697
						}
5698
					}
5699
				}
5700
 
5701
				var dom = self.get_node(node, true);
5702
 
5703
				//A node's state is undetermined if some but not all of it's children are checked/selected .
5704
				var undetermined = selectedChildrenIds.length > 0 && selectedChildrenIds.length < node.children.length;
5705
 
5706
				if(node.original && node.original.state && node.original.state.undetermined) {
5707
					node.original.state.undetermined = undetermined;
5708
				}
5709
 
5710
				//If a node is undetermined then remove selected class
5711
				if (undetermined) {
5712
					node.state[ t ? 'selected' : 'checked' ] = false;
5713
					dom.attr('aria-selected', false).children('.jstree-anchor').removeClass(t ? 'jstree-clicked' : 'jstree-checked');
5714
				}
5715
				//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),
5716
				//check the node and style it correctly.
5717
				else if (checkedState && selectedChildrenIds.length === node.children.length) {
5718
					node.state[ t ? 'selected' : 'checked' ] = checkedState;
5719
					selectedNodeIds.push(node.id);
5720
 
5721
					dom.attr('aria-selected', true).children('.jstree-anchor').addClass(t ? 'jstree-clicked' : 'jstree-checked');
5722
				}
5723
				else {
5724
					node.state[ t ? 'selected' : 'checked' ] = false;
5725
					dom.attr('aria-selected', false).children('.jstree-anchor').removeClass(t ? 'jstree-clicked' : 'jstree-checked');
5726
				}
5727
			}
5728
			else {
5729
				selectedChildIds = this.get_checked_descendants(id);
5730
 
5731
				if (node.state[ t ? 'selected' : 'checked' ]) {
5732
					selectedChildIds.push(node.id);
5733
				}
5734
 
5735
				selectedNodeIds = selectedNodeIds.concat(selectedChildIds);
5736
			}
5737
 
5738
			return selectedNodeIds;
5739
		};
5740
 
5741
		/**
5742
		 * Gets ids of nodes selected in branch (of tree) specified by id (does not include the node specified by id)
5743
		 * @name get_checked_descendants(obj)
5744
		 * @param {string} id the node ID
5745
		 * @return {Array} array of IDs
5746
		 * @plugin checkbox
5747
		 */
5748
		this.get_checked_descendants = function (id) {
5749
			var self = this;
5750
			var t = self.settings.checkbox.tie_selection;
5751
			var node = self._model.data[id];
5752
 
5753
			return $.vakata.array_filter(node.children_d, function(_id) {
5754
				return self._model.data[_id].state[ t ? 'selected' : 'checked' ];
5755
			});
5756
		};
5757
 
5758
		/**
5759
		 * check a node (only if tie_selection in checkbox settings is false, otherwise select_node will be called internally)
5760
		 * @name check_node(obj)
5761
		 * @param {mixed} obj an array can be used to check multiple nodes
5762
		 * @trigger check_node.jstree
5763
		 * @plugin checkbox
5764
		 */
5765
		this.check_node = function (obj, e) {
5766
			if(this.settings.checkbox.tie_selection) { return this.select_node(obj, false, true, e); }
5767
			var dom, t1, t2, th;
5768
			if($.vakata.is_array(obj)) {
5769
				obj = obj.slice();
5770
				for(t1 = 0, t2 = obj.length; t1 < t2; t1++) {
5771
					this.check_node(obj[t1], e);
5772
				}
5773
				return true;
5774
			}
5775
			obj = this.get_node(obj);
5776
			if(!obj || obj.id === $.jstree.root) {
5777
				return false;
5778
			}
5779
			dom = this.get_node(obj, true);
5780
			if(!obj.state.checked) {
5781
				obj.state.checked = true;
5782
				this._data.checkbox.selected.push(obj.id);
5783
				if(dom && dom.length) {
5784
					dom.children('.jstree-anchor').addClass('jstree-checked');
5785
				}
5786
				/**
5787
				 * triggered when an node is checked (only if tie_selection in checkbox settings is false)
5788
				 * @event
5789
				 * @name check_node.jstree
5790
				 * @param {Object} node
5791
				 * @param {Array} selected the current selection
5792
				 * @param {Object} event the event (if any) that triggered this check_node
5793
				 * @plugin checkbox
5794
				 */
5795
				this.trigger('check_node', { 'node' : obj, 'selected' : this._data.checkbox.selected, 'event' : e });
5796
			}
5797
		};
5798
		/**
5799
		 * uncheck a node (only if tie_selection in checkbox settings is false, otherwise deselect_node will be called internally)
5800
		 * @name uncheck_node(obj)
5801
		 * @param {mixed} obj an array can be used to uncheck multiple nodes
5802
		 * @trigger uncheck_node.jstree
5803
		 * @plugin checkbox
5804
		 */
5805
		this.uncheck_node = function (obj, e) {
5806
			if(this.settings.checkbox.tie_selection) { return this.deselect_node(obj, false, e); }
5807
			var t1, t2, dom;
5808
			if($.vakata.is_array(obj)) {
5809
				obj = obj.slice();
5810
				for(t1 = 0, t2 = obj.length; t1 < t2; t1++) {
5811
					this.uncheck_node(obj[t1], e);
5812
				}
5813
				return true;
5814
			}
5815
			obj = this.get_node(obj);
5816
			if(!obj || obj.id === $.jstree.root) {
5817
				return false;
5818
			}
5819
			dom = this.get_node(obj, true);
5820
			if(obj.state.checked) {
5821
				obj.state.checked = false;
5822
				this._data.checkbox.selected = $.vakata.array_remove_item(this._data.checkbox.selected, obj.id);
5823
				if(dom.length) {
5824
					dom.children('.jstree-anchor').removeClass('jstree-checked');
5825
				}
5826
				/**
5827
				 * triggered when an node is unchecked (only if tie_selection in checkbox settings is false)
5828
				 * @event
5829
				 * @name uncheck_node.jstree
5830
				 * @param {Object} node
5831
				 * @param {Array} selected the current selection
5832
				 * @param {Object} event the event (if any) that triggered this uncheck_node
5833
				 * @plugin checkbox
5834
				 */
5835
				this.trigger('uncheck_node', { 'node' : obj, 'selected' : this._data.checkbox.selected, 'event' : e });
5836
			}
5837
		};
5838
 
5839
		/**
5840
		 * checks all nodes in the tree (only if tie_selection in checkbox settings is false, otherwise select_all will be called internally)
5841
		 * @name check_all()
5842
		 * @trigger check_all.jstree, changed.jstree
5843
		 * @plugin checkbox
5844
		 */
5845
		this.check_all = function () {
5846
			if(this.settings.checkbox.tie_selection) { return this.select_all(); }
5847
			var tmp = this._data.checkbox.selected.concat([]), i, j;
5848
			this._data.checkbox.selected = this._model.data[$.jstree.root].children_d.concat();
5849
			for(i = 0, j = this._data.checkbox.selected.length; i < j; i++) {
5850
				if(this._model.data[this._data.checkbox.selected[i]]) {
5851
					this._model.data[this._data.checkbox.selected[i]].state.checked = true;
5852
				}
5853
			}
5854
			this.redraw(true);
5855
			/**
5856
			 * triggered when all nodes are checked (only if tie_selection in checkbox settings is false)
5857
			 * @event
5858
			 * @name check_all.jstree
5859
			 * @param {Array} selected the current selection
5860
			 * @plugin checkbox
5861
			 */
5862
			this.trigger('check_all', { 'selected' : this._data.checkbox.selected });
5863
		};
5864
		/**
5865
		 * uncheck all checked nodes (only if tie_selection in checkbox settings is false, otherwise deselect_all will be called internally)
5866
		 * @name uncheck_all()
5867
		 * @trigger uncheck_all.jstree
5868
		 * @plugin checkbox
5869
		 */
5870
		this.uncheck_all = function () {
5871
			if(this.settings.checkbox.tie_selection) { return this.deselect_all(); }
5872
			var tmp = this._data.checkbox.selected.concat([]), i, j;
5873
			for(i = 0, j = this._data.checkbox.selected.length; i < j; i++) {
5874
				if(this._model.data[this._data.checkbox.selected[i]]) {
5875
					this._model.data[this._data.checkbox.selected[i]].state.checked = false;
5876
				}
5877
			}
5878
			this._data.checkbox.selected = [];
5879
			this.element.find('.jstree-checked').removeClass('jstree-checked');
5880
			/**
5881
			 * triggered when all nodes are unchecked (only if tie_selection in checkbox settings is false)
5882
			 * @event
5883
			 * @name uncheck_all.jstree
5884
			 * @param {Object} node the previous selection
5885
			 * @param {Array} selected the current selection
5886
			 * @plugin checkbox
5887
			 */
5888
			this.trigger('uncheck_all', { 'selected' : this._data.checkbox.selected, 'node' : tmp });
5889
		};
5890
		/**
5891
		 * checks if a node is checked (if tie_selection is on in the settings this function will return the same as is_selected)
5892
		 * @name is_checked(obj)
5893
		 * @param  {mixed}  obj
5894
		 * @return {Boolean}
5895
		 * @plugin checkbox
5896
		 */
5897
		this.is_checked = function (obj) {
5898
			if(this.settings.checkbox.tie_selection) { return this.is_selected(obj); }
5899
			obj = this.get_node(obj);
5900
			if(!obj || obj.id === $.jstree.root) { return false; }
5901
			return obj.state.checked;
5902
		};
5903
		/**
5904
		 * get an array of all checked nodes (if tie_selection is on in the settings this function will return the same as get_selected)
5905
		 * @name get_checked([full])
5906
		 * @param  {mixed}  full if set to `true` the returned array will consist of the full node objects, otherwise - only IDs will be returned
5907
		 * @return {Array}
5908
		 * @plugin checkbox
5909
		 */
5910
		this.get_checked = function (full) {
5911
			if(this.settings.checkbox.tie_selection) { return this.get_selected(full); }
5912
			return full ? $.map(this._data.checkbox.selected, function (i) { return this.get_node(i); }.bind(this)) : this._data.checkbox.selected.slice();
5913
		};
5914
		/**
5915
		 * 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)
5916
		 * @name get_top_checked([full])
5917
		 * @param  {mixed}  full if set to `true` the returned array will consist of the full node objects, otherwise - only IDs will be returned
5918
		 * @return {Array}
5919
		 * @plugin checkbox
5920
		 */
5921
		this.get_top_checked = function (full) {
5922
			if(this.settings.checkbox.tie_selection) { return this.get_top_selected(full); }
5923
			var tmp = this.get_checked(true),
5924
				obj = {}, i, j, k, l;
5925
			for(i = 0, j = tmp.length; i < j; i++) {
5926
				obj[tmp[i].id] = tmp[i];
5927
			}
5928
			for(i = 0, j = tmp.length; i < j; i++) {
5929
				for(k = 0, l = tmp[i].children_d.length; k < l; k++) {
5930
					if(obj[tmp[i].children_d[k]]) {
5931
						delete obj[tmp[i].children_d[k]];
5932
					}
5933
				}
5934
			}
5935
			tmp = [];
5936
			for(i in obj) {
5937
				if(obj.hasOwnProperty(i)) {
5938
					tmp.push(i);
5939
				}
5940
			}
5941
			return full ? $.map(tmp, function (i) { return this.get_node(i); }.bind(this)) : tmp;
5942
		};
5943
		/**
5944
		 * 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)
5945
		 * @name get_bottom_checked([full])
5946
		 * @param  {mixed}  full if set to `true` the returned array will consist of the full node objects, otherwise - only IDs will be returned
5947
		 * @return {Array}
5948
		 * @plugin checkbox
5949
		 */
5950
		this.get_bottom_checked = function (full) {
5951
			if(this.settings.checkbox.tie_selection) { return this.get_bottom_selected(full); }
5952
			var tmp = this.get_checked(true),
5953
				obj = [], i, j;
5954
			for(i = 0, j = tmp.length; i < j; i++) {
5955
				if(!tmp[i].children.length) {
5956
					obj.push(tmp[i].id);
5957
				}
5958
			}
5959
			return full ? $.map(obj, function (i) { return this.get_node(i); }.bind(this)) : obj;
5960
		};
5961
		this.load_node = function (obj, callback) {
5962
			var k, l, i, j, c, tmp;
5963
			if(!$.vakata.is_array(obj) && !this.settings.checkbox.tie_selection) {
5964
				tmp = this.get_node(obj);
5965
				if(tmp && tmp.state.loaded) {
5966
					for(k = 0, l = tmp.children_d.length; k < l; k++) {
5967
						if(this._model.data[tmp.children_d[k]].state.checked) {
5968
							c = true;
5969
							this._data.checkbox.selected = $.vakata.array_remove_item(this._data.checkbox.selected, tmp.children_d[k]);
5970
						}
5971
					}
5972
				}
5973
			}
5974
			return parent.load_node.apply(this, arguments);
5975
		};
5976
		this.get_state = function () {
5977
			var state = parent.get_state.apply(this, arguments);
5978
			if(this.settings.checkbox.tie_selection) { return state; }
5979
			state.checkbox = this._data.checkbox.selected.slice();
5980
			return state;
5981
		};
5982
		this.set_state = function (state, callback) {
5983
			var res = parent.set_state.apply(this, arguments);
5984
			if(res && state.checkbox) {
5985
				if(!this.settings.checkbox.tie_selection) {
5986
					this.uncheck_all();
5987
					var _this = this;
5988
					$.each(state.checkbox, function (i, v) {
5989
						_this.check_node(v);
5990
					});
5991
				}
5992
				delete state.checkbox;
5993
				this.set_state(state, callback);
5994
				return false;
5995
			}
5996
			return res;
5997
		};
5998
		this.refresh = function (skip_loading, forget_state) {
5999
			if(this.settings.checkbox.tie_selection) {
6000
				this._data.checkbox.selected = [];
6001
			}
6002
			return parent.refresh.apply(this, arguments);
6003
		};
6004
	};
6005
 
6006
	// include the checkbox plugin by default
6007
	// $.jstree.defaults.plugins.push("checkbox");
6008
 
6009
 
6010
/**
6011
 * ### Conditionalselect plugin
6012
 *
6013
 * This plugin allows defining a callback to allow or deny node selection by user input (activate node method).
6014
 */
6015
 
6016
	/**
6017
	 * a callback (function) which is invoked in the instance's scope and receives two arguments - the node and the event that triggered the `activate_node` call. Returning false prevents working with the node, returning true allows invoking activate_node. Defaults to returning `true`.
6018
	 * @name $.jstree.defaults.checkbox.visible
6019
	 * @plugin checkbox
6020
	 */
6021
	$.jstree.defaults.conditionalselect = function () { return true; };
6022
	$.jstree.plugins.conditionalselect = function (options, parent) {
6023
		// own function
6024
		this.activate_node = function (obj, e) {
6025
			if(this.settings.conditionalselect.call(this, this.get_node(obj), e)) {
6026
				return parent.activate_node.call(this, obj, e);
6027
			}
6028
		};
6029
	};
6030
 
6031
 
6032
/**
6033
 * ### Contextmenu plugin
6034
 *
6035
 * Shows a context menu when a node is right-clicked.
6036
 */
6037
 
6038
	/**
6039
	 * stores all defaults for the contextmenu plugin
6040
	 * @name $.jstree.defaults.contextmenu
6041
	 * @plugin contextmenu
6042
	 */
6043
	$.jstree.defaults.contextmenu = {
6044
		/**
6045
		 * a boolean indicating if the node should be selected when the context menu is invoked on it. Defaults to `true`.
6046
		 * @name $.jstree.defaults.contextmenu.select_node
6047
		 * @plugin contextmenu
6048
		 */
6049
		select_node : true,
6050
		/**
6051
		 * a boolean indicating if the menu should be shown aligned with the node. Defaults to `true`, otherwise the mouse coordinates are used.
6052
		 * @name $.jstree.defaults.contextmenu.show_at_node
6053
		 * @plugin contextmenu
6054
		 */
6055
		show_at_node : true,
6056
		/**
6057
		 * an object of actions, or a function that accepts a node and a callback function and calls the callback function with an object of actions available for that node (you can also return the items too).
6058
		 *
6059
		 * Each action consists of a key (a unique name) and a value which is an object with the following properties (only label and action are required). Once a menu item is activated the `action` function will be invoked with an object containing the following keys: item - the contextmenu item definition as seen below, reference - the DOM node that was used (the tree node), element - the contextmenu DOM element, position - an object with x/y properties indicating the position of the menu.
6060
		 *
6061
		 * * `separator_before` - a boolean indicating if there should be a separator before this item
6062
		 * * `separator_after` - a boolean indicating if there should be a separator after this item
6063
		 * * `_disabled` - a boolean indicating if this action should be disabled
6064
		 * * `label` - a string - the name of the action (could be a function returning a string)
6065
		 * * `title` - a string - an optional tooltip for the item
6066
		 * * `action` - a function to be executed if this item is chosen, the function will receive
6067
		 * * `icon` - a string, can be a path to an icon or a className, if using an image that is in the current directory use a `./` prefix, otherwise it will be detected as a class
6068
		 * * `shortcut` - keyCode which will trigger the action if the menu is open (for example `113` for rename, which equals F2)
6069
		 * * `shortcut_label` - shortcut label (like for example `F2` for rename)
6070
		 * * `submenu` - an object with the same structure as $.jstree.defaults.contextmenu.items which can be used to create a submenu - each key will be rendered as a separate option in a submenu that will appear once the current item is hovered
6071
		 *
6072
		 * @name $.jstree.defaults.contextmenu.items
6073
		 * @plugin contextmenu
6074
		 */
6075
		items : function (o, cb) { // Could be an object directly
6076
			return {
6077
				"create" : {
6078
					"separator_before"	: false,
6079
					"separator_after"	: true,
6080
					"_disabled"			: false, //(this.check("create_node", data.reference, {}, "last")),
6081
					"label"				: "Create",
6082
					"action"			: function (data) {
6083
						var inst = $.jstree.reference(data.reference),
6084
							obj = inst.get_node(data.reference);
6085
						inst.create_node(obj, {}, "last", function (new_node) {
6086
							try {
6087
								inst.edit(new_node);
6088
							} catch (ex) {
6089
								setTimeout(function () { inst.edit(new_node); },0);
6090
							}
6091
						});
6092
					}
6093
				},
6094
				"rename" : {
6095
					"separator_before"	: false,
6096
					"separator_after"	: false,
6097
					"_disabled"			: false, //(this.check("rename_node", data.reference, this.get_parent(data.reference), "")),
6098
					"label"				: "Rename",
6099
					/*!
6100
					"shortcut"			: 113,
6101
					"shortcut_label"	: 'F2',
6102
					"icon"				: "glyphicon glyphicon-leaf",
6103
					*/
6104
					"action"			: function (data) {
6105
						var inst = $.jstree.reference(data.reference),
6106
							obj = inst.get_node(data.reference);
6107
						inst.edit(obj);
6108
					}
6109
				},
6110
				"remove" : {
6111
					"separator_before"	: false,
6112
					"icon"				: false,
6113
					"separator_after"	: false,
6114
					"_disabled"			: false, //(this.check("delete_node", data.reference, this.get_parent(data.reference), "")),
6115
					"label"				: "Delete",
6116
					"action"			: function (data) {
6117
						var inst = $.jstree.reference(data.reference),
6118
							obj = inst.get_node(data.reference);
6119
						if(inst.is_selected(obj)) {
6120
							inst.delete_node(inst.get_selected());
6121
						}
6122
						else {
6123
							inst.delete_node(obj);
6124
						}
6125
					}
6126
				},
6127
				"ccp" : {
6128
					"separator_before"	: true,
6129
					"icon"				: false,
6130
					"separator_after"	: false,
6131
					"label"				: "Edit",
6132
					"action"			: false,
6133
					"submenu" : {
6134
						"cut" : {
6135
							"separator_before"	: false,
6136
							"separator_after"	: false,
6137
							"label"				: "Cut",
6138
							"action"			: function (data) {
6139
								var inst = $.jstree.reference(data.reference),
6140
									obj = inst.get_node(data.reference);
6141
								if(inst.is_selected(obj)) {
6142
									inst.cut(inst.get_top_selected());
6143
								}
6144
								else {
6145
									inst.cut(obj);
6146
								}
6147
							}
6148
						},
6149
						"copy" : {
6150
							"separator_before"	: false,
6151
							"icon"				: false,
6152
							"separator_after"	: false,
6153
							"label"				: "Copy",
6154
							"action"			: function (data) {
6155
								var inst = $.jstree.reference(data.reference),
6156
									obj = inst.get_node(data.reference);
6157
								if(inst.is_selected(obj)) {
6158
									inst.copy(inst.get_top_selected());
6159
								}
6160
								else {
6161
									inst.copy(obj);
6162
								}
6163
							}
6164
						},
6165
						"paste" : {
6166
							"separator_before"	: false,
6167
							"icon"				: false,
6168
							"_disabled"			: function (data) {
6169
								return !$.jstree.reference(data.reference).can_paste();
6170
							},
6171
							"separator_after"	: false,
6172
							"label"				: "Paste",
6173
							"action"			: function (data) {
6174
								var inst = $.jstree.reference(data.reference),
6175
									obj = inst.get_node(data.reference);
6176
								inst.paste(obj);
6177
							}
6178
						}
6179
					}
6180
				}
6181
			};
6182
		}
6183
	};
6184
 
6185
	$.jstree.plugins.contextmenu = function (options, parent) {
6186
		this.bind = function () {
6187
			parent.bind.call(this);
6188
 
6189
			var last_ts = 0, cto = null, ex, ey;
6190
			this.element
6191
				.on("init.jstree loading.jstree ready.jstree", function () {
6192
						this.get_container_ul().addClass('jstree-contextmenu');
6193
					}.bind(this))
6194
				.on("contextmenu.jstree", ".jstree-anchor", function (e, data) {
6195
						if (e.target.tagName.toLowerCase() === 'input') {
6196
							return;
6197
						}
6198
						e.preventDefault();
6199
						last_ts = e.ctrlKey ? +new Date() : 0;
6200
						if(data || cto) {
6201
							last_ts = (+new Date()) + 10000;
6202
						}
6203
						if(cto) {
6204
							clearTimeout(cto);
6205
						}
6206
						if(!this.is_loading(e.currentTarget)) {
6207
							this.show_contextmenu(e.currentTarget, e.pageX, e.pageY, e);
6208
						}
6209
					}.bind(this))
6210
				.on("click.jstree", ".jstree-anchor", function (e) {
6211
						if(this._data.contextmenu.visible && (!last_ts || (+new Date()) - last_ts > 250)) { // work around safari & macOS ctrl+click
6212
							$.vakata.context.hide();
6213
						}
6214
						last_ts = 0;
6215
					}.bind(this))
6216
				.on("touchstart.jstree", ".jstree-anchor", function (e) {
6217
						if(!e.originalEvent || !e.originalEvent.changedTouches || !e.originalEvent.changedTouches[0]) {
6218
							return;
6219
						}
6220
						ex = e.originalEvent.changedTouches[0].clientX;
6221
						ey = e.originalEvent.changedTouches[0].clientY;
6222
						cto = setTimeout(function () {
6223
							$(e.currentTarget).trigger('contextmenu', true);
6224
						}, 750);
6225
					})
6226
				.on('touchmove.vakata.jstree', function (e) {
6227
						if(cto && e.originalEvent && e.originalEvent.changedTouches && e.originalEvent.changedTouches[0] && (Math.abs(ex - e.originalEvent.changedTouches[0].clientX) > 10 || Math.abs(ey - e.originalEvent.changedTouches[0].clientY) > 10)) {
6228
							clearTimeout(cto);
6229
							$.vakata.context.hide();
6230
						}
6231
					})
6232
				.on('touchend.vakata.jstree', function (e) {
6233
						if(cto) {
6234
							clearTimeout(cto);
6235
						}
6236
					});
6237
 
6238
			/*!
6239
			if(!('oncontextmenu' in document.body) && ('ontouchstart' in document.body)) {
6240
				var el = null, tm = null;
6241
				this.element
6242
					.on("touchstart", ".jstree-anchor", function (e) {
6243
						el = e.currentTarget;
6244
						tm = +new Date();
6245
						$(document).one("touchend", function (e) {
6246
							e.target = document.elementFromPoint(e.originalEvent.targetTouches[0].pageX - window.pageXOffset, e.originalEvent.targetTouches[0].pageY - window.pageYOffset);
6247
							e.currentTarget = e.target;
6248
							tm = ((+(new Date())) - tm);
6249
							if(e.target === el && tm > 600 && tm < 1000) {
6250
								e.preventDefault();
6251
								$(el).trigger('contextmenu', e);
6252
							}
6253
							el = null;
6254
							tm = null;
6255
						});
6256
					});
6257
			}
6258
			*/
6259
			$(document).on("context_hide.vakata.jstree", function (e, data) {
6260
				this._data.contextmenu.visible = false;
6261
				$(data.reference).removeClass('jstree-context');
6262
			}.bind(this));
6263
		};
6264
		this.teardown = function () {
6265
			if(this._data.contextmenu.visible) {
6266
				$.vakata.context.hide();
6267
			}
6268
			$(document).off("context_hide.vakata.jstree");
6269
			parent.teardown.call(this);
6270
		};
6271
 
6272
		/**
6273
		 * prepare and show the context menu for a node
6274
		 * @name show_contextmenu(obj [, x, y])
6275
		 * @param {mixed} obj the node
6276
		 * @param {Number} x the x-coordinate relative to the document to show the menu at
6277
		 * @param {Number} y the y-coordinate relative to the document to show the menu at
6278
		 * @param {Object} e the event if available that triggered the contextmenu
6279
		 * @plugin contextmenu
6280
		 * @trigger show_contextmenu.jstree
6281
		 */
6282
		this.show_contextmenu = function (obj, x, y, e) {
6283
			obj = this.get_node(obj);
6284
			if(!obj || obj.id === $.jstree.root) { return false; }
6285
			var s = this.settings.contextmenu,
6286
				d = this.get_node(obj, true),
6287
				a = d.children(".jstree-anchor"),
6288
				o = false,
6289
				i = false;
6290
			if(s.show_at_node || x === undefined || y === undefined) {
6291
				o = a.offset();
6292
				x = o.left;
6293
				y = o.top + this._data.core.li_height;
6294
			}
6295
			if(this.settings.contextmenu.select_node && !this.is_selected(obj)) {
6296
				this.activate_node(obj, e);
6297
			}
6298
 
6299
			i = s.items;
6300
			if($.vakata.is_function(i)) {
6301
				i = i.call(this, obj, function (i) {
6302
					this._show_contextmenu(obj, x, y, i);
6303
				}.bind(this));
6304
			}
6305
			if($.isPlainObject(i)) {
6306
				this._show_contextmenu(obj, x, y, i);
6307
			}
6308
		};
6309
		/**
6310
		 * show the prepared context menu for a node
6311
		 * @name _show_contextmenu(obj, x, y, i)
6312
		 * @param {mixed} obj the node
6313
		 * @param {Number} x the x-coordinate relative to the document to show the menu at
6314
		 * @param {Number} y the y-coordinate relative to the document to show the menu at
6315
		 * @param {Number} i the object of items to show
6316
		 * @plugin contextmenu
6317
		 * @trigger show_contextmenu.jstree
6318
		 * @private
6319
		 */
6320
		this._show_contextmenu = function (obj, x, y, i) {
6321
			var d = this.get_node(obj, true),
6322
				a = d.children(".jstree-anchor");
6323
			$(document).one("context_show.vakata.jstree", function (e, data) {
6324
				var cls = 'jstree-contextmenu jstree-' + this.get_theme() + '-contextmenu';
6325
				$(data.element).addClass(cls);
6326
				a.addClass('jstree-context');
6327
			}.bind(this));
6328
			this._data.contextmenu.visible = true;
6329
			$.vakata.context.show(a, { 'x' : x, 'y' : y }, i);
6330
			/**
6331
			 * triggered when the contextmenu is shown for a node
6332
			 * @event
6333
			 * @name show_contextmenu.jstree
6334
			 * @param {Object} node the node
6335
			 * @param {Number} x the x-coordinate of the menu relative to the document
6336
			 * @param {Number} y the y-coordinate of the menu relative to the document
6337
			 * @plugin contextmenu
6338
			 */
6339
			this.trigger('show_contextmenu', { "node" : obj, "x" : x, "y" : y });
6340
		};
6341
	};
6342
 
6343
	// contextmenu helper
6344
	(function ($) {
6345
		var right_to_left = false,
6346
			vakata_context = {
6347
				element		: false,
6348
				reference	: false,
6349
				position_x	: 0,
6350
				position_y	: 0,
6351
				items		: [],
6352
				html		: "",
6353
				is_visible	: false
6354
			};
6355
 
6356
		$.vakata.context = {
6357
			settings : {
6358
				hide_onmouseleave	: 0,
6359
				icons				: true
6360
			},
6361
			_trigger : function (event_name) {
6362
				$(document).triggerHandler("context_" + event_name + ".vakata", {
6363
					"reference"	: vakata_context.reference,
6364
					"element"	: vakata_context.element,
6365
					"position"	: {
6366
						"x" : vakata_context.position_x,
6367
						"y" : vakata_context.position_y
6368
					}
6369
				});
6370
			},
6371
			_execute : function (i) {
6372
				i = vakata_context.items[i];
6373
				return i && (!i._disabled || ($.vakata.is_function(i._disabled) && !i._disabled({ "item" : i, "reference" : vakata_context.reference, "element" : vakata_context.element }))) && i.action ? i.action.call(null, {
6374
							"item"		: i,
6375
							"reference"	: vakata_context.reference,
6376
							"element"	: vakata_context.element,
6377
							"position"	: {
6378
								"x" : vakata_context.position_x,
6379
								"y" : vakata_context.position_y
6380
							}
6381
						}) : false;
6382
			},
6383
			_parse : function (o, is_callback) {
6384
				if(!o) { return false; }
6385
				if(!is_callback) {
6386
					vakata_context.html		= "";
6387
					vakata_context.items	= [];
6388
				}
6389
				var str = "",
6390
					sep = false,
6391
					tmp;
6392
 
6393
				if(is_callback) { str += "<"+"ul>"; }
6394
				$.each(o, function (i, val) {
6395
					if(!val) { return true; }
6396
					vakata_context.items.push(val);
6397
					if(!sep && val.separator_before) {
6398
						str += "<"+"li class='vakata-context-separator'><"+"a href='#' " + ($.vakata.context.settings.icons ? '' : 'class="vakata-context-no-icons"') + ">&#160;<"+"/a><"+"/li>";
6399
					}
6400
					sep = false;
6401
					str += "<"+"li class='" + (val._class || "") + (val._disabled === true || ($.vakata.is_function(val._disabled) && val._disabled({ "item" : val, "reference" : vakata_context.reference, "element" : vakata_context.element })) ? " vakata-contextmenu-disabled " : "") + "' "+(val.shortcut?" data-shortcut='"+val.shortcut+"' ":'')+">";
6402
					str += "<"+"a href='#' rel='" + (vakata_context.items.length - 1) + "' " + (val.title ? "title='" + val.title + "'" : "") + ">";
6403
					if($.vakata.context.settings.icons) {
6404
						str += "<"+"i ";
6405
						if(val.icon) {
6406
							if(val.icon.indexOf("/") !== -1 || val.icon.indexOf(".") !== -1) { str += " style='background:url(\"" + val.icon + "\") center center no-repeat' "; }
6407
							else { str += " class='" + val.icon + "' "; }
6408
						}
6409
						str += "><"+"/i><"+"span class='vakata-contextmenu-sep'>&#160;<"+"/span>";
6410
					}
6411
					str += ($.vakata.is_function(val.label) ? val.label({ "item" : i, "reference" : vakata_context.reference, "element" : vakata_context.element }) : val.label) + (val.shortcut?' <span class="vakata-contextmenu-shortcut vakata-contextmenu-shortcut-'+val.shortcut+'">'+ (val.shortcut_label || '') +'</span>':'') + "<"+"/a>";
6412
					if(val.submenu) {
6413
						tmp = $.vakata.context._parse(val.submenu, true);
6414
						if(tmp) { str += tmp; }
6415
					}
6416
					str += "<"+"/li>";
6417
					if(val.separator_after) {
6418
						str += "<"+"li class='vakata-context-separator'><"+"a href='#' " + ($.vakata.context.settings.icons ? '' : 'class="vakata-context-no-icons"') + ">&#160;<"+"/a><"+"/li>";
6419
						sep = true;
6420
					}
6421
				});
6422
				str  = str.replace(/<li class\='vakata-context-separator'\><\/li\>$/,"");
6423
				if(is_callback) { str += "</ul>"; }
6424
				/**
6425
				 * triggered on the document when the contextmenu is parsed (HTML is built)
6426
				 * @event
6427
				 * @plugin contextmenu
6428
				 * @name context_parse.vakata
6429
				 * @param {jQuery} reference the element that was right clicked
6430
				 * @param {jQuery} element the DOM element of the menu itself
6431
				 * @param {Object} position the x & y coordinates of the menu
6432
				 */
6433
				if(!is_callback) { vakata_context.html = str; $.vakata.context._trigger("parse"); }
6434
				return str.length > 10 ? str : false;
6435
			},
6436
			_show_submenu : function (o) {
6437
				o = $(o);
6438
				if(!o.length || !o.children("ul").length) { return; }
6439
				var e = o.children("ul"),
6440
					xl = o.offset().left,
6441
					x = xl + o.outerWidth(),
6442
					y = o.offset().top,
6443
					w = e.width(),
6444
					h = e.height(),
6445
					dw = $(window).width() + $(window).scrollLeft(),
6446
					dh = $(window).height() + $(window).scrollTop();
6447
				// може да се спести е една проверка - дали няма някой от класовете вече нагоре
6448
				if(right_to_left) {
6449
					o[x - (w + 10 + o.outerWidth()) < 0 ? "addClass" : "removeClass"]("vakata-context-left");
6450
				}
6451
				else {
6452
					o[x + w > dw  && xl > dw - x ? "addClass" : "removeClass"]("vakata-context-right");
6453
				}
6454
				if(y + h + 10 > dh) {
6455
					e.css("bottom","-1px");
6456
				}
6457
 
6458
				//if does not fit - stick it to the side
6459
				if (o.hasClass('vakata-context-right')) {
6460
					if (xl < w) {
6461
						e.css("margin-right", xl - w);
6462
					}
6463
				} else {
6464
					if (dw - x < w) {
6465
						e.css("margin-left", dw - x - w);
6466
					}
6467
				}
6468
 
6469
				e.show();
6470
			},
6471
			show : function (reference, position, data) {
6472
				var o, e, x, y, w, h, dw, dh, cond = true;
6473
				if(vakata_context.element && vakata_context.element.length) {
6474
					vakata_context.element.width('');
6475
				}
6476
				switch(cond) {
6477
					case (!position && !reference):
6478
						return false;
6479
					case (!!position && !!reference):
6480
						vakata_context.reference	= reference;
6481
						vakata_context.position_x	= position.x;
6482
						vakata_context.position_y	= position.y;
6483
						break;
6484
					case (!position && !!reference):
6485
						vakata_context.reference	= reference;
6486
						o = reference.offset();
6487
						vakata_context.position_x	= o.left + reference.outerHeight();
6488
						vakata_context.position_y	= o.top;
6489
						break;
6490
					case (!!position && !reference):
6491
						vakata_context.position_x	= position.x;
6492
						vakata_context.position_y	= position.y;
6493
						break;
6494
				}
6495
				if(!!reference && !data && $(reference).data('vakata_contextmenu')) {
6496
					data = $(reference).data('vakata_contextmenu');
6497
				}
6498
				if($.vakata.context._parse(data)) {
6499
					vakata_context.element.html(vakata_context.html);
6500
				}
6501
				if(vakata_context.items.length) {
6502
					vakata_context.element.appendTo(document.body);
6503
					e = vakata_context.element;
6504
					x = vakata_context.position_x;
6505
					y = vakata_context.position_y;
6506
					w = e.width();
6507
					h = e.height();
6508
					dw = $(window).width() + $(window).scrollLeft();
6509
					dh = $(window).height() + $(window).scrollTop();
6510
					if(right_to_left) {
6511
						x -= (e.outerWidth() - $(reference).outerWidth());
6512
						if(x < $(window).scrollLeft() + 20) {
6513
							x = $(window).scrollLeft() + 20;
6514
						}
6515
					}
6516
					if(x + w + 20 > dw) {
6517
						x = dw - (w + 20);
6518
					}
6519
					if(y + h + 20 > dh) {
6520
						y = dh - (h + 20);
6521
					}
6522
 
6523
					vakata_context.element
6524
						.css({ "left" : x, "top" : y })
6525
						.show()
6526
						.find('a').first().trigger('focus').parent().addClass("vakata-context-hover");
6527
					vakata_context.is_visible = true;
6528
					/**
6529
					 * triggered on the document when the contextmenu is shown
6530
					 * @event
6531
					 * @plugin contextmenu
6532
					 * @name context_show.vakata
6533
					 * @param {jQuery} reference the element that was right clicked
6534
					 * @param {jQuery} element the DOM element of the menu itself
6535
					 * @param {Object} position the x & y coordinates of the menu
6536
					 */
6537
					$.vakata.context._trigger("show");
6538
				}
6539
			},
6540
			hide : function () {
6541
				if(vakata_context.is_visible) {
6542
					vakata_context.element.hide().find("ul").hide().end().find(':focus').trigger('blur').end().detach();
6543
					vakata_context.is_visible = false;
6544
					/**
6545
					 * triggered on the document when the contextmenu is hidden
6546
					 * @event
6547
					 * @plugin contextmenu
6548
					 * @name context_hide.vakata
6549
					 * @param {jQuery} reference the element that was right clicked
6550
					 * @param {jQuery} element the DOM element of the menu itself
6551
					 * @param {Object} position the x & y coordinates of the menu
6552
					 */
6553
					$.vakata.context._trigger("hide");
6554
				}
6555
			}
6556
		};
6557
		$(function () {
6558
			right_to_left = $(document.body).css("direction") === "rtl";
6559
			var to = false;
6560
 
6561
			vakata_context.element = $("<ul class='vakata-context'></ul>");
6562
			vakata_context.element
6563
				.on("mouseenter", "li", function (e) {
6564
					e.stopImmediatePropagation();
6565
 
6566
					if($.contains(this, e.relatedTarget)) {
6567
						// премахнато заради delegate mouseleave по-долу
6568
						// $(this).find(".vakata-context-hover").removeClass("vakata-context-hover");
6569
						return;
6570
					}
6571
 
6572
					if(to) { clearTimeout(to); }
6573
					vakata_context.element.find(".vakata-context-hover").removeClass("vakata-context-hover").end();
6574
 
6575
					$(this)
6576
						.siblings().find("ul").hide().end().end()
6577
						.parentsUntil(".vakata-context", "li").addBack().addClass("vakata-context-hover");
6578
					$.vakata.context._show_submenu(this);
6579
				})
6580
				// тестово - дали не натоварва?
6581
				.on("mouseleave", "li", function (e) {
6582
					if($.contains(this, e.relatedTarget)) { return; }
6583
					$(this).find(".vakata-context-hover").addBack().removeClass("vakata-context-hover");
6584
				})
6585
				.on("mouseleave", function (e) {
6586
					$(this).find(".vakata-context-hover").removeClass("vakata-context-hover");
6587
					if($.vakata.context.settings.hide_onmouseleave) {
6588
						to = setTimeout(
6589
							(function (t) {
6590
								return function () { $.vakata.context.hide(); };
6591
							}(this)), $.vakata.context.settings.hide_onmouseleave);
6592
					}
6593
				})
6594
				.on("click", "a", function (e) {
6595
					e.preventDefault();
6596
				//})
6597
				//.on("mouseup", "a", function (e) {
6598
					if(!$(this).trigger('blur').parent().hasClass("vakata-context-disabled") && $.vakata.context._execute($(this).attr("rel")) !== false) {
6599
						$.vakata.context.hide();
6600
					}
6601
				})
6602
				.on('keydown', 'a', function (e) {
6603
						var o = null;
6604
						switch(e.which) {
6605
							case 13:
6606
							case 32:
6607
								e.type = "click";
6608
								e.preventDefault();
6609
								$(e.currentTarget).trigger(e);
6610
								break;
6611
							case 37:
6612
								if(vakata_context.is_visible) {
6613
									vakata_context.element.find(".vakata-context-hover").last().closest("li").first().find("ul").hide().find(".vakata-context-hover").removeClass("vakata-context-hover").end().end().children('a').trigger('focus');
6614
									e.stopImmediatePropagation();
6615
									e.preventDefault();
6616
								}
6617
								break;
6618
							case 38:
6619
								if(vakata_context.is_visible) {
6620
									o = vakata_context.element.find("ul:visible").addBack().last().children(".vakata-context-hover").removeClass("vakata-context-hover").prevAll("li:not(.vakata-context-separator)").first();
6621
									if(!o.length) { o = vakata_context.element.find("ul:visible").addBack().last().children("li:not(.vakata-context-separator)").last(); }
6622
									o.addClass("vakata-context-hover").children('a').trigger('focus');
6623
									e.stopImmediatePropagation();
6624
									e.preventDefault();
6625
								}
6626
								break;
6627
							case 39:
6628
								if(vakata_context.is_visible) {
6629
									vakata_context.element.find(".vakata-context-hover").last().children("ul").show().children("li:not(.vakata-context-separator)").removeClass("vakata-context-hover").first().addClass("vakata-context-hover").children('a').trigger('focus');
6630
									e.stopImmediatePropagation();
6631
									e.preventDefault();
6632
								}
6633
								break;
6634
							case 40:
6635
								if(vakata_context.is_visible) {
6636
									o = vakata_context.element.find("ul:visible").addBack().last().children(".vakata-context-hover").removeClass("vakata-context-hover").nextAll("li:not(.vakata-context-separator)").first();
6637
									if(!o.length) { o = vakata_context.element.find("ul:visible").addBack().last().children("li:not(.vakata-context-separator)").first(); }
6638
									o.addClass("vakata-context-hover").children('a').trigger('focus');
6639
									e.stopImmediatePropagation();
6640
									e.preventDefault();
6641
								}
6642
								break;
6643
							case 27:
6644
								$.vakata.context.hide();
6645
								e.preventDefault();
6646
								break;
6647
							default:
6648
								//console.log(e.which);
6649
								break;
6650
						}
6651
					})
6652
				.on('keydown', function (e) {
6653
					e.preventDefault();
6654
					var a = vakata_context.element.find('.vakata-contextmenu-shortcut-' + e.which).parent();
6655
					if(a.parent().not('.vakata-context-disabled')) {
6656
						a.trigger('click');
6657
					}
6658
				});
6659
 
6660
			$(document)
6661
				.on("mousedown.vakata.jstree", function (e) {
6662
					if(vakata_context.is_visible && vakata_context.element[0] !== e.target  && !$.contains(vakata_context.element[0], e.target)) {
6663
						$.vakata.context.hide();
6664
					}
6665
				})
6666
				.on("context_show.vakata.jstree", function (e, data) {
6667
					vakata_context.element.find("li:has(ul)").children("a").addClass("vakata-context-parent");
6668
					if(right_to_left) {
6669
						vakata_context.element.addClass("vakata-context-rtl").css("direction", "rtl");
6670
					}
6671
					// also apply a RTL class?
6672
					vakata_context.element.find("ul").hide().end();
6673
				});
6674
		});
6675
	}($));
6676
	// $.jstree.defaults.plugins.push("contextmenu");
6677
 
6678
 
6679
/**
6680
 * ### Drag'n'drop plugin
6681
 *
6682
 * Enables dragging and dropping of nodes in the tree, resulting in a move or copy operations.
6683
 */
6684
 
6685
	/**
6686
	 * stores all defaults for the drag'n'drop plugin
6687
	 * @name $.jstree.defaults.dnd
6688
	 * @plugin dnd
6689
	 */
6690
	$.jstree.defaults.dnd = {
6691
		/**
6692
		 * a boolean indicating if a copy should be possible while dragging (by pressint the meta key or Ctrl). Defaults to `true`.
6693
		 * @name $.jstree.defaults.dnd.copy
6694
		 * @plugin dnd
6695
		 */
6696
		copy : true,
6697
		/**
6698
		 * a number indicating how long a node should remain hovered while dragging to be opened. Defaults to `500`.
6699
		 * @name $.jstree.defaults.dnd.open_timeout
6700
		 * @plugin dnd
6701
		 */
6702
		open_timeout : 500,
6703
		/**
6704
		 * a function invoked each time a node is about to be dragged, invoked in the tree's scope and receives the nodes about to be dragged as an argument (array) and the event that started the drag - return `false` to prevent dragging
6705
		 * @name $.jstree.defaults.dnd.is_draggable
6706
		 * @plugin dnd
6707
		 */
6708
		is_draggable : true,
6709
		/**
6710
		 * a boolean indicating if checks should constantly be made while the user is dragging the node (as opposed to checking only on drop), default is `true`
6711
		 * @name $.jstree.defaults.dnd.check_while_dragging
6712
		 * @plugin dnd
6713
		 */
6714
		check_while_dragging : true,
6715
		/**
6716
		 * a boolean indicating if nodes from this tree should only be copied with dnd (as opposed to moved), default is `false`
6717
		 * @name $.jstree.defaults.dnd.always_copy
6718
		 * @plugin dnd
6719
		 */
6720
		always_copy : false,
6721
		/**
6722
		 * when dropping a node "inside", this setting indicates the position the node should go to - it can be an integer or a string: "first" (same as 0) or "last", default is `0`
6723
		 * @name $.jstree.defaults.dnd.inside_pos
6724
		 * @plugin dnd
6725
		 */
6726
		inside_pos : 0,
6727
		/**
6728
		 * when starting the drag on a node that is selected this setting controls if all selected nodes are dragged or only the single node, default is `true`, which means all selected nodes are dragged when the drag is started on a selected node
6729
		 * @name $.jstree.defaults.dnd.drag_selection
6730
		 * @plugin dnd
6731
		 */
6732
		drag_selection : true,
6733
		/**
6734
		 * controls whether dnd works on touch devices. If left as boolean true dnd will work the same as in desktop browsers, which in some cases may impair scrolling. If set to boolean false dnd will not work on touch devices. There is a special third option - string "selected" which means only selected nodes can be dragged on touch devices.
6735
		 * @name $.jstree.defaults.dnd.touch
6736
		 * @plugin dnd
6737
		 */
6738
		touch : true,
6739
		/**
6740
		 * controls whether items can be dropped anywhere on the node, not just on the anchor, by default only the node anchor is a valid drop target. Works best with the wholerow plugin. If enabled on mobile depending on the interface it might be hard for the user to cancel the drop, since the whole tree container will be a valid drop target.
6741
		 * @name $.jstree.defaults.dnd.large_drop_target
6742
		 * @plugin dnd
6743
		 */
6744
		large_drop_target : false,
6745
		/**
6746
		 * controls whether a drag can be initiated from any part of the node and not just the text/icon part, works best with the wholerow plugin. Keep in mind it can cause problems with tree scrolling on mobile depending on the interface - in that case set the touch option to "selected".
6747
		 * @name $.jstree.defaults.dnd.large_drag_target
6748
		 * @plugin dnd
6749
		 */
6750
		large_drag_target : false,
6751
		/**
6752
		 * controls whether use HTML5 dnd api instead of classical. That will allow better integration of dnd events with other HTML5 controls.
6753
		 * @reference http://caniuse.com/#feat=dragndrop
6754
		 * @name $.jstree.defaults.dnd.use_html5
6755
		 * @plugin dnd
6756
		 */
6757
		use_html5: false
6758
	};
6759
	var drg, elm;
6760
	// TODO: now check works by checking for each node individually, how about max_children, unique, etc?
6761
	$.jstree.plugins.dnd = function (options, parent) {
6762
		this.init = function (el, options) {
6763
			parent.init.call(this, el, options);
6764
			this.settings.dnd.use_html5 = this.settings.dnd.use_html5 && ('draggable' in document.createElement('span'));
6765
		};
6766
		this.bind = function () {
6767
			parent.bind.call(this);
6768
 
6769
			this.element
6770
				.on(this.settings.dnd.use_html5 ? 'dragstart.jstree' : 'mousedown.jstree touchstart.jstree', this.settings.dnd.large_drag_target ? '.jstree-node' : '.jstree-anchor', function (e) {
6771
						if(this.settings.dnd.large_drag_target && $(e.target).closest('.jstree-node')[0] !== e.currentTarget) {
6772
							return true;
6773
						}
6774
						if(e.type === "touchstart" && (!this.settings.dnd.touch || (this.settings.dnd.touch === 'selected' && !$(e.currentTarget).closest('.jstree-node').children('.jstree-anchor').hasClass('jstree-clicked')))) {
6775
							return true;
6776
						}
6777
						var obj = this.get_node(e.target),
6778
							mlt = this.is_selected(obj) && this.settings.dnd.drag_selection ? this.get_top_selected().length : 1,
6779
							txt = (mlt > 1 ? mlt + ' ' + this.get_string('nodes') : this.get_text(e.currentTarget));
6780
						if(this.settings.core.force_text) {
6781
							txt = $.vakata.html.escape(txt);
6782
						}
6783
						if(obj && obj.id && obj.id !== $.jstree.root && (e.which === 1 || e.type === "touchstart" || e.type === "dragstart") &&
6784
							(this.settings.dnd.is_draggable === true || ($.vakata.is_function(this.settings.dnd.is_draggable) && this.settings.dnd.is_draggable.call(this, (mlt > 1 ? this.get_top_selected(true) : [obj]), e)))
6785
						) {
6786
							drg = { 'jstree' : true, 'origin' : this, 'obj' : this.get_node(obj,true), 'nodes' : mlt > 1 ? this.get_top_selected() : [obj.id] };
6787
							elm = e.currentTarget;
6788
							if (this.settings.dnd.use_html5) {
6789
								$.vakata.dnd._trigger('start', e, { 'helper': $(), 'element': elm, 'data': drg });
6790
							} else {
6791
								this.element.trigger('mousedown.jstree');
6792
								return $.vakata.dnd.start(e, drg, '<div id="jstree-dnd" class="jstree-' + this.get_theme() + ' jstree-' + this.get_theme() + '-' + this.get_theme_variant() + ' ' + ( this.settings.core.themes.responsive ? ' jstree-dnd-responsive' : '' ) + '"><i class="jstree-icon jstree-er"></i>' + txt + '<ins class="jstree-copy">+</ins></div>');
6793
							}
6794
						}
6795
					}.bind(this));
6796
			if (this.settings.dnd.use_html5) {
6797
				this.element
6798
					.on('dragover.jstree', function (e) {
6799
							e.preventDefault();
6800
							$.vakata.dnd._trigger('move', e, { 'helper': $(), 'element': elm, 'data': drg });
6801
							return false;
6802
						})
6803
					//.on('dragenter.jstree', this.settings.dnd.large_drop_target ? '.jstree-node' : '.jstree-anchor', $.proxy(function (e) {
6804
					//		e.preventDefault();
6805
					//		$.vakata.dnd._trigger('move', e, { 'helper': $(), 'element': elm, 'data': drg });
6806
					//		return false;
6807
					//	}, this))
6808
					.on('drop.jstree', function (e) {
6809
							e.preventDefault();
6810
							$.vakata.dnd._trigger('stop', e, { 'helper': $(), 'element': elm, 'data': drg });
6811
							return false;
6812
						}.bind(this));
6813
			}
6814
		};
6815
		this.redraw_node = function(obj, deep, callback, force_render) {
6816
			obj = parent.redraw_node.apply(this, arguments);
6817
			if (obj && this.settings.dnd.use_html5) {
6818
				if (this.settings.dnd.large_drag_target) {
6819
					obj.setAttribute('draggable', true);
6820
				} else {
6821
					var i, j, tmp = null;
6822
					for(i = 0, j = obj.childNodes.length; i < j; i++) {
6823
						if(obj.childNodes[i] && obj.childNodes[i].className && obj.childNodes[i].className.indexOf("jstree-anchor") !== -1) {
6824
							tmp = obj.childNodes[i];
6825
							break;
6826
						}
6827
					}
6828
					if(tmp) {
6829
						tmp.setAttribute('draggable', true);
6830
					}
6831
				}
6832
			}
6833
			return obj;
6834
		};
6835
	};
6836
 
6837
	$(function() {
6838
		// bind only once for all instances
6839
		var lastmv = false,
6840
			laster = false,
6841
			lastev = false,
6842
			opento = false,
6843
			marker = $('<div id="jstree-marker">&#160;</div>').hide(); //.appendTo('body');
6844
 
6845
		$(document)
6846
			.on('dragover.vakata.jstree', function (e) {
6847
				if (elm) {
6848
					$.vakata.dnd._trigger('move', e, { 'helper': $(), 'element': elm, 'data': drg });
6849
				}
6850
			})
6851
			.on('drop.vakata.jstree', function (e) {
6852
				if (elm) {
6853
					$.vakata.dnd._trigger('stop', e, { 'helper': $(), 'element': elm, 'data': drg });
6854
					elm = null;
6855
					drg = null;
6856
				}
6857
			})
6858
			.on('dnd_start.vakata.jstree', function (e, data) {
6859
				lastmv = false;
6860
				lastev = false;
6861
				if(!data || !data.data || !data.data.jstree) { return; }
6862
				marker.appendTo(document.body); //.show();
6863
			})
6864
			.on('dnd_move.vakata.jstree', function (e, data) {
6865
				var isDifferentNode = data.event.target !== lastev.target;
6866
				if(opento) {
6867
					if (!data.event || data.event.type !== 'dragover' || isDifferentNode) {
6868
						clearTimeout(opento);
6869
					}
6870
				}
6871
				if(!data || !data.data || !data.data.jstree) { return; }
6872
 
6873
				// if we are hovering the marker image do nothing (can happen on "inside" drags)
6874
				if(data.event.target.id && data.event.target.id === 'jstree-marker') {
6875
					return;
6876
				}
6877
				lastev = data.event;
6878
 
6879
				var ins = $.jstree.reference(data.event.target),
6880
					ref = false,
6881
					off = false,
6882
					rel = false,
6883
					tmp, l, t, h, p, i, o, ok, t1, t2, op, ps, pr, ip, tm, is_copy, pn, c;
6884
				// if we are over an instance
6885
				if(ins && ins._data && ins._data.dnd) {
6886
					marker.attr('class', 'jstree-' + ins.get_theme() + ( ins.settings.core.themes.responsive ? ' jstree-dnd-responsive' : '' ));
6887
					is_copy = data.data.origin && (data.data.origin.settings.dnd.always_copy || (data.data.origin.settings.dnd.copy && (data.event.metaKey || data.event.ctrlKey)));
6888
					data.helper
6889
						.children().attr('class', 'jstree-' + ins.get_theme() + ' jstree-' + ins.get_theme() + '-' + ins.get_theme_variant() + ' ' + ( ins.settings.core.themes.responsive ? ' jstree-dnd-responsive' : '' ))
6890
						.find('.jstree-copy').first()[ is_copy ? 'show' : 'hide' ]();
6891
 
6892
					// if are hovering the container itself add a new root node
6893
					//console.log(data.event);
6894
					if( (data.event.target === ins.element[0] || data.event.target === ins.get_container_ul()[0]) && ins.get_container_ul().children().length === 0) {
6895
						ok = true;
6896
						for(t1 = 0, t2 = data.data.nodes.length; t1 < t2; t1++) {
6897
							ok = ok && ins.check( (data.data.origin && (data.data.origin.settings.dnd.always_copy || (data.data.origin.settings.dnd.copy && (data.event.metaKey || data.event.ctrlKey)) ) ? "copy_node" : "move_node"), (data.data.origin && data.data.origin !== ins ? data.data.origin.get_node(data.data.nodes[t1]) : data.data.nodes[t1]), $.jstree.root, 'last', { 'dnd' : true, 'ref' : ins.get_node($.jstree.root), 'pos' : 'i', 'origin' : data.data.origin, 'is_multi' : (data.data.origin && data.data.origin !== ins), 'is_foreign' : (!data.data.origin) });
6898
							if(!ok) { break; }
6899
						}
6900
						if(ok) {
6901
							lastmv = { 'ins' : ins, 'par' : $.jstree.root, 'pos' : 'last' };
6902
							marker.hide();
6903
							data.helper.find('.jstree-icon').first().removeClass('jstree-er').addClass('jstree-ok');
6904
							if (data.event.originalEvent && data.event.originalEvent.dataTransfer) {
6905
								data.event.originalEvent.dataTransfer.dropEffect = is_copy ? 'copy' : 'move';
6906
							}
6907
							return;
6908
						}
6909
					}
6910
					else {
6911
						// if we are hovering a tree node
6912
						ref = ins.settings.dnd.large_drop_target ? $(data.event.target).closest('.jstree-node').children('.jstree-anchor') : $(data.event.target).closest('.jstree-anchor');
6913
						if(ref && ref.length && ref.parent().is('.jstree-closed, .jstree-open, .jstree-leaf')) {
6914
							off = ref.offset();
6915
							rel = (data.event.pageY !== undefined ? data.event.pageY : data.event.originalEvent.pageY) - off.top;
6916
							h = ref.outerHeight();
6917
							if(rel < h / 3) {
6918
								o = ['b', 'i', 'a'];
6919
							}
6920
							else if(rel > h - h / 3) {
6921
								o = ['a', 'i', 'b'];
6922
							}
6923
							else {
6924
								o = rel > h / 2 ? ['i', 'a', 'b'] : ['i', 'b', 'a'];
6925
							}
6926
							$.each(o, function (j, v) {
6927
								switch(v) {
6928
									case 'b':
6929
										l = off.left - 6;
6930
										t = off.top;
6931
										p = ins.get_parent(ref);
6932
										i = ref.parent().index();
6933
										c = 'jstree-below';
6934
										break;
6935
									case 'i':
6936
										ip = ins.settings.dnd.inside_pos;
6937
										tm = ins.get_node(ref.parent());
6938
										l = off.left - 2;
6939
										t = off.top + h / 2 + 1;
6940
										p = tm.id;
6941
										i = ip === 'first' ? 0 : (ip === 'last' ? tm.children.length : Math.min(ip, tm.children.length));
6942
										c = 'jstree-inside';
6943
										break;
6944
									case 'a':
6945
										l = off.left - 6;
6946
										t = off.top + h;
6947
										p = ins.get_parent(ref);
6948
										i = ref.parent().index() + 1;
6949
										c = 'jstree-above';
6950
										break;
6951
								}
6952
								ok = true;
6953
								for(t1 = 0, t2 = data.data.nodes.length; t1 < t2; t1++) {
6954
									op = data.data.origin && (data.data.origin.settings.dnd.always_copy || (data.data.origin.settings.dnd.copy && (data.event.metaKey || data.event.ctrlKey))) ? "copy_node" : "move_node";
6955
									ps = i;
6956
									if(op === "move_node" && v === 'a' && (data.data.origin && data.data.origin === ins) && p === ins.get_parent(data.data.nodes[t1])) {
6957
										pr = ins.get_node(p);
6958
										if(ps > $.inArray(data.data.nodes[t1], pr.children)) {
6959
											ps -= 1;
6960
										}
6961
									}
6962
									ok = ok && ( (ins && ins.settings && ins.settings.dnd && ins.settings.dnd.check_while_dragging === false) || ins.check(op, (data.data.origin && data.data.origin !== ins ? data.data.origin.get_node(data.data.nodes[t1]) : data.data.nodes[t1]), p, ps, { 'dnd' : true, 'ref' : ins.get_node(ref.parent()), 'pos' : v, 'origin' : data.data.origin, 'is_multi' : (data.data.origin && data.data.origin !== ins), 'is_foreign' : (!data.data.origin) }) );
6963
									if(!ok) {
6964
										if(ins && ins.last_error) { laster = ins.last_error(); }
6965
										break;
6966
									}
6967
								}
6968
								if(v === 'i' && ref.parent().is('.jstree-closed') && ins.settings.dnd.open_timeout) {
6969
									if (!data.event || data.event.type !== 'dragover' || isDifferentNode) {
6970
										if (opento) { clearTimeout(opento); }
6971
										opento = setTimeout((function (x, z) { return function () { x.open_node(z); }; }(ins, ref)), ins.settings.dnd.open_timeout);
6972
									}
6973
								}
6974
								if(ok) {
6975
									pn = ins.get_node(p, true);
6976
									if (!pn.hasClass('.jstree-dnd-parent')) {
6977
										$('.jstree-dnd-parent').removeClass('jstree-dnd-parent');
6978
										pn.addClass('jstree-dnd-parent');
6979
									}
6980
									lastmv = { 'ins' : ins, 'par' : p, 'pos' : v === 'i' && ip === 'last' && i === 0 && !ins.is_loaded(tm) ? 'last' : i };
6981
									marker.css({ 'left' : l + 'px', 'top' : t + 'px' }).show();
6982
									marker.removeClass('jstree-above jstree-inside jstree-below').addClass(c);
6983
									data.helper.find('.jstree-icon').first().removeClass('jstree-er').addClass('jstree-ok');
6984
									if (data.event.originalEvent && data.event.originalEvent.dataTransfer) {
6985
										data.event.originalEvent.dataTransfer.dropEffect = is_copy ? 'copy' : 'move';
6986
									}
6987
									laster = {};
6988
									o = true;
6989
									return false;
6990
								}
6991
							});
6992
							if(o === true) { return; }
6993
						}
6994
					}
6995
				}
6996
				$('.jstree-dnd-parent').removeClass('jstree-dnd-parent');
6997
				lastmv = false;
6998
				data.helper.find('.jstree-icon').removeClass('jstree-ok').addClass('jstree-er');
6999
				if (data.event.originalEvent && data.event.originalEvent.dataTransfer) {
7000
					//data.event.originalEvent.dataTransfer.dropEffect = 'none';
7001
				}
7002
				marker.hide();
7003
			})
7004
			.on('dnd_scroll.vakata.jstree', function (e, data) {
7005
				if(!data || !data.data || !data.data.jstree) { return; }
7006
				marker.hide();
7007
				lastmv = false;
7008
				lastev = false;
7009
				data.helper.find('.jstree-icon').first().removeClass('jstree-ok').addClass('jstree-er');
7010
			})
7011
			.on('dnd_stop.vakata.jstree', function (e, data) {
7012
				$('.jstree-dnd-parent').removeClass('jstree-dnd-parent');
7013
				if(opento) { clearTimeout(opento); }
7014
				if(!data || !data.data || !data.data.jstree) { return; }
7015
				marker.hide().detach();
7016
				var i, j, nodes = [];
7017
				if(lastmv) {
7018
					for(i = 0, j = data.data.nodes.length; i < j; i++) {
7019
						nodes[i] = data.data.origin ? data.data.origin.get_node(data.data.nodes[i]) : data.data.nodes[i];
7020
					}
7021
					lastmv.ins[ data.data.origin && (data.data.origin.settings.dnd.always_copy || (data.data.origin.settings.dnd.copy && (data.event.metaKey || data.event.ctrlKey))) ? 'copy_node' : 'move_node' ](nodes, lastmv.par, lastmv.pos, false, false, false, data.data.origin);
7022
				}
7023
				else {
7024
					i = $(data.event.target).closest('.jstree');
7025
					if(i.length && laster && laster.error && laster.error === 'check') {
7026
						i = i.jstree(true);
7027
						if(i) {
7028
							i.settings.core.error.call(this, laster);
7029
						}
7030
					}
7031
				}
7032
				lastev = false;
7033
				lastmv = false;
7034
			})
7035
			.on('keyup.jstree keydown.jstree', function (e, data) {
7036
				data = $.vakata.dnd._get();
7037
				if(data && data.data && data.data.jstree) {
7038
					if (e.type === "keyup" && e.which === 27) {
7039
						if (opento) { clearTimeout(opento); }
7040
						lastmv = false;
7041
						laster = false;
7042
						lastev = false;
7043
						opento = false;
7044
						marker.hide().detach();
7045
						$.vakata.dnd._clean();
7046
					} else {
7047
						data.helper.find('.jstree-copy').first()[ data.data.origin && (data.data.origin.settings.dnd.always_copy || (data.data.origin.settings.dnd.copy && (e.metaKey || e.ctrlKey))) ? 'show' : 'hide' ]();
7048
						if(lastev) {
7049
							lastev.metaKey = e.metaKey;
7050
							lastev.ctrlKey = e.ctrlKey;
7051
							$.vakata.dnd._trigger('move', lastev);
7052
						}
7053
					}
7054
				}
7055
			});
7056
	});
7057
 
7058
	// helpers
7059
	(function ($) {
7060
		$.vakata.html = {
7061
			div : $('<div></div>'),
7062
			escape : function (str) {
7063
				return $.vakata.html.div.text(str).html();
7064
			},
7065
			strip : function (str) {
7066
				return $.vakata.html.div.empty().append($.parseHTML(str)).text();
7067
			}
7068
		};
7069
		// private variable
7070
		var vakata_dnd = {
7071
			element	: false,
7072
			target	: false,
7073
			is_down	: false,
7074
			is_drag	: false,
7075
			helper	: false,
7076
			helper_w: 0,
7077
			data	: false,
7078
			init_x	: 0,
7079
			init_y	: 0,
7080
			scroll_l: 0,
7081
			scroll_t: 0,
7082
			scroll_e: false,
7083
			scroll_i: false,
7084
			is_touch: false
7085
		};
7086
		$.vakata.dnd = {
7087
			settings : {
7088
				scroll_speed		: 10,
7089
				scroll_proximity	: 20,
7090
				helper_left			: 5,
7091
				helper_top			: 10,
7092
				threshold			: 5,
7093
				threshold_touch		: 10
7094
			},
7095
			_trigger : function (event_name, e, data) {
7096
				if (data === undefined) {
7097
					data = $.vakata.dnd._get();
7098
				}
7099
				data.event = e;
7100
				$(document).triggerHandler("dnd_" + event_name + ".vakata", data);
7101
			},
7102
			_get : function () {
7103
				return {
7104
					"data"		: vakata_dnd.data,
7105
					"element"	: vakata_dnd.element,
7106
					"helper"	: vakata_dnd.helper
7107
				};
7108
			},
7109
			_clean : function () {
7110
				if(vakata_dnd.helper) { vakata_dnd.helper.remove(); }
7111
				if(vakata_dnd.scroll_i) { clearInterval(vakata_dnd.scroll_i); vakata_dnd.scroll_i = false; }
7112
				vakata_dnd = {
7113
					element	: false,
7114
					target	: false,
7115
					is_down	: false,
7116
					is_drag	: false,
7117
					helper	: false,
7118
					helper_w: 0,
7119
					data	: false,
7120
					init_x	: 0,
7121
					init_y	: 0,
7122
					scroll_l: 0,
7123
					scroll_t: 0,
7124
					scroll_e: false,
7125
					scroll_i: false,
7126
					is_touch: false
7127
				};
7128
				elm = null;
7129
				$(document).off("mousemove.vakata.jstree touchmove.vakata.jstree", $.vakata.dnd.drag);
7130
				$(document).off("mouseup.vakata.jstree touchend.vakata.jstree", $.vakata.dnd.stop);
7131
			},
7132
			_scroll : function (init_only) {
7133
				if(!vakata_dnd.scroll_e || (!vakata_dnd.scroll_l && !vakata_dnd.scroll_t)) {
7134
					if(vakata_dnd.scroll_i) { clearInterval(vakata_dnd.scroll_i); vakata_dnd.scroll_i = false; }
7135
					return false;
7136
				}
7137
				if(!vakata_dnd.scroll_i) {
7138
					vakata_dnd.scroll_i = setInterval($.vakata.dnd._scroll, 100);
7139
					return false;
7140
				}
7141
				if(init_only === true) { return false; }
7142
 
7143
				var i = vakata_dnd.scroll_e.scrollTop(),
7144
					j = vakata_dnd.scroll_e.scrollLeft();
7145
				vakata_dnd.scroll_e.scrollTop(i + vakata_dnd.scroll_t * $.vakata.dnd.settings.scroll_speed);
7146
				vakata_dnd.scroll_e.scrollLeft(j + vakata_dnd.scroll_l * $.vakata.dnd.settings.scroll_speed);
7147
				if(i !== vakata_dnd.scroll_e.scrollTop() || j !== vakata_dnd.scroll_e.scrollLeft()) {
7148
					/**
7149
					 * triggered on the document when a drag causes an element to scroll
7150
					 * @event
7151
					 * @plugin dnd
7152
					 * @name dnd_scroll.vakata
7153
					 * @param {Mixed} data any data supplied with the call to $.vakata.dnd.start
7154
					 * @param {DOM} element the DOM element being dragged
7155
					 * @param {jQuery} helper the helper shown next to the mouse
7156
					 * @param {jQuery} event the element that is scrolling
7157
					 */
7158
					$.vakata.dnd._trigger("scroll", vakata_dnd.scroll_e);
7159
				}
7160
			},
7161
			start : function (e, data, html) {
7162
				if(e.type === "touchstart" && e.originalEvent && e.originalEvent.changedTouches && e.originalEvent.changedTouches[0]) {
7163
					e.pageX = e.originalEvent.changedTouches[0].pageX;
7164
					e.pageY = e.originalEvent.changedTouches[0].pageY;
7165
					e.target = document.elementFromPoint(e.originalEvent.changedTouches[0].pageX - window.pageXOffset, e.originalEvent.changedTouches[0].pageY - window.pageYOffset);
7166
				}
7167
				if(vakata_dnd.is_drag) { $.vakata.dnd.stop({}); }
7168
				try {
7169
					e.currentTarget.unselectable = "on";
7170
					e.currentTarget.onselectstart = function() { return false; };
7171
					if(e.currentTarget.style) {
7172
						e.currentTarget.style.touchAction = "none";
7173
						e.currentTarget.style.msTouchAction = "none";
7174
						e.currentTarget.style.MozUserSelect = "none";
7175
					}
7176
				} catch(ignore) { }
7177
				vakata_dnd.init_x	= e.pageX;
7178
				vakata_dnd.init_y	= e.pageY;
7179
				vakata_dnd.data		= data;
7180
				vakata_dnd.is_down	= true;
7181
				vakata_dnd.element	= e.currentTarget;
7182
				vakata_dnd.target	= e.target;
7183
				vakata_dnd.is_touch	= e.type === "touchstart";
7184
				if(html !== false) {
7185
					vakata_dnd.helper = $("<div id='vakata-dnd'></div>").html(html).css({
7186
						"display"		: "block",
7187
						"margin"		: "0",
7188
						"padding"		: "0",
7189
						"position"		: "absolute",
7190
						"top"			: "-2000px",
7191
						"lineHeight"	: "16px",
7192
						"zIndex"		: "10000"
7193
					});
7194
				}
7195
				$(document).on("mousemove.vakata.jstree touchmove.vakata.jstree", $.vakata.dnd.drag);
7196
				$(document).on("mouseup.vakata.jstree touchend.vakata.jstree", $.vakata.dnd.stop);
7197
				return false;
7198
			},
7199
			drag : function (e) {
7200
				if(e.type === "touchmove" && e.originalEvent && e.originalEvent.changedTouches && e.originalEvent.changedTouches[0]) {
7201
					e.pageX = e.originalEvent.changedTouches[0].pageX;
7202
					e.pageY = e.originalEvent.changedTouches[0].pageY;
7203
					e.target = document.elementFromPoint(e.originalEvent.changedTouches[0].pageX - window.pageXOffset, e.originalEvent.changedTouches[0].pageY - window.pageYOffset);
7204
				}
7205
				if(!vakata_dnd.is_down) { return; }
7206
				if(!vakata_dnd.is_drag) {
7207
					if(
7208
						Math.abs(e.pageX - vakata_dnd.init_x) > (vakata_dnd.is_touch ? $.vakata.dnd.settings.threshold_touch : $.vakata.dnd.settings.threshold) ||
7209
						Math.abs(e.pageY - vakata_dnd.init_y) > (vakata_dnd.is_touch ? $.vakata.dnd.settings.threshold_touch : $.vakata.dnd.settings.threshold)
7210
					) {
7211
						if(vakata_dnd.helper) {
7212
							vakata_dnd.helper.appendTo(document.body);
7213
							vakata_dnd.helper_w = vakata_dnd.helper.outerWidth();
7214
						}
7215
						vakata_dnd.is_drag = true;
7216
						$(vakata_dnd.target).one('click.vakata', false);
7217
						/**
7218
						 * triggered on the document when a drag starts
7219
						 * @event
7220
						 * @plugin dnd
7221
						 * @name dnd_start.vakata
7222
						 * @param {Mixed} data any data supplied with the call to $.vakata.dnd.start
7223
						 * @param {DOM} element the DOM element being dragged
7224
						 * @param {jQuery} helper the helper shown next to the mouse
7225
						 * @param {Object} event the event that caused the start (probably mousemove)
7226
						 */
7227
						$.vakata.dnd._trigger("start", e);
7228
					}
7229
					else { return; }
7230
				}
7231
 
7232
				var d  = false, w  = false,
7233
					dh = false, wh = false,
7234
					dw = false, ww = false,
7235
					dt = false, dl = false,
7236
					ht = false, hl = false;
7237
 
7238
				vakata_dnd.scroll_t = 0;
7239
				vakata_dnd.scroll_l = 0;
7240
				vakata_dnd.scroll_e = false;
7241
				$($(e.target).parentsUntil("body").addBack().get().reverse())
7242
					.filter(function () {
7243
						return	(/^auto|scroll$/).test($(this).css("overflow")) &&
7244
								(this.scrollHeight > this.offsetHeight || this.scrollWidth > this.offsetWidth);
7245
					})
7246
					.each(function () {
7247
						var t = $(this), o = t.offset();
7248
						if(this.scrollHeight > this.offsetHeight) {
7249
							if(o.top + t.height() - e.pageY < $.vakata.dnd.settings.scroll_proximity)	{ vakata_dnd.scroll_t = 1; }
7250
							if(e.pageY - o.top < $.vakata.dnd.settings.scroll_proximity)				{ vakata_dnd.scroll_t = -1; }
7251
						}
7252
						if(this.scrollWidth > this.offsetWidth) {
7253
							if(o.left + t.width() - e.pageX < $.vakata.dnd.settings.scroll_proximity)	{ vakata_dnd.scroll_l = 1; }
7254
							if(e.pageX - o.left < $.vakata.dnd.settings.scroll_proximity)				{ vakata_dnd.scroll_l = -1; }
7255
						}
7256
						if(vakata_dnd.scroll_t || vakata_dnd.scroll_l) {
7257
							vakata_dnd.scroll_e = $(this);
7258
							return false;
7259
						}
7260
					});
7261
 
7262
				if(!vakata_dnd.scroll_e) {
7263
					d  = $(document); w = $(window);
7264
					dh = d.height(); wh = w.height();
7265
					dw = d.width(); ww = w.width();
7266
					dt = d.scrollTop(); dl = d.scrollLeft();
7267
					if(dh > wh && e.pageY - dt < $.vakata.dnd.settings.scroll_proximity)		{ vakata_dnd.scroll_t = -1;  }
7268
					if(dh > wh && wh - (e.pageY - dt) < $.vakata.dnd.settings.scroll_proximity)	{ vakata_dnd.scroll_t = 1; }
7269
					if(dw > ww && e.pageX - dl < $.vakata.dnd.settings.scroll_proximity)		{ vakata_dnd.scroll_l = -1; }
7270
					if(dw > ww && ww - (e.pageX - dl) < $.vakata.dnd.settings.scroll_proximity)	{ vakata_dnd.scroll_l = 1; }
7271
					if(vakata_dnd.scroll_t || vakata_dnd.scroll_l) {
7272
						vakata_dnd.scroll_e = d;
7273
					}
7274
				}
7275
				if(vakata_dnd.scroll_e) { $.vakata.dnd._scroll(true); }
7276
 
7277
				if(vakata_dnd.helper) {
7278
					ht = parseInt(e.pageY + $.vakata.dnd.settings.helper_top, 10);
7279
					hl = parseInt(e.pageX + $.vakata.dnd.settings.helper_left, 10);
7280
					if(dh && ht + 25 > dh) { ht = dh - 50; }
7281
					if(dw && hl + vakata_dnd.helper_w > dw) { hl = dw - (vakata_dnd.helper_w + 2); }
7282
					vakata_dnd.helper.css({
7283
						left	: hl + "px",
7284
						top		: ht + "px"
7285
					});
7286
				}
7287
				/**
7288
				 * triggered on the document when a drag is in progress
7289
				 * @event
7290
				 * @plugin dnd
7291
				 * @name dnd_move.vakata
7292
				 * @param {Mixed} data any data supplied with the call to $.vakata.dnd.start
7293
				 * @param {DOM} element the DOM element being dragged
7294
				 * @param {jQuery} helper the helper shown next to the mouse
7295
				 * @param {Object} event the event that caused this to trigger (most likely mousemove)
7296
				 */
7297
				$.vakata.dnd._trigger("move", e);
7298
				return false;
7299
			},
7300
			stop : function (e) {
7301
				if(e.type === "touchend" && e.originalEvent && e.originalEvent.changedTouches && e.originalEvent.changedTouches[0]) {
7302
					e.pageX = e.originalEvent.changedTouches[0].pageX;
7303
					e.pageY = e.originalEvent.changedTouches[0].pageY;
7304
					e.target = document.elementFromPoint(e.originalEvent.changedTouches[0].pageX - window.pageXOffset, e.originalEvent.changedTouches[0].pageY - window.pageYOffset);
7305
				}
7306
				if(vakata_dnd.is_drag) {
7307
					/**
7308
					 * triggered on the document when a drag stops (the dragged element is dropped)
7309
					 * @event
7310
					 * @plugin dnd
7311
					 * @name dnd_stop.vakata
7312
					 * @param {Mixed} data any data supplied with the call to $.vakata.dnd.start
7313
					 * @param {DOM} element the DOM element being dragged
7314
					 * @param {jQuery} helper the helper shown next to the mouse
7315
					 * @param {Object} event the event that caused the stop
7316
					 */
7317
					if (e.target !== vakata_dnd.target) {
7318
						$(vakata_dnd.target).off('click.vakata');
7319
					}
7320
					$.vakata.dnd._trigger("stop", e);
7321
				}
7322
				else {
7323
					if(e.type === "touchend" && e.target === vakata_dnd.target) {
7324
						var to = setTimeout(function () { $(e.target).trigger('click'); }, 100);
7325
						$(e.target).one('click', function() { if(to) { clearTimeout(to); } });
7326
					}
7327
				}
7328
				$.vakata.dnd._clean();
7329
				return false;
7330
			}
7331
		};
7332
	}($));
7333
 
7334
	// include the dnd plugin by default
7335
	// $.jstree.defaults.plugins.push("dnd");
7336
 
7337
 
7338
/**
7339
 * ### Massload plugin
7340
 *
7341
 * Adds massload functionality to jsTree, so that multiple nodes can be loaded in a single request (only useful with lazy loading).
7342
 */
7343
 
7344
	/**
7345
	 * massload configuration
7346
	 *
7347
	 * It is possible to set this to a standard jQuery-like AJAX config.
7348
	 * In addition to the standard jQuery ajax options here you can supply functions for `data` and `url`, the functions will be run in the current instance's scope and a param will be passed indicating which node IDs need to be loaded, the return value of those functions will be used.
7349
	 *
7350
	 * You can also set this to a function, that function will receive the node IDs being loaded as argument and a second param which is a function (callback) which should be called with the result.
7351
	 *
7352
	 * Both the AJAX and the function approach rely on the same return value - an object where the keys are the node IDs, and the value is the children of that node as an array.
7353
	 *
7354
	 *	{
7355
	 *		"id1" : [{ "text" : "Child of ID1", "id" : "c1" }, { "text" : "Another child of ID1", "id" : "c2" }],
7356
	 *		"id2" : [{ "text" : "Child of ID2", "id" : "c3" }]
7357
	 *	}
7358
	 *
7359
	 * @name $.jstree.defaults.massload
7360
	 * @plugin massload
7361
	 */
7362
	$.jstree.defaults.massload = null;
7363
	$.jstree.plugins.massload = function (options, parent) {
7364
		this.init = function (el, options) {
7365
			this._data.massload = {};
7366
			parent.init.call(this, el, options);
7367
		};
7368
		this._load_nodes = function (nodes, callback, is_callback, force_reload) {
7369
			var s = this.settings.massload,
7370
				toLoad = [],
7371
				m = this._model.data,
7372
				i, j, dom;
7373
			if (!is_callback) {
7374
				for(i = 0, j = nodes.length; i < j; i++) {
7375
					if(!m[nodes[i]] || ( (!m[nodes[i]].state.loaded && !m[nodes[i]].state.failed) || force_reload) ) {
7376
						toLoad.push(nodes[i]);
7377
						dom = this.get_node(nodes[i], true);
7378
						if (dom && dom.length) {
7379
							dom.addClass("jstree-loading").attr('aria-busy',true);
7380
						}
7381
					}
7382
				}
7383
				this._data.massload = {};
7384
				if (toLoad.length) {
7385
					if($.vakata.is_function(s)) {
7386
						return s.call(this, toLoad, function (data) {
7387
							var i, j;
7388
							if(data) {
7389
								for(i in data) {
7390
									if(data.hasOwnProperty(i)) {
7391
										this._data.massload[i] = data[i];
7392
									}
7393
								}
7394
							}
7395
							for(i = 0, j = nodes.length; i < j; i++) {
7396
								dom = this.get_node(nodes[i], true);
7397
								if (dom && dom.length) {
7398
									dom.removeClass("jstree-loading").attr('aria-busy',false);
7399
								}
7400
							}
7401
							parent._load_nodes.call(this, nodes, callback, is_callback, force_reload);
7402
						}.bind(this));
7403
					}
7404
					if(typeof s === 'object' && s && s.url) {
7405
						s = $.extend(true, {}, s);
7406
						if($.vakata.is_function(s.url)) {
7407
							s.url = s.url.call(this, toLoad);
7408
						}
7409
						if($.vakata.is_function(s.data)) {
7410
							s.data = s.data.call(this, toLoad);
7411
						}
7412
						return $.ajax(s)
7413
							.done(function (data,t,x) {
7414
									var i, j;
7415
									if(data) {
7416
										for(i in data) {
7417
											if(data.hasOwnProperty(i)) {
7418
												this._data.massload[i] = data[i];
7419
											}
7420
										}
7421
									}
7422
									for(i = 0, j = nodes.length; i < j; i++) {
7423
										dom = this.get_node(nodes[i], true);
7424
										if (dom && dom.length) {
7425
											dom.removeClass("jstree-loading").attr('aria-busy',false);
7426
										}
7427
									}
7428
									parent._load_nodes.call(this, nodes, callback, is_callback, force_reload);
7429
								}.bind(this))
7430
							.fail(function (f) {
7431
									parent._load_nodes.call(this, nodes, callback, is_callback, force_reload);
7432
								}.bind(this));
7433
					}
7434
				}
7435
			}
7436
			return parent._load_nodes.call(this, nodes, callback, is_callback, force_reload);
7437
		};
7438
		this._load_node = function (obj, callback) {
7439
			var data = this._data.massload[obj.id],
7440
				rslt = null, dom;
7441
			if(data) {
7442
				rslt = this[typeof data === 'string' ? '_append_html_data' : '_append_json_data'](
7443
					obj,
7444
					typeof data === 'string' ? $($.parseHTML(data)).filter(function () { return this.nodeType !== 3; }) : data,
7445
					function (status) { callback.call(this, status); }
7446
				);
7447
				dom = this.get_node(obj.id, true);
7448
				if (dom && dom.length) {
7449
					dom.removeClass("jstree-loading").attr('aria-busy',false);
7450
				}
7451
				delete this._data.massload[obj.id];
7452
				return rslt;
7453
			}
7454
			return parent._load_node.call(this, obj, callback);
7455
		};
7456
	};
7457
 
7458
 
7459
/**
7460
 * ### Search plugin
7461
 *
7462
 * Adds search functionality to jsTree.
7463
 */
7464
 
7465
	/**
7466
	 * stores all defaults for the search plugin
7467
	 * @name $.jstree.defaults.search
7468
	 * @plugin search
7469
	 */
7470
	$.jstree.defaults.search = {
7471
		/**
7472
		 * a jQuery-like AJAX config, which jstree uses if a server should be queried for results.
7473
		 *
7474
		 * A `str` (which is the search string) parameter will be added with the request, an optional `inside` parameter will be added if the search is limited to a node id. The expected result is a JSON array with nodes that need to be opened so that matching nodes will be revealed.
7475
		 * Leave this setting as `false` to not query the server. You can also set this to a function, which will be invoked in the instance's scope and receive 3 parameters - the search string, the callback to call with the array of nodes to load, and the optional node ID to limit the search to
7476
		 * @name $.jstree.defaults.search.ajax
7477
		 * @plugin search
7478
		 */
7479
		ajax : false,
7480
		/**
7481
		 * Indicates if the search should be fuzzy or not (should `chnd3` match `child node 3`). Default is `false`.
7482
		 * @name $.jstree.defaults.search.fuzzy
7483
		 * @plugin search
7484
		 */
7485
		fuzzy : false,
7486
		/**
7487
		 * Indicates if the search should be case sensitive. Default is `false`.
7488
		 * @name $.jstree.defaults.search.case_sensitive
7489
		 * @plugin search
7490
		 */
7491
		case_sensitive : false,
7492
		/**
7493
		 * Indicates if the tree should be filtered (by default) to show only matching nodes (keep in mind this can be a heavy on large trees in old browsers).
7494
		 * This setting can be changed at runtime when calling the search method. Default is `false`.
7495
		 * @name $.jstree.defaults.search.show_only_matches
7496
		 * @plugin search
7497
		 */
7498
		show_only_matches : false,
7499
		/**
7500
		 * Indicates if the children of matched element are shown (when show_only_matches is true)
7501
		 * This setting can be changed at runtime when calling the search method. Default is `false`.
7502
		 * @name $.jstree.defaults.search.show_only_matches_children
7503
		 * @plugin search
7504
		 */
7505
		show_only_matches_children : false,
7506
		/**
7507
		 * Indicates if all nodes opened to reveal the search result, should be closed when the search is cleared or a new search is performed. Default is `true`.
7508
		 * @name $.jstree.defaults.search.close_opened_onclear
7509
		 * @plugin search
7510
		 */
7511
		close_opened_onclear : true,
7512
		/**
7513
		 * Indicates if only leaf nodes should be included in search results. Default is `false`.
7514
		 * @name $.jstree.defaults.search.search_leaves_only
7515
		 * @plugin search
7516
		 */
7517
		search_leaves_only : false,
7518
		/**
7519
		 * If set to a function it wil be called in the instance's scope with two arguments - search string and node (where node will be every node in the structure, so use with caution).
7520
		 * If the function returns a truthy value the node will be considered a match (it might not be displayed if search_only_leaves is set to true and the node is not a leaf). Default is `false`.
7521
		 * @name $.jstree.defaults.search.search_callback
7522
		 * @plugin search
7523
		 */
7524
		search_callback : false
7525
	};
7526
 
7527
	$.jstree.plugins.search = function (options, parent) {
7528
		this.bind = function () {
7529
			parent.bind.call(this);
7530
 
7531
			this._data.search.str = "";
7532
			this._data.search.dom = $();
7533
			this._data.search.res = [];
7534
			this._data.search.opn = [];
7535
			this._data.search.som = false;
7536
			this._data.search.smc = false;
7537
			this._data.search.hdn = [];
7538
 
7539
			this.element
7540
				.on("search.jstree", function (e, data) {
7541
						if(this._data.search.som && data.res.length) {
7542
							var m = this._model.data, i, j, p = [], k, l;
7543
							for(i = 0, j = data.res.length; i < j; i++) {
7544
								if(m[data.res[i]] && !m[data.res[i]].state.hidden) {
7545
									p.push(data.res[i]);
7546
									p = p.concat(m[data.res[i]].parents);
7547
									if(this._data.search.smc) {
7548
										for (k = 0, l = m[data.res[i]].children_d.length; k < l; k++) {
7549
											if (m[m[data.res[i]].children_d[k]] && !m[m[data.res[i]].children_d[k]].state.hidden) {
7550
												p.push(m[data.res[i]].children_d[k]);
7551
											}
7552
										}
7553
									}
7554
								}
7555
							}
7556
							p = $.vakata.array_remove_item($.vakata.array_unique(p), $.jstree.root);
7557
							this._data.search.hdn = this.hide_all(true);
7558
							this.show_node(p, true);
7559
							this.redraw(true);
7560
						}
7561
					}.bind(this))
7562
				.on("clear_search.jstree", function (e, data) {
7563
						if(this._data.search.som && data.res.length) {
7564
							this.show_node(this._data.search.hdn, true);
7565
							this.redraw(true);
7566
						}
7567
					}.bind(this));
7568
		};
7569
		/**
7570
		 * used to search the tree nodes for a given string
7571
		 * @name search(str [, skip_async])
7572
		 * @param {String} str the search string
7573
		 * @param {Boolean} skip_async if set to true server will not be queried even if configured
7574
		 * @param {Boolean} show_only_matches if set to true only matching nodes will be shown (keep in mind this can be very slow on large trees or old browsers)
7575
		 * @param {mixed} inside an optional node to whose children to limit the search
7576
		 * @param {Boolean} append if set to true the results of this search are appended to the previous search
7577
		 * @plugin search
7578
		 * @trigger search.jstree
7579
		 */
7580
		this.search = function (str, skip_async, show_only_matches, inside, append, show_only_matches_children) {
7581
			if(str === false || $.vakata.trim(str.toString()) === "") {
7582
				return this.clear_search();
7583
			}
7584
			inside = this.get_node(inside);
7585
			inside = inside && inside.id ? inside.id : null;
7586
			str = str.toString();
7587
			var s = this.settings.search,
7588
				a = s.ajax ? s.ajax : false,
7589
				m = this._model.data,
7590
				f = null,
7591
				r = [],
7592
				p = [], i, j;
7593
			if(this._data.search.res.length && !append) {
7594
				this.clear_search();
7595
			}
7596
			if(show_only_matches === undefined) {
7597
				show_only_matches = s.show_only_matches;
7598
			}
7599
			if(show_only_matches_children === undefined) {
7600
				show_only_matches_children = s.show_only_matches_children;
7601
			}
7602
			if(!skip_async && a !== false) {
7603
				if($.vakata.is_function(a)) {
7604
					return a.call(this, str, function (d) {
7605
							if(d && d.d) { d = d.d; }
7606
							this._load_nodes(!$.vakata.is_array(d) ? [] : $.vakata.array_unique(d), function () {
7607
								this.search(str, true, show_only_matches, inside, append, show_only_matches_children);
7608
							});
7609
						}.bind(this), inside);
7610
				}
7611
				else {
7612
					a = $.extend({}, a);
7613
					if(!a.data) { a.data = {}; }
7614
					a.data.str = str;
7615
					if(inside) {
7616
						a.data.inside = inside;
7617
					}
7618
					if (this._data.search.lastRequest) {
7619
						this._data.search.lastRequest.abort();
7620
					}
7621
					this._data.search.lastRequest = $.ajax(a)
7622
						.fail(function () {
7623
							this._data.core.last_error = { 'error' : 'ajax', 'plugin' : 'search', 'id' : 'search_01', 'reason' : 'Could not load search parents', 'data' : JSON.stringify(a) };
7624
							this.settings.core.error.call(this, this._data.core.last_error);
7625
						}.bind(this))
7626
						.done(function (d) {
7627
							if(d && d.d) { d = d.d; }
7628
							this._load_nodes(!$.vakata.is_array(d) ? [] : $.vakata.array_unique(d), function () {
7629
								this.search(str, true, show_only_matches, inside, append, show_only_matches_children);
7630
							});
7631
						}.bind(this));
7632
					return this._data.search.lastRequest;
7633
				}
7634
			}
7635
			if(!append) {
7636
				this._data.search.str = str;
7637
				this._data.search.dom = $();
7638
				this._data.search.res = [];
7639
				this._data.search.opn = [];
7640
				this._data.search.som = show_only_matches;
7641
				this._data.search.smc = show_only_matches_children;
7642
			}
7643
 
7644
			f = new $.vakata.search(str, true, { caseSensitive : s.case_sensitive, fuzzy : s.fuzzy });
7645
			$.each(m[inside ? inside : $.jstree.root].children_d, function (ii, i) {
7646
				var v = m[i];
7647
				if(v.text && !v.state.hidden && (!s.search_leaves_only || (v.state.loaded && v.children.length === 0)) && ( (s.search_callback && s.search_callback.call(this, str, v)) || (!s.search_callback && f.search(v.text).isMatch) ) ) {
7648
					r.push(i);
7649
					p = p.concat(v.parents);
7650
				}
7651
			});
7652
			if(r.length) {
7653
				p = $.vakata.array_unique(p);
7654
				for(i = 0, j = p.length; i < j; i++) {
7655
					if(p[i] !== $.jstree.root && m[p[i]] && this.open_node(p[i], null, 0) === true) {
7656
						this._data.search.opn.push(p[i]);
7657
					}
7658
				}
7659
				if(!append) {
7660
					this._data.search.dom = $(this.element[0].querySelectorAll('#' + $.map(r, function (v) { return "0123456789".indexOf(v[0]) !== -1 ? '\\3' + v[0] + ' ' + v.substr(1).replace($.jstree.idregex,'\\$&') : v.replace($.jstree.idregex,'\\$&'); }).join(', #')));
7661
					this._data.search.res = r;
7662
				}
7663
				else {
7664
					this._data.search.dom = this._data.search.dom.add($(this.element[0].querySelectorAll('#' + $.map(r, function (v) { return "0123456789".indexOf(v[0]) !== -1 ? '\\3' + v[0] + ' ' + v.substr(1).replace($.jstree.idregex,'\\$&') : v.replace($.jstree.idregex,'\\$&'); }).join(', #'))));
7665
					this._data.search.res = $.vakata.array_unique(this._data.search.res.concat(r));
7666
				}
7667
				this._data.search.dom.children(".jstree-anchor").addClass('jstree-search');
7668
			}
7669
			/**
7670
			 * triggered after search is complete
7671
			 * @event
7672
			 * @name search.jstree
7673
			 * @param {jQuery} nodes a jQuery collection of matching nodes
7674
			 * @param {String} str the search string
7675
			 * @param {Array} res a collection of objects represeing the matching nodes
7676
			 * @plugin search
7677
			 */
7678
			this.trigger('search', { nodes : this._data.search.dom, str : str, res : this._data.search.res, show_only_matches : show_only_matches });
7679
		};
7680
		/**
7681
		 * used to clear the last search (removes classes and shows all nodes if filtering is on)
7682
		 * @name clear_search()
7683
		 * @plugin search
7684
		 * @trigger clear_search.jstree
7685
		 */
7686
		this.clear_search = function () {
7687
			if(this.settings.search.close_opened_onclear) {
7688
				this.close_node(this._data.search.opn, 0);
7689
			}
7690
			/**
7691
			 * triggered after search is complete
7692
			 * @event
7693
			 * @name clear_search.jstree
7694
			 * @param {jQuery} nodes a jQuery collection of matching nodes (the result from the last search)
7695
			 * @param {String} str the search string (the last search string)
7696
			 * @param {Array} res a collection of objects represeing the matching nodes (the result from the last search)
7697
			 * @plugin search
7698
			 */
7699
			this.trigger('clear_search', { 'nodes' : this._data.search.dom, str : this._data.search.str, res : this._data.search.res });
7700
			if(this._data.search.res.length) {
7701
				this._data.search.dom = $(this.element[0].querySelectorAll('#' + $.map(this._data.search.res, function (v) {
7702
					return "0123456789".indexOf(v[0]) !== -1 ? '\\3' + v[0] + ' ' + v.substr(1).replace($.jstree.idregex,'\\$&') : v.replace($.jstree.idregex,'\\$&');
7703
				}).join(', #')));
7704
				this._data.search.dom.children(".jstree-anchor").removeClass("jstree-search");
7705
			}
7706
			this._data.search.str = "";
7707
			this._data.search.res = [];
7708
			this._data.search.opn = [];
7709
			this._data.search.dom = $();
7710
		};
7711
 
7712
		this.redraw_node = function(obj, deep, callback, force_render) {
7713
			obj = parent.redraw_node.apply(this, arguments);
7714
			if(obj) {
7715
				if($.inArray(obj.id, this._data.search.res) !== -1) {
7716
					var i, j, tmp = null;
7717
					for(i = 0, j = obj.childNodes.length; i < j; i++) {
7718
						if(obj.childNodes[i] && obj.childNodes[i].className && obj.childNodes[i].className.indexOf("jstree-anchor") !== -1) {
7719
							tmp = obj.childNodes[i];
7720
							break;
7721
						}
7722
					}
7723
					if(tmp) {
7724
						tmp.className += ' jstree-search';
7725
					}
7726
				}
7727
			}
7728
			return obj;
7729
		};
7730
	};
7731
 
7732
	// helpers
7733
	(function ($) {
7734
		// from http://kiro.me/projects/fuse.html
7735
		$.vakata.search = function(pattern, txt, options) {
7736
			options = options || {};
7737
			options = $.extend({}, $.vakata.search.defaults, options);
7738
			if(options.fuzzy !== false) {
7739
				options.fuzzy = true;
7740
			}
7741
			pattern = options.caseSensitive ? pattern : pattern.toLowerCase();
7742
			var MATCH_LOCATION	= options.location,
7743
				MATCH_DISTANCE	= options.distance,
7744
				MATCH_THRESHOLD	= options.threshold,
7745
				patternLen = pattern.length,
7746
				matchmask, pattern_alphabet, match_bitapScore, search;
7747
			if(patternLen > 32) {
7748
				options.fuzzy = false;
7749
			}
7750
			if(options.fuzzy) {
7751
				matchmask = 1 << (patternLen - 1);
7752
				pattern_alphabet = (function () {
7753
					var mask = {},
7754
						i = 0;
7755
					for (i = 0; i < patternLen; i++) {
7756
						mask[pattern.charAt(i)] = 0;
7757
					}
7758
					for (i = 0; i < patternLen; i++) {
7759
						mask[pattern.charAt(i)] |= 1 << (patternLen - i - 1);
7760
					}
7761
					return mask;
7762
				}());
7763
				match_bitapScore = function (e, x) {
7764
					var accuracy = e / patternLen,
7765
						proximity = Math.abs(MATCH_LOCATION - x);
7766
					if(!MATCH_DISTANCE) {
7767
						return proximity ? 1.0 : accuracy;
7768
					}
7769
					return accuracy + (proximity / MATCH_DISTANCE);
7770
				};
7771
			}
7772
			search = function (text) {
7773
				text = options.caseSensitive ? text : text.toLowerCase();
7774
				if(pattern === text || text.indexOf(pattern) !== -1) {
7775
					return {
7776
						isMatch: true,
7777
						score: 0
7778
					};
7779
				}
7780
				if(!options.fuzzy) {
7781
					return {
7782
						isMatch: false,
7783
						score: 1
7784
					};
7785
				}
7786
				var i, j,
7787
					textLen = text.length,
7788
					scoreThreshold = MATCH_THRESHOLD,
7789
					bestLoc = text.indexOf(pattern, MATCH_LOCATION),
7790
					binMin, binMid,
7791
					binMax = patternLen + textLen,
7792
					lastRd, start, finish, rd, charMatch,
7793
					score = 1,
7794
					locations = [];
7795
				if (bestLoc !== -1) {
7796
					scoreThreshold = Math.min(match_bitapScore(0, bestLoc), scoreThreshold);
7797
					bestLoc = text.lastIndexOf(pattern, MATCH_LOCATION + patternLen);
7798
					if (bestLoc !== -1) {
7799
						scoreThreshold = Math.min(match_bitapScore(0, bestLoc), scoreThreshold);
7800
					}
7801
				}
7802
				bestLoc = -1;
7803
				for (i = 0; i < patternLen; i++) {
7804
					binMin = 0;
7805
					binMid = binMax;
7806
					while (binMin < binMid) {
7807
						if (match_bitapScore(i, MATCH_LOCATION + binMid) <= scoreThreshold) {
7808
							binMin = binMid;
7809
						} else {
7810
							binMax = binMid;
7811
						}
7812
						binMid = Math.floor((binMax - binMin) / 2 + binMin);
7813
					}
7814
					binMax = binMid;
7815
					start = Math.max(1, MATCH_LOCATION - binMid + 1);
7816
					finish = Math.min(MATCH_LOCATION + binMid, textLen) + patternLen;
7817
					rd = new Array(finish + 2);
7818
					rd[finish + 1] = (1 << i) - 1;
7819
					for (j = finish; j >= start; j--) {
7820
						charMatch = pattern_alphabet[text.charAt(j - 1)];
7821
						if (i === 0) {
7822
							rd[j] = ((rd[j + 1] << 1) | 1) & charMatch;
7823
						} else {
7824
							rd[j] = ((rd[j + 1] << 1) | 1) & charMatch | (((lastRd[j + 1] | lastRd[j]) << 1) | 1) | lastRd[j + 1];
7825
						}
7826
						if (rd[j] & matchmask) {
7827
							score = match_bitapScore(i, j - 1);
7828
							if (score <= scoreThreshold) {
7829
								scoreThreshold = score;
7830
								bestLoc = j - 1;
7831
								locations.push(bestLoc);
7832
								if (bestLoc > MATCH_LOCATION) {
7833
									start = Math.max(1, 2 * MATCH_LOCATION - bestLoc);
7834
								} else {
7835
									break;
7836
								}
7837
							}
7838
						}
7839
					}
7840
					if (match_bitapScore(i + 1, MATCH_LOCATION) > scoreThreshold) {
7841
						break;
7842
					}
7843
					lastRd = rd;
7844
				}
7845
				return {
7846
					isMatch: bestLoc >= 0,
7847
					score: score
7848
				};
7849
			};
7850
			return txt === true ? { 'search' : search } : search(txt);
7851
		};
7852
		$.vakata.search.defaults = {
7853
			location : 0,
7854
			distance : 100,
7855
			threshold : 0.6,
7856
			fuzzy : false,
7857
			caseSensitive : false
7858
		};
7859
	}($));
7860
 
7861
	// include the search plugin by default
7862
	// $.jstree.defaults.plugins.push("search");
7863
 
7864
 
7865
/**
7866
 * ### Sort plugin
7867
 *
7868
 * Automatically sorts all siblings in the tree according to a sorting function.
7869
 */
7870
 
7871
	/**
7872
	 * the settings function used to sort the nodes.
7873
	 * It is executed in the tree's context, accepts two nodes as arguments and should return `1` or `-1`.
7874
	 * @name $.jstree.defaults.sort
7875
	 * @plugin sort
7876
	 */
7877
	$.jstree.defaults.sort = function (a, b) {
7878
		//return this.get_type(a) === this.get_type(b) ? (this.get_text(a) > this.get_text(b) ? 1 : -1) : this.get_type(a) >= this.get_type(b);
7879
		return this.get_text(a) > this.get_text(b) ? 1 : -1;
7880
	};
7881
	$.jstree.plugins.sort = function (options, parent) {
7882
		this.bind = function () {
7883
			parent.bind.call(this);
7884
			this.element
7885
				.on("model.jstree", function (e, data) {
7886
						this.sort(data.parent, true);
7887
					}.bind(this))
7888
				.on("rename_node.jstree create_node.jstree", function (e, data) {
7889
						this.sort(data.parent || data.node.parent, false);
7890
						this.redraw_node(data.parent || data.node.parent, true);
7891
					}.bind(this))
7892
				.on("move_node.jstree copy_node.jstree", function (e, data) {
7893
						this.sort(data.parent, false);
7894
						this.redraw_node(data.parent, true);
7895
					}.bind(this));
7896
		};
7897
		/**
7898
		 * used to sort a node's children
7899
		 * @private
7900
		 * @name sort(obj [, deep])
7901
		 * @param  {mixed} obj the node
7902
		 * @param {Boolean} deep if set to `true` nodes are sorted recursively.
7903
		 * @plugin sort
7904
		 * @trigger search.jstree
7905
		 */
7906
		this.sort = function (obj, deep) {
7907
			var i, j;
7908
			obj = this.get_node(obj);
7909
			if(obj && obj.children && obj.children.length) {
7910
				obj.children.sort(this.settings.sort.bind(this));
7911
				if(deep) {
7912
					for(i = 0, j = obj.children_d.length; i < j; i++) {
7913
						this.sort(obj.children_d[i], false);
7914
					}
7915
				}
7916
			}
7917
		};
7918
	};
7919
 
7920
	// include the sort plugin by default
7921
	// $.jstree.defaults.plugins.push("sort");
7922
 
7923
/**
7924
 * ### State plugin
7925
 *
7926
 * Saves the state of the tree (selected nodes, opened nodes) on the user's computer using available options (localStorage, cookies, etc)
7927
 */
7928
 
7929
	var to = false;
7930
	/**
7931
	 * stores all defaults for the state plugin
7932
	 * @name $.jstree.defaults.state
7933
	 * @plugin state
7934
	 */
7935
	$.jstree.defaults.state = {
7936
		/**
7937
		 * A string for the key to use when saving the current tree (change if using multiple trees in your project). Defaults to `jstree`.
7938
		 * @name $.jstree.defaults.state.key
7939
		 * @plugin state
7940
		 */
7941
		key		: 'jstree',
7942
		/**
7943
		 * A space separated list of events that trigger a state save. Defaults to `changed.jstree open_node.jstree close_node.jstree`.
7944
		 * @name $.jstree.defaults.state.events
7945
		 * @plugin state
7946
		 */
7947
		events	: 'changed.jstree open_node.jstree close_node.jstree check_node.jstree uncheck_node.jstree',
7948
		/**
7949
		 * Time in milliseconds after which the state will expire. Defaults to 'false' meaning - no expire.
7950
		 * @name $.jstree.defaults.state.ttl
7951
		 * @plugin state
7952
		 */
7953
		ttl		: false,
7954
		/**
7955
		 * A function that will be executed prior to restoring state with one argument - the state object. Can be used to clear unwanted parts of the state.
7956
		 * @name $.jstree.defaults.state.filter
7957
		 * @plugin state
7958
		 */
7959
		filter	: false,
7960
		/**
7961
		 * Should loaded nodes be restored (setting this to true means that it is possible that the whole tree will be loaded for some users - use with caution). Defaults to `false`
7962
		 * @name $.jstree.defaults.state.preserve_loaded
7963
		 * @plugin state
7964
		 */
7965
		preserve_loaded : false
7966
	};
7967
	$.jstree.plugins.state = function (options, parent) {
7968
		this.bind = function () {
7969
			parent.bind.call(this);
7970
			var bind = function () {
7971
				this.element.on(this.settings.state.events, function () {
7972
					if(to) { clearTimeout(to); }
7973
					to = setTimeout(function () { this.save_state(); }.bind(this), 100);
7974
				}.bind(this));
7975
				/**
7976
				 * triggered when the state plugin is finished restoring the state (and immediately after ready if there is no state to restore).
7977
				 * @event
7978
				 * @name state_ready.jstree
7979
				 * @plugin state
7980
				 */
7981
				this.trigger('state_ready');
7982
			}.bind(this);
7983
			this.element
7984
				.on("ready.jstree", function (e, data) {
7985
						this.element.one("restore_state.jstree", bind);
7986
						if(!this.restore_state()) { bind(); }
7987
					}.bind(this));
7988
		};
7989
		/**
7990
		 * save the state
7991
		 * @name save_state()
7992
		 * @plugin state
7993
		 */
7994
		this.save_state = function () {
7995
			var tm = this.get_state();
7996
			if (!this.settings.state.preserve_loaded) {
7997
				delete tm.core.loaded;
7998
			}
7999
			var st = { 'state' : tm, 'ttl' : this.settings.state.ttl, 'sec' : +(new Date()) };
8000
			$.vakata.storage.set(this.settings.state.key, JSON.stringify(st));
8001
		};
8002
		/**
8003
		 * restore the state from the user's computer
8004
		 * @name restore_state()
8005
		 * @plugin state
8006
		 */
8007
		this.restore_state = function () {
8008
			var k = $.vakata.storage.get(this.settings.state.key);
8009
			if(!!k) { try { k = JSON.parse(k); } catch(ex) { return false; } }
8010
			if(!!k && k.ttl && k.sec && +(new Date()) - k.sec > k.ttl) { return false; }
8011
			if(!!k && k.state) { k = k.state; }
8012
			if(!!k && $.vakata.is_function(this.settings.state.filter)) { k = this.settings.state.filter.call(this, k); }
8013
			if(!!k) {
8014
				if (!this.settings.state.preserve_loaded) {
8015
					delete k.core.loaded;
8016
				}
8017
				this.element.one("set_state.jstree", function (e, data) { data.instance.trigger('restore_state', { 'state' : $.extend(true, {}, k) }); });
8018
				this.set_state(k);
8019
				return true;
8020
			}
8021
			return false;
8022
		};
8023
		/**
8024
		 * clear the state on the user's computer
8025
		 * @name clear_state()
8026
		 * @plugin state
8027
		 */
8028
		this.clear_state = function () {
8029
			return $.vakata.storage.del(this.settings.state.key);
8030
		};
8031
	};
8032
 
8033
	(function ($, undefined) {
8034
		$.vakata.storage = {
8035
			// simply specifying the functions in FF throws an error
8036
			set : function (key, val) { return window.localStorage.setItem(key, val); },
8037
			get : function (key) { return window.localStorage.getItem(key); },
8038
			del : function (key) { return window.localStorage.removeItem(key); }
8039
		};
8040
	}($));
8041
 
8042
	// include the state plugin by default
8043
	// $.jstree.defaults.plugins.push("state");
8044
 
8045
/**
8046
 * ### Types plugin
8047
 *
8048
 * Makes it possible to add predefined types for groups of nodes, which make it possible to easily control nesting rules and icon for each group.
8049
 */
8050
 
8051
	/**
8052
	 * An object storing all types as key value pairs, where the key is the type name and the value is an object that could contain following keys (all optional).
8053
	 *
8054
	 * * `max_children` the maximum number of immediate children this node type can have. Do not specify or set to `-1` for unlimited.
8055
	 * * `max_depth` the maximum number of nesting this node type can have. A value of `1` would mean that the node can have children, but no grandchildren. Do not specify or set to `-1` for unlimited.
8056
	 * * `valid_children` an array of node type strings, that nodes of this type can have as children. Do not specify or set to `-1` for no limits.
8057
	 * * `icon` a string - can be a path to an icon or a className, if using an image that is in the current directory use a `./` prefix, otherwise it will be detected as a class. Omit to use the default icon from your theme.
8058
	 * * `li_attr` an object of values which will be used to add HTML attributes on the resulting LI DOM node (merged with the node's own data)
8059
	 * * `a_attr` an object of values which will be used to add HTML attributes on the resulting A DOM node (merged with the node's own data)
8060
	 *
8061
	 * There are two predefined types:
8062
	 *
8063
	 * * `#` represents the root of the tree, for example `max_children` would control the maximum number of root nodes.
8064
	 * * `default` represents the default node - any settings here will be applied to all nodes that do not have a type specified.
8065
	 *
8066
	 * @name $.jstree.defaults.types
8067
	 * @plugin types
8068
	 */
8069
	$.jstree.defaults.types = {
8070
		'default' : {}
8071
	};
8072
	$.jstree.defaults.types[$.jstree.root] = {};
8073
 
8074
	$.jstree.plugins.types = function (options, parent) {
8075
		this.init = function (el, options) {
8076
			var i, j;
8077
			if(options && options.types && options.types['default']) {
8078
				for(i in options.types) {
8079
					if(i !== "default" && i !== $.jstree.root && options.types.hasOwnProperty(i)) {
8080
						for(j in options.types['default']) {
8081
							if(options.types['default'].hasOwnProperty(j) && options.types[i][j] === undefined) {
8082
								options.types[i][j] = options.types['default'][j];
8083
							}
8084
						}
8085
					}
8086
				}
8087
			}
8088
			parent.init.call(this, el, options);
8089
			this._model.data[$.jstree.root].type = $.jstree.root;
8090
		};
8091
		this.refresh = function (skip_loading, forget_state) {
8092
			parent.refresh.call(this, skip_loading, forget_state);
8093
			this._model.data[$.jstree.root].type = $.jstree.root;
8094
		};
8095
		this.bind = function () {
8096
			this.element
8097
				.on('model.jstree', function (e, data) {
8098
						var m = this._model.data,
8099
							dpc = data.nodes,
8100
							t = this.settings.types,
8101
							i, j, c = 'default', k;
8102
						for(i = 0, j = dpc.length; i < j; i++) {
8103
							c = 'default';
8104
							if(m[dpc[i]].original && m[dpc[i]].original.type && t[m[dpc[i]].original.type]) {
8105
								c = m[dpc[i]].original.type;
8106
							}
8107
							if(m[dpc[i]].data && m[dpc[i]].data.jstree && m[dpc[i]].data.jstree.type && t[m[dpc[i]].data.jstree.type]) {
8108
								c = m[dpc[i]].data.jstree.type;
8109
							}
8110
							m[dpc[i]].type = c;
8111
							if(m[dpc[i]].icon === true && t[c].icon !== undefined) {
8112
								m[dpc[i]].icon = t[c].icon;
8113
							}
8114
							if(t[c].li_attr !== undefined && typeof t[c].li_attr === 'object') {
8115
								for (k in t[c].li_attr) {
8116
									if (t[c].li_attr.hasOwnProperty(k)) {
8117
										if (k === 'id') {
8118
											continue;
8119
										}
8120
										else if (m[dpc[i]].li_attr[k] === undefined) {
8121
											m[dpc[i]].li_attr[k] = t[c].li_attr[k];
8122
										}
8123
										else if (k === 'class') {
8124
											m[dpc[i]].li_attr['class'] = t[c].li_attr['class'] + ' ' + m[dpc[i]].li_attr['class'];
8125
										}
8126
									}
8127
								}
8128
							}
8129
							if(t[c].a_attr !== undefined && typeof t[c].a_attr === 'object') {
8130
								for (k in t[c].a_attr) {
8131
									if (t[c].a_attr.hasOwnProperty(k)) {
8132
										if (k === 'id') {
8133
											continue;
8134
										}
8135
										else if (m[dpc[i]].a_attr[k] === undefined) {
8136
											m[dpc[i]].a_attr[k] = t[c].a_attr[k];
8137
										}
8138
										else if (k === 'href' && m[dpc[i]].a_attr[k] === '#') {
8139
											m[dpc[i]].a_attr['href'] = t[c].a_attr['href'];
8140
										}
8141
										else if (k === 'class') {
8142
											m[dpc[i]].a_attr['class'] = t[c].a_attr['class'] + ' ' + m[dpc[i]].a_attr['class'];
8143
										}
8144
									}
8145
								}
8146
							}
8147
						}
8148
						m[$.jstree.root].type = $.jstree.root;
8149
					}.bind(this));
8150
			parent.bind.call(this);
8151
		};
8152
		this.get_json = function (obj, options, flat) {
8153
			var i, j,
8154
				m = this._model.data,
8155
				opt = options ? $.extend(true, {}, options, {no_id:false}) : {},
8156
				tmp = parent.get_json.call(this, obj, opt, flat);
8157
			if(tmp === false) { return false; }
8158
			if($.vakata.is_array(tmp)) {
8159
				for(i = 0, j = tmp.length; i < j; i++) {
8160
					tmp[i].type = tmp[i].id && m[tmp[i].id] && m[tmp[i].id].type ? m[tmp[i].id].type : "default";
8161
					if(options && options.no_id) {
8162
						delete tmp[i].id;
8163
						if(tmp[i].li_attr && tmp[i].li_attr.id) {
8164
							delete tmp[i].li_attr.id;
8165
						}
8166
						if(tmp[i].a_attr && tmp[i].a_attr.id) {
8167
							delete tmp[i].a_attr.id;
8168
						}
8169
					}
8170
				}
8171
			}
8172
			else {
8173
				tmp.type = tmp.id && m[tmp.id] && m[tmp.id].type ? m[tmp.id].type : "default";
8174
				if(options && options.no_id) {
8175
					tmp = this._delete_ids(tmp);
8176
				}
8177
			}
8178
			return tmp;
8179
		};
8180
		this._delete_ids = function (tmp) {
8181
			if($.vakata.is_array(tmp)) {
8182
				for(var i = 0, j = tmp.length; i < j; i++) {
8183
					tmp[i] = this._delete_ids(tmp[i]);
8184
				}
8185
				return tmp;
8186
			}
8187
			delete tmp.id;
8188
			if(tmp.li_attr && tmp.li_attr.id) {
8189
				delete tmp.li_attr.id;
8190
			}
8191
			if(tmp.a_attr && tmp.a_attr.id) {
8192
				delete tmp.a_attr.id;
8193
			}
8194
			if(tmp.children && $.vakata.is_array(tmp.children)) {
8195
				tmp.children = this._delete_ids(tmp.children);
8196
			}
8197
			return tmp;
8198
		};
8199
		this.check = function (chk, obj, par, pos, more) {
8200
			if(parent.check.call(this, chk, obj, par, pos, more) === false) { return false; }
8201
			obj = obj && obj.id ? obj : this.get_node(obj);
8202
			par = par && par.id ? par : this.get_node(par);
8203
			var m = obj && obj.id ? (more && more.origin ? more.origin : $.jstree.reference(obj.id)) : null, tmp, d, i, j;
8204
			m = m && m._model && m._model.data ? m._model.data : null;
8205
			switch(chk) {
8206
				case "create_node":
8207
				case "move_node":
8208
				case "copy_node":
8209
					if(chk !== 'move_node' || $.inArray(obj.id, par.children) === -1) {
8210
						tmp = this.get_rules(par);
8211
						if(tmp.max_children !== undefined && tmp.max_children !== -1 && tmp.max_children === par.children.length) {
8212
							this._data.core.last_error = { 'error' : 'check', 'plugin' : 'types', 'id' : 'types_01', 'reason' : 'max_children prevents function: ' + chk, 'data' : JSON.stringify({ 'chk' : chk, 'pos' : pos, 'obj' : obj && obj.id ? obj.id : false, 'par' : par && par.id ? par.id : false }) };
8213
							return false;
8214
						}
8215
						if(tmp.valid_children !== undefined && tmp.valid_children !== -1 && $.inArray((obj.type || 'default'), tmp.valid_children) === -1) {
8216
							this._data.core.last_error = { 'error' : 'check', 'plugin' : 'types', 'id' : 'types_02', 'reason' : 'valid_children prevents function: ' + chk, 'data' : JSON.stringify({ 'chk' : chk, 'pos' : pos, 'obj' : obj && obj.id ? obj.id : false, 'par' : par && par.id ? par.id : false }) };
8217
							return false;
8218
						}
8219
						if(m && obj.children_d && obj.parents) {
8220
							d = 0;
8221
							for(i = 0, j = obj.children_d.length; i < j; i++) {
8222
								d = Math.max(d, m[obj.children_d[i]].parents.length);
8223
							}
8224
							d = d - obj.parents.length + 1;
8225
						}
8226
						if(d <= 0 || d === undefined) { d = 1; }
8227
						do {
8228
							if(tmp.max_depth !== undefined && tmp.max_depth !== -1 && tmp.max_depth < d) {
8229
								this._data.core.last_error = { 'error' : 'check', 'plugin' : 'types', 'id' : 'types_03', 'reason' : 'max_depth prevents function: ' + chk, 'data' : JSON.stringify({ 'chk' : chk, 'pos' : pos, 'obj' : obj && obj.id ? obj.id : false, 'par' : par && par.id ? par.id : false }) };
8230
								return false;
8231
							}
8232
							par = this.get_node(par.parent);
8233
							tmp = this.get_rules(par);
8234
							d++;
8235
						} while(par);
8236
					}
8237
					break;
8238
			}
8239
			return true;
8240
		};
8241
		/**
8242
		 * used to retrieve the type settings object for a node
8243
		 * @name get_rules(obj)
8244
		 * @param {mixed} obj the node to find the rules for
8245
		 * @return {Object}
8246
		 * @plugin types
8247
		 */
8248
		this.get_rules = function (obj) {
8249
			obj = this.get_node(obj);
8250
			if(!obj) { return false; }
8251
			var tmp = this.get_type(obj, true);
8252
			if(tmp.max_depth === undefined) { tmp.max_depth = -1; }
8253
			if(tmp.max_children === undefined) { tmp.max_children = -1; }
8254
			if(tmp.valid_children === undefined) { tmp.valid_children = -1; }
8255
			return tmp;
8256
		};
8257
		/**
8258
		 * used to retrieve the type string or settings object for a node
8259
		 * @name get_type(obj [, rules])
8260
		 * @param {mixed} obj the node to find the rules for
8261
		 * @param {Boolean} rules if set to `true` instead of a string the settings object will be returned
8262
		 * @return {String|Object}
8263
		 * @plugin types
8264
		 */
8265
		this.get_type = function (obj, rules) {
8266
			obj = this.get_node(obj);
8267
			return (!obj) ? false : ( rules ? $.extend({ 'type' : obj.type }, this.settings.types[obj.type]) : obj.type);
8268
		};
8269
		/**
8270
		 * used to change a node's type
8271
		 * @name set_type(obj, type)
8272
		 * @param {mixed} obj the node to change
8273
		 * @param {String} type the new type
8274
		 * @plugin types
8275
		 */
8276
		this.set_type = function (obj, type) {
8277
			var m = this._model.data, t, t1, t2, old_type, old_icon, k, d, a;
8278
			if($.vakata.is_array(obj)) {
8279
				obj = obj.slice();
8280
				for(t1 = 0, t2 = obj.length; t1 < t2; t1++) {
8281
					this.set_type(obj[t1], type);
8282
				}
8283
				return true;
8284
			}
8285
			t = this.settings.types;
8286
			obj = this.get_node(obj);
8287
			if(!t[type] || !obj) { return false; }
8288
			d = this.get_node(obj, true);
8289
			if (d && d.length) {
8290
				a = d.children('.jstree-anchor');
8291
			}
8292
			old_type = obj.type;
8293
			old_icon = this.get_icon(obj);
8294
			obj.type = type;
8295
			if(old_icon === true || !t[old_type] || (t[old_type].icon !== undefined && old_icon === t[old_type].icon)) {
8296
				this.set_icon(obj, t[type].icon !== undefined ? t[type].icon : true);
8297
			}
8298
 
8299
			// remove old type props
8300
			if(t[old_type] && t[old_type].li_attr !== undefined && typeof t[old_type].li_attr === 'object') {
8301
				for (k in t[old_type].li_attr) {
8302
					if (t[old_type].li_attr.hasOwnProperty(k)) {
8303
						if (k === 'id') {
8304
							continue;
8305
						}
8306
						else if (k === 'class') {
8307
							m[obj.id].li_attr['class'] = (m[obj.id].li_attr['class'] || '').replace(t[old_type].li_attr[k], '');
8308
							if (d) { d.removeClass(t[old_type].li_attr[k]); }
8309
						}
8310
						else if (m[obj.id].li_attr[k] === t[old_type].li_attr[k]) {
8311
							m[obj.id].li_attr[k] = null;
8312
							if (d) { d.removeAttr(k); }
8313
						}
8314
					}
8315
				}
8316
			}
8317
			if(t[old_type] && t[old_type].a_attr !== undefined && typeof t[old_type].a_attr === 'object') {
8318
				for (k in t[old_type].a_attr) {
8319
					if (t[old_type].a_attr.hasOwnProperty(k)) {
8320
						if (k === 'id') {
8321
							continue;
8322
						}
8323
						else if (k === 'class') {
8324
							m[obj.id].a_attr['class'] = (m[obj.id].a_attr['class'] || '').replace(t[old_type].a_attr[k], '');
8325
							if (a) { a.removeClass(t[old_type].a_attr[k]); }
8326
						}
8327
						else if (m[obj.id].a_attr[k] === t[old_type].a_attr[k]) {
8328
							if (k === 'href') {
8329
								m[obj.id].a_attr[k] = '#';
8330
								if (a) { a.attr('href', '#'); }
8331
							}
8332
							else {
8333
								delete m[obj.id].a_attr[k];
8334
								if (a) { a.removeAttr(k); }
8335
							}
8336
						}
8337
					}
8338
				}
8339
			}
8340
 
8341
			// add new props
8342
			if(t[type].li_attr !== undefined && typeof t[type].li_attr === 'object') {
8343
				for (k in t[type].li_attr) {
8344
					if (t[type].li_attr.hasOwnProperty(k)) {
8345
						if (k === 'id') {
8346
							continue;
8347
						}
8348
						else if (m[obj.id].li_attr[k] === undefined) {
8349
							m[obj.id].li_attr[k] = t[type].li_attr[k];
8350
							if (d) {
8351
								if (k === 'class') {
8352
									d.addClass(t[type].li_attr[k]);
8353
								}
8354
								else {
8355
									d.attr(k, t[type].li_attr[k]);
8356
								}
8357
							}
8358
						}
8359
						else if (k === 'class') {
8360
							m[obj.id].li_attr['class'] = t[type].li_attr[k] + ' ' + m[obj.id].li_attr['class'];
8361
							if (d) { d.addClass(t[type].li_attr[k]); }
8362
						}
8363
					}
8364
				}
8365
			}
8366
			if(t[type].a_attr !== undefined && typeof t[type].a_attr === 'object') {
8367
				for (k in t[type].a_attr) {
8368
					if (t[type].a_attr.hasOwnProperty(k)) {
8369
						if (k === 'id') {
8370
							continue;
8371
						}
8372
						else if (m[obj.id].a_attr[k] === undefined) {
8373
							m[obj.id].a_attr[k] = t[type].a_attr[k];
8374
							if (a) {
8375
								if (k === 'class') {
8376
									a.addClass(t[type].a_attr[k]);
8377
								}
8378
								else {
8379
									a.attr(k, t[type].a_attr[k]);
8380
								}
8381
							}
8382
						}
8383
						else if (k === 'href' && m[obj.id].a_attr[k] === '#') {
8384
							m[obj.id].a_attr['href'] = t[type].a_attr['href'];
8385
							if (a) { a.attr('href', t[type].a_attr['href']); }
8386
						}
8387
						else if (k === 'class') {
8388
							m[obj.id].a_attr['class'] = t[type].a_attr['class'] + ' ' + m[obj.id].a_attr['class'];
8389
							if (a) { a.addClass(t[type].a_attr[k]); }
8390
						}
8391
					}
8392
				}
8393
			}
8394
 
8395
			return true;
8396
		};
8397
	};
8398
	// include the types plugin by default
8399
	// $.jstree.defaults.plugins.push("types");
8400
 
8401
 
8402
/**
8403
 * ### Unique plugin
8404
 *
8405
 * Enforces that no nodes with the same name can coexist as siblings.
8406
 */
8407
 
8408
	/**
8409
	 * stores all defaults for the unique plugin
8410
	 * @name $.jstree.defaults.unique
8411
	 * @plugin unique
8412
	 */
8413
	$.jstree.defaults.unique = {
8414
		/**
8415
		 * Indicates if the comparison should be case sensitive. Default is `false`.
8416
		 * @name $.jstree.defaults.unique.case_sensitive
8417
		 * @plugin unique
8418
		 */
8419
		case_sensitive : false,
8420
		/**
8421
		 * Indicates if white space should be trimmed before the comparison. Default is `false`.
8422
		 * @name $.jstree.defaults.unique.trim_whitespace
8423
		 * @plugin unique
8424
		 */
8425
		trim_whitespace : false,
8426
		/**
8427
		 * A callback executed in the instance's scope when a new node is created and the name is already taken, the two arguments are the conflicting name and the counter. The default will produce results like `New node (2)`.
8428
		 * @name $.jstree.defaults.unique.duplicate
8429
		 * @plugin unique
8430
		 */
8431
		duplicate : function (name, counter) {
8432
			return name + ' (' + counter + ')';
8433
		}
8434
	};
8435
 
8436
	$.jstree.plugins.unique = function (options, parent) {
8437
		this.check = function (chk, obj, par, pos, more) {
8438
			if(parent.check.call(this, chk, obj, par, pos, more) === false) { return false; }
8439
			obj = obj && obj.id ? obj : this.get_node(obj);
8440
			par = par && par.id ? par : this.get_node(par);
8441
			if(!par || !par.children) { return true; }
8442
			var n = chk === "rename_node" ? pos : obj.text,
8443
				c = [],
8444
				s = this.settings.unique.case_sensitive,
8445
				w = this.settings.unique.trim_whitespace,
8446
				m = this._model.data, i, j, t;
8447
			for(i = 0, j = par.children.length; i < j; i++) {
8448
				t = m[par.children[i]].text;
8449
				if (!s) {
8450
					t = t.toLowerCase();
8451
				}
8452
				if (w) {
8453
					t = t.replace(/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g, '');
8454
				}
8455
				c.push(t);
8456
			}
8457
			if(!s) { n = n.toLowerCase(); }
8458
			if (w) { n = n.replace(/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g, ''); }
8459
			switch(chk) {
8460
				case "delete_node":
8461
					return true;
8462
				case "rename_node":
8463
					t = obj.text || '';
8464
					if (!s) {
8465
						t = t.toLowerCase();
8466
					}
8467
					if (w) {
8468
						t = t.replace(/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g, '');
8469
					}
8470
					i = ($.inArray(n, c) === -1 || (obj.text && t === n));
8471
					if(!i) {
8472
						this._data.core.last_error = { 'error' : 'check', 'plugin' : 'unique', 'id' : 'unique_01', 'reason' : 'Child with name ' + n + ' already exists. Preventing: ' + chk, 'data' : JSON.stringify({ 'chk' : chk, 'pos' : pos, 'obj' : obj && obj.id ? obj.id : false, 'par' : par && par.id ? par.id : false }) };
8473
					}
8474
					return i;
8475
				case "create_node":
8476
					i = ($.inArray(n, c) === -1);
8477
					if(!i) {
8478
						this._data.core.last_error = { 'error' : 'check', 'plugin' : 'unique', 'id' : 'unique_04', 'reason' : 'Child with name ' + n + ' already exists. Preventing: ' + chk, 'data' : JSON.stringify({ 'chk' : chk, 'pos' : pos, 'obj' : obj && obj.id ? obj.id : false, 'par' : par && par.id ? par.id : false }) };
8479
					}
8480
					return i;
8481
				case "copy_node":
8482
					i = ($.inArray(n, c) === -1);
8483
					if(!i) {
8484
						this._data.core.last_error = { 'error' : 'check', 'plugin' : 'unique', 'id' : 'unique_02', 'reason' : 'Child with name ' + n + ' already exists. Preventing: ' + chk, 'data' : JSON.stringify({ 'chk' : chk, 'pos' : pos, 'obj' : obj && obj.id ? obj.id : false, 'par' : par && par.id ? par.id : false }) };
8485
					}
8486
					return i;
8487
				case "move_node":
8488
					i = ( (obj.parent === par.id && (!more || !more.is_multi)) || $.inArray(n, c) === -1);
8489
					if(!i) {
8490
						this._data.core.last_error = { 'error' : 'check', 'plugin' : 'unique', 'id' : 'unique_03', 'reason' : 'Child with name ' + n + ' already exists. Preventing: ' + chk, 'data' : JSON.stringify({ 'chk' : chk, 'pos' : pos, 'obj' : obj && obj.id ? obj.id : false, 'par' : par && par.id ? par.id : false }) };
8491
					}
8492
					return i;
8493
			}
8494
			return true;
8495
		};
8496
		this.create_node = function (par, node, pos, callback, is_loaded) {
8497
			if(!node || node.text === undefined) {
8498
				if(par === null) {
8499
					par = $.jstree.root;
8500
				}
8501
				par = this.get_node(par);
8502
				if(!par) {
8503
					return parent.create_node.call(this, par, node, pos, callback, is_loaded);
8504
				}
8505
				pos = pos === undefined ? "last" : pos;
8506
				if(!pos.toString().match(/^(before|after)$/) && !is_loaded && !this.is_loaded(par)) {
8507
					return parent.create_node.call(this, par, node, pos, callback, is_loaded);
8508
				}
8509
				if(!node) { node = {}; }
8510
				var tmp, n, dpc, i, j, m = this._model.data, s = this.settings.unique.case_sensitive, w = this.settings.unique.trim_whitespace, cb = this.settings.unique.duplicate, t;
8511
				n = tmp = this.get_string('New node');
8512
				dpc = [];
8513
				for(i = 0, j = par.children.length; i < j; i++) {
8514
					t = m[par.children[i]].text;
8515
					if (!s) {
8516
						t = t.toLowerCase();
8517
					}
8518
					if (w) {
8519
						t = t.replace(/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g, '');
8520
					}
8521
					dpc.push(t);
8522
				}
8523
				i = 1;
8524
				t = n;
8525
				if (!s) {
8526
					t = t.toLowerCase();
8527
				}
8528
				if (w) {
8529
					t = t.replace(/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g, '');
8530
				}
8531
				while($.inArray(t, dpc) !== -1) {
8532
					n = cb.call(this, tmp, (++i)).toString();
8533
					t = n;
8534
					if (!s) {
8535
						t = t.toLowerCase();
8536
					}
8537
					if (w) {
8538
						t = t.replace(/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g, '');
8539
					}
8540
				}
8541
				node.text = n;
8542
			}
8543
			return parent.create_node.call(this, par, node, pos, callback, is_loaded);
8544
		};
8545
	};
8546
 
8547
	// include the unique plugin by default
8548
	// $.jstree.defaults.plugins.push("unique");
8549
 
8550
 
8551
/**
8552
 * ### Wholerow plugin
8553
 *
8554
 * Makes each node appear block level. Making selection easier. May cause slow down for large trees in old browsers.
8555
 */
8556
 
8557
	var div = document.createElement('DIV');
8558
	div.setAttribute('unselectable','on');
8559
	div.setAttribute('role','presentation');
8560
	div.className = 'jstree-wholerow';
8561
	div.innerHTML = '&#160;';
8562
	$.jstree.plugins.wholerow = function (options, parent) {
8563
		this.bind = function () {
8564
			parent.bind.call(this);
8565
 
8566
			this.element
8567
				.on('ready.jstree set_state.jstree', function () {
8568
						this.hide_dots();
8569
					}.bind(this))
8570
				.on("init.jstree loading.jstree ready.jstree", function () {
8571
						//div.style.height = this._data.core.li_height + 'px';
8572
						this.get_container_ul().addClass('jstree-wholerow-ul');
8573
					}.bind(this))
8574
				.on("deselect_all.jstree", function (e, data) {
8575
						this.element.find('.jstree-wholerow-clicked').removeClass('jstree-wholerow-clicked');
8576
					}.bind(this))
8577
				.on("changed.jstree", function (e, data) {
8578
						this.element.find('.jstree-wholerow-clicked').removeClass('jstree-wholerow-clicked');
8579
						var tmp = false, i, j;
8580
						for(i = 0, j = data.selected.length; i < j; i++) {
8581
							tmp = this.get_node(data.selected[i], true);
8582
							if(tmp && tmp.length) {
8583
								tmp.children('.jstree-wholerow').addClass('jstree-wholerow-clicked');
8584
							}
8585
						}
8586
					}.bind(this))
8587
				.on("open_node.jstree", function (e, data) {
8588
						this.get_node(data.node, true).find('.jstree-clicked').parent().children('.jstree-wholerow').addClass('jstree-wholerow-clicked');
8589
					}.bind(this))
8590
				.on("hover_node.jstree dehover_node.jstree", function (e, data) {
8591
						if(e.type === "hover_node" && this.is_disabled(data.node)) { return; }
8592
						this.get_node(data.node, true).children('.jstree-wholerow')[e.type === "hover_node"?"addClass":"removeClass"]('jstree-wholerow-hovered');
8593
					}.bind(this))
8594
				.on("contextmenu.jstree", ".jstree-wholerow", function (e) {
8595
						if (this._data.contextmenu) {
8596
							e.preventDefault();
8597
							var tmp = $.Event('contextmenu', { metaKey : e.metaKey, ctrlKey : e.ctrlKey, altKey : e.altKey, shiftKey : e.shiftKey, pageX : e.pageX, pageY : e.pageY });
8598
							$(e.currentTarget).closest(".jstree-node").children(".jstree-anchor").first().trigger(tmp);
8599
						}
8600
					}.bind(this))
8601
				/*!
8602
				.on("mousedown.jstree touchstart.jstree", ".jstree-wholerow", function (e) {
8603
						if(e.target === e.currentTarget) {
8604
							var a = $(e.currentTarget).closest(".jstree-node").children(".jstree-anchor");
8605
							e.target = a[0];
8606
							a.trigger(e);
8607
						}
8608
					})
8609
				*/
8610
				.on("click.jstree", ".jstree-wholerow", function (e) {
8611
						e.stopImmediatePropagation();
8612
						var tmp = $.Event('click', { metaKey : e.metaKey, ctrlKey : e.ctrlKey, altKey : e.altKey, shiftKey : e.shiftKey });
8613
						$(e.currentTarget).closest(".jstree-node").children(".jstree-anchor").first().trigger(tmp).trigger('focus');
8614
					})
8615
				.on("dblclick.jstree", ".jstree-wholerow", function (e) {
8616
						e.stopImmediatePropagation();
8617
						var tmp = $.Event('dblclick', { metaKey : e.metaKey, ctrlKey : e.ctrlKey, altKey : e.altKey, shiftKey : e.shiftKey });
8618
						$(e.currentTarget).closest(".jstree-node").children(".jstree-anchor").first().trigger(tmp).trigger('focus');
8619
					})
8620
				.on("click.jstree", ".jstree-leaf > .jstree-ocl", function (e) {
8621
						e.stopImmediatePropagation();
8622
						var tmp = $.Event('click', { metaKey : e.metaKey, ctrlKey : e.ctrlKey, altKey : e.altKey, shiftKey : e.shiftKey });
8623
						$(e.currentTarget).closest(".jstree-node").children(".jstree-anchor").first().trigger(tmp).trigger('focus');
8624
					}.bind(this))
8625
				.on("mouseover.jstree", ".jstree-wholerow, .jstree-icon", function (e) {
8626
						e.stopImmediatePropagation();
8627
						if(!this.is_disabled(e.currentTarget)) {
8628
							this.hover_node(e.currentTarget);
8629
						}
8630
						return false;
8631
					}.bind(this))
8632
				.on("mouseleave.jstree", ".jstree-node", function (e) {
8633
						this.dehover_node(e.currentTarget);
8634
					}.bind(this));
8635
		};
8636
		this.teardown = function () {
8637
			if(this.settings.wholerow) {
8638
				this.element.find(".jstree-wholerow").remove();
8639
			}
8640
			parent.teardown.call(this);
8641
		};
8642
		this.redraw_node = function(obj, deep, callback, force_render) {
8643
			obj = parent.redraw_node.apply(this, arguments);
8644
			if(obj) {
8645
				var tmp = div.cloneNode(true);
8646
				//tmp.style.height = this._data.core.li_height + 'px';
8647
				if($.inArray(obj.id, this._data.core.selected) !== -1) { tmp.className += ' jstree-wholerow-clicked'; }
8648
				if(this._data.core.focused && this._data.core.focused === obj.id) { tmp.className += ' jstree-wholerow-hovered'; }
8649
				obj.insertBefore(tmp, obj.childNodes[0]);
8650
			}
8651
			return obj;
8652
		};
8653
	};
8654
	// include the wholerow plugin by default
8655
	// $.jstree.defaults.plugins.push("wholerow");
8656
	if(window.customElements && Object && Object.create) {
8657
		var proto = Object.create(HTMLElement.prototype);
8658
		proto.createdCallback = function () {
8659
			var c = { core : {}, plugins : [] }, i;
8660
			for(i in $.jstree.plugins) {
8661
				if($.jstree.plugins.hasOwnProperty(i) && this.attributes[i]) {
8662
					c.plugins.push(i);
8663
					if(this.getAttribute(i) && JSON.parse(this.getAttribute(i))) {
8664
						c[i] = JSON.parse(this.getAttribute(i));
8665
					}
8666
				}
8667
			}
8668
			for(i in $.jstree.defaults.core) {
8669
				if($.jstree.defaults.core.hasOwnProperty(i) && this.attributes[i]) {
8670
					c.core[i] = JSON.parse(this.getAttribute(i)) || this.getAttribute(i);
8671
				}
8672
			}
8673
			$(this).jstree(c);
8674
		};
8675
		// proto.attributeChangedCallback = function (name, previous, value) { };
8676
		try {
8677
			window.customElements.define("vakata-jstree", function() {}, { prototype: proto });
8678
		} catch (ignore) { }
8679
	}
8680
 
8681
}));