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