PageSourceSearch

https://ilias.hu/node_modules/jstree/dist/jstree.js?_v=11_3

js ilias.hu collected 2026-10-09 01:23:12 UTC 310,643 bytes, 8,745 lines download raw bytes

vendor: 310,643 bytes, lines 1-8745
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.17
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.17',
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						"on" : {
4586							"blur" : function (e) {
4587								e.stopImmediatePropagation();
4588								e.preventDefault();
4589								var i = s.children(".jstree-rename-input"),
4590									v = i.val(),
4591									f = this.settings.core.force_text,
4592									nv;
4593								if(v === "") { v = t; }
4594								h1.remove();
4595								s.replaceWith(a);
4596								s.remove();
4597								t = f ? t : $('<div></div>').append($.parseHTML(t)).html();
4598								obj = this.get_node(obj);
4599								this.set_text(obj, t);
4600								nv = !!this.rename_node(obj, f ? $('<div></div>').text(v).text() : $('<div></div>').append($.parseHTML(v)).html());
4601								if(!nv) {
4602									this.set_text(obj, t); // move this up? and fix #483
4603								}
4604								this._data.core.focused = tmp.id;
4605								setTimeout(function () {
4606									var node = this.get_node(tmp.id, true);
4607									if(node.length) {
4608										this._data.core.focused = tmp.id;
4609										node.children('.jstree-anchor').trigger('focus');
4610									}
4611								}.bind(this), 0);
4612								if(callback) {
4613									callback.call(this, tmp, nv, cancel, v);
4614								}
4615								h2 = null;
4616							}.bind(this),
4617							"keydown" : function (e) {
4618								var key = e.which;
4619								if(key === 27) {
4620									cancel = true;
4621									this.value = t;
4622								}
4623								if(key === 27 || key === 13 || key === 37 || key === 38 || key === 39 || key === 40 || key === 32) {
4624									e.stopImmediatePropagation();
4625								}
4626								if(key === 27 || key === 13) {
4627									e.preventDefault();
4628									this.blur();
4629								}
4630							},
4631							"click" : function (e) { e.stopImmediatePropagation(); },
4632							"mousedown" : function (e) { e.stopImmediatePropagation(); },
4633							"keyup" : function (e) {
4634								h2.width(Math.min(h1.text("pW" + this.value).width(),w));
4635							},
4636							"keypress" : function(e) {
4637								if(e.which === 13) { return false; }
4638							}
4639						}
4640					});
4641				fn = {
4642						fontFamily		: a.css('fontFamily')		|| '',
4643						fontSize		: a.css('fontSize')			|| '',
4644						fontWeight		: a.css('fontWeight')		|| '',
4645						fontStyle		: a.css('fontStyle')		|| '',
4646						fontStretch		: a.css('fontStretch')		|| '',
4647						fontVariant		: a.css('fontVariant')		|| '',
4648						letterSpacing	: a.css('letterSpacing')	|| '',
4649						wordSpacing		: a.css('wordSpacing')		|| ''
4650				};
4651			s.attr('class', a.attr('class')).append(a.contents().clone()).append(h2);
4652			a.replaceWith(s);
4653			h1.css(fn);
4654			h2.css(fn).width(Math.min(h1.text("pW" + h2[0].value).width(),w))[0].select();
4655			$(document).one('mousedown.jstree touchstart.jstree dnd_start.vakata', function (e) {
4656				if (h2 && e.target !== h2) {
4657					$(h2).trigger('blur');
4658				}
4659			});
4660		},
4661
4662
4663		/**
4664		 * changes the theme
4665		 * @name set_theme(theme_name [, theme_url])
4666		 * @param {String} theme_name the name of the new theme to apply
4667		 * @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.
4668		 * @trigger set_theme.jstree
4669		 */
4670		set_theme : function (theme_name, theme_url) {
4671			if(!theme_name) { return false; }
4672			if(theme_url === true) {
4673				var dir = this.settings.core.themes.dir;
4674				if(!dir) { dir = $.jstree.path + '/themes'; }
4675				theme_url = dir + '/' + theme_name + '/style.css';
4676			}
4677			if(theme_url && $.inArray(theme_url, themes_loaded) === -1) {
4678				$('head').append('<'+'link rel="stylesheet" href="' + theme_url + '" type="text/css" />');
4679				themes_loaded.push(theme_url);
4680			}
4681			if(this._data.core.themes.name) {
4682				this.element.removeClass('jstree-' + this._data.core.themes.name);
4683			}
4684			this._data.core.themes.name = theme_name;
4685			this.element.addClass('jstree-' + theme_name);
4686			this.element[this.settings.core.themes.responsive ? 'addClass' : 'removeClass' ]('jstree-' + theme_name + '-responsive');
4687			/**
4688			 * triggered when a theme is set
4689			 * @event
4690			 * @name set_theme.jstree
4691			 * @param {String} theme the new theme
4692			 */
4693			this.trigger('set_theme', { 'theme' : theme_name });
4694		},
4695		/**
4696		 * gets the name of the currently applied theme name
4697		 * @name get_theme()
4698		 * @return {String}
4699		 */
4700		get_theme : function () { return this._data.core.themes.name; },
4701		/**
4702		 * changes the theme variant (if the theme has variants)
4703		 * @name set_theme_variant(variant_name)
4704		 * @param {String|Boolean} variant_name the variant to apply (if `false` is used the current variant is removed)
4705		 */
4706		set_theme_variant : function (variant_name) {
4707			if(this._data.core.themes.variant) {
4708				this.element.removeClass('jstree-' + this._data.core.themes.name + '-' + this._data.core.themes.variant);
4709			}
4710			this._data.core.themes.variant = variant_name;
4711			if(variant_name) {
4712				this.element.addClass('jstree-' + this._data.core.themes.name + '-' + this._data.core.themes.variant);
4713			}
4714		},
4715		/**
4716		 * gets the name of the currently applied theme variant
4717		 * @name get_theme()
4718		 * @return {String}
4719		 */
4720		get_theme_variant : function () { return this._data.core.themes.variant; },
4721		/**
4722		 * shows a striped background on the container (if the theme supports it)
4723		 * @name show_stripes()
4724		 */
4725		show_stripes : function () {
4726			this._data.core.themes.stripes = true;
4727			this.get_container_ul().addClass("jstree-striped");
4728			/**
4729			 * triggered when stripes are shown
4730			 * @event
4731			 * @name show_stripes.jstree
4732			 */
4733			this.trigger('show_stripes');
4734		},
4735		/**
4736		 * hides the striped background on the container
4737		 * @name hide_stripes()
4738		 */
4739		hide_stripes : function () {
4740			this._data.core.themes.stripes = false;
4741			this.get_container_ul().removeClass("jstree-striped");
4742			/**
4743			 * triggered when stripes are hidden
4744			 * @event
4745			 * @name hide_stripes.jstree
4746			 */
4747			this.trigger('hide_stripes');
4748		},
4749		/**
4750		 * toggles the striped background on the container
4751		 * @name toggle_stripes()
4752		 */
4753		toggle_stripes : function () { if(this._data.core.themes.stripes) { this.hide_stripes(); } else { this.show_stripes(); } },
4754		/**
4755		 * shows the connecting dots (if the theme supports it)
4756		 * @name show_dots()
4757		 */
4758		show_dots : function () {
4759			this._data.core.themes.dots = true;
4760			this.get_container_ul().removeClass("jstree-no-dots");
4761			/**
4762			 * triggered when dots are shown
4763			 * @event
4764			 * @name show_dots.jstree
4765			 */
4766			this.trigger('show_dots');
4767		},
4768		/**
4769		 * hides the connecting dots
4770		 * @name hide_dots()
4771		 */
4772		hide_dots : function () {
4773			this._data.core.themes.dots = false;
4774			this.get_container_ul().addClass("jstree-no-dots");
4775			/**
4776			 * triggered when dots are hidden
4777			 * @event
4778			 * @name hide_dots.jstree
4779			 */
4780			this.trigger('hide_dots');
4781		},
4782		/**
4783		 * toggles the connecting dots
4784		 * @name toggle_dots()
4785		 */
4786		toggle_dots : function () { if(this._data.core.themes.dots) { this.hide_dots(); } else { this.show_dots(); } },
4787		/**
4788		 * show the node icons
4789		 * @name show_icons()
4790		 */
4791		show_icons : function () {
4792			this._data.core.themes.icons = true;
4793			this.get_container_ul().removeClass("jstree-no-icons");
4794			/**
4795			 * triggered when icons are shown
4796			 * @event
4797			 * @name show_icons.jstree
4798			 */
4799			this.trigger('show_icons');
4800		},
4801		/**
4802		 * hide the node icons
4803		 * @name hide_icons()
4804		 */
4805		hide_icons : function () {
4806			this._data.core.themes.icons = false;
4807			this.get_container_ul().addClass("jstree-no-icons");
4808			/**
4809			 * triggered when icons are hidden
4810			 * @event
4811			 * @name hide_icons.jstree
4812			 */
4813			this.trigger('hide_icons');
4814		},
4815		/**
4816		 * toggle the node icons
4817		 * @name toggle_icons()
4818		 */
4819		toggle_icons : function () { if(this._data.core.themes.icons) { this.hide_icons(); } else { this.show_icons(); } },
4820		/**
4821		 * show the node ellipsis
4822		 * @name show_icons()
4823		 */
4824		show_ellipsis : function () {
4825			this._data.core.themes.ellipsis = true;
4826			this.get_container_ul().addClass("jstree-ellipsis");
4827			/**
4828			 * triggered when ellisis is shown
4829			 * @event
4830			 * @name show_ellipsis.jstree
4831			 */
4832			this.trigger('show_ellipsis');
4833		},
4834		/**
4835		 * hide the node ellipsis
4836		 * @name hide_ellipsis()
4837		 */
4838		hide_ellipsis : function () {
4839			this._data.core.themes.ellipsis = false;
4840			this.get_container_ul().removeClass("jstree-ellipsis");
4841			/**
4842			 * triggered when ellisis is hidden
4843			 * @event
4844			 * @name hide_ellipsis.jstree
4845			 */
4846			this.trigger('hide_ellipsis');
4847		},
4848		/**
4849		 * toggle the node ellipsis
4850		 * @name toggle_icons()
4851		 */
4852		toggle_ellipsis : function () { if(this._data.core.themes.ellipsis) { this.hide_ellipsis(); } else { this.show_ellipsis(); } },
4853		/**
4854		 * set the node icon for a node
4855		 * @name set_icon(obj, icon)
4856		 * @param {mixed} obj
4857		 * @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
4858		 */
4859		set_icon : function (obj, icon) {
4860			var t1, t2, dom, old;
4861			if($.vakata.is_array(obj)) {
4862				obj = obj.slice();
4863				for(t1 = 0, t2 = obj.length; t1 < t2; t1++) {
4864					this.set_icon(obj[t1], icon);
4865				}
4866				return true;
4867			}
4868			obj = this.get_node(obj);
4869			if(!obj || obj.id === $.jstree.root) { return false; }
4870			old = obj.icon;
4871			obj.icon = icon === true || icon === null || icon === undefined || icon === '' ? true : icon;
4872			dom = this.get_node(obj, true).children(".jstree-anchor").children(".jstree-themeicon");
4873			if(icon === false) {
4874				dom.removeClass('jstree-themeicon-custom ' + old).css("background","").removeAttr("rel");
4875				this.hide_icon(obj);
4876			}
4877			else if(icon === true || icon === null || icon === undefined || icon === '') {
4878				dom.removeClass('jstree-themeicon-custom ' + old).css("background","").removeAttr("rel");
4879				if(old === false) { this.show_icon(obj); }
4880			}
4881			else if(icon.indexOf("/") === -1 && icon.indexOf(".") === -1) {
4882				dom.removeClass(old).css("background","");
4883				dom.addClass(icon + ' jstree-themeicon-custom').attr("rel",icon);
4884				if(old === false) { this.show_icon(obj); }
4885			}
4886			else {
4887				dom.removeClass(old).css("background","");
4888				dom.addClass('jstree-themeicon-custom').css("background", "url('" + icon + "') center center no-repeat").attr("rel",icon);
4889				if(old === false) { this.show_icon(obj); }
4890			}
4891			return true;
4892		},
4893		/**
4894		 * get the node icon for a node
4895		 * @name get_icon(obj)
4896		 * @param {mixed} obj
4897		 * @return {String}
4898		 */
4899		get_icon : function (obj) {
4900			obj = this.get_node(obj);
4901			return (!obj || obj.id === $.jstree.root) ? false : obj.icon;
4902		},
4903		/**
4904		 * hide the icon on an individual node
4905		 * @name hide_icon(obj)
4906		 * @param {mixed} obj
4907		 */
4908		hide_icon : function (obj) {
4909			var t1, t2;
4910			if($.vakata.is_array(obj)) {
4911				obj = obj.slice();
4912				for(t1 = 0, t2 = obj.length; t1 < t2; t1++) {
4913					this.hide_icon(obj[t1]);
4914				}
4915				return true;
4916			}
4917			obj = this.get_node(obj);
4918			if(!obj || obj === $.jstree.root) { return false; }
4919			obj.icon = false;
4920			this.get_node(obj, true).children(".jstree-anchor").children(".jstree-themeicon").addClass('jstree-themeicon-hidden');
4921			return true;
4922		},
4923		/**
4924		 * show the icon on an individual node
4925		 * @name show_icon(obj)
4926		 * @param {mixed} obj
4927		 */
4928		show_icon : function (obj) {
4929			var t1, t2, dom;
4930			if($.vakata.is_array(obj)) {
4931				obj = obj.slice();
4932				for(t1 = 0, t2 = obj.length; t1 < t2; t1++) {
4933					this.show_icon(obj[t1]);
4934				}
4935				return true;
4936			}
4937			obj = this.get_node(obj);
4938			if(!obj || obj === $.jstree.root) { return false; }
4939			dom = this.get_node(obj, true);
4940			obj.icon = dom.length ? dom.children(".jstree-anchor").children(".jstree-themeicon").attr('rel') : true;
4941			if(!obj.icon) { obj.icon = true; }
4942			dom.children(".jstree-anchor").children(".jstree-themeicon").removeClass('jstree-themeicon-hidden');
4943			return true;
4944		}
4945	};
4946
4947	// helpers
4948	$.vakata = {};
4949	// collect attributes
4950	$.vakata.attributes = function(node, with_values) {
4951		node = $(node)[0];
4952		var attr = with_values ? {} : [];
4953		if(node && node.attributes) {
4954			$.each(node.attributes, function (i, v) {
4955				if($.inArray(v.name.toLowerCase(),['style','contenteditable','hasfocus','tabindex']) !== -1) { return; }
4956				if(v.value !== null && $.vakata.trim(v.value) !== '') {
4957					if(with_values) { attr[v.name] = v.value; }
4958					else { attr.push(v.name); }
4959				}
4960			});
4961		}
4962		return attr;
4963	};
4964	$.vakata.array_unique = function(array) {
4965		var a = [], i, j, l, o = {};
4966		for(i = 0, l = array.length; i < l; i++) {
4967			if(o[array[i]] === undefined) {
4968				a.push(array[i]);
4969				o[array[i]] = true;
4970			}
4971		}
4972		return a;
4973	};
4974	// remove item from array
4975	$.vakata.array_remove = function(array, from) {
4976		array.splice(from, 1);
4977		return array;
4978		//var rest = array.slice((to || from) + 1 || array.length);
4979		//array.length = from < 0 ? array.length + from : from;
4980		//array.push.apply(array, rest);
4981		//return array;
4982	};
4983	// remove item from array
4984	$.vakata.array_remove_item = function(array, item) {
4985		var tmp = $.inArray(item, array);
4986		return tmp !== -1 ? $.vakata.array_remove(array, tmp) : array;
4987	};
4988	$.vakata.array_filter = function(c,a,b,d,e) {
4989		if (c.filter) {
4990			return c.filter(a, b);
4991		}
4992		d=[];
4993		for (e in c) {
4994			if (~~e+''===e+'' && e>=0 && a.call(b,c[e],+e,c)) {
4995				d.push(c[e]);
4996			}
4997		}
4998		return d;
4999	};
5000	$.vakata.trim = function (text) {
5001		return String.prototype.trim ? 
5002			String.prototype.trim.call(text.toString()) :
5003			text.toString().replace(/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g, '');
5004	};
5005	$.vakata.is_function = function(obj) {
5006		return typeof obj === "function" && typeof obj.nodeType !== "number";
5007	};
5008	$.vakata.is_array = Array.isArray || function (obj) {
5009		return Object.prototype.toString.call(obj) === "[object Array]";
5010	};
5011
5012	// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_objects/Function/bind#polyfill
5013	if (!Function.prototype.bind) {
5014		Function.prototype.bind = function () {
5015			var thatFunc = this, thatArg = arguments[0];
5016			var args = Array.prototype.slice.call(arguments, 1);
5017			if (typeof thatFunc !== 'function') {
5018				// closest thing possible to the ECMAScript 5
5019				// internal IsCallable function
5020				throw new TypeError('Function.prototype.bind - what is trying to be bound is not callable');
5021			}
5022			return function(){
5023				var funcArgs = args.concat(Array.prototype.slice.call(arguments));
5024				return thatFunc.apply(thatArg, funcArgs);
5025			};
5026		};
5027	}
5028
5029
5030/**
5031 * ### Changed plugin
5032 *
5033 * 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.
5034 */
5035
5036	$.jstree.plugins.changed = function (options, parent) {
5037		var last = [];
5038		this.trigger = function (ev, data) {
5039			var i, j;
5040			if(!data) {
5041				data = {};
5042			}
5043			if(ev.replace('.jstree','') === 'changed') {
5044				data.changed = { selected : [], deselected : [] };
5045				var tmp = {};
5046				for(i = 0, j = last.length; i < j; i++) {
5047					tmp[last[i]] = 1;
5048				}
5049				for(i = 0, j = data.selected.length; i < j; i++) {
5050					if(!tmp[data.selected[i]]) {
5051						data.changed.selected.push(data.selected[i]);
5052					}
5053					else {
5054						tmp[data.selected[i]] = 2;
5055					}
5056				}
5057				for(i = 0, j = last.length; i < j; i++) {
5058					if(tmp[last[i]] === 1) {
5059						data.changed.deselected.push(last[i]);
5060					}
5061				}
5062				last = data.selected.slice();
5063			}
5064			/**
5065			 * triggered when selection changes (the "changed" plugin enhances the original event with more data)
5066			 * @event
5067			 * @name changed.jstree
5068			 * @param {Object} node
5069			 * @param {Object} action the action that caused the selection to change
5070			 * @param {Array} selected the current selection
5071			 * @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
5072			 * @param {Object} event the event (if any) that triggered this changed event
5073			 * @plugin changed
5074			 */
5075			parent.trigger.call(this, ev, data);
5076		};
5077		this.refresh = function (skip_loading, forget_state) {
5078			last = [];
5079			return parent.refresh.apply(this, arguments);
5080		};
5081	};
5082
5083/**
5084 * ### Checkbox plugin
5085 *
5086 * This plugin renders checkbox icons in front of each node, making multiple selection much easier.
5087 * 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.
5088 */
5089
5090	var _i = document.createElement('I');
5091	_i.className = 'jstree-icon jstree-checkbox';
5092	_i.setAttribute('role', 'presentation');
5093	/**
5094	 * stores all defaults for the checkbox plugin
5095	 * @name $.jstree.defaults.checkbox
5096	 * @plugin checkbox
5097	 */
5098	$.jstree.defaults.checkbox = {
5099		/**
5100		 * a boolean indicating if checkboxes should be visible (can be changed at a later time using `show_checkboxes()` and `hide_checkboxes`). Defaults to `true`.
5101		 * @name $.jstree.defaults.checkbox.visible
5102		 * @plugin checkbox
5103		 */
5104		visible				: true,
5105		/**
5106		 * a boolean indicating if checkboxes should cascade down and have an undetermined state. Defaults to `true`.
5107		 * @name $.jstree.defaults.checkbox.three_state
5108		 * @plugin checkbox
5109		 */
5110		three_state			: true,
5111		/**
5112		 * a boolean indicating if clicking anywhere on the node should act as clicking on the checkbox. Defaults to `true`.
5113		 * @name $.jstree.defaults.checkbox.whole_node
5114		 * @plugin checkbox
5115		 */
5116		whole_node			: true,
5117		/**
5118		 * a boolean indicating if the selected style of a node should be kept, or removed. Defaults to `true`.
5119		 * @name $.jstree.defaults.checkbox.keep_selected_style
5120		 * @plugin checkbox
5121		 */
5122		keep_selected_style	: true,
5123		/**
5124		 * This setting controls how cascading and undetermined nodes are applied.
5125		 * 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.
5126		 * If `three_state` is set to `true` this setting is automatically set to 'up+down+undetermined'. Defaults to ''.
5127		 * @name $.jstree.defaults.checkbox.cascade
5128		 * @plugin checkbox
5129		 */
5130		cascade				: '',
5131		/**
5132		 * 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.
5133		 * @name $.jstree.defaults.checkbox.tie_selection
5134		 * @plugin checkbox
5135		 */
5136		tie_selection		: true,
5137
5138		/**
5139		 * This setting controls if cascading down affects disabled checkboxes
5140		 * @name $.jstree.defaults.checkbox.cascade_to_disabled
5141		 * @plugin checkbox
5142		 */
5143		cascade_to_disabled : true,
5144
5145		/**
5146		 * This setting controls if cascading down affects hidden checkboxes
5147		 * @name $.jstree.defaults.checkbox.cascade_to_hidden
5148		 * @plugin checkbox
5149		 */
5150		cascade_to_hidden : true
5151	};
5152	$.jstree.plugins.checkbox = function (options, parent) {
5153		this.bind = function () {
5154			parent.bind.call(this);
5155			this._data.checkbox.uto = false;
5156			this._data.checkbox.selected = [];
5157			if(this.settings.checkbox.three_state) {
5158				this.settings.checkbox.cascade = 'up+down+undetermined';
5159			}
5160			this.element
5161				.on("init.jstree", function () {
5162						this._data.checkbox.visible = this.settings.checkbox.visible;
5163						if(!this.settings.checkbox.keep_selected_style) {
5164							this.element.addClass('jstree-checkbox-no-clicked');
5165						}
5166						if(this.settings.checkbox.tie_selection) {
5167							this.element.addClass('jstree-checkbox-selection');
5168						}
5169					}.bind(this))
5170				.on("loading.jstree", function () {
5171						this[ this._data.checkbox.visible ? 'show_checkboxes' : 'hide_checkboxes' ]();
5172					}.bind(this));
5173			if(this.settings.checkbox.cascade.indexOf('undetermined') !== -1) {
5174				this.element
5175					.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 () {
5176							// only if undetermined is in setting
5177							if(this._data.checkbox.uto) { clearTimeout(this._data.checkbox.uto); }
5178							this._data.checkbox.uto = setTimeout(this._undetermined.bind(this), 50);
5179						}.bind(this));
5180			}
5181			if(!this.settings.checkbox.tie_selection) {
5182				this.element
5183					.on('model.jstree', function (e, data) {
5184						var m = this._model.data,
5185							p = m[data.parent],
5186							dpc = data.nodes,
5187							i, j;
5188						for(i = 0, j = dpc.length; i < j; i++) {
5189							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);
5190							if(m[dpc[i]].state.checked) {
5191								this._data.checkbox.selected.push(dpc[i]);
5192							}
5193						}
5194					}.bind(this));
5195			}
5196			if(this.settings.checkbox.cascade.indexOf('up') !== -1 || this.settings.checkbox.cascade.indexOf('down') !== -1) {
5197				this.element
5198					.on('model.jstree', function (e, data) {
5199							var m = this._model.data,
5200								p = m[data.parent],
5201								dpc = data.nodes,
5202								chd = [],
5203								c, i, j, k, l, tmp, s = this.settings.checkbox.cascade, t = this.settings.checkbox.tie_selection;
5204
5205							if(s.indexOf('down') !== -1) {
5206								// apply down
5207								if(p.state[ t ? 'selected' : 'checked' ]) {
5208									for(i = 0, j = dpc.length; i < j; i++) {
5209										m[dpc[i]].state[ t ? 'selected' : 'checked' ] = true;
5210									}
5211
5212									this._data[ t ? 'core' : 'checkbox' ].selected = this._data[ t ? 'core' : 'checkbox' ].selected.concat(dpc);
5213								}
5214								else {
5215									for(i = 0, j = dpc.length; i < j; i++) {
5216										if(m[dpc[i]].state[ t ? 'selected' : 'checked' ]) {
5217											for(k = 0, l = m[dpc[i]].children_d.length; k < l; k++) {
5218												m[m[dpc[i]].children_d[k]].state[ t ? 'selected' : 'checked' ] = true;
5219											}
5220											this._data[ t ? 'core' : 'checkbox' ].selected = this._data[ t ? 'core' : 'checkbox' ].selected.concat(m[dpc[i]].children_d);
5221										}
5222									}
5223								}
5224							}
5225
5226							if(s.indexOf('up') !== -1) {
5227								// apply up
5228								for(i = 0, j = p.children_d.length; i < j; i++) {
5229									if(!m[p.children_d[i]].children.length) {
5230										chd.push(m[p.children_d[i]].parent);
5231									}
5232								}
5233								chd = $.vakata.array_unique(chd);
5234								for(k = 0, l = chd.length; k < l; k++) {
5235									p = m[chd[k]];
5236									while(p && p.id !== $.jstree.root) {
5237										c = 0;
5238										for(i = 0, j = p.children.length; i < j; i++) {
5239											c += m[p.children[i]].state[ t ? 'selected' : 'checked' ];
5240										}
5241										if(c === j) {
5242											p.state[ t ? 'selected' : 'checked' ] = true;
5243											this._data[ t ? 'core' : 'checkbox' ].selected.push(p.id);
5244											tmp = this.get_node(p, true);
5245											if(tmp && tmp.length) {
5246												tmp.children('.jstree-anchor').attr('aria-selected', true).addClass( t ? 'jstree-clicked' : 'jstree-checked');
5247											}
5248										}
5249										else {
5250											break;
5251										}
5252										p = this.get_node(p.parent);
5253									}
5254								}
5255							}
5256
5257							this._data[ t ? 'core' : 'checkbox' ].selected = $.vakata.array_unique(this._data[ t ? 'core' : 'checkbox' ].selected);
5258						}.bind(this))
5259					.on(this.settings.checkbox.tie_selection ? 'select_node.jstree' : 'check_node.jstree', function (e, data) {
5260							var self = this,
5261								obj = data.node,
5262								m = this._model.data,
5263								par = this.get_node(obj.parent),
5264								i, j, c, tmp, s = this.settings.checkbox.cascade, t = this.settings.checkbox.tie_selection,
5265								sel = {}, cur = this._data[ t ? 'core' : 'checkbox' ].selected;
5266
5267							for (i = 0, j = cur.length; i < j; i++) {
5268								sel[cur[i]] = true;
5269							}
5270
5271							// apply down
5272							if(s.indexOf('down') !== -1) {
5273								//this._data[ t ? 'core' : 'checkbox' ].selected = $.vakata.array_unique(this._data[ t ? 'core' : 'checkbox' ].selected.concat(obj.children_d));
5274								var selectedIds = this._cascade_new_checked_state(obj.id, true);
5275								var temp = obj.children_d.concat(obj.id);
5276								for (i = 0, j = temp.length; i < j; i++) {
5277									if (selectedIds.indexOf(temp[i]) > -1) {
5278										sel[temp[i]] = true;
5279									}
5280									else {
5281										delete sel[temp[i]];
5282									}
5283								}
5284							}
5285
5286							// apply up
5287							if(s.indexOf('up') !== -1) {
5288								while(par && par.id !== $.jstree.root) {
5289									c = 0;
5290									for(i = 0, j = par.children.length; i < j; i++) {
5291										c += m[par.children[i]].state[ t ? 'selected' : 'checked' ];
5292									}
5293									if(c === j) {
5294										par.state[ t ? 'selected' : 'checked' ] = true;
5295										sel[par.id] = true;
5296										//this._data[ t ? 'core' : 'checkbox' ].selected.push(par.id);
5297										tmp = this.get_node(par, true);
5298										if(tmp && tmp.length) {
5299											tmp.children('.jstree-anchor').attr('aria-selected', true).addClass(t ? 'jstree-clicked' : 'jstree-checked');
5300										}
5301									}
5302									else {
5303										break;
5304									}
5305									par = this.get_node(par.parent);
5306								}
5307							}
5308
5309							cur = [];
5310							for (i in sel) {
5311								if (sel.hasOwnProperty(i)) {
5312									cur.push(i);
5313								}
5314							}
5315							this._data[ t ? 'core' : 'checkbox' ].selected = cur;
5316						}.bind(this))
5317					.on(this.settings.checkbox.tie_selection ? 'deselect_all.jstree' : 'uncheck_all.jstree', function (e, data) {
5318							var obj = this.get_node($.jstree.root),
5319								m = this._model.data,
5320								i, j, tmp;
5321							for(i = 0, j = obj.children_d.length; i < j; i++) {
5322								tmp = m[obj.children_d[i]];
5323								if(tmp && tmp.original && tmp.original.state && tmp.original.state.undetermined) {
5324									tmp.original.state.undetermined = false;
5325								}
5326							}
5327						}.bind(this))
5328					.on(this.settings.checkbox.tie_selection ? 'deselect_node.jstree' : 'uncheck_node.jstree', function (e, data) {
5329							var self = this,
5330								obj = data.node,
5331								dom = this.get_node(obj, true),
5332								i, j, tmp, s = this.settings.checkbox.cascade, t = this.settings.checkbox.tie_selection,
5333								cur = this._data[ t ? 'core' : 'checkbox' ].selected, sel = {},
5334								stillSelectedIds = [],
5335								allIds = obj.children_d.concat(obj.id);
5336
5337							// apply down
5338							if(s.indexOf('down') !== -1) {
5339								var selectedIds = this._cascade_new_checked_state(obj.id, false);
5340
5341								cur = $.vakata.array_filter(cur, function(id) {
5342									return allIds.indexOf(id) === -1 || selectedIds.indexOf(id) > -1;
5343								});
5344							}
5345
5346							// only apply up if cascade up is enabled and if this node is not selected
5347							// (if all child nodes are disabled and cascade_to_disabled === false then this node will till be selected).
5348							if(s.indexOf('up') !== -1 && cur.indexOf(obj.id) === -1) {
5349								for(i = 0, j = obj.parents.length; i < j; i++) {
5350									tmp = this._model.data[obj.parents[i]];
5351									tmp.state[ t ? 'selected' : 'checked' ] = false;
5352									if(tmp && tmp.original && tmp.original.state && tmp.original.state.undetermined) {
5353										tmp.original.state.undetermined = false;
5354									}
5355									tmp = this.get_node(obj.parents[i], true);
5356									if(tmp && tmp.length) {
5357										tmp.children('.jstree-anchor').attr('aria-selected', false).removeClass(t ? 'jstree-clicked' : 'jstree-checked');
5358									}
5359								}
5360
5361								cur = $.vakata.array_filter(cur, function(id) {
5362									return obj.parents.indexOf(id) === -1;
5363								});
5364							}
5365
5366							this._data[ t ? 'core' : 'checkbox' ].selected = cur;
5367						}.bind(this));
5368			}
5369			if(this.settings.checkbox.cascade.indexOf('up') !== -1) {
5370				this.element
5371					.on('delete_node.jstree', function (e, data) {
5372							// apply up (whole handler)
5373							var p = this.get_node(data.parent),
5374								m = this._model.data,
5375								i, j, c, tmp, t = this.settings.checkbox.tie_selection;
5376							while(p && p.id !== $.jstree.root && !p.state[ t ? 'selected' : 'checked' ]) {
5377								c = 0;
5378								for(i = 0, j = p.children.length; i < j; i++) {
5379									c += m[p.children[i]].state[ t ? 'selected' : 'checked' ];
5380								}
5381								if(j > 0 && c === j) {
5382									p.state[ t ? 'selected' : 'checked' ] = true;
5383									this._data[ t ? 'core' : 'checkbox' ].selected.push(p.id);
5384									tmp = this.get_node(p, true);
5385									if(tmp && tmp.length) {
5386										tmp.children('.jstree-anchor').attr('aria-selected', true).addClass(t ? 'jstree-clicked' : 'jstree-checked');
5387									}
5388								}
5389								else {
5390									break;
5391								}
5392								p = this.get_node(p.parent);
5393							}
5394						}.bind(this))
5395					.on('move_node.jstree', function (e, data) {
5396							// apply up (whole handler)
5397							var is_multi = data.is_multi,
5398								old_par = data.old_parent,
5399								new_par = this.get_node(data.parent),
5400								m = this._model.data,
5401								p, c, i, j, tmp, t = this.settings.checkbox.tie_selection;
5402							if(!is_multi) {
5403								p = this.get_node(old_par);
5404								while(p && p.id !== $.jstree.root && !p.state[ t ? 'selected' : 'checked' ]) {
5405									c = 0;
5406									for(i = 0, j = p.children.length; i < j; i++) {
5407										c += m[p.children[i]].state[ t ? 'selected' : 'checked' ];
5408									}
5409									if(j > 0 && c === j) {
5410										p.state[ t ? 'selected' : 'checked' ] = true;
5411										this._data[ t ? 'core' : 'checkbox' ].selected.push(p.id);
5412										tmp = this.get_node(p, true);
5413										if(tmp && tmp.length) {
5414											tmp.children('.jstree-anchor').attr('aria-selected', true).addClass(t ? 'jstree-clicked' : 'jstree-checked');
5415										}
5416									}
5417									else {
5418										break;
5419									}
5420									p = this.get_node(p.parent);
5421								}
5422							}
5423							p = new_par;
5424							while(p && p.id !== $.jstree.root) {
5425								c = 0;
5426								for(i = 0, j = p.children.length; i < j; i++) {
5427									c += m[p.children[i]].state[ t ? 'selected' : 'checked' ];
5428								}
5429								if(c === j) {
5430									if(!p.state[ t ? 'selected' : 'checked' ]) {
5431										p.state[ t ? 'selected' : 'checked' ] = true;
5432										this._data[ t ? 'core' : 'checkbox' ].selected.push(p.id);
5433										tmp = this.get_node(p, true);
5434										if(tmp && tmp.length) {
5435											tmp.children('.jstree-anchor').attr('aria-selected', true).addClass(t ? 'jstree-clicked' : 'jstree-checked');
5436										}
5437									}
5438								}
5439								else {
5440									if(p.state[ t ? 'selected' : 'checked' ]) {
5441										p.state[ t ? 'selected' : 'checked' ] = false;
5442										this._data[ t ? 'core' : 'checkbox' ].selected = $.vakata.array_remove_item(this._data[ t ? 'core' : 'checkbox' ].selected, p.id);
5443										tmp = this.get_node(p, true);
5444										if(tmp && tmp.length) {
5445											tmp.children('.jstree-anchor').attr('aria-selected', false).removeClass(t ? 'jstree-clicked' : 'jstree-checked');
5446										}
5447									}
5448									else {
5449										break;
5450									}
5451								}
5452								p = this.get_node(p.parent);
5453							}
5454						}.bind(this));
5455			}
5456		};
5457		/**
5458		 * get an array of all nodes whose state is "undetermined"
5459		 * @name get_undetermined([full])
5460		 * @param  {boolean} full: if set to `true` the returned array will consist of the full node objects, otherwise - only IDs will be returned
5461		 * @return {Array}
5462		 * @plugin checkbox
5463		 */
5464		this.get_undetermined = function (full) {
5465			if (this.settings.checkbox.cascade.indexOf('undetermined') === -1) {
5466				return [];
5467			}
5468			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 = [];
5469			for(i = 0, j = s.length; i < j; i++) {
5470				if(m[s[i]] && m[s[i]].parents) {
5471					for(k = 0, l = m[s[i]].parents.length; k < l; k++) {
5472						if(o[m[s[i]].parents[k]] !== undefined) {
5473							break;
5474						}
5475						if(m[s[i]].parents[k] !== $.jstree.root) {
5476							o[m[s[i]].parents[k]] = true;
5477							p.push(m[s[i]].parents[k]);
5478						}
5479					}
5480				}
5481			}
5482			// attempt for server side undetermined state
5483			this.element.find('.jstree-closed').not(':has(.jstree-children)')
5484				.each(function () {
5485					var tmp = tt.get_node(this), tmp2;
5486					
5487					if(!tmp) { return; }
5488					
5489					if(!tmp.state.loaded) {
5490						if(tmp.original && tmp.original.state && tmp.original.state.undetermined && tmp.original.state.undetermined === true) {
5491							if(o[tmp.id] === undefined && tmp.id !== $.jstree.root) {
5492								o[tmp.id] = true;
5493								p.push(tmp.id);
5494							}
5495							for(k = 0, l = tmp.parents.length; k < l; k++) {
5496								if(o[tmp.parents[k]] === undefined && tmp.parents[k] !== $.jstree.root) {
5497									o[tmp.parents[k]] = true;
5498									p.push(tmp.parents[k]);
5499								}
5500							}
5501						}
5502					}
5503					else {
5504						for(i = 0, j = tmp.children_d.length; i < j; i++) {
5505							tmp2 = m[tmp.children_d[i]];
5506							if(!tmp2.state.loaded && tmp2.original && tmp2.original.state && tmp2.original.state.undetermined && tmp2.original.state.undetermined === true) {
5507								if(o[tmp2.id] === undefined && tmp2.id !== $.jstree.root) {
5508									o[tmp2.id] = true;
5509									p.push(tmp2.id);
5510								}
5511								for(k = 0, l = tmp2.parents.length; k < l; k++) {
5512									if(o[tmp2.parents[k]] === undefined && tmp2.parents[k] !== $.jstree.root) {
5513										o[tmp2.parents[k]] = true;
5514										p.push(tmp2.parents[k]);
5515									}
5516								}
5517							}
5518						}
5519					}
5520				});
5521			for (i = 0, j = p.length; i < j; i++) {
5522				if(!m[p[i]].state[ t ? 'selected' : 'checked' ]) {
5523					r.push(full ? m[p[i]] : p[i]);
5524				}
5525			}
5526			return r;
5527		};
5528		/**
5529		 * set the undetermined state where and if necessary. Used internally.
5530		 * @private
5531		 * @name _undetermined()
5532		 * @plugin checkbox
5533		 */
5534		this._undetermined = function () {
5535			if(this.element === null) { return; }
5536			var p = this.get_undetermined(false), i, j, s;
5537
5538			this.element.find('.jstree-undetermined').removeClass('jstree-undetermined');
5539			for (i = 0, j = p.length; i < j; i++) {
5540				s = this.get_node(p[i], true);
5541				if(s && s.length) {
5542					s.children('.jstree-anchor').children('.jstree-checkbox').addClass('jstree-undetermined');
5543				}
5544			}
5545		};
5546		this.redraw_node = function(obj, deep, is_callback, force_render) {
5547			obj = parent.redraw_node.apply(this, arguments);
5548			if(obj) {
5549				var i, j, tmp = null, icon = null;
5550				for(i = 0, j = obj.childNodes.length; i < j; i++) {
5551					if(obj.childNodes[i] && obj.childNodes[i].className && obj.childNodes[i].className.indexOf("jstree-anchor") !== -1) {
5552						tmp = obj.childNodes[i];
5553						break;
5554					}
5555				}
5556				if(tmp) {
5557					if(!this.settings.checkbox.tie_selection && this._model.data[obj.id].state.checked) { tmp.className += ' jstree-checked'; }
5558					icon = _i.cloneNode(false);
5559					if(this._model.data[obj.id].state.checkbox_disabled) { icon.className += ' jstree-checkbox-disabled'; }
5560					tmp.insertBefore(icon, tmp.childNodes[0]);
5561				}
5562			}
5563			if(!is_callback && this.settings.checkbox.cascade.indexOf('undetermined') !== -1) {
5564				if(this._data.checkbox.uto) { clearTimeout(this._data.checkbox.uto); }
5565				this._data.checkbox.uto = setTimeout(this._undetermined.bind(this), 50);
5566			}
5567			return obj;
5568		};
5569		/**
5570		 * show the node checkbox icons
5571		 * @name show_checkboxes()
5572		 * @plugin checkbox
5573		 */
5574		this.show_checkboxes = function () { this._data.core.themes.checkboxes = true; this.get_container_ul().removeClass("jstree-no-checkboxes"); };
5575		/**
5576		 * hide the node checkbox icons
5577		 * @name hide_checkboxes()
5578		 * @plugin checkbox
5579		 */
5580		this.hide_checkboxes = function () { this._data.core.themes.checkboxes = false; this.get_container_ul().addClass("jstree-no-checkboxes"); };
5581		/**
5582		 * toggle the node icons
5583		 * @name toggle_checkboxes()
5584		 * @plugin checkbox
5585		 */
5586		this.toggle_checkboxes = function () { if(this._data.core.themes.checkboxes) { this.hide_checkboxes(); } else { this.show_checkboxes(); } };
5587		/**
5588		 * checks if a node is in an undetermined state
5589		 * @name is_undetermined(obj)
5590		 * @param  {mixed} obj
5591		 * @return {Boolean}
5592		 */
5593		this.is_undetermined = function (obj) {
5594			obj = this.get_node(obj);
5595			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;
5596			if(!obj || obj.state[ t ? 'selected' : 'checked' ] === true || s.indexOf('undetermined') === -1 || (s.indexOf('down') === -1 && s.indexOf('up') === -1)) {
5597				return false;
5598			}
5599			if(!obj.state.loaded && obj.original.state.undetermined === true) {
5600				return true;
5601			}
5602			for(i = 0, j = obj.children_d.length; i < j; i++) {
5603				if($.inArray(obj.children_d[i], d) !== -1 || (!m[obj.children_d[i]].state.loaded && m[obj.children_d[i]].original.state.undetermined)) {
5604					return true;
5605				}
5606			}
5607			return false;
5608		};
5609		/**
5610		 * disable a node's checkbox
5611		 * @name disable_checkbox(obj)
5612		 * @param {mixed} obj an array can be used too
5613		 * @trigger disable_checkbox.jstree
5614		 * @plugin checkbox
5615		 */
5616		this.disable_checkbox = function (obj) {
5617			var t1, t2, dom;
5618			if($.vakata.is_array(obj)) {
5619				obj = obj.slice();
5620				for(t1 = 0, t2 = obj.length; t1 < t2; t1++) {
5621					this.disable_checkbox(obj[t1]);
5622				}
5623				return true;
5624			}
5625			obj = this.get_node(obj);
5626			if(!obj || obj.id === $.jstree.root) {
5627				return false;
5628			}
5629			dom = this.get_node(obj, true);
5630			if(!obj.state.checkbox_disabled) {
5631				obj.state.checkbox_disabled = true;
5632				if(dom && dom.length) {
5633					dom.children('.jstree-anchor').children('.jstree-checkbox').addClass('jstree-checkbox-disabled');
5634				}
5635				/**
5636				 * triggered when an node's checkbox is disabled
5637				 * @event
5638				 * @name disable_checkbox.jstree
5639				 * @param {Object} node
5640				 * @plugin checkbox
5641				 */
5642				this.trigger('disable_checkbox', { 'node' : obj });
5643			}
5644		};
5645		/**
5646		 * enable a node's checkbox
5647		 * @name enable_checkbox(obj)
5648		 * @param {mixed} obj an array can be used too
5649		 * @trigger enable_checkbox.jstree
5650		 * @plugin checkbox
5651		 */
5652		this.enable_checkbox = function (obj) {
5653			var t1, t2, dom;
5654			if($.vakata.is_array(obj)) {
5655				obj = obj.slice();
5656				for(t1 = 0, t2 = obj.length; t1 < t2; t1++) {
5657					this.enable_checkbox(obj[t1]);
5658				}
5659				return true;
5660			}
5661			obj = this.get_node(obj);
5662			if(!obj || obj.id === $.jstree.root) {
5663				return false;
5664			}
5665			dom = this.get_node(obj, true);
5666			if(obj.state.checkbox_disabled) {
5667				obj.state.checkbox_disabled = false;
5668				if(dom && dom.length) {
5669					dom.children('.jstree-anchor').children('.jstree-checkbox').removeClass('jstree-checkbox-disabled');
5670				}
5671				/**
5672				 * triggered when an node's checkbox is enabled
5673				 * @event
5674				 * @name enable_checkbox.jstree
5675				 * @param {Object} node
5676				 * @plugin checkbox
5677				 */
5678				this.trigger('enable_checkbox', { 'node' : obj });
5679			}
5680		};
5681
5682		this.activate_node = function (obj, e) {
5683			if($(e.target).hasClass('jstree-checkbox-disabled')) {
5684				return false;
5685			}
5686			if(this.settings.checkbox.tie_selection && (this.settings.checkbox.whole_node || $(e.target).hasClass('jstree-checkbox'))) {
5687				e.ctrlKey = true;
5688			}
5689			if(this.settings.checkbox.tie_selection || (!this.settings.checkbox.whole_node && !$(e.target).hasClass('jstree-checkbox'))) {
5690				return parent.activate_node.call(this, obj, e);
5691			}
5692			if(this.is_disabled(obj)) {
5693				return false;
5694			}
5695			if(this.is_checked(obj)) {
5696				this.uncheck_node(obj, e);
5697			}
5698			else {
5699				this.check_node(obj, e);
5700			}
5701			this.trigger('activate_node', { 'node' : this.get_node(obj) });
5702		};
5703		this.delete_node = function (obj) {
5704			if(this.settings.checkbox.tie_selection || $.vakata.is_array(obj)) {
5705				return parent.delete_node.call(this, obj);
5706			}
5707			var tmp, k, l, c = false;
5708			obj = this.get_node(obj);
5709			if(!obj || obj.id === $.jstree.root) { return false; }
5710			tmp = obj.children_d.concat([]);
5711			tmp.push(obj.id);
5712			for(k = 0, l = tmp.length; k < l; k++) {
5713				if(this._model.data[tmp[k]].state.checked) {
5714					c = true;
5715					break;
5716				}
5717			}
5718			if (c) {
5719				this._data.checkbox.selected = $.vakata.array_filter(this._data.checkbox.selected, function (v) {
5720					return $.inArray(v, tmp) === -1;
5721				});
5722			}
5723			return parent.delete_node.call(this, obj);
5724		};
5725
5726		/**
5727		 * Cascades checked state to a node and all its descendants. This function does NOT affect hidden and disabled nodes (or their descendants).
5728		 * However if these unaffected nodes are already selected their ids will be included in the returned array.
5729		 * @private
5730		 * @name _cascade_new_checked_state(id, checkedState)
5731		 * @param {string} id the node ID
5732		 * @param {bool} checkedState should the nodes be checked or not
5733		 * @returns {Array} Array of all node id's (in this tree branch) that are checked.
5734		 */
5735		this._cascade_new_checked_state = function (id, checkedState) {
5736			var self = this;
5737			var t = this.settings.checkbox.tie_selection;
5738			var node = this._model.data[id];
5739			var selectedNodeIds = [];
5740			var selectedChildrenIds = [], i, j, selectedChildIds;
5741
5742			if (
5743				(this.settings.checkbox.cascade_to_disabled || !node.state.disabled) &&
5744				(this.settings.checkbox.cascade_to_hidden || !node.state.hidden)
5745			) {
5746				//First try and check/uncheck the children
5747				if (node.children) {
5748					for (i = 0, j = node.children.length; i < j; i++) {
5749						var childId = node.children[i];
5750						selectedChildIds = self._cascade_new_checked_state(childId, checkedState);
5751						selectedNodeIds = selectedNodeIds.concat(selectedChildIds);
5752						if (selectedChildIds.indexOf(childId) > -1) {
5753							selectedChildrenIds.push(childId);
5754						}
5755					}
5756				}
5757
5758				var dom = self.get_node(node, true);
5759
5760				//A node's state is undetermined if some but not all of it's children are checked/selected .
5761				var undetermined = selectedChildrenIds.length > 0 && selectedChildrenIds.length < node.children.length;
5762
5763				if(node.original && node.original.state && node.original.state.undetermined) {
5764					node.original.state.undetermined = undetermined;
5765				}
5766
5767				//If a node is undetermined then remove selected class
5768				if (undetermined) {
5769					node.state[ t ? 'selected' : 'checked' ] = false;
5770					dom.children('.jstree-anchor').attr('aria-selected', false).removeClass(t ? 'jstree-clicked' : 'jstree-checked');
5771				}
5772				//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),
5773				//check the node and style it correctly.
5774				else if (checkedState && selectedChildrenIds.length === node.children.length) {
5775					node.state[ t ? 'selected' : 'checked' ] = checkedState;
5776					selectedNodeIds.push(node.id);
5777
5778					dom.children('.jstree-anchor').attr('aria-selected', true).addClass(t ? 'jstree-clicked' : 'jstree-checked');
5779				}
5780				else {
5781					node.state[ t ? 'selected' : 'checked' ] = false;
5782					dom.children('.jstree-anchor').attr('aria-selected', false).removeClass(t ? 'jstree-clicked' : 'jstree-checked');
5783				}
5784			}
5785			else {
5786				selectedChildIds = this.get_checked_descendants(id);
5787
5788				if (node.state[ t ? 'selected' : 'checked' ]) {
5789					selectedChildIds.push(node.id);
5790				}
5791
5792				selectedNodeIds = selectedNodeIds.concat(selectedChildIds);
5793			}
5794
5795			return selectedNodeIds;
5796		};
5797
5798		/**
5799		 * Gets ids of nodes selected in branch (of tree) specified by id (does not include the node specified by id)
5800		 * @name get_checked_descendants(obj)
5801		 * @param {string} id the node ID
5802		 * @return {Array} array of IDs
5803		 * @plugin checkbox
5804		 */
5805		this.get_checked_descendants = function (id) {
5806			var self = this;
5807			var t = self.settings.checkbox.tie_selection;
5808			var node = self._model.data[id];
5809
5810			return $.vakata.array_filter(node.children_d, function(_id) {
5811				return self._model.data[_id].state[ t ? 'selected' : 'checked' ];
5812			});
5813		};
5814
5815		/**
5816		 * check a node (only if tie_selection in checkbox settings is false, otherwise select_node will be called internally)
5817		 * @name check_node(obj)
5818		 * @param {mixed} obj an array can be used to check multiple nodes
5819		 * @trigger check_node.jstree
5820		 * @plugin checkbox
5821		 */
5822		this.check_node = function (obj, e) {
5823			if(this.settings.checkbox.tie_selection) { return this.select_node(obj, false, true, e); }
5824			var dom, t1, t2, th;
5825			if($.vakata.is_array(obj)) {
5826				obj = obj.slice();
5827				for(t1 = 0, t2 = obj.length; t1 < t2; t1++) {
5828					this.check_node(obj[t1], e);
5829				}
5830				return true;
5831			}
5832			obj = this.get_node(obj);
5833			if(!obj || obj.id === $.jstree.root) {
5834				return false;
5835			}
5836			dom = this.get_node(obj, true);
5837			if(!obj.state.checked) {
5838				obj.state.checked = true;
5839				this._data.checkbox.selected.push(obj.id);
5840				if(dom && dom.length) {
5841					dom.children('.jstree-anchor').addClass('jstree-checked');
5842				}
5843				/**
5844				 * triggered when an node is checked (only if tie_selection in checkbox settings is false)
5845				 * @event
5846				 * @name check_node.jstree
5847				 * @param {Object} node
5848				 * @param {Array} selected the current selection
5849				 * @param {Object} event the event (if any) that triggered this check_node
5850				 * @plugin checkbox
5851				 */
5852				this.trigger('check_node', { 'node' : obj, 'selected' : this._data.checkbox.selected, 'event' : e });
5853			}
5854		};
5855		/**
5856		 * uncheck a node (only if tie_selection in checkbox settings is false, otherwise deselect_node will be called internally)
5857		 * @name uncheck_node(obj)
5858		 * @param {mixed} obj an array can be used to uncheck multiple nodes
5859		 * @trigger uncheck_node.jstree
5860		 * @plugin checkbox
5861		 */
5862		this.uncheck_node = function (obj, e) {
5863			if(this.settings.checkbox.tie_selection) { return this.deselect_node(obj, false, e); }
5864			var t1, t2, dom;
5865			if($.vakata.is_array(obj)) {
5866				obj = obj.slice();
5867				for(t1 = 0, t2 = obj.length; t1 < t2; t1++) {
5868					this.uncheck_node(obj[t1], e);
5869				}
5870				return true;
5871			}
5872			obj = this.get_node(obj);
5873			if(!obj || obj.id === $.jstree.root) {
5874				return false;
5875			}
5876			dom = this.get_node(obj, true);
5877			if(obj.state.checked) {
5878				obj.state.checked = false;
5879				this._data.checkbox.selected = $.vakata.array_remove_item(this._data.checkbox.selected, obj.id);
5880				if(dom.length) {
5881					dom.children('.jstree-anchor').removeClass('jstree-checked');
5882				}
5883				/**
5884				 * triggered when an node is unchecked (only if tie_selection in checkbox settings is false)
5885				 * @event
5886				 * @name uncheck_node.jstree
5887				 * @param {Object} node
5888				 * @param {Array} selected the current selection
5889				 * @param {Object} event the event (if any) that triggered this uncheck_node
5890				 * @plugin checkbox
5891				 */
5892				this.trigger('uncheck_node', { 'node' : obj, 'selected' : this._data.checkbox.selected, 'event' : e });
5893			}
5894		};
5895		
5896		/**
5897		 * checks all nodes in the tree (only if tie_selection in checkbox settings is false, otherwise select_all will be called internally)
5898		 * @name check_all()
5899		 * @trigger check_all.jstree, changed.jstree
5900		 * @plugin checkbox
5901		 */
5902		this.check_all = function () {
5903			if(this.settings.checkbox.tie_selection) { return this.select_all(); }
5904			var tmp = this._data.checkbox.selected.concat([]), i, j;
5905			this._data.checkbox.selected = this._model.data[$.jstree.root].children_d.concat();
5906			for(i = 0, j = this._data.checkbox.selected.length; i < j; i++) {
5907				if(this._model.data[this._data.checkbox.selected[i]]) {
5908					this._model.data[this._data.checkbox.selected[i]].state.checked = true;
5909				}
5910			}
5911			this.redraw(true);
5912			/**
5913			 * triggered when all nodes are checked (only if tie_selection in checkbox settings is false)
5914			 * @event
5915			 * @name check_all.jstree
5916			 * @param {Array} selected the current selection
5917			 * @plugin checkbox
5918			 */
5919			this.trigger('check_all', { 'selected' : this._data.checkbox.selected });
5920		};
5921		/**
5922		 * uncheck all checked nodes (only if tie_selection in checkbox settings is false, otherwise deselect_all will be called internally)
5923		 * @name uncheck_all()
5924		 * @trigger uncheck_all.jstree
5925		 * @plugin checkbox
5926		 */
5927		this.uncheck_all = function () {
5928			if(this.settings.checkbox.tie_selection) { return this.deselect_all(); }
5929			var tmp = this._data.checkbox.selected.concat([]), i, j;
5930			for(i = 0, j = this._data.checkbox.selected.length; i < j; i++) {
5931				if(this._model.data[this._data.checkbox.selected[i]]) {
5932					this._model.data[this._data.checkbox.selected[i]].state.checked = false;
5933				}
5934			}
5935			this._data.checkbox.selected = [];
5936			this.element.find('.jstree-checked').removeClass('jstree-checked');
5937			/**
5938			 * triggered when all nodes are unchecked (only if tie_selection in checkbox settings is false)
5939			 * @event
5940			 * @name uncheck_all.jstree
5941			 * @param {Object} node the previous selection
5942			 * @param {Array} selected the current selection
5943			 * @plugin checkbox
5944			 */
5945			this.trigger('uncheck_all', { 'selected' : this._data.checkbox.selected, 'node' : tmp });
5946		};
5947		/**
5948		 * checks if a node is checked (if tie_selection is on in the settings this function will return the same as is_selected)
5949		 * @name is_checked(obj)
5950		 * @param  {mixed}  obj
5951		 * @return {Boolean}
5952		 * @plugin checkbox
5953		 */
5954		this.is_checked = function (obj) {
5955			if(this.settings.checkbox.tie_selection) { return this.is_selected(obj); }
5956			obj = this.get_node(obj);
5957			if(!obj || obj.id === $.jstree.root) { return false; }
5958			return obj.state.checked;
5959		};
5960		/**
5961		 * get an array of all checked nodes (if tie_selection is on in the settings this function will return the same as get_selected)
5962		 * @name get_checked([full])
5963		 * @param  {mixed}  full if set to `true` the returned array will consist of the full node objects, otherwise - only IDs will be returned
5964		 * @return {Array}
5965		 * @plugin checkbox
5966		 */
5967		this.get_checked = function (full) {
5968			if(this.settings.checkbox.tie_selection) { return this.get_selected(full); }
5969			return full ? $.map(this._data.checkbox.selected, function (i) { return this.get_node(i); }.bind(this)) : this._data.checkbox.selected.slice();
5970		};
5971		/**
5972		 * 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)
5973		 * @name get_top_checked([full])
5974		 * @param  {mixed}  full if set to `true` the returned array will consist of the full node objects, otherwise - only IDs will be returned
5975		 * @return {Array}
5976		 * @plugin checkbox
5977		 */
5978		this.get_top_checked = function (full) {
5979			if(this.settings.checkbox.tie_selection) { return this.get_top_selected(full); }
5980			var tmp = this.get_checked(true),
5981				obj = {}, i, j, k, l;
5982			for(i = 0, j = tmp.length; i < j; i++) {
5983				obj[tmp[i].id] = tmp[i];
5984			}
5985			for(i = 0, j = tmp.length; i < j; i++) {
5986				for(k = 0, l = tmp[i].children_d.length; k < l; k++) {
5987					if(obj[tmp[i].children_d[k]]) {
5988						delete obj[tmp[i].children_d[k]];
5989					}
5990				}
5991			}
5992			tmp = [];
5993			for(i in obj) {
5994				if(obj.hasOwnProperty(i)) {
5995					tmp.push(i);
5996				}
5997			}
5998			return full ? $.map(tmp, function (i) { return this.get_node(i); }.bind(this)) : tmp;
5999		};
6000		/**
6001		 * 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)
6002		 * @name get_bottom_checked([full])
6003		 * @param  {mixed}  full if set to `true` the returned array will consist of the full node objects, otherwise - only IDs will be returned
6004		 * @return {Array}
6005		 * @plugin checkbox
6006		 */
6007		this.get_bottom_checked = function (full) {
6008			if(this.settings.checkbox.tie_selection) { return this.get_bottom_selected(full); }
6009			var tmp = this.get_checked(true),
6010				obj = [], i, j;
6011			for(i = 0, j = tmp.length; i < j; i++) {
6012				if(!tmp[i].children.length) {
6013					obj.push(tmp[i].id);
6014				}
6015			}
6016			return full ? $.map(obj, function (i) { return this.get_node(i); }.bind(this)) : obj;
6017		};
6018		this.load_node = function (obj, callback) {
6019			var k, l, i, j, c, tmp;
6020			if(!$.vakata.is_array(obj) && !this.settings.checkbox.tie_selection) {
6021				tmp = this.get_node(obj);
6022				if(tmp && tmp.state.loaded) {
6023					for(k = 0, l = tmp.children_d.length; k < l; k++) {
6024						if(this._model.data[tmp.children_d[k]].state.checked) {
6025							c = true;
6026							this._data.checkbox.selected = $.vakata.array_remove_item(this._data.checkbox.selected, tmp.children_d[k]);
6027						}
6028					}
6029				}
6030			}
6031			return parent.load_node.apply(this, arguments);
6032		};
6033		this.get_state = function () {
6034			var state = parent.get_state.apply(this, arguments);
6035			if(this.settings.checkbox.tie_selection) { return state; }
6036			state.checkbox = this._data.checkbox.selected.slice();
6037			return state;
6038		};
6039		this.set_state = function (state, callback) {
6040			var res = parent.set_state.apply(this, arguments);
6041			if(res && state.checkbox) {
6042				if(!this.settings.checkbox.tie_selection) {
6043					this.uncheck_all();
6044					var _this = this;
6045					$.each(state.checkbox, function (i, v) {
6046						_this.check_node(v);
6047					});
6048				}
6049				delete state.checkbox;
6050				this.set_state(state, callback);
6051				return false;
6052			}
6053			return res;
6054		};
6055		this.refresh = function (skip_loading, forget_state) {
6056			if(this.settings.checkbox.tie_selection) {
6057				this._data.checkbox.selected = [];
6058			}
6059			return parent.refresh.apply(this, arguments);
6060		};
6061	};
6062
6063	// include the checkbox plugin by default
6064	// $.jstree.defaults.plugins.push("checkbox");
6065
6066
6067/**
6068 * ### Conditionalselect plugin
6069 *
6070 * This plugin allows defining a callback to allow or deny node selection by user input (activate node method).
6071 */
6072
6073	/**
6074	 * 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`.
6075	 * @name $.jstree.defaults.checkbox.visible
6076	 * @plugin checkbox
6077	 */
6078	$.jstree.defaults.conditionalselect = function () { return true; };
6079	$.jstree.plugins.conditionalselect = function (options, parent) {
6080		// own function
6081		this.activate_node = function (obj, e) {
6082			if(this.settings.conditionalselect.call(this, this.get_node(obj), e)) {
6083				return parent.activate_node.call(this, obj, e);
6084			}
6085		};
6086	};
6087
6088
6089/**
6090 * ### Contextmenu plugin
6091 *
6092 * Shows a context menu when a node is right-clicked.
6093 */
6094
6095	/**
6096	 * stores all defaults for the contextmenu plugin
6097	 * @name $.jstree.defaults.contextmenu
6098	 * @plugin contextmenu
6099	 */
6100	$.jstree.defaults.contextmenu = {
6101		/**
6102		 * a boolean indicating if the node should be selected when the context menu is invoked on it. Defaults to `true`.
6103		 * @name $.jstree.defaults.contextmenu.select_node
6104		 * @plugin contextmenu
6105		 */
6106		select_node : true,
6107		/**
6108		 * a boolean indicating if the menu should be shown aligned with the node. Defaults to `true`, otherwise the mouse coordinates are used.
6109		 * @name $.jstree.defaults.contextmenu.show_at_node
6110		 * @plugin contextmenu
6111		 */
6112		show_at_node : true,
6113		/**
6114		 * 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).
6115		 *
6116		 * 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.
6117		 *
6118		 * * `separator_before` - a boolean indicating if there should be a separator before this item
6119		 * * `separator_after` - a boolean indicating if there should be a separator after this item
6120		 * * `_disabled` - a boolean indicating if this action should be disabled
6121		 * * `label` - a string - the name of the action (could be a function returning a string)
6122		 * * `title` - a string - an optional tooltip for the item
6123		 * * `action` - a function to be executed if this item is chosen, the function will receive 
6124		 * * `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
6125		 * * `shortcut` - keyCode which will trigger the action if the menu is open (for example `113` for rename, which equals F2)
6126		 * * `shortcut_label` - shortcut label (like for example `F2` for rename)
6127		 * * `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
6128		 *
6129		 * @name $.jstree.defaults.contextmenu.items
6130		 * @plugin contextmenu
6131		 */
6132		items : function (o, cb) { // Could be an object directly
6133			return {
6134				"create" : {
6135					"separator_before"	: false,
6136					"separator_after"	: true,
6137					"_disabled"			: false, //(this.check("create_node", data.reference, {}, "last")),
6138					"label"				: "Create",
6139					"action"			: function (data) {
6140						var inst = $.jstree.reference(data.reference),
6141							obj = inst.get_node(data.reference);
6142						inst.create_node(obj, {}, "last", function (new_node) {
6143							try {
6144								inst.edit(new_node);
6145							} catch (ex) {
6146								setTimeout(function () { inst.edit(new_node); },0);
6147							}
6148						});
6149					}
6150				},
6151				"rename" : {
6152					"separator_before"	: false,
6153					"separator_after"	: false,
6154					"_disabled"			: false, //(this.check("rename_node", data.reference, this.get_parent(data.reference), "")),
6155					"label"				: "Rename",
6156					/*!
6157					"shortcut"			: 113,
6158					"shortcut_label"	: 'F2',
6159					"icon"				: "glyphicon glyphicon-leaf",
6160					*/
6161					"action"			: function (data) {
6162						var inst = $.jstree.reference(data.reference),
6163							obj = inst.get_node(data.reference);
6164						inst.edit(obj);
6165					}
6166				},
6167				"remove" : {
6168					"separator_before"	: false,
6169					"icon"				: false,
6170					"separator_after"	: false,
6171					"_disabled"			: false, //(this.check("delete_node", data.reference, this.get_parent(data.reference), "")),
6172					"label"				: "Delete",
6173					"action"			: function (data) {
6174						var inst = $.jstree.reference(data.reference),
6175							obj = inst.get_node(data.reference);
6176						if(inst.is_selected(obj)) {
6177							inst.delete_node(inst.get_selected());
6178						}
6179						else {
6180							inst.delete_node(obj);
6181						}
6182					}
6183				},
6184				"ccp" : {
6185					"separator_before"	: true,
6186					"icon"				: false,
6187					"separator_after"	: false,
6188					"label"				: "Edit",
6189					"action"			: false,
6190					"submenu" : {
6191						"cut" : {
6192							"separator_before"	: false,
6193							"separator_after"	: false,
6194							"label"				: "Cut",
6195							"action"			: function (data) {
6196								var inst = $.jstree.reference(data.reference),
6197									obj = inst.get_node(data.reference);
6198								if(inst.is_selected(obj)) {
6199									inst.cut(inst.get_top_selected());
6200								}
6201								else {
6202									inst.cut(obj);
6203								}
6204							}
6205						},
6206						"copy" : {
6207							"separator_before"	: false,
6208							"icon"				: false,
6209							"separator_after"	: false,
6210							"label"				: "Copy",
6211							"action"			: function (data) {
6212								var inst = $.jstree.reference(data.reference),
6213									obj = inst.get_node(data.reference);
6214								if(inst.is_selected(obj)) {
6215									inst.copy(inst.get_top_selected());
6216								}
6217								else {
6218									inst.copy(obj);
6219								}
6220							}
6221						},
6222						"paste" : {
6223							"separator_before"	: false,
6224							"icon"				: false,
6225							"_disabled"			: function (data) {
6226								return !$.jstree.reference(data.reference).can_paste();
6227							},
6228							"separator_after"	: false,
6229							"label"				: "Paste",
6230							"action"			: function (data) {
6231								var inst = $.jstree.reference(data.reference),
6232									obj = inst.get_node(data.reference);
6233								inst.paste(obj);
6234							}
6235						}
6236					}
6237				}
6238			};
6239		}
6240	};
6241
6242	$.jstree.plugins.contextmenu = function (options, parent) {
6243		this.bind = function () {
6244			parent.bind.call(this);
6245
6246			var last_ts = 0, cto = null, ex, ey;
6247			this.element
6248				.on("init.jstree loading.jstree ready.jstree", function () {
6249						this.get_container_ul().addClass('jstree-contextmenu');
6250					}.bind(this))
6251				.on("contextmenu.jstree", ".jstree-anchor", function (e, data) {
6252						if (e.target.tagName.toLowerCase() === 'input') {
6253							return;
6254						}
6255						e.preventDefault();
6256						last_ts = e.ctrlKey ? +new Date() : 0;
6257						if(data || cto) {
6258							last_ts = (+new Date()) + 10000;
6259						}
6260						if(cto) {
6261							clearTimeout(cto);
6262						}
6263						if(!this.is_loading(e.currentTarget)) {
6264							this.show_contextmenu(e.currentTarget, e.pageX, e.pageY, e);
6265						}
6266					}.bind(this))
6267				.on("click.jstree", ".jstree-anchor", function (e) {
6268						if(this._data.contextmenu.visible && (!last_ts || (+new Date()) - last_ts > 250)) { // work around safari & macOS ctrl+click
6269							$.vakata.context.hide();
6270						}
6271						last_ts = 0;
6272					}.bind(this))
6273				.on("touchstart.jstree", ".jstree-anchor", function (e) {
6274						if(!e.originalEvent || !e.originalEvent.changedTouches || !e.originalEvent.changedTouches[0]) {
6275							return;
6276						}
6277						ex = e.originalEvent.changedTouches[0].clientX;
6278						ey = e.originalEvent.changedTouches[0].clientY;
6279						cto = setTimeout(function () {
6280							$(e.currentTarget).trigger('contextmenu', true);
6281						}, 750);
6282					})
6283				.on('touchmove.vakata.jstree', function (e) {
6284						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)) {
6285							clearTimeout(cto);
6286							$.vakata.context.hide();
6287						}
6288					})
6289				.on('touchend.vakata.jstree', function (e) {
6290						if(cto) {
6291							clearTimeout(cto);
6292						}
6293					});
6294
6295			/*!
6296			if(!('oncontextmenu' in document.body) && ('ontouchstart' in document.body)) {
6297				var el = null, tm = null;
6298				this.element
6299					.on("touchstart", ".jstree-anchor", function (e) {
6300						el = e.currentTarget;
6301						tm = +new Date();
6302						$(document).one("touchend", function (e) {
6303							e.target = document.elementFromPoint(e.originalEvent.targetTouches[0].pageX - window.pageXOffset, e.originalEvent.targetTouches[0].pageY - window.pageYOffset);
6304							e.currentTarget = e.target;
6305							tm = ((+(new Date())) - tm);
6306							if(e.target === el && tm > 600 && tm < 1000) {
6307								e.preventDefault();
6308								$(el).trigger('contextmenu', e);
6309							}
6310							el = null;
6311							tm = null;
6312						});
6313					});
6314			}
6315			*/
6316			$(document).on("context_hide.vakata.jstree", function (e, data) {
6317				this._data.contextmenu.visible = false;
6318				$(data.reference).removeClass('jstree-context');
6319			}.bind(this));
6320		};
6321		this.teardown = function () {
6322			if(this._data.contextmenu.visible) {
6323				$.vakata.context.hide();
6324			}
6325			$(document).off("context_hide.vakata.jstree");
6326			parent.teardown.call(this);
6327		};
6328
6329		/**
6330		 * prepare and show the context menu for a node
6331		 * @name show_contextmenu(obj [, x, y])
6332		 * @param {mixed} obj the node
6333		 * @param {Number} x the x-coordinate relative to the document to show the menu at
6334		 * @param {Number} y the y-coordinate relative to the document to show the menu at
6335		 * @param {Object} e the event if available that triggered the contextmenu
6336		 * @plugin contextmenu
6337		 * @trigger show_contextmenu.jstree
6338		 */
6339		this.show_contextmenu = function (obj, x, y, e) {
6340			obj = this.get_node(obj);
6341			if(!obj || obj.id === $.jstree.root) { return false; }
6342			var s = this.settings.contextmenu,
6343				d = this.get_node(obj, true),
6344				a = d.children(".jstree-anchor"),
6345				o = false,
6346				i = false;
6347			if(s.show_at_node || x === undefined || y === undefined) {
6348				o = a.offset();
6349				x = o.left;
6350				y = o.top + this._data.core.li_height;
6351			}
6352			if(this.settings.contextmenu.select_node && !this.is_selected(obj)) {
6353				this.activate_node(obj, e);
6354			}
6355
6356			i = s.items;
6357			if($.vakata.is_function(i)) {
6358				i = i.call(this, obj, function (i) {
6359					this._show_contextmenu(obj, x, y, i);
6360				}.bind(this));
6361			}
6362			if($.isPlainObject(i)) {
6363				this._show_contextmenu(obj, x, y, i);
6364			}
6365		};
6366		/**
6367		 * show the prepared context menu for a node
6368		 * @name _show_contextmenu(obj, x, y, i)
6369		 * @param {mixed} obj the node
6370		 * @param {Number} x the x-coordinate relative to the document to show the menu at
6371		 * @param {Number} y the y-coordinate relative to the document to show the menu at
6372		 * @param {Number} i the object of items to show
6373		 * @plugin contextmenu
6374		 * @trigger show_contextmenu.jstree
6375		 * @private
6376		 */
6377		this._show_contextmenu = function (obj, x, y, i) {
6378			var d = this.get_node(obj, true),
6379				a = d.children(".jstree-anchor");
6380			$(document).one("context_show.vakata.jstree", function (e, data) {
6381				var cls = 'jstree-contextmenu jstree-' + this.get_theme() + '-contextmenu';
6382				$(data.element).addClass(cls);
6383				a.addClass('jstree-context');
6384			}.bind(this));
6385			this._data.contextmenu.visible = true;
6386			$.vakata.context.show(a, { 'x' : x, 'y' : y }, i);
6387			/**
6388			 * triggered when the contextmenu is shown for a node
6389			 * @event
6390			 * @name show_contextmenu.jstree
6391			 * @param {Object} node the node
6392			 * @param {Number} x the x-coordinate of the menu relative to the document
6393			 * @param {Number} y the y-coordinate of the menu relative to the document
6394			 * @plugin contextmenu
6395			 */
6396			this.trigger('show_contextmenu', { "node" : obj, "x" : x, "y" : y });
6397		};
6398	};
6399
6400	// contextmenu helper
6401	(function ($) {
6402		var right_to_left = false,
6403			vakata_context = {
6404				element		: false,
6405				reference	: false,
6406				position_x	: 0,
6407				position_y	: 0,
6408				items		: [],
6409				html		: "",
6410				is_visible	: false
6411			};
6412
6413		$.vakata.context = {
6414			settings : {
6415				hide_onmouseleave	: 0,
6416				icons				: true
6417			},
6418			_trigger : function (event_name) {
6419				$(document).triggerHandler("context_" + event_name + ".vakata", {
6420					"reference"	: vakata_context.reference,
6421					"element"	: vakata_context.element,
6422					"position"	: {
6423						"x" : vakata_context.position_x,
6424						"y" : vakata_context.position_y
6425					}
6426				});
6427			},
6428			_execute : function (i) {
6429				i = vakata_context.items[i];
6430				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, {
6431							"item"		: i,
6432							"reference"	: vakata_context.reference,
6433							"element"	: vakata_context.element,
6434							"position"	: {
6435								"x" : vakata_context.position_x,
6436								"y" : vakata_context.position_y
6437							}
6438						}) : false;
6439			},
6440			_parse : function (o, is_callback) {
6441				if(!o) { return false; }
6442				if(!is_callback) {
6443					vakata_context.html		= "";
6444					vakata_context.items	= [];
6445				}
6446				var str = "",
6447					sep = false,
6448					tmp;
6449
6450				if(is_callback) { str += "<"+"ul>"; }
6451				$.each(o, function (i, val) {
6452					if(!val) { return true; }
6453					vakata_context.items.push(val);
6454					if(!sep && val.separator_before) {
6455						str += "<"+"li class='vakata-context-separator'><"+"a href='#' " + ($.vakata.context.settings.icons ? '' : 'class="vakata-context-no-icons"') + ">&#160;<"+"/a><"+"/li>";
6456					}
6457					sep = false;
6458					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+"' ":'')+">";
6459					str += "<"+"a href='#' rel='" + (vakata_context.items.length - 1) + "' " + (val.title ? "title='" + val.title + "'" : "") + ">";
6460					if($.vakata.context.settings.icons) {
6461						str += "<"+"i ";
6462						if(val.icon) {
6463							if(val.icon.indexOf("/") !== -1 || val.icon.indexOf(".") !== -1) { str += " style='background:url(\"" + val.icon + "\") center center no-repeat' "; }
6464							else { str += " class='" + val.icon + "' "; }
6465						}
6466						str += "><"+"/i><"+"span class='vakata-contextmenu-sep'>&#160;<"+"/span>";
6467					}
6468					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>";
6469					if(val.submenu) {
6470						tmp = $.vakata.context._parse(val.submenu, true);
6471						if(tmp) { str += tmp; }
6472					}
6473					str += "<"+"/li>";
6474					if(val.separator_after) {
6475						str += "<"+"li class='vakata-context-separator'><"+"a href='#' " + ($.vakata.context.settings.icons ? '' : 'class="vakata-context-no-icons"') + ">&#160;<"+"/a><"+"/li>";
6476						sep = true;
6477					}
6478				});
6479				str  = str.replace(/<li class\='vakata-context-separator'\><\/li\>$/,"");
6480				if(is_callback) { str += "</ul>"; }
6481				/**
6482				 * triggered on the document when the contextmenu is parsed (HTML is built)
6483				 * @event
6484				 * @plugin contextmenu
6485				 * @name context_parse.vakata
6486				 * @param {jQuery} reference the element that was right clicked
6487				 * @param {jQuery} element the DOM element of the menu itself
6488				 * @param {Object} position the x & y coordinates of the menu
6489				 */
6490				if(!is_callback) { vakata_context.html = str; $.vakata.context._trigger("parse"); }
6491				return str.length > 10 ? str : false;
6492			},
6493			_show_submenu : function (o) {
6494				o = $(o);
6495				if(!o.length || !o.children("ul").length) { return; }
6496				var e = o.children("ul"),
6497					xl = o.offset().left,
6498					x = xl + o.outerWidth(),
6499					y = o.offset().top,
6500					w = e.width(),
6501					h = e.height(),
6502					dw = $(window).width() + $(window).scrollLeft(),
6503					dh = $(window).height() + $(window).scrollTop();
6504				// може да се спести е една проверка - дали няма някой от класовете вече нагоре
6505				if(right_to_left) {
6506					o[x - (w + 10 + o.outerWidth()) < 0 ? "addClass" : "removeClass"]("vakata-context-left");
6507				}
6508				else {
6509					o[x + w > dw  && xl > dw - x ? "addClass" : "removeClass"]("vakata-context-right");
6510				}
6511				if(y + h + 10 > dh) {
6512					e.css("bottom","-1px");
6513				}
6514
6515				//if does not fit - stick it to the side
6516				if (o.hasClass('vakata-context-right')) {
6517					if (xl < w) {
6518						e.css("margin-right", xl - w);
6519					}
6520				} else {
6521					if (dw - x < w) {
6522						e.css("margin-left", dw - x - w);
6523					}
6524				}
6525
6526				e.show();
6527			},
6528			show : function (reference, position, data) {
6529				var o, e, x, y, w, h, dw, dh, cond = true;
6530				if(vakata_context.element && vakata_context.element.length) {
6531					vakata_context.element.width('');
6532				}
6533				switch(cond) {
6534					case (!position && !reference):
6535						return false;
6536					case (!!position && !!reference):
6537						vakata_context.reference	= reference;
6538						vakata_context.position_x	= position.x;
6539						vakata_context.position_y	= position.y;
6540						break;
6541					case (!position && !!reference):
6542						vakata_context.reference	= reference;
6543						o = reference.offset();
6544						vakata_context.position_x	= o.left + reference.outerHeight();
6545						vakata_context.position_y	= o.top;
6546						break;
6547					case (!!position && !reference):
6548						vakata_context.position_x	= position.x;
6549						vakata_context.position_y	= position.y;
6550						break;
6551				}
6552				if(!!reference && !data && $(reference).data('vakata_contextmenu')) {
6553					data = $(reference).data('vakata_contextmenu');
6554				}
6555				if($.vakata.context._parse(data)) {
6556					vakata_context.element.html(vakata_context.html);
6557				}
6558				if(vakata_context.items.length) {
6559					vakata_context.element.appendTo(document.body);
6560					e = vakata_context.element;
6561					x = vakata_context.position_x;
6562					y = vakata_context.position_y;
6563					w = e.width();
6564					h = e.height();
6565					dw = $(window).width() + $(window).scrollLeft();
6566					dh = $(window).height() + $(window).scrollTop();
6567					if(right_to_left) {
6568						x -= (e.outerWidth() - $(reference).outerWidth());
6569						if(x < $(window).scrollLeft() + 20) {
6570							x = $(window).scrollLeft() + 20;
6571						}
6572					}
6573					if(x + w + 20 > dw) {
6574						x = dw - (w + 20);
6575					}
6576					if(y + h + 20 > dh) {
6577						y = dh - (h + 20);
6578					}
6579
6580					vakata_context.element
6581						.css({ "left" : x, "top" : y })
6582						.show()
6583						.find('a').first().trigger('focus').parent().addClass("vakata-context-hover");
6584					vakata_context.is_visible = true;
6585					/**
6586					 * triggered on the document when the contextmenu is shown
6587					 * @event
6588					 * @plugin contextmenu
6589					 * @name context_show.vakata
6590					 * @param {jQuery} reference the element that was right clicked
6591					 * @param {jQuery} element the DOM element of the menu itself
6592					 * @param {Object} position the x & y coordinates of the menu
6593					 */
6594					$.vakata.context._trigger("show");
6595				}
6596			},
6597			hide : function () {
6598				if(vakata_context.is_visible) {
6599					vakata_context.element.hide().find("ul").hide().end().find(':focus').trigger('blur').end().detach();
6600					vakata_context.is_visible = false;
6601					/**
6602					 * triggered on the document when the contextmenu is hidden
6603					 * @event
6604					 * @plugin contextmenu
6605					 * @name context_hide.vakata
6606					 * @param {jQuery} reference the element that was right clicked
6607					 * @param {jQuery} element the DOM element of the menu itself
6608					 * @param {Object} position the x & y coordinates of the menu
6609					 */
6610					$.vakata.context._trigger("hide");
6611				}
6612			}
6613		};
6614		$(function () {
6615			right_to_left = $(document.body).css("direction") === "rtl";
6616			var to = false;
6617
6618			vakata_context.element = $("<ul class='vakata-context'></ul>");
6619			vakata_context.element
6620				.on("mouseenter", "li", function (e) {
6621					e.stopImmediatePropagation();
6622
6623					if($.contains(this, e.relatedTarget)) {
6624						// премахнато заради delegate mouseleave по-долу
6625						// $(this).find(".vakata-context-hover").removeClass("vakata-context-hover");
6626						return;
6627					}
6628
6629					if(to) { clearTimeout(to); }
6630					vakata_context.element.find(".vakata-context-hover").removeClass("vakata-context-hover").end();
6631
6632					$(this)
6633						.siblings().find("ul").hide().end().end()
6634						.parentsUntil(".vakata-context", "li").addBack().addClass("vakata-context-hover");
6635					$.vakata.context._show_submenu(this);
6636				})
6637				// тестово - дали не натоварва?
6638				.on("mouseleave", "li", function (e) {
6639					if($.contains(this, e.relatedTarget)) { return; }
6640					$(this).find(".vakata-context-hover").addBack().removeClass("vakata-context-hover");
6641				})
6642				.on("mouseleave", function (e) {
6643					$(this).find(".vakata-context-hover").removeClass("vakata-context-hover");
6644					if($.vakata.context.settings.hide_onmouseleave) {
6645						to = setTimeout(
6646							(function (t) {
6647								return function () { $.vakata.context.hide(); };
6648							}(this)), $.vakata.context.settings.hide_onmouseleave);
6649					}
6650				})
6651				.on("click", "a", function (e) {
6652					e.preventDefault();
6653				//})
6654				//.on("mouseup", "a", function (e) {
6655					if(!$(this).trigger('blur').parent().hasClass("vakata-context-disabled") && $.vakata.context._execute($(this).attr("rel")) !== false) {
6656						$.vakata.context.hide();
6657					}
6658				})
6659				.on('keydown', 'a', function (e) {
6660						var o = null;
6661						switch(e.which) {
6662							case 13:
6663							case 32:
6664								e.type = "click";
6665								e.preventDefault();
6666								$(e.currentTarget).trigger(e);
6667								break;
6668							case 37:
6669								if(vakata_context.is_visible) {
6670									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');
6671									e.stopImmediatePropagation();
6672									e.preventDefault();
6673								}
6674								break;
6675							case 38:
6676								if(vakata_context.is_visible) {
6677									o = vakata_context.element.find("ul:visible").addBack().last().children(".vakata-context-hover").removeClass("vakata-context-hover").prevAll("li:not(.vakata-context-separator)").first();
6678									if(!o.length) { o = vakata_context.element.find("ul:visible").addBack().last().children("li:not(.vakata-context-separator)").last(); }
6679									o.addClass("vakata-context-hover").children('a').trigger('focus');
6680									e.stopImmediatePropagation();
6681									e.preventDefault();
6682								}
6683								break;
6684							case 39:
6685								if(vakata_context.is_visible) {
6686									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');
6687									e.stopImmediatePropagation();
6688									e.preventDefault();
6689								}
6690								break;
6691							case 40:
6692								if(vakata_context.is_visible) {
6693									o = vakata_context.element.find("ul:visible").addBack().last().children(".vakata-context-hover").removeClass("vakata-context-hover").nextAll("li:not(.vakata-context-separator)").first();
6694									if(!o.length) { o = vakata_context.element.find("ul:visible").addBack().last().children("li:not(.vakata-context-separator)").first(); }
6695									o.addClass("vakata-context-hover").children('a').trigger('focus');
6696									e.stopImmediatePropagation();
6697									e.preventDefault();
6698								}
6699								break;
6700							case 27:
6701								$.vakata.context.hide();
6702								e.preventDefault();
6703								break;
6704							default:
6705								//console.log(e.which);
6706								break;
6707						}
6708					})
6709				.on('keydown', function (e) {
6710					e.preventDefault();
6711					var a = vakata_context.element.find('.vakata-contextmenu-shortcut-' + e.which).parent();
6712					if(a.parent().not('.vakata-context-disabled')) {
6713						a.trigger('click');
6714					}
6715				});
6716
6717			$(document)
6718				.on("mousedown.vakata.jstree", function (e) {
6719					if(vakata_context.is_visible && vakata_context.element[0] !== e.target  && !$.contains(vakata_context.element[0], e.target)) {
6720						$.vakata.context.hide();
6721					}
6722				})
6723				.on("context_show.vakata.jstree", function (e, data) {
6724					vakata_context.element.find("li:has(ul)").children("a").addClass("vakata-context-parent");
6725					if(right_to_left) {
6726						vakata_context.element.addClass("vakata-context-rtl").css("direction", "rtl");
6727					}
6728					// also apply a RTL class?
6729					vakata_context.element.find("ul").hide().end();
6730				});
6731		});
6732	}($));
6733	// $.jstree.defaults.plugins.push("contextmenu");
6734
6735
6736/**
6737 * ### Drag'n'drop plugin
6738 *
6739 * Enables dragging and dropping of nodes in the tree, resulting in a move or copy operations.
6740 */
6741
6742	/**
6743	 * stores all defaults for the drag'n'drop plugin
6744	 * @name $.jstree.defaults.dnd
6745	 * @plugin dnd
6746	 */
6747	$.jstree.defaults.dnd = {
6748		/**
6749		 * a boolean indicating if a copy should be possible while dragging (by pressint the meta key or Ctrl). Defaults to `true`.
6750		 * @name $.jstree.defaults.dnd.copy
6751		 * @plugin dnd
6752		 */
6753		copy : true,
6754		/**
6755		 * a number indicating how long a node should remain hovered while dragging to be opened. Defaults to `500`.
6756		 * @name $.jstree.defaults.dnd.open_timeout
6757		 * @plugin dnd
6758		 */
6759		open_timeout : 500,
6760		/**
6761		 * 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
6762		 * @name $.jstree.defaults.dnd.is_draggable
6763		 * @plugin dnd
6764		 */
6765		is_draggable : true,
6766		/**
6767		 * 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`
6768		 * @name $.jstree.defaults.dnd.check_while_dragging
6769		 * @plugin dnd
6770		 */
6771		check_while_dragging : true,
6772		/**
6773		 * a boolean indicating if nodes from this tree should only be copied with dnd (as opposed to moved), default is `false`
6774		 * @name $.jstree.defaults.dnd.always_copy
6775		 * @plugin dnd
6776		 */
6777		always_copy : false,
6778		/**
6779		 * 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`
6780		 * @name $.jstree.defaults.dnd.inside_pos
6781		 * @plugin dnd
6782		 */
6783		inside_pos : 0,
6784		/**
6785		 * 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
6786		 * @name $.jstree.defaults.dnd.drag_selection
6787		 * @plugin dnd
6788		 */
6789		drag_selection : true,
6790		/**
6791		 * 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.
6792		 * @name $.jstree.defaults.dnd.touch
6793		 * @plugin dnd
6794		 */
6795		touch : true,
6796		/**
6797		 * 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.
6798		 * @name $.jstree.defaults.dnd.large_drop_target
6799		 * @plugin dnd
6800		 */
6801		large_drop_target : false,
6802		/**
6803		 * 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".
6804		 * @name $.jstree.defaults.dnd.large_drag_target
6805		 * @plugin dnd
6806		 */
6807		large_drag_target : false,
6808		/**
6809		 * controls whether use HTML5 dnd api instead of classical. That will allow better integration of dnd events with other HTML5 controls.
6810		 * @reference http://caniuse.com/#feat=dragndrop
6811		 * @name $.jstree.defaults.dnd.use_html5
6812		 * @plugin dnd
6813		 */
6814		use_html5: false,
6815		/**
6816		 * controls whether items can be dropped anywhere on the tree.
6817		 * @name $.jstree.defaults.dnd.blank_space_drop
6818		 * @plugin dnd
6819		 */
6820		blank_space_drop: false
6821	};
6822	var drg, elm;
6823	// TODO: now check works by checking for each node individually, how about max_children, unique, etc?
6824	$.jstree.plugins.dnd = function (options, parent) {
6825		this.init = function (el, options) {
6826			parent.init.call(this, el, options);
6827			this.settings.dnd.use_html5 = this.settings.dnd.use_html5 && ('draggable' in document.createElement('span'));
6828		};
6829		this.bind = function () {
6830			parent.bind.call(this);
6831
6832			this.element
6833				.on(this.settings.dnd.use_html5 ? 'dragstart.jstree' : 'mousedown.jstree touchstart.jstree', this.settings.dnd.large_drag_target ? '.jstree-node' : '.jstree-anchor', function (e) {
6834						if(this.settings.dnd.large_drag_target && $(e.target).closest('.jstree-node')[0] !== e.currentTarget) {
6835							return true;
6836						}
6837						if(e.type === "touchstart" && (!this.settings.dnd.touch || (this.settings.dnd.touch === 'selected' && !$(e.currentTarget).closest('.jstree-node').children('.jstree-anchor').hasClass('jstree-clicked')))) {
6838							return true;
6839						}
6840						var obj = this.get_node(e.target),
6841							mlt = this.is_selected(obj) && this.settings.dnd.drag_selection ? this.get_top_selected().length : 1,
6842							txt = (mlt > 1 ? mlt + ' ' + this.get_string('nodes') : this.get_text(e.currentTarget));
6843						if(this.settings.core.force_text) {
6844							txt = $.vakata.html.escape(txt);
6845						}
6846						if(obj && (obj.id || obj.id === 0) && obj.id !== $.jstree.root && (e.which === 1 || e.type === "touchstart" || e.type === "dragstart") &&
6847							(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)))
6848						) {
6849							drg = { 'jstree' : true, 'origin' : this, 'obj' : this.get_node(obj,true), 'nodes' : mlt > 1 ? this.get_top_selected() : [obj.id] };
6850							elm = e.currentTarget;
6851							if (this.settings.dnd.use_html5) {
6852								$.vakata.dnd._trigger('start', e, { 'helper': $(), 'element': elm, 'data': drg });
6853							} else {
6854								this.element.trigger('mousedown.jstree');
6855								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>');
6856							}
6857						}
6858					}.bind(this));
6859			if (this.settings.dnd.use_html5) {
6860				this.element
6861					.on('dragover.jstree', function (e) {
6862							e.preventDefault();
6863							$.vakata.dnd._trigger('move', e, { 'helper': $(), 'element': elm, 'data': drg });
6864							return false;
6865						})
6866					//.on('dragenter.jstree', this.settings.dnd.large_drop_target ? '.jstree-node' : '.jstree-anchor', $.proxy(function (e) {
6867					//		e.preventDefault();
6868					//		$.vakata.dnd._trigger('move', e, { 'helper': $(), 'element': elm, 'data': drg });
6869					//		return false;
6870					//	}, this))
6871					.on('drop.jstree', function (e) {
6872							e.preventDefault();
6873							$.vakata.dnd._trigger('stop', e, { 'helper': $(), 'element': elm, 'data': drg });
6874							return false;
6875						}.bind(this));
6876			}
6877		};
6878		this.redraw_node = function(obj, deep, callback, force_render) {
6879			obj = parent.redraw_node.apply(this, arguments);
6880			if (obj && this.settings.dnd.use_html5) {
6881				if (this.settings.dnd.large_drag_target) {
6882					obj.setAttribute('draggable', true);
6883				} else {
6884					var i, j, tmp = null;
6885					for(i = 0, j = obj.childNodes.length; i < j; i++) {
6886						if(obj.childNodes[i] && obj.childNodes[i].className && obj.childNodes[i].className.indexOf("jstree-anchor") !== -1) {
6887							tmp = obj.childNodes[i];
6888							break;
6889						}
6890					}
6891					if(tmp) {
6892						tmp.setAttribute('draggable', true);
6893					}
6894				}
6895			}
6896			return obj;
6897		};
6898	};
6899
6900	$(function() {
6901		// bind only once for all instances
6902		var lastmv = false,
6903			laster = false,
6904			lastev = false,
6905			opento = false,
6906			marker = $('<div id="jstree-marker">&#160;</div>').hide(); //.appendTo('body');
6907
6908		$(document)
6909			.on('dragover.vakata.jstree', function (e) {
6910				if (elm) {
6911					$.vakata.dnd._trigger('move', e, { 'helper': $(), 'element': elm, 'data': drg });
6912				}
6913			})
6914			.on('drop.vakata.jstree', function (e) {
6915				if (elm) {
6916					$.vakata.dnd._trigger('stop', e, { 'helper': $(), 'element': elm, 'data': drg });
6917					elm = null;
6918					drg = null;
6919				}
6920			})
6921			.on('dnd_start.vakata.jstree', function (e, data) {
6922				lastmv = false;
6923				lastev = false;
6924				if(!data || !data.data || !data.data.jstree) { return; }
6925				marker.appendTo(document.body); //.show();
6926			})
6927			.on('dnd_move.vakata.jstree', function (e, data) {
6928				var isDifferentNode = data.event.target !== lastev.target;
6929				if(opento) {
6930					if (!data.event || data.event.type !== 'dragover' || isDifferentNode) {
6931						clearTimeout(opento);
6932					}
6933				}
6934				if(!data || !data.data || !data.data.jstree) { return; }
6935
6936				// if we are hovering the marker image do nothing (can happen on "inside" drags)
6937				if(data.event.target.id && data.event.target.id === 'jstree-marker') {
6938					return;
6939				}
6940				lastev = data.event;
6941
6942				var ins = $.jstree.reference(data.event.target),
6943					ref = false,
6944					off = false,
6945					rel = false,
6946					tmp, l, t, h, p, i, o, ok, t1, t2, op, ps, pr, ip, tm, is_copy, pn, c;
6947				// if we are over an instance
6948				if(ins && ins._data && ins._data.dnd) {
6949					marker.attr('class', 'jstree-' + ins.get_theme() + ( ins.settings.core.themes.responsive ? ' jstree-dnd-responsive' : '' ));
6950					is_copy = data.data.origin && (data.data.origin.settings.dnd.always_copy || (data.data.origin.settings.dnd.copy && (data.event.metaKey || data.event.ctrlKey)));
6951					data.helper
6952						.children().attr('class', 'jstree-' + ins.get_theme() + ' jstree-' + ins.get_theme() + '-' + ins.get_theme_variant() + ' ' + ( ins.settings.core.themes.responsive ? ' jstree-dnd-responsive' : '' ))
6953						.find('.jstree-copy').first()[ is_copy ? 'show' : 'hide' ]();
6954
6955					// if are hovering the container itself add a new root node
6956					//console.log(data.event);
6957					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)) {
6958						ok = true;
6959						for(t1 = 0, t2 = data.data.nodes.length; t1 < t2; t1++) {
6960							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) });
6961							if(!ok) { break; }
6962						}
6963						if(ok) {
6964							lastmv = { 'ins' : ins, 'par' : $.jstree.root, 'pos' : 'last' };
6965							marker.hide();
6966							data.helper.find('.jstree-icon').first().removeClass('jstree-er').addClass('jstree-ok');
6967							if (data.event.originalEvent && data.event.originalEvent.dataTransfer) {
6968								data.event.originalEvent.dataTransfer.dropEffect = is_copy ? 'copy' : 'move';
6969							}
6970							return;
6971						}
6972					}
6973					else {
6974						// if we are hovering a tree node
6975						ref = ins.settings.dnd.large_drop_target ? $(data.event.target).closest('.jstree-node').children('.jstree-anchor') : $(data.event.target).closest('.jstree-anchor');
6976						
6977						if(ref && ref.length && ref.parent().is('.jstree-closed, .jstree-open, .jstree-leaf')) {
6978							off = ref.offset();
6979							rel = (data.event.pageY !== undefined ? data.event.pageY : data.event.originalEvent.pageY) - off.top;
6980							h = ref.outerHeight();
6981							if(rel < h / 3) {
6982								o = ['b', 'i', 'a'];
6983							}
6984							else if(rel > h - h / 3) {
6985								o = ['a', 'i', 'b'];
6986							}
6987							else {
6988								o = rel > h / 2 ? ['i', 'a', 'b'] : ['i', 'b', 'a'];
6989							}
6990							$.each(o, function (j, v) {
6991								switch(v) {
6992									case 'b':
6993										l = off.left - 6;
6994										t = off.top;
6995										p = ins.get_parent(ref);
6996										i = ref.parent().index();
6997										c = 'jstree-below';
6998										break;
6999									case 'i':
7000										ip = ins.settings.dnd.inside_pos;
7001										tm = ins.get_node(ref.parent());
7002										l = off.left - 2;
7003										t = off.top + h / 2 + 1;
7004										p = tm.id;
7005										i = ip === 'first' ? 0 : (ip === 'last' ? tm.children.length : Math.min(ip, tm.children.length));
7006										c = 'jstree-inside';
7007										break;
7008									case 'a':
7009										l = off.left - 6;
7010										t = off.top + h;
7011										p = ins.get_parent(ref);
7012										i = ref.parent().index() + 1;
7013										c = 'jstree-above';
7014										break;
7015								}
7016								ok = true;
7017								for(t1 = 0, t2 = data.data.nodes.length; t1 < t2; t1++) {
7018									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";
7019									ps = i;
7020									if(op === "move_node" && v === 'a' && (data.data.origin && data.data.origin === ins) && p === ins.get_parent(data.data.nodes[t1])) {
7021										pr = ins.get_node(p);
7022										if(ps > $.inArray(data.data.nodes[t1], pr.children)) {
7023											ps -= 1;
7024										}
7025									}
7026									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) }) );
7027									if(!ok) {
7028										if(ins && ins.last_error) { laster = ins.last_error(); }
7029										break;
7030									}
7031								}
7032								if(v === 'i' && ref.parent().is('.jstree-closed') && ins.settings.dnd.open_timeout) {
7033									if (!data.event || data.event.type !== 'dragover' || isDifferentNode) {
7034										if (opento) { clearTimeout(opento); }
7035										opento = setTimeout((function (x, z) { return function () { x.open_node(z); }; }(ins, ref)), ins.settings.dnd.open_timeout);
7036									}
7037								}
7038								if(ok) {
7039									pn = ins.get_node(p, true);
7040									if (!pn.hasClass('.jstree-dnd-parent')) {
7041										$('.jstree-dnd-parent').removeClass('jstree-dnd-parent');
7042										pn.addClass('jstree-dnd-parent');
7043									}
7044									lastmv = { 'ins' : ins, 'par' : p, 'pos' : v === 'i' && ip === 'last' && i === 0 && !ins.is_loaded(tm) ? 'last' : i };
7045									marker.css({ 'left' : l + 'px', 'top' : t + 'px' }).show();
7046									marker.removeClass('jstree-above jstree-inside jstree-below').addClass(c);
7047									data.helper.find('.jstree-icon').first().removeClass('jstree-er').addClass('jstree-ok');
7048									if (data.event.originalEvent && data.event.originalEvent.dataTransfer) {
7049										data.event.originalEvent.dataTransfer.dropEffect = is_copy ? 'copy' : 'move';
7050									}
7051									laster = {};
7052									o = true;
7053									return false;
7054								}
7055							});
7056							if(o === true) { return; }
7057						}
7058					}
7059				}
7060				$('.jstree-dnd-parent').removeClass('jstree-dnd-parent');
7061				lastmv = false;
7062				data.helper.find('.jstree-icon').removeClass('jstree-ok').addClass('jstree-er');
7063				if (data.event.originalEvent && data.event.originalEvent.dataTransfer) {
7064					//data.event.originalEvent.dataTransfer.dropEffect = 'none';
7065				}
7066				marker.hide();
7067			})
7068			.on('dnd_scroll.vakata.jstree', function (e, data) {
7069				if(!data || !data.data || !data.data.jstree) { return; }
7070				marker.hide();
7071				lastmv = false;
7072				lastev = false;
7073				data.helper.find('.jstree-icon').first().removeClass('jstree-ok').addClass('jstree-er');
7074			})
7075			.on('dnd_stop.vakata.jstree', function (e, data) {
7076				$('.jstree-dnd-parent').removeClass('jstree-dnd-parent');
7077				if(opento) { clearTimeout(opento); }
7078				if(!data || !data.data || !data.data.jstree) { return; }
7079				marker.hide().detach();
7080				var i, j, nodes = [];
7081				if(lastmv) {
7082					for(i = 0, j = data.data.nodes.length; i < j; i++) {
7083						nodes[i] = data.data.origin ? data.data.origin.get_node(data.data.nodes[i]) : data.data.nodes[i];
7084					}
7085					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);
7086				}
7087				else {
7088					i = $(data.event.target).closest('.jstree');
7089					if(i.length && laster && laster.error && laster.error === 'check') {
7090						i = i.jstree(true);
7091						if(i) {
7092							i.settings.core.error.call(this, laster);
7093						}
7094					}
7095				}
7096				lastev = false;
7097				lastmv = false;
7098			})
7099			.on('keyup.jstree keydown.jstree', function (e, data) {
7100				data = $.vakata.dnd._get();
7101				if(data && data.data && data.data.jstree) {
7102					if (e.type === "keyup" && e.which === 27) {
7103						if (opento) { clearTimeout(opento); }
7104						lastmv = false;
7105						laster = false;
7106						lastev = false;
7107						opento = false;
7108						marker.hide().detach();
7109						$.vakata.dnd._clean();
7110					} else {
7111						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' ]();
7112						if(lastev) {
7113							lastev.metaKey = e.metaKey;
7114							lastev.ctrlKey = e.ctrlKey;
7115							$.vakata.dnd._trigger('move', lastev);
7116						}
7117					}
7118				}
7119			});
7120	});
7121
7122	// helpers
7123	(function ($) {
7124		$.vakata.html = {
7125			div : $('<div></div>'),
7126			escape : function (str) {
7127				return $.vakata.html.div.text(str).html();
7128			},
7129			strip : function (str) {
7130				return $.vakata.html.div.empty().append($.parseHTML(str)).text();
7131			}
7132		};
7133		// private variable
7134		var vakata_dnd = {
7135			element	: false,
7136			target	: false,
7137			is_down	: false,
7138			is_drag	: false,
7139			helper	: false,
7140			helper_w: 0,
7141			data	: false,
7142			init_x	: 0,
7143			init_y	: 0,
7144			scroll_l: 0,
7145			scroll_t: 0,
7146			scroll_e: false,
7147			scroll_i: false,
7148			is_touch: false
7149		};
7150		$.vakata.dnd = {
7151			settings : {
7152				scroll_speed		: 10,
7153				scroll_proximity	: 20,
7154				helper_left			: 5,
7155				helper_top			: 10,
7156				threshold			: 5,
7157				threshold_touch		: 10
7158			},
7159			_trigger : function (event_name, e, data) {
7160				if (data === undefined) {
7161					data = $.vakata.dnd._get();
7162				}
7163				data.event = e;
7164				$(document).triggerHandler("dnd_" + event_name + ".vakata", data);
7165			},
7166			_get : function () {
7167				return {
7168					"data"		: vakata_dnd.data,
7169					"element"	: vakata_dnd.element,
7170					"helper"	: vakata_dnd.helper
7171				};
7172			},
7173			_clean : function () {
7174				if(vakata_dnd.helper) { vakata_dnd.helper.remove(); }
7175				if(vakata_dnd.scroll_i) { clearInterval(vakata_dnd.scroll_i); vakata_dnd.scroll_i = false; }
7176				vakata_dnd = {
7177					element	: false,
7178					target	: false,
7179					is_down	: false,
7180					is_drag	: false,
7181					helper	: false,
7182					helper_w: 0,
7183					data	: false,
7184					init_x	: 0,
7185					init_y	: 0,
7186					scroll_l: 0,
7187					scroll_t: 0,
7188					scroll_e: false,
7189					scroll_i: false,
7190					is_touch: false
7191				};
7192				elm = null;
7193				$(document).off("mousemove.vakata.jstree touchmove.vakata.jstree", $.vakata.dnd.drag);
7194				$(document).off("mouseup.vakata.jstree touchend.vakata.jstree", $.vakata.dnd.stop);
7195			},
7196			_scroll : function (init_only) {
7197				if(!vakata_dnd.scroll_e || (!vakata_dnd.scroll_l && !vakata_dnd.scroll_t)) {
7198					if(vakata_dnd.scroll_i) { clearInterval(vakata_dnd.scroll_i); vakata_dnd.scroll_i = false; }
7199					return false;
7200				}
7201				if(!vakata_dnd.scroll_i) {
7202					vakata_dnd.scroll_i = setInterval($.vakata.dnd._scroll, 100);
7203					return false;
7204				}
7205				if(init_only === true) { return false; }
7206
7207				var i = vakata_dnd.scroll_e.scrollTop(),
7208					j = vakata_dnd.scroll_e.scrollLeft();
7209				vakata_dnd.scroll_e.scrollTop(i + vakata_dnd.scroll_t * $.vakata.dnd.settings.scroll_speed);
7210				vakata_dnd.scroll_e.scrollLeft(j + vakata_dnd.scroll_l * $.vakata.dnd.settings.scroll_speed);
7211				if(i !== vakata_dnd.scroll_e.scrollTop() || j !== vakata_dnd.scroll_e.scrollLeft()) {
7212					/**
7213					 * triggered on the document when a drag causes an element to scroll
7214					 * @event
7215					 * @plugin dnd
7216					 * @name dnd_scroll.vakata
7217					 * @param {Mixed} data any data supplied with the call to $.vakata.dnd.start
7218					 * @param {DOM} element the DOM element being dragged
7219					 * @param {jQuery} helper the helper shown next to the mouse
7220					 * @param {jQuery} event the element that is scrolling
7221					 */
7222					$.vakata.dnd._trigger("scroll", vakata_dnd.scroll_e);
7223				}
7224			},
7225			start : function (e, data, html) {
7226				if(e.type === "touchstart" && e.originalEvent && e.originalEvent.changedTouches && e.originalEvent.changedTouches[0]) {
7227					e.pageX = e.originalEvent.changedTouches[0].pageX;
7228					e.pageY = e.originalEvent.changedTouches[0].pageY;
7229					e.target = document.elementFromPoint(e.originalEvent.changedTouches[0].pageX - window.pageXOffset, e.originalEvent.changedTouches[0].pageY - window.pageYOffset);
7230				}
7231				if(vakata_dnd.is_drag) { $.vakata.dnd.stop({}); }
7232				try {
7233					e.currentTarget.unselectable = "on";
7234					e.currentTarget.onselectstart = function() { return false; };
7235					if(e.currentTarget.style) {
7236						e.currentTarget.style.touchAction = "none";
7237						e.currentTarget.style.msTouchAction = "none";
7238						e.currentTarget.style.MozUserSelect = "none";
7239					}
7240				} catch(ignore) { }
7241				vakata_dnd.init_x	= e.pageX;
7242				vakata_dnd.init_y	= e.pageY;
7243				vakata_dnd.data		= data;
7244				vakata_dnd.is_down	= true;
7245				vakata_dnd.element	= e.currentTarget;
7246				vakata_dnd.target	= e.target;
7247				vakata_dnd.is_touch	= e.type === "touchstart";
7248				if(html !== false) {
7249					vakata_dnd.helper = $("<div id='vakata-dnd'></div>").html(html).css({
7250						"display"		: "block",
7251						"margin"		: "0",
7252						"padding"		: "0",
7253						"position"		: "absolute",
7254						"top"			: "-2000px",
7255						"lineHeight"	: "16px",
7256						"zIndex"		: "10000"
7257					});
7258				}
7259				$(document).on("mousemove.vakata.jstree touchmove.vakata.jstree", $.vakata.dnd.drag);
7260				$(document).on("mouseup.vakata.jstree touchend.vakata.jstree", $.vakata.dnd.stop);
7261				return false;
7262			},
7263			drag : function (e) {
7264				if(e.type === "touchmove" && e.originalEvent && e.originalEvent.changedTouches && e.originalEvent.changedTouches[0]) {
7265					e.pageX = e.originalEvent.changedTouches[0].pageX;
7266					e.pageY = e.originalEvent.changedTouches[0].pageY;
7267					e.target = document.elementFromPoint(e.originalEvent.changedTouches[0].pageX - window.pageXOffset, e.originalEvent.changedTouches[0].pageY - window.pageYOffset);
7268				}
7269				if(!vakata_dnd.is_down) { return; }
7270				if(!vakata_dnd.is_drag) {
7271					if(
7272						Math.abs(e.pageX - vakata_dnd.init_x) > (vakata_dnd.is_touch ? $.vakata.dnd.settings.threshold_touch : $.vakata.dnd.settings.threshold) ||
7273						Math.abs(e.pageY - vakata_dnd.init_y) > (vakata_dnd.is_touch ? $.vakata.dnd.settings.threshold_touch : $.vakata.dnd.settings.threshold)
7274					) {
7275						if(vakata_dnd.helper) {
7276							vakata_dnd.helper.appendTo(document.body);
7277							vakata_dnd.helper_w = vakata_dnd.helper.outerWidth();
7278						}
7279						vakata_dnd.is_drag = true;
7280						$(vakata_dnd.target).one('click.vakata', false);
7281						/**
7282						 * triggered on the document when a drag starts
7283						 * @event
7284						 * @plugin dnd
7285						 * @name dnd_start.vakata
7286						 * @param {Mixed} data any data supplied with the call to $.vakata.dnd.start
7287						 * @param {DOM} element the DOM element being dragged
7288						 * @param {jQuery} helper the helper shown next to the mouse
7289						 * @param {Object} event the event that caused the start (probably mousemove)
7290						 */
7291						$.vakata.dnd._trigger("start", e);
7292					}
7293					else { return; }
7294				}
7295
7296				var d  = false, w  = false,
7297					dh = false, wh = false,
7298					dw = false, ww = false,
7299					dt = false, dl = false,
7300					ht = false, hl = false;
7301
7302				vakata_dnd.scroll_t = 0;
7303				vakata_dnd.scroll_l = 0;
7304				vakata_dnd.scroll_e = false;
7305				$($(e.target).parentsUntil("body").addBack().get().reverse())
7306					.filter(function () {
7307						return	this.ownerDocument && (/^auto|scroll$/).test($(this).css("overflow")) &&
7308								(this.scrollHeight > this.offsetHeight || this.scrollWidth > this.offsetWidth);
7309					})
7310					.each(function () {
7311						var t = $(this), o = t.offset();
7312						if(this.scrollHeight > this.offsetHeight) {
7313							if(o.top + t.height() - e.pageY < $.vakata.dnd.settings.scroll_proximity)	{ vakata_dnd.scroll_t = 1; }
7314							if(e.pageY - o.top < $.vakata.dnd.settings.scroll_proximity)				{ vakata_dnd.scroll_t = -1; }
7315						}
7316						if(this.scrollWidth > this.offsetWidth) {
7317							if(o.left + t.width() - e.pageX < $.vakata.dnd.settings.scroll_proximity)	{ vakata_dnd.scroll_l = 1; }
7318							if(e.pageX - o.left < $.vakata.dnd.settings.scroll_proximity)				{ vakata_dnd.scroll_l = -1; }
7319						}
7320						if(vakata_dnd.scroll_t || vakata_dnd.scroll_l) {
7321							vakata_dnd.scroll_e = $(this);
7322							return false;
7323						}
7324					});
7325
7326				if(!vakata_dnd.scroll_e) {
7327					d  = $(document); w = $(window);
7328					dh = d.height(); wh = w.height();
7329					dw = d.width(); ww = w.width();
7330					dt = d.scrollTop(); dl = d.scrollLeft();
7331					if(dh > wh && e.pageY - dt < $.vakata.dnd.settings.scroll_proximity)		{ vakata_dnd.scroll_t = -1;  }
7332					if(dh > wh && wh - (e.pageY - dt) < $.vakata.dnd.settings.scroll_proximity)	{ vakata_dnd.scroll_t = 1; }
7333					if(dw > ww && e.pageX - dl < $.vakata.dnd.settings.scroll_proximity)		{ vakata_dnd.scroll_l = -1; }
7334					if(dw > ww && ww - (e.pageX - dl) < $.vakata.dnd.settings.scroll_proximity)	{ vakata_dnd.scroll_l = 1; }
7335					if(vakata_dnd.scroll_t || vakata_dnd.scroll_l) {
7336						vakata_dnd.scroll_e = d;
7337					}
7338				}
7339				if(vakata_dnd.scroll_e) { $.vakata.dnd._scroll(true); }
7340
7341				if(vakata_dnd.helper) {
7342					ht = parseInt(e.pageY + $.vakata.dnd.settings.helper_top, 10);
7343					hl = parseInt(e.pageX + $.vakata.dnd.settings.helper_left, 10);
7344					if(dh && ht + 25 > dh) { ht = dh - 50; }
7345					if(dw && hl + vakata_dnd.helper_w > dw) { hl = dw - (vakata_dnd.helper_w + 2); }
7346					vakata_dnd.helper.css({
7347						left	: hl + "px",
7348						top		: ht + "px"
7349					});
7350				}
7351				/**
7352				 * triggered on the document when a drag is in progress
7353				 * @event
7354				 * @plugin dnd
7355				 * @name dnd_move.vakata
7356				 * @param {Mixed} data any data supplied with the call to $.vakata.dnd.start
7357				 * @param {DOM} element the DOM element being dragged
7358				 * @param {jQuery} helper the helper shown next to the mouse
7359				 * @param {Object} event the event that caused this to trigger (most likely mousemove)
7360				 */
7361				$.vakata.dnd._trigger("move", e);
7362				return false;
7363			},
7364			stop : function (e) {
7365				if(e.type === "touchend" && e.originalEvent && e.originalEvent.changedTouches && e.originalEvent.changedTouches[0]) {
7366					e.pageX = e.originalEvent.changedTouches[0].pageX;
7367					e.pageY = e.originalEvent.changedTouches[0].pageY;
7368					e.target = document.elementFromPoint(e.originalEvent.changedTouches[0].pageX - window.pageXOffset, e.originalEvent.changedTouches[0].pageY - window.pageYOffset);
7369				}
7370				if(vakata_dnd.is_drag) {
7371					/**
7372					 * triggered on the document when a drag stops (the dragged element is dropped)
7373					 * @event
7374					 * @plugin dnd
7375					 * @name dnd_stop.vakata
7376					 * @param {Mixed} data any data supplied with the call to $.vakata.dnd.start
7377					 * @param {DOM} element the DOM element being dragged
7378					 * @param {jQuery} helper the helper shown next to the mouse
7379					 * @param {Object} event the event that caused the stop
7380					 */
7381					if (e.target !== vakata_dnd.target) {
7382						$(vakata_dnd.target).off('click.vakata');
7383					}
7384					$.vakata.dnd._trigger("stop", e);
7385				}
7386				else {
7387					if(e.type === "touchend" && e.target === vakata_dnd.target) {
7388						var to = setTimeout(function () { $(e.target).trigger('click'); }, 100);
7389						$(e.target).one('click', function() { if(to) { clearTimeout(to); } });
7390					}
7391				}
7392				$.vakata.dnd._clean();
7393				return false;
7394			}
7395		};
7396	}($));
7397
7398	// include the dnd plugin by default
7399	// $.jstree.defaults.plugins.push("dnd");
7400
7401
7402/**
7403 * ### Massload plugin
7404 *
7405 * Adds massload functionality to jsTree, so that multiple nodes can be loaded in a single request (only useful with lazy loading).
7406 */
7407
7408	/**
7409	 * massload configuration
7410	 *
7411	 * It is possible to set this to a standard jQuery-like AJAX config.
7412	 * 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.
7413	 *
7414	 * 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.
7415	 *
7416	 * 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.
7417	 *
7418	 *	{
7419	 *		"id1" : [{ "text" : "Child of ID1", "id" : "c1" }, { "text" : "Another child of ID1", "id" : "c2" }],
7420	 *		"id2" : [{ "text" : "Child of ID2", "id" : "c3" }]
7421	 *	}
7422	 * 
7423	 * @name $.jstree.defaults.massload
7424	 * @plugin massload
7425	 */
7426	$.jstree.defaults.massload = null;
7427	$.jstree.plugins.massload = function (options, parent) {
7428		this.init = function (el, options) {
7429			this._data.massload = {};
7430			parent.init.call(this, el, options);
7431		};
7432		this._load_nodes = function (nodes, callback, is_callback, force_reload) {
7433			var s = this.settings.massload,				
7434				toLoad = [],
7435				m = this._model.data,
7436				i, j, dom;
7437			if (!is_callback) {
7438				for(i = 0, j = nodes.length; i < j; i++) {
7439					if(!m[nodes[i]] || ( (!m[nodes[i]].state.loaded && !m[nodes[i]].state.failed) || force_reload) ) {
7440						toLoad.push(nodes[i]);
7441						dom = this.get_node(nodes[i], true);
7442						if (dom && dom.length) {
7443							dom.addClass("jstree-loading").attr('aria-busy',true);
7444						}
7445					}
7446				}
7447				this._data.massload = {};
7448				if (toLoad.length) {
7449					if($.vakata.is_function(s)) {
7450						return s.call(this, toLoad, function (data) {
7451							var i, j;
7452							if(data) {
7453								for(i in data) {
7454									if(data.hasOwnProperty(i)) {
7455										this._data.massload[i] = data[i];
7456									}
7457								}
7458							}
7459							for(i = 0, j = nodes.length; i < j; i++) {
7460								dom = this.get_node(nodes[i], true);
7461								if (dom && dom.length) {
7462									dom.removeClass("jstree-loading").attr('aria-busy',false);
7463								}
7464							}
7465							parent._load_nodes.call(this, nodes, callback, is_callback, force_reload);
7466						}.bind(this));
7467					}
7468					if(typeof s === 'object' && s && s.url) {
7469						s = $.extend(true, {}, s);
7470						if($.vakata.is_function(s.url)) {
7471							s.url = s.url.call(this, toLoad);
7472						}
7473						if($.vakata.is_function(s.data)) {
7474							s.data = s.data.call(this, toLoad);
7475						}
7476						return $.ajax(s)
7477							.done(function (data,t,x) {
7478									var i, j;
7479									if(data) {
7480										for(i in data) {
7481											if(data.hasOwnProperty(i)) {
7482												this._data.massload[i] = data[i];
7483											}
7484										}
7485									}
7486									for(i = 0, j = nodes.length; i < j; i++) {
7487										dom = this.get_node(nodes[i], true);
7488										if (dom && dom.length) {
7489											dom.removeClass("jstree-loading").attr('aria-busy',false);
7490										}
7491									}
7492									parent._load_nodes.call(this, nodes, callback, is_callback, force_reload);
7493								}.bind(this))
7494							.fail(function (f) {
7495									parent._load_nodes.call(this, nodes, callback, is_callback, force_reload);
7496								}.bind(this));
7497					}
7498				}
7499			}
7500			return parent._load_nodes.call(this, nodes, callback, is_callback, force_reload);
7501		};
7502		this._load_node = function (obj, callback) {
7503			var data = this._data.massload[obj.id],
7504				rslt = null, dom;
7505			if(data) {
7506				rslt = this[typeof data === 'string' ? '_append_html_data' : '_append_json_data'](
7507					obj,
7508					typeof data === 'string' ? $($.parseHTML(data)).filter(function () { return this.nodeType !== 3; }) : data,
7509					function (status) { callback.call(this, status); }
7510				);
7511				dom = this.get_node(obj.id, true);
7512				if (dom && dom.length) {
7513					dom.removeClass("jstree-loading").attr('aria-busy',false);
7514				}
7515				delete this._data.massload[obj.id];
7516				return rslt;
7517			}
7518			return parent._load_node.call(this, obj, callback);
7519		};
7520	};
7521
7522
7523/**
7524 * ### Search plugin
7525 *
7526 * Adds search functionality to jsTree.
7527 */
7528
7529	/**
7530	 * stores all defaults for the search plugin
7531	 * @name $.jstree.defaults.search
7532	 * @plugin search
7533	 */
7534	$.jstree.defaults.search = {
7535		/**
7536		 * a jQuery-like AJAX config, which jstree uses if a server should be queried for results.
7537		 *
7538		 * 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.
7539		 * 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
7540		 * @name $.jstree.defaults.search.ajax
7541		 * @plugin search
7542		 */
7543		ajax : false,
7544		/**
7545		 * Indicates if the search should be fuzzy or not (should `chnd3` match `child node 3`). Default is `false`.
7546		 * @name $.jstree.defaults.search.fuzzy
7547		 * @plugin search
7548		 */
7549		fuzzy : false,
7550		/**
7551		 * Indicates if the search should be case sensitive. Default is `false`.
7552		 * @name $.jstree.defaults.search.case_sensitive
7553		 * @plugin search
7554		 */
7555		case_sensitive : false,
7556		/**
7557		 * 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).
7558		 * This setting can be changed at runtime when calling the search method. Default is `false`.
7559		 * @name $.jstree.defaults.search.show_only_matches
7560		 * @plugin search
7561		 */
7562		show_only_matches : false,
7563		/**
7564		 * Indicates if the children of matched element are shown (when show_only_matches is true)
7565		 * This setting can be changed at runtime when calling the search method. Default is `false`.
7566		 * @name $.jstree.defaults.search.show_only_matches_children
7567		 * @plugin search
7568		 */
7569		show_only_matches_children : false,
7570		/**
7571		 * 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`.
7572		 * @name $.jstree.defaults.search.close_opened_onclear
7573		 * @plugin search
7574		 */
7575		close_opened_onclear : true,
7576		/**
7577		 * Indicates if only leaf nodes should be included in search results. Default is `false`.
7578		 * @name $.jstree.defaults.search.search_leaves_only
7579		 * @plugin search
7580		 */
7581		search_leaves_only : false,
7582		/**
7583		 * 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).
7584		 * 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`.
7585		 * @name $.jstree.defaults.search.search_callback
7586		 * @plugin search
7587		 */
7588		search_callback : false
7589	};
7590
7591	$.jstree.plugins.search = function (options, parent) {
7592		this.bind = function () {
7593			parent.bind.call(this);
7594
7595			this._data.search.str = "";
7596			this._data.search.dom = $();
7597			this._data.search.res = [];
7598			this._data.search.opn = [];
7599			this._data.search.som = false;
7600			this._data.search.smc = false;
7601			this._data.search.hdn = [];
7602
7603			this.element
7604				.on("search.jstree", function (e, data) {
7605						if(this._data.search.som && data.res.length) {
7606							var m = this._model.data, i, j, p = [], k, l;
7607							for(i = 0, j = data.res.length; i < j; i++) {
7608								if(m[data.res[i]] && !m[data.res[i]].state.hidden) {
7609									p.push(data.res[i]);
7610									p = p.concat(m[data.res[i]].parents);
7611									if(this._data.search.smc) {
7612										for (k = 0, l = m[data.res[i]].children_d.length; k < l; k++) {
7613											if (m[m[data.res[i]].children_d[k]] && !m[m[data.res[i]].children_d[k]].state.hidden) {
7614												p.push(m[data.res[i]].children_d[k]);
7615											}
7616										}
7617									}
7618								}
7619							}
7620							p = $.vakata.array_remove_item($.vakata.array_unique(p), $.jstree.root);
7621							this._data.search.hdn = this.hide_all(true);
7622							this.show_node(p, true);
7623							this.redraw(true);
7624						}
7625					}.bind(this))
7626				.on("clear_search.jstree", function (e, data) {
7627						if(this._data.search.som && data.res.length) {
7628							this.show_node(this._data.search.hdn, true);
7629							this.redraw(true);
7630						}
7631					}.bind(this));
7632		};
7633		/**
7634		 * used to search the tree nodes for a given string
7635		 * @name search(str [, skip_async])
7636		 * @param {String} str the search string
7637		 * @param {Boolean} skip_async if set to true server will not be queried even if configured
7638		 * @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)
7639		 * @param {mixed} inside an optional node to whose children to limit the search
7640		 * @param {Boolean} append if set to true the results of this search are appended to the previous search
7641		 * @plugin search
7642		 * @trigger search.jstree
7643		 */
7644		this.search = function (str, skip_async, show_only_matches, inside, append, show_only_matches_children) {
7645			if(str === false || $.vakata.trim(str.toString()) === "") {
7646				return this.clear_search();
7647			}
7648			inside = this.get_node(inside);
7649			inside = inside && (inside.id || inside.id === 0) ? inside.id : null;
7650			str = str.toString();
7651			var s = this.settings.search,
7652				a = s.ajax ? s.ajax : false,
7653				m = this._model.data,
7654				f = null,
7655				r = [],
7656				p = [], i, j;
7657			if(this._data.search.res.length && !append) {
7658				this.clear_search();
7659			}
7660			if(show_only_matches === undefined) {
7661				show_only_matches = s.show_only_matches;
7662			}
7663			if(show_only_matches_children === undefined) {
7664				show_only_matches_children = s.show_only_matches_children;
7665			}
7666			if(!skip_async && a !== false) {
7667				if($.vakata.is_function(a)) {
7668					return a.call(this, str, function (d) {
7669							if(d && d.d) { d = d.d; }
7670							this._load_nodes(!$.vakata.is_array(d) ? [] : $.vakata.array_unique(d), function () {
7671								this.search(str, true, show_only_matches, inside, append, show_only_matches_children);
7672							});
7673						}.bind(this), inside);
7674				}
7675				else {
7676					a = $.extend({}, a);
7677					if(!a.data) { a.data = {}; }
7678					a.data.str = str;
7679					if(inside) {
7680						a.data.inside = inside;
7681					}
7682					if (this._data.search.lastRequest) {
7683						this._data.search.lastRequest.abort();
7684					}
7685					this._data.search.lastRequest = $.ajax(a)
7686						.fail(function () {
7687							this._data.core.last_error = { 'error' : 'ajax', 'plugin' : 'search', 'id' : 'search_01', 'reason' : 'Could not load search parents', 'data' : JSON.stringify(a) };
7688							this.settings.core.error.call(this, this._data.core.last_error);
7689						}.bind(this))
7690						.done(function (d) {
7691							if(d && d.d) { d = d.d; }
7692							this._load_nodes(!$.vakata.is_array(d) ? [] : $.vakata.array_unique(d), function () {
7693								this.search(str, true, show_only_matches, inside, append, show_only_matches_children);
7694							});
7695						}.bind(this));
7696					return this._data.search.lastRequest;
7697				}
7698			}
7699			if(!append) {
7700				this._data.search.str = str;
7701				this._data.search.dom = $();
7702				this._data.search.res = [];
7703				this._data.search.opn = [];
7704				this._data.search.som = show_only_matches;
7705				this._data.search.smc = show_only_matches_children;
7706			}
7707
7708			f = new $.vakata.search(str, true, { caseSensitive : s.case_sensitive, fuzzy : s.fuzzy });
7709			$.each(m[inside ? inside : $.jstree.root].children_d, function (ii, i) {
7710				var v = m[i];
7711				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) ) ) {
7712					r.push(i);
7713					p = p.concat(v.parents);
7714				}
7715			});
7716			if(r.length) {
7717				p = $.vakata.array_unique(p);
7718				for(i = 0, j = p.length; i < j; i++) {
7719					if(p[i] !== $.jstree.root && m[p[i]] && this.open_node(p[i], null, 0) === true) {
7720						this._data.search.opn.push(p[i]);
7721					}
7722				}
7723				if(!append) {
7724					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(', #')));
7725					this._data.search.res = r;
7726				}
7727				else {
7728					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(', #'))));
7729					this._data.search.res = $.vakata.array_unique(this._data.search.res.concat(r));
7730				}
7731				this._data.search.dom.children(".jstree-anchor").addClass('jstree-search');
7732			}
7733			/**
7734			 * triggered after search is complete
7735			 * @event
7736			 * @name search.jstree
7737			 * @param {jQuery} nodes a jQuery collection of matching nodes
7738			 * @param {String} str the search string
7739			 * @param {Array} res a collection of objects represeing the matching nodes
7740			 * @plugin search
7741			 */
7742			this.trigger('search', { nodes : this._data.search.dom, str : str, res : this._data.search.res, show_only_matches : show_only_matches });
7743		};
7744		/**
7745		 * used to clear the last search (removes classes and shows all nodes if filtering is on)
7746		 * @name clear_search()
7747		 * @plugin search
7748		 * @trigger clear_search.jstree
7749		 */
7750		this.clear_search = function () {
7751			if(this.settings.search.close_opened_onclear) {
7752				this.close_node(this._data.search.opn, 0);
7753			}
7754			/**
7755			 * triggered after search is complete
7756			 * @event
7757			 * @name clear_search.jstree
7758			 * @param {jQuery} nodes a jQuery collection of matching nodes (the result from the last search)
7759			 * @param {String} str the search string (the last search string)
7760			 * @param {Array} res a collection of objects represeing the matching nodes (the result from the last search)
7761			 * @plugin search
7762			 */
7763			this.trigger('clear_search', { 'nodes' : this._data.search.dom, str : this._data.search.str, res : this._data.search.res });
7764			if(this._data.search.res.length) {
7765				this._data.search.dom = $(this.element[0].querySelectorAll('#' + $.map(this._data.search.res, function (v) {
7766					return "0123456789".indexOf(v[0]) !== -1 ? '\\3' + v[0] + ' ' + v.substr(1).replace($.jstree.idregex,'\\$&') : v.replace($.jstree.idregex,'\\$&');
7767				}).join(', #')));
7768				this._data.search.dom.children(".jstree-anchor").removeClass("jstree-search");
7769			}
7770			this._data.search.str = "";
7771			this._data.search.res = [];
7772			this._data.search.opn = [];
7773			this._data.search.dom = $();
7774		};
7775
7776		this.redraw_node = function(obj, deep, callback, force_render) {
7777			obj = parent.redraw_node.apply(this, arguments);
7778			if(obj) {
7779				if($.inArray(obj.id, this._data.search.res) !== -1) {
7780					var i, j, tmp = null;
7781					for(i = 0, j = obj.childNodes.length; i < j; i++) {
7782						if(obj.childNodes[i] && obj.childNodes[i].className && obj.childNodes[i].className.indexOf("jstree-anchor") !== -1) {
7783							tmp = obj.childNodes[i];
7784							break;
7785						}
7786					}
7787					if(tmp) {
7788						tmp.className += ' jstree-search';
7789					}
7790				}
7791			}
7792			return obj;
7793		};
7794	};
7795
7796	// helpers
7797	(function ($) {
7798		// from http://kiro.me/projects/fuse.html
7799		$.vakata.search = function(pattern, txt, options) {
7800			options = options || {};
7801			options = $.extend({}, $.vakata.search.defaults, options);
7802			if(options.fuzzy !== false) {
7803				options.fuzzy = true;
7804			}
7805			pattern = options.caseSensitive ? pattern : pattern.toLowerCase();
7806			var MATCH_LOCATION	= options.location,
7807				MATCH_DISTANCE	= options.distance,
7808				MATCH_THRESHOLD	= options.threshold,
7809				patternLen = pattern.length,
7810				matchmask, pattern_alphabet, match_bitapScore, search;
7811			if(patternLen > 32) {
7812				options.fuzzy = false;
7813			}
7814			if(options.fuzzy) {
7815				matchmask = 1 << (patternLen - 1);
7816				pattern_alphabet = (function () {
7817					var mask = {},
7818						i = 0;
7819					for (i = 0; i < patternLen; i++) {
7820						mask[pattern.charAt(i)] = 0;
7821					}
7822					for (i = 0; i < patternLen; i++) {
7823						mask[pattern.charAt(i)] |= 1 << (patternLen - i - 1);
7824					}
7825					return mask;
7826				}());
7827				match_bitapScore = function (e, x) {
7828					var accuracy = e / patternLen,
7829						proximity = Math.abs(MATCH_LOCATION - x);
7830					if(!MATCH_DISTANCE) {
7831						return proximity ? 1.0 : accuracy;
7832					}
7833					return accuracy + (proximity / MATCH_DISTANCE);
7834				};
7835			}
7836			search = function (text) {
7837				text = options.caseSensitive ? text.toString() : text.toString().toLowerCase();
7838				if(pattern === text || text.indexOf(pattern) !== -1) {
7839					return {
7840						isMatch: true,
7841						score: 0
7842					};
7843				}
7844				if(!options.fuzzy) {
7845					return {
7846						isMatch: false,
7847						score: 1
7848					};
7849				}
7850				var i, j,
7851					textLen = text.length,
7852					scoreThreshold = MATCH_THRESHOLD,
7853					bestLoc = text.indexOf(pattern, MATCH_LOCATION),
7854					binMin, binMid,
7855					binMax = patternLen + textLen,
7856					lastRd, start, finish, rd, charMatch,
7857					score = 1,
7858					locations = [];
7859				if (bestLoc !== -1) {
7860					scoreThreshold = Math.min(match_bitapScore(0, bestLoc), scoreThreshold);
7861					bestLoc = text.lastIndexOf(pattern, MATCH_LOCATION + patternLen);
7862					if (bestLoc !== -1) {
7863						scoreThreshold = Math.min(match_bitapScore(0, bestLoc), scoreThreshold);
7864					}
7865				}
7866				bestLoc = -1;
7867				for (i = 0; i < patternLen; i++) {
7868					binMin = 0;
7869					binMid = binMax;
7870					while (binMin < binMid) {
7871						if (match_bitapScore(i, MATCH_LOCATION + binMid) <= scoreThreshold) {
7872							binMin = binMid;
7873						} else {
7874							binMax = binMid;
7875						}
7876						binMid = Math.floor((binMax - binMin) / 2 + binMin);
7877					}
7878					binMax = binMid;
7879					start = Math.max(1, MATCH_LOCATION - binMid + 1);
7880					finish = Math.min(MATCH_LOCATION + binMid, textLen) + patternLen;
7881					rd = new Array(finish + 2);
7882					rd[finish + 1] = (1 << i) - 1;
7883					for (j = finish; j >= start; j--) {
7884						charMatch = pattern_alphabet[text.charAt(j - 1)];
7885						if (i === 0) {
7886							rd[j] = ((rd[j + 1] << 1) | 1) & charMatch;
7887						} else {
7888							rd[j] = ((rd[j + 1] << 1) | 1) & charMatch | (((lastRd[j + 1] | lastRd[j]) << 1) | 1) | lastRd[j + 1];
7889						}
7890						if (rd[j] & matchmask) {
7891							score = match_bitapScore(i, j - 1);
7892							if (score <= scoreThreshold) {
7893								scoreThreshold = score;
7894								bestLoc = j - 1;
7895								locations.push(bestLoc);
7896								if (bestLoc > MATCH_LOCATION) {
7897									start = Math.max(1, 2 * MATCH_LOCATION - bestLoc);
7898								} else {
7899									break;
7900								}
7901							}
7902						}
7903					}
7904					if (match_bitapScore(i + 1, MATCH_LOCATION) > scoreThreshold) {
7905						break;
7906					}
7907					lastRd = rd;
7908				}
7909				return {
7910					isMatch: bestLoc >= 0,
7911					score: score
7912				};
7913			};
7914			return txt === true ? { 'search' : search } : search(txt);
7915		};
7916		$.vakata.search.defaults = {
7917			location : 0,
7918			distance : 100,
7919			threshold : 0.6,
7920			fuzzy : false,
7921			caseSensitive : false
7922		};
7923	}($));
7924
7925	// include the search plugin by default
7926	// $.jstree.defaults.plugins.push("search");
7927
7928
7929/**
7930 * ### Sort plugin
7931 *
7932 * Automatically sorts all siblings in the tree according to a sorting function.
7933 */
7934
7935	/**
7936	 * the settings function used to sort the nodes.
7937	 * It is executed in the tree's context, accepts two nodes as arguments and should return `1` or `-1`.
7938	 * @name $.jstree.defaults.sort
7939	 * @plugin sort
7940	 */
7941	$.jstree.defaults.sort = function (a, b) {
7942		//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);
7943		return this.get_text(a) > this.get_text(b) ? 1 : -1;
7944	};
7945	$.jstree.plugins.sort = function (options, parent) {
7946		this.bind = function () {
7947			parent.bind.call(this);
7948			this.element
7949				.on("model.jstree", function (e, data) {
7950						this.sort(data.parent, true);
7951					}.bind(this))
7952				.on("rename_node.jstree create_node.jstree", function (e, data) {
7953						this.sort(data.parent || data.node.parent, false);
7954						this.redraw_node(data.parent || data.node.parent, true);
7955					}.bind(this))
7956				.on("move_node.jstree copy_node.jstree", function (e, data) {
7957						this.sort(data.parent, false);
7958						this.redraw_node(data.parent, true);
7959					}.bind(this));
7960		};
7961		/**
7962		 * used to sort a node's children
7963		 * @private
7964		 * @name sort(obj [, deep])
7965		 * @param  {mixed} obj the node
7966		 * @param {Boolean} deep if set to `true` nodes are sorted recursively.
7967		 * @plugin sort
7968		 * @trigger search.jstree
7969		 */
7970		this.sort = function (obj, deep) {
7971			var i, j;
7972			obj = this.get_node(obj);
7973			if(obj && obj.children && obj.children.length) {
7974				obj.children.sort(this.settings.sort.bind(this));
7975				if(deep) {
7976					for(i = 0, j = obj.children_d.length; i < j; i++) {
7977						this.sort(obj.children_d[i], false);
7978					}
7979				}
7980			}
7981		};
7982	};
7983
7984	// include the sort plugin by default
7985	// $.jstree.defaults.plugins.push("sort");
7986
7987/**
7988 * ### State plugin
7989 *
7990 * Saves the state of the tree (selected nodes, opened nodes) on the user's computer using available options (localStorage, cookies, etc)
7991 */
7992
7993	var to = false;
7994	/**
7995	 * stores all defaults for the state plugin
7996	 * @name $.jstree.defaults.state
7997	 * @plugin state
7998	 */
7999	$.jstree.defaults.state = {
8000		/**
8001		 * A string for the key to use when saving the current tree (change if using multiple trees in your project). Defaults to `jstree`.
8002		 * @name $.jstree.defaults.state.key
8003		 * @plugin state
8004		 */
8005		key		: 'jstree',
8006		/**
8007		 * A space separated list of events that trigger a state save. Defaults to `changed.jstree open_node.jstree close_node.jstree`.
8008		 * @name $.jstree.defaults.state.events
8009		 * @plugin state
8010		 */
8011		events	: 'changed.jstree open_node.jstree close_node.jstree check_node.jstree uncheck_node.jstree',
8012		/**
8013		 * Time in milliseconds after which the state will expire. Defaults to 'false' meaning - no expire.
8014		 * @name $.jstree.defaults.state.ttl
8015		 * @plugin state
8016		 */
8017		ttl		: false,
8018		/**
8019		 * 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.
8020		 * @name $.jstree.defaults.state.filter
8021		 * @plugin state
8022		 */
8023		filter	: false,
8024		/**
8025		 * 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`
8026		 * @name $.jstree.defaults.state.preserve_loaded
8027		 * @plugin state
8028		 */
8029		preserve_loaded : false
8030	};
8031	$.jstree.plugins.state = function (options, parent) {
8032		this.bind = function () {
8033			parent.bind.call(this);
8034			var bind = function () {
8035				this.element.on(this.settings.state.events, function () {
8036					if(to) { clearTimeout(to); }
8037					to = setTimeout(function () { this.save_state(); }.bind(this), 100);
8038				}.bind(this));
8039				/**
8040				 * triggered when the state plugin is finished restoring the state (and immediately after ready if there is no state to restore).
8041				 * @event
8042				 * @name state_ready.jstree
8043				 * @plugin state
8044				 */
8045				this.trigger('state_ready');
8046			}.bind(this);
8047			this.element
8048				.on("ready.jstree", function (e, data) {
8049						this.element.one("restore_state.jstree", bind);
8050						if(!this.restore_state()) { bind(); }
8051					}.bind(this));
8052		};
8053		/**
8054		 * save the state
8055		 * @name save_state()
8056		 * @plugin state
8057		 */
8058		this.save_state = function () {
8059			var tm = this.get_state();
8060			if (!this.settings.state.preserve_loaded) {
8061				delete tm.core.loaded;
8062			}
8063			var st = { 'state' : tm, 'ttl' : this.settings.state.ttl, 'sec' : +(new Date()) };
8064			$.vakata.storage.set(this.settings.state.key, JSON.stringify(st));
8065		};
8066		/**
8067		 * restore the state from the user's computer
8068		 * @name restore_state()
8069		 * @plugin state
8070		 */
8071		this.restore_state = function () {
8072			var k = $.vakata.storage.get(this.settings.state.key);
8073			if(!!k) { try { k = JSON.parse(k); } catch(ex) { return false; } }
8074			if(!!k && k.ttl && k.sec && +(new Date()) - k.sec > k.ttl) { return false; }
8075			if(!!k && k.state) { k = k.state; }
8076			if(!!k && $.vakata.is_function(this.settings.state.filter)) { k = this.settings.state.filter.call(this, k); }
8077			if(!!k) {
8078				if (!this.settings.state.preserve_loaded) {
8079					delete k.core.loaded;
8080				}
8081				this.element.one("set_state.jstree", function (e, data) { data.instance.trigger('restore_state', { 'state' : $.extend(true, {}, k) }); });
8082				this.set_state(k);
8083				return true;
8084			}
8085			return false;
8086		};
8087		/**
8088		 * clear the state on the user's computer
8089		 * @name clear_state()
8090		 * @plugin state
8091		 */
8092		this.clear_state = function () {
8093			return $.vakata.storage.del(this.settings.state.key);
8094		};
8095	};
8096
8097	(function ($, undefined) {
8098		$.vakata.storage = {
8099			// simply specifying the functions in FF throws an error
8100			set : function (key, val) { return window.localStorage.setItem(key, val); },
8101			get : function (key) { return window.localStorage.getItem(key); },
8102			del : function (key) { return window.localStorage.removeItem(key); }
8103		};
8104	}($));
8105
8106	// include the state plugin by default
8107	// $.jstree.defaults.plugins.push("state");
8108
8109/**
8110 * ### Types plugin
8111 *
8112 * 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.
8113 */
8114
8115	/**
8116	 * 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).
8117	 *
8118	 * * `max_children` the maximum number of immediate children this node type can have. Do not specify or set to `-1` for unlimited.
8119	 * * `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.
8120	 * * `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.
8121	 * * `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.
8122	 * * `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)
8123	 * * `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)
8124	 *
8125	 * There are two predefined types:
8126	 *
8127	 * * `#` represents the root of the tree, for example `max_children` would control the maximum number of root nodes.
8128	 * * `default` represents the default node - any settings here will be applied to all nodes that do not have a type specified.
8129	 *
8130	 * @name $.jstree.defaults.types
8131	 * @plugin types
8132	 */
8133	$.jstree.defaults.types = {
8134		'default' : {}
8135	};
8136	$.jstree.defaults.types[$.jstree.root] = {};
8137
8138	$.jstree.plugins.types = function (options, parent) {
8139		this.init = function (el, options) {
8140			var i, j;
8141			if(options && options.types && options.types['default']) {
8142				for(i in options.types) {
8143					if(i !== "default" && i !== $.jstree.root && options.types.hasOwnProperty(i)) {
8144						for(j in options.types['default']) {
8145							if(options.types['default'].hasOwnProperty(j) && options.types[i][j] === undefined) {
8146								options.types[i][j] = options.types['default'][j];
8147							}
8148						}
8149					}
8150				}
8151			}
8152			parent.init.call(this, el, options);
8153			this._model.data[$.jstree.root].type = $.jstree.root;
8154		};
8155		this.refresh = function (skip_loading, forget_state) {
8156			parent.refresh.call(this, skip_loading, forget_state);
8157			this._model.data[$.jstree.root].type = $.jstree.root;
8158		};
8159		this.bind = function () {
8160			this.element
8161				.on('model.jstree', function (e, data) {
8162						var m = this._model.data,
8163							dpc = data.nodes,
8164							t = this.settings.types,
8165							i, j, c = 'default', k;
8166						for(i = 0, j = dpc.length; i < j; i++) {
8167							c = 'default';
8168							if(m[dpc[i]].original && m[dpc[i]].original.type && t[m[dpc[i]].original.type]) {
8169								c = m[dpc[i]].original.type;
8170							}
8171							if(m[dpc[i]].data && m[dpc[i]].data.jstree && m[dpc[i]].data.jstree.type && t[m[dpc[i]].data.jstree.type]) {
8172								c = m[dpc[i]].data.jstree.type;
8173							}
8174							m[dpc[i]].type = c;
8175							if(m[dpc[i]].icon === true && t[c].icon !== undefined) {
8176								m[dpc[i]].icon = t[c].icon;
8177							}
8178							if(t[c].li_attr !== undefined && typeof t[c].li_attr === 'object') {
8179								for (k in t[c].li_attr) {
8180									if (t[c].li_attr.hasOwnProperty(k)) {
8181										if (k === 'id') {
8182											continue;
8183										}
8184										else if (m[dpc[i]].li_attr[k] === undefined) {
8185											m[dpc[i]].li_attr[k] = t[c].li_attr[k];
8186										}
8187										else if (k === 'class') {
8188											m[dpc[i]].li_attr['class'] = t[c].li_attr['class'] + ' ' + m[dpc[i]].li_attr['class'];
8189										}
8190									}
8191								}
8192							}
8193							if(t[c].a_attr !== undefined && typeof t[c].a_attr === 'object') {
8194								for (k in t[c].a_attr) {
8195									if (t[c].a_attr.hasOwnProperty(k)) {
8196										if (k === 'id') {
8197											continue;
8198										}
8199										else if (m[dpc[i]].a_attr[k] === undefined) {
8200											m[dpc[i]].a_attr[k] = t[c].a_attr[k];
8201										}
8202										else if (k === 'href' && m[dpc[i]].a_attr[k] === '#') {
8203											m[dpc[i]].a_attr['href'] = t[c].a_attr['href'];
8204										}
8205										else if (k === 'class') {
8206											m[dpc[i]].a_attr['class'] = t[c].a_attr['class'] + ' ' + m[dpc[i]].a_attr['class'];
8207										}
8208									}
8209								}
8210							}
8211						}
8212						m[$.jstree.root].type = $.jstree.root;
8213					}.bind(this));
8214			parent.bind.call(this);
8215		};
8216		this.get_json = function (obj, options, flat) {
8217			var i, j,
8218				m = this._model.data,
8219				opt = options ? $.extend(true, {}, options, {no_id:false}) : {},
8220				tmp = parent.get_json.call(this, obj, opt, flat);
8221			if(tmp === false) { return false; }
8222			if($.vakata.is_array(tmp)) {
8223				for(i = 0, j = tmp.length; i < j; i++) {
8224					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";
8225					if(options && options.no_id) {
8226						delete tmp[i].id;
8227						if(tmp[i].li_attr && tmp[i].li_attr.id) {
8228							delete tmp[i].li_attr.id;
8229						}
8230						if(tmp[i].a_attr && tmp[i].a_attr.id) {
8231							delete tmp[i].a_attr.id;
8232						}
8233					}
8234				}
8235			}
8236			else {
8237				tmp.type = (tmp.id || tmp.id === 0) && m[tmp.id] && m[tmp.id].type ? m[tmp.id].type : "default";
8238				if(options && options.no_id) {
8239					tmp = this._delete_ids(tmp);
8240				}
8241			}
8242			return tmp;
8243		};
8244		this._delete_ids = function (tmp) {
8245			if($.vakata.is_array(tmp)) {
8246				for(var i = 0, j = tmp.length; i < j; i++) {
8247					tmp[i] = this._delete_ids(tmp[i]);
8248				}
8249				return tmp;
8250			}
8251			delete tmp.id;
8252			if(tmp.li_attr && tmp.li_attr.id) {
8253				delete tmp.li_attr.id;
8254			}
8255			if(tmp.a_attr && tmp.a_attr.id) {
8256				delete tmp.a_attr.id;
8257			}
8258			if(tmp.children && $.vakata.is_array(tmp.children)) {
8259				tmp.children = this._delete_ids(tmp.children);
8260			}
8261			return tmp;
8262		};
8263		this.check = function (chk, obj, par, pos, more) {
8264			if(parent.check.call(this, chk, obj, par, pos, more) === false) { return false; }
8265			obj = obj && (obj.id || obj.id === 0) ? obj : this.get_node(obj);
8266			par = par && (par.id || par.id === 0) ? par : this.get_node(par);
8267			var m = obj && (obj.id || obj.id === 0) ? (more && more.origin ? more.origin : $.jstree.reference(obj.id)) : null, tmp, d, i, j;
8268			m = m && m._model && m._model.data ? m._model.data : null;
8269			switch(chk) {
8270				case "create_node":
8271				case "move_node":
8272				case "copy_node":
8273					if(chk !== 'move_node' || $.inArray(obj.id, par.children) === -1) {
8274						tmp = this.get_rules(par);
8275						if(tmp.max_children !== undefined && tmp.max_children !== -1 && tmp.max_children === par.children.length) {
8276							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 }) };
8277							return false;
8278						}
8279						if(tmp.valid_children !== undefined && tmp.valid_children !== -1 && $.inArray((obj.type || 'default'), tmp.valid_children) === -1) {
8280							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 }) };
8281							return false;
8282						}
8283						if(m && obj.children_d && obj.parents) {
8284							d = 0;
8285							for(i = 0, j = obj.children_d.length; i < j; i++) {
8286								d = Math.max(d, m[obj.children_d[i]].parents.length);
8287							}
8288							d = d - obj.parents.length + 1;
8289						}
8290						if(d <= 0 || d === undefined) { d = 1; }
8291						do {
8292							if(tmp.max_depth !== undefined && tmp.max_depth !== -1 && tmp.max_depth < d) {
8293								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 }) };
8294								return false;
8295							}
8296							par = this.get_node(par.parent);
8297							tmp = this.get_rules(par);
8298							d++;
8299						} while(par);
8300					}
8301					break;
8302			}
8303			return true;
8304		};
8305		/**
8306		 * used to retrieve the type settings object for a node
8307		 * @name get_rules(obj)
8308		 * @param {mixed} obj the node to find the rules for
8309		 * @return {Object}
8310		 * @plugin types
8311		 */
8312		this.get_rules = function (obj) {
8313			obj = this.get_node(obj);
8314			if(!obj) { return false; }
8315			var tmp = this.get_type(obj, true);
8316			if(tmp.max_depth === undefined) { tmp.max_depth = -1; }
8317			if(tmp.max_children === undefined) { tmp.max_children = -1; }
8318			if(tmp.valid_children === undefined) { tmp.valid_children = -1; }
8319			return tmp;
8320		};
8321		/**
8322		 * used to retrieve the type string or settings object for a node
8323		 * @name get_type(obj [, rules])
8324		 * @param {mixed} obj the node to find the rules for
8325		 * @param {Boolean} rules if set to `true` instead of a string the settings object will be returned
8326		 * @return {String|Object}
8327		 * @plugin types
8328		 */
8329		this.get_type = function (obj, rules) {
8330			obj = this.get_node(obj);
8331			return (!obj) ? false : ( rules ? $.extend({ 'type' : obj.type }, this.settings.types[obj.type]) : obj.type);
8332		};
8333		/**
8334		 * used to change a node's type
8335		 * @name set_type(obj, type)
8336		 * @param {mixed} obj the node to change
8337		 * @param {String} type the new type
8338		 * @plugin types
8339		 */
8340		this.set_type = function (obj, type) {
8341			var m = this._model.data, t, t1, t2, old_type, old_icon, k, d, a;
8342			if($.vakata.is_array(obj)) {
8343				obj = obj.slice();
8344				for(t1 = 0, t2 = obj.length; t1 < t2; t1++) {
8345					this.set_type(obj[t1], type);
8346				}
8347				return true;
8348			}
8349			t = this.settings.types;
8350			obj = this.get_node(obj);
8351			if(!t[type] || !obj) { return false; }
8352			d = this.get_node(obj, true);
8353			if (d && d.length) {
8354				a = d.children('.jstree-anchor');
8355			}
8356			old_type = obj.type;
8357			old_icon = this.get_icon(obj);
8358			obj.type = type;
8359			if(old_icon === true || !t[old_type] || (t[old_type].icon !== undefined && old_icon === t[old_type].icon)) {
8360				this.set_icon(obj, t[type].icon !== undefined ? t[type].icon : true);
8361			}
8362
8363			// remove old type props
8364			if(t[old_type] && t[old_type].li_attr !== undefined && typeof t[old_type].li_attr === 'object') {
8365				for (k in t[old_type].li_attr) {
8366					if (t[old_type].li_attr.hasOwnProperty(k)) {
8367						if (k === 'id') {
8368							continue;
8369						}
8370						else if (k === 'class') {
8371							m[obj.id].li_attr['class'] = (m[obj.id].li_attr['class'] || '').replace(t[old_type].li_attr[k], '');
8372							if (d) { d.removeClass(t[old_type].li_attr[k]); }
8373						}
8374						else if (m[obj.id].li_attr[k] === t[old_type].li_attr[k]) {
8375							m[obj.id].li_attr[k] = null;
8376							if (d) { d.removeAttr(k); }
8377						}
8378					}
8379				}
8380			}
8381			if(t[old_type] && t[old_type].a_attr !== undefined && typeof t[old_type].a_attr === 'object') {
8382				for (k in t[old_type].a_attr) {
8383					if (t[old_type].a_attr.hasOwnProperty(k)) {
8384						if (k === 'id') {
8385							continue;
8386						}
8387						else if (k === 'class') {
8388							m[obj.id].a_attr['class'] = (m[obj.id].a_attr['class'] || '').replace(t[old_type].a_attr[k], '');
8389							if (a) { a.removeClass(t[old_type].a_attr[k]); }
8390						}
8391						else if (m[obj.id].a_attr[k] === t[old_type].a_attr[k]) {
8392							if (k === 'href') {
8393								m[obj.id].a_attr[k] = '#';
8394								if (a) { a.attr('href', '#'); }
8395							}
8396							else {
8397								delete m[obj.id].a_attr[k];
8398								if (a) { a.removeAttr(k); }
8399							}
8400						}
8401					}
8402				}
8403			}
8404
8405			// add new props
8406			if(t[type].li_attr !== undefined && typeof t[type].li_attr === 'object') {
8407				for (k in t[type].li_attr) {
8408					if (t[type].li_attr.hasOwnProperty(k)) {
8409						if (k === 'id') {
8410							continue;
8411						}
8412						else if (m[obj.id].li_attr[k] === undefined) {
8413							m[obj.id].li_attr[k] = t[type].li_attr[k];
8414							if (d) {
8415								if (k === 'class') {
8416									d.addClass(t[type].li_attr[k]);
8417								}
8418								else {
8419									d.attr(k, t[type].li_attr[k]);
8420								}
8421							}
8422						}
8423						else if (k === 'class') {
8424							m[obj.id].li_attr['class'] = t[type].li_attr[k] + ' ' + m[obj.id].li_attr['class'];
8425							if (d) { d.addClass(t[type].li_attr[k]); }
8426						}
8427					}
8428				}
8429			}
8430			if(t[type].a_attr !== undefined && typeof t[type].a_attr === 'object') {
8431				for (k in t[type].a_attr) {
8432					if (t[type].a_attr.hasOwnProperty(k)) {
8433						if (k === 'id') {
8434							continue;
8435						}
8436						else if (m[obj.id].a_attr[k] === undefined) {
8437							m[obj.id].a_attr[k] = t[type].a_attr[k];
8438							if (a) {
8439								if (k === 'class') {
8440									a.addClass(t[type].a_attr[k]);
8441								}
8442								else {
8443									a.attr(k, t[type].a_attr[k]);
8444								}
8445							}
8446						}
8447						else if (k === 'href' && m[obj.id].a_attr[k] === '#') {
8448							m[obj.id].a_attr['href'] = t[type].a_attr['href'];
8449							if (a) { a.attr('href', t[type].a_attr['href']); }
8450						}
8451						else if (k === 'class') {
8452							m[obj.id].a_attr['class'] = t[type].a_attr['class'] + ' ' + m[obj.id].a_attr['class'];
8453							if (a) { a.addClass(t[type].a_attr[k]); }
8454						}
8455					}
8456				}
8457			}
8458
8459			return true;
8460		};
8461	};
8462	// include the types plugin by default
8463	// $.jstree.defaults.plugins.push("types");
8464
8465
8466/**
8467 * ### Unique plugin
8468 *
8469 * Enforces that no nodes with the same name can coexist as siblings.
8470 */
8471
8472	/**
8473	 * stores all defaults for the unique plugin
8474	 * @name $.jstree.defaults.unique
8475	 * @plugin unique
8476	 */
8477	$.jstree.defaults.unique = {
8478		/**
8479		 * Indicates if the comparison should be case sensitive. Default is `false`.
8480		 * @name $.jstree.defaults.unique.case_sensitive
8481		 * @plugin unique
8482		 */
8483		case_sensitive : false,
8484		/**
8485		 * Indicates if white space should be trimmed before the comparison. Default is `false`.
8486		 * @name $.jstree.defaults.unique.trim_whitespace
8487		 * @plugin unique
8488		 */
8489		trim_whitespace : false,
8490		/**
8491		 * 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)`.
8492		 * @name $.jstree.defaults.unique.duplicate
8493		 * @plugin unique
8494		 */
8495		duplicate : function (name, counter) {
8496			return name + ' (' + counter + ')';
8497		}
8498	};
8499
8500	$.jstree.plugins.unique = function (options, parent) {
8501		this.check = function (chk, obj, par, pos, more) {
8502			if(parent.check.call(this, chk, obj, par, pos, more) === false) { return false; }
8503			obj = obj && (obj.id || obj.id === 0) ? obj : this.get_node(obj);
8504			par = par && (par.id || par.id === 0) ? par : this.get_node(par);
8505			if(!par || !par.children) { return true; }
8506			var n = chk === "rename_node" ? pos : obj.text,
8507				c = [],
8508				s = this.settings.unique.case_sensitive,
8509				w = this.settings.unique.trim_whitespace,
8510				m = this._model.data, i, j, t;
8511			for(i = 0, j = par.children.length; i < j; i++) {
8512				t = m[par.children[i]].text;
8513				if (!s) {
8514					t = t.toLowerCase();
8515				}
8516				if (w) {
8517					t = t.replace(/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g, '');
8518				}
8519				c.push(t);
8520			}
8521			if(!s) { n = n.toLowerCase(); }
8522			if (w) { n = n.replace(/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g, ''); }
8523			switch(chk) {
8524				case "delete_node":
8525					return true;
8526				case "rename_node":
8527					t = obj.text || '';
8528					if (!s) {
8529						t = t.toLowerCase();
8530					}
8531					if (w) {
8532						t = t.replace(/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g, '');
8533					}
8534					i = ($.inArray(n, c) === -1 || (obj.text && t === n));
8535					if(!i) {
8536						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 }) };
8537					}
8538					return i;
8539				case "create_node":
8540					i = ($.inArray(n, c) === -1);
8541					if(!i) {
8542						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 }) };
8543					}
8544					return i;
8545				case "copy_node":
8546					i = ($.inArray(n, c) === -1);
8547					if(!i) {
8548						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 }) };
8549					}
8550					return i;
8551				case "move_node":
8552					i = ( (obj.parent === par.id && (!more || !more.is_multi)) || $.inArray(n, c) === -1);
8553					if(!i) {
8554						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 }) };
8555					}
8556					return i;
8557			}
8558			return true;
8559		};
8560		this.create_node = function (par, node, pos, callback, is_loaded) {
8561			if(!node || (typeof node === 'object' && node.text === undefined)) {
8562				if(par === null) {
8563					par = $.jstree.root;
8564				}
8565				par = this.get_node(par);
8566				if(!par) {
8567					return parent.create_node.call(this, par, node, pos, callback, is_loaded);
8568				}
8569				pos = pos === undefined ? "last" : pos;
8570				if(!pos.toString().match(/^(before|after)$/) && !is_loaded && !this.is_loaded(par)) {
8571					return parent.create_node.call(this, par, node, pos, callback, is_loaded);
8572				}
8573				if(!node) { node = {}; }
8574				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;
8575				n = tmp = this.get_string('New node');
8576				dpc = [];
8577				for(i = 0, j = par.children.length; i < j; i++) {
8578					t = m[par.children[i]].text;
8579					if (!s) {
8580						t = t.toLowerCase();
8581					}
8582					if (w) {
8583						t = t.replace(/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g, '');
8584					}
8585					dpc.push(t);
8586				}
8587				i = 1;
8588				t = n;
8589				if (!s) {
8590					t = t.toLowerCase();
8591				}
8592				if (w) {
8593					t = t.replace(/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g, '');
8594				}
8595				while($.inArray(t, dpc) !== -1) {
8596					n = cb.call(this, tmp, (++i)).toString();
8597					t = n;
8598					if (!s) {
8599						t = t.toLowerCase();
8600					}
8601					if (w) {
8602						t = t.replace(/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g, '');
8603					}
8604				}
8605				node.text = n;
8606			}
8607			return parent.create_node.call(this, par, node, pos, callback, is_loaded);
8608		};
8609	};
8610
8611	// include the unique plugin by default
8612	// $.jstree.defaults.plugins.push("unique");
8613
8614
8615/**
8616 * ### Wholerow plugin
8617 *
8618 * Makes each node appear block level. Making selection easier. May cause slow down for large trees in old browsers.
8619 */
8620
8621	var div = document.createElement('DIV');
8622	div.setAttribute('unselectable','on');
8623	div.setAttribute('role','presentation');
8624	div.className = 'jstree-wholerow';
8625	div.innerHTML = '&#160;';
8626	$.jstree.plugins.wholerow = function (options, parent) {
8627		this.bind = function () {
8628			parent.bind.call(this);
8629
8630			this.element
8631				.on('ready.jstree set_state.jstree', function () {
8632						this.hide_dots();
8633					}.bind(this))
8634				.on("init.jstree loading.jstree ready.jstree", function () {
8635						//div.style.height = this._data.core.li_height + 'px';
8636						this.get_container_ul().addClass('jstree-wholerow-ul');
8637					}.bind(this))
8638				.on("deselect_all.jstree", function (e, data) {
8639						this.element.find('.jstree-wholerow-clicked').removeClass('jstree-wholerow-clicked');
8640					}.bind(this))
8641				.on("changed.jstree", function (e, data) {
8642						this.element.find('.jstree-wholerow-clicked').removeClass('jstree-wholerow-clicked');
8643						var tmp = false, i, j;
8644						for(i = 0, j = data.selected.length; i < j; i++) {
8645							tmp = this.get_node(data.selected[i], true);
8646							if(tmp && tmp.length) {
8647								tmp.children('.jstree-wholerow').addClass('jstree-wholerow-clicked');
8648							}
8649						}
8650					}.bind(this))
8651				.on("open_node.jstree", function (e, data) {
8652						this.get_node(data.node, true).find('.jstree-clicked').parent().children('.jstree-wholerow').addClass('jstree-wholerow-clicked');
8653					}.bind(this))
8654				.on("hover_node.jstree dehover_node.jstree", function (e, data) {
8655						if(e.type === "hover_node" && this.is_disabled(data.node)) { return; }
8656						this.get_node(data.node, true).children('.jstree-wholerow')[e.type === "hover_node"?"addClass":"removeClass"]('jstree-wholerow-hovered');
8657					}.bind(this))
8658				.on("contextmenu.jstree", ".jstree-wholerow", function (e) {
8659						if (this._data.contextmenu) {
8660							e.preventDefault();
8661							var tmp = $.Event('contextmenu', { metaKey : e.metaKey, ctrlKey : e.ctrlKey, altKey : e.altKey, shiftKey : e.shiftKey, pageX : e.pageX, pageY : e.pageY });
8662							$(e.currentTarget).closest(".jstree-node").children(".jstree-anchor").first().trigger(tmp);
8663						}
8664					}.bind(this))
8665				/*!
8666				.on("mousedown.jstree touchstart.jstree", ".jstree-wholerow", function (e) {
8667						if(e.target === e.currentTarget) {
8668							var a = $(e.currentTarget).closest(".jstree-node").children(".jstree-anchor");
8669							e.target = a[0];
8670							a.trigger(e);
8671						}
8672					})
8673				*/
8674				.on("click.jstree", ".jstree-wholerow", function (e) {
8675						e.stopImmediatePropagation();
8676						var tmp = $.Event('click', { metaKey : e.metaKey, ctrlKey : e.ctrlKey, altKey : e.altKey, shiftKey : e.shiftKey });
8677						$(e.currentTarget).closest(".jstree-node").children(".jstree-anchor").first().trigger(tmp).trigger('focus');
8678					})
8679				.on("dblclick.jstree", ".jstree-wholerow", function (e) {
8680						e.stopImmediatePropagation();
8681						var tmp = $.Event('dblclick', { metaKey : e.metaKey, ctrlKey : e.ctrlKey, altKey : e.altKey, shiftKey : e.shiftKey });
8682						$(e.currentTarget).closest(".jstree-node").children(".jstree-anchor").first().trigger(tmp).trigger('focus');
8683					})
8684				.on("click.jstree", ".jstree-leaf > .jstree-ocl", function (e) {
8685						e.stopImmediatePropagation();
8686						var tmp = $.Event('click', { metaKey : e.metaKey, ctrlKey : e.ctrlKey, altKey : e.altKey, shiftKey : e.shiftKey });
8687						$(e.currentTarget).closest(".jstree-node").children(".jstree-anchor").first().trigger(tmp).trigger('focus');
8688					}.bind(this))
8689				.on("mouseover.jstree", ".jstree-wholerow, .jstree-icon", function (e) {
8690						e.stopImmediatePropagation();
8691						if(!this.is_disabled(e.currentTarget)) {
8692							this.hover_node(e.currentTarget);
8693						}
8694						return false;
8695					}.bind(this))
8696				.on("mouseleave.jstree", ".jstree-node", function (e) {
8697						this.dehover_node(e.currentTarget);
8698					}.bind(this));
8699		};
8700		this.teardown = function () {
8701			if(this.settings.wholerow) {
8702				this.element.find(".jstree-wholerow").remove();
8703			}
8704			parent.teardown.call(this);
8705		};
8706		this.redraw_node = function(obj, deep, callback, force_render) {
8707			obj = parent.redraw_node.apply(this, arguments);
8708			if(obj) {
8709				var tmp = div.cloneNode(true);
8710				//tmp.style.height = this._data.core.li_height + 'px';
8711				if($.inArray(obj.id, this._data.core.selected) !== -1) { tmp.className += ' jstree-wholerow-clicked'; }
8712				if(this._data.core.focused && this._data.core.focused === obj.id) { tmp.className += ' jstree-wholerow-hovered'; }
8713				obj.insertBefore(tmp, obj.childNodes[0]);
8714			}
8715			return obj;
8716		};
8717	};
8718	// include the wholerow plugin by default
8719	// $.jstree.defaults.plugins.push("wholerow");
8720	if(window.customElements && Object && Object.create) {
8721		var proto = Object.create(HTMLElement.prototype);
8722		proto.createdCallback = function () {
8723			var c = { core : {}, plugins : [] }, i;
8724			for(i in $.jstree.plugins) {
8725				if($.jstree.plugins.hasOwnProperty(i) && this.attributes[i]) {
8726					c.plugins.push(i);
8727					if(this.getAttribute(i) && JSON.parse(this.getAttribute(i))) {
8728						c[i] = JSON.parse(this.getAttribute(i));
8729					}
8730				}
8731			}
8732			for(i in $.jstree.defaults.core) {
8733				if($.jstree.defaults.core.hasOwnProperty(i) && this.attributes[i]) {
8734					c.core[i] = JSON.parse(this.getAttribute(i)) || this.getAttribute(i);
8735				}
8736			}
8737			$(this).jstree(c);
8738		};
8739		// proto.attributeChangedCallback = function (name, previous, value) { };
8740		try {
8741			window.customElements.define("vakata-jstree", function() {}, { prototype: proto });
8742		} catch (ignore) { }
8743	}
8744
8745}));

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.