PageSourceSearch

https://albo.arpa.puglia.it/jcitygov-albi-theme/js/jstree.js

js arpa.puglia.it collected 2026-09-24 18:16:38 UTC 310,567 bytes, 8,743 lines download raw bytes

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

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.