PageSourceSearch

https://p-on.ru/third-party/vakata-jstree/dist/jstree.js?1789650323

js p-on.ru collected 2026-09-24 10:56:01 UTC 196,263 bytes, 5,799 lines download raw bytes

vendor: 4,499 bytes, lines 1-162
1/*globals jQuery, define, exports, require, window, document */
2(function (factory) {
3	"use strict";
4	if (typeof define === 'function' && define.amd) {
5		define(['jquery'], factory);
6	}
7	else if(typeof exports === 'object') {
8		factory(require('jquery'));
9	}
10	else {
11		factory(jQuery);
12	}
13}(function ($, undefined) {
14	"use strict";
15/*!
16 * jsTree 3.0.0
17 * http://jstree.com/
18 *
19 * Copyright (c) 2013 Ivan Bozhanov (http://vakata.com)
20 *
21 * Licensed same as jquery - under the terms of the MIT License
22 *   http://www.opensource.org/licenses/mit-license.php
23 */
24/*!
25 * if using jslint please allow for the jQuery global and use following options: 
26 * jslint: browser: true, ass: true, bitwise: true, continue: true, nomen: true, plusplus: true, regexp: true, unparam: true, todo: true, white: true
27 */
28
29	// prevent another load? maybe there is a better way?
30	if($.jstree) {
31		return;
32	}
33
34	/**
35	 * ### jsTree core functionality
36	 */
37
38	// internal variables
39	var instance_counter = 0,
40		ccp_node = false,
41		ccp_mode = false,
42		ccp_inst = false,
43		themes_loaded = [],
44		src = $('script:last').attr('src'),
45		_d = document, _node = _d.createElement('LI'), _temp1, _temp2;
46
47	_node.setAttribute('role', 'treeitem');
48	_temp1 = _d.createElement('I');
49	_temp1.className = 'jstree-icon jstree-ocl';
50	_node.appendChild(_temp1);
51	_temp1 = _d.createElement('A');
52	_temp1.className = 'jstree-anchor';
53	_temp1.setAttribute('href','#');
54	_temp2 = _d.createElement('I');
55	_temp2.className = 'jstree-icon jstree-themeicon';
56	_temp1.appendChild(_temp2);
57	_node.appendChild(_temp1);
58	_temp1 = _temp2 = null;
59
60
61	/**
62	 * holds all jstree related functions and variables, including the actual class and methods to create, access and manipulate instances.
63	 * @name $.jstree
64	 */
65	$.jstree = {
66		/** 
67		 * specifies the jstree version in use
68		 * @name $.jstree.version
69		 */
70		version : '3.0.0-beta9',
71		/**
72		 * holds all the default options used when creating new instances
73		 * @name $.jstree.defaults
74		 */
75		defaults : {
76			/**
77			 * 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 `[]`
78			 * @name $.jstree.defaults.plugins
79			 */
80			plugins : []
81		},
82		/**
83		 * stores all loaded jstree plugins (used internally)
84		 * @name $.jstree.plugins
85		 */
86		plugins : {},
87		path : src && src.indexOf('/') !== -1 ? src.replace(/\/[^\/]+$/,'') : ''
88	};
89	/**
90	 * creates a jstree instance
91	 * @name $.jstree.create(el [, options])
92	 * @param {DOMElement|jQuery|String} el the element to create the instance on, can be jQuery extended or a selector
93	 * @param {Object} options options for this instance (extends `$.jstree.defaults`)
94	 * @return {jsTree} the new instance
95	 */
96	$.jstree.create = function (el, options) {
97		var tmp = new $.jstree.core(++instance_counter),
98			opt = options;
99		options = $.extend(true, {}, $.jstree.defaults, options);
100		if(opt && opt.plugins) {
101			options.plugins = opt.plugins;
102		}
103		$.each(options.plugins, function (i, k) {
104			if(i !== 'core') {
105				tmp = tmp.plugin(k, options[k]);
106			}
107		});
108		tmp.init(el, options);
109		return tmp;
110	};
111	/**
112	 * the jstree class constructor, used only internally
113	 * @private
114	 * @name $.jstree.core(id)
115	 * @param {Number} id this instance's index
116	 */
117	$.jstree.core = function (id) {
118		this._id = id;
119		this._cnt = 0;
120		this._data = {
121			core : {
122				themes : {
123					name : false,
124					dots : false,
125					icons : false
126				},
127				selected : [],
128				last_error : {}
129			}
130		};
131	};
132	/**
133	 * get a reference to an existing instance
134	 *
135	 * __Examples__
136	 *
137	 *	// provided a container with an ID of "tree", and a nested node with an ID of "branch"
138	 *	// all of there will return the same instance
139	 *	$.jstree.reference('tree');
140	 *	$.jstree.reference('#tree');
141	 *	$.jstree.reference($('#tree'));
142	 *	$.jstree.reference(document.getElementByID('tree'));
143	 *	$.jstree.reference('branch');
144	 *	$.jstree.reference('#branch');
145	 *	$.jstree.reference($('#branch'));
146	 *	$.jstree.reference(document.getElementByID('branch'));
147	 *
148	 * @name $.jstree.reference(needle)
149	 * @param {DOMElement|jQuery|String} needle
150	 * @return {jsTree|null} the instance or `null` if not found
151	 */
152	$.jstree.reference = function (needle) {
153		if(needle && !$(needle).length) {
154			if(needle.id) {
155				needle = needle.id;
156			}
157			var tmp = null;
158			$('.jstree').each(function () {
159				var inst = $(this).data('jstree');
160				if(inst && inst._model.data[needle]) {
161					tmp = inst;
162					return false;
163				}
164			});
165			return tmp;
166		}
167		return $(needle).closest('.jstree').data('jstree');
168	};
169	/**
170	 * Create an instance, get an instance or invoke a command on a instance. 
171	 * 
172	 * 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).
173	 * 
174	 * 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).
175	 * 
176	 * If there is an existing instance and `arg` is not a string the instance itself is returned (similar to `$.jstree.reference`).
177	 * 
178	 * In any other case - nothing is returned and chaining is not broken.
179	 *
180	 * __Examples__
181	 *
182	 *	$('#tree1').jstree(); // creates an instance
183	 *	$('#tree2').jstree({ plugins : [] }); // create an instance with some options
184	 *	$('#tree1').jstree('open_node', '#branch_1'); // call a method on an existing instance, passing additional arguments
185	 *	$('#tree2').jstree(); // get an existing instance (or create an instance)
186	 *	$('#tree2').jstree(true); // get an existing instance (will not create new instance)
187	 *	$('#branch_1').jstree().select_node('#branch_1'); // get an instance (using a nested element and call a method)
188	 *
189	 * @name $().jstree([arg])
190	 * @param {String|Object} arg
191	 * @return {Mixed}
192	 */
193	$.fn.jstree = function (arg) {
194		// check for string argument
195		var is_method	= (typeof arg === 'string'),
196			args		= Array.prototype.slice.call(arguments, 1),
197			result		= null;
198		this.each(function () {
199			// get the instance (if there is one) and method (if it exists)
200			var instance = $.jstree.reference(this),
201				method = is_method && instance ? instance[arg] : null;
202			// if calling a method, and method is available - execute on the instance
203			result = is_method && method ?
204				method.apply(instance, args) :
205				null;
206			// if there is no instance and no method is being called - create one
207			if(!instance && !is_method && (arg === undefined || $.isPlainObject(arg))) {
208				$(this).data('jstree', new $.jstree.create(this, arg));
209			}
210			// if there is an instance and no method is called - return the instance
211			if(instance && !is_method) {
212				result = instance;
213			}
214			// if there was a method call which returned a result - break and return the value
215			if(result !== null && result !== undefined) {
216				return false;
217			}
218		});
219		// if there was a method call with a valid return value - return that, otherwise continue the chain
220		return result !== null && result !== undefined ?
221			result : this;
222	};
223	/**
224	 * used to find elements containing an instance
225	 *
226	 * __Examples__
227	 *
228	 *	$('div:jstree').each(function () {
229	 *		$(this).jstree('destroy');
230	 *	});
231	 *
232	 * @name $(':jstree')
233	 * @return {jQuery}
234	 */
235	$.expr[':'].jstree = $.expr.createPseudo(function(search) {
236		return function(a) {
237			return $(a).hasClass('jstree') &&
238				$(a).data('jstree') !== undefined;
239		};
240	});
241
242	/**
243	 * stores all defaults for the core
244	 * @name $.jstree.defaults.core
245	 */
246	$.jstree.defaults.core = {
247		/**
248		 * data configuration
249		 * 
250		 * 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).
251		 *
252		 * You can also pass in a HTML string or a JSON array here.
253		 * 
254		 * 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. 
255		 * 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.
256		 * 
257		 * 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.
258		 *
259		 * __Examples__
260		 *
261		 *	// AJAX
262		 *	$('#tree').jstree({
263		 *		'core' : {
264		 *			'data' : {
265		 *				'url' : '/get/children/',
266		 *				'data' : function (node) {
267		 *					return { 'id' : node.id };
268		 *				}
269		 *			}
270		 *		});
271		 *
272		 *	// direct data
273		 *	$('#tree').jstree({
274		 *		'core' : {
275		 *			'data' : [
276		 *				'Simple root node',
277		 *				{
278		 *					'id' : 'node_2',
279		 *					'text' : 'Root node with options',
280		 *					'state' : { 'opened' : true, 'selected' : true },
281		 *					'children' : [ { 'text' : 'Child 1' }, 'Child 2']
282		 *				}
283		 *			]
284		 *		});
285		 *	
286		 *	// function
287		 *	$('#tree').jstree({
288		 *		'core' : {
289		 *			'data' : function (obj, callback) {
290		 *				callback.call(this, ['Root 1', 'Root 2']);
291		 *			}
292		 *		});
293		 * 
294		 * @name $.jstree.defaults.core.data
295		 */
296		data			: false,
297		/**
298		 * configure the various strings used throughout the tree
299		 *
300		 * You can use an object where the key is the string you need to replace and the value is your replacement.
301		 * Another option is to specify a function which will be called with an argument of the needed 
301string and should return the replacement.
302		 * If left as `false` no replacement is made.
303		 *
304		 * __Examples__
305		 *
306		 *	$('#tree').jstree({
307		 *		'core' : {
308		 *			'strings' : {
309		 *				'Loading...' : 'Please wait ...'
310		 *			}
311		 *		}
312		 *	});
313		 *
314		 * @name $.jstree.defaults.core.strings
315		 */
316		strings			: false,
317		/**
318		 * determines what happens when a user tries to modify the structure of the tree
319		 * If left as `false` all operations like create, rename, delete, move or copy are prevented.
320		 * You can set this to `true` to allow all interactions or use a function to have better control.
321		 *
322		 * __Examples__
323		 *
324		 *	$('#tree').jstree({
325		 *		'core' : {
326		 *			'check_callback' : function (operation, node, node_parent, node_position) {
327		 *				// operation can be 'create_node', 'rename_node', 'delete_node', 'move_node' or 'copy_node'
328		 *				// in case of 'rename_node' node_position is filled with the new node name
329		 *				return operation === 'rename_node' ? true : false;
330		 *			}
331		 *		}
332		 *	});
333		 * 
334		 * @name $.jstree.defaults.core.check_callback
335		 */
336		check_callback	: false,
337		/**
338		 * a callback called with a single object parameter in the instance's scope when something goes wrong (operation prevented, ajax failed, etc)
339		 * @name $.jstree.defaults.core.error
340		 */
341		error			: $.noop,
342		/**
343		 * the open / close animation duration in milliseconds - set this to `false` to disable the animation (default is `200`)
344		 * @name $.jstree.defaults.core.animation
345		 */
346		animation		: 200,
347		/**
348		 * a boolean indicating if multiple nodes can be selected
349		 * @name $.jstree.defaults.core.multiple
350		 */
351		multiple		: true,
352		/**
353		 * theme configuration object
354		 * @name $.jstree.defaults.core.themes
355		 */
356		themes			: {
357			/**
358			 * the name of the theme to use (if left as `false` the default theme is used)
359			 * @name $.jstree.defaults.core.themes.name
360			 */
361			name			: false,
362			/**
363			 * 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.
364			 * @name $.jstree.defaults.core.themes.url
365			 */
366			url				: false,
367			/**
368			 * the location of all jstree themes - only used if `url` is set to `true`
369			 * @name $.jstree.defaults.core.themes.dir
370			 */
371			dir				: false,
372			/**
373			 * a boolean indicating if connecting dots are shown
374			 * @name $.jstree.defaults.core.themes.dots
375			 */
376			dots			: true,
377			/**
378			 * a boolean indicating if node icons are shown
379			 * @name $.jstree.defaults.core.themes.icons
380			 */
381			icons			: true,
382			/**
383			 * a boolean indicating if the tree background is striped
384			 * @name $.jstree.defaults.core.themes.stripes
385			 */
386			stripes			: false,
387			/**
388			 * a string (or boolean `false`) specifying the theme variant to use (if the theme supports variants)
389			 * @name $.jstree.defaults.core.themes.variant
390			 */
391			variant			: false,
392			/**
393			 * a boolean specifying if a reponsive version of the theme should kick in on smaller screens (if the theme supports it). Defaults to `true`.
394			 * @name $.jstree.defaults.core.themes.responsive
395			 */
396			responsive		: true
397		},
398		/**
399		 * 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)
400		 * @name $.jstree.defaults.core.expand_selected_onload
401		 */
402		expand_selected_onload : true
403	};
404	$.jstree.core.prototype = {
405		/**
406		 * used to decorate an instance with a plugin. Used internally.
407		 * @private
408		 * @name plugin(deco [, opts])
409		 * @param  {String} deco the plugin to decorate with
410		 * @param  {Object} opts options for the plugin
411		 * @return {jsTree}
412		 */
413		plugin : function (deco, opts) {
414			var Child = $.jstree.plugins[deco];
415			if(Child) {
416				this._data[deco] = {};
417				Child.prototype = this;
418				return new Child(opts, this);
419			}
420			return this;
421		},
422		/**
423		 * used to decorate an instance with a plugin. Used internally.
424		 * @private
425		 * @name init(el, optons)
426		 * @param {DOMElement|jQuery|String} el the element we are transforming
427		 * @param {Object} options options for this instance
428		 * @trigger init.jstree, loading.jstree, loaded.jstree, ready.jstree, changed.jstree
429		 */
430		init : function (el, options) {
431			this._model = {
432				data : {
433					'#' : {
434						id : '#',
435						parent : null,
436						parents : [],
437						children : [],
438						children_d : [],
439						state : { loaded : false }
440					}
441				},
442				changed : [],
443				force_full_redraw : false,
444				redraw_timeout : false,
445				default_state : {
446					loaded : true,
447					opened : false,
448					selected : false,
449					disabled : false
450				}
451			};
452
453			this.element = $(el).addClass('jstree jstree-' + this._id);
454			this.settings = options;
455			this.element.bind("destroyed", $.proxy(this.teardown, this));
456
457			this._data.core.ready = false;
458			this._data.core.loaded = false;
459			this._data.core.rtl = (this.element.css("direction") === "rtl");
460			this.element[this._data.core.rtl ? 'addClass' : 'removeClass']("jstree-rtl");
461			this.element.attr('role','tree');
462
463			this.bind();
464			/**
465			 * triggered after all events are bound
466			 * @event
467			 * @name init.jstree
468			 */
469			this.trigger("init");
470
471			this._data.core.original_container_html = this.element.find(" > ul > li").clone(true);
472			this._data.core.original_container_html
473				.find("li").addBack()
474				.contents().filter(function() {
475					return this.nodeType === 3 && (!this.nodeValue || /^\s+$/.test(this.nodeValue));
476				})
477				.remove();
478			this.element.html("<"+"ul class='jstree-container-ul'><"+"li class='jstree-initial-node jstree-loading jstree-leaf jstree-last'><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>");
479			this._data.core.li_height = this.get_container_ul().children("li:eq(0)").height() || 18;
480			/**
481			 * triggered after the loading text is shown and before loading starts
482			 * @event
483			 * @name loading.jstree
484			 */
485			this.trigger("loading");
486			this.load_node('#');
487		},
488		/**
489		 * destroy an instance
490		 * @name destroy()
491		 */
492		destroy : function () {
493			this.element.unbind("destroyed", this.teardown);
494			this.teardown();
495		},
496		/**
497		 * part of the destroying of an instance. Used internally.
498		 * @private
499		 * @name teardown()
500		 */
501		teardown : function () {
502			this.unbind();
503			this.element
504				.removeClass('jstree')
505				.removeData('jstree')
506				.find("[class^='jstree']")
507					.addBack()
508					.attr("class", function () { return this.className.replace(/jstree[^ ]*|$/ig,''); });
509			this.element = null;
510		},
511		/**
512		 * bind all events. Used internally.
513		 * @private
514		 * @name bind()
515		 */
516		bind : function () {
517			this.element
518				.on("dblclick.jstree", function () {
519						if(document.selection && document.selection.empty) {
520							document.selection.empty();
521						}
522						else {
523							if(window.getSelection) {
524								var sel = window.getSelection();
525								try {
526									sel.removeAllRanges();
527									sel.collapse();
528								} catch (ignore) { }
529							}
530						}
531					})
532				.on("click.jstree", ".jstree-ocl", $.proxy(function (e) {
533						this.toggle_node(e.target);
534					}, this))
535				.on("click.jstree", ".jstree-anchor", $.proxy(function (e) {
536						e.preventDefault();
537						$(e.currentTarget).focus();
538						this.activate_node(e.currentTarget, e);
539					}, this))
540				.on('keydown.jstree', '.jstree-anchor', $.proxy(function (e) {
541						var o = null;
542						switch(e.which) {
543							case 13:
544							case 32:
545								e.type = "click";
546								$(e.currentTarget).trigger(e);
547								break;
548							case 37:
549								e.preventDefault();
550								if(this.is_open(e.currentTarget)) {
551									this.close_node(e.currentTarget);
552								}
553								else {
554									o = this.get_prev_dom(e.currentTarget);
555									if(o && o.length) { o.children('.jstree-anchor').focus(); }
556								}
557								break;
558							case 38:
559								e.preventDefault();
560								o = this.get_prev_dom(e.currentTarget);
561								if(o && o.length) { o.children('.jstree-anchor').focus(); }
562								break;
563							case 39:
564								e.preventDefault();
565								if(this.is_closed(e.currentTarget)) {
566									this.open_node(e.currentTarget, function (o) { this.get_node(o, true).children('.jstree-anchor').focus(); });
567								}
568								else {
569									o = this.get_next_dom(e.currentTarget);
570									if(o && o.length) { o.children('.jstree-anchor').focus(); }
571								}
572								break;
573							case 40:
574								e.preventDefault();
575								o = this.get_next_dom(e.currentTarget);
576								if(o && o.length) { o.children('.jstree-anchor').focus(); }
577								break;
578							// delete
579							case 46:
580								e.preventDefault();
581								o = this.get_node(e.currentTarget);
582								if(o && o.id && o.id !== '#') {
583									o = this.is_selected(o) ? this.get_selected() : o;
584									// this.delete_node(o);
585								}
586								break;
587							// f2
588							case 113:
589								e.preventDefault();
590								o = this.get_node(e.currentTarget);
591								/*!
592								if(o && o.id && o.id !== '#') {
593									// this.edit(o);
594								}
595								*/
596								break;
597							default:
598								// console.log(e.which);
599								break;
600						}
601					}, this))
602				.on("load_node.jstree", $.proxy(function (e, data) {
603						if(data.status) {
604							if(data.node.id === '#' && !this._data.core.loaded) {
605								this._data.core.loaded = true;
606								/**
607								 * triggered after the root node is loaded for the first time
608								 * @event
609								 * @name loaded.jstree
610								 */
611								this.trigger("loaded");
612							}
613							if(!this._data.core.ready && !this.get_container_ul().find('.jstree-loading:eq(0)').length) {
614								this._data.core.ready = true;
615								if(this._data.core.selected.length) {
616									if(this.settings.core.expand_selected_onload) {
617										var tmp = [], i, j;
618										for(i = 0, j = this._data.core.selected.length; i < j; i++) {
619											tmp = tmp.concat(this._model.data[this._data.core.selected[i]].parents);
620										}
621										tmp = $.vakata.array_unique(tmp);
622										for(i = 0, j = tmp.length; i < j; i++) {
623											this.open_node(tmp[i], false, 0);
624										}
625									}
626									this.trigger('changed', { 'action' : 'ready', 'selected' : this._data.core.selected });
627								}
628								/**
629								 * triggered after all nodes are finished loading
630								 * @event
631								 * @name ready.jstree
632								 */
633								setTimeout($.proxy(function () { this.trigger("ready"); }, this), 0);
634							}
635						}
636					}, this))
637				// THEME RELATED
638				.on("init.jstree", $.proxy(function () {
639						var s = this.settings.core.themes;
640						this._data.core.themes.dots			= s.dots;
641						this._data.core.themes.stripes		= s.stripes;
642						this._data.core.themes.icons		= s.icons;
643						this.set_theme(s.name || "default", s.url);
644						this.set_theme_variant(s.variant);
645					}, this))
646				.on("loading.jstree", $.proxy(function () {
647						this[ this._data.core.themes.dots ? "show_dots" : "hide_dots" ]();
648						this[ this._data.core.themes.icons ? "show_icons" : "hide_icons" ]();
649						this[ this._data.core.themes.stripes ? "show_stripes" : "hide_stripes" ]();
650					}, this))
651				.on('focus.jstree', '.jstree-anchor', $.proxy(function (e) {
652						this.element.find('.jstree-hovered').not(e.currentTarget).mouseleave();
653						$(e.currentTarget).mouseenter();
654					}, this))
655				.on('mouseenter.jstree', '.jstree-anchor', $.proxy(function (e) {
656						this.hover_node(e.currentTarget);
657					}, this))
658				.on('mouseleave.jstree', '.jstree-anchor', $.proxy(function (e) {
659						this.dehover_node(e.currentTarget);
660					}, this));
661		},
662		/**
663		 * part of the destroying of an instance. Used internally.
664		 * @private
665		 * @name unbind()
666		 */
667		unbind : function () {
668			this.element.off('.jstree');
669			$(document).off('.jstree-' + this._id);
670		},
671		/**
672		 * trigger an event. Used internally.
673		 * @private
674		 * @name trigger(ev [, data])
675		 * @param  {String} ev the name of the event to trigger
676		 * @param  {Object} data additional data to pass with the event
677		 */
678		trigger : function (ev, data) {
679			if(!data) {
680				data = {};
681			}
682			data.instance = this;
683			this.element.triggerHandler(ev.replace('.jstree','') + '.jstree', data);
684		},
685		/**
686		 * returns the jQuery extended instance container
687		 * @name get_container()
688		 * @return {jQuery}
689		 */
690		get_container : function () {
691			return this.element;
692		},
693		/**
694		 * returns the jQuery extended main UL node inside the instance container. Used internally.
695		 * @private
696		 * @name get_container_ul()
697		 * @return {jQuery}
698		 */
699		get_container_ul : function () {
700			return this.element.children("ul:eq(0)");
701		},
702		/**
703		 * gets string replacements (localization). Used internally.
704		 * @private
705		 * @name get_string(key)
706		 * @param  {String} key
707		 * @return {String}
708		 */
709		get_string : function (key) {
710			var a = this.settings.core.strings;
711			if($.isFunction(a)) { return a.call(this, key); }
712			if(a && a[key]) { return a[key]; }
713			return key;
714		},
715		/**
716		 * gets the first child of a DOM node. Used internally.
717		 * @private
718		 * @name _firstChild(dom)
719		 * @param  {DOMElement} dom
720		 * @return {DOMElement}
721		 */
722		_firstChild : function (dom) {
723			dom = dom ? dom.firstChild : null;
724			while(dom !== null && dom.nodeType !== 1) {
725				dom = dom.nextSibling;
726			}
727			return dom;
728		},
729		/**
730		 * gets the next sibling of a DOM node. Used internally.
731		 * @private
732		 * @name _nextSibling(dom)
733		 * @param  {DOMElement} dom
734		 * @return {DOMElement}
735		 */
736		_nextSibling : function (dom) {
737			dom = dom ? dom.nextSibling : null;
738			while(dom !== null && dom.nodeType !== 1) {
739				dom = dom.nextSibling;
740			}
741			return dom;
742		},
743		/**
744		 * gets the previous sibling of a DOM node. Used internally.
745		 * @private
746		 * @name _previousSibling(dom)
747		 * @param  {DOMElement} dom
748		 * @return {DOMElement}
749		 */
750		_previousSibling : function (dom) {
751			dom = dom ? dom.previousSibling : null;
752			while(dom !== null && dom.nodeType !== 1) {
753				dom = dom.previousSibling;
754			}
755			return dom;
756		},
757		/**
758		 * 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)
759		 * @name get_node(obj [, as_dom])
760		 * @param  {mixed} obj
761		 * @param  {Boolean} as_dom
762		 * @return {Object|jQuery}
763		 */
764		get_node : function (obj, as_dom) {
765			if(obj && obj.id) {
766				obj = obj.id;
767			}
768			var dom;
769			try {
770				if(this._model.data[obj]) {
771					obj = this._model.data[obj];
772				}
773				else if(((dom = $(obj, this.element)).length || (dom = $('#' + obj, this.element)).length) && this._model.data[dom.closest('li').attr('id')]) {
774					obj = this._model.data[dom.closest('li').attr('id')];
775				}
776				else if((dom = $(obj, this.element)).length && dom.hasClass('jstree')) {
777					obj = this._model.data['#'];
778				}
779				else {
780					return false;
781				}
782
783				if(as_dom) {
784					obj = obj.id === '#' ? this.element : $(document.getElementById(obj.id));
785				}
786				return obj;
787			} catch (ex) { return false; }
788		},
789		/**
790		 * get the path to a node, either consisting of node texts, or of node IDs, optionally glued together (otherwise an array)
791		 * @name get_path(obj [, glue, ids])
792		 * @param  {mixed} obj the node
793		 * @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
794		 * @param  {Boolean} ids if set to true build the path using ID, otherwise node text is used
795		 * @return {mixed}
796		 */
797		get_path : function (obj, glue, ids) {
798			obj = obj.parents ? obj : this.get_node(obj);
799			if(!obj || obj.id === '#' || !obj.parents) {
800				return false;
801			}
802			var i, j, p = [];
803			p.push(ids ? obj.id : obj.text);
804			for(i = 0, j = obj.parents.length; i < j; i++) {
805				p.push(ids ? obj.parents[i] : this.get_text(obj.parents[i]));
806			}
807			p = p.reverse().slice(1);
808			return glue ? p.join(glue) : p;
809		},
810		/**
811		 * get the next visible node that is below the `obj` node. If `strict` is set to `true` only sibling nodes are returned.
812		 * @name get_next_dom(obj [, strict])
813		 * @param  {mixed} obj
814		 * @param  {Boolean} strict
815		 * @return {jQuery}
816		 */
817		get_next_dom : function (obj, strict) {
818			var tmp;
819			obj = this.get_node(obj, true);
820			if(obj[0] === this.element[0]) {
821				tmp = this._firstChild(this.get_container_ul()[0]);
822				return tmp ? $(tmp) : false;
823			}
824			if(!obj || !obj.length) {
825				return false;
826			}
827			if(strict) {
828				tmp = this._nextSibling(obj[0]);
829				return tmp ? $(tmp) : false;
830			}
831			if(obj.hasClass("jstree-open")) {
832				tmp = this._firstChild(obj.children('ul')[0]);
833				return tmp ? $(tmp) : false;
834			}
835			if((tmp = this._nextSibling(obj[0])) !== null) {
836				return $(tmp);
837			}
838			return obj.parentsUntil(".jstree","li").next("li").eq(0);
839		},
840		/**
841		 * get the previous visible node that is above the `obj` node. If `strict` is set to `true` only sibling nodes are returned.
842		 * @name get_prev_dom(obj [, strict])
843		 * @param  {mixed} obj
844		 * @param  {Boolean} strict
845		 * @return {jQuery}
846		 */
847		get_prev_dom : function (obj, strict) {
848			var tmp;
849			obj = this.get_node(obj, true);
850			if(obj[0] === this.element[0]) {
851				tmp = this.get_container_ul()[0].lastChild;
852				return tmp ? $(tmp) : false;
853			}
854			if(!obj || !obj.length) {
855				return false;
856			}
857			if(strict) {
858				tmp = this._previousSibling(obj[0]);
859				return tmp ? $(tmp) : false;
860			}
861			if((tmp = this._previousSibling(obj[0])) !== null) {
862				obj = $(tmp);
863				while(obj.hasClass("jstree-open")) {
864					obj = obj.children("ul:eq(0)").children("li:last");
865				}
866				return obj;
867			}
868			tmp = obj[0].parentNode.parentNode;
869			return tmp && tmp.tagName === 'LI' ? $(tmp) : false;
870		},
871		/**
872		 * get the parent ID of a node
873		 * @name get_parent(obj)
874		 * @param  {mixed} obj
875		 * @return {String}
876		 */
877		get_parent : function (obj) {
878			obj = this.get_node(obj);
879			if(!obj || obj.id === '#') {
880				return false;
881			}
882			return obj.parent;
883		},
884		/**
885		 * get a jQuery collection of all the children of a node (node must be rendered)
886		 * @name get_children_dom(obj)
887		 * @param  {mixed} obj
888		 * @return {jQuery}
889		 */
890		get_children_dom : function (obj) {
891			obj = this.get_node(obj, true);
892			if(obj[0] === this.element[0]) {
893				return this.get_container_ul().children("li");
894			}
895			if(!obj || !obj.length) {
896				return false;
897			}
898			return obj.children("ul").children("li");
899		},
900		/**
901		 * checks if a node has children
902		 * @name is_parent(obj)
903		 * @param  {mixed} obj
904		 * @return {Boolean}
905		 */
906		is_parent : function (obj) {
907			obj = this.get_node(obj);
908			return obj && (obj.state.loaded === false || obj.children.length > 0);
909		},
910		/**
911		 * checks if a node is loaded (its children are available)
912		 * @name is_loaded(obj)
913		 * @param  {mixed} obj
914		 * @return {Boolean}
915		 */
916		is_loaded : function (obj) {
917			obj = this.get_node(obj);
918			return obj && obj.state.loaded;
919		},
920		/**
921		 * check if a node is currently loading (fetching children)
922		 * @name is_loading(obj)
923		 * @param  {mixed} obj
924		 * @return {Boolean}
925		 */
926		is_loading : function (obj) {
927			obj = this.get_node(obj, true);
928			return obj && obj.hasClass("jstree-loading");
929		},
930		/**
931		 * check if a node is opened
932		 * @name is_open(obj)
933		 * @param  {mixed} obj
934		 * @return {Boolean}
935		 */
936		is_open : function (obj) {
937			obj = this.get_node(obj);
938			return obj && obj.state.opened;
939		},
940		/**
941		 * check if a node is in a closed state
942		 * @name is_closed(obj)
943		 * @param  {mixed} obj
944		 * @return {Boolean}
945		 */
946		is_closed : function (obj) {
947			obj = this.get_node(obj);
948			return obj && this.is_parent(obj) && !obj.state.opened;
949		},
950		/**
951		 * check if a node has no children
952		 * @name is_leaf(obj)
953		 * @param  {mixed} obj
954		 * @return {Boolean}
955		 */
956		is_leaf : function (obj) {
957			return !this.is_parent(obj);
958		},
959		/**
960		 * loads a node (fetches its children using the `core.data` setting). Multiple nodes can be passed to by using an array.
961		 * @name load_node(obj [, callback])
962		 * @param  {mixed} obj
963		 * @param  {function} callback a function to be executed once loading is conplete, the function is executed in the instance's scope and receives two arguments - the node and a boolean status
964		 * @return {Boolean}
965		 * @trigger load_node.jstree
966		 */
967		load_node : function (obj, callback) {
968			var t1, t2;
969			if($.isArray(obj)) {
970				obj = obj.slice();
971				for(t1 = 0, t2 = obj.length; t1 < t2; t1++) {
972					this.load_node(obj[t1], callback);
973				}
974				return true;
975			}
976			obj = this.get_node(obj);
977			if(!obj) {
978				callback.call(this, obj, false);
979				return false;
980			}
981			this.get_node(obj, true).addClass("jstree-loading");
982			this._load_node(obj, $.proxy(function (status) {
983				obj.state.loaded = status;
984				this.get_node(obj, true).removeClass("jstree-loading");
985				/**
986				 * triggered after a node is loaded
987				 * @event
988				 * @name load_node.jstree
989				 * @param {Object} node the node that was loading
990				 * @param {Boolean} status was the node loaded successfully
991				 */
992				this.trigger('load_node', { "node" : obj, "status" : status });
993				if(callback) {
994					callback.call(this, obj, status);
995				}
996			}, this));
997			return true;
998		},
999		/**
1000		 * handles the actual loading of a node. Used only internally.
1001		 * @private
1002		 * @name _load_node(obj [, callback])
1003		 * @param  {mixed} obj
1004		 * @param  {function} callback a function to be executed once loading is conplete, the function is executed in the instance's scope and receives one argument - a boolean status
1005		 * @return {Boolean}
1006		 */
1007		_load_node : function (obj, callback) {
1008			var s = this.settings.core.data, t;
1009			// use original HTML
1010			if(!s) {
1011				return callback.call(this, obj.id === '#' ? this._append_html_data(obj, this._data.core.original_container_html.clone(true)) : false);
1012			}
1013			if($.isFunction(s)) {
1014				return s.call(this, obj, $.proxy(function (d) {
1015					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));
1016				}, this));
1017			}
1018			if(typeof s === 'object') {
1019				if(s.url) {
1020					s = $.extend(true, {}, s);
1021					if($.isFunction(s.url)) {
1022						s.url = s.url.call(this, obj);
1023					}
1024					if($.isFunction(s.data)) {
1025						s.data = s.data.call(this, obj);
1026					}
1027					return $.ajax(s)
1028						.done($.proxy(function (d,t,x) {
1029								var type = x.getResponseHeader('Content-Type');
1030								if(type.indexOf('json') !== -1) {
1031									return callback.call(this, this._append_json_data(obj, d));
1032								}
1033								if(type.indexOf('html') !== -1) {
1034									return callback.call(this, this._append_html_data(obj, $(d)));
1035								}
1036							}, this))
1037						.fail($.proxy(function () {
1038								callback.call(this, false);
1039								this._data.core.last_error = { 'error' : 'ajax', 'plugin' : 'core', 'id' : 'core_04', 'reason' : 'Could not load node', 'data' : JSON.stringify(s) };
1040								this.settings.core.error.call(this, this._data.core.last_error);
1041							}, this));
1042				}
1043				t = ($.isArray(s) || $.isPlainObject(s)) ? JSON.parse(JSON.stringify(s)) : s;
1044				return callback.call(this, this._append_json_data(obj, t));
1045			}
1046			if(typeof s === 'string') {
1047				return callback.call(this, this._append_html_data(obj, s));
1048			}
1049			return callback.call(this, false);
1050		},
1051		/**
1052		 * adds a node to the list of nodes to redraw. Used only internally.
1053		 * @private
1054		 * @name _node_changed(obj [, callback])
1055		 * @param  {mixed} obj
1056		 */
1057		_node_changed : function (obj) {
1058			obj = this.get_node(obj);
1059			if(obj) {
1060				this._model.changed.push(obj.id);
1061			}
1062		},
1063		/**
1064		 * appends HTML content to the tree. Used internally.
1065		 * @private
1066		 * @name _append_html_data(obj, data)
1067		 * @param  {mixed} obj the node to append to
1068		 * @param  {String} data the HTML string to parse and append
1069		 * @return {Boolean}
1070		 * @trigger model.jstree, changed.jstree
1071		 */
1072		_append_html_data : function (dom, data) {
1073			dom = this.get_node(dom);
1074			dom.children = [];
1075			dom.children_d = [];
1076			var dat = data.is('ul') ? data.children() : data,
1077				par = dom.id,
1078				chd = [],
1079				dpc = [],
1080				m = this._model.data,
1081				p = m[par],
1082				s = this._data.core.selected.length,
1083				tmp, i, j;
1084			dat.each($.proxy(function (i, v) {
1085				tmp = this._parse_model_from_html($(v), par, p.parents.concat());
1086				if(tmp) {
1087					chd.push(tmp);
1088					dpc.push(tmp);
1089					if(m[tmp].children_d.length) {
1090						dpc = dpc.concat(m[tmp].children_d);
1091					}
1092				}
1093			}, this));
1094			p.children = chd;
1095			p.children_d = dpc;
1096			for(i = 0, j = p.parents.length; i < j; i++) {
1097				m[p.parents[i]].children_d = m[p.parents[i]].children_d.concat(dpc);
1098			}
1099			/**
1100			 * triggered when new data is inserted to the tree model
1101			 * @event
1102			 * @name model.jstree
1103			 * @param {Array} nodes an array of node IDs
1104			 * @param {String} parent the parent ID of the nodes
1105			 */
1106			this.trigger('model', { "nodes" : dpc, 'parent' : par });
1107			if(par !== '#') {
1108				this._node_changed(par);
1109				this.redraw();
1110			}
1111			else {
1112				this.get_container_ul().children('.jstree-initial-node').remove();
1113				this.redraw(true);
1114			}
1115			if(this._data.core.selected.length !== s) {
1116				this.trigger('changed', { 'action' : 'model', 'selected' : this._data.core.selected });
1117			}
1118			return true;
1119		},
1120		/**
1121		 * appends JSON content to the tree. Used internally.
1122		 * @private
1123		 * @name _append_json_data(obj, data)
1124		 * @param  {mixed} obj the node to append to
1125		 * @param  {String} data the JSON object to parse and append
1126		 * @return {Boolean}
1127		 */
1128		_append_json_data : function (dom, data) {
1129			dom = this.get_node(dom);
1130			dom.children = [];
1131			dom.children_d = [];
1132			var dat = data,
1133				par = dom.id,
1134				chd = [],
1135				dpc = [],
1136				m = this._model.data,
1137				p = m[par],
1138				s = this._data.core.selected.length,
1139				tmp, i, j;
1140			// *%$@!!!
1141			if(dat.d) {
1142				dat = dat.d;
1143				if(typeof dat === "string") {
1144					dat = JSON.parse(dat);
1145				}
1146			}
1147			if(!$.isArray(dat)) { dat = [dat]; }
1148			if(dat.length && dat[0].id !== undefined && dat[0].parent !== undefined) {
1149				// Flat JSON support (for easy import from DB):
1150				// 1) convert to object (foreach)
vendor: 16,499 bytes, lines 1151-1710
1151				for(i = 0, j = dat.length; i < j; i++) {
1152					if(!dat[i].children) {
1153						dat[i].children = [];
1154					}
1155					m[dat[i].id] = dat[i];
1156				}
1157				// 2) populate children (foreach)
1158				for(i = 0, j = dat.length; i < j; i++) {
1159					m[dat[i].parent].children.push(dat[i].id);
1160					// populate parent.children_d
1161					p.children_d.push(dat[i].id);
1162				}
1163				// 3) normalize && populate parents and children_d with recursion
1164				for(i = 0, j = p.children.length; i < j; i++) {
1165					tmp = this._parse_model_from_flat_json(m[p.children[i]], par, p.parents.concat());
1166					dpc.push(tmp);
1167					if(m[tmp].children_d.length) {
1168						dpc = dpc.concat(m[tmp].children_d);
1169					}
1170				}
1171				// ?) three_state selection - p.state.selected && t - (if three_state foreach(dat => ch) -> foreach(parents) if(parent.selected) child.selected = true;
1172			}
1173			else {
1174				for(i = 0, j = dat.length; i < j; i++) {
1175					tmp = this._parse_model_from_json(dat[i], par, p.parents.concat());
1176					if(tmp) {
1177						chd.push(tmp);
1178						dpc.push(tmp);
1179						if(m[tmp].children_d.length) {
1180							dpc = dpc.concat(m[tmp].children_d);
1181						}
1182					}
1183				}
1184				p.children = chd;
1185				p.children_d = dpc;
1186				for(i = 0, j = p.parents.length; i < j; i++) {
1187					m[p.parents[i]].children_d = m[p.parents[i]].children_d.concat(dpc);
1188				}
1189			}
1190			this.trigger('model', { "nodes" : dpc, 'parent' : par });
1191
1192			if(par !== '#') {
1193				this._node_changed(par);
1194				this.redraw();
1195			}
1196			else {
1197				// this.get_container_ul().children('.jstree-initial-node').remove();
1198				this.redraw(true);
1199			}
1200			if(this._data.core.selected.length !== s) {
1201				this.trigger('changed', { 'action' : 'model', 'selected' : this._data.core.selected });
1202			}
1203			return true;
1204		},
1205		/**
1206		 * parses a node from a jQuery object and appends them to the in memory tree model. Used internally.
1207		 * @private
1208		 * @name _parse_model_from_html(d [, p, ps])
1209		 * @param  {jQuery} d the jQuery object to parse
1210		 * @param  {String} p the parent ID
1211		 * @param  {Array} ps list of all parents
1212		 * @return {String} the ID of the object added to the model
1213		 */
1214		_parse_model_from_html : function (d, p, ps) {
1215			if(!ps) { ps = []; }
1216			else { ps = [].concat(ps); }
1217			if(p) { ps.unshift(p); }
1218			var c, e, m = this._model.data,
1219				data = {
1220					id			: false,
1221					text		: false,
1222					icon		: true,
1223					parent		: p,
1224					parents		: ps,
1225					children	: [],
1226					children_d	: [],
1227					data		: null,
1228					state		: { },
1229					li_attr		: { id : false },
1230					a_attr		: { href : '#' },
1231					original	: false
1232				}, i, tmp, tid;
1233			for(i in this._model.default_state) {
1234				if(this._model.default_state.hasOwnProperty(i)) {
1235					data.state[i] = this._model.default_state[i];
1236				}
1237			}
1238			tmp = $.vakata.attributes(d, true);
1239			$.each(tmp, function (i, v) {
1240				v = $.trim(v);
1241				if(!v.length) { return true; }
1242				data.li_attr[i] = v;
1243				if(i === 'id') {
1244					data.id = v;
1245				}
1246			});
1247			tmp = d.children('a').eq(0);
1248			if(tmp.length) {
1249				tmp = $.vakata.attributes(tmp, true);
1250				$.each(tmp, function (i, v) {
1251					v = $.trim(v);
1252					if(v.length) {
1253						data.a_attr[i] = v;
1254					}
1255				});
1256			}
1257			tmp = d.children("a:eq(0)").length ? d.children("a:eq(0)").clone() : d.clone();
1258			tmp.children("ins, i, ul").remove();
1259			tmp = tmp.html();
1260			tmp = $('<div />').html(tmp);
1261			data.text = tmp.html();
1262			tmp = d.data();
1263			data.data = tmp ? $.extend(true, {}, tmp) : null;
1264			data.state.opened = d.hasClass('jstree-open');
1265			data.state.selected = d.children('a').hasClass('jstree-clicked');
1266			data.state.disabled = d.children('a').hasClass('jstree-disabled');
1267			if(data.data && data.data.jstree) {
1268				for(i in data.data.jstree) {
1269					if(data.data.jstree.hasOwnProperty(i)) {
1270						data.state[i] = data.data.jstree[i];
1271					}
1272				}
1273			}
1274			tmp = d.children("a").children(".jstree-themeicon");
1275			if(tmp.length) {
1276				data.icon = tmp.hasClass('jstree-themeicon-hidden') ? false : tmp.attr('rel');
1277			}
1278			if(data.state.icon) {
1279				data.icon = data.state.icon;
1280			}
1281			tmp = d.children("ul").children("li");
1282			do {
1283				tid = 'j' + this._id + '_' + (++this._cnt);
1284			} while(m[tid]);
1285			data.id = data.li_attr.id || tid;
1286			if(tmp.length) {
1287				tmp.each($.proxy(function (i, v) {
1288					c = this._parse_model_from_html($(v), data.id, ps);
1289					e = this._model.data[c];
1290					data.children.push(c);
1291					if(e.children_d.length) {
1292						data.children_d = data.children_d.concat(e.children_d);
1293					}
1294				}, this));
1295				data.children_d = data.children_d.concat(data.children);
1296			}
1297			else {
1298				if(d.hasClass('jstree-closed')) {
1299					data.state.loaded = false;
1300				}
1301			}
1302			if(data.li_attr['class']) {
1303				data.li_attr['class'] = data.li_attr['class'].replace('jstree-closed','').replace('jstree-open','');
1304			}
1305			if(data.a_attr['class']) {
1306				data.a_attr['class'] = data.a_attr['class'].replace('jstree-clicked','').replace('jstree-disabled','');
1307			}
1308			m[data.id] = data;
1309			if(data.state.selected) {
1310				this._data.core.selected.push(data.id);
1311			}
1312			return data.id;
1313		},
1314		/**
1315		 * 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.
1316		 * @private
1317		 * @name _parse_model_from_flat_json(d [, p, ps])
1318		 * @param  {Object} d the JSON object to parse
1319		 * @param  {String} p the parent ID
1320		 * @param  {Array} ps list of all parents
1321		 * @return {String} the ID of the object added to the model
1322		 */
1323		_parse_model_from_flat_json : function (d, p, ps) {
1324			if(!ps) { ps = []; }
1325			else { ps = ps.concat(); }
1326			if(p) { ps.unshift(p); }
1327			var tid = d.id,
1328				m = this._model.data,
1329				df = this._model.default_state,
1330				i, j, c, e,
1331				tmp = {
1332					id			: tid,
1333					text		: d.text || '',
1334					icon		: d.icon !== undefined ? d.icon : true,
1335					parent		: p,
1336					parents		: ps,
1337					children	: d.children || [],
1338					children_d	: d.children_d || [],
1339					data		: d.data,
1340					state		: { },
1341					li_attr		: { id : false },
1342					a_attr		: { href : '#' },
1343					original	: false
1344				};
1345			for(i in df) {
1346				if(df.hasOwnProperty(i)) {
1347					tmp.state[i] = df[i];
1348				}
1349			}
1350			if(d && d.data && d.data.jstree && d.data.jstree.icon) {
1351				tmp.icon = d.data.jstree.icon;
1352			}
1353			if(d && d.data) {
1354				tmp.data = d.data;
1355				if(d.data.jstree) {
1356					for(i in d.data.jstree) {
1357						if(d.data.jstree.hasOwnProperty(i)) {
1358							tmp.state[i] = d.data.jstree[i];
1359						}
1360					}
1361				}
1362			}
1363			if(d && typeof d.state === 'object') {
1364				for (i in d.state) {
1365					if(d.state.hasOwnProperty(i)) {
1366						tmp.state[i] = d.state[i];
1367					}
1368				}
1369			}
1370			if(d && typeof d.li_attr === 'object') {
1371				for (i in d.li_attr) {
1372					if(d.li_attr.hasOwnProperty(i)) {
1373						tmp.li_attr[i] = d.li_attr[i];
1374					}
1375				}
1376			}
1377			if(!tmp.li_attr.id) {
1378				tmp.li_attr.id = tid;
1379			}
1380			if(d && typeof d.a_attr === 'object') {
1381				for (i in d.a_attr) {
1382					if(d.a_attr.hasOwnProperty(i)) {
1383						tmp.a_attr[i] = d.a_attr[i];
1384					}
1385				}
1386			}
1387			if(d && d.children && d.children === true) {
1388				tmp.state.loaded = false;
1389				tmp.children = [];
1390				tmp.children_d = [];
1391			}
1392			m[tmp.id] = tmp;
1393			for(i = 0, j = tmp.children.length; i < j; i++) {
1394				c = this._parse_model_from_flat_json(m[tmp.children[i]], tmp.id, ps);
1395				e = m[c];
1396				tmp.children_d.push(c);
1397				if(e.children_d.length) {
1398					tmp.children_d = tmp.children_d.concat(e.children_d);
1399				}
1400			}
1401			delete d.data;
1402			delete d.children;
1403			m[tmp.id].original = d;
1404			if(tmp.state.selected) {
1405				this._data.core.selected.push(tmp.id);
1406			}
1407			return tmp.id;
1408		},
1409		/**
1410		 * parses a node from a JSON object and appends it to the in memory tree model. Used internally.
1411		 * @private
1412		 * @name _parse_model_from_json(d [, p, ps])
1413		 * @param  {Object} d the JSON object to parse
1414		 * @param  {String} p the parent ID
1415		 * @param  {Array} ps list of all parents
1416		 * @return {String} the ID of the object added to the model
1417		 */
1418		_parse_model_from_json : function (d, p, ps) {
1419			if(!ps) { ps = []; }
1420			else { ps = ps.concat(); }
1421			if(p) { ps.unshift(p); }
1422			var tid = false, i, j, c, e, m = this._model.data, df = this._model.default_state, tmp;
1423			do {
1424				tid = 'j' + this._id + '_' + (++this._cnt);
1425			} while(m[tid]);
1426
1427			tmp = {
1428				id			: false,
1429				text		: typeof d === 'string' ? d : '',
1430				icon		: typeof d === 'object' && d.icon !== undefined ? d.icon : true,
1431				parent		: p,
1432				parents		: ps,
1433				children	: [],
1434				children_d	: [],
1435				data		: null,
1436				state		: { },
1437				li_attr		: { id : false },
1438				a_attr		: { href : '#' },
1439				original	: false
1440			};
1441			for(i in df) {
1442				if(df.hasOwnProperty(i)) {
1443					tmp.state[i] = df[i];
1444				}
1445			}
1446			if(d && d.id) { tmp.id = d.id; }
1447			if(d && d.text) { tmp.text = d.text; }
1448			if(d && d.data && d.data.jstree && d.data.jstree.icon) {
1449				tmp.icon = d.data.jstree.icon;
1450			}
1451			if(d && d.data) {
1452				tmp.data = d.data;
1453				if(d.data.jstree) {
1454					for(i in d.data.jstree) {
1455						if(d.data.jstree.hasOwnProperty(i)) {
1456							tmp.state[i] = d.data.jstree[i];
1457						}
1458					}
1459				}
1460			}
1461			if(d && typeof d.state === 'object') {
1462				for (i in d.state) {
1463					if(d.state.hasOwnProperty(i)) {
1464						tmp.state[i] = d.state[i];
1465					}
1466				}
1467			}
1468			if(d && typeof d.li_attr === 'object') {
1469				for (i in d.li_attr) {
1470					if(d.li_attr.hasOwnProperty(i)) {
1471						tmp.li_attr[i] = d.li_attr[i];
1472					}
1473				}
1474			}
1475			if(tmp.li_attr.id && !tmp.id) {
1476				tmp.id = tmp.li_attr.id;
1477			}
1478			if(!tmp.id) {
1479				tmp.id = tid;
1480			}
1481			if(!tmp.li_attr.id) {
1482				tmp.li_attr.id = tmp.id;
1483			}
1484			if(d && typeof d.a_attr === 'object') {
1485				for (i in d.a_attr) {
1486					if(d.a_attr.hasOwnProperty(i)) {
1487						tmp.a_attr[i] = d.a_attr[i];
1488					}
1489				}
1490			}
1491			if(d && d.children && d.children.length) {
1492				for(i = 0, j = d.children.length; i < j; i++) {
1493					c = this._parse_model_from_json(d.children[i], tmp.id, ps);
1494					e = m[c];
1495					tmp.children.push(c);
1496					if(e.children_d.length) {
1497						tmp.children_d = tmp.children_d.concat(e.children_d);
1498					}
1499				}
1500				tmp.children_d = tmp.children_d.concat(tmp.children);
1501			}
1502			if(d && d.children && d.children === true) {
1503				tmp.state.loaded = false;
1504				tmp.children = [];
1505				tmp.children_d = [];
1506			}
1507			delete d.data;
1508			delete d.children;
1509			tmp.original = d;
1510			m[tmp.id] = tmp;
1511			if(tmp.state.selected) {
1512				this._data.core.selected.push(tmp.id);
1513			}
1514			return tmp.id;
1515		},
1516		/**
1517		 * redraws all nodes that need to be redrawn. Used internally.
1518		 * @private
1519		 * @name _redraw()
1520		 * @trigger redraw.jstree
1521		 */
1522		_redraw : function () {
1523			var nodes = this._model.force_full_redraw ? this._model.data['#'].children.concat([]) : this._model.changed.concat([]),
1524				f = document.createElement('UL'), tmp, i, j;
1525			for(i = 0, j = nodes.length; i < j; i++) {
1526				tmp = this.redraw_node(nodes[i], true, this._model.force_full_redraw);
1527				if(tmp && this._model.force_full_redraw) {
1528					f.appendChild(tmp);
1529				}
1530			}
1531			if(this._model.force_full_redraw) {
1532				f.className = this.get_container_ul()[0].className;
1533				this.element.empty().append(f);
1534				//this.get_container_ul()[0].appendChild(f);
1535			}
1536			this._model.force_full_redraw = false;
1537			this._model.changed = [];
1538			/**
1539			 * triggered after nodes are redrawn
1540			 * @event
1541			 * @name redraw.jstree
1542			 * @param {array} nodes the redrawn nodes
1543			 */
1544			this.trigger('redraw', { "nodes" : nodes });
1545		},
1546		/**
1547		 * redraws all nodes that need to be redrawn or optionally - the whole tree
1548		 * @name redraw([full])
1549		 * @param {Boolean} full if set to `true` all nodes are redrawn.
1550		 */
1551		redraw : function (full) {
1552			if(full) {
1553				this._model.force_full_redraw = true;
1554			}
1555			//if(this._model.redraw_timeout) {
1556			//	clearTimeout(this._model.redraw_timeout);
1557			//}
1558			//this._model.redraw_timeout = setTimeout($.proxy(this._redraw, this),0);
1559			this._redraw();
1560		},
1561		/**
1562		 * redraws a single node. Used internally.
1563		 * @private
1564		 * @name redraw_node(node, deep, is_callback)
1565		 * @param {mixed} node the node to redraw
1566		 * @param {Boolean} deep should child nodes be redrawn too
1567		 * @param {Boolean} is_callback is this a recursion call
1568		 */
1569		redraw_node : function (node, deep, is_callback) {
1570			var obj = this.get_node(node),
1571				par = false,
1572				ind = false,
1573				old = false,
1574				i = false,
1575				j = false,
1576				k = false,
1577				c = '',
1578				d = document,
1579				m = this._model.data,
1580				f = false,
1581				s = false;
1582			if(!obj) { return false; }
1583			if(obj.id === '#') {  return this.redraw(true); }
1584			deep = deep || obj.children.length === 0;
1585			node = d.getElementById(obj.id); //, this.element);
1586			if(!node) {
1587				deep = true;
1588				//node = d.createElement('LI');
1589				if(!is_callback) {
1590					par = obj.parent !== '#' ? $('#' + obj.parent, this.element)[0] : null;
1591					if(par !== null && (!par || !m[obj.parent].state.opened)) {
1592						return false;
1593					}
1594					ind = $.inArray(obj.id, par === null ? m['#'].children : m[obj.parent].children);
1595				}
1596			}
1597			else {
1598				node = $(node);
1599				if(!is_callback) {
1600					par = node.parent().parent()[0];
1601					if(par === this.element[0]) {
1602						par = null;
1603					}
1604					ind = node.index();
1605				}
1606				// m[obj.id].data = node.data(); // use only node's data, no need to touch jquery storage
1607				if(!deep && obj.children.length && !node.children('ul').length) {
1608					deep = true;
1609				}
1610				if(!deep) {
1611					old = node.children('UL')[0];
1612				}
1613				s = node.attr('aria-selected');
1614				f = node.children('.jstree-anchor')[0] === document.activeElement;
1615				node.remove();
1616				//node = d.createElement('LI');
1617				//node = node[0];
1618			}
1619			node = _node.cloneNode(true);
1620			// node is DOM, deep is boolean
1621
1622			c = 'jstree-node ';
1623			for(i in obj.li_attr) {
1624				if(obj.li_attr.hasOwnProperty(i)) {
1625					if(i === 'id') { continue; }
1626					if(i !== 'class') {
1627						node.setAttribute(i, obj.li_attr[i]);
1628					}
1629					else {
1630						c += obj.li_attr[i];
1631					}
1632				}
1633			}
1634			if(s && s !== "false") {
1635				node.setAttribute('aria-selected', true);
1636			}
1637			if(!obj.children.length && obj.state.loaded) {
1638				c += ' jstree-leaf';
1639			}
1640			else {
1641				c += obj.state.opened ? ' jstree-open' : ' jstree-closed';
1642				node.setAttribute('aria-expanded', obj.state.opened);
1643			}
1644			if(obj.parent !== null && m[obj.parent].children[m[obj.parent].children.length - 1] === obj.id) {
1645				c += ' jstree-last';
1646			}
1647			node.id = obj.id;
1648			node.className = c;
1649			c = ( obj.state.selected ? ' jstree-clicked' : '') + ( obj.state.disabled ? ' jstree-disabled' : '');
1650			for(j in obj.a_attr) {
1651				if(obj.a_attr.hasOwnProperty(j)) {
1652					if(j === 'href' && obj.a_attr[j] === '#') { continue; }
1653					if(j !== 'class') {
1654						node.childNodes[1].setAttribute(j, obj.a_attr[j]);
1655					}
1656					else {
1657						c += ' ' + obj.a_attr[j];
1658					}
1659				}
1660			}
1661			if(c.length) {
1662				node.childNodes[1].className = 'jstree-anchor ' + c;
1663			}
1664			if((obj.icon && obj.icon !== true) || obj.icon === false) {
1665				if(obj.icon === false) {
1666					node.childNodes[1].childNodes[0].className += ' jstree-themeicon-hidden';
1667				}
1668				else if(obj.icon.indexOf('/') === -1 && obj.icon.indexOf('.') === -1) {
1669					node.childNodes[1].childNodes[0].className += ' ' + obj.icon + ' jstree-themeicon-custom';
1670				}
1671				else {
1672					node.childNodes[1].childNodes[0].style.backgroundImage = 'url('+obj.icon+')';
1673					node.childNodes[1].childNodes[0].style.backgroundPosition = 'center center';
1674					node.childNodes[1].childNodes[0].style.backgroundSize = 'auto';
1675					node.childNodes[1].childNodes[0].className += ' jstree-themeicon-custom';
1676				}
1677			}
1678			//node.childNodes[1].appendChild(d.createTextNode(obj.text));
1679			node.childNodes[1].innerHTML += obj.text;
1680			// if(obj.data) { $.data(node, obj.data); } // always work with node's data, no need to touch jquery store
1681
1682			if(deep && obj.children.length && obj.state.opened) {
1683				k = d.createElement('UL');
1684				k.setAttribute('role', 'group');
1685				k.className = 'jstree-children';
1686				for(i = 0, j = obj.children.length; i < j; i++) {
1687					k.appendChild(this.redraw_node(obj.children[i], deep, true));
1688				}
1689				node.appendChild(k);
1690			}
1691			if(old) {
1692				node.appendChild(old);
1693			}
1694			if(!is_callback) {
1695				// append back using par / ind
1696				if(!par) {
1697					par = this.element[0];
1698				}
1699				if(!par.getElementsByTagName('UL').length) {
1700					i = d.createElement('UL');
1701					i.setAttribute('role', 'group');
1702					i.className = 'jstree-children';
1703					par.appendChild(i);
1704					par = i;
1705				}
1706				else {
1707					par = par.getElementsByTagName('UL')[0];
1708				}
1709
1710				if(ind < par.childNodes.length) {
1711					par.insertBefore(node, par.childNodes[ind]);
1712				}
1713				else {
1714					par.appendChild(node);
1715				}
1716				if(f) {
1717					node.childNodes[1].focus();
1718				}
1719			}
1720			return node;
1721		},
1722		/**
1723		 * opens a node, revaling its children. If the node is not loaded it will be loaded and opened once ready.
1724		 * @name open_node(obj [, callback, animation])
1725		 * @param {mixed} obj the node to open
1726		 * @param {Function} callback a function to execute once the node is opened
1727		 * @param {Number} animation the animation duration in milliseconds when opening the node (overrides the `core.animation` setting). Use `false` for no animation.
1728		 * @trigger open_node.jstree, after_open.jstree
1729		 */
1730		open_node : function (obj, callback, animation) {
1731			var t1, t2, d, t;
1732			if($.isArray(obj)) {
1733				obj = obj.slice();
1734				for(t1 = 0, t2 = obj.length; t1 < t2; t1++) {
1735					this.open_node(obj[t1], callback, animation);
1736				}
1737				return true;
1738			}
1739			obj = this.get_node(obj);
1740			if(!obj || obj.id === '#') {
1741				return false;
1742			}
1743			animation = animation === undefined ? this.settings.core.animation : animation;
1744			if(!this.is_closed(obj)) {
1745				if(callback) {
1746					callback.call(this, obj, false);
1747				}
1748				return false;
1749			}
1750			if(!this.is_loaded(obj)) {
1751				if(this.is_loading(obj)) {
1752					return setTimeout($.proxy(function () {
1753						this.open_node(obj, callback, animation);
1754					}, this), 500);
1755				}
1756				this.load_node(obj, function (o, ok) {
1757					return ok ? this.open_node(o, callback, animation) : (callback ? callback.call(this, o, false) : false);
1758				});
1759			}
1760			else {
1761				d = this.get_node(obj, true);
1762				t = this;
1763				if(d.length) {
1764					if(obj.children.length && !this._firstChild(d.children('ul')[0])) {
1765						obj.state.opened = true;
1766						this.redraw_node(obj, true);
1767						d = this.get_node(obj, true);
1768					}
1769					if(!animation) {
1770						d[0].className = d[0].className.replace('jstree-closed', 'jstree-open');
1771						d[0].setAttribute("aria-expanded", true);
1772					}
1773					else {
1774						d
1775							.children("ul").css("display","none").end()
1776							.removeClass("jstree-closed").addClass("jstree-open").attr("aria-expanded", true)
1777							.children("ul").stop(true, true)
1778								.slideDown(animation, function () {
1779									this.style.display = "";
1780									t.trigger("after_open", { "node" : obj });
1781								});
1782					}
1783				}
1784				obj.state.opened = true;
1785				if(callback) {
1786					callback.call(this, obj, true);
1787				}
1788				/**
1789				 * triggered when a node is opened (if there is an animation it will not be completed yet)
1790				 * @event
1791				 * @name open_node.jstree
1792				 * @param {Object} node the opened node
1793				 */
1794				this.trigger('open_node', { "node" : obj });
1795				if(!animation || !d.length) {
1796					/**
1797					 * triggered when a node is opened and the animation is complete
1798					 * @event
1799					 * @name after_open.jstree
1800					 * @param {Object} node the opened node
1801					 */
1802					this.trigger("after_open", { "node" : obj });
1803				}
1804			}
1805		},
1806		/**
1807		 * opens every parent of a node (node should be loaded)
1808		 * @name _open_to(obj)
1809		 * @param {mixed} obj the node to reveal
1810		 * @private
1811		 */
1812		_open_to : function (obj) {
1813			obj = this.get_node(obj);
1814			if(!obj || obj.id === '#') {
1815				return false;
1816			}
1817			var i, j, p = obj.parents;
1818			for(i = 0, j = p.length; i < j; i+=1) {
1819				if(i !== '#') {
1820					this.open_node(p[i], false, 0);
1821				}
1822			}
1823			return $(document.getElementById(obj.id));
1824		},
1825		/**
1826		 * closes a node, hiding its children
1827		 * @name close_node(obj [, animation])
1828		 * @param {mixed} obj the node to close
1829		 * @param {Number} animation the animation duration in milliseconds when closing the node (overrides the `core.animation` setting). Use `false` for no animation.
1830		 * @trigger close_node.jstree, after_close.jstree
1831		 */
1832		close_node : function (obj, animation) {
1833			var t1, t2, t, d;
1834			if($.isArray(obj)) {
1835				obj = obj.slice();
1836				for(t1 = 0, t2 = obj.length; t1 < t2; t1++) {
1837					this.close_node(obj[t1], animation);
1838				}
1839				return true;
1840			}
1841			obj = this.get_node(obj);
1842			if(!obj || obj.id === '#') {
1843				return false;
1844			}
1845			animation = animation === undefined ? this.settings.core.animation : animation;
1846			t = this;
1847			d = this.get_node(obj, true);
1848			if(d.length) {
1849				if(!animation) {
1850					d[0].className = d[0].className.replace('jstree-open', 'jstree-closed');
1851					d.attr("aria-expanded", false).children('ul').remove();
1852				}
1853				else {
1854					d
1855						.children("ul").attr("style","display:block !important").end()
1856						.removeClass("jstree-open").addClass("jstree-closed").attr("aria-expanded", false)
vendor: 14,300 bytes, lines 1857-2280
1857						.children("ul").stop(true, true).slideUp(animation, function () {
1858							this.style.display = "";
1859							d.children('ul').remove();
1860							t.trigger("after_close", { "node" : obj });
1861						});
1862				}
1863			}
1864			obj.state.opened = false;
1865			/**
1866			 * triggered when a node is closed (if there is an animation it will not be complete yet)
1867			 * @event
1868			 * @name close_node.jstree
1869			 * @param {Object} node the closed node
1870			 */
1871			this.trigger('close_node',{ "node" : obj });
1872			if(!animation || !d.length) {
1873				/**
1874				 * triggered when a node is closed and the animation is complete
1875				 * @event
1876				 * @name after_close.jstree
1877				 * @param {Object} node the closed node
1878				 */
1879				this.trigger("after_close", { "node" : obj });
1880			}
1881		},
1882		/**
1883		 * toggles a node - closing it if it is open, opening it if it is closed
1884		 * @name toggle_node(obj)
1885		 * @param {mixed} obj the node to toggle
1886		 */
1887		toggle_node : function (obj) {
1888			var t1, t2;
1889			if($.isArray(obj)) {
1890				obj = obj.slice();
1891				for(t1 = 0, t2 = obj.length; t1 < t2; t1++) {
1892					this.toggle_node(obj[t1]);
1893				}
1894				return true;
1895			}
1896			if(this.is_closed(obj)) {
1897				return this.open_node(obj);
1898			}
1899			if(this.is_open(obj)) {
1900				return this.close_node(obj);
1901			}
1902		},
1903		/**
1904		 * 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.
1905		 * @name open_all([obj, animation, original_obj])
1906		 * @param {mixed} obj the node to open recursively, omit to open all nodes in the tree
1907		 * @param {Number} animation the animation duration in milliseconds when opening the nodes, the default is no animation
1908		 * @param {jQuery} reference to the node that started the process (internal use)
1909		 * @trigger open_all.jstree
1910		 */
1911		open_all : function (obj, animation, original_obj) {
1912			if(!obj) { obj = '#'; }
1913			obj = this.get_node(obj);
1914			if(!obj) { return false; }
1915			var dom = obj.id === '#' ? this.get_container_ul() : this.get_node(obj, true), i, j, _this;
1916			if(!dom.length) {
1917				for(i = 0, j = obj.children_d.length; i < j; i++) {
1918					if(this.is_closed(this._model.data[obj.children_d[i]])) {
1919						this._model.data[obj.children_d[i]].state.opened = true;
1920					}
1921				}
1922				return this.trigger('open_all', { "node" : obj });
1923			}
1924			original_obj = original_obj || dom;
1925			_this = this;
1926			dom = this.is_closed(obj) ? dom.find('li.jstree-closed').addBack() : dom.find('li.jstree-closed');
1927			dom.each(function () {
1928				_this.open_node(
1929					this,
1930					function(node, status) { if(status && this.is_parent(node)) { this.open_all(node, animation, original_obj); } },
1931					animation || 0
1932				);
1933			});
1934			if(original_obj.find('li.jstree-closed').length === 0) {
1935				/**
1936				 * triggered when an `open_all` call completes
1937				 * @event
1938				 * @name open_all.jstree
1939				 * @param {Object} node the opened node
1940				 */
1941				this.trigger('open_all', { "node" : this.get_node(original_obj) });
1942			}
1943		},
1944		/**
1945		 * closes all nodes within a node (or the tree), revaling their children
1946		 * @name close_all([obj, animation])
1947		 * @param {mixed} obj the node to close recursively, omit to close all nodes in the tree
1948		 * @param {Number} animation the animation duration in milliseconds when closing the nodes, the default is no animation
1949		 * @trigger close_all.jstree
1950		 */
1951		close_all : function (obj, animation) {
1952			if(!obj) { obj = '#'; }
1953			obj = this.get_node(obj);
1954			if(!obj) { return false; }
1955			var dom = obj.id === '#' ? this.get_container_ul() : this.get_node(obj, true),
1956				_this = this, i, j;
1957			if(!dom.length) {
1958				for(i = 0, j = obj.children_d.length; i < j; i++) {
1959					this._model.data[obj.children_d[i]].state.opened = false;
1960				}
1961				return this.trigger('close_all', { "node" : obj });
1962			}
1963			dom = this.is_open(obj) ? dom.find('li.jstree-open').addBack() : dom.find('li.jstree-open');
1964			dom.vakata_reverse().each(function () { _this.close_node(this, animation || 0); });
1965			/**
1966			 * triggered when an `close_all` call completes
1967			 * @event
1968			 * @name close_all.jstree
1969			 * @param {Object} node the closed node
1970			 */
1971			this.trigger('close_all', { "node" : obj });
1972		},
1973		/**
1974		 * checks if a node is disabled (not selectable)
1975		 * @name is_disabled(obj)
1976		 * @param  {mixed} obj
1977		 * @return {Boolean}
1978		 */
1979		is_disabled : function (obj) {
1980			obj = this.get_node(obj);
1981			return obj && obj.state && obj.state.disabled;
1982		},
1983		/**
1984		 * enables a node - so that it can be selected
1985		 * @name enable_node(obj)
1986		 * @param {mixed} obj the node to enable
1987		 * @trigger enable_node.jstree
1988		 */
1989		enable_node : function (obj) {
1990			var t1, t2;
1991			if($.isArray(obj)) {
1992				obj = obj.slice();
1993				for(t1 = 0, t2 = obj.length; t1 < t2; t1++) {
1994					this.enable_node(obj[t1]);
1995				}
1996				return true;
1997			}
1998			obj = this.get_node(obj);
1999			if(!obj || obj.id === '#') {
2000				return false;
2001			}
2002			obj.state.disabled = false;
2003			this.get_node(obj,true).children('.jstree-anchor').removeClass('jstree-disabled');
2004			/**
2005			 * triggered when an node is enabled
2006			 * @event
2007			 * @name enable_node.jstree
2008			 * @param {Object} node the enabled node
2009			 */
2010			this.trigger('enable_node', { 'node' : obj });
2011		},
2012		/**
2013		 * disables a node - so that it can not be selected
2014		 * @name disable_node(obj)
2015		 * @param {mixed} obj the node to disable
2016		 * @trigger disable_node.jstree
2017		 */
2018		disable_node : function (obj) {
2019			var t1, t2;
2020			if($.isArray(obj)) {
2021				obj = obj.slice();
2022				for(t1 = 0, t2 = obj.length; t1 < t2; t1++) {
2023					this.disable_node(obj[t1]);
2024				}
2025				return true;
2026			}
2027			obj = this.get_node(obj);
2028			if(!obj || obj.id === '#') {
2029				return false;
2030			}
2031			obj.state.disabled = true;
2032			this.get_node(obj,true).children('.jstree-anchor').addClass('jstree-disabled');
2033			/**
2034			 * triggered when an node is disabled
2035			 * @event
2036			 * @name disable_node.jstree
2037			 * @param {Object} node the disabled node
2038			 */
2039			this.trigger('disable_node', { 'node' : obj });
2040		},
2041		/**
2042		 * called when a node is selected by the user. Used internally.
2043		 * @private
2044		 * @name activate_node(obj, e)
2045		 * @param {mixed} obj the node
2046		 * @param {Object} e the related event
2047		 * @trigger activate_node.jstree
2048		 */
2049		activate_node : function (obj, e) {
2050			if(this.is_disabled(obj)) {
2051				return false;
2052			}
2053			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 ) )) {
2054				if(!this.settings.core.multiple && (e.metaKey || e.ctrlKey || e.shiftKey) && this.is_selected(obj)) {
2055					this.deselect_node(obj, false, false, e);
2056				}
2057				else {
2058					this.deselect_all(true);
2059					this.select_node(obj, false, false, e);
2060					this._data.core.last_clicked = this.get_node(obj);
2061				}
2062			}
2063			else {
2064				if(e.shiftKey) {
2065					var o = this.get_node(obj).id,
2066						l = this._data.core.last_clicked.id,
2067						p = this.get_node(this._data.core.last_clicked.parent).children,
2068						c = false,
2069						i, j;
2070					for(i = 0, j = p.length; i < j; i += 1) {
2071						// separate IFs work whem o and l are the same
2072						if(p[i] === o) {
2073							c = !c;
2074						}
2075						if(p[i] === l) {
2076							c = !c;
2077						}
2078						if(c || p[i] === o || p[i] === l) {
2079							this.select_node(p[i], false, false, e);
2080						}
2081						else {
2082							this.deselect_node(p[i], false, false, e);
2083						}
2084					}
2085				}
2086				else {
2087					if(!this.is_selected(obj)) {
2088						this.select_node(obj, false, false, e);
2089					}
2090					else {
2091						this.deselect_node(obj, false, false, e);
2092					}
2093				}
2094			}
2095			/**
2096			 * triggered when an node is clicked or intercated with by the user
2097			 * @event
2098			 * @name activate_node.jstree
2099			 * @param {Object} node
2100			 */
2101			this.trigger('activate_node', { 'node' : this.get_node(obj) });
2102		},
2103		/**
2104		 * applies the hover state on a node, called when a node is hovered by the user. Used internally.
2105		 * @private
2106		 * @name hover_node(obj)
2107		 * @param {mixed} obj
2108		 * @trigger hover_node.jstree
2109		 */
2110		hover_node : function (obj) {
2111			obj = this.get_node(obj, true);
2112			if(!obj || !obj.length || obj.children('.jstree-hovered').length) {
2113				return false;
2114			}
2115			var o = this.element.find('.jstree-hovered'), t = this.element;
2116			if(o && o.length) { this.dehover_node(o); }
2117
2118			obj.children('.jstree-anchor').addClass('jstree-hovered');
2119			/**
2120			 * triggered when an node is hovered
2121			 * @event
2122			 * @name hover_node.jstree
2123			 * @param {Object} node
2124			 */
2125			this.trigger('hover_node', { 'node' : this.get_node(obj) });
2126			setTimeout(function () { t.attr('aria-activedescendant', obj[0].id); obj.attr('aria-selected', true); }, 0);
2127		},
2128		/**
2129		 * removes the hover state from a nodecalled when a node is no longer hovered by the user. Used internally.
2130		 * @private
2131		 * @name dehover_node(obj)
2132		 * @param {mixed} obj
2133		 * @trigger dehover_node.jstree
2134		 */
2135		dehover_node : function (obj) {
2136			obj = this.get_node(obj, true);
2137			if(!obj || !obj.length || !obj.children('.jstree-hovered').length) {
2138				return false;
2139			}
2140			obj.attr('aria-selected', false).children('.jstree-anchor').removeClass('jstree-hovered');
2141			/**
2142			 * triggered when an node is no longer hovered
2143			 * @event
2144			 * @name dehover_node.jstree
2145			 * @param {Object} node
2146			 */
2147			this.trigger('dehover_node', { 'node' : this.get_node(obj) });
2148		},
2149		/**
2150		 * select a node
2151		 * @name select_node(obj [, supress_event, prevent_open])
2152		 * @param {mixed} obj an array can be used to select multiple nodes
2153		 * @param {Boolean} supress_event if set to `true` the `changed.jstree` event won't be triggered
2154		 * @param {Boolean} prevent_open if set to `true` parents of the selected node won't be opened
2155		 * @trigger select_node.jstree, changed.jstree
2156		 */
2157		select_node : function (obj, supress_event, prevent_open, e) {
2158			var dom, t1, t2, th;
2159			if($.isArray(obj)) {
2160				obj = obj.slice();
2161				for(t1 = 0, t2 = obj.length; t1 < t2; t1++) {
2162					this.select_node(obj[t1], supress_event, prevent_open, e);
2163				}
2164				return true;
2165			}
2166			obj = this.get_node(obj);
2167			if(!obj || obj.id === '#') {
2168				return false;
2169			}
2170			dom = this.get_node(obj, true);
2171			if(!obj.state.selected) {
2172				obj.state.selected = true;
2173				this._data.core.selected.push(obj.id);
2174				if(!prevent_open) {
2175					dom = this._open_to(obj);
2176				}
2177				if(dom && dom.length) {
2178					dom.children('.jstree-anchor').addClass('jstree-clicked');
2179				}
2180				/**
2181				 * triggered when an node is selected
2182				 * @event
2183				 * @name select_node.jstree
2184				 * @param {Object} node
2185				 * @param {Array} selected the current selection
2186				 * @param {Object} event the event (if any) that triggered this select_node
2187				 */
2188				this.trigger('select_node', { 'node' : obj, 'selected' : this._data.core.selected, 'event' : e });
2189				if(!supress_event) {
2190					/**
2191					 * triggered when selection changes
2192					 * @event
2193					 * @name changed.jstree
2194					 * @param {Object} node
2195					 * @param {Object} action the action that caused the selection to change
2196					 * @param {Array} selected the current selection
2197					 * @param {Object} event the event (if any) that triggered this changed event
2198					 */
2199					this.trigger('changed', { 'action' : 'select_node', 'node' : obj, 'selected' : this._data.core.selected, 'event' : e });
2200				}
2201			}
2202		},
2203		/**
2204		 * deselect a node
2205		 * @name deselect_node(obj [, supress_event])
2206		 * @param {mixed} obj an array can be used to deselect multiple nodes
2207		 * @param {Boolean} supress_event if set to `true` the `changed.jstree` event won't be triggered
2208		 * @trigger deselect_node.jstree, changed.jstree
2209		 */
2210		deselect_node : function (obj, supress_event, e) {
2211			var t1, t2, dom;
2212			if($.isArray(obj)) {
2213				obj = obj.slice();
2214				for(t1 = 0, t2 = obj.length; t1 < t2; t1++) {
2215					this.deselect_node(obj[t1], supress_event, e);
2216				}
2217				return true;
2218			}
2219			obj = this.get_node(obj);
2220			if(!obj || obj.id === '#') {
2221				return false;
2222			}
2223			dom = this.get_node(obj, true);
2224			if(obj.state.selected) {
2225				obj.state.selected = false;
2226				this._data.core.selected = $.vakata.array_remove_item(this._data.core.selected, obj.id);
2227				if(dom.length) {
2228					dom.children('.jstree-anchor').removeClass('jstree-clicked');
2229				}
2230				/**
2231				 * triggered when an node is deselected
2232				 * @event
2233				 * @name deselect_node.jstree
2234				 * @param {Object} node
2235				 * @param {Array} selected the current selection
2236				 * @param {Object} event the event (if any) that triggered this deselect_node
2237				 */
2238				this.trigger('deselect_node', { 'node' : obj, 'selected' : this._data.core.selected, 'event' : e });
2239				if(!supress_event) {
2240					this.trigger('changed', { 'action' : 'deselect_node', 'node' : obj, 'selected' : this._data.core.selected, 'event' : e });
2241				}
2242			}
2243		},
2244		/**
2245		 * select all nodes in the tree
2246		 * @name select_all([supress_event])
2247		 * @param {Boolean} supress_event if set to `true` the `changed.jstree` event won't be triggered
2248		 * @trigger select_all.jstree, changed.jstree
2249		 */
2250		select_all : function (supress_event) {
2251			var tmp = this._data.core.selected.concat([]), i, j;
2252			this._data.core.selected = this._model.data['#'].children_d.concat();
2253			for(i = 0, j = this._data.core.selected.length; i < j; i++) {
2254				if(this._model.data[this._data.core.selected[i]]) {
2255					this._model.data[this._data.core.selected[i]].state.selected = true;
2256				}
2257			}
2258			this.redraw(true);
2259			/**
2260			 * triggered when all nodes are selected
2261			 * @event
2262			 * @name select_all.jstree
2263			 * @param {Array} selected the current selection
2264			 */
2265			this.trigger('select_all', { 'selected' : this._data.core.selected });
2266			if(!supress_event) {
2267				this.trigger('changed', { 'action' : 'select_all', 'selected' : this._data.core.selected, 'old_selection' : tmp });
2268			}
2269		},
2270		/**
2271		 * deselect all selected nodes
2272		 * @name deselect_all([supress_event])
2273		 * @param {Boolean} supress_event if set to `true` the `changed.jstree` event won't be triggered
2274		 * @trigger deselect_all.jstree, changed.jstree
2275		 */
2276		deselect_all : function (supress_event) {
2277			var tmp = this._data.core.selected.concat([]), i, j;
2278			for(i = 0, j = this._data.core.selected.length; i < j; i++) {
2279				if(this._model.data[this._data.core.selected[i]]) {
2280					this._model.data[this._data.core.selected[i]].state.selected = false;
2281				}
2282			}
2283			this._data.core.selected = [];
2284			this.element.find('.jstree-clicked').removeClass('jstree-clicked');
2285			/**
2286			 * triggered when all nodes are deselected
2287			 * @event
2288			 * @name deselect_all.jstree
2289			 * @param {Object} node the previous selection
2290			 * @param {Array} selected the current selection
2291			 */
2292			this.trigger('deselect_all', { 'selected' : this._data.core.selected, 'node' : tmp });
2293			if(!supress_event) {
2294				this.trigger('changed', { 'action' : 'deselect_all', 'selected' : this._data.core.selected, 'old_selection' : tmp });
2295			}
2296		},
2297		/**
2298		 * checks if a node is selected
2299		 * @name is_selected(obj)
2300		 * @param  {mixed}  obj
2301		 * @return {Boolean}
2302		 */
2303		is_selected : function (obj) {
2304			obj = this.get_node(obj);
2305			if(!obj || obj.id === '#') {
2306				return false;
2307			}
2308			return obj.state.selected;
2309		},
2310		/**
2311		 * get an array of all selected node IDs
2312		 * @name get_selected([full])
2313		 * @param  {mixed}  full if set to `true` the returned array will consist of the full node objects, otherwise - only IDs will be returned
2314		 * @return {Array}
2315		 */
2316		get_selected : function (full) {
2317			return full ? $.map(this._data.core.selected, $.proxy(function (i) { return this.get_node(i); }, this)) : this._data.core.selected;
2318		},
2319		/**
2320		 * gets the current state of the tree so that it can be restored later with `set_state(state)`. Used internally.
2321		 * @name get_state()
2322		 * @private
2323		 * @return {Object}
2324		 */
2325		get_state : function () {
2326			var state	= {
2327				'core' : {
2328					'open' : [],
2329					'scroll' : {
2330						'left' : this.element.scrollLeft(),
2331						'top' : this.element.scrollTop()
2332					},
2333					/*!
2334					'themes' : {
2335						'name' : this.get_theme(),
2336						'icons' : this._data.core.themes.icons,
2337						'dots' : this._data.core.themes.dots
2338					},
2339					*/
2340					'selected' : []
2341				}
2342			}, i;
2343			for(i in this._model.data) {
2344				if(this._model.data.hasOwnProperty(i)) {
2345					if(i !== '#') {
2346						if(this._model.data[i].state.opened) {
2347							state.core.open.push(i);
2348						}
2349						if(this._model.data[i].state.selected) {
2350							state.core.selected.push(i);
2351						}
2352					}
2353				}
2354			}
2355			return state;
2356		},
2357		/**
2358		 * sets the state of the tree. Used internally.
2359		 * @name set_state(state [, callback])
2360		 * @private
2361		 * @param {Object} state the state to restore
2362		 * @param {Function} callback an optional function to execute once the state is restored.
2363		 * @trigger set_state.jstree
2364		 */
2365		set_state : function (state, callback) {
2366			if(state) {
2367				if(state.core) {
2368					var res, n, t, _this;
2369					if(state.core.open) {
2370						if(!$.isArray(state.core.open)) {
2371							delete state.core.open;
2372							this.set_state(state, callback);
2373							return false;
2374						}
2375						res = true;
2376						n = false;
2377						t = this;
2378						$.each(state.core.open.concat([]), function (i, v) {
2379							n = t.get_node(v);
2380							if(n) {
2381								if(t.is_loaded(v)) {
2382									if(t.is_closed(v)) {
2383										t.open_node(v, false, 0);
2384									}
2385									if(state && state.core && state.core.open) {
2386										$.vakata.array_remove_item(state.core.open, v);
2387									}
2388								}
2389								else {
2390									if(!t.is_loading(v)) {
2391										t.open_node(v, $.proxy(function () { this.set_state(state, callback); }, t), 0);
2392									}
2393									// there will be some async activity - so wait for it
2394									res = false;
2395								}
2396							}
2397						});
2398						if(res) {
2399							delete state.core.open;
2400							this.set_state(state, callback);
2401						}
2402						return false;
2403					}
2404					if(state.core.scroll) {
2405						if(state.core.scroll && state.core.scroll.left !== undefined) {
2406							this.element.scrollLeft(state.core.scroll.left);
2407						}
2408						if(state.core.scroll && state.core.scroll.top !== undefined) {
2409							this.element.scrollTop(state.core.scroll.top);
2410						}
2411						delete state.core.scroll;
2412						this.set_state(state, callback);
2413						return false;
2414					}
2415					/*!
2416					if(state.core.themes) {
2417						if(state.core.themes.name) {
2418							this.set_theme(state.core.themes.name);
2419						}
2420						if(typeof state.core.themes.dots !== 'undefined') {
2421							this[ state.core.themes.dots ? "show_dots" : "hide_dots" ]();
2422						}
2423						if(typeof state.core.themes.icons !== 'undefined') {
2424							this[ state.core.themes.icons ? "show_icons" : "hide_icons" ]();
2425						}
2426						delete state.core.themes;
2427						delete state.core.open;
2428						this.set_state(state, callback);
2429						return false;
2430					}
2431					*/
2432					if(state.core.selected) {
2433						_this = this;
2434						this.deselect_all();
2435						$.each(state.core.selected, function (i, v) {
2436							_this.select_node(v);
2437						});
2438						delete state.core.selected;
2439						this.set_state(state, callback);
2440						return false;
2441					}
2442					if($.isEmptyObject(state.core)) {
2443						delete state.core;
2444						this.set_state(state, callback);
2445						return false;
2446					}
2447				}
2448				if($.isEmptyObject(state)) {
2449					state = null;
2450					if(callback) { callback.call(this); }
2451					/**
2452					 * triggered when a `set_state` call completes
2453					 * @event
2454					 * @name set_state.jstree
2455					 */
2456					this.trigger('set_state');
2457					return false;
2458				}
2459				return true;
2460			}
2461			return false;
2462		},
2463		/**
2464		 * refreshes the tree - all nodes are reloaded with calls to `load_node`.
2465		 * @name refresh()
2466		 * @param {Boolean} skip_loading an option to skip showing the loading indicator
2467		 * @trigger refresh.jstree
2468		 */
2469		refresh : function (skip_loading) {
2470			this._data.core.state = this.get_state();
2471			this._cnt = 0;
2472			this._model.data = {
2473				'#' : {
2474					id : '#',
2475					parent : null,
2476					parents : [],
2477					children : [],
2478					children_d : [],
2479					state : { loaded : false }
2480				}
2481			};
2482			var c = this.get_container_ul()[0].className;
2483			if(!skip_loading) {
2484				this.element.html("<"+"ul class='jstree-container-ul'><"+"li class='jstree-initial-node jstree-loading jstree-leaf jstree-last'><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>");
2485			}
2486			this.load_node('#', function (o, s) {
2487				if(s) {
2488					this.get_container_ul()[0].className = c;
2489					this.set_state($.extend(true, {}, this._data.core.state), function () {
2490						/**
2491						 * triggered when a `refresh` call completes
2492						 * @event
2493						 * @name refresh.jstree
2494						 */
2495						this.trigger('refresh');
2496					});
2497				}
2498				this._data.core.state = null;
2499			});
2500		},
2501		/**
2502		 * set (change) the ID of a node
2503		 * @name set_id(obj, id)
2504		 * @param  {mixed} obj the node
2505		 * @param  {String} id the new ID
2506		 * @return {Boolean}
2507		 */
2508		set_id : function (obj, id) {
2509			obj = this.get_node(obj);
2510			if(!obj || obj.id === '#') { return false; }
2511			var i, j, m = this._model.data;
2512			// update parents (replace current ID with new one in children and children_d)
2513			m[obj.parent].children[$.inArray(obj.id, m[obj.parent].children)] = id;
2514			for(i = 0, j = obj.parents.length; i < j; i++) {
2515				m[obj.parents[i]].children_d[$.inArray(obj.id, m[obj.parents[i]].children_d)] = id;
2516			}
2517			// update children (replace current ID with new one in parent and parents)
2518			for(i = 0, j = obj.children.length; i < j; i++) {
2519				m[obj.children[i]].parent = id;
2520			}
2521			for(i = 0, j = obj.children_d.length; i < j; i++) {
2522				m[obj.children_d[i]].parents[$.inArray(obj.id, m[obj.children_d[i]].parents)] = id;
2523			}
2524			i = $.inArray(obj.id, this._data.core.selected);
2525			if(i !== -1) { this._data.core.selected[i] = id; }
2526			// update model and obj itself (obj.id, this._model.data[KEY])
2527			i = this.get_node(obj.id, true);
2528			if(i) {
2529				i.attr('id', id);
2530			}
2531			delete m[obj.id];
2532			obj.id = id;
2533			m[id] = obj;
2534			return true;
2535		},
2536		/**
2537		 * get the text value of a node
2538		 * @name get_text(obj)
2539		 * @param  {mixed} obj the node
2540		 * @return {String}
2541		 */
2542		get_text : function (obj) {
2543			obj = this.get_node(obj);
2544			return (!obj || obj.id === '#') ? false : obj.text;
2545		},
2546		/**
2547		 * set the text value of a node. Used internally, please use `rename_node(obj, val)`.
2548		 * @private
2549		 * @name set_text(obj, val)
2550		 * @param  {mixed} obj the node, you can pass an array to set the text on multiple nodes
2551		 * @param  {String} val the new text value
2552		 * @return {Boolean}
2553		 * @trigger set_text.jstree
2554		 */
2555		set_text : function (obj, val) {
2556			var t1, t2, dom, tmp;
2557			if($.isArray(obj)) {
2558				obj = obj.slice();
2559				for(t1 = 0, t2 = obj.length; t1 < t2; t1++) {
2560					this.set_text(obj[t1], val);
2561				}
2562				return true;
2563			}
2564			obj = this.get_node(obj);
2565			if(!obj || obj.id === '#') { return false; }
2566			obj.text = val;
2567			dom = this.get_node(obj, true);
2568			if(dom.length) {
2569				dom = dom.children(".jstree-anchor:eq(0)");
2570				tmp = dom.children("I").clone();
2571				dom.html(val).prepend(tmp);
2572				/**
2573				 * triggered when a node text value is changed
2574				 * @event
2575				 * @name set_text.jstree
2576				 * @param {Object} obj
2577				 * @param {String} text the new value
2578				 */
2579				this.trigger('set_text',{ "obj" : obj, "text" : val });
2580			}
2581			return true;
2582		},
2583		/**
2584		 * gets a JSON representation of a node (or the whole tree)
2585		 * @name get_json([obj, options])
2586		 * @param  {mixed} obj
2587		 * @param  {Object} options
2588		 * @param  {Boolean} options.no_state do not return state information
2589		 * @param  {Boolean} options.no_id do not return ID
2590		 * @param  {Boolean} options.no_children do not include children
2591		 * @param  {Boolean}
vendor: 21,580 bytes, lines 2591-3177
2591 options.no_data do not include node data
2592		 * @param  {Boolean} options.flat return flat JSON instead of nested
2593		 * @return {Object}
2594		 */
2595		get_json : function (obj, options, flat) {
2596			obj = this.get_node(obj || '#');
2597			if(!obj) { return false; }
2598			if(options && options.flat && !flat) { flat = []; }
2599			var tmp = {
2600				'id' : obj.id,
2601				'text' : obj.text,
2602				'icon' : this.get_icon(obj),
2603				'li_attr' : obj.li_attr,
2604				'a_attr' : obj.a_attr,
2605				'state' : {},
2606				'data' : options && options.no_data ? false : obj.data
2607				//( this.get_node(obj, true).length ? this.get_node(obj, true).data() : obj.data ),
2608			}, i, j;
2609			if(options && options.flat) {
2610				tmp.parent = obj.parent;
2611			}
2612			else {
2613				tmp.children = [];
2614			}
2615			if(!options || !options.no_state) {
2616				for(i in obj.state) {
2617					if(obj.state.hasOwnProperty(i)) {
2618						tmp.state[i] = obj.state[i];
2619					}
2620				}
2621			}
2622			if(options && options.no_id) {
2623				delete tmp.id;
2624				if(tmp.li_attr && tmp.li_attr.id) {
2625					delete tmp.li_attr.id;
2626				}
2627			}
2628			if(options && options.flat && obj.id !== '#') {
2629				flat.push(tmp);
2630			}
2631			if(!options || !options.no_children) {
2632				for(i = 0, j = obj.children.length; i < j; i++) {
2633					if(options && options.flat) {
2634						this.get_json(obj.children[i], options, flat);
2635					}
2636					else {
2637						tmp.children.push(this.get_json(obj.children[i], options));
2638					}
2639				}
2640			}
2641			return options && options.flat ? flat : (obj.id === '#' ? tmp.children : tmp);
2642		},
2643		/**
2644		 * create a new node (do not confuse with load_node)
2645		 * @name create_node([obj, node, pos, callback, is_loaded])
2646		 * @param  {mixed}   par       the parent node
2647		 * @param  {mixed}   node      the data for the new node (a valid JSON object, or a simple string with the name)
2648		 * @param  {mixed}   pos       the index at which to insert the node, "first" and "last" are also supported, default is "last"
2649		 * @param  {Function} callback a function to be called once the node is created
2650		 * @param  {Boolean} is_loaded internal argument indicating if the parent node was succesfully loaded
2651		 * @return {String}            the ID of the newly create node
2652		 * @trigger model.jstree, create_node.jstree
2653		 */
2654		create_node : function (par, node, pos, callback, is_loaded) {
2655			par = this.get_node(par);
2656			if(!par) { return false; }
2657			pos = pos === undefined ? "last" : pos;
2658			if(!pos.toString().match(/^(before|after)$/) && !is_loaded && !this.is_loaded(par)) {
2659				return this.load_node(par, function () { this.create_node(par, node, pos, callback, true); });
2660			}
2661			if(!node) { node = { "text" : this.get_string('New node') }; }
2662			if(node.text === undefined) { node.text = this.get_string('New node'); }
2663			var tmp, dpc, i, j;
2664
2665			if(par.id === '#') {
2666				if(pos === "before") { pos = "first"; }
2667				if(pos === "after") { pos = "last"; }
2668			}
2669			switch(pos) {
2670				case "before":
2671					tmp = this.get_node(par.parent);
2672					pos = $.inArray(par.id, tmp.children);
2673					par = tmp;
2674					break;
2675				case "after" :
2676					tmp = this.get_node(par.parent);
2677					pos = $.inArray(par.id, tmp.children) + 1;
2678					par = tmp;
2679					break;
2680				case "inside":
2681				case "first":
2682					pos = 0;
2683					break;
2684				case "last":
2685					pos = par.children.length;
2686					break;
2687				default:
2688					if(!pos) { pos = 0; }
2689					break;
2690			}
2691			if(pos > par.children.length) { pos = par.children.length; }
2692			if(!node.id) { node.id = true; }
2693			if(!this.check("create_node", node, par, pos)) {
2694				this.settings.core.error.call(this, this._data.core.last_error);
2695				return false;
2696			}
2697			if(node.id === true) { delete node.id; }
2698			node = this._parse_model_from_json(node, par.id, par.parents.concat());
2699			if(!node) { return false; }
2700			tmp = this.get_node(node);
2701			dpc = [];
2702			dpc.push(node);
2703			dpc = dpc.concat(tmp.children_d);
2704			this.trigger('model', { "nodes" : dpc, "parent" : par.id });
2705
2706			par.children_d = par.children_d.concat(dpc);
2707			for(i = 0, j = par.parents.length; i < j; i++) {
2708				this._model.data[par.parents[i]].children_d = this._model.data[par.parents[i]].children_d.concat(dpc);
2709			}
2710			node = tmp;
2711			tmp = [];
2712			for(i = 0, j = par.children.length; i < j; i++) {
2713				tmp[i >= pos ? i+1 : i] = par.children[i];
2714			}
2715			tmp[pos] = node.id;
2716			par.children = tmp;
2717
2718			this.redraw_node(par, true);
2719			if(callback) { callback.call(this, this.get_node(node)); }
2720			/**
2721			 * triggered when a node is created
2722			 * @event
2723			 * @name create_node.jstree
2724			 * @param {Object} node
2725			 * @param {String} parent the parent's ID
2726			 * @param {Number} position the position of the new node among the parent's children
2727			 */
2728			this.trigger('create_node', { "node" : this.get_node(node), "parent" : par.id, "position" : pos });
2729			return node.id;
2730		},
2731		/**
2732		 * set the text value of a node
2733		 * @name rename_node(obj, val)
2734		 * @param  {mixed} obj the node, you can pass an array to rename multiple nodes to the same name
2735		 * @param  {String} val the new text value
2736		 * @return {Boolean}
2737		 * @trigger rename_node.jstree
2738		 */
2739		rename_node : function (obj, val) {
2740			var t1, t2, old;
2741			if($.isArray(obj)) {
2742				obj = obj.slice();
2743				for(t1 = 0, t2 = obj.length; t1 < t2; t1++) {
2744					this.rename_node(obj[t1], val);
2745				}
2746				return true;
2747			}
2748			obj = this.get_node(obj);
2749			if(!obj || obj.id === '#') { return false; }
2750			old = obj.text;
2751			if(!this.check("rename_node", obj, this.get_parent(obj), val)) {
2752				this.settings.core.error.call(this, this._data.core.last_error);
2753				return false;
2754			}
2755			this.set_text(obj, val); // .apply(this, Array.prototype.slice.call(arguments))
2756			/**
2757			 * triggered when a node is renamed
2758			 * @event
2759			 * @name rename_node.jstree
2760			 * @param {Object} node
2761			 * @param {String} text the new value
2762			 * @param {String} old the old value
2763			 */
2764			this.trigger('rename_node', { "node" : obj, "text" : val, "old" : old });
2765			return true;
2766		},
2767		/**
2768		 * remove a node
2769		 * @name delete_node(obj)
2770		 * @param  {mixed} obj the node, you can pass an array to delete multiple nodes
2771		 * @return {Boolean}
2772		 * @trigger delete_node.jstree, changed.jstree
2773		 */
2774		delete_node : function (obj) {
2775			var t1, t2, par, pos, tmp, i, j, k, l, c;
2776			if($.isArray(obj)) {
2777				obj = obj.slice();
2778				for(t1 = 0, t2 = obj.length; t1 < t2; t1++) {
2779					this.delete_node(obj[t1]);
2780				}
2781				return true;
2782			}
2783			obj = this.get_node(obj);
2784			if(!obj || obj.id === '#') { return false; }
2785			par = this.get_node(obj.parent);
2786			pos = $.inArray(obj.id, par.children);
2787			c = false;
2788			if(!this.check("delete_node", obj, par, pos)) {
2789				this.settings.core.error.call(this, this._data.core.last_error);
2790				return false;
2791			}
2792			if(pos !== -1) {
2793				par.children = $.vakata.array_remove(par.children, pos);
2794			}
2795			tmp = obj.children_d.concat([]);
2796			tmp.push(obj.id);
2797			for(k = 0, l = tmp.length; k < l; k++) {
2798				for(i = 0, j = obj.parents.length; i < j; i++) {
2799					pos = $.inArray(tmp[k], this._model.data[obj.parents[i]].children_d);
2800					if(pos !== -1) {
2801						this._model.data[obj.parents[i]].children_d = $.vakata.array_remove(this._model.data[obj.parents[i]].children_d, pos);
2802					}
2803				}
2804				if(this._model.data[tmp[k]].state.selected) {
2805					c = true;
2806					pos = $.inArray(tmp[k], this._data.core.selected);
2807					if(pos !== -1) {
2808						this._data.core.selected = $.vakata.array_remove(this._data.core.selected, pos);
2809					}
2810				}
2811			}
2812			/**
2813			 * triggered when a node is deleted
2814			 * @event
2815			 * @name delete_node.jstree
2816			 * @param {Object} node
2817			 * @param {String} parent the parent's ID
2818			 */
2819			this.trigger('delete_node', { "node" : obj, "parent" : par.id });
2820			if(c) {
2821				this.trigger('changed', { 'action' : 'delete_node', 'node' : obj, 'selected' : this._data.core.selected, 'parent' : par.id });
2822			}
2823			for(k = 0, l = tmp.length; k < l; k++) {
2824				delete this._model.data[tmp[k]];
2825			}
2826			this.redraw_node(par, true);
2827			return true;
2828		},
2829		/**
2830		 * check if an operation is premitted on the tree. Used internally.
2831		 * @private
2832		 * @name check(chk, obj, par, pos)
2833		 * @param  {String} chk the operation to check, can be "create_node", "rename_node", "delete_node", "copy_node" or "move_node"
2834		 * @param  {mixed} obj the node
2835		 * @param  {mixed} par the parent
2836		 * @param  {mixed} pos the position to insert at, or if "rename_node" - the new name
2837		 * @return {Boolean}
2838		 */
2839		check : function (chk, obj, par, pos) {
2840			obj = obj && obj.id ? obj : this.get_node(obj);
2841			par = par && par.id ? par : this.get_node(par);
2842			var tmp = chk.match(/^move_node|copy_node|create_node$/i) ? par : obj,
2843				chc = this.settings.core.check_callback;
2844			if(chk === "move_node") {
2845				if(obj.id === par.id || $.inArray(obj.id, par.children) === pos || $.inArray(par.id, obj.children_d) !== -1) {
2846					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 }) };
2847					return false;
2848				}
2849			}
2850			tmp = this.get_node(tmp, true);
2851			if(tmp.length) { tmp = tmp.data('jstree'); }
2852			if(tmp && tmp.functions && (tmp.functions[chk] === false || tmp.functions[chk] === true)) {
2853				if(tmp.functions[chk] === false) {
2854					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 }) };
2855				}
2856				return tmp.functions[chk];
2857			}
2858			if(chc === false || ($.isFunction(chc) && chc.call(this, chk, obj, par, pos) === false) || (chc && chc[chk] === false)) {
2859				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 }) };
2860				return false;
2861			}
2862			return true;
2863		},
2864		/**
2865		 * get the last error
2866		 * @name last_error()
2867		 * @return {Object}
2868		 */
2869		last_error : function () {
2870			return this._data.core.last_error;
2871		},
2872		/**
2873		 * move a node to a new parent
2874		 * @name move_node(obj, par [, pos, callback, is_loaded])
2875		 * @param  {mixed} obj the node to move, pass an array to move multiple nodes
2876		 * @param  {mixed} par the new parent
2877		 * @param  {mixed} pos the position to insert at ("first" and "last" are supported, as well as "before" and "after"), defaults to `0`
2878		 * @param  {function} callback a function to call once the move is completed, receives 3 arguments - the node, the new parent and the position
2879		 * @param  {Boolean} internal parameter indicating if the parent node has been loaded
2880		 * @trigger move_node.jstree
2881		 */
2882		move_node : function (obj, par, pos, callback, is_loaded) {
2883			var t1, t2, old_par, new_par, old_ins, is_multi, dpc, tmp, i, j, k, l, p;
2884			if($.isArray(obj)) {
2885				obj = obj.reverse().slice();
2886				for(t1 = 0, t2 = obj.length; t1 < t2; t1++) {
2887					this.move_node(obj[t1], par, pos, callback, is_loaded);
2888				}
2889				return true;
2890			}
2891			obj = obj && obj.id ? obj : this.get_node(obj);
2892			par = this.get_node(par);
2893			pos = pos === undefined ? 0 : pos;
2894
2895			if(!par || !obj || obj.id === '#') { return false; }
2896			if(!pos.toString().match(/^(before|after)$/) && !is_loaded && !this.is_loaded(par)) {
2897				return this.load_node(par, function () { this.move_node(obj, par, pos, callback, true); });
2898			}
2899
2900			old_par = (obj.parent || '#').toString();
2901			new_par = (!pos.toString().match(/^(before|after)$/) || par.id === '#') ? par : this.get_node(par.parent);
2902			old_ins = this._model.data[obj.id] ? this : $.jstree.reference(obj.id);
2903			is_multi = !old_ins || !old_ins._id || (this._id !== old_ins._id);
2904			if(is_multi) {
2905				if(this.copy_node(obj, par, pos, callback, is_loaded)) {
2906					if(old_ins) { old_ins.delete_node(obj); }
2907					return true;
2908				}
2909				return false;
2910			}
2911			//var m = this._model.data;
2912			if(new_par.id === '#') {
2913				if(pos === "before") { pos = "first"; }
2914				if(pos === "after") { pos = "last"; }
2915			}
2916			switch(pos) {
2917				case "before":
2918					pos = $.inArray(par.id, new_par.children);
2919					break;
2920				case "after" :
2921					pos = $.inArray(par.id, new_par.children) + 1;
2922					break;
2923				case "inside":
2924				case "first":
2925					pos = 0;
2926					break;
2927				case "last":
2928					pos = new_par.children.length;
2929					break;
2930				default:
2931					if(!pos) { pos = 0; }
2932					break;
2933			}
2934			if(pos > new_par.children.length) { pos = new_par.children.length; }
2935			if(!this.check("move_node", obj, new_par, pos)) {
2936				this.settings.core.error.call(this, this._data.core.last_error);
2937				return false;
2938			}
2939			if(obj.parent === new_par.id) {
2940				dpc = new_par.children.concat();
2941				tmp = $.inArray(obj.id, dpc);
2942				if(tmp !== -1) {
2943					dpc = $.vakata.array_remove(dpc, tmp);
2944					if(pos > tmp) { pos--; }
2945				}
2946				tmp = [];
2947				for(i = 0, j = dpc.length; i < j; i++) {
2948					tmp[i >= pos ? i+1 : i] = dpc[i];
2949				}
2950				tmp[pos] = obj.id;
2951				new_par.children = tmp;
2952				this._node_changed(new_par.id);
2953				this.redraw(new_par.id === '#');
2954			}
2955			else {
2956				// clean old parent and up
2957				tmp = obj.children_d.concat();
2958				tmp.push(obj.id);
2959				for(i = 0, j = obj.parents.length; i < j; i++) {
2960					dpc = [];
2961					p = old_ins._model.data[obj.parents[i]].children_d;
2962					for(k = 0, l = p.length; k < l; k++) {
2963						if($.inArray(p[k], tmp) === -1) {
2964							dpc.push(p[k]);
2965						}
2966					}
2967					old_ins._model.data[obj.parents[i]].children_d = dpc;
2968				}
2969				old_ins._model.data[old_par].children = $.vakata.array_remove_item(old_ins._model.data[old_par].children, obj.id);
2970
2971				// insert into new parent and up
2972				for(i = 0, j = new_par.parents.length; i < j; i++) {
2973					this._model.data[new_par.parents[i]].children_d = this._model.data[new_par.parents[i]].children_d.concat(tmp);
2974				}
2975				dpc = [];
2976				for(i = 0, j = new_par.children.length; i < j; i++) {
2977					dpc[i >= pos ? i+1 : i] = new_par.children[i];
2978				}
2979				dpc[pos] = obj.id;
2980				new_par.children = dpc;
2981				new_par.children_d.push(obj.id);
2982				new_par.children_d = new_par.children_d.concat(obj.children_d);
2983
2984				// update object
2985				obj.parent = new_par.id;
2986				tmp = new_par.parents.concat();
2987				tmp.unshift(new_par.id);
2988				p = obj.parents.length;
2989				obj.parents = tmp;
2990
2991				// update object children
2992				tmp = tmp.concat();
2993				for(i = 0, j = obj.children_d.length; i < j; i++) {
2994					this._model.data[obj.children_d[i]].parents = this._model.data[obj.children_d[i]].parents.slice(0,p*-1);
2995					Array.prototype.push.apply(this._model.data[obj.children_d[i]].parents, tmp);
2996				}
2997
2998				this._node_changed(old_par);
2999				this._node_changed(new_par.id);
3000				this.redraw(old_par === '#' || new_par.id === '#');
3001			}
3002			if(callback) { callback.call(this, obj, new_par, pos); }
3003			/**
3004			 * triggered when a node is moved
3005			 * @event
3006			 * @name move_node.jstree
3007			 * @param {Object} node
3008			 * @param {String} parent the parent's ID
3009			 * @param {Number} position the position of the node among the parent's children
3010			 * @param {String} old_parent the old parent of the node
3011			 * @param {Boolean} is_multi do the node and new parent belong to different instances
3012			 * @param {jsTree} old_instance the instance the node came from
3013			 * @param {jsTree} new_instance the instance of the new parent
3014			 */
3015			this.trigger('move_node', { "node" : obj, "parent" : new_par.id, "position" : pos, "old_parent" : old_par, "is_multi" : is_multi, 'old_instance' : old_ins, 'new_instance' : this });
3016			return true;
3017		},
3018		/**
3019		 * copy a node to a new parent
3020		 * @name copy_node(obj, par [, pos, callback, is_loaded])
3021		 * @param  {mixed} obj the node to copy, pass an array to copy multiple nodes
3022		 * @param  {mixed} par the new parent
3023		 * @param  {mixed} pos the position to insert at ("first" and "last" are supported, as well as "before" and "after"), defaults to `0`
3024		 * @param  {function} callback a function to call once the move is completed, receives 3 arguments - the node, the new parent and the position
3025		 * @param  {Boolean} internal parameter indicating if the parent node has been loaded
3026		 * @trigger model.jstree copy_node.jstree
3027		 */
3028		copy_node : function (obj, par, pos, callback, is_loaded) {
3029			var t1, t2, dpc, tmp, i, j, node, old_par, new_par, old_ins, is_multi;
3030			if($.isArray(obj)) {
3031				obj = obj.reverse().slice();
3032				for(t1 = 0, t2 = obj.length; t1 < t2; t1++) {
3033					this.copy_node(obj[t1], par, pos, callback, is_loaded);
3034				}
3035				return true;
3036			}
3037			obj = obj && obj.id ? obj : this.get_node(obj);
3038			par = this.get_node(par);
3039			pos = pos === undefined ? 0 : pos;
3040
3041			if(!par || !obj || obj.id === '#') { return false; }
3042			if(!pos.toString().match(/^(before|after)$/) && !is_loaded && !this.is_loaded(par)) {
3043				return this.load_node(par, function () { this.copy_node(obj, par, pos, callback, true); });
3044			}
3045
3046			old_par = (obj.parent || '#').toString();
3047			new_par = (!pos.toString().match(/^(before|after)$/) || par.id === '#') ? par : this.get_node(par.parent);
3048			old_ins = this._model.data[obj.id] ? this : $.jstree.reference(obj.id);
3049			is_multi = !old_ins || !old_ins._id || (this._id !== old_ins._id);
3050			if(new_par.id === '#') {
3051				if(pos === "before") { pos = "first"; }
3052				if(pos === "after") { pos = "last"; }
3053			}
3054			switch(pos) {
3055				case "before":
3056					pos = $.inArray(par.id, new_par.children);
3057					break;
3058				case "after" :
3059					pos = $.inArray(par.id, new_par.children) + 1;
3060					break;
3061				case "inside":
3062				case "first":
3063					pos = 0;
3064					break;
3065				case "last":
3066					pos = new_par.children.length;
3067					break;
3068				default:
3069					if(!pos) { pos = 0; }
3070					break;
3071			}
3072			if(pos > new_par.children.length) { pos = new_par.children.length; }
3073			if(!this.check("copy_node", obj, new_par, pos)) {
3074				this.settings.core.error.call(this, this._data.core.last_error);
3075				return false;
3076			}
3077			node = old_ins ? old_ins.get_json(obj, { no_id : true, no_data : true, no_state : true }) : obj;
3078			if(!node) { return false; }
3079			if(node.id === true) { delete node.id; }
3080			node = this._parse_model_from_json(node, new_par.id, new_par.parents.concat());
3081			if(!node) { return false; }
3082			tmp = this.get_node(node);
3083			dpc = [];
3084			dpc.push(node);
3085			dpc = dpc.concat(tmp.children_d);
3086			this.trigger('model', { "nodes" : dpc, "parent" : new_par.id });
3087
3088			// insert into new parent and up
3089			for(i = 0, j = new_par.parents.length; i < j; i++) {
3090				this._model.data[new_par.parents[i]].children_d = this._model.data[new_par.parents[i]].children_d.concat(dpc);
3091			}
3092			dpc = [];
3093			for(i = 0, j = new_par.children.length; i < j; i++) {
3094				dpc[i >= pos ? i+1 : i] = new_par.children[i];
3095			}
3096			dpc[pos] = tmp.id;
3097			new_par.children = dpc;
3098			new_par.children_d.push(tmp.id);
3099			new_par.children_d = new_par.children_d.concat(tmp.children_d);
3100
3101			this._node_changed(new_par.id);
3102			this.redraw(new_par.id === '#');
3103			if(callback) { callback.call(this, tmp, new_par, pos); }
3104			/**
3105			 * triggered when a node is copied
3106			 * @event
3107			 * @name copy_node.jstree
3108			 * @param {Object} node the copied node
3109			 * @param {Object} original the original node
3110			 * @param {String} parent the parent's ID
3111			 * @param {Number} position the position of the node among the parent's children
3112			 * @param {String} old_parent the old parent of the node
3113			 * @param {Boolean} is_multi do the node and new parent belong to different instances
3114			 * @param {jsTree} old_instance the instance the node came from
3115			 * @param {jsTree} new_instance the instance of the new parent
3116			 */
3117			this.trigger('copy_node', { "node" : tmp, "original" : obj, "parent" : new_par.id, "position" : pos, "old_parent" : old_par, "is_multi" : is_multi, 'old_instance' : old_ins, 'new_instance' : this });
3118			return tmp.id;
3119		},
3120		/**
3121		 * cut a node (a later call to `paste(obj)` would move the node)
3122		 * @name cut(obj)
3123		 * @param  {mixed} obj multiple objects can be passed using an array
3124		 * @trigger cut.jstree
3125		 */
3126		cut : function (obj) {
3127			if(!obj) { obj = this._data.core.selected.concat(); }
3128			if(!$.isArray(obj)) { obj = [obj]; }
3129			if(!obj.length) { return false; }
3130			var tmp = [], o, t1, t2;
3131			for(t1 = 0, t2 = obj.length; t1 < t2; t1++) {
3132				o = this.get_node(obj[t1]);
3133				if(o && o.id && o.id !== '#') { tmp.push(o); }
3134			}
3135			if(!tmp.length) { return false; }
3136			ccp_node = tmp;
3137			ccp_inst = this;
3138			ccp_mode = 'move_node';
3139			/**
3140			 * triggered when nodes are added to the buffer for moving
3141			 * @event
3142			 * @name cut.jstree
3143			 * @param {Array} node
3144			 */
3145			this.trigger('cut', { "node" : obj });
3146		},
3147		/**
3148		 * copy a node (a later call to `paste(obj)` would copy the node)
3149		 * @name copy(obj)
3150		 * @param  {mixed} obj multiple objects can be passed using an array
3151		 * @trigger copy.jstre
3152		 */
3153		copy : function (obj) {
3154			if(!obj) { obj = this._data.core.selected.concat(); }
3155			if(!$.isArray(obj)) { obj = [obj]; }
3156			if(!obj.length) { return false; }
3157			var tmp = [], o, t1, t2;
3158			for(t1 = 0, t2 = obj.length; t1 < t2; t1++) {
3159				o = this.get_node(obj[t1]);
3160				if(o && o.id && o.id !== '#') { tmp.push(o); }
3161			}
3162			if(!tmp.length) { return false; }
3163			ccp_node = tmp;
3164			ccp_inst = this;
3165			ccp_mode = 'copy_node';
3166			/**
3167			 * triggered when nodes are added to the buffer for copying
3168			 * @event
3169			 * @name copy.jstree
3170			 * @param {Array} node
3171			 */
3172			this.trigger('copy', { "node" : obj });
3173		},
3174		/**
3175		 * get the current buffer (any nodes that are waiting for a paste operation)
3176		 * @name get_buffer()
3177		 * @return {Object}
3177 an object consisting of `mode` ("copy_node" or "move_node"), `node` (an array of objects) and `inst` (the instance)
3178		 */
3179		get_buffer : function () {
3180			return { 'mode' : ccp_mode, 'node' : ccp_node, 'inst' : ccp_inst };
3181		},
3182		/**
3183		 * check if there is something in the buffer to paste
3184		 * @name can_paste()
3185		 * @return {Boolean}
3186		 */
3187		can_paste : function () {
3188			return ccp_mode !== false && ccp_node !== false; // && ccp_inst._model.data[ccp_node];
3189		},
3190		/**
3191		 * copy or move the previously cut or copied nodes to a new parent
3192		 * @name paste(obj)
3193		 * @param  {mixed} obj the new parent
3194		 * @trigger paste.jstree
3195		 */
3196		paste : function (obj) {
3197			obj = this.get_node(obj);
3198			if(!obj || !ccp_mode || !ccp_mode.match(/^(copy_node|move_node)$/) || !ccp_node) { return false; }
3199			if(this[ccp_mode](ccp_node, obj)) {
3200				/**
3201				 * triggered when paste is invoked
3202				 * @event
3203				 * @name paste.jstree
3204				 * @param {String} parent the ID of the receiving node
3205				 * @param {Array} node the nodes in the buffer
3206				 * @param {String} mode the performed operation - "copy_node" or "move_node"
3207				 */
3208				this.trigger('paste', { "parent" : obj.id, "node" : ccp_node, "mode" : ccp_mode });
3209			}
3210			ccp_node = false;
3211			ccp_mode = false;
vendor: 10,208 bytes, lines 3212-3500
3212			ccp_inst = false;
3213		},
3214		/**
3215		 * put a node in edit mode (input field to rename the node)
3216		 * @name edit(obj [, default_text])
3217		 * @param  {mixed} obj
3218		 * @param  {String} default_text the text to populate the input with (if omitted the node text value is used)
3219		 */
3220		edit : function (obj, default_text) {
3221			obj = this._open_to(obj);
3222			if(!obj || !obj.length) { return false; }
3223			var rtl = this._data.core.rtl,
3224				w  = this.element.width(),
3225				a  = obj.children('.jstree-anchor'),
3226				s  = $('<span>'),
3227				/*!
3228				oi = obj.children("i:visible"),
3229				ai = a.children("i:visible"),
3230				w1 = oi.width() * oi.length,
3231				w2 = ai.width() * ai.length,
3232				*/
3233				t  = typeof default_text === 'string' ? default_text : this.get_text(obj),
3234				h1 = $("<"+"div />", { css : { "position" : "absolute", "top" : "-200px", "left" : (rtl ? "0px" : "-1000px"), "visibility" : "hidden" } }).appendTo("body"),
3235				h2 = $("<"+"input />", {
3236						"value" : t,
3237						"class" : "jstree-rename-input",
3238						// "size" : t.length,
3239						"css" : {
3240							"padding" : "0",
3241							"border" : "1px solid silver",
3242							"box-sizing" : "border-box",
3243							"display" : "inline-block",
3244							"height" : (this._data.core.li_height) + "px",
3245							"lineHeight" : (this._data.core.li_height) + "px",
3246							"width" : "150px" // will be set a bit further down
3247						},
3248						"blur" : $.proxy(function () {
3249							var i = s.children(".jstree-rename-input"),
3250								v = i.val();
3251							if(v === "") { v = t; }
3252							h1.remove();
3253							s.replaceWith(a);
3254							s.remove();
3255							this.set_text(obj, t);
3256							if(this.rename_node(obj, v) === false) {
3257								this.set_text(obj, t); // move this up? and fix #483
3258							}
3259						}, this),
3260						"keydown" : function (event) {
3261							var key = event.which;
3262							if(key === 27) {
3263								this.value = t;
3264							}
3265							if(key === 27 || key === 13 || key === 37 || key === 38 || key === 39 || key === 40 || key === 32) {
3266								event.stopImmediatePropagation();
3267							}
3268							if(key === 27 || key === 13) {
3269								event.preventDefault();
3270								this.blur();
3271							}
3272						},
3273						"click" : function (e) { e.stopImmediatePropagation(); },
3274						"mousedown" : function (e) { e.stopImmediatePropagation(); },
3275						"keyup" : function (event) {
3276							h2.width(Math.min(h1.text("pW" + this.value).width(),w));
3277						},
3278						"keypress" : function(event) {
3279							if(event.which === 13) { return false; }
3280						}
3281					}),
3282				fn = {
3283						fontFamily		: a.css('fontFamily')		|| '',
3284						fontSize		: a.css('fontSize')			|| '',
3285						fontWeight		: a.css('fontWeight')		|| '',
3286						fontStyle		: a.css('fontStyle')		|| '',
3287						fontStretch		: a.css('fontStretch')		|| '',
3288						fontVariant		: a.css('fontVariant')		|| '',
3289						letterSpacing	: a.css('letterSpacing')	|| '',
3290						wordSpacing		: a.css('wordSpacing')		|| ''
3291				};
3292			this.set_text(obj, "");
3293			s.attr('class', a.attr('class')).append(a.contents().clone()).append(h2);
3294			a.replaceWith(s);
3295			h1.css(fn);
3296			h2.css(fn).width(Math.min(h1.text("pW" + h2[0].value).width(),w))[0].select();
3297		},
3298
3299
3300		/**
3301		 * changes the theme
3302		 * @name set_theme(theme_name [, theme_url])
3303		 * @param {String} theme_name the name of the new theme to apply
3304		 * @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.
3305		 * @trigger set_theme.jstree
3306		 */
3307		set_theme : function (theme_name, theme_url) {
3308			if(!theme_name) { return false; }
3309			if(theme_url === true) {
3310				var dir = this.settings.core.themes.dir;
3311				if(!dir) { dir = $.jstree.path + '/themes'; }
3312				theme_url = dir + '/' + theme_name + '/style.css';
3313			}
3314			if(theme_url && $.inArray(theme_url, themes_loaded) === -1) {
3315				$('head').append('<'+'link rel="stylesheet" href="' + theme_url + '" type="text/css" />');
3316				themes_loaded.push(theme_url);
3317			}
3318			if(this._data.core.themes.name) {
3319				this.element.removeClass('jstree-' + this._data.core.themes.name);
3320			}
3321			this._data.core.themes.name = theme_name;
3322			this.element.addClass('jstree-' + theme_name);
3323			this.element[this.settings.core.themes.responsive ? 'addClass' : 'removeClass' ]('jstree-' + theme_name + '-responsive');
3324			/**
3325			 * triggered when a theme is set
3326			 * @event
3327			 * @name set_theme.jstree
3328			 * @param {String} theme the new theme
3329			 */
3330			this.trigger('set_theme', { 'theme' : theme_name });
3331		},
3332		/**
3333		 * gets the name of the currently applied theme name
3334		 * @name get_theme()
3335		 * @return {String}
3336		 */
3337		get_theme : function () { return this._data.core.themes.name; },
3338		/**
3339		 * changes the theme variant (if the theme has variants)
3340		 * @name set_theme_variant(variant_name)
3341		 * @param {String|Boolean} variant_name the variant to apply (if `false` is used the current variant is removed)
3342		 */
3343		set_theme_variant : function (variant_name) {
3344			if(this._data.core.themes.variant) {
3345				this.element.removeClass('jstree-' + this._data.core.themes.name + '-' + this._data.core.themes.variant);
3346			}
3347			this._data.core.themes.variant = variant_name;
3348			if(variant_name) {
3349				this.element.addClass('jstree-' + this._data.core.themes.name + '-' + this._data.core.themes.variant);
3350			}
3351		},
3352		/**
3353		 * gets the name of the currently applied theme variant
3354		 * @name get_theme()
3355		 * @return {String}
3356		 */
3357		get_theme_variant : function () { return this._data.core.themes.variant; },
3358		/**
3359		 * shows a striped background on the container (if the theme supports it)
3360		 * @name show_stripes()
3361		 */
3362		show_stripes : function () { this._data.core.themes.stripes = true; this.get_container_ul().addClass("jstree-striped"); },
3363		/**
3364		 * hides the striped background on the container
3365		 * @name hide_stripes()
3366		 */
3367		hide_stripes : function () { this._data.core.themes.stripes = false; this.get_container_ul().removeClass("jstree-striped"); },
3368		/**
3369		 * toggles the striped background on the container
3370		 * @name toggle_stripes()
3371		 */
3372		toggle_stripes : function () { if(this._data.core.themes.stripes) { this.hide_stripes(); } else { this.show_stripes(); } },
3373		/**
3374		 * shows the connecting dots (if the theme supports it)
3375		 * @name show_dots()
3376		 */
3377		show_dots : function () { this._data.core.themes.dots = true; this.get_container_ul().removeClass("jstree-no-dots"); },
3378		/**
3379		 * hides the connecting dots
3380		 * @name hide_dots()
3381		 */
3382		hide_dots : function () { this._data.core.themes.dots = false; this.get_container_ul().addClass("jstree-no-dots"); },
3383		/**
3384		 * toggles the connecting dots
3385		 * @name toggle_dots()
3386		 */
3387		toggle_dots : function () { if(this._data.core.themes.dots) { this.hide_dots(); } else { this.show_dots(); } },
3388		/**
3389		 * show the node icons
3390		 * @name show_icons()
3391		 */
3392		show_icons : function () { this._data.core.themes.icons = true; this.get_container_ul().removeClass("jstree-no-icons"); },
3393		/**
3394		 * hide the node icons
3395		 * @name hide_icons()
3396		 */
3397		hide_icons : function () { this._data.core.themes.icons = false; this.get_container_ul().addClass("jstree-no-icons"); },
3398		/**
3399		 * toggle the node icons
3400		 * @name toggle_icons()
3401		 */
3402		toggle_icons : function () { if(this._data.core.themes.icons) { this.hide_icons(); } else { this.show_icons(); } },
3403		/**
3404		 * set the node icon for a node
3405		 * @name set_icon(obj, icon)
3406		 * @param {mixed} obj
3407		 * @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
3408		 */
3409		set_icon : function (obj, icon) {
3410			var t1, t2, dom, old;
3411			if($.isArray(obj)) {
3412				obj = obj.slice();
3413				for(t1 = 0, t2 = obj.length; t1 < t2; t1++) {
3414					this.set_icon(obj[t1], icon);
3415				}
3416				return true;
3417			}
3418			obj = this.get_node(obj);
3419			if(!obj || obj.id === '#') { return false; }
3420			old = obj.icon;
3421			obj.icon = icon;
3422			dom = this.get_node(obj, true).children(".jstree-anchor").children(".jstree-themeicon");
3423			if(icon === false) {
3424				this.hide_icon(obj);
3425			}
3426			else if(icon === true) {
3427				dom.removeClass('jstree-themeicon-custom ' + old).css("background","").removeAttr("rel");
3428			}
3429			else if(icon.indexOf("/") === -1 && icon.indexOf(".") === -1) {
3430				dom.removeClass(old).css("background","");
3431				dom.addClass(icon + ' jstree-themeicon-custom').attr("rel",icon);
3432			}
3433			else {
3434				dom.removeClass(old).css("background","");
3435				dom.addClass('jstree-themeicon-custom').css("background", "url('" + icon + "') center center no-repeat").attr("rel",icon);
3436			}
3437			return true;
3438		},
3439		/**
3440		 * get the node icon for a node
3441		 * @name get_icon(obj)
3442		 * @param {mixed} obj
3443		 * @return {String}
3444		 */
3445		get_icon : function (obj) {
3446			obj = this.get_node(obj);
3447			return (!obj || obj.id === '#') ? false : obj.icon;
3448		},
3449		/**
3450		 * hide the icon on an individual node
3451		 * @name hide_icon(obj)
3452		 * @param {mixed} obj
3453		 */
3454		hide_icon : function (obj) {
3455			var t1, t2;
3456			if($.isArray(obj)) {
3457				obj = obj.slice();
3458				for(t1 = 0, t2 = obj.length; t1 < t2; t1++) {
3459					this.hide_icon(obj[t1]);
3460				}
3461				return true;
3462			}
3463			obj = this.get_node(obj);
3464			if(!obj || obj === '#') { return false; }
3465			obj.icon = false;
3466			this.get_node(obj, true).children("a").children(".jstree-themeicon").addClass('jstree-themeicon-hidden');
3467			return true;
3468		},
3469		/**
3470		 * show the icon on an individual node
3471		 * @name show_icon(obj)
3472		 * @param {mixed} obj
3473		 */
3474		show_icon : function (obj) {
3475			var t1, t2, dom;
3476			if($.isArray(obj)) {
3477				obj = obj.slice();
3478				for(t1 = 0, t2 = obj.length; t1 < t2; t1++) {
3479					this.show_icon(obj[t1]);
3480				}
3481				return true;
3482			}
3483			obj = this.get_node(obj);
3484			if(!obj || obj === '#') { return false; }
3485			dom = this.get_node(obj, true);
3486			obj.icon = dom.length ? dom.children("a").children(".jstree-themeicon").attr('rel') : true;
3487			if(!obj.icon) { obj.icon = true; }
3488			dom.children("a").children(".jstree-themeicon").removeClass('jstree-themeicon-hidden');
3489			return true;
3490		}
3491	};
3492
3493	// helpers
3494	$.vakata = {};
3495	// reverse
3496	$.fn.vakata_reverse = [].reverse;
3497	// collect attributes
3498	$.vakata.attributes = function(node, with_values) {
3499		node = $(node)[0];
3500		var attr = with_values ? {}
3500 : [];
3501		if(node && node.attributes) {
3502			$.each(node.attributes, function (i, v) {
3503				if($.inArray(v.nodeName.toLowerCase(),['style','contenteditable','hasfocus','tabindex']) !== -1) { return; }
3504				if(v.nodeValue !== null && $.trim(v.nodeValue) !== '') {
3505					if(with_values) { attr[v.nodeName] = v.nodeValue; }
3506					else { attr.push(v.nodeName); }
3507				}
3508			});
3509		}
3510		return attr;
3511	};
3512	$.vakata.array_unique = function(array) {
3513		var a = [], i, j, l;
3514		for(i = 0, l = array.length; i < l; i++) {
3515			for(j = 0; j <= i; j++) {
3516				if(array[i] === array[j]) {
3517					break;
3518				}
3519			}
3520			if(j === i) { a.push(array[i]); }
3521		}
3522		return a;
3523	};
3524	// remove item from array
3525	$.vakata.array_remove = function(array, from, to) {
3526		var rest = array.slice((to || from) + 1 || array.length);
3527		array.length = from < 0 ? array.length + from : from;
3528		array.push.apply(array, rest);
3529		return array;
3530	};
3531	// remove item from array
3532	$.vakata.array_remove_item = function(array, item) {
3533		var tmp = $.inArray(item, array);
3534		return tmp !== -1 ? $.vakata.array_remove(array, tmp) : array;
3535	};
3536	// browser sniffing
3537	(function () {
3538		var browser = {},
3539			b_match = function(ua) {
3540			ua = ua.toLowerCase();
3541
3542			var match =	/(chrome)[ \/]([\w.]+)/.exec( ua ) ||
3543						/(webkit)[ \/]([\w.]+)/.exec( ua ) ||
3544						/(opera)(?:.*version|)[ \/]([\w.]+)/.exec( ua ) ||
3545						/(msie) ([\w.]+)/.exec( ua ) ||
3546						(ua.indexOf("compatible") < 0 && /(mozilla)(?:.*? rv:([\w.]+)|)/.exec( ua )) ||
3547						[];
3548				return {
3549					browser: match[1] || "",
3550					version: match[2] || "0"
3551				};
3552			},
3553			matched = b_match(window.navigator.userAgent);
3554		if(matched.browser) {
3555			browser[ matched.browser ] = true;
3556			browser.version = matched.version;
3557		}
3558		if(browser.chrome) {
3559			browser.webkit = true;
3560		}
3561		else if(browser.webkit) {
3562			browser.safari = true;
3563		}
3564		$.vakata.browser = browser;
3565	}());
3566	if($.vakata.browser.msie && $.vakata.browser.version < 8) {
3567		$.jstree.defaults.core.animation = 0;
3568	}
3569
3570/**
3571 * ### Checkbox plugin
3572 *
3573 * This plugin renders checkbox icons in front of each node, making multiple selection much easier. 
3574 * 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.
3575 */
3576
3577	var _i = document.createElement('I');
3578	_i.className = 'jstree-icon jstree-checkbox';
3579	/**
3580	 * stores all defaults for the checkbox plugin
3581	 * @name $.jstree.defaults.checkbox
3582	 * @plugin checkbox
3583	 */
3584	$.jstree.defaults.checkbox = {
3585		/**
3586		 * a boolean indicating if checkboxes should be visible (can be changed at a later time using `show_checkboxes()` and `hide_checkboxes`). Defaults to `true`.
3587		 * @name $.jstree.defaults.checkbox.visible
3588		 * @plugin checkbox
3589		 */
3590		visible				: true,
3591		/**
3592		 * a boolean indicating if checkboxes should cascade down and have an undetermined state. Defaults to `true`.
3593		 * @name $.jstree.defaults.checkbox.three_state
3594		 * @plugin checkbox
3595		 */
3596		three_state			: true,
3597		/**
3598		 * a boolean indicating if clicking anywhere on the node should act as clicking on the checkbox. Defaults to `true`.
3599		 * @name $.jstree.defaults.checkbox.whole_node
3600		 * @plugin checkbox
3601		 */
3602		whole_node			: true,
3603		/**
3604		 * a boolean indicating if the selected style of a node should be kept, or removed. Defaults to `true`.
3605		 * @name $.jstree.defaults.checkbox.keep_selected_style
3606		 * @plugin checkbox
3607		 */
3608		keep_selected_style	: true
3609	};
3610	$.jstree.plugins.checkbox = function (options, parent) {
3611		this.bind = function () {
3612			parent.bind.call(this);
3613			this._data.checkbox.uto = false;
3614			this.element
3615				.on("init.jstree", $.proxy(function () {
3616						this._data.checkbox.visible = this.settings.checkbox.visible;
3617						if(!this.settings.checkbox.keep_selected_style) {
3618							this.element.addClass('jstree-checkbox-no-clicked');
3619						}
3620					}, this))
3621				.on("loading.jstree", $.proxy(function () {
3622						this[ this._data.checkbox.visible ? 'show_checkboxes' : 'hide_checkboxes' ]();
3623					}, this));
3624			if(this.settings.checkbox.three_state) {
3625				this.element
3626					.on('changed.jstree move_node.jstree copy_node.jstree redraw.jstree open_node.jstree', $.proxy(function () {
3627							if(this._data.checkbox.uto) { clearTimeout(this._data.checkbox.uto); }
3628							this._data.checkbox.uto = setTimeout($.proxy(this._undetermined, this), 50);
3629						}, this))
3630					.on('model.jstree', $.proxy(function (e, data) {
3631							var m = this._model.data,
3632								p = m[data.parent],
3633								dpc = data.nodes,
3634								chd = [],
3635								c, i, j, k, l, tmp;
3636
3637							// apply down
3638							if(p.state.selected) {
3639								for(i = 0, j = dpc.length; i < j; i++) {
3640									m[dpc[i]].state.selected = true;
3641								}
3642								this._data.core.selected = this._data.core.selected.concat(dpc);
3643							}
3644							else {
3645								for(i = 0, j = dpc.length; i < j; i++) {
3646									if(m[dpc[i]].state.selected) {
3647										for(k = 0, l = m[dpc[i]].children_d.length; k < l; k++) {
3648											m[m[dpc[i]].children_d[k]].state.selected = true;
3649										}
3650										this._data.core.selected = this._data.core.selected.concat(m[dpc[i]].children_d);
3651									}
3652								}
3653							}
3654
3655							// apply up
3656							for(i = 0, j = p.children_d.length; i < j; i++) {
3657								if(!m[p.children_d[i]].children.length) {
3658									chd.push(m[p.children_d[i]].parent);
3659								}
3660							}
3661							chd = $.vakata.array_unique(chd);
3662							for(k = 0, l = chd.length; k < l; k++) {
3663								p = m[chd[k]];
3664								while(p && p.id !== '#') {
3665									c = 0;
3666									for(i = 0, j = p.children.length; i < j; i++) {
3667										c += m[p.children[i]].state.selected;
3668									}
3669									if(c === j) {
3670										p.state.selected = true;
3671										this._data.core.selected.push(p.id);
3672										tmp = this.get_node(p, true);
3673										if(tmp && tmp.length) {
3674											tmp.children('.jstree-anchor').addClass('jstree-clicked');
3675										}
3676									}
3677									else {
3678										break;
3679									}
3680									p = this.get_node(p.parent);
3681								}
3682							}
3683							this._data.core.selected = $.vakata.array_unique(this._data.core.selected);
3684						}, this))
3685					.on('select_node.jstree', $.proxy(function (e, data) {
3686							var obj = data.node,
3687								m = this._model.data,
3688								par = this.get_node(obj.parent),
3689								dom = this.get_node(obj, true),
3690								i, j, c, tmp;
3691							this._data.core.selected = $.vakata.array_unique(this._data.core.selected.concat(obj.children_d));
3692							for(i = 0, j = obj.children_d.length; i < j; i++) {
3693								m[obj.children_d[i]].state.selected = true;
3694							}
3695							while(par && par.id !== '#') {
3696								c = 0;
3697								for(i = 0, j = par.children.length; i < j; i++) {
3698									c += m[par.children[i]].state.selected;
3699								}
3700								if(c === j) {
3701									par.state.selected = true;
3702									this._data.core.selected.push(par.id);
3703									tmp = this.get_node(par, true);
3704									if(tmp && tmp.length) {
3705										tmp.children('.jstree-anchor').addClass('jstree-clicked');
3706									}
3707								}
3708								else {
3709									break;
3710								}
3711								par = this.get_node(par.parent);
3712							}
3713							if(dom.length) {
3714								dom.find('.jstree-anchor').addClass('jstree-clicked');
3715							}
3716						}, this))
3717					.on('deselect_node.jstree', $.proxy(function (e, data) {
3718							var obj = data.node,
3719								dom = this.get_node(obj, true),
3720								i, j, tmp;
3721							for(i = 0, j = obj.children_d.length; i < j; i++) {
3722								this._model.data[obj.children_d[i]].state.selected = false;
3723							}
3724							for(i = 0, j = obj.parents.length; i < j; i++) {
3725								this._model.data[obj.parents[i]].state.selected = false;
3726								tmp = this.get_node(obj.parents[i], true);
3727								if(tmp && tmp.length) {
3728									tmp.children('.jstree-anchor').removeClass('jstree-clicked');
3729								}
3730							}
3731							tmp = [];
3732							for(i = 0, j = this._data.core.selected.length; i < j; i++) {
3733								if($.inArray(this._data.core.selected[i], obj.children_d) === -1 && $.inArray(this._data.core.selected[i], obj.parents) === -1) {
3734									tmp.push(this._data.core.selected[i]);
3735								}
3736							}
3737							this._data.core.selected = $.vakata.array_unique(tmp);
3738							if(dom.length) {
3739								dom.find('.jstree-anchor').removeClass('jstree-clicked');
3740							}
3741						}, this))
3742					.on('delete_node.jstree', $.proxy(function (e, data) {
3743							var p = this.get_node(data.parent),
3744								m = this._model.data,
3745								i, j, c, tmp;
3746							while(p && p.id !== '#') {
3747								c = 0;
3748								for(i = 0, j = p.children.length; i < j; i++) {
3749									c += m[p.children[i]].state.selected;
3750								}
3751								if(c === j) {
3752									p.state.selected = true;
3753									this._data.core.selected.push(p.id);
3754									tmp = this.get_node(p, true);
3755									if(tmp && tmp.length) {
3756										tmp.children('.jstree-anchor').addClass('jstree-clicked');
3757									}
3758								}
3759								else {
3760									break;
3761								}
3762								p = this.get_node(p.parent);
3763							}
3764						}, this))
3765					.on('move_node.jstree', $.proxy(function (e, data) {
3766							var is_multi = data.is_multi,
3767								old_par = data.old_parent,
3768								new_par = this.get_node(data.parent),
3769								m = this._model.data,
3770								p, c, i, j, tmp;
3771							if(!is_multi) {
3772								p = this.get_node(old_par);
3773								while(p && p.id !== '#') {
3774									c = 0;
3775									for(i = 0, j = p.children.length; i < j; i++) {
3776										c += m[p.children[i]].state.selected;
3777									}
3778									if(c === j) {
3779										p.state.selected = true;
3780										this._data.core.selected.push(p.id);
3781										tmp = this.get_node(p, true);
3782										if(tmp && tmp.length) {
3783											tmp.children('.jstree-anchor').addClass('jstree-clicked');
3784										}
3785									}
3786									else {
3787										break;
3788									}
3789									p = this.get_node(p.parent);
3790								}
3791							}
3792							p = new_par;
3793							while(p && p.id !== '#') {
3794								c = 0;
3795								for(i = 0, j = p.children.length; i < j; i++) {
3796									c += m[p.children[i]].state.selected;
3797								}
3798								if(c === j) {
3799									if(!p.state.selected) {
3800										p.state.selected = true;
3801										this._data.core.selected.push(p.id);
3802										tmp = this.get_node(p, true);
3803										if(tmp && tmp.length) {
3804											tmp.children('.jstree-anchor').addClass('jstree-clicked');
3805										}
3806									}
3807								}
3808								else {
3809									if(p.state.selected) {
3810										p.state.selected = false;
3811										this._data.core.selected = $.vakata.array_remove_item(this._data.core.selected, p.id);
3812										tmp = this.get_node(p, true);
3813										if(tmp && tmp.length) {
3814											tmp.children('.jstree-anchor').removeClass('jstree-clicked');
3815										}
3816									}
3817									else {
3818										break;
3819									}
3820								}
3821								p = this.get_node(p.parent);
3822							}
3823						}, this));
3824			}
3825		};
3826		/**
3827		 * set the undetermined state where and if necessary. Used internally.
3828		 * @private
3829		 * @name _undetermined()
3830		 * @plugin checkbox
3831		 */
3832		this._undetermined = function () {
3833			var i, j, m = this._model.data, s = this._data.core.selected, p = [], t = this;
3834			for(i = 0, j = s.length; i < j; i++) {
3835				if(m[s[i]] && m[s[i]].parents) {
3836					p = p.concat(m[s[i]].parents);
3837				}
3838			}
3839			// attempt for server side undetermined state
3840			this.element.find('.jstree-closed').not(':has(ul)')
3841				.each(function () {
3842					var tmp = t.get_node(this);
3843					if(!tmp.state.loaded && tmp.original && tmp.original.state && tmp.original.state.undetermined && tmp.original.state.undetermined === true) {
3844						p.push(tmp.id);
3845						p = p.concat(tmp.parents);
3846					}
3847				});
3848			p = $.vakata.array_unique(p);
3849			i = $.inArray('#', p);
3850			if(i !== -1) {
3851				p = $.vakata.array_remove(p, i);
3852			}
3853
3854			this.element.find('.jstree-undetermined').removeClass('jstree-undetermined');
3855			for(i = 0, j = p.length; i < j; i++) {
3856				if(!m[p[i]].state.selected) {
3857					s = this.get_node(p[i], true);
3858					if(s && s.length) {
3859						s.children('a').children('.jstree-checkbox').addClass('jstree-undetermined');
3860					}
3861				}
3862			}
3863		};
3864		this.redraw_node = function(obj, deep, is_callback) {
3865			obj = parent.redraw_node.call(this, obj, deep, is_callback);
3866			if(obj) {
3867				var tmp = obj.getElementsByTagName('A')[0];
3868				tmp.insertBefore(_i.cloneNode(), tmp.childNodes[0]);
3869			}
3870			if(!is_callback && this.settings.checkbox.three_state) {
3871				if(this._data.checkbox.uto) { clearTimeout(this._data.checkbox.uto); }
3872				this._data.checkbox.uto = setTimeout($.proxy(this._undetermined, this), 50);
3873			}
3874			return obj;
3875		};
3876		this.activate_node = function (obj, e) {
3877			if(this.settings.checkbox.whole_node || $(e.target).hasClass('jstree-checkbox')) {
3878				e.ctrlKey = true;
3879			}
3880			return parent.activate_node.call(this, obj, e);
3881		};
3882		/**
3883		 * show the node checkbox icons
3884		 * @name show_checkboxes()
3885		 * @plugin checkbox
3886		 */
3887		this.show_checkboxes = function () { this._data.core.themes.checkboxes = true; this.element.children("ul").removeClass("jstree-no-checkboxes"); };
3888		/**
3889		 * hide the node checkbox icons
3890		 * @name hide_checkboxes()
3891		 * @plugin checkbox
3892		 */
3893		this.hide_checkboxes = function () { this._data.core.themes.checkboxes = false; this.element.children("ul").addClass("jstree-no-checkboxes"); };
3894		/**
3895		 * toggle the node icons
3896		 * @name toggle_checkboxes()
3897		 * @plugin checkbox
3898		 */
3899		this.toggle_checkboxes = function () { if(this._data.core.themes.checkboxes) { this.hide_checkboxes(); } else { this.show_checkboxes(); } };
3900	};
3901
3902	// include the checkbox plugin by default
3903	// $.jstree.defaults.plugins.push("checkbox");
3904
3905/**
3906 * ### Contextmenu plugin
3907 *
3908 * Shows a context menu when a node is right-clicked.
3909 */
3910// TODO: move logic outside of function + check multiple move
3911
3912	/**
3913	 * stores all defaults for the contextmenu plugin
3914	 * @name $.jstree.defaults.contextmenu
3915	 * @plugin contextmenu
3916	 */
3917	$.jstree.defaults.contextmenu = {
3918		/**
3919		 * a boolean indicating if the node should be selected when the context menu is invoked on it. Defaults to `true`.
3920		 * @name $.jstree.defaults.contextmenu.select_node
3921		 * @plugin contextmenu
3922		 */
3923		select_node : true,
3924		/**
3925		 * a boolean indicating if the menu should be shown aligned with the node. Defaults to `true`, otherwise the mouse 
3925coordinates are used.
3926		 * @name $.jstree.defaults.contextmenu.show_at_node
3927		 * @plugin contextmenu
3928		 */
3929		show_at_node : true,
3930		/**
3931		 * 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).
3932		 * 
3933		 * 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):
3934		 * 
3935		 * * `separator_before` - a boolean indicating if there should be a separator before this item
3936		 * * `separator_after` - a boolean indicating if there should be a separator after this item
3937		 * * `_disabled` - a boolean indicating if this action should be disabled
3938		 * * `label` - a string - the name of the action
3939		 * * `action` - a function to be executed if this item is chosen
3940		 * * `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
3941		 * * `shortcut` - keyCode which will trigger the action if the menu is open (for example `113` for rename, which equals F2)
3942		 * * `shortcut_label` - shortcut label (like for example `F2` for rename)
3943		 * 
3944		 * @name $.jstree.defaults.contextmenu.items
3945		 * @plugin contextmenu
3946		 */
3947		items : function (o, cb) { // Could be an object directly
3948			return {
3949				"create" : {
3950					"separator_before"	: false,
3951					"separator_after"	: true,
3952					"_disabled"			: false, //(this.check("create_node", data.reference, {}, "last")),
3953					"label"				: "Create",
3954					"action"			: function (data) {
3955						var inst = $.jstree.reference(data.reference),
3956							obj = inst.get_node(data.reference);
3957						inst.create_node(obj, {}, "last", function (new_node) {
3958							setTimeout(function () { inst.edit(new_node); },0);
3959						});
3960					}
3961				},
3962				"rename" : {
3963					"separator_before"	: false,
3964					"separator_after"	: false,
3965					"_disabled"			: false, //(this.check("rename_node", data.reference, this.get_parent(data.reference), "")),
3966					"label"				: "Rename",
3967					/*
3968					"shortcut"			: 113,
3969					"shortcut_label"	: 'F2',
3970					"icon"				: "glyphicon glyphicon-leaf",
3971					*/
3972					"action"			: function (data) {
3973						var inst = $.jstree.reference(data.reference),
3974							obj = inst.get_node(data.reference);
3975						inst.edit(obj);
3976					}
3977				},
3978				"remove" : {
3979					"separator_before"	: false,
3980					"icon"				: false,
3981					"separator_after"	: false,
3982					"_disabled"			: false, //(this.check("delete_node", data.reference, this.get_parent(data.reference), "")),
3983					"label"				: "Delete",
3984					"action"			: function (data) {
3985						var inst = $.jstree.reference(data.reference),
3986							obj = inst.get_node(data.reference);
3987						if(inst.is_selected(obj)) {
3988							inst.delete_node(inst.get_selected());
3989						}
3990						else {
3991							inst.delete_node(obj);
3992						}
3993					}
3994				},
3995				"ccp" : {
3996					"separator_before"	: true,
3997					"icon"				: false,
3998					"separator_after"	: false,
3999					"label"				: "Edit",
4000					"action"			: false,
4001					"submenu" : {
4002						"cut" : {
4003							"separator_before"	: false,
4004							"separator_after"	: false,
4005							"label"				: "Cut",
4006							"action"			: function (data) {
4007								var inst = $.jstree.reference(data.reference),
4008									obj = inst.get_node(data.reference);
4009								if(inst.is_selected(obj)) {
4010									inst.cut(inst.get_selected());
4011								}
4012								else {
4013									inst.cut(obj);
4014								}
4015							}
4016						},
4017						"copy" : {
4018							"separator_before"	: false,
4019							"icon"				: false,
4020							"separator_after"	: false,
4021							"label"				: "Copy",
4022							"action"			: function (data) {
4023								var inst = $.jstree.reference(data.reference),
4024									obj = inst.get_node(data.reference);
4025								if(inst.is_selected(obj)) {
4026									inst.copy(inst.get_selected());
4027								}
4028								else {
4029									inst.copy(obj);
4030								}
4031							}
4032						},
4033						"paste" : {
4034							"separator_before"	: false,
4035							"icon"				: false,
4036							"_disabled"			: function (data) {
4037								return !$.jstree.reference(data.reference).can_paste();
4038							},
4039							"separator_after"	: false,
4040							"label"				: "Paste",
4041							"action"			: function (data) {
4042								var inst = $.jstree.reference(data.reference),
4043									obj = inst.get_node(data.reference);
4044								inst.paste(obj);
4045							}
4046						}
4047					}
4048				}
4049			};
4050		}
4051	};
4052
4053	$.jstree.plugins.contextmenu = function (options, parent) {
4054		this.bind = function () {
4055			parent.bind.call(this);
4056
4057			this.element
4058				.on("contextmenu.jstree", ".jstree-anchor", $.proxy(function (e) {
4059						e.preventDefault();
4060						if(!this.is_loading(e.currentTarget)) {
4061							this.show_contextmenu(e.currentTarget, e.pageX, e.pageY, e);
4062						}
4063					}, this))
4064				.on("click.jstree", ".jstree-anchor", $.proxy(function (e) {
4065						if(this._data.contextmenu.visible) {
4066							$.vakata.context.hide();
4067						}
4068					}, this));
4069			/*
4070			if(!('oncontextmenu' in document.body) && ('ontouchstart' in document.body)) {
4071				var el = null, tm = null;
4072				this.element
4073					.on("touchstart", ".jstree-anchor", function (e) {
4074						el = e.currentTarget;
4075						tm = +new Date();
4076						$(document).one("touchend", function (e) {
4077							e.target = document.elementFromPoint(e.originalEvent.targetTouches[0].pageX - window.pageXOffset, e.originalEvent.targetTouches[0].pageY - window.pageYOffset);
4078							e.currentTarget = e.target;
4079							tm = ((+(new Date())) - tm);
4080							if(e.target === el && tm > 600 && tm < 1000) {
4081								e.preventDefault();
4082								$(el).trigger('contextmenu', e);
4083							}
4084							el = null;
4085							tm = null;
4086						});
4087					});
4088			}
4089			*/
4090			$(document).on("context_hide.vakata", $.proxy(function () { this._data.contextmenu.visible = false; }, this));
4091		};
4092		this.teardown = function () {
4093			if(this._data.contextmenu.visible) {
4094				$.vakata.context.hide();
4095			}
4096			parent.teardown.call(this);
4097		};
4098
4099		/**
4100		 * prepare and show the context menu for a node
4101		 * @name show_contextmenu(obj [, x, y])
4102		 * @param {mixed} obj the node
4103		 * @param {Number} x the x-coordinate relative to the document to show the menu at
4104		 * @param {Number} y the y-coordinate relative to the document to show the menu at
4105		 * @param {Object} e the event if available that triggered the contextmenu
4106		 * @plugin contextmenu
4107		 * @trigger show_contextmenu.jstree
4108		 */
4109		this.show_contextmenu = function (obj, x, y, e) {
4110			obj = this.get_node(obj);
4111			if(!obj || obj.id === '#') { return false; }
4112			var s = this.settings.contextmenu,
4113				d = this.get_node(obj, true),
4114				a = d.children(".jstree-anchor"),
4115				o = false,
4116				i = false;
vendor: 11,150 bytes, lines 4117-4424
4117			if(s.show_at_node || x === undefined || y === undefined) {
4118				o = a.offset();
4119				x = o.left;
4120				y = o.top + this._data.core.li_height;
4121			}
4122			if(this.settings.contextmenu.select_node && !this.is_selected(obj)) {
4123				this.deselect_all();
4124				this.select_node(obj, false, false, e);
4125			}
4126
4127			i = s.items;
4128			if($.isFunction(i)) {
4129				i = i.call(this, obj, $.proxy(function (i) {
4130					this._show_contextmenu(obj, x, y, i);
4131				}, this));
4132			}
4133			if($.isPlainObject(i)) {
4134				this._show_contextmenu(obj, x, y, i);
4135			}
4136		};
4137		/**
4138		 * show the prepared context menu for a node
4139		 * @name _show_contextmenu(obj, x, y, i)
4140		 * @param {mixed} obj the node
4141		 * @param {Number} x the x-coordinate relative to the document to show the menu at
4142		 * @param {Number} y the y-coordinate relative to the document to show the menu at
4143		 * @param {Number} i the object of items to show
4144		 * @plugin contextmenu
4145		 * @trigger show_contextmenu.jstree
4146		 * @private
4147		 */
4148		this._show_contextmenu = function (obj, x, y, i) {
4149			var d = this.get_node(obj, true),
4150				a = d.children(".jstree-anchor");
4151			$(document).one("context_show.vakata", $.proxy(function (e, data) {
4152				var cls = 'jstree-contextmenu jstree-' + this.get_theme() + '-contextmenu';
4153				$(data.element).addClass(cls);
4154			}, this));
4155			this._data.contextmenu.visible = true;
4156			$.vakata.context.show(a, { 'x' : x, 'y' : y }, i);
4157			/**
4158			 * triggered when the contextmenu is shown for a node
4159			 * @event
4160			 * @name show_contextmenu.jstree
4161			 * @param {Object} node the node
4162			 * @param {Number} x the x-coordinate of the menu relative to the document
4163			 * @param {Number} y the y-coordinate of the menu relative to the document
4164			 * @plugin contextmenu
4165			 */
4166			this.trigger('show_contextmenu', { "node" : obj, "x" : x, "y" : y });
4167		};
4168	};
4169
4170	// contextmenu helper
4171	(function ($) {
4172		var right_to_left = false,
4173			vakata_context = {
4174				element		: false,
4175				reference	: false,
4176				position_x	: 0,
4177				position_y	: 0,
4178				items		: [],
4179				html		: "",
4180				is_visible	: false
4181			};
4182
4183		$.vakata.context = {
4184			settings : {
4185				hide_onmouseleave	: 0,
4186				icons				: true
4187			},
4188			_trigger : function (event_name) {
4189				$(document).triggerHandler("context_" + event_name + ".vakata", {
4190					"reference"	: vakata_context.reference,
4191					"element"	: vakata_context.element,
4192					"position"	: {
4193						"x" : vakata_context.position_x,
4194						"y" : vakata_context.position_y
4195					}
4196				});
4197			},
4198			_execute : function (i) {
4199				i = vakata_context.items[i];
4200				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, {
4201							"item"		: i,
4202							"reference"	: vakata_context.reference,
4203							"element"	: vakata_context.element,
4204							"position"	: {
4205								"x" : vakata_context.position_x,
4206								"y" : vakata_context.position_y
4207							}
4208						}) : false;
4209			},
4210			_parse : function (o, is_callback) {
4211				if(!o) { return false; }
4212				if(!is_callback) {
4213					vakata_context.html		= "";
4214					vakata_context.items	= [];
4215				}
4216				var str = "",
4217					sep = false,
4218					tmp;
4219
4220				if(is_callback) { str += "<"+"ul>"; }
4221				$.each(o, function (i, val) {
4222					if(!val) { return true; }
4223					vakata_context.items.push(val);
4224					if(!sep && val.separator_before) {
4225						str += "<"+"li class='vakata-context-separator'><"+"a href='#' " + ($.vakata.context.settings.icons ? '' : 'style="margin-left:0px;"') + ">&#160;<"+"/a><"+"/li>";
4226					}
4227					sep = false;
4228					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+"' ":'')+">";
4229					str += "<"+"a href='#' rel='" + (vakata_context.items.length - 1) + "'>";
4230					if($.vakata.context.settings.icons) {
4231						str += "<"+"i ";
4232						if(val.icon) {
4233							if(val.icon.indexOf("/") !== -1 || val.icon.indexOf(".") !== -1) { str += " style='background:url(\"" + val.icon + "\") center center no-repeat' "; }
4234							else { str += " class='" + val.icon + "' "; }
4235						}
4236						str += "><"+"/i><"+"span class='vakata-contextmenu-sep'>&#160;<"+"/span>";
4237					}
4238					str += val.label + (val.shortcut?' <span class="vakata-contextmenu-shortcut vakata-contextmenu-shortcut-'+val.shortcut+'">'+ (val.shortcut_label || '') +'</span>':'') + "<"+"/a>";
4239					if(val.submenu) {
4240						tmp = $.vakata.context._parse(val.submenu, true);
4241						if(tmp) { str += tmp; }
4242					}
4243					str += "<"+"/li>";
4244					if(val.separator_after) {
4245						str += "<"+"li class='vakata-context-separator'><"+"a href='#' " + ($.vakata.context.settings.icons ? '' : 'style="margin-left:0px;"') + ">&#160;<"+"/a><"+"/li>";
4246						sep = true;
4247					}
4248				});
4249				str  = str.replace(/<li class\='vakata-context-separator'\><\/li\>$/,"");
4250				if(is_callback) { str += "</ul>"; }
4251				/**
4252				 * triggered on the document when the contextmenu is parsed (HTML is built)
4253				 * @event
4254				 * @plugin contextmenu
4255				 * @name context_parse.vakata
4256				 * @param {jQuery} reference the element that was right clicked
4257				 * @param {jQuery} element the DOM element of the menu itself
4258				 * @param {Object} position the x & y coordinates of the menu
4259				 */
4260				if(!is_callback) { vakata_context.html = str; $.vakata.context._trigger("parse"); }
4261				return str.length > 10 ? str : false;
4262			},
4263			_show_submenu : function (o) {
4264				o = $(o);
4265				if(!o.length || !o.children("ul").length) { return; }
4266				var e = o.children("ul"),
4267					x = o.offset().left + o.outerWidth(),
4268					y = o.offset().top,
4269					w = e.width(),
4270					h = e.height(),
4271					dw = $(window).width() + $(window).scrollLeft(),
4272					dh = $(window).height() + $(window).scrollTop();
4273				// може да се спести е една проверка - дали няма някой от класовете вече нагоре
4274				if(right_to_left) {
4275					o[x - (w + 10 + o.outerWidth()) < 0 ? "addClass" : "removeClass"]("vakata-context-left");
4276				}
4277				else {
4278					o[x + w + 10 > dw ? "addClass" : "removeClass"]("vakata-context-right");
4279				}
4280				if(y + h + 10 > dh) {
4281					e.css("bottom","-1px");
4282				}
4283				e.show();
4284			},
4285			show : function (reference, position, data) {
4286				var o, e, x, y, w, h, dw, dh, cond = true;
4287				if(vakata_context.element && vakata_context.element.length) {
4288					vakata_context.element.width('');
4289				}
4290				switch(cond) {
4291					case (!position && !reference):
4292						return false;
4293					case (!!position && !!reference):
4294						vakata_context.reference	= reference;
4295						vakata_context.position_x	= position.x;
4296						vakata_context.position_y	= position.y;
4297						break;
4298					case (!position && !!reference):
4299						vakata_context.reference	= reference;
4300						o = reference.offset();
4301						vakata_context.position_x	= o.left + reference.outerHeight();
4302						vakata_context.position_y	= o.top;
4303						break;
4304					case (!!position && !reference):
4305						vakata_context.position_x	= position.x;
4306						vakata_context.position_y	= position.y;
4307						break;
4308				}
4309				if(!!reference && !data && $(reference).data('vakata_contextmenu')) {
4310					data = $(reference).data('vakata_contextmenu');
4311				}
4312				if($.vakata.context._parse(data)) {
4313					vakata_context.element.html(vakata_context.html);
4314				}
4315				if(vakata_context.items.length) {
4316					e = vakata_context.element;
4317					x = vakata_context.position_x;
4318					y = vakata_context.position_y;
4319					w = e.width();
4320					h = e.height();
4321					dw = $(window).width() + $(window).scrollLeft();
4322					dh = $(window).height() + $(window).scrollTop();
4323					if(right_to_left) {
4324						x -= e.outerWidth();
4325						if(x < $(window).scrollLeft() + 20) {
4326							x = $(window).scrollLeft() + 20;
4327						}
4328					}
4329					if(x + w + 20 > dw) {
4330						x = dw - (w + 20);
4331					}
4332					if(y + h + 20 > dh) {
4333						y = dh - (h + 20);
4334					}
4335
4336					vakata_context.element
4337						.css({ "left" : x, "top" : y })
4338						.show()
4339						.find('a:eq(0)').focus().parent().addClass("vakata-context-hover");
4340					vakata_context.is_visible = true;
4341					/**
4342					 * triggered on the document when the contextmenu is shown
4343					 * @event
4344					 * @plugin contextmenu
4345					 * @name context_show.vakata
4346					 * @param {jQuery} reference the element that was right clicked
4347					 * @param {jQuery} element the DOM element of the menu itself
4348					 * @param {Object} position the x & y coordinates of the menu
4349					 */
4350					$.vakata.context._trigger("show");
4351				}
4352			},
4353			hide : function () {
4354				if(vakata_context.is_visible) {
4355					vakata_context.element.hide().find("ul").hide().end().find(':focus').blur();
4356					vakata_context.is_visible = false;
4357					/**
4358					 * triggered on the document when the contextmenu is hidden
4359					 * @event
4360					 * @plugin contextmenu
4361					 * @name context_hide.vakata
4362					 * @param {jQuery} reference the element that was right clicked
4363					 * @param {jQuery} element the DOM element of the menu itself
4364					 * @param {Object} position the x & y coordinates of the menu
4365					 */
4366					$.vakata.context._trigger("hide");
4367				}
4368			}
4369		};
4370		$(function () {
4371			right_to_left = $("body").css("direction") === "rtl";
4372			var to = false;
4373
4374			vakata_context.element = $("<ul class='vakata-context'></ul>");
4375			vakata_context.element
4376				.on("mouseenter", "li", function (e) {
4377					e.stopImmediatePropagation();
4378
4379					if($.contains(this, e.relatedTarget)) {
4380						// премахнато заради delegate mouseleave по-долу
4381						// $(this).find(".vakata-context-hover").removeClass("vakata-context-hover");
4382						return;
4383					}
4384
4385					if(to) { clearTimeout(to); }
4386					vakata_context.element.find(".vakata-context-hover").removeClass("vakata-context-hover").end();
4387
4388					$(this)
4389						.siblings().find("ul").hide().end().end()
4390						.parentsUntil(".vakata-context", "li").addBack().addClass("vakata-context-hover");
4391					$.vakata.context._show_submenu(this);
4392				})
4393				// тестово - дали не натоварва?
4394				.on("mouseleave", "li", function (e) {
4395					if($.contains(this, e.relatedTarget)) { return; }
4396					$(this).find(".vakata-context-hover").addBack().removeClass("vakata-context-hover");
4397				})
4398				.on("mouseleave", function (e) {
4399					$(this).find(".vakata-context-hover").removeClass("vakata-context-hover");
4400					if($.vakata.context.settings.hide_onmouseleave) {
4401						to = setTimeout(
4402							(function (t) {
4403								return function () { $.vakata.context.hide(); };
4404							}(this)), $.vakata.context.settings.hide_onmouseleave);
4405					}
4406				})
4407				.on("click", "a", function (e) {
4408					e.preventDefault();
4409				})
4410				.on("mouseup", "a", function (e) {
4411					if(!$(this).blur().parent().hasClass("vakata-context-disabled") && $.vakata.context._execute($(this).attr("rel")) !== false) {
4412						$.vakata.context.hide();
4413					}
4414				})
4415				.on('keydown', 'a', function (e) {
4416						var o = null;
4417						switch(e.which) {
4418							case 13:
4419							case 32:
4420								e.type = "mouseup";
4421								e.preventDefault();
4422								$(e.currentTarget).trigger(e);
4423								break;
4424							case 37:
4425								if(vakata_context.is_visible) {
4426									vakata_context.element.find(".vakata-context-hover").last().parents("li:eq(0)").find("ul").hide().find(".vakata-context-hover").removeClass("vakata-context-hover").end().end().children('a').focus();
4427									e.stopImmediatePropagation();
4428									e.preventDefault();
4429								}
4430								break;
4431							case 38:
4432								if(vakata_context.is_visible) {
4433									o = vakata_context.element.find("ul:visible").addBack().last().children(".vakata-context-hover").removeClass("vakata-context-hover").prevAll("li:not(.vakata-context-separator)").first();
4434									if(!o.length) { o = vakata_context.element.find("ul:visible").addBack().last().children("li:not(.vakata-context-separator)").last(); }
4435									o.addClass("vakata-context-hover").children('a').focus();
4436									e.stopImmediatePropagation();
4437									e.preventDefault();
4438								}
4439								break;
4440							case 39:
4441								if(vakata_context.is_visible) {
4442									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();
4443									e.stopImmediatePropagation();
4444									e.preventDefault();
4445								}
4446								break;
4447							case 40:
4448								if(vakata_context.is_visible) {
4449									o = vakata_context.element.find("ul:visible").addBack().last().children(".vakata-context-hover").removeClass("vakata-context-hover").nextAll("li:not(.vakata-context-separator)").first();
4450									if(!o.length) { o = vakata_context.element.find("ul:visible").addBack().last().children("li:not(.vakata-context-separator)").first(); }
4451									o.addClass("vakata-context-hover").children('a').focus();
4452									e.stopImmediatePropagation();
4453									e.preventDefault();
4454								}
4455								break;
4456							case 27:
4457								$.vakata.context.hide();
4458								e.preventDefault();
4459								break;
4460							default:
4461								//console.log(e.which);
4462								break;
4463						}
4464					})
4465				.on('keydown', function (e) {
4466					e.preventDefault();
4467					var a = vakata_context.element.find('.vakata-contextmenu-shortcut-' + e.which).parent();
4468					if(a.parent().not('.vakata-context-disabled')) {
4469						a.mouseup();
4470					}
4471				})
4472				.appendTo("body");
4473
4474			$(document)
4475				.on("mousedown", function (e) {
4476					if(vakata_context.is_visible && !$.contains(vakata_context.element[0], e.target)) { $.vakata.context.hide(); }
4477				})
4478				.on("context_show.vakata", function (e, data) {
4479					vakata_context.element.find("li:has(ul)").children("a").addClass("vakata-context-parent");
4480					if(right_to_left) {
4481						vakata_context.element.addClass("vakata-context-rtl").css("direction", "rtl");
4482					}
4483					// also apply a RTL class?
4484					vakata_context.element.find("ul").hide().end();
4485				});
4486		});
4487	}($));
4488	// $.jstree.defaults.plugins.push("contextmenu");
4489
4490/**
4491 * ### Drag'n'drop plugin
4492 *
4493 * Enables dragging and dropping of nodes in the tree, resulting in a move or copy operations.
4494 */
4495
4496	/**
4497	 * stores all defaults for the drag'n'drop plugin
4498	 * @name $.jstree.defaults.dnd
4499	 * @plugin dnd
4500	 */
4501	$.jstree.defaults.dnd = {
4502		/**
4503		 * a boolean indicating if a copy should be possible while dragging (by pressint the meta key or Ctrl). Defaults to `true`.
4504		 * @name $.jstree.defaults.dnd.copy
4505		 * @plugin dnd
4506		 */
4507		copy : true,
4508		/**
4509		 * a number indicating how long a node should remain hovered while dragging to be opened. Defaults to `500`.
4510		 * @name $.jstree.defaults.dnd.open_timeout
4511		 * @plugin dnd
4512		 */
4513		open_timeout : 500,
4514		/**
4515		 * a function invoked each time a node is about to be dragged, invoked in the tree's scope and receives the node as an argument - return `false` to prevent dragging
4516		 * @name $.jstree.defaults.dnd.is_draggable
4517		 * @plugin dnd
4518		 */
4519		is_draggable : true,
4520		/**
4521		 * 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`
4522		 * @name $.jstree.defaults.dnd.check_while_dragging
4523		 * @plugin dnd
4524		 */
4525		check_while_dragging : true
4526	};
4527	// TODO: now check works by checking for each node individually, how about max_children, unique, etc?
4528	// TODO: drop somewhere else - maybe demo only?
4529	$.jstree.plugins.dnd = function (options, parent) {
4530		this.bind = function () {
4531			parent.bind.call(this);
4532
4533			this.element
4534				.on('mousedown touchstart', '.jstree-anchor', $.proxy(function (e) {
4535					var obj = this.get_node(e.target),
4536						mlt = this.is_selected(obj) ? this.get_selected().length : 1;
4537					if(obj && obj.id && obj.id !== "#" && (e.which === 1 || e.type === "touchstart") &&
4538						(this.settings.dnd.is_draggable === true || ($.isFunction(this.settings.dnd.is_draggable) && this.settings.dnd.is_draggable.call(this, obj)))
4539					) {
4540						this.element.trigger('mousedown.jstree');
4541						return $.vakata.dnd.start(e, { 'jstree' : true, 'origin' : this, 'obj' : this.get_node(obj,true), 'nodes' : mlt > 1 ? this.get_selected() : [obj.id] }, '<div id="jstree-dnd" class="jstree-' + this.get_theme() + '"><i class="jstree-icon jstree-er"></i>' + (mlt > 1 ? mlt + ' ' + this.get_string('nodes') : this.get_text(e.currentTarget, true)) + '<ins class="jstree-copy" style="display:none;">+</ins></div>');
4542					}
4543				}, this));
4544		};
4545	};
4546
4547	$(function() {
4548		// bind only once for all instances
4549		var lastmv = false,
4550			laster = false,
4551			opento = false,
4552			marker = $('<div id="jstree-marker">&#160;</div>').hide().appendTo('body');
4553
4554		$(document)
4555			.bind('dnd_start.vakata', function (e, data) {
4556				lastmv = false;
4557			})
4558			.bind('dnd_move.vakata', function (e, data) {
4559				if(opento) { clearTimeout(opento); }
4560				if(!data.data.jstree) { return; }
4561
4562				// if we are hovering the marker image do nothing (can happen on "inside" drags)
4563				if(data.event.target.id && data.event.target.id === 'jstree-marker') {
4564					return;
4565				}
4566
4567				var ins = $.jstree.reference(data.event.target),
4568					ref = false,
4569					off = false,
4570					rel = false,
4571					l, t, h, p, i, o, ok, t1, t2, op, ps, pr;
4572				// if we are over an instance
4573				if(ins && ins._data && ins._data.dnd) {
4574					marker.attr('class', 'jstree-' + ins.get_theme());
4575					data.helper
4576						.children().attr('class', 'jstree-' + ins.get_theme())
4577						.find('.jstree-copy:eq(0)')[ data.data.origin && data.data.origin.settings.dnd.copy && (data.event.metaKey || data.event.ctrlKey) ? 'show' : 'hide' ]();
4578
4579
4580					// if are hovering the container itself add a new root node
4581					if( (data.event.target === ins.element[0] || data.event.target === ins.get_container_ul()[0]) && ins.get_container_ul().children().length === 0) {
4582						ok = true;
4583						for(t1 = 0, t2 = data.data.nodes.length; t1 < t2; t1++) {
4584							ok = ok && ins.check( (data.data.origin && data.data.origin.settings.dnd.copy && (data.event.metaKey || data.event.ctrlKey) ? "copy_node" : "move_node"), (data.data.origin && data.data.origin !== ins ? data.data.origin.get_node(data.data.nodes[t1]) : data.data.nodes[t1]), '#', 'last');
4585							if(!ok) { break; }
4586						}
4587						if(ok) {
4588							lastmv = { 'ins' : ins, 'par' : '#', 'pos' : 'last' };
4589							marker.hide();
4590							data.helper.find('.jstree-icon:eq(0)').removeClass('jstree-er').addClass('jstree-ok');
4591							return;
4592						}
4593					}
4594					else {
4595						// if we are hovering a tree node
4596						ref = $(data.event.target).closest('a');
4597						if(ref && ref.length && ref.parent().is('.jstree-closed, .jstree-open, .jstree-leaf')) {
4598							off = ref.offset();
4599							rel = data.event.pageY - off.top;
4600							h = ref.height();
4601							if(rel < h / 3) {
4602								o = ['b', 'i', 'a'];
4603							}
4604							else if(rel > h - h / 3) {
4605								o = ['a', 'i', 'b'];
4606							}
4607							else {
4608								o = rel > h / 2 ? ['i', 'a', 'b'] : ['i', 'b', 'a'];
4609							}
4610							$.each(o, function (j, v) {
4611								switch(v) {
4612									case 'b':
4613										l = off.left - 6;
4614										t = off.top - 5;
4615										p = ins.get_parent(ref);
4616										i = ref.parent().index();
4617										break;
4618									case 'i':
4619										l = off.left - 2;
4620										t = off.top - 5 + h / 2 + 1;
4621										p = ref.parent();
4622										i = 0;
4623										break;
4624									case 'a':
4625										l = off.left - 6;
4626										t = off.top - 5 + h;
4627										p = ins.get_parent(ref);
4628										i = ref.parent().index() + 1;
4629										break;
4630								}
4631								/*!
4632								// TODO: moving inside, but the node is not yet loaded?
4633								// the check will work anyway, as when moving the node will be loaded first and checked again
4634								if(v === 'i' && !ins.is_loaded(p)) { }
4635								*/
4636								ok = true;
4637								for(t1 = 0, t2 = data.data.nodes.length; t1 < t2; t1++) {
4638									op = data.data.origin && data.data.origin.settings.dnd.copy && (data.event.metaKey || data.event.ctrlKey) ? "copy_node" : "move_node";
4639									ps = i;
4640									if(op === "move_node" && v === 'a' && (data.data.origin && data.data.origin === ins) && p === ins.get_parent(data.data.nodes[t1])) {
4641										pr = ins.get_node(p);
4642										if(ps > $.inArray(data.data.nodes[t1], pr.children)) {
4643											ps -= 1;
4644										}
4645									}
4646									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) );
4647									if(!ok) {
4648										if(ins && ins.last_error) { laster = ins.last_error(); }
4649										break;
4650									}
4651								}
4652								if(ok) {
4653									if(v === 'i' && ref.parent().is('.jstree-closed') && ins.settings.dnd.open_timeout) {
4654										opento = setTimeout((function (x, z) { return function () { x.open_node(z); }; }(ins, ref)), ins.settings.dnd.open_timeout);
4655									}
4656									lastmv = { 'ins' : ins, 'par' : p, 'pos' : i };
4657									marker.css({ 'left' : l + 'px', 'top' : t + 'px' }).show();
4658									data.helper.find('.jstree-icon:eq(0)').removeClass('jstree-er').addClass('jstree-ok');
4659									laster = {};
4660									o = true;
4661									return false;
vendor: 11,290 bytes, lines 4662-4969
4662								}
4663							});
4664							if(o === true) { return; }
4665						}
4666					}
4667				}
4668				lastmv = false;
4669				data.helper.find('.jstree-icon').removeClass('jstree-ok').addClass('jstree-er');
4670				marker.hide();
4671			})
4672			.bind('dnd_scroll.vakata', function (e, data) {
4673				if(!data.data.jstree) { return; }
4674				marker.hide();
4675				lastmv = false;
4676				data.helper.find('.jstree-icon:eq(0)').removeClass('jstree-ok').addClass('jstree-er');
4677			})
4678			.bind('dnd_stop.vakata', function (e, data) {
4679				if(opento) { clearTimeout(opento); }
4680				if(!data.data.jstree) { return; }
4681				marker.hide();
4682				var i, j, nodes = [];
4683				if(lastmv) {
4684					for(i = 0, j = data.data.nodes.length; i < j; i++) {
4685						nodes[i] = data.data.origin ? data.data.origin.get_node(data.data.nodes[i]) : data.data.nodes[i];
4686					}
4687					lastmv.ins[ data.data.origin && data.data.origin.settings.dnd.copy && (data.event.metaKey || data.event.ctrlKey) ? 'copy_node' : 'move_node' ](nodes, lastmv.par, lastmv.pos);
4688				}
4689				else {
4690					i = $(data.event.target).closest('.jstree');
4691					if(i.length && laster && laster.error && laster.error === 'check') {
4692						i = i.jstree(true);
4693						if(i) {
4694							i.settings.core.error.call(this, laster);
4695						}
4696					}
4697				}
4698			})
4699			.bind('keyup keydown', function (e, data) {
4700				data = $.vakata.dnd._get();
4701				if(data.data && data.data.jstree) {
4702					data.helper.find('.jstree-copy:eq(0)')[ data.data.origin && data.data.origin.settings.dnd.copy && (e.metaKey || e.ctrlKey) ? 'show' : 'hide' ]();
4703				}
4704			});
4705	});
4706
4707	// helpers
4708	(function ($) {
4709		$.fn.vakata_reverse = [].reverse;
4710		// private variable
4711		var vakata_dnd = {
4712			element	: false,
4713			is_down	: false,
4714			is_drag	: false,
4715			helper	: false,
4716			helper_w: 0,
4717			data	: false,
4718			init_x	: 0,
4719			init_y	: 0,
4720			scroll_l: 0,
4721			scroll_t: 0,
4722			scroll_e: false,
4723			scroll_i: false
4724		};
4725		$.vakata.dnd = {
4726			settings : {
4727				scroll_speed		: 10,
4728				scroll_proximity	: 20,
4729				helper_left			: 5,
4730				helper_top			: 10,
4731				threshold			: 5
4732			},
4733			_trigger : function (event_name, e) {
4734				var data = $.vakata.dnd._get();
4735				data.event = e;
4736				$(document).triggerHandler("dnd_" + event_name + ".vakata", data);
4737			},
4738			_get : function () {
4739				return {
4740					"data"		: vakata_dnd.data,
4741					"element"	: vakata_dnd.element,
4742					"helper"	: vakata_dnd.helper
4743				};
4744			},
4745			_clean : function () {
4746				if(vakata_dnd.helper) { vakata_dnd.helper.remove(); }
4747				if(vakata_dnd.scroll_i) { clearInterval(vakata_dnd.scroll_i); vakata_dnd.scroll_i = false; }
4748				vakata_dnd = {
4749					element	: false,
4750					is_down	: false,
4751					is_drag	: false,
4752					helper	: false,
4753					helper_w: 0,
4754					data	: false,
4755					init_x	: 0,
4756					init_y	: 0,
4757					scroll_l: 0,
4758					scroll_t: 0,
4759					scroll_e: false,
4760					scroll_i: false
4761				};
4762				$(document).off("mousemove touchmove", $.vakata.dnd.drag);
4763				$(document).off("mouseup touchend", $.vakata.dnd.stop);
4764			},
4765			_scroll : function (init_only) {
4766				if(!vakata_dnd.scroll_e || (!vakata_dnd.scroll_l && !vakata_dnd.scroll_t)) {
4767					if(vakata_dnd.scroll_i) { clearInterval(vakata_dnd.scroll_i); vakata_dnd.scroll_i = false; }
4768					return false;
4769				}
4770				if(!vakata_dnd.scroll_i) {
4771					vakata_dnd.scroll_i = setInterval($.vakata.dnd._scroll, 100);
4772					return false;
4773				}
4774				if(init_only === true) { return false; }
4775
4776				var i = vakata_dnd.scroll_e.scrollTop(),
4777					j = vakata_dnd.scroll_e.scrollLeft();
4778				vakata_dnd.scroll_e.scrollTop(i + vakata_dnd.scroll_t * $.vakata.dnd.settings.scroll_speed);
4779				vakata_dnd.scroll_e.scrollLeft(j + vakata_dnd.scroll_l * $.vakata.dnd.settings.scroll_speed);
4780				if(i !== vakata_dnd.scroll_e.scrollTop() || j !== vakata_dnd.scroll_e.scrollLeft()) {
4781					/**
4782					 * triggered on the document when a drag causes an element to scroll
4783					 * @event
4784					 * @plugin dnd
4785					 * @name dnd_scroll.vakata
4786					 * @param {Mixed} data any data supplied with the call to $.vakata.dnd.start
4787					 * @param {DOM} element the DOM element being dragged
4788					 * @param {jQuery} helper the helper shown next to the mouse
4789					 * @param {jQuery} event the element that is scrolling
4790					 */
4791					$.vakata.dnd._trigger("scroll", vakata_dnd.scroll_e);
4792				}
4793			},
4794			start : function (e, data, html) {
4795				if(e.type === "touchstart" && e.originalEvent && e.originalEvent.changedTouches && e.originalEvent.changedTouches[0]) {
4796					e.pageX = e.originalEvent.changedTouches[0].pageX;
4797					e.pageY = e.originalEvent.changedTouches[0].pageY;
4798					e.target = document.elementFromPoint(e.originalEvent.changedTouches[0].pageX - window.pageXOffset, e.originalEvent.changedTouches[0].pageY - window.pageYOffset);
4799				}
4800				if(vakata_dnd.is_drag) { $.vakata.dnd.stop({}); }
4801				try {
4802					e.currentTarget.unselectable = "on";
4803					e.currentTarget.onselectstart = function() { return false; };
4804					if(e.currentTarget.style) { e.currentTarget.style.MozUserSelect = "none"; }
4805				} catch(ignore) { }
4806				vakata_dnd.init_x	= e.pageX;
4807				vakata_dnd.init_y	= e.pageY;
4808				vakata_dnd.data		= data;
4809				vakata_dnd.is_down	= true;
4810				vakata_dnd.element	= e.currentTarget;
4811				if(html !== false) {
4812					vakata_dnd.helper = $("<div id='vakata-dnd'></div>").html(html).css({
4813						"display"		: "block",
4814						"margin"		: "0",
4815						"padding"		: "0",
4816						"position"		: "absolute",
4817						"top"			: "-2000px",
4818						"lineHeight"	: "16px",
4819						"zIndex"		: "10000"
4820					});
4821				}
4822				$(document).bind("mousemove touchmove", $.vakata.dnd.drag);
4823				$(document).bind("mouseup touchend", $.vakata.dnd.stop);
4824				return false;
4825			},
4826			drag : function (e) {
4827				if(e.type === "touchmove" && e.originalEvent && e.originalEvent.changedTouches && e.originalEvent.changedTouches[0]) {
4828					e.pageX = e.originalEvent.changedTouches[0].pageX;
4829					e.pageY = e.originalEvent.changedTouches[0].pageY;
4830					e.target = document.elementFromPoint(e.originalEvent.changedTouches[0].pageX - window.pageXOffset, e.originalEvent.changedTouches[0].pageY - window.pageYOffset);
4831				}
4832				if(!vakata_dnd.is_down) { return; }
4833				if(!vakata_dnd.is_drag) {
4834					if(
4835						Math.abs(e.pageX - vakata_dnd.init_x) > $.vakata.dnd.settings.threshold ||
4836						Math.abs(e.pageY - vakata_dnd.init_y) > $.vakata.dnd.settings.threshold
4837					) {
4838						if(vakata_dnd.helper) {
4839							vakata_dnd.helper.appendTo("body");
4840							vakata_dnd.helper_w = vakata_dnd.helper.outerWidth();
4841						}
4842						vakata_dnd.is_drag = true;
4843						/**
4844						 * triggered on the document when a drag starts
4845						 * @event
4846						 * @plugin dnd
4847						 * @name dnd_start.vakata
4848						 * @param {Mixed} data any data supplied with the call to $.vakata.dnd.start
4849						 * @param {DOM} element the DOM element being dragged
4850						 * @param {jQuery} helper the helper shown next to the mouse
4851						 * @param {Object} event the event that caused the start (probably mousemove)
4852						 */
4853						$.vakata.dnd._trigger("start", e);
4854					}
4855					else { return; }
4856				}
4857
4858				var d  = false, w  = false,
4859					dh = false, wh = false,
4860					dw = false, ww = false,
4861					dt = false, dl = false,
4862					ht = false, hl = false;
4863
4864				vakata_dnd.scroll_t = 0;
4865				vakata_dnd.scroll_l = 0;
4866				vakata_dnd.scroll_e = false;
4867				$(e.target)
4868					.parentsUntil("body").addBack().vakata_reverse()
4869					.filter(function () {
4870						return	(/^auto|scroll$/).test($(this).css("overflow")) &&
4871								(this.scrollHeight > this.offsetHeight || this.scrollWidth > this.offsetWidth);
4872					})
4873					.each(function () {
4874						var t = $(this), o = t.offset();
4875						if(this.scrollHeight > this.offsetHeight) {
4876							if(o.top + t.height() - e.pageY < $.vakata.dnd.settings.scroll_proximity)	{ vakata_dnd.scroll_t = 1; }
4877							if(e.pageY - o.top < $.vakata.dnd.settings.scroll_proximity)				{ vakata_dnd.scroll_t = -1; }
4878						}
4879						if(this.scrollWidth > this.offsetWidth) {
4880							if(o.left + t.width() - e.pageX < $.vakata.dnd.settings.scroll_proximity)	{ vakata_dnd.scroll_l = 1; }
4881							if(e.pageX - o.left < $.vakata.dnd.settings.scroll_proximity)				{ vakata_dnd.scroll_l = -1; }
4882						}
4883						if(vakata_dnd.scroll_t || vakata_dnd.scroll_l) {
4884							vakata_dnd.scroll_e = $(this);
4885							return false;
4886						}
4887					});
4888
4889				if(!vakata_dnd.scroll_e) {
4890					d  = $(document); w = $(window);
4891					dh = d.height(); wh = w.height();
4892					dw = d.width(); ww = w.width();
4893					dt = d.scrollTop(); dl = d.scrollLeft();
4894					if(dh > wh && e.pageY - dt < $.vakata.dnd.settings.scroll_proximity)		{ vakata_dnd.scroll_t = -1;  }
4895					if(dh > wh && wh - (e.pageY - dt) < $.vakata.dnd.settings.scroll_proximity)	{ vakata_dnd.scroll_t = 1; }
4896					if(dw > ww && e.pageX - dl < $.vakata.dnd.settings.scroll_proximity)		{ vakata_dnd.scroll_l = -1; }
4897					if(dw > ww && ww - (e.pageX - dl) < $.vakata.dnd.settings.scroll_proximity)	{ vakata_dnd.scroll_l = 1; }
4898					if(vakata_dnd.scroll_t || vakata_dnd.scroll_l) {
4899						vakata_dnd.scroll_e = d;
4900					}
4901				}
4902				if(vakata_dnd.scroll_e) { $.vakata.dnd._scroll(true); }
4903
4904				if(vakata_dnd.helper) {
4905					ht = parseInt(e.pageY + $.vakata.dnd.settings.helper_top, 10);
4906					hl = parseInt(e.pageX + $.vakata.dnd.settings.helper_left, 10);
4907					if(dh && ht + 25 > dh) { ht = dh - 50; }
4908					if(dw && hl + vakata_dnd.helper_w > dw) { hl = dw - (vakata_dnd.helper_w + 2); }
4909					vakata_dnd.helper.css({
4910						left	: hl + "px",
4911						top		: ht + "px"
4912					});
4913				}
4914				/**
4915				 * triggered on the document when a drag is in progress
4916				 * @event
4917				 * @plugin dnd
4918				 * @name dnd_move.vakata
4919				 * @param {Mixed} data any data supplied with the call to $.vakata.dnd.start
4920				 * @param {DOM} element the DOM element being dragged
4921				 * @param {jQuery} helper the helper shown next to the mouse
4922				 * @param {Object} event the event that caused this to trigger (most likely mousemove)
4923				 */
4924				$.vakata.dnd._trigger("move", e);
4925			},
4926			stop : function (e) {
4927				if(e.type === "touchend" && e.originalEvent && e.originalEvent.changedTouches && e.originalEvent.changedTouches[0]) {
4928					e.pageX = e.originalEvent.changedTouches[0].pageX;
4929					e.pageY = e.originalEvent.changedTouches[0].pageY;
4930					e.target = document.elementFromPoint(e.originalEvent.changedTouches[0].pageX - window.pageXOffset, e.originalEvent.changedTouches[0].pageY - window.pageYOffset);
4931				}
4932				if(vakata_dnd.is_drag) {
4933					/**
4934					 * triggered on the document when a drag stops (the dragged element is dropped)
4935					 * @event
4936					 * @plugin dnd
4937					 * @name dnd_stop.vakata
4938					 * @param {Mixed} data any data supplied with the call to $.vakata.dnd.start
4939					 * @param {DOM} element the DOM element being dragged
4940					 * @param {jQuery} helper the helper shown next to the mouse
4941					 * @param {Object} event the event that caused the stop
4942					 */
4943					$.vakata.dnd._trigger("stop", e);
4944				}
4945				$.vakata.dnd._clean();
4946			}
4947		};
4948	}(jQuery));
4949
4950	// include the dnd plugin by default
4951	// $.jstree.defaults.plugins.push("dnd");
4952
4953
4954/**
4955 * ### Search plugin
4956 *
4957 * Adds search functionality to jsTree.
4958 */
4959
4960	/**
4961	 * stores all defaults for the search plugin
4962	 * @name $.jstree.defaults.search
4963	 * @plugin search
4964	 */
4965	$.jstree.defaults.search = {
4966		/**
4967		 * a jQuery-like AJAX config, which jstree uses if a server should be queried for results. 
4968		 * 
4969		 * A `str` (which is the search string) parameter will be added with the request. The expected result is a JSON array with 
4969nodes that need to be opened so that matching nodes will be revealed.
4970		 * Leave this setting as `false` to not query the server.
4971		 * @name $.jstree.defaults.search.ajax
4972		 * @plugin search
4973		 */
4974		ajax : false,
4975		/**
4976		 * Indicates if the search should be fuzzy or not (should `chnd3` match `child node 3`). Default is `true`.
4977		 * @name $.jstree.defaults.search.fuzzy
4978		 * @plugin search
4979		 */
4980		fuzzy : true,
4981		/**
4982		 * Indicates if the search should be case sensitive. Default is `false`.
4983		 * @name $.jstree.defaults.search.case_sensitive
4984		 * @plugin search
4985		 */
4986		case_sensitive : false,
4987		/**
4988		 * Indicates if the tree should be filtered to show only matching nodes (keep in mind this can be a heavy on large trees in old browsers). Default is `false`.
4989		 * @name $.jstree.defaults.search.show_only_matches
4990		 * @plugin search
4991		 */
4992		show_only_matches : false,
4993		/**
4994		 * 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`.
4995		 * @name $.jstree.defaults.search.close_opened_onclear
4996		 * @plugin search
4997		 */
4998		close_opened_onclear : true
4999	};
5000
5001	$.jstree.plugins.search = function (options, parent) {
5002		this.bind = function () {
5003			parent.bind.call(this);
5004
5005			this._data.search.str = "";
5006			this._data.search.dom = $();
5007			this._data.search.res = [];
5008			this._data.search.opn = [];
5009			this._data.search.sln = null;
5010
5011			if(this.settings.search.show_only_matches) {
5012				this.element
5013					.on("search.jstree", function (e, data) {
5014						if(data.nodes.length) {
5015							$(this).find("li").hide().filter('.jstree-last').filter(function() { return this.nextSibling; }).removeClass('jstree-last');
5016							data.nodes.parentsUntil(".jstree").addBack().show()
5017								.filter("ul").each(function () { $(this).children("li:visible").eq(-1).addClass("jstree-last"); });
5018						}
5019					})
5020					.on("clear_search.jstree", function (e, data) {
5021						if(data.nodes.length) {
5022							$(this).find("li").css("display","").filter('.jstree-last').filter(function() { return this.nextSibling; }).removeClass('jstree-last');
5023						}
5024					});
5025			}
5026		};
5027		/**
5028		 * used to search the tree nodes for a given string
5029		 * @name search(str [, skip_async])
5030		 * @param {String} str the search string
5031		 * @param {Boolean} skip_async if set to true server will not be queried even if configured
5032		 * @plugin search
5033		 * @trigger search.jstree
5034		 */
5035		this.search = function (str, skip_async) {
5036			if(str === false || $.trim(str) === "") {
5037				return this.clear_search();
5038			}
5039			var s = this.settings.search,
5040				a = s.ajax ? $.extend({}, s.ajax) : false,
5041				f = null,
5042				r = [],
5043				p = [], i, j;
5044			if(this._data.search.res.length) {
5045				this.clear_search();
5046			}
5047			if(!skip_async && a !== false) {
5048				if(!a.data) { a.data = {}; }
5049				a.data.str = str;
5050				return $.ajax(a)
5051					.fail($.proxy(function () {
5052						this._data.core.last_error = { 'error' : 'ajax', 'plugin' : 'search', 'id' : 'search_01', 'reason' : 'Could not load search parents', 'data' : JSON.stringify(a) };
5053						this.settings.core.error.call(this, this._data.core.last_error);
5054					}, this))
5055					.done($.proxy(function (d) {
5056						if(d && d.d) { d = d.d; }
5057						this._data.search.sln = !$.isArray(d) ? [] : d;
5058						this._search_load(str);
5059					}, this));
5060			}
5061			this._data.search.str = str;
5062			this._data.search.dom = $();
5063			this._data.search.res = [];
5064			this._data.search.opn = [];
5065
5066			f = new $.vakata.search(str, true, { caseSensitive : s.case_sensitive, fuzzy : s.fuzzy });
5067
5068			$.each(this._model.data, function (i, v) {
5069				if(v.text && f.search(v.text).isMatch) {
5070					r.push(i);
5071					p = p.concat(v.parents);
5072				}
5073			});
5074			if(r.length) {
5075				p = $.vakata.array_unique(p);
5076				this._search_open(p);
5077				for(i = 0, j = r.length; i < j; i++) {
5078					f = this.get_node(r[i], true);
5079					if(f) {
5080						this._data.search.dom = this._data.search.dom.add(f);
5081					}
5082				}
5083				this._data.search.res = r;
5084				this._data.search.dom.children(".jstree-anchor").addClass('jstree-search');
5085			}
5086			/**
5087			 * triggered after search is complete
5088			 * @event
5089			 * @name search.jstree
5090			 * @param {jQuery}
vendor: 20,190 bytes, lines 5090-5679
5090 nodes a jQuery collection of matching nodes
5091			 * @param {String} str the search string
5092			 * @param {Array} res a collection of objects represeing the matching nodes
5093			 * @plugin search
5094			 */
5095			this.trigger('search', { nodes : this._data.search.dom, str : str, res : this._data.search.res });
5096		};
5097		/**
5098		 * used to clear the last search (removes classes and shows all nodes if filtering is on)
5099		 * @name clear_search()
5100		 * @plugin search
5101		 * @trigger clear_search.jstree
5102		 */
5103		this.clear_search = function () {
5104			this._data.search.dom.children(".jstree-anchor").removeClass("jstree-search");
5105			if(this.settings.search.close_opened_onclear) {
5106				this.close_node(this._data.search.opn, 0);
5107			}
5108			/**
5109			 * triggered after search is complete
5110			 * @event
5111			 * @name clear_search.jstree
5112			 * @param {jQuery} nodes a jQuery collection of matching nodes (the result from the last search)
5113			 * @param {String} str the search string (the last search string)
5114			 * @param {Array} res a collection of objects represeing the matching nodes (the result from the last search)
5115			 * @plugin search
5116			 */
5117			this.trigger('clear_search', { 'nodes' : this._data.search.dom, str : this._data.search.str, res : this._data.search.res });
5118			this._data.search.str = "";
5119			this._data.search.res = [];
5120			this._data.search.opn = [];
5121			this._data.search.dom = $();
5122		};
5123		/**
5124		 * opens nodes that need to be opened to reveal the search results. Used only internally.
5125		 * @private
5126		 * @name _search_open(d)
5127		 * @param {Array} d an array of node IDs
5128		 * @plugin search
5129		 */
5130		this._search_open = function (d) {
5131			var t = this;
5132			$.each(d.concat([]), function (i, v) {
5133				v = document.getElementById(v);
5134				if(v) {
5135					if(t.is_closed(v)) {
5136						t._data.search.opn.push(v.id);
5137						t.open_node(v, function () { t._search_open(d); }, 0);
5138					}
5139				}
5140			});
5141		};
5142		/**
5143		 * loads nodes that need to be opened to reveal the search results. Used only internally.
5144		 * @private
5145		 * @name _search_load(d, str)
5146		 * @param {String} str the search string
5147		 * @plugin search
5148		 */
5149		this._search_load = function (str) {
5150			var res = true,
5151				t = this,
5152				m = t._model.data;
5153			if($.isArray(this._data.search.sln)) {
5154				if(!this._data.search.sln.length) {
5155					this._data.search.sln = null;
5156					this.search(str, true);
5157				}
5158				else {
5159					$.each(this._data.search.sln, function (i, v) {
5160						if(m[v]) {
5161							$.vakata.array_remove_item(t._data.search.sln, v);
5162							if(!m[v].state.loaded) {
5163								t.load_node(v, function (o, s) { if(s) { t._search_load(str); } });
5164								res = false;
5165							}
5166						}
5167					});
5168					if(res) {
5169						this._data.search.sln = [];
5170						this._search_load(str);
5171					}
5172				}
5173			}
5174		};
5175	};
5176
5177	// helpers
5178	(function ($) {
5179		// from http://kiro.me/projects/fuse.html
5180		$.vakata.search = function(pattern, txt, options) {
5181			options = options || {};
5182			if(options.fuzzy !== false) {
5183				options.fuzzy = true;
5184			}
5185			pattern = options.caseSensitive ? pattern : pattern.toLowerCase();
5186			var MATCH_LOCATION	= options.location || 0,
5187				MATCH_DISTANCE	= options.distance || 100,
5188				MATCH_THRESHOLD	= options.threshold || 0.6,
5189				patternLen = pattern.length,
5190				matchmask, pattern_alphabet, match_bitapScore, search;
5191			if(patternLen > 32) {
5192				options.fuzzy = false;
5193			}
5194			if(options.fuzzy) {
5195				matchmask = 1 << (patternLen - 1);
5196				pattern_alphabet = (function () {
5197					var mask = {},
5198						i = 0;
5199					for (i = 0; i < patternLen; i++) {
5200						mask[pattern.charAt(i)] = 0;
5201					}
5202					for (i = 0; i < patternLen; i++) {
5203						mask[pattern.charAt(i)] |= 1 << (patternLen - i - 1);
5204					}
5205					return mask;
5206				}());
5207				match_bitapScore = function (e, x) {
5208					var accuracy = e / patternLen,
5209						proximity = Math.abs(MATCH_LOCATION - x);
5210					if(!MATCH_DISTANCE) {
5211						return proximity ? 1.0 : accuracy;
5212					}
5213					return accuracy + (proximity / MATCH_DISTANCE);
5214				};
5215			}
5216			search = function (text) {
5217				text = options.caseSensitive ? text : text.toLowerCase();
5218				if(pattern === text || text.indexOf(pattern) !== -1) {
5219					return {
5220						isMatch: true,
5221						score: 0
5222					};
5223				}
5224				if(!options.fuzzy) {
5225					return {
5226						isMatch: false,
5227						score: 1
5228					};
5229				}
5230				var i, j,
5231					textLen = text.length,
5232					scoreThreshold = MATCH_THRESHOLD,
5233					bestLoc = text.indexOf(pattern, MATCH_LOCATION),
5234					binMin, binMid,
5235					binMax = patternLen + textLen,
5236					lastRd, start, finish, rd, charMatch,
5237					score = 1,
5238					locations = [];
5239				if (bestLoc !== -1) {
5240					scoreThreshold = Math.min(match_bitapScore(0, bestLoc), scoreThreshold);
5241					bestLoc = text.lastIndexOf(pattern, MATCH_LOCATION + patternLen);
5242					if (bestLoc !== -1) {
5243						scoreThreshold = Math.min(match_bitapScore(0, bestLoc), scoreThreshold);
5244					}
5245				}
5246				bestLoc = -1;
5247				for (i = 0; i < patternLen; i++) {
5248					binMin = 0;
5249					binMid = binMax;
5250					while (binMin < binMid) {
5251						if (match_bitapScore(i, MATCH_LOCATION + binMid) <= scoreThreshold) {
5252							binMin = binMid;
5253						} else {
5254							binMax = binMid;
5255						}
5256						binMid = Math.floor((binMax - binMin) / 2 + binMin);
5257					}
5258					binMax = binMid;
5259					start = Math.max(1, MATCH_LOCATION - binMid + 1);
5260					finish = Math.min(MATCH_LOCATION + binMid, textLen) + patternLen;
5261					rd = new Array(finish + 2);
5262					rd[finish + 1] = (1 << i) - 1;
5263					for (j = finish; j >= start; j--) {
5264						charMatch = pattern_alphabet[text.charAt(j - 1)];
5265						if (i === 0) {
5266							rd[j] = ((rd[j + 1] << 1) | 1) & charMatch;
5267						} else {
5268							rd[j] = ((rd[j + 1] << 1) | 1) & charMatch | (((lastRd[j + 1] | lastRd[j]) << 1) | 1) | lastRd[j + 1];
5269						}
5270						if (rd[j] & matchmask) {
5271							score = match_bitapScore(i, j - 1);
5272							if (score <= scoreThreshold) {
5273								scoreThreshold = score;
5274								bestLoc = j - 1;
5275								locations.push(bestLoc);
5276								if (bestLoc > MATCH_LOCATION) {
5277									start = Math.max(1, 2 * MATCH_LOCATION - bestLoc);
5278								} else {
5279									break;
5280								}
5281							}
5282						}
5283					}
5284					if (match_bitapScore(i + 1, MATCH_LOCATION) > scoreThreshold) {
5285						break;
5286					}
5287					lastRd = rd;
5288				}
5289				return {
5290					isMatch: bestLoc >= 0,
5291					score: score
5292				};
5293			};
5294			return txt === true ? { 'search' : search } : search(txt);
5295		};
5296	}(jQuery));
5297
5298	// include the search plugin by default
5299	// $.jstree.defaults.plugins.push("search");
5300
5301/**
5302 * ### Sort plugin
5303 *
5304 * Autmatically sorts all siblings in the tree according to a sorting function.
5305 */
5306
5307	/**
5308	 * the settings function used to sort the nodes.
5309	 * It is executed in the tree's context, accepts two nodes as arguments and should return `1` or `-1`.
5310	 * @name $.jstree.defaults.sort
5311	 * @plugin sort
5312	 */
5313	$.jstree.defaults.sort = function (a, b) {
5314		//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);
5315		return this.get_text(a) > this.get_text(b) ? 1 : -1;
5316	};
5317	$.jstree.plugins.sort = function (options, parent) {
5318		this.bind = function () {
5319			parent.bind.call(this);
5320			this.element
5321				.on("model.jstree", $.proxy(function (e, data) {
5322						this.sort(data.parent, true);
5323					}, this))
5324				.on("rename_node.jstree create_node.jstree", $.proxy(function (e, data) {
5325						this.sort(data.parent || data.node.parent, false);
5326						this.redraw_node(data.parent || data.node.parent, true);
5327					}, this))
5328				.on("move_node.jstree copy_node.jstree", $.proxy(function (e, data) {
5329						this.sort(data.parent, false);
5330						this.redraw_node(data.parent, true);
5331					}, this));
5332		};
5333		/**
5334		 * used to sort a node's children
5335		 * @private
5336		 * @name sort(obj [, deep])
5337		 * @param  {mixed} obj the node
5338		 * @param {Boolean} deep if set to `true` nodes are sorted recursively.
5339		 * @plugin sort
5340		 * @trigger search.jstree
5341		 */
5342		this.sort = function (obj, deep) {
5343			var i, j;
5344			obj = this.get_node(obj);
5345			if(obj && obj.children && obj.children.length) {
5346				obj.children.sort($.proxy(this.settings.sort, this));
5347				if(deep) {
5348					for(i = 0, j = obj.children_d.length; i < j; i++) {
5349						this.sort(obj.children_d[i], false);
5350					}
5351				}
5352			}
5353		};
5354	};
5355
5356	// include the sort plugin by default
5357	// $.jstree.defaults.plugins.push("sort");
5358
5359/**
5360 * ### State plugin
5361 *
5362 * Saves the state of the tree (selected nodes, opened nodes) on the user's computer using available options (localStorage, cookies, etc)
5363 */
5364
5365	var to = false;
5366	/**
5367	 * stores all defaults for the state plugin
5368	 * @name $.jstree.defaults.state
5369	 * @plugin state
5370	 */
5371	$.jstree.defaults.state = {
5372		/**
5373		 * A string for the key to use when saving the current tree (change if using multiple trees in your project). Defaults to `jstree`.
5374		 * @name $.jstree.defaults.state.key
5375		 * @plugin state
5376		 */
5377		key		: 'jstree',
5378		/**
5379		 * A space separated list of events that trigger a state save. Defaults to `changed.jstree open_node.jstree close_node.jstree`.
5380		 * @name $.jstree.defaults.state.events
5381		 * @plugin state
5382		 */
5383		events	: 'changed.jstree open_node.jstree close_node.jstree',
5384		/**
5385		 * Time in milliseconds after which the state will expire. Defaults to 'false' meaning - no expire.
5386		 * @name $.jstree.defaults.state.ttl
5387		 * @plugin state
5388		 */
5389		ttl		: false,
5390		/**
5391		 * 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.
5392		 * @name $.jstree.defaults.state.filter
5393		 * @plugin state
5394		 */
5395		filter	: false
5396	};
5397	$.jstree.plugins.state = function (options, parent) {
5398		this.bind = function () {
5399			parent.bind.call(this);
5400			var bind = $.proxy(function () {
5401				this.element.on(this.settings.state.events, $.proxy(function () {
5402					if(to) { clearTimeout(to); }
5403					to = setTimeout($.proxy(function () { this.save_state(); }, this), 100);
5404				}, this));
5405			}, this);
5406			this.element
5407				.on("ready.jstree", $.proxy(function (e, data) {
5408						this.element.one("restore_state.jstree", bind);
5409						if(!this.restore_state()) { bind(); }
5410					}, this));
5411		};
5412		/**
5413		 * save the state
5414		 * @name save_state()
5415		 * @plugin state
5416		 */
5417		this.save_state = function () {
5418			var st = { 'state' : this.get_state(), 'ttl' : this.settings.state.ttl, 'sec' : +(new Date()) };
5419			$.vakata.storage.set(this.settings.state.key, JSON.stringify(st));
5420		};
5421		/**
5422		 * restore the state from the user's computer
5423		 * @name restore_state()
5424		 * @plugin state
5425		 */
5426		this.restore_state = function () {
5427			var k = $.vakata.storage.get(this.settings.state.key);
5428			if(!!k) { try { k = JSON.parse(k); } catch(ex) { return false; } }
5429			if(!!k && k.ttl && k.sec && +(new Date()) - k.sec > k.ttl) { return false; }
5430			if(!!k && k.state) { k = k.state; }
5431			if(!!k && $.isFunction(this.settings.state.filter)) { k = this.settings.state.filter.call(this, k); }
5432			if(!!k) {
5433				this.element.one("set_state.jstree", function (e, data) { data.instance.trigger('restore_state', { 'state' : $.extend(true, {}, k) }); });
5434				this.set_state(k);
5435				return true;
5436			}
5437			return false;
5438		};
5439		/**
5440		 * clear the state on the user's computer
5441		 * @name clear_state()
5442		 * @plugin state
5443		 */
5444		this.clear_state = function () {
5445			return $.vakata.storage.del(this.settings.state.key);
5446		};
5447	};
5448
5449	(function ($, undefined) {
5450		$.vakata.storage = {
5451			// simply specifying the functions in FF throws an error
5452			set : function (key, val) { return window.localStorage.setItem(key, val); },
5453			get : function (key) { return window.localStorage.getItem(key); },
5454			del : function (key) { return window.localStorage.removeItem(key); }
5455		};
5456	}(jQuery));
5457
5458	// include the state plugin by default
5459	// $.jstree.defaults.plugins.push("state");
5460
5461/**
5462 * ### Types plugin
5463 *
5464 * 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.
5465 */
5466
5467	/**
5468	 * 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).
5469	 * 
5470	 * * `max_children` the maximum number of immediate children this node type can have. Do not specify or set to `-1` for unlimited.
5471	 * * `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.
5472	 * * `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.
5473	 * * `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.
5474	 *
5475	 * There are two predefined types:
5476	 * 
5477	 * * `#` represents the root of the tree, for example `max_children` would control the maximum number of root nodes.
5478	 * * `default` represents the default node - any settings here will be applied to all nodes that do not have a type specified.
5479	 * 
5480	 * @name $.jstree.defaults.types
5481	 * @plugin types
5482	 */
5483	$.jstree.defaults.types = {
5484		'#' : {},
5485		'default' : {}
5486	};
5487
5488	$.jstree.plugins.types = function (options, parent) {
5489		this.init = function (el, options) {
5490			var i, j;
5491			if(options && options.types && options.types['default']) {
5492				for(i in options.types) {
5493					if(i !== "default" && i !== "#" && options.types.hasOwnProperty(i)) {
5494						for(j in options.types['default']) {
5495							if(options.types['default'].hasOwnProperty(j) && options.types[i][j] === undefined) {
5496								options.types[i][j] = options.types['default'][j];
5497							}
5498						}
5499					}
5500				}
5501			}
5502			parent.init.call(this, el, options);
5503			this._model.data['#'].type = '#';
5504		};
5505		this.bind = function () {
5506			parent.bind.call(this);
5507			this.element
5508				.on('model.jstree', $.proxy(function (e, data) {
5509						var m = this._model.data,
5510							dpc = data.nodes,
5511							t = this.settings.types,
5512							i, j, c = 'default';
5513						for(i = 0, j = dpc.length; i < j; i++) {
5514							c = 'default';
5515							if(m[dpc[i]].original && m[dpc[i]].original.type && t[m[dpc[i]].original.type]) {
5516								c = m[dpc[i]].original.type;
5517							}
5518							if(m[dpc[i]].data && m[dpc[i]].data.jstree && m[dpc[i]].data.jstree.type && t[m[dpc[i]].data.jstree.type]) {
5519								c = m[dpc[i]].data.jstree.type;
5520							}
5521							m[dpc[i]].type = c;
5522							if(m[dpc[i]].icon === true && t[c].icon !== undefined) {
5523								m[dpc[i]].icon = t[c].icon;
5524							}
5525						}
5526					}, this));
5527		};
5528		this.get_json = function (obj, options, flat) {
5529			var i, j,
5530				m = this._model.data,
5531				opt = options ? $.extend(true, {}, options, {no_id:false}) : {},
5532				tmp = parent.get_json.call(this, obj, opt, flat);
5533			if(tmp === false) { return false; }
5534			if($.isArray(tmp)) {
5535				for(i = 0, j = tmp.length; i < j; i++) {
5536					tmp[i].type = tmp[i].id && m[tmp[i].id] && m[tmp[i].id].type ? m[tmp[i].id].type : "default";
5537					if(options && options.no_id) {
5538						delete tmp[i].id;
5539						if(tmp[i].li_attr && tmp[i].li_attr.id) {
5540							delete tmp[i].li_attr.id;
5541						}
5542					}
5543				}
5544			}
5545			else {
5546				tmp.type = tmp.id && m[tmp.id] && m[tmp.id].type ? m[tmp.id].type : "default";
5547				if(options && options.no_id) {
5548					tmp = this._delete_ids(tmp);
5549				}
5550			}
5551			return tmp;
5552		};
5553		this._delete_ids = function (tmp) {
5554			if($.isArray(tmp)) {
5555				for(var i = 0, j = tmp.length; i < j; i++) {
5556					tmp[i] = this._delete_ids(tmp[i]);
5557				}
5558				return tmp;
5559			}
5560			delete tmp.id;
5561			if(tmp.li_attr && tmp.li_attr.id) {
5562				delete tmp.li_attr.id;
5563			}
5564			if(tmp.children && $.isArray(tmp.children)) {
5565				tmp.children = this._delete_ids(tmp.children);
5566			}
5567			return tmp;
5568		};
5569		this.check = function (chk, obj, par, pos) {
5570			if(parent.check.call(this, chk, obj, par, pos) === false) { return false; }
5571			obj = obj && obj.id ? obj : this.get_node(obj);
5572			par = par && par.id ? par : this.get_node(par);
5573			var m = obj && obj.id ? $.jstree.reference(obj.id) : null, tmp, d, i, j;
5574			m = m && m._model && m._model.data ? m._model.data : null;
5575			switch(chk) {
5576				case "create_node":
5577				case "move_node":
5578				case "copy_node":
5579					if(chk !== 'move_node' || $.inArray(obj.id, par.children) === -1) {
5580						tmp = this.get_rules(par);
5581						if(tmp.max_children !== undefined && tmp.max_children !== -1 && tmp.max_children === par.children.length) {
5582							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 }) };
5583							return false;
5584						}
5585						if(tmp.valid_children !== undefined && tmp.valid_children !== -1 && $.inArray(obj.type, tmp.valid_children) === -1) {
5586							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 }) };
5587							return false;
5588						}
5589						if(m && obj.children_d && obj.parents) {
5590							d = 0;
5591							for(i = 0, j = obj.children_d.length; i < j; i++) {
5592								d = Math.max(d, m[obj.children_d[i]].parents.length);
5593							}
5594							d = d - obj.parents.length + 1;
5595						}
5596						if(d <= 0 || d === undefined) { d = 1; }
5597						do {
5598							if(tmp.max_depth !== undefined && tmp.max_depth !== -1 && tmp.max_depth < d) {
5599								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 }) };
5600								return false;
5601							}
5602							par = this.get_node(par.parent);
5603							tmp = this.get_rules(par);
5604							d++;
5605						} while(par);
5606					}
5607					break;
5608			}
5609			return true;
5610		};
5611		/**
5612		 * used to retrieve the type settings object for a node
5613		 * @name get_rules(obj)
5614		 * @param {mixed} obj the node to find the rules for
5615		 * @return {Object}
5616		 * @plugin types
5617		 */
5618		this.get_rules = function (obj) {
5619			obj = this.get_node(obj);
5620			if(!obj) { return false; }
5621			var tmp = this.get_type(obj, true);
5622			if(tmp.max_depth === undefined) { tmp.max_depth = -1; }
5623			if(tmp.max_children === undefined) { tmp.max_children = -1; }
5624			if(tmp.valid_children === undefined) { tmp.valid_children = -1; }
5625			return tmp;
5626		};
5627		/**
5628		 * used to retrieve the type string or settings object for a node
5629		 * @name get_type(obj [, rules])
5630		 * @param {mixed} obj the node to find the rules for
5631		 * @param {Boolean} rules if set to `true` instead of a string the settings object will be returned
5632		 * @return {String|Object}
5633		 * @plugin types
5634		 */
5635		this.get_type = function (obj, rules) {
5636			obj = this.get_node(obj);
5637			return (!obj) ? false : ( rules ? $.extend({ 'type' : obj.type }, this.settings.types[obj.type]) : obj.type);
5638		};
5639		/**
5640		 * used to change a node's type
5641		 * @name set_type(obj, type)
5642		 * @param {mixed} obj the node to change
5643		 * @param {String} type the new type
5644		 * @plugin types
5645		 */
5646		this.set_type = function (obj, type) {
5647			var t, t1, t2, old_type, old_icon;
5648			if($.isArray(obj)) {
5649				obj = obj.slice();
5650				for(t1 = 0, t2 = obj.length; t1 < t2; t1++) {
5651					this.set_type(obj[t1], type);
5652				}
5653				return true;
5654			}
5655			t = this.settings.types;
5656			obj = this.get_node(obj);
5657			if(!t[type] || !obj) { return false; }
5658			old_type = obj.type;
5659			old_icon = this.get_icon(obj);
5660			obj.type = type;
5661			if(old_icon === true || (t[old_type] && t[old_type].icon && old_icon === t[old_type].icon)) {
5662				this.set_icon(obj, t[type].icon !== undefined ? t[type].icon : true);
5663			}
5664			return true;
5665		};
5666	};
5667	// include the types plugin by default
5668	// $.jstree.defaults.plugins.push("types");
5669
5670/**
5671 * ### Unique plugin
5672 *
5673 * Enforces that no nodes with the same name can coexist as siblings.
5674 */
5675
5676	$.jstree.plugins.unique = function (options, parent) {
5677		this.check = function (chk, obj, par, pos) {
5678			if(parent.check.call(this, chk, obj, par, pos) === false) { return false; }
5679			obj = obj && obj.id ? obj : this.get_node(obj);
5680			par = par && par.id ? par : this.get_node(par);
5681			if(!par || !par.children) { return true; }
5682			var n = chk === "rename_node" ? pos : obj.text,
5683				c = [],
5684				m = this._model.data, i, j;
5685			for(i = 0, j = par.children.length; i < j; i++) {
5686				c.push(m[par.children[i]].text);
5687			}
5688			switch(chk) {
5689				case "delete_node":
5690					return true;
5691				case "rename_node":
5692				case "copy_node":
5693					i = ($.inArray(n, c) === -1);
5694					if(!i) {
5695						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 }) };
5696					}
5697					return i;
5698				case "move_node":
5699					i = (obj.parent === par.id || $.inArray(n, c) === -1);
5700					if(!i) {
5701						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 }) };
5702					}
5703					return i;
5704			}
5705			return true;
5706		};
5707	};
5708
5709	// include the unique plugin by default
5710	// $.jstree.defaults.plugins.push("unique");
5711
5712
5713/**
5714 * ### Wholerow plugin
5715 *
5716 * Makes each node appear block level. Making selection easier. May cause slow down for large trees in old browsers.
5717 */
5718
5719	var div = document.createElement('DIV');
5720	div.setAttribute('unselectable','on');
5721	div.className = 'jstree-wholerow';
5722	div.innerHTML = '&#160;';
5723	$.jstree.plugins.wholerow = function (options, parent) {
5724		this.bind = function () {
5725			parent.bind.call(this);
5726
5727			this.element
5728				.on('loading', $.proxy(function () {
5729						div.style.height = this._data.core.li_height + 'px';
5730					}, this))
5731				.on('ready.jstree set_state.jstree', $.proxy(function () {
5732						this.hide_dots();
5733					}, this))
5734				.on("ready.jstree", $.proxy(function () {
5735						this.get_container_ul().addClass('jstree-wholerow-ul');
5736					}, this))
5737				.on("deselect_all.jstree", $.proxy(function (e, data) {
5738						this.element.find('.jstree-wholerow-clicked').removeClass('jstree-wholerow-clicked');
5739					}, this))
5740				.on("changed.jstree", $.proxy(function (e, data) {
5741						this.element.find('.jstree-wholerow-clicked').removeClass('jstree-wholerow-clicked');
5742						var tmp = false, i, j;
5743						for(i = 0, j = data.selected.length; i < j; i++) {
5744							tmp = this.get_node(data.selected[i], true);
5745							if(tmp && tmp.length) {
5746								tmp.children('.jstree-wholerow').addClass('jstree-wholerow-clicked');
5747							}
5748						}
5749					}, this))
5750				.on("open_node.jstree", $.proxy(function (e, data) {
5751						this.get_node(data.node, true).find('.jstree-clicked').parent().children('.jstree-wholerow').addClass('jstree-wholerow-clicked');
5752					}, this))
5753				.on("hover_node.jstree dehover_node.jstree", $.proxy(function (e, data) {
5754						this.get_node(data.node, true).children('.jstree-wholerow')[e.type === "hover_node"?"addClass":"removeClass"]('jstree-wholerow-hovered');
5755					}, this))
5756				.on("contextmenu.jstree", ".jstree-wholerow", $.proxy(function (e) {
5757						e.preventDefault();
5758						$(e.currentTarget).closest("li").children("a:eq(0)").trigger('contextmenu',e);
5759					}, this))
5760				.on("click.jstree", ".jstree-wholerow", function (e) {
5761						e.stopImmediatePropagation();
5762						var tmp = $.Event('click', { metaKey : e.metaKey, ctrlKey : e.ctrlKey, altKey : e.altKey, shiftKey : e.shiftKey });
5763						$(e.currentTarget).closest("li").children("a:eq(0)").trigger(tmp).focus();
5764					})
5765				.on("click.jstree", ".jstree-leaf > .jstree-ocl", $.proxy(function (e) {
5766						e.stopImmediatePropagation();
5767						var tmp = $.Event('click', { metaKey : e.metaKey, ctrlKey : e.ctrlKey, altKey : e.altKey, shiftKey : e.shiftKey });
5768						$(e.currentTarget).closest("li").children("a:eq(0)").trigger(tmp).focus();
5769					}, this))
5770				.on("mouseover.jstree", ".jstree-wholerow, .jstree-icon", $.proxy(function (e) {
5771						e.stopImmediatePropagation();
5772						this.hover_node(e.currentTarget);
5773						return false;
vendor: 803 bytes, lines 5774-5799
5774					}, this))
5775				.on("mouseleave.jstree", ".jstree-node", $.proxy(function (e) {
5776						this.dehover_node(e.currentTarget);
5777					}, this));
5778		};
5779		this.teardown = function () {
5780			if(this.settings.wholerow) {
5781				this.element.find(".jstree-wholerow").remove();
5782			}
5783			parent.teardown.call(this);
5784		};
5785		this.redraw_node = function(obj, deep, callback) {
5786			obj = parent.redraw_node.call(this, obj, deep, callback);
5787			if(obj) {
5788				var tmp = div.cloneNode(true);
5789				//tmp.style.height = this._data.core.li_height + 'px';
5790				if($.inArray(obj.id, this._data.core.selected) !== -1) { tmp.className += ' jstree-wholerow-clicked'; }
5791				obj.insertBefore(tmp, obj.childNodes[0]);
5792			}
5793			return obj;
5794		};
5795	};
5796	// include the wholerow plugin by default
5797	// $.jstree.defaults.plugins.push("wholerow");
5798
5799}));

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.