PageSourceSearch

https://www.navicat.com/templates/Navicat/dist/jstree.js

js navicat.com collected 2026-10-02 02:16:08 UTC 306,372 bytes, 8,423 lines download raw bytes

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

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