Subversion-Projekte lars-tiefland.ci

Revision

Details | Letzte Änderung | Log anzeigen | RSS feed

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