Subversion-Projekte lars-tiefland.cienc

Revision

Details | Letzte Änderung | Log anzeigen | RSS feed

Revision Autor Zeilennr. Zeile
9 lars 1
/*!
2
 * jsTree {{VERSION}}
3
 * http://jstree.com/
4
 *
5
 * Copyright (c) 2014 Ivan Bozhanov (http://vakata.com)
6
 *
7
 * Licensed same as jquery - under the terms of the MIT License
8
 *   http://www.opensource.org/licenses/mit-license.php
9
 */
10
/*!
11
 * if using jslint please allow for the jQuery global and use following options:
12
 * jslint: browser: true, ass: true, bitwise: true, continue: true, nomen: true, plusplus: true, regexp: true, unparam: true, todo: true, white: true
13
 */
14
/*globals jQuery, define, module, exports, require, window, document, postMessage */
15
(function (factory) {
16
	"use strict";
17
	if (typeof define === 'function' && define.amd) {
18
		define(['jquery'], factory);
19
	}
20
	else if(typeof module !== 'undefined' && module.exports) {
21
		module.exports = factory(require('jquery'));
22
	}
23
	else {
24
		factory(jQuery);
25
	}
26
}(function ($, undefined) {
27
	"use strict";
28
 
29
	// prevent another load? maybe there is a better way?
30
	if($.jstree) {
31
		return;
32
	}
33
 
34
	/**
35
	 * ### jsTree core functionality
36
	 */
37
 
38
	// internal variables
39
	var instance_counter = 0,
40
		ccp_node = false,
41
		ccp_mode = false,
42
		ccp_inst = false,
43
		themes_loaded = [],
44
		src = $('script:last').attr('src'),
45
		document = window.document, // local variable is always faster to access then a global
46
		_node = document.createElement('LI'), _temp1, _temp2;
47
 
48
	_node.setAttribute('role', 'treeitem');
49
	_temp1 = document.createElement('I');
50
	_temp1.className = 'jstree-icon jstree-ocl';
51
	_temp1.setAttribute('role', 'presentation');
52
	_node.appendChild(_temp1);
53
	_temp1 = document.createElement('A');
54
	_temp1.className = 'jstree-anchor';
55
	_temp1.setAttribute('href','#');
56
	_temp1.setAttribute('tabindex','-1');
57
	_temp2 = document.createElement('I');
58
	_temp2.className = 'jstree-icon jstree-themeicon';
59
	_temp2.setAttribute('role', 'presentation');
60
	_temp1.appendChild(_temp2);
61
	_node.appendChild(_temp1);
62
	_temp1 = _temp2 = null;
63
 
64
 
65
	/**
66
	 * holds all jstree related functions and variables, including the actual class and methods to create, access and manipulate instances.
67
	 * @name $.jstree
68
	 */
69
	$.jstree = {
70
		/**
71
		 * specifies the jstree version in use
72
		 * @name $.jstree.version
73
		 */
74
		version : '{{VERSION}}',
75
		/**
76
		 * holds all the default options used when creating new instances
77
		 * @name $.jstree.defaults
78
		 */
79
		defaults : {
80
			/**
81
			 * configure which plugins will be active on an instance. Should be an array of strings, where each element is a plugin name. The default is `[]`
82
			 * @name $.jstree.defaults.plugins
83
			 */
84
			plugins : []
85
		},
86
		/**
87
		 * stores all loaded jstree plugins (used internally)
88
		 * @name $.jstree.plugins
89
		 */
90
		plugins : {},
91
		path : src && src.indexOf('/') !== -1 ? src.replace(/\/[^\/]+$/,'') : '',
92
		idregex : /[\\:&!^|()\[\]<>@*'+~#";.,=\- \/${}%?`]/g,
93
		root : '#'
94
	};
95
	/**
96
	 * creates a jstree instance
97
	 * @name $.jstree.create(el [, options])
98
	 * @param {DOMElement|jQuery|String} el the element to create the instance on, can be jQuery extended or a selector
99
	 * @param {Object} options options for this instance (extends `$.jstree.defaults`)
100
	 * @return {jsTree} the new instance
101
	 */
102
	$.jstree.create = function (el, options) {
103
		var tmp = new $.jstree.core(++instance_counter),
104
			opt = options;
105
		options = $.extend(true, {}, $.jstree.defaults, options);
106
		if(opt && opt.plugins) {
107
			options.plugins = opt.plugins;
108
		}
109
		$.each(options.plugins, function (i, k) {
110
			if(i !== 'core') {
111
				tmp = tmp.plugin(k, options[k]);
112
			}
113
		});
114
		$(el).data('jstree', tmp);
115
		tmp.init(el, options);
116
		return tmp;
117
	};
118
	/**
119
	 * remove all traces of jstree from the DOM and destroy all instances
120
	 * @name $.jstree.destroy()
121
	 */
122
	$.jstree.destroy = function () {
123
		$('.jstree:jstree').jstree('destroy');
124
		$(document).off('.jstree');
125
	};
126
	/**
127
	 * the jstree class constructor, used only internally
128
	 * @private
129
	 * @name $.jstree.core(id)
130
	 * @param {Number} id this instance's index
131
	 */
132
	$.jstree.core = function (id) {
133
		this._id = id;
134
		this._cnt = 0;
135
		this._wrk = null;
136
		this._data = {
137
			core : {
138
				themes : {
139
					name : false,
140
					dots : false,
141
					icons : false
142
				},
143
				selected : [],
144
				last_error : {},
145
				working : false,
146
				worker_queue : [],
147
				focused : null
148
			}
149
		};
150
	};
151
	/**
152
	 * get a reference to an existing instance
153
	 *
154
	 * __Examples__
155
	 *
156
	 *	// provided a container with an ID of "tree", and a nested node with an ID of "branch"
157
	 *	// all of there will return the same instance
158
	 *	$.jstree.reference('tree');
159
	 *	$.jstree.reference('#tree');
160
	 *	$.jstree.reference($('#tree'));
161
	 *	$.jstree.reference(document.getElementByID('tree'));
162
	 *	$.jstree.reference('branch');
163
	 *	$.jstree.reference('#branch');
164
	 *	$.jstree.reference($('#branch'));
165
	 *	$.jstree.reference(document.getElementByID('branch'));
166
	 *
167
	 * @name $.jstree.reference(needle)
168
	 * @param {DOMElement|jQuery|String} needle
169
	 * @return {jsTree|null} the instance or `null` if not found
170
	 */
171
	$.jstree.reference = function (needle) {
172
		var tmp = null,
173
			obj = null;
174
		if(needle && needle.id && (!needle.tagName || !needle.nodeType)) { needle = needle.id; }
175
 
176
		if(!obj || !obj.length) {
177
			try { obj = $(needle); } catch (ignore) { }
178
		}
179
		if(!obj || !obj.length) {
180
			try { obj = $('#' + needle.replace($.jstree.idregex,'\\$&')); } catch (ignore) { }
181
		}
182
		if(obj && obj.length && (obj = obj.closest('.jstree')).length && (obj = obj.data('jstree'))) {
183
			tmp = obj;
184
		}
185
		else {
186
			$('.jstree').each(function () {
187
				var inst = $(this).data('jstree');
188
				if(inst && inst._model.data[needle]) {
189
					tmp = inst;
190
					return false;
191
				}
192
			});
193
		}
194
		return tmp;
195
	};
196
	/**
197
	 * Create an instance, get an instance or invoke a command on a instance.
198
	 *
199
	 * 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).
200
	 *
201
	 * 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).
202
	 *
203
	 * If there is an existing instance and `arg` is not a string the instance itself is returned (similar to `$.jstree.reference`).
204
	 *
205
	 * In any other case - nothing is returned and chaining is not broken.
206
	 *
207
	 * __Examples__
208
	 *
209
	 *	$('#tree1').jstree(); // creates an instance
210
	 *	$('#tree2').jstree({ plugins : [] }); // create an instance with some options
211
	 *	$('#tree1').jstree('open_node', '#branch_1'); // call a method on an existing instance, passing additional arguments
212
	 *	$('#tree2').jstree(); // get an existing instance (or create an instance)
213
	 *	$('#tree2').jstree(true); // get an existing instance (will not create new instance)
214
	 *	$('#branch_1').jstree().select_node('#branch_1'); // get an instance (using a nested element and call a method)
215
	 *
216
	 * @name $().jstree([arg])
217
	 * @param {String|Object} arg
218
	 * @return {Mixed}
219
	 */
220
	$.fn.jstree = function (arg) {
221
		// check for string argument
222
		var is_method	= (typeof arg === 'string'),
223
			args		= Array.prototype.slice.call(arguments, 1),
224
			result		= null;
225
		if(arg === true && !this.length) { return false; }
226
		this.each(function () {
227
			// get the instance (if there is one) and method (if it exists)
228
			var instance = $.jstree.reference(this),
229
				method = is_method && instance ? instance[arg] : null;
230
			// if calling a method, and method is available - execute on the instance
231
			result = is_method && method ?
232
				method.apply(instance, args) :
233
				null;
234
			// if there is no instance and no method is being called - create one
235
			if(!instance && !is_method && (arg === undefined || $.isPlainObject(arg))) {
236
				$.jstree.create(this, arg);
237
			}
238
			// if there is an instance and no method is called - return the instance
239
			if( (instance && !is_method) || arg === true ) {
240
				result = instance || false;
241
			}
242
			// if there was a method call which returned a result - break and return the value
243
			if(result !== null && result !== undefined) {
244
				return false;
245
			}
246
		});
247
		// if there was a method call with a valid return value - return that, otherwise continue the chain
248
		return result !== null && result !== undefined ?
249
			result : this;
250
	};
251
	/**
252
	 * used to find elements containing an instance
253
	 *
254
	 * __Examples__
255
	 *
256
	 *	$('div:jstree').each(function () {
257
	 *		$(this).jstree('destroy');
258
	 *	});
259
	 *
260
	 * @name $(':jstree')
261
	 * @return {jQuery}
262
	 */
263
	$.expr[':'].jstree = $.expr.createPseudo(function(search) {
264
		return function(a) {
265
			return $(a).hasClass('jstree') &&
266
				$(a).data('jstree') !== undefined;
267
		};
268
	});
269
 
270
	/**
271
	 * stores all defaults for the core
272
	 * @name $.jstree.defaults.core
273
	 */
274
	$.jstree.defaults.core = {
275
		/**
276
		 * data configuration
277
		 *
278
		 * 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).
279
		 *
280
		 * You can also pass in a HTML string or a JSON array here.
281
		 *
282
		 * 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.
283
		 * 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.
284
		 *
285
		 * 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.
286
		 *
287
		 * __Examples__
288
		 *
289
		 *	// AJAX
290
		 *	$('#tree').jstree({
291
		 *		'core' : {
292
		 *			'data' : {
293
		 *				'url' : '/get/children/',
294
		 *				'data' : function (node) {
295
		 *					return { 'id' : node.id };
296
		 *				}
297
		 *			}
298
		 *		});
299
		 *
300
		 *	// direct data
301
		 *	$('#tree').jstree({
302
		 *		'core' : {
303
		 *			'data' : [
304
		 *				'Simple root node',
305
		 *				{
306
		 *					'id' : 'node_2',
307
		 *					'text' : 'Root node with options',
308
		 *					'state' : { 'opened' : true, 'selected' : true },
309
		 *					'children' : [ { 'text' : 'Child 1' }, 'Child 2']
310
		 *				}
311
		 *			]
312
		 *		});
313
		 *
314
		 *	// function
315
		 *	$('#tree').jstree({
316
		 *		'core' : {
317
		 *			'data' : function (obj, callback) {
318
		 *				callback.call(this, ['Root 1', 'Root 2']);
319
		 *			}
320
		 *		});
321
		 *
322
		 * @name $.jstree.defaults.core.data
323
		 */
324
		data			: false,
325
		/**
326
		 * configure the various strings used throughout the tree
327
		 *
328
		 * You can use an object where the key is the string you need to replace and the value is your replacement.
329
		 * Another option is to specify a function which will be called with an argument of the needed string and should return the replacement.
330
		 * If left as `false` no replacement is made.
331
		 *
332
		 * __Examples__
333
		 *
334
		 *	$('#tree').jstree({
335
		 *		'core' : {
336
		 *			'strings' : {
337
		 *				'Loading ...' : 'Please wait ...'
338
		 *			}
339
		 *		}
340
		 *	});
341
		 *
342
		 * @name $.jstree.defaults.core.strings
343
		 */
344
		strings			: false,
345
		/**
346
		 * determines what happens when a user tries to modify the structure of the tree
347
		 * If left as `false` all operations like create, rename, delete, move or copy are prevented.
348
		 * You can set this to `true` to allow all interactions or use a function to have better control.
349
		 *
350
		 * __Examples__
351
		 *
352
		 *	$('#tree').jstree({
353
		 *		'core' : {
354
		 *			'check_callback' : function (operation, node, node_parent, node_position, more) {
355
		 *				// operation can be 'create_node', 'rename_node', 'delete_node', 'move_node' or 'copy_node'
356
		 *				// in case of 'rename_node' node_position is filled with the new node name
357
		 *				return operation === 'rename_node' ? true : false;
358
		 *			}
359
		 *		}
360
		 *	});
361
		 *
362
		 * @name $.jstree.defaults.core.check_callback
363
		 */
364
		check_callback	: false,
365
		/**
366
		 * a callback called with a single object parameter in the instance's scope when something goes wrong (operation prevented, ajax failed, etc)
367
		 * @name $.jstree.defaults.core.error
368
		 */
369
		error			: $.noop,
370
		/**
371
		 * the open / close animation duration in milliseconds - set this to `false` to disable the animation (default is `200`)
372
		 * @name $.jstree.defaults.core.animation
373
		 */
374
		animation		: 200,
375
		/**
376
		 * a boolean indicating if multiple nodes can be selected
377
		 * @name $.jstree.defaults.core.multiple
378
		 */
379
		multiple		: true,
380
		/**
381
		 * theme configuration object
382
		 * @name $.jstree.defaults.core.themes
383
		 */
384
		themes			: {
385
			/**
386
			 * the name of the theme to use (if left as `false` the default theme is used)
387
			 * @name $.jstree.defaults.core.themes.name
388
			 */
389
			name			: false,
390
			/**
391
			 * 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.
392
			 * @name $.jstree.defaults.core.themes.url
393
			 */
394
			url				: false,
395
			/**
396
			 * the location of all jstree themes - only used if `url` is set to `true`
397
			 * @name $.jstree.defaults.core.themes.dir
398
			 */
399
			dir				: false,
400
			/**
401
			 * a boolean indicating if connecting dots are shown
402
			 * @name $.jstree.defaults.core.themes.dots
403
			 */
404
			dots			: true,
405
			/**
406
			 * a boolean indicating if node icons are shown
407
			 * @name $.jstree.defaults.core.themes.icons
408
			 */
409
			icons			: true,
410
			/**
411
			 * a boolean indicating if the tree background is striped
412
			 * @name $.jstree.defaults.core.themes.stripes
413
			 */
414
			stripes			: false,
415
			/**
416
			 * a string (or boolean `false`) specifying the theme variant to use (if the theme supports variants)
417
			 * @name $.jstree.defaults.core.themes.variant
418
			 */
419
			variant			: false,
420
			/**
421
			 * a boolean specifying if a reponsive version of the theme should kick in on smaller screens (if the theme supports it). Defaults to `false`.
422
			 * @name $.jstree.defaults.core.themes.responsive
423
			 */
424
			responsive		: false
425
		},
426
		/**
427
		 * if left as `true` all parents of all selected nodes will be opened once the tree loads (so that all selected nodes are visible to the user)
428
		 * @name $.jstree.defaults.core.expand_selected_onload
429
		 */
430
		expand_selected_onload : true,
431
		/**
432
		 * if left as `true` web workers will be used to parse incoming JSON data where possible, so that the UI will not be blocked by large requests. Workers are however about 30% slower. Defaults to `true`
433
		 * @name $.jstree.defaults.core.worker
434
		 */
435
		worker : true,
436
		/**
437
		 * Force node text to plain text (and escape HTML). Defaults to `false`
438
		 * @name $.jstree.defaults.core.force_text
439
		 */
440
		force_text : false,
441
		/**
442
		 * Should the node should be toggled if the text is double clicked . Defaults to `true`
443
		 * @name $.jstree.defaults.core.dblclick_toggle
444
		 */
445
		dblclick_toggle : true
446
	};
447
	$.jstree.core.prototype = {
448
		/**
449
		 * used to decorate an instance with a plugin. Used internally.
450
		 * @private
451
		 * @name plugin(deco [, opts])
452
		 * @param  {String} deco the plugin to decorate with
453
		 * @param  {Object} opts options for the plugin
454
		 * @return {jsTree}
455
		 */
456
		plugin : function (deco, opts) {
457
			var Child = $.jstree.plugins[deco];
458
			if(Child) {
459
				this._data[deco] = {};
460
				Child.prototype = this;
461
				return new Child(opts, this);
462
			}
463
			return this;
464
		},
465
		/**
466
		 * initialize the instance. Used internally.
467
		 * @private
468
		 * @name init(el, optons)
469
		 * @param {DOMElement|jQuery|String} el the element we are transforming
470
		 * @param {Object} options options for this instance
471
		 * @trigger init.jstree, loading.jstree, loaded.jstree, ready.jstree, changed.jstree
472
		 */
473
		init : function (el, options) {
474
			this._model = {
475
				data : {},
476
				changed : [],
477
				force_full_redraw : false,
478
				redraw_timeout : false,
479
				default_state : {
480
					loaded : true,
481
					opened : false,
482
					selected : false,
483
					disabled : false
484
				}
485
			};
486
			this._model.data[$.jstree.root] = {
487
				id : $.jstree.root,
488
				parent : null,
489
				parents : [],
490
				children : [],
491
				children_d : [],
492
				state : { loaded : false }
493
			};
494
 
495
			this.element = $(el).addClass('jstree jstree-' + this._id);
496
			this.settings = options;
497
 
498
			this._data.core.ready = false;
499
			this._data.core.loaded = false;
500
			this._data.core.rtl = (this.element.css("direction") === "rtl");
501
			this.element[this._data.core.rtl ? 'addClass' : 'removeClass']("jstree-rtl");
502
			this.element.attr('role','tree');
503
			if(this.settings.core.multiple) {
504
				this.element.attr('aria-multiselectable', true);
505
			}
506
			if(!this.element.attr('tabindex')) {
507
				this.element.attr('tabindex','0');
508
			}
509
 
510
			this.bind();
511
			/**
512
			 * triggered after all events are bound
513
			 * @event
514
			 * @name init.jstree
515
			 */
516
			this.trigger("init");
517
 
518
			this._data.core.original_container_html = this.element.find(" > ul > li").clone(true);
519
			this._data.core.original_container_html
520
				.find("li").addBack()
521
				.contents().filter(function() {
522
					return this.nodeType === 3 && (!this.nodeValue || /^\s+$/.test(this.nodeValue));
523
				})
524
				.remove();
525
			this.element.html("<"+"ul class='jstree-container-ul jstree-children' role='group'><"+"li id='j"+this._id+"_loading' class='jstree-initial-node jstree-loading jstree-leaf jstree-last' role='tree-item'><i class='jstree-icon jstree-ocl'></i><"+"a class='jstree-anchor' href='#'><i class='jstree-icon jstree-themeicon-hidden'></i>" + this.get_string("Loading ...") + "</a></li></ul>");
526
			this.element.attr('aria-activedescendant','j' + this._id + '_loading');
527
			this._data.core.li_height = this.get_container_ul().children("li").first().height() || 24;
528
			/**
529
			 * triggered after the loading text is shown and before loading starts
530
			 * @event
531
			 * @name loading.jstree
532
			 */
533
			this.trigger("loading");
534
			this.load_node($.jstree.root);
535
		},
536
		/**
537
		 * destroy an instance
538
		 * @name destroy()
539
		 * @param  {Boolean} keep_html if not set to `true` the container will be emptied, otherwise the current DOM elements will be kept intact
540
		 */
541
		destroy : function (keep_html) {
542
			if(this._wrk) {
543
				try {
544
					window.URL.revokeObjectURL(this._wrk);
545
					this._wrk = null;
546
				}
547
				catch (ignore) { }
548
			}
549
			if(!keep_html) { this.element.empty(); }
550
			this.teardown();
551
		},
552
		/**
553
		 * part of the destroying of an instance. Used internally.
554
		 * @private
555
		 * @name teardown()
556
		 */
557
		teardown : function () {
558
			this.unbind();
559
			this.element
560
				.removeClass('jstree')
561
				.removeData('jstree')
562
				.find("[class^='jstree']")
563
					.addBack()
564
					.attr("class", function () { return this.className.replace(/jstree[^ ]*|$/ig,''); });
565
			this.element = null;
566
		},
567
		/**
568
		 * bind all events. Used internally.
569
		 * @private
570
		 * @name bind()
571
		 */
572
		bind : function () {
573
			var word = '',
574
				tout = null,
575
				was_click = 0;
576
			this.element
577
				.on("dblclick.jstree", function (e) {
578
						if(e.target.tagName && e.target.tagName.toLowerCase() === "input") { return true; }
579
						if(document.selection && document.selection.empty) {
580
							document.selection.empty();
581
						}
582
						else {
583
							if(window.getSelection) {
584
								var sel = window.getSelection();
585
								try {
586
									sel.removeAllRanges();
587
									sel.collapse();
588
								} catch (ignore) { }
589
							}
590
						}
591
					})
592
				.on("mousedown.jstree", $.proxy(function (e) {
593
						if(e.target === this.element[0]) {
594
							e.preventDefault(); // prevent losing focus when clicking scroll arrows (FF, Chrome)
595
							was_click = +(new Date()); // ie does not allow to prevent losing focus
596
						}
597
					}, this))
598
				.on("mousedown.jstree", ".jstree-ocl", function (e) {
599
						e.preventDefault(); // prevent any node inside from losing focus when clicking the open/close icon
600
					})
601
				.on("click.jstree", ".jstree-ocl", $.proxy(function (e) {
602
						this.toggle_node(e.target);
603
					}, this))
604
				.on("dblclick.jstree", ".jstree-anchor", $.proxy(function (e) {
605
						if(e.target.tagName && e.target.tagName.toLowerCase() === "input") { return true; }
606
						if(this.settings.core.dblclick_toggle) {
607
							this.toggle_node(e.target);
608
						}
609
					}, this))
610
				.on("click.jstree", ".jstree-anchor", $.proxy(function (e) {
611
						e.preventDefault();
612
						if(e.currentTarget !== document.activeElement) { $(e.currentTarget).focus(); }
613
						this.activate_node(e.currentTarget, e);
614
					}, this))
615
				.on('keydown.jstree', '.jstree-anchor', $.proxy(function (e) {
616
						if(e.target.tagName && e.target.tagName.toLowerCase() === "input") { return true; }
617
						if(e.which !== 32 && e.which !== 13 && (e.shiftKey || e.ctrlKey || e.altKey || e.metaKey)) { return true; }
618
						var o = null;
619
						if(this._data.core.rtl) {
620
							if(e.which === 37) { e.which = 39; }
621
							else if(e.which === 39) { e.which = 37; }
622
						}
623
						switch(e.which) {
624
							case 32: // aria defines space only with Ctrl
625
								if(e.ctrlKey) {
626
									e.type = "click";
627
									$(e.currentTarget).trigger(e);
628
								}
629
								break;
630
							case 13: // enter
631
								e.type = "click";
632
								$(e.currentTarget).trigger(e);
633
								break;
634
							case 37: // right
635
								e.preventDefault();
636
								if(this.is_open(e.currentTarget)) {
637
									this.close_node(e.currentTarget);
638
								}
639
								else {
640
									o = this.get_parent(e.currentTarget);
641
									if(o && o.id !== $.jstree.root) { this.get_node(o, true).children('.jstree-anchor').focus(); }
642
								}
643
								break;
644
							case 38: // up
645
								e.preventDefault();
646
								o = this.get_prev_dom(e.currentTarget);
647
								if(o && o.length) { o.children('.jstree-anchor').focus(); }
648
								break;
649
							case 39: // left
650
								e.preventDefault();
651
								if(this.is_closed(e.currentTarget)) {
652
									this.open_node(e.currentTarget, function (o) { this.get_node(o, true).children('.jstree-anchor').focus(); });
653
								}
654
								else if (this.is_open(e.currentTarget)) {
655
									o = this.get_node(e.currentTarget, true).children('.jstree-children')[0];
656
									if(o) { $(this._firstChild(o)).children('.jstree-anchor').focus(); }
657
								}
658
								break;
659
							case 40: // down
660
								e.preventDefault();
661
								o = this.get_next_dom(e.currentTarget);
662
								if(o && o.length) { o.children('.jstree-anchor').focus(); }
663
								break;
664
							case 106: // aria defines * on numpad as open_all - not very common
665
								this.open_all();
666
								break;
667
							case 36: // home
668
								e.preventDefault();
669
								o = this._firstChild(this.get_container_ul()[0]);
670
								if(o) { $(o).children('.jstree-anchor').filter(':visible').focus(); }
671
								break;
672
							case 35: // end
673
								e.preventDefault();
674
								this.element.find('.jstree-anchor').filter(':visible').last().focus();
675
								break;
676
							/*!
677
							// delete
678
							case 46:
679
								e.preventDefault();
680
								o = this.get_node(e.currentTarget);
681
								if(o && o.id && o.id !== $.jstree.root) {
682
									o = this.is_selected(o) ? this.get_selected() : o;
683
									this.delete_node(o);
684
								}
685
								break;
686
							// f2
687
							case 113:
688
								e.preventDefault();
689
								o = this.get_node(e.currentTarget);
690
								if(o && o.id && o.id !== $.jstree.root) {
691
									// this.edit(o);
692
								}
693
								break;
694
							default:
695
								// console.log(e.which);
696
								break;
697
							*/
698
						}
699
					}, this))
700
				.on("load_node.jstree", $.proxy(function (e, data) {
701
						if(data.status) {
702
							if(data.node.id === $.jstree.root && !this._data.core.loaded) {
703
								this._data.core.loaded = true;
704
								if(this._firstChild(this.get_container_ul()[0])) {
705
									this.element.attr('aria-activedescendant',this._firstChild(this.get_container_ul()[0]).id);
706
								}
707
								/**
708
								 * triggered after the root node is loaded for the first time
709
								 * @event
710
								 * @name loaded.jstree
711
								 */
712
								this.trigger("loaded");
713
							}
714
							if(!this._data.core.ready) {
715
								setTimeout($.proxy(function() {
716
									if(this.element && !this.get_container_ul().find('.jstree-loading').length) {
717
										this._data.core.ready = true;
718
										if(this._data.core.selected.length) {
719
											if(this.settings.core.expand_selected_onload) {
720
												var tmp = [], i, j;
721
												for(i = 0, j = this._data.core.selected.length; i < j; i++) {
722
													tmp = tmp.concat(this._model.data[this._data.core.selected[i]].parents);
723
												}
724
												tmp = $.vakata.array_unique(tmp);
725
												for(i = 0, j = tmp.length; i < j; i++) {
726
													this.open_node(tmp[i], false, 0);
727
												}
728
											}
729
											this.trigger('changed', { 'action' : 'ready', 'selected' : this._data.core.selected });
730
										}
731
										/**
732
										 * triggered after all nodes are finished loading
733
										 * @event
734
										 * @name ready.jstree
735
										 */
736
										this.trigger("ready");
737
									}
738
								}, this), 0);
739
							}
740
						}
741
					}, this))
742
				// quick searching when the tree is focused
743
				.on('keypress.jstree', $.proxy(function (e) {
744
						if(e.target.tagName && e.target.tagName.toLowerCase() === "input") { return true; }
745
						if(tout) { clearTimeout(tout); }
746
						tout = setTimeout(function () {
747
							word = '';
748
						}, 500);
749
 
750
						var chr = String.fromCharCode(e.which).toLowerCase(),
751
							col = this.element.find('.jstree-anchor').filter(':visible'),
752
							ind = col.index(document.activeElement) || 0,
753
							end = false;
754
						word += chr;
755
 
756
						// match for whole word from current node down (including the current node)
757
						if(word.length > 1) {
758
							col.slice(ind).each($.proxy(function (i, v) {
759
								if($(v).text().toLowerCase().indexOf(word) === 0) {
760
									$(v).focus();
761
									end = true;
762
									return false;
763
								}
764
							}, this));
765
							if(end) { return; }
766
 
767
							// match for whole word from the beginning of the tree
768
							col.slice(0, ind).each($.proxy(function (i, v) {
769
								if($(v).text().toLowerCase().indexOf(word) === 0) {
770
									$(v).focus();
771
									end = true;
772
									return false;
773
								}
774
							}, this));
775
							if(end) { return; }
776
						}
777
						// list nodes that start with that letter (only if word consists of a single char)
778
						if(new RegExp('^' + chr.replace(/[-\/\\^$*+?.()|[\]{}]/g, '\\$&') + '+$').test(word)) {
779
							// search for the next node starting with that letter
780
							col.slice(ind + 1).each($.proxy(function (i, v) {
781
								if($(v).text().toLowerCase().charAt(0) === chr) {
782
									$(v).focus();
783
									end = true;
784
									return false;
785
								}
786
							}, this));
787
							if(end) { return; }
788
 
789
							// search from the beginning
790
							col.slice(0, ind + 1).each($.proxy(function (i, v) {
791
								if($(v).text().toLowerCase().charAt(0) === chr) {
792
									$(v).focus();
793
									end = true;
794
									return false;
795
								}
796
							}, this));
797
							if(end) { return; }
798
						}
799
					}, this))
800
				// THEME RELATED
801
				.on("init.jstree", $.proxy(function () {
802
						var s = this.settings.core.themes;
803
						this._data.core.themes.dots			= s.dots;
804
						this._data.core.themes.stripes		= s.stripes;
805
						this._data.core.themes.icons		= s.icons;
806
						this.set_theme(s.name || "default", s.url);
807
						this.set_theme_variant(s.variant);
808
					}, this))
809
				.on("loading.jstree", $.proxy(function () {
810
						this[ this._data.core.themes.dots ? "show_dots" : "hide_dots" ]();
811
						this[ this._data.core.themes.icons ? "show_icons" : "hide_icons" ]();
812
						this[ this._data.core.themes.stripes ? "show_stripes" : "hide_stripes" ]();
813
					}, this))
814
				.on('blur.jstree', '.jstree-anchor', $.proxy(function (e) {
815
						this._data.core.focused = null;
816
						$(e.currentTarget).filter('.jstree-hovered').mouseleave();
817
						this.element.attr('tabindex', '0');
818
					}, this))
819
				.on('focus.jstree', '.jstree-anchor', $.proxy(function (e) {
820
						var tmp = this.get_node(e.currentTarget);
821
						if(tmp && tmp.id) {
822
							this._data.core.focused = tmp.id;
823
						}
824
						this.element.find('.jstree-hovered').not(e.currentTarget).mouseleave();
825
						$(e.currentTarget).mouseenter();
826
						this.element.attr('tabindex', '-1');
827
					}, this))
828
				.on('focus.jstree', $.proxy(function () {
829
						if(+(new Date()) - was_click > 500 && !this._data.core.focused) {
830
							was_click = 0;
831
							var act = this.get_node(this.element.attr('aria-activedescendant'), true);
832
							if(act) {
833
								act.find('> .jstree-anchor').focus();
834
							}
835
						}
836
					}, this))
837
				.on('mouseenter.jstree', '.jstree-anchor', $.proxy(function (e) {
838
						this.hover_node(e.currentTarget);
839
					}, this))
840
				.on('mouseleave.jstree', '.jstree-anchor', $.proxy(function (e) {
841
						this.dehover_node(e.currentTarget);
842
					}, this));
843
		},
844
		/**
845
		 * part of the destroying of an instance. Used internally.
846
		 * @private
847
		 * @name unbind()
848
		 */
849
		unbind : function () {
850
			this.element.off('.jstree');
851
			$(document).off('.jstree-' + this._id);
852
		},
853
		/**
854
		 * trigger an event. Used internally.
855
		 * @private
856
		 * @name trigger(ev [, data])
857
		 * @param  {String} ev the name of the event to trigger
858
		 * @param  {Object} data additional data to pass with the event
859
		 */
860
		trigger : function (ev, data) {
861
			if(!data) {
862
				data = {};
863
			}
864
			data.instance = this;
865
			this.element.triggerHandler(ev.replace('.jstree','') + '.jstree', data);
866
		},
867
		/**
868
		 * returns the jQuery extended instance container
869
		 * @name get_container()
870
		 * @return {jQuery}
871
		 */
872
		get_container : function () {
873
			return this.element;
874
		},
875
		/**
876
		 * returns the jQuery extended main UL node inside the instance container. Used internally.
877
		 * @private
878
		 * @name get_container_ul()
879
		 * @return {jQuery}
880
		 */
881
		get_container_ul : function () {
882
			return this.element.children(".jstree-children").first();
883
		},
884
		/**
885
		 * gets string replacements (localization). Used internally.
886
		 * @private
887
		 * @name get_string(key)
888
		 * @param  {String} key
889
		 * @return {String}
890
		 */
891
		get_string : function (key) {
892
			var a = this.settings.core.strings;
893
			if($.isFunction(a)) { return a.call(this, key); }
894
			if(a && a[key]) { return a[key]; }
895
			return key;
896
		},
897
		/**
898
		 * gets the first child of a DOM node. Used internally.
899
		 * @private
900
		 * @name _firstChild(dom)
901
		 * @param  {DOMElement} dom
902
		 * @return {DOMElement}
903
		 */
904
		_firstChild : function (dom) {
905
			dom = dom ? dom.firstChild : null;
906
			while(dom !== null && dom.nodeType !== 1) {
907
				dom = dom.nextSibling;
908
			}
909
			return dom;
910
		},
911
		/**
912
		 * gets the next sibling of a DOM node. Used internally.
913
		 * @private
914
		 * @name _nextSibling(dom)
915
		 * @param  {DOMElement} dom
916
		 * @return {DOMElement}
917
		 */
918
		_nextSibling : function (dom) {
919
			dom = dom ? dom.nextSibling : null;
920
			while(dom !== null && dom.nodeType !== 1) {
921
				dom = dom.nextSibling;
922
			}
923
			return dom;
924
		},
925
		/**
926
		 * gets the previous sibling of a DOM node. Used internally.
927
		 * @private
928
		 * @name _previousSibling(dom)
929
		 * @param  {DOMElement} dom
930
		 * @return {DOMElement}
931
		 */
932
		_previousSibling : function (dom) {
933
			dom = dom ? dom.previousSibling : null;
934
			while(dom !== null && dom.nodeType !== 1) {
935
				dom = dom.previousSibling;
936
			}
937
			return dom;
938
		},
939
		/**
940
		 * 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)
941
		 * @name get_node(obj [, as_dom])
942
		 * @param  {mixed} obj
943
		 * @param  {Boolean} as_dom
944
		 * @return {Object|jQuery}
945
		 */
946
		get_node : function (obj, as_dom) {
947
			if(obj && obj.id) {
948
				obj = obj.id;
949
			}
950
			var dom;
951
			try {
952
				if(this._model.data[obj]) {
953
					obj = this._model.data[obj];
954
				}
955
				else if(typeof obj === "string" && this._model.data[obj.replace(/^#/, '')]) {
956
					obj = this._model.data[obj.replace(/^#/, '')];
957
				}
958
				else if(typeof obj === "string" && (dom = $('#' + obj.replace($.jstree.idregex,'\\$&'), this.element)).length && this._model.data[dom.closest('.jstree-node').attr('id')]) {
959
					obj = this._model.data[dom.closest('.jstree-node').attr('id')];
960
				}
961
				else if((dom = $(obj, this.element)).length && this._model.data[dom.closest('.jstree-node').attr('id')]) {
962
					obj = this._model.data[dom.closest('.jstree-node').attr('id')];
963
				}
964
				else if((dom = $(obj, this.element)).length && dom.hasClass('jstree')) {
965
					obj = this._model.data[$.jstree.root];
966
				}
967
				else {
968
					return false;
969
				}
970
 
971
				if(as_dom) {
972
					obj = obj.id === $.jstree.root ? this.element : $('#' + obj.id.replace($.jstree.idregex,'\\$&'), this.element);
973
				}
974
				return obj;
975
			} catch (ex) { return false; }
976
		},
977
		/**
978
		 * get the path to a node, either consisting of node texts, or of node IDs, optionally glued together (otherwise an array)
979
		 * @name get_path(obj [, glue, ids])
980
		 * @param  {mixed} obj the node
981
		 * @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
982
		 * @param  {Boolean} ids if set to true build the path using ID, otherwise node text is used
983
		 * @return {mixed}
984
		 */
985
		get_path : function (obj, glue, ids) {
986
			obj = obj.parents ? obj : this.get_node(obj);
987
			if(!obj || obj.id === $.jstree.root || !obj.parents) {
988
				return false;
989
			}
990
			var i, j, p = [];
991
			p.push(ids ? obj.id : obj.text);
992
			for(i = 0, j = obj.parents.length; i < j; i++) {
993
				p.push(ids ? obj.parents[i] : this.get_text(obj.parents[i]));
994
			}
995
			p = p.reverse().slice(1);
996
			return glue ? p.join(glue) : p;
997
		},
998
		/**
999
		 * get the next visible node that is below the `obj` node. If `strict` is set to `true` only sibling nodes are returned.
1000
		 * @name get_next_dom(obj [, strict])
1001
		 * @param  {mixed} obj
1002
		 * @param  {Boolean} strict
1003
		 * @return {jQuery}
1004
		 */
1005
		get_next_dom : function (obj, strict) {
1006
			var tmp;
1007
			obj = this.get_node(obj, true);
1008
			if(obj[0] === this.element[0]) {
1009
				tmp = this._firstChild(this.get_container_ul()[0]);
1010
				while (tmp && tmp.offsetHeight === 0) {
1011
					tmp = this._nextSibling(tmp);
1012
				}
1013
				return tmp ? $(tmp) : false;
1014
			}
1015
			if(!obj || !obj.length) {
1016
				return false;
1017
			}
1018
			if(strict) {
1019
				tmp = obj[0];
1020
				do {
1021
					tmp = this._nextSibling(tmp);
1022
				} while (tmp && tmp.offsetHeight === 0);
1023
				return tmp ? $(tmp) : false;
1024
			}
1025
			if(obj.hasClass("jstree-open")) {
1026
				tmp = this._firstChild(obj.children('.jstree-children')[0]);
1027
				while (tmp && tmp.offsetHeight === 0) {
1028
					tmp = this._nextSibling(tmp);
1029
				}
1030
				if(tmp !== null) {
1031
					return $(tmp);
1032
				}
1033
			}
1034
			tmp = obj[0];
1035
			do {
1036
				tmp = this._nextSibling(tmp);
1037
			} while (tmp && tmp.offsetHeight === 0);
1038
			if(tmp !== null) {
1039
				return $(tmp);
1040
			}
1041
			return obj.parentsUntil(".jstree",".jstree-node").nextAll(".jstree-node:visible").first();
1042
		},
1043
		/**
1044
		 * get the previous visible node that is above the `obj` node. If `strict` is set to `true` only sibling nodes are returned.
1045
		 * @name get_prev_dom(obj [, strict])
1046
		 * @param  {mixed} obj
1047
		 * @param  {Boolean} strict
1048
		 * @return {jQuery}
1049
		 */
1050
		get_prev_dom : function (obj, strict) {
1051
			var tmp;
1052
			obj = this.get_node(obj, true);
1053
			if(obj[0] === this.element[0]) {
1054
				tmp = this.get_container_ul()[0].lastChild;
1055
				while (tmp && tmp.offsetHeight === 0) {
1056
					tmp = this._previousSibling(tmp);
1057
				}
1058
				return tmp ? $(tmp) : false;
1059
			}
1060
			if(!obj || !obj.length) {
1061
				return false;
1062
			}
1063
			if(strict) {
1064
				tmp = obj[0];
1065
				do {
1066
					tmp = this._previousSibling(tmp);
1067
				} while (tmp && tmp.offsetHeight === 0);
1068
				return tmp ? $(tmp) : false;
1069
			}
1070
			tmp = obj[0];
1071
			do {
1072
				tmp = this._previousSibling(tmp);
1073
			} while (tmp && tmp.offsetHeight === 0);
1074
			if(tmp !== null) {
1075
				obj = $(tmp);
1076
				while(obj.hasClass("jstree-open")) {
1077
					obj = obj.children(".jstree-children").first().children(".jstree-node:visible:last");
1078
				}
1079
				return obj;
1080
			}
1081
			tmp = obj[0].parentNode.parentNode;
1082
			return tmp && tmp.className && tmp.className.indexOf('jstree-node') !== -1 ? $(tmp) : false;
1083
		},
1084
		/**
1085
		 * get the parent ID of a node
1086
		 * @name get_parent(obj)
1087
		 * @param  {mixed} obj
1088
		 * @return {String}
1089
		 */
1090
		get_parent : function (obj) {
1091
			obj = this.get_node(obj);
1092
			if(!obj || obj.id === $.jstree.root) {
1093
				return false;
1094
			}
1095
			return obj.parent;
1096
		},
1097
		/**
1098
		 * get a jQuery collection of all the children of a node (node must be rendered)
1099
		 * @name get_children_dom(obj)
1100
		 * @param  {mixed} obj
1101
		 * @return {jQuery}
1102
		 */
1103
		get_children_dom : function (obj) {
1104
			obj = this.get_node(obj, true);
1105
			if(obj[0] === this.element[0]) {
1106
				return this.get_container_ul().children(".jstree-node");
1107
			}
1108
			if(!obj || !obj.length) {
1109
				return false;
1110
			}
1111
			return obj.children(".jstree-children").children(".jstree-node");
1112
		},
1113
		/**
1114
		 * checks if a node has children
1115
		 * @name is_parent(obj)
1116
		 * @param  {mixed} obj
1117
		 * @return {Boolean}
1118
		 */
1119
		is_parent : function (obj) {
1120
			obj = this.get_node(obj);
1121
			return obj && (obj.state.loaded === false || obj.children.length > 0);
1122
		},
1123
		/**
1124
		 * checks if a node is loaded (its children are available)
1125
		 * @name is_loaded(obj)
1126
		 * @param  {mixed} obj
1127
		 * @return {Boolean}
1128
		 */
1129
		is_loaded : function (obj) {
1130
			obj = this.get_node(obj);
1131
			return obj && obj.state.loaded;
1132
		},
1133
		/**
1134
		 * check if a node is currently loading (fetching children)
1135
		 * @name is_loading(obj)
1136
		 * @param  {mixed} obj
1137
		 * @return {Boolean}
1138
		 */
1139
		is_loading : function (obj) {
1140
			obj = this.get_node(obj);
1141
			return obj && obj.state && obj.state.loading;
1142
		},
1143
		/**
1144
		 * check if a node is opened
1145
		 * @name is_open(obj)
1146
		 * @param  {mixed} obj
1147
		 * @return {Boolean}
1148
		 */
1149
		is_open : function (obj) {
1150
			obj = this.get_node(obj);
1151
			return obj && obj.state.opened;
1152
		},
1153
		/**
1154
		 * check if a node is in a closed state
1155
		 * @name is_closed(obj)
1156
		 * @param  {mixed} obj
1157
		 * @return {Boolean}
1158
		 */
1159
		is_closed : function (obj) {
1160
			obj = this.get_node(obj);
1161
			return obj && this.is_parent(obj) && !obj.state.opened;
1162
		},
1163
		/**
1164
		 * check if a node has no children
1165
		 * @name is_leaf(obj)
1166
		 * @param  {mixed} obj
1167
		 * @return {Boolean}
1168
		 */
1169
		is_leaf : function (obj) {
1170
			return !this.is_parent(obj);
1171
		},
1172
		/**
1173
		 * loads a node (fetches its children using the `core.data` setting). Multiple nodes can be passed to by using an array.
1174
		 * @name load_node(obj [, callback])
1175
		 * @param  {mixed} obj
1176
		 * @param  {function} callback a function to be executed once loading is complete, the function is executed in the instance's scope and receives two arguments - the node and a boolean status
1177
		 * @return {Boolean}
1178
		 * @trigger load_node.jstree
1179
		 */
1180
		load_node : function (obj, callback) {
1181
			var k, l, i, j, c;
1182
			if($.isArray(obj)) {
1183
				this._load_nodes(obj.slice(), callback);
1184
				return true;
1185
			}
1186
			obj = this.get_node(obj);
1187
			if(!obj) {
1188
				if(callback) { callback.call(this, obj, false); }
1189
				return false;
1190
			}
1191
			// if(obj.state.loading) { } // the node is already loading - just wait for it to load and invoke callback? but if called implicitly it should be loaded again?
1192
			if(obj.state.loaded) {
1193
				obj.state.loaded = false;
1194
				for(k = 0, l = obj.children_d.length; k < l; k++) {
1195
					for(i = 0, j = obj.parents.length; i < j; i++) {
1196
						this._model.data[obj.parents[i]].children_d = $.vakata.array_remove_item(this._model.data[obj.parents[i]].children_d, obj.children_d[k]);
1197
					}
1198
					if(this._model.data[obj.children_d[k]].state.selected) {
1199
						c = true;
1200
						this._data.core.selected = $.vakata.array_remove_item(this._data.core.selected, obj.children_d[k]);
1201
					}
1202
					delete this._model.data[obj.children_d[k]];
1203
				}
1204
				obj.children = [];
1205
				obj.children_d = [];
1206
				if(c) {
1207
					this.trigger('changed', { 'action' : 'load_node', 'node' : obj, 'selected' : this._data.core.selected });
1208
				}
1209
			}
1210
			obj.state.failed = false;
1211
			obj.state.loading = true;
1212
			this.get_node(obj, true).addClass("jstree-loading").attr('aria-busy',true);
1213
			this._load_node(obj, $.proxy(function (status) {
1214
				obj = this._model.data[obj.id];
1215
				obj.state.loading = false;
1216
				obj.state.loaded = status;
1217
				obj.state.failed = !obj.state.loaded;
1218
				var dom = this.get_node(obj, true), i = 0, j = 0, m = this._model.data, has_children = false;
1219
				for(i = 0, j = obj.children.length; i < j; i++) {
1220
					if(m[obj.children[i]] && !m[obj.children[i]].state.hidden) {
1221
						has_children = true;
1222
						break;
1223
					}
1224
				}
1225
				if(obj.state.loaded && !has_children && dom && dom.length && !dom.hasClass('jstree-leaf')) {
1226
					dom.removeClass('jstree-closed jstree-open').addClass('jstree-leaf');
1227
				}
1228
				dom.removeClass("jstree-loading").attr('aria-busy',false);
1229
				/**
1230
				 * triggered after a node is loaded
1231
				 * @event
1232
				 * @name load_node.jstree
1233
				 * @param {Object} node the node that was loading
1234
				 * @param {Boolean} status was the node loaded successfully
1235
				 */
1236
				this.trigger('load_node', { "node" : obj, "status" : status });
1237
				if(callback) {
1238
					callback.call(this, obj, status);
1239
				}
1240
			}, this));
1241
			return true;
1242
		},
1243
		/**
1244
		 * load an array of nodes (will also load unavailable nodes as soon as the appear in the structure). Used internally.
1245
		 * @private
1246
		 * @name _load_nodes(nodes [, callback])
1247
		 * @param  {array} nodes
1248
		 * @param  {function} callback a function to be executed once loading is complete, the function is executed in the instance's scope and receives one argument - the array passed to _load_nodes
1249
		 */
1250
		_load_nodes : function (nodes, callback, is_callback) {
1251
			var r = true,
1252
				c = function () { this._load_nodes(nodes, callback, true); },
1253
				m = this._model.data, i, j, tmp = [];
1254
			for(i = 0, j = nodes.length; i < j; i++) {
1255
				if(m[nodes[i]] && ( (!m[nodes[i]].state.loaded && !m[nodes[i]].state.failed) || !is_callback)) {
1256
					if(!this.is_loading(nodes[i])) {
1257
						this.load_node(nodes[i], c);
1258
					}
1259
					r = false;
1260
				}
1261
			}
1262
			if(r) {
1263
				for(i = 0, j = nodes.length; i < j; i++) {
1264
					if(m[nodes[i]] && m[nodes[i]].state.loaded) {
1265
						tmp.push(nodes[i]);
1266
					}
1267
				}
1268
				if(callback && !callback.done) {
1269
					callback.call(this, tmp);
1270
					callback.done = true;
1271
				}
1272
			}
1273
		},
1274
		/**
1275
		 * loads all unloaded nodes
1276
		 * @name load_all([obj, callback])
1277
		 * @param {mixed} obj the node to load recursively, omit to load all nodes in the tree
1278
		 * @param {function} callback a function to be executed once loading all the nodes is complete,
1279
		 * @trigger load_all.jstree
1280
		 */
1281
		load_all : function (obj, callback) {
1282
			if(!obj) { obj = $.jstree.root; }
1283
			obj = this.get_node(obj);
1284
			if(!obj) { return false; }
1285
			var to_load = [],
1286
				m = this._model.data,
1287
				c = m[obj.id].children_d,
1288
				i, j;
1289
			if(obj.state && !obj.state.loaded) {
1290
				to_load.push(obj.id);
1291
			}
1292
			for(i = 0, j = c.length; i < j; i++) {
1293
				if(m[c[i]] && m[c[i]].state && !m[c[i]].state.loaded) {
1294
					to_load.push(c[i]);
1295
				}
1296
			}
1297
			if(to_load.length) {
1298
				this._load_nodes(to_load, function () {
1299
					this.load_all(obj, callback);
1300
				});
1301
			}
1302
			else {
1303
				/**
1304
				 * triggered after a load_all call completes
1305
				 * @event
1306
				 * @name load_all.jstree
1307
				 * @param {Object} node the recursively loaded node
1308
				 */
1309
				if(callback) { callback.call(this, obj); }
1310
				this.trigger('load_all', { "node" : obj });
1311
			}
1312
		},
1313
		/**
1314
		 * handles the actual loading of a node. Used only internally.
1315
		 * @private
1316
		 * @name _load_node(obj [, callback])
1317
		 * @param  {mixed} obj
1318
		 * @param  {function} callback a function to be executed once loading is complete, the function is executed in the instance's scope and receives one argument - a boolean status
1319
		 * @return {Boolean}
1320
		 */
1321
		_load_node : function (obj, callback) {
1322
			var s = this.settings.core.data, t;
1323
			// use original HTML
1324
			if(!s) {
1325
				if(obj.id === $.jstree.root) {
1326
					return this._append_html_data(obj, this._data.core.original_container_html.clone(true), function (status) {
1327
						callback.call(this, status);
1328
					});
1329
				}
1330
				else {
1331
					return callback.call(this, false);
1332
				}
1333
				// return callback.call(this, obj.id === $.jstree.root ? this._append_html_data(obj, this._data.core.original_container_html.clone(true)) : false);
1334
			}
1335
			if($.isFunction(s)) {
1336
				return s.call(this, obj, $.proxy(function (d) {
1337
					if(d === false) {
1338
						callback.call(this, false);
1339
					}
1340
					this[typeof d === 'string' ? '_append_html_data' : '_append_json_data'](obj, typeof d === 'string' ? $($.parseHTML(d)).filter(function () { return this.nodeType !== 3; }) : d, function (status) {
1341
						callback.call(this, status);
1342
					});
1343
					// 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));
1344
				}, this));
1345
			}
1346
			if(typeof s === 'object') {
1347
				if(s.url) {
1348
					s = $.extend(true, {}, s);
1349
					if($.isFunction(s.url)) {
1350
						s.url = s.url.call(this, obj);
1351
					}
1352
					if($.isFunction(s.data)) {
1353
						s.data = s.data.call(this, obj);
1354
					}
1355
					return $.ajax(s)
1356
						.done($.proxy(function (d,t,x) {
1357
								var type = x.getResponseHeader('Content-Type');
1358
								if((type && type.indexOf('json') !== -1) || typeof d === "object") {
1359
									return this._append_json_data(obj, d, function (status) { callback.call(this, status); });
1360
									//return callback.call(this, this._append_json_data(obj, d));
1361
								}
1362
								if((type && type.indexOf('html') !== -1) || typeof d === "string") {
1363
									return this._append_html_data(obj, $($.parseHTML(d)).filter(function () { return this.nodeType !== 3; }), function (status) { callback.call(this, status); });
1364
									// return callback.call(this, this._append_html_data(obj, $(d)));
1365
								}
1366
								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 }) };
1367
								this.settings.core.error.call(this, this._data.core.last_error);
1368
								return callback.call(this, false);
1369
							}, this))
1370
						.fail($.proxy(function (f) {
1371
								callback.call(this, false);
1372
								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 }) };
1373
								this.settings.core.error.call(this, this._data.core.last_error);
1374
							}, this));
1375
				}
1376
				t = ($.isArray(s) || $.isPlainObject(s)) ? JSON.parse(JSON.stringify(s)) : s;
1377
				if(obj.id === $.jstree.root) {
1378
					return this._append_json_data(obj, t, function (status) {
1379
						callback.call(this, status);
1380
					});
1381
				}
1382
				else {
1383
					this._data.core.last_error = { 'error' : 'nodata', 'plugin' : 'core', 'id' : 'core_05', 'reason' : 'Could not load node', 'data' : JSON.stringify({ 'id' : obj.id }) };
1384
					this.settings.core.error.call(this, this._data.core.last_error);
1385
					return callback.call(this, false);
1386
				}
1387
				//return callback.call(this, (obj.id === $.jstree.root ? this._append_json_data(obj, t) : false) );
1388
			}
1389
			if(typeof s === 'string') {
1390
				if(obj.id === $.jstree.root) {
1391
					return this._append_html_data(obj, $($.parseHTML(s)).filter(function () { return this.nodeType !== 3; }), function (status) {
1392
						callback.call(this, status);
1393
					});
1394
				}
1395
				else {
1396
					this._data.core.last_error = { 'error' : 'nodata', 'plugin' : 'core', 'id' : 'core_06', 'reason' : 'Could not load node', 'data' : JSON.stringify({ 'id' : obj.id }) };
1397
					this.settings.core.error.call(this, this._data.core.last_error);
1398
					return callback.call(this, false);
1399
				}
1400
				//return callback.call(this, (obj.id === $.jstree.root ? this._append_html_data(obj, $(s)) : false) );
1401
			}
1402
			return callback.call(this, false);
1403
		},
1404
		/**
1405
		 * adds a node to the list of nodes to redraw. Used only internally.
1406
		 * @private
1407
		 * @name _node_changed(obj [, callback])
1408
		 * @param  {mixed} obj
1409
		 */
1410
		_node_changed : function (obj) {
1411
			obj = this.get_node(obj);
1412
			if(obj) {
1413
				this._model.changed.push(obj.id);
1414
			}
1415
		},
1416
		/**
1417
		 * appends HTML content to the tree. Used internally.
1418
		 * @private
1419
		 * @name _append_html_data(obj, data)
1420
		 * @param  {mixed} obj the node to append to
1421
		 * @param  {String} data the HTML string to parse and append
1422
		 * @trigger model.jstree, changed.jstree
1423
		 */
1424
		_append_html_data : function (dom, data, cb) {
1425
			dom = this.get_node(dom);
1426
			dom.children = [];
1427
			dom.children_d = [];
1428
			var dat = data.is('ul') ? data.children() : data,
1429
				par = dom.id,
1430
				chd = [],
1431
				dpc = [],
1432
				m = this._model.data,
1433
				p = m[par],
1434
				s = this._data.core.selected.length,
1435
				tmp, i, j;
1436
			dat.each($.proxy(function (i, v) {
1437
				tmp = this._parse_model_from_html($(v), par, p.parents.concat());
1438
				if(tmp) {
1439
					chd.push(tmp);
1440
					dpc.push(tmp);
1441
					if(m[tmp].children_d.length) {
1442
						dpc = dpc.concat(m[tmp].children_d);
1443
					}
1444
				}
1445
			}, this));
1446
			p.children = chd;
1447
			p.children_d = dpc;
1448
			for(i = 0, j = p.parents.length; i < j; i++) {
1449
				m[p.parents[i]].children_d = m[p.parents[i]].children_d.concat(dpc);
1450
			}
1451
			/**
1452
			 * triggered when new data is inserted to the tree model
1453
			 * @event
1454
			 * @name model.jstree
1455
			 * @param {Array} nodes an array of node IDs
1456
			 * @param {String} parent the parent ID of the nodes
1457
			 */
1458
			this.trigger('model', { "nodes" : dpc, 'parent' : par });
1459
			if(par !== $.jstree.root) {
1460
				this._node_changed(par);
1461
				this.redraw();
1462
			}
1463
			else {
1464
				this.get_container_ul().children('.jstree-initial-node').remove();
1465
				this.redraw(true);
1466
			}
1467
			if(this._data.core.selected.length !== s) {
1468
				this.trigger('changed', { 'action' : 'model', 'selected' : this._data.core.selected });
1469
			}
1470
			cb.call(this, true);
1471
		},
1472
		/**
1473
		 * appends JSON content to the tree. Used internally.
1474
		 * @private
1475
		 * @name _append_json_data(obj, data)
1476
		 * @param  {mixed} obj the node to append to
1477
		 * @param  {String} data the JSON object to parse and append
1478
		 * @param  {Boolean} force_processing internal param - do not set
1479
		 * @trigger model.jstree, changed.jstree
1480
		 */
1481
		_append_json_data : function (dom, data, cb, force_processing) {
1482
			if(this.element === null) { return; }
1483
			dom = this.get_node(dom);
1484
			dom.children = [];
1485
			dom.children_d = [];
1486
			// *%$@!!!
1487
			if(data.d) {
1488
				data = data.d;
1489
				if(typeof data === "string") {
1490
					data = JSON.parse(data);
1491
				}
1492
			}
1493
			if(!$.isArray(data)) { data = [data]; }
1494
			var w = null,
1495
				args = {
1496
					'df'	: this._model.default_state,
1497
					'dat'	: data,
1498
					'par'	: dom.id,
1499
					'm'		: this._model.data,
1500
					't_id'	: this._id,
1501
					't_cnt'	: this._cnt,
1502
					'sel'	: this._data.core.selected
1503
				},
1504
				func = function (data, undefined) {
1505
					if(data.data) { data = data.data; }
1506
					var dat = data.dat,
1507
						par = data.par,
1508
						chd = [],
1509
						dpc = [],
1510
						add = [],
1511
						df = data.df,
1512
						t_id = data.t_id,
1513
						t_cnt = data.t_cnt,
1514
						m = data.m,
1515
						p = m[par],
1516
						sel = data.sel,
1517
						tmp, i, j, rslt,
1518
						parse_flat = function (d, p, ps) {
1519
							if(!ps) { ps = []; }
1520
							else { ps = ps.concat(); }
1521
							if(p) { ps.unshift(p); }
1522
							var tid = d.id.toString(),
1523
								i, j, c, e,
1524
								tmp = {
1525
									id			: tid,
1526
									text		: d.text || '',
1527
									icon		: d.icon !== undefined ? d.icon : true,
1528
									parent		: p,
1529
									parents		: ps,
1530
									children	: d.children || [],
1531
									children_d	: d.children_d || [],
1532
									data		: d.data,
1533
									state		: { },
1534
									li_attr		: { id : false },
1535
									a_attr		: { href : '#' },
1536
									original	: false
1537
								};
1538
							for(i in df) {
1539
								if(df.hasOwnProperty(i)) {
1540
									tmp.state[i] = df[i];
1541
								}
1542
							}
1543
							if(d && d.data && d.data.jstree && d.data.jstree.icon) {
1544
								tmp.icon = d.data.jstree.icon;
1545
							}
1546
							if(tmp.icon === undefined || tmp.icon === null || tmp.icon === "") {
1547
								tmp.icon = true;
1548
							}
1549
							if(d && d.data) {
1550
								tmp.data = d.data;
1551
								if(d.data.jstree) {
1552
									for(i in d.data.jstree) {
1553
										if(d.data.jstree.hasOwnProperty(i)) {
1554
											tmp.state[i] = d.data.jstree[i];
1555
										}
1556
									}
1557
								}
1558
							}
1559
							if(d && typeof d.state === 'object') {
1560
								for (i in d.state) {
1561
									if(d.state.hasOwnProperty(i)) {
1562
										tmp.state[i] = d.state[i];
1563
									}
1564
								}
1565
							}
1566
							if(d && typeof d.li_attr === 'object') {
1567
								for (i in d.li_attr) {
1568
									if(d.li_attr.hasOwnProperty(i)) {
1569
										tmp.li_attr[i] = d.li_attr[i];
1570
									}
1571
								}
1572
							}
1573
							if(!tmp.li_attr.id) {
1574
								tmp.li_attr.id = tid;
1575
							}
1576
							if(d && typeof d.a_attr === 'object') {
1577
								for (i in d.a_attr) {
1578
									if(d.a_attr.hasOwnProperty(i)) {
1579
										tmp.a_attr[i] = d.a_attr[i];
1580
									}
1581
								}
1582
							}
1583
							if(d && d.children && d.children === true) {
1584
								tmp.state.loaded = false;
1585
								tmp.children = [];
1586
								tmp.children_d = [];
1587
							}
1588
							m[tmp.id] = tmp;
1589
							for(i = 0, j = tmp.children.length; i < j; i++) {
1590
								c = parse_flat(m[tmp.children[i]], tmp.id, ps);
1591
								e = m[c];
1592
								tmp.children_d.push(c);
1593
								if(e.children_d.length) {
1594
									tmp.children_d = tmp.children_d.concat(e.children_d);
1595
								}
1596
							}
1597
							delete d.data;
1598
							delete d.children;
1599
							m[tmp.id].original = d;
1600
							if(tmp.state.selected) {
1601
								add.push(tmp.id);
1602
							}
1603
							return tmp.id;
1604
						},
1605
						parse_nest = function (d, p, ps) {
1606
							if(!ps) { ps = []; }
1607
							else { ps = ps.concat(); }
1608
							if(p) { ps.unshift(p); }
1609
							var tid = false, i, j, c, e, tmp;
1610
							do {
1611
								tid = 'j' + t_id + '_' + (++t_cnt);
1612
							} while(m[tid]);
1613
 
1614
							tmp = {
1615
								id			: false,
1616
								text		: typeof d === 'string' ? d : '',
1617
								icon		: typeof d === 'object' && d.icon !== undefined ? d.icon : true,
1618
								parent		: p,
1619
								parents		: ps,
1620
								children	: [],
1621
								children_d	: [],
1622
								data		: null,
1623
								state		: { },
1624
								li_attr		: { id : false },
1625
								a_attr		: { href : '#' },
1626
								original	: false
1627
							};
1628
							for(i in df) {
1629
								if(df.hasOwnProperty(i)) {
1630
									tmp.state[i] = df[i];
1631
								}
1632
							}
1633
							if(d && d.id) { tmp.id = d.id.toString(); }
1634
							if(d && d.text) { tmp.text = d.text; }
1635
							if(d && d.data && d.data.jstree && d.data.jstree.icon) {
1636
								tmp.icon = d.data.jstree.icon;
1637
							}
1638
							if(tmp.icon === undefined || tmp.icon === null || tmp.icon === "") {
1639
								tmp.icon = true;
1640
							}
1641
							if(d && d.data) {
1642
								tmp.data = d.data;
1643
								if(d.data.jstree) {
1644
									for(i in d.data.jstree) {
1645
										if(d.data.jstree.hasOwnProperty(i)) {
1646
											tmp.state[i] = d.data.jstree[i];
1647
										}
1648
									}
1649
								}
1650
							}
1651
							if(d && typeof d.state === 'object') {
1652
								for (i in d.state) {
1653
									if(d.state.hasOwnProperty(i)) {
1654
										tmp.state[i] = d.state[i];
1655
									}
1656
								}
1657
							}
1658
							if(d && typeof d.li_attr === 'object') {
1659
								for (i in d.li_attr) {
1660
									if(d.li_attr.hasOwnProperty(i)) {
1661
										tmp.li_attr[i] = d.li_attr[i];
1662
									}
1663
								}
1664
							}
1665
							if(tmp.li_attr.id && !tmp.id) {
1666
								tmp.id = tmp.li_attr.id.toString();
1667
							}
1668
							if(!tmp.id) {
1669
								tmp.id = tid;
1670
							}
1671
							if(!tmp.li_attr.id) {
1672
								tmp.li_attr.id = tmp.id;
1673
							}
1674
							if(d && typeof d.a_attr === 'object') {
1675
								for (i in d.a_attr) {
1676
									if(d.a_attr.hasOwnProperty(i)) {
1677
										tmp.a_attr[i] = d.a_attr[i];
1678
									}
1679
								}
1680
							}
1681
							if(d && d.children && d.children.length) {
1682
								for(i = 0, j = d.children.length; i < j; i++) {
1683
									c = parse_nest(d.children[i], tmp.id, ps);
1684
									e = m[c];
1685
									tmp.children.push(c);
1686
									if(e.children_d.length) {
1687
										tmp.children_d = tmp.children_d.concat(e.children_d);
1688
									}
1689
								}
1690
								tmp.children_d = tmp.children_d.concat(tmp.children);
1691
							}
1692
							if(d && d.children && d.children === true) {
1693
								tmp.state.loaded = false;
1694
								tmp.children = [];
1695
								tmp.children_d = [];
1696
							}
1697
							delete d.data;
1698
							delete d.children;
1699
							tmp.original = d;
1700
							m[tmp.id] = tmp;
1701
							if(tmp.state.selected) {
1702
								add.push(tmp.id);
1703
							}
1704
							return tmp.id;
1705
						};
1706
 
1707
					if(dat.length && dat[0].id !== undefined && dat[0].parent !== undefined) {
1708
						// Flat JSON support (for easy import from DB):
1709
						// 1) convert to object (foreach)
1710
						for(i = 0, j = dat.length; i < j; i++) {
1711
							if(!dat[i].children) {
1712
								dat[i].children = [];
1713
							}
1714
							m[dat[i].id.toString()] = dat[i];
1715
						}
1716
						// 2) populate children (foreach)
1717
						for(i = 0, j = dat.length; i < j; i++) {
1718
							m[dat[i].parent.toString()].children.push(dat[i].id.toString());
1719
							// populate parent.children_d
1720
							p.children_d.push(dat[i].id.toString());
1721
						}
1722
						// 3) normalize && populate parents and children_d with recursion
1723
						for(i = 0, j = p.children.length; i < j; i++) {
1724
							tmp = parse_flat(m[p.children[i]], par, p.parents.concat());
1725
							dpc.push(tmp);
1726
							if(m[tmp].children_d.length) {
1727
								dpc = dpc.concat(m[tmp].children_d);
1728
							}
1729
						}
1730
						for(i = 0, j = p.parents.length; i < j; i++) {
1731
							m[p.parents[i]].children_d = m[p.parents[i]].children_d.concat(dpc);
1732
						}
1733
						// ?) three_state selection - p.state.selected && t - (if three_state foreach(dat => ch) -> foreach(parents) if(parent.selected) child.selected = true;
1734
						rslt = {
1735
							'cnt' : t_cnt,
1736
							'mod' : m,
1737
							'sel' : sel,
1738
							'par' : par,
1739
							'dpc' : dpc,
1740
							'add' : add
1741
						};
1742
					}
1743
					else {
1744
						for(i = 0, j = dat.length; i < j; i++) {
1745
							tmp = parse_nest(dat[i], par, p.parents.concat());
1746
							if(tmp) {
1747
								chd.push(tmp);
1748
								dpc.push(tmp);
1749
								if(m[tmp].children_d.length) {
1750
									dpc = dpc.concat(m[tmp].children_d);
1751
								}
1752
							}
1753
						}
1754
						p.children = chd;
1755
						p.children_d = dpc;
1756
						for(i = 0, j = p.parents.length; i < j; i++) {
1757
							m[p.parents[i]].children_d = m[p.parents[i]].children_d.concat(dpc);
1758
						}
1759
						rslt = {
1760
							'cnt' : t_cnt,
1761
							'mod' : m,
1762
							'sel' : sel,
1763
							'par' : par,
1764
							'dpc' : dpc,
1765
							'add' : add
1766
						};
1767
					}
1768
					if(typeof window === 'undefined' || typeof window.document === 'undefined') {
1769
						postMessage(rslt);
1770
					}
1771
					else {
1772
						return rslt;
1773
					}
1774
				},
1775
				rslt = function (rslt, worker) {
1776
					if(this.element === null) { return; }
1777
					this._cnt = rslt.cnt;
1778
					this._model.data = rslt.mod; // breaks the reference in load_node - careful
1779
 
1780
					if(worker) {
1781
						var i, j, a = rslt.add, r = rslt.sel, s = this._data.core.selected.slice(), m = this._model.data;
1782
						// if selection was changed while calculating in worker
1783
						if(r.length !== s.length || $.vakata.array_unique(r.concat(s)).length !== r.length) {
1784
							// deselect nodes that are no longer selected
1785
							for(i = 0, j = r.length; i < j; i++) {
1786
								if($.inArray(r[i], a) === -1 && $.inArray(r[i], s) === -1) {
1787
									m[r[i]].state.selected = false;
1788
								}
1789
							}
1790
							// select nodes that were selected in the mean time
1791
							for(i = 0, j = s.length; i < j; i++) {
1792
								if($.inArray(s[i], r) === -1) {
1793
									m[s[i]].state.selected = true;
1794
								}
1795
							}
1796
						}
1797
					}
1798
					if(rslt.add.length) {
1799
						this._data.core.selected = this._data.core.selected.concat(rslt.add);
1800
					}
1801
 
1802
					this.trigger('model', { "nodes" : rslt.dpc, 'parent' : rslt.par });
1803
 
1804
					if(rslt.par !== $.jstree.root) {
1805
						this._node_changed(rslt.par);
1806
						this.redraw();
1807
					}
1808
					else {
1809
						// this.get_container_ul().children('.jstree-initial-node').remove();
1810
						this.redraw(true);
1811
					}
1812
					if(rslt.add.length) {
1813
						this.trigger('changed', { 'action' : 'model', 'selected' : this._data.core.selected });
1814
					}
1815
					cb.call(this, true);
1816
				};
1817
			if(this.settings.core.worker && window.Blob && window.URL && window.Worker) {
1818
				try {
1819
					if(this._wrk === null) {
1820
						this._wrk = window.URL.createObjectURL(
1821
							new window.Blob(
1822
								['self.onmessage = ' + func.toString()],
1823
								{type:"text/javascript"}
1824
							)
1825
						);
1826
					}
1827
					if(!this._data.core.working || force_processing) {
1828
						this._data.core.working = true;
1829
						w = new window.Worker(this._wrk);
1830
						w.onmessage = $.proxy(function (e) {
1831
							rslt.call(this, e.data, true);
1832
							try { w.terminate(); w = null; } catch(ignore) { }
1833
							if(this._data.core.worker_queue.length) {
1834
								this._append_json_data.apply(this, this._data.core.worker_queue.shift());
1835
							}
1836
							else {
1837
								this._data.core.working = false;
1838
							}
1839
						}, this);
1840
						if(!args.par) {
1841
							if(this._data.core.worker_queue.length) {
1842
								this._append_json_data.apply(this, this._data.core.worker_queue.shift());
1843
							}
1844
							else {
1845
								this._data.core.working = false;
1846
							}
1847
						}
1848
						else {
1849
							w.postMessage(args);
1850
						}
1851
					}
1852
					else {
1853
						this._data.core.worker_queue.push([dom, data, cb, true]);
1854
					}
1855
				}
1856
				catch(e) {
1857
					rslt.call(this, func(args), false);
1858
					if(this._data.core.worker_queue.length) {
1859
						this._append_json_data.apply(this, this._data.core.worker_queue.shift());
1860
					}
1861
					else {
1862
						this._data.core.working = false;
1863
					}
1864
				}
1865
			}
1866
			else {
1867
				rslt.call(this, func(args), false);
1868
			}
1869
		},
1870
		/**
1871
		 * parses a node from a jQuery object and appends them to the in memory tree model. Used internally.
1872
		 * @private
1873
		 * @name _parse_model_from_html(d [, p, ps])
1874
		 * @param  {jQuery} d the jQuery object to parse
1875
		 * @param  {String} p the parent ID
1876
		 * @param  {Array} ps list of all parents
1877
		 * @return {String} the ID of the object added to the model
1878
		 */
1879
		_parse_model_from_html : function (d, p, ps) {
1880
			if(!ps) { ps = []; }
1881
			else { ps = [].concat(ps); }
1882
			if(p) { ps.unshift(p); }
1883
			var c, e, m = this._model.data,
1884
				data = {
1885
					id			: false,
1886
					text		: false,
1887
					icon		: true,
1888
					parent		: p,
1889
					parents		: ps,
1890
					children	: [],
1891
					children_d	: [],
1892
					data		: null,
1893
					state		: { },
1894
					li_attr		: { id : false },
1895
					a_attr		: { href : '#' },
1896
					original	: false
1897
				}, i, tmp, tid;
1898
			for(i in this._model.default_state) {
1899
				if(this._model.default_state.hasOwnProperty(i)) {
1900
					data.state[i] = this._model.default_state[i];
1901
				}
1902
			}
1903
			tmp = $.vakata.attributes(d, true);
1904
			$.each(tmp, function (i, v) {
1905
				v = $.trim(v);
1906
				if(!v.length) { return true; }
1907
				data.li_attr[i] = v;
1908
				if(i === 'id') {
1909
					data.id = v.toString();
1910
				}
1911
			});
1912
			tmp = d.children('a').first();
1913
			if(tmp.length) {
1914
				tmp = $.vakata.attributes(tmp, true);
1915
				$.each(tmp, function (i, v) {
1916
					v = $.trim(v);
1917
					if(v.length) {
1918
						data.a_attr[i] = v;
1919
					}
1920
				});
1921
			}
1922
			tmp = d.children("a").first().length ? d.children("a").first().clone() : d.clone();
1923
			tmp.children("ins, i, ul").remove();
1924
			tmp = tmp.html();
1925
			tmp = $('<div />').html(tmp);
1926
			data.text = this.settings.core.force_text ? tmp.text() : tmp.html();
1927
			tmp = d.data();
1928
			data.data = tmp ? $.extend(true, {}, tmp) : null;
1929
			data.state.opened = d.hasClass('jstree-open');
1930
			data.state.selected = d.children('a').hasClass('jstree-clicked');
1931
			data.state.disabled = d.children('a').hasClass('jstree-disabled');
1932
			if(data.data && data.data.jstree) {
1933
				for(i in data.data.jstree) {
1934
					if(data.data.jstree.hasOwnProperty(i)) {
1935
						data.state[i] = data.data.jstree[i];
1936
					}
1937
				}
1938
			}
1939
			tmp = d.children("a").children(".jstree-themeicon");
1940
			if(tmp.length) {
1941
				data.icon = tmp.hasClass('jstree-themeicon-hidden') ? false : tmp.attr('rel');
1942
			}
1943
			if(data.state.icon !== undefined) {
1944
				data.icon = data.state.icon;
1945
			}
1946
			if(data.icon === undefined || data.icon === null || data.icon === "") {
1947
				data.icon = true;
1948
			}
1949
			tmp = d.children("ul").children("li");
1950
			do {
1951
				tid = 'j' + this._id + '_' + (++this._cnt);
1952
			} while(m[tid]);
1953
			data.id = data.li_attr.id ? data.li_attr.id.toString() : tid;
1954
			if(tmp.length) {
1955
				tmp.each($.proxy(function (i, v) {
1956
					c = this._parse_model_from_html($(v), data.id, ps);
1957
					e = this._model.data[c];
1958
					data.children.push(c);
1959
					if(e.children_d.length) {
1960
						data.children_d = data.children_d.concat(e.children_d);
1961
					}
1962
				}, this));
1963
				data.children_d = data.children_d.concat(data.children);
1964
			}
1965
			else {
1966
				if(d.hasClass('jstree-closed')) {
1967
					data.state.loaded = false;
1968
				}
1969
			}
1970
			if(data.li_attr['class']) {
1971
				data.li_attr['class'] = data.li_attr['class'].replace('jstree-closed','').replace('jstree-open','');
1972
			}
1973
			if(data.a_attr['class']) {
1974
				data.a_attr['class'] = data.a_attr['class'].replace('jstree-clicked','').replace('jstree-disabled','');
1975
			}
1976
			m[data.id] = data;
1977
			if(data.state.selected) {
1978
				this._data.core.selected.push(data.id);
1979
			}
1980
			return data.id;
1981
		},
1982
		/**
1983
		 * 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.
1984
		 * @private
1985
		 * @name _parse_model_from_flat_json(d [, p, ps])
1986
		 * @param  {Object} d the JSON object to parse
1987
		 * @param  {String} p the parent ID
1988
		 * @param  {Array} ps list of all parents
1989
		 * @return {String} the ID of the object added to the model
1990
		 */
1991
		_parse_model_from_flat_json : function (d, p, ps) {
1992
			if(!ps) { ps = []; }
1993
			else { ps = ps.concat(); }
1994
			if(p) { ps.unshift(p); }
1995
			var tid = d.id.toString(),
1996
				m = this._model.data,
1997
				df = this._model.default_state,
1998
				i, j, c, e,
1999
				tmp = {
2000
					id			: tid,
2001
					text		: d.text || '',
2002
					icon		: d.icon !== undefined ? d.icon : true,
2003
					parent		: p,
2004
					parents		: ps,
2005
					children	: d.children || [],
2006
					children_d	: d.children_d || [],
2007
					data		: d.data,
2008
					state		: { },
2009
					li_attr		: { id : false },
2010
					a_attr		: { href : '#' },
2011
					original	: false
2012
				};
2013
			for(i in df) {
2014
				if(df.hasOwnProperty(i)) {
2015
					tmp.state[i] = df[i];
2016
				}
2017
			}
2018
			if(d && d.data && d.data.jstree && d.data.jstree.icon) {
2019
				tmp.icon = d.data.jstree.icon;
2020
			}
2021
			if(tmp.icon === undefined || tmp.icon === null || tmp.icon === "") {
2022
				tmp.icon = true;
2023
			}
2024
			if(d && d.data) {
2025
				tmp.data = d.data;
2026
				if(d.data.jstree) {
2027
					for(i in d.data.jstree) {
2028
						if(d.data.jstree.hasOwnProperty(i)) {
2029
							tmp.state[i] = d.data.jstree[i];
2030
						}
2031
					}
2032
				}
2033
			}
2034
			if(d && typeof d.state === 'object') {
2035
				for (i in d.state) {
2036
					if(d.state.hasOwnProperty(i)) {
2037
						tmp.state[i] = d.state[i];
2038
					}
2039
				}
2040
			}
2041
			if(d && typeof d.li_attr === 'object') {
2042
				for (i in d.li_attr) {
2043
					if(d.li_attr.hasOwnProperty(i)) {
2044
						tmp.li_attr[i] = d.li_attr[i];
2045
					}
2046
				}
2047
			}
2048
			if(!tmp.li_attr.id) {
2049
				tmp.li_attr.id = tid;
2050
			}
2051
			if(d && typeof d.a_attr === 'object') {
2052
				for (i in d.a_attr) {
2053
					if(d.a_attr.hasOwnProperty(i)) {
2054
						tmp.a_attr[i] = d.a_attr[i];
2055
					}
2056
				}
2057
			}
2058
			if(d && d.children && d.children === true) {
2059
				tmp.state.loaded = false;
2060
				tmp.children = [];
2061
				tmp.children_d = [];
2062
			}
2063
			m[tmp.id] = tmp;
2064
			for(i = 0, j = tmp.children.length; i < j; i++) {
2065
				c = this._parse_model_from_flat_json(m[tmp.children[i]], tmp.id, ps);
2066
				e = m[c];
2067
				tmp.children_d.push(c);
2068
				if(e.children_d.length) {
2069
					tmp.children_d = tmp.children_d.concat(e.children_d);
2070
				}
2071
			}
2072
			delete d.data;
2073
			delete d.children;
2074
			m[tmp.id].original = d;
2075
			if(tmp.state.selected) {
2076
				this._data.core.selected.push(tmp.id);
2077
			}
2078
			return tmp.id;
2079
		},
2080
		/**
2081
		 * parses a node from a JSON object and appends it to the in memory tree model. Used internally.
2082
		 * @private
2083
		 * @name _parse_model_from_json(d [, p, ps])
2084
		 * @param  {Object} d the JSON object to parse
2085
		 * @param  {String} p the parent ID
2086
		 * @param  {Array} ps list of all parents
2087
		 * @return {String} the ID of the object added to the model
2088
		 */
2089
		_parse_model_from_json : function (d, p, ps) {
2090
			if(!ps) { ps = []; }
2091
			else { ps = ps.concat(); }
2092
			if(p) { ps.unshift(p); }
2093
			var tid = false, i, j, c, e, m = this._model.data, df = this._model.default_state, tmp;
2094
			do {
2095
				tid = 'j' + this._id + '_' + (++this._cnt);
2096
			} while(m[tid]);
2097
 
2098
			tmp = {
2099
				id			: false,
2100
				text		: typeof d === 'string' ? d : '',
2101
				icon		: typeof d === 'object' && d.icon !== undefined ? d.icon : true,
2102
				parent		: p,
2103
				parents		: ps,
2104
				children	: [],
2105
				children_d	: [],
2106
				data		: null,
2107
				state		: { },
2108
				li_attr		: { id : false },
2109
				a_attr		: { href : '#' },
2110
				original	: false
2111
			};
2112
			for(i in df) {
2113
				if(df.hasOwnProperty(i)) {
2114
					tmp.state[i] = df[i];
2115
				}
2116
			}
2117
			if(d && d.id) { tmp.id = d.id.toString(); }
2118
			if(d && d.text) { tmp.text = d.text; }
2119
			if(d && d.data && d.data.jstree && d.data.jstree.icon) {
2120
				tmp.icon = d.data.jstree.icon;
2121
			}
2122
			if(tmp.icon === undefined || tmp.icon === null || tmp.icon === "") {
2123
				tmp.icon = true;
2124
			}
2125
			if(d && d.data) {
2126
				tmp.data = d.data;
2127
				if(d.data.jstree) {
2128
					for(i in d.data.jstree) {
2129
						if(d.data.jstree.hasOwnProperty(i)) {
2130
							tmp.state[i] = d.data.jstree[i];
2131
						}
2132
					}
2133
				}
2134
			}
2135
			if(d && typeof d.state === 'object') {
2136
				for (i in d.state) {
2137
					if(d.state.hasOwnProperty(i)) {
2138
						tmp.state[i] = d.state[i];
2139
					}
2140
				}
2141
			}
2142
			if(d && typeof d.li_attr === 'object') {
2143
				for (i in d.li_attr) {
2144
					if(d.li_attr.hasOwnProperty(i)) {
2145
						tmp.li_attr[i] = d.li_attr[i];
2146
					}
2147
				}
2148
			}
2149
			if(tmp.li_attr.id && !tmp.id) {
2150
				tmp.id = tmp.li_attr.id.toString();
2151
			}
2152
			if(!tmp.id) {
2153
				tmp.id = tid;
2154
			}
2155
			if(!tmp.li_attr.id) {
2156
				tmp.li_attr.id = tmp.id;
2157
			}
2158
			if(d && typeof d.a_attr === 'object') {
2159
				for (i in d.a_attr) {
2160
					if(d.a_attr.hasOwnProperty(i)) {
2161
						tmp.a_attr[i] = d.a_attr[i];
2162
					}
2163
				}
2164
			}
2165
			if(d && d.children && d.children.length) {
2166
				for(i = 0, j = d.children.length; i < j; i++) {
2167
					c = this._parse_model_from_json(d.children[i], tmp.id, ps);
2168
					e = m[c];
2169
					tmp.children.push(c);
2170
					if(e.children_d.length) {
2171
						tmp.children_d = tmp.children_d.concat(e.children_d);
2172
					}
2173
				}
2174
				tmp.children_d = tmp.children_d.concat(tmp.children);
2175
			}
2176
			if(d && d.children && d.children === true) {
2177
				tmp.state.loaded = false;
2178
				tmp.children = [];
2179
				tmp.children_d = [];
2180
			}
2181
			delete d.data;
2182
			delete d.children;
2183
			tmp.original = d;
2184
			m[tmp.id] = tmp;
2185
			if(tmp.state.selected) {
2186
				this._data.core.selected.push(tmp.id);
2187
			}
2188
			return tmp.id;
2189
		},
2190
		/**
2191
		 * redraws all nodes that need to be redrawn. Used internally.
2192
		 * @private
2193
		 * @name _redraw()
2194
		 * @trigger redraw.jstree
2195
		 */
2196
		_redraw : function () {
2197
			var nodes = this._model.force_full_redraw ? this._model.data[$.jstree.root].children.concat([]) : this._model.changed.concat([]),
2198
				f = document.createElement('UL'), tmp, i, j, fe = this._data.core.focused;
2199
			for(i = 0, j = nodes.length; i < j; i++) {
2200
				tmp = this.redraw_node(nodes[i], true, this._model.force_full_redraw);
2201
				if(tmp && this._model.force_full_redraw) {
2202
					f.appendChild(tmp);
2203
				}
2204
			}
2205
			if(this._model.force_full_redraw) {
2206
				f.className = this.get_container_ul()[0].className;
2207
				f.setAttribute('role','group');
2208
				this.element.empty().append(f);
2209
				//this.get_container_ul()[0].appendChild(f);
2210
			}
2211
			if(fe !== null) {
2212
				tmp = this.get_node(fe, true);
2213
				if(tmp && tmp.length && tmp.children('.jstree-anchor')[0] !== document.activeElement) {
2214
					tmp.children('.jstree-anchor').focus();
2215
				}
2216
				else {
2217
					this._data.core.focused = null;
2218
				}
2219
			}
2220
			this._model.force_full_redraw = false;
2221
			this._model.changed = [];
2222
			/**
2223
			 * triggered after nodes are redrawn
2224
			 * @event
2225
			 * @name redraw.jstree
2226
			 * @param {array} nodes the redrawn nodes
2227
			 */
2228
			this.trigger('redraw', { "nodes" : nodes });
2229
		},
2230
		/**
2231
		 * redraws all nodes that need to be redrawn or optionally - the whole tree
2232
		 * @name redraw([full])
2233
		 * @param {Boolean} full if set to `true` all nodes are redrawn.
2234
		 */
2235
		redraw : function (full) {
2236
			if(full) {
2237
				this._model.force_full_redraw = true;
2238
			}
2239
			//if(this._model.redraw_timeout) {
2240
			//	clearTimeout(this._model.redraw_timeout);
2241
			//}
2242
			//this._model.redraw_timeout = setTimeout($.proxy(this._redraw, this),0);
2243
			this._redraw();
2244
		},
2245
		/**
2246
		 * redraws a single node's children. Used internally.
2247
		 * @private
2248
		 * @name draw_children(node)
2249
		 * @param {mixed} node the node whose children will be redrawn
2250
		 */
2251
		draw_children : function (node) {
2252
			var obj = this.get_node(node),
2253
				i = false,
2254
				j = false,
2255
				k = false,
2256
				d = document;
2257
			if(!obj) { return false; }
2258
			if(obj.id === $.jstree.root) { return this.redraw(true); }
2259
			node = this.get_node(node, true);
2260
			if(!node || !node.length) { return false; } // TODO: quick toggle
2261
 
2262
			node.children('.jstree-children').remove();
2263
			node = node[0];
2264
			if(obj.children.length && obj.state.loaded) {
2265
				k = d.createElement('UL');
2266
				k.setAttribute('role', 'group');
2267
				k.className = 'jstree-children';
2268
				for(i = 0, j = obj.children.length; i < j; i++) {
2269
					k.appendChild(this.redraw_node(obj.children[i], true, true));
2270
				}
2271
				node.appendChild(k);
2272
			}
2273
		},
2274
		/**
2275
		 * redraws a single node. Used internally.
2276
		 * @private
2277
		 * @name redraw_node(node, deep, is_callback, force_render)
2278
		 * @param {mixed} node the node to redraw
2279
		 * @param {Boolean} deep should child nodes be redrawn too
2280
		 * @param {Boolean} is_callback is this a recursion call
2281
		 * @param {Boolean} force_render should children of closed parents be drawn anyway
2282
		 */
2283
		redraw_node : function (node, deep, is_callback, force_render) {
2284
			var obj = this.get_node(node),
2285
				par = false,
2286
				ind = false,
2287
				old = false,
2288
				i = false,
2289
				j = false,
2290
				k = false,
2291
				c = '',
2292
				d = document,
2293
				m = this._model.data,
2294
				f = false,
2295
				s = false,
2296
				tmp = null,
2297
				t = 0,
2298
				l = 0,
2299
				has_children = false,
2300
				last_sibling = false;
2301
			if(!obj) { return false; }
2302
			if(obj.id === $.jstree.root) {  return this.redraw(true); }
2303
			deep = deep || obj.children.length === 0;
2304
			node = !document.querySelector ? document.getElementById(obj.id) : this.element[0].querySelector('#' + ("0123456789".indexOf(obj.id[0]) !== -1 ? '\\3' + obj.id[0] + ' ' + obj.id.substr(1).replace($.jstree.idregex,'\\$&') : obj.id.replace($.jstree.idregex,'\\$&')) ); //, this.element);
2305
			if(!node) {
2306
				deep = true;
2307
				//node = d.createElement('LI');
2308
				if(!is_callback) {
2309
					par = obj.parent !== $.jstree.root ? $('#' + obj.parent.replace($.jstree.idregex,'\\$&'), this.element)[0] : null;
2310
					if(par !== null && (!par || !m[obj.parent].state.opened)) {
2311
						return false;
2312
					}
2313
					ind = $.inArray(obj.id, par === null ? m[$.jstree.root].children : m[obj.parent].children);
2314
				}
2315
			}
2316
			else {
2317
				node = $(node);
2318
				if(!is_callback) {
2319
					par = node.parent().parent()[0];
2320
					if(par === this.element[0]) {
2321
						par = null;
2322
					}
2323
					ind = node.index();
2324
				}
2325
				// m[obj.id].data = node.data(); // use only node's data, no need to touch jquery storage
2326
				if(!deep && obj.children.length && !node.children('.jstree-children').length) {
2327
					deep = true;
2328
				}
2329
				if(!deep) {
2330
					old = node.children('.jstree-children')[0];
2331
				}
2332
				f = node.children('.jstree-anchor')[0] === document.activeElement;
2333
				node.remove();
2334
				//node = d.createElement('LI');
2335
				//node = node[0];
2336
			}
2337
			node = _node.cloneNode(true);
2338
			// node is DOM, deep is boolean
2339
 
2340
			c = 'jstree-node ';
2341
			for(i in obj.li_attr) {
2342
				if(obj.li_attr.hasOwnProperty(i)) {
2343
					if(i === 'id') { continue; }
2344
					if(i !== 'class') {
2345
						node.setAttribute(i, obj.li_attr[i]);
2346
					}
2347
					else {
2348
						c += obj.li_attr[i];
2349
					}
2350
				}
2351
			}
2352
			if(!obj.a_attr.id) {
2353
				obj.a_attr.id = obj.id + '_anchor';
2354
			}
2355
			node.setAttribute('aria-selected', !!obj.state.selected);
2356
			node.setAttribute('aria-level', obj.parents.length);
2357
			node.setAttribute('aria-labelledby', obj.a_attr.id);
2358
			if(obj.state.disabled) {
2359
				node.setAttribute('aria-disabled', true);
2360
			}
2361
 
2362
			for(i = 0, j = obj.children.length; i < j; i++) {
2363
				if(!m[obj.children[i]].state.hidden) {
2364
					has_children = true;
2365
					break;
2366
				}
2367
			}
2368
			if(obj.parent !== null && m[obj.parent] && !obj.state.hidden) {
2369
				i = $.inArray(obj.id, m[obj.parent].children);
2370
				last_sibling = obj.id;
2371
				if(i !== -1) {
2372
					i++;
2373
					for(j = m[obj.parent].children.length; i < j; i++) {
2374
						if(!m[m[obj.parent].children[i]].state.hidden) {
2375
							last_sibling = m[obj.parent].children[i];
2376
						}
2377
						if(last_sibling !== obj.id) {
2378
							break;
2379
						}
2380
					}
2381
				}
2382
			}
2383
 
2384
			if(obj.state.hidden) {
2385
				c += ' jstree-hidden';
2386
			}
2387
			if(obj.state.loaded && !has_children) {
2388
				c += ' jstree-leaf';
2389
			}
2390
			else {
2391
				c += obj.state.opened && obj.state.loaded ? ' jstree-open' : ' jstree-closed';
2392
				node.setAttribute('aria-expanded', (obj.state.opened && obj.state.loaded) );
2393
			}
2394
			if(last_sibling === obj.id) {
2395
				c += ' jstree-last';
2396
			}
2397
			node.id = obj.id;
2398
			node.className = c;
2399
			c = ( obj.state.selected ? ' jstree-clicked' : '') + ( obj.state.disabled ? ' jstree-disabled' : '');
2400
			for(j in obj.a_attr) {
2401
				if(obj.a_attr.hasOwnProperty(j)) {
2402
					if(j === 'href' && obj.a_attr[j] === '#') { continue; }
2403
					if(j !== 'class') {
2404
						node.childNodes[1].setAttribute(j, obj.a_attr[j]);
2405
					}
2406
					else {
2407
						c += ' ' + obj.a_attr[j];
2408
					}
2409
				}
2410
			}
2411
			if(c.length) {
2412
				node.childNodes[1].className = 'jstree-anchor ' + c;
2413
			}
2414
			if((obj.icon && obj.icon !== true) || obj.icon === false) {
2415
				if(obj.icon === false) {
2416
					node.childNodes[1].childNodes[0].className += ' jstree-themeicon-hidden';
2417
				}
2418
				else if(obj.icon.indexOf('/') === -1 && obj.icon.indexOf('.') === -1) {
2419
					node.childNodes[1].childNodes[0].className += ' ' + obj.icon + ' jstree-themeicon-custom';
2420
				}
2421
				else {
2422
					node.childNodes[1].childNodes[0].style.backgroundImage = 'url('+obj.icon+')';
2423
					node.childNodes[1].childNodes[0].style.backgroundPosition = 'center center';
2424
					node.childNodes[1].childNodes[0].style.backgroundSize = 'auto';
2425
					node.childNodes[1].childNodes[0].className += ' jstree-themeicon-custom';
2426
				}
2427
			}
2428
 
2429
			if(this.settings.core.force_text) {
2430
				node.childNodes[1].appendChild(d.createTextNode(obj.text));
2431
			}
2432
			else {
2433
				node.childNodes[1].innerHTML += obj.text;
2434
			}
2435
 
2436
 
2437
			if(deep && obj.children.length && (obj.state.opened || force_render) && obj.state.loaded) {
2438
				k = d.createElement('UL');
2439
				k.setAttribute('role', 'group');
2440
				k.className = 'jstree-children';
2441
				for(i = 0, j = obj.children.length; i < j; i++) {
2442
					k.appendChild(this.redraw_node(obj.children[i], deep, true));
2443
				}
2444
				node.appendChild(k);
2445
			}
2446
			if(old) {
2447
				node.appendChild(old);
2448
			}
2449
			if(!is_callback) {
2450
				// append back using par / ind
2451
				if(!par) {
2452
					par = this.element[0];
2453
				}
2454
				for(i = 0, j = par.childNodes.length; i < j; i++) {
2455
					if(par.childNodes[i] && par.childNodes[i].className && par.childNodes[i].className.indexOf('jstree-children') !== -1) {
2456
						tmp = par.childNodes[i];
2457
						break;
2458
					}
2459
				}
2460
				if(!tmp) {
2461
					tmp = d.createElement('UL');
2462
					tmp.setAttribute('role', 'group');
2463
					tmp.className = 'jstree-children';
2464
					par.appendChild(tmp);
2465
				}
2466
				par = tmp;
2467
 
2468
				if(ind < par.childNodes.length) {
2469
					par.insertBefore(node, par.childNodes[ind]);
2470
				}
2471
				else {
2472
					par.appendChild(node);
2473
				}
2474
				if(f) {
2475
					t = this.element[0].scrollTop;
2476
					l = this.element[0].scrollLeft;
2477
					node.childNodes[1].focus();
2478
					this.element[0].scrollTop = t;
2479
					this.element[0].scrollLeft = l;
2480
				}
2481
			}
2482
			if(obj.state.opened && !obj.state.loaded) {
2483
				obj.state.opened = false;
2484
				setTimeout($.proxy(function () {
2485
					this.open_node(obj.id, false, 0);
2486
				}, this), 0);
2487
			}
2488
			return node;
2489
		},
2490
		/**
2491
		 * opens a node, revaling its children. If the node is not loaded it will be loaded and opened once ready.
2492
		 * @name open_node(obj [, callback, animation])
2493
		 * @param {mixed} obj the node to open
2494
		 * @param {Function} callback a function to execute once the node is opened
2495
		 * @param {Number} animation the animation duration in milliseconds when opening the node (overrides the `core.animation` setting). Use `false` for no animation.
2496
		 * @trigger open_node.jstree, after_open.jstree, before_open.jstree
2497
		 */
2498
		open_node : function (obj, callback, animation) {
2499
			var t1, t2, d, t;
2500
			if($.isArray(obj)) {
2501
				obj = obj.slice();
2502
				for(t1 = 0, t2 = obj.length; t1 < t2; t1++) {
2503
					this.open_node(obj[t1], callback, animation);
2504
				}
2505
				return true;
2506
			}
2507
			obj = this.get_node(obj);
2508
			if(!obj || obj.id === $.jstree.root) {
2509
				return false;
2510
			}
2511
			animation = animation === undefined ? this.settings.core.animation : animation;
2512
			if(!this.is_closed(obj)) {
2513
				if(callback) {
2514
					callback.call(this, obj, false);
2515
				}
2516
				return false;
2517
			}
2518
			if(!this.is_loaded(obj)) {
2519
				if(this.is_loading(obj)) {
2520
					return setTimeout($.proxy(function () {
2521
						this.open_node(obj, callback, animation);
2522
					}, this), 500);
2523
				}
2524
				this.load_node(obj, function (o, ok) {
2525
					return ok ? this.open_node(o, callback, animation) : (callback ? callback.call(this, o, false) : false);
2526
				});
2527
			}
2528
			else {
2529
				d = this.get_node(obj, true);
2530
				t = this;
2531
				if(d.length) {
2532
					if(animation && d.children(".jstree-children").length) {
2533
						d.children(".jstree-children").stop(true, true);
2534
					}
2535
					if(obj.children.length && !this._firstChild(d.children('.jstree-children')[0])) {
2536
						this.draw_children(obj);
2537
						//d = this.get_node(obj, true);
2538
					}
2539
					if(!animation) {
2540
						this.trigger('before_open', { "node" : obj });
2541
						d[0].className = d[0].className.replace('jstree-closed', 'jstree-open');
2542
						d[0].setAttribute("aria-expanded", true);
2543
					}
2544
					else {
2545
						this.trigger('before_open', { "node" : obj });
2546
						d
2547
							.children(".jstree-children").css("display","none").end()
2548
							.removeClass("jstree-closed").addClass("jstree-open").attr("aria-expanded", true)
2549
							.children(".jstree-children").stop(true, true)
2550
								.slideDown(animation, function () {
2551
									this.style.display = "";
2552
									t.trigger("after_open", { "node" : obj });
2553
								});
2554
					}
2555
				}
2556
				obj.state.opened = true;
2557
				if(callback) {
2558
					callback.call(this, obj, true);
2559
				}
2560
				if(!d.length) {
2561
					/**
2562
					 * 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)
2563
					 * @event
2564
					 * @name before_open.jstree
2565
					 * @param {Object} node the opened node
2566
					 */
2567
					this.trigger('before_open', { "node" : obj });
2568
				}
2569
				/**
2570
				 * triggered when a node is opened (if there is an animation it will not be completed yet)
2571
				 * @event
2572
				 * @name open_node.jstree
2573
				 * @param {Object} node the opened node
2574
				 */
2575
				this.trigger('open_node', { "node" : obj });
2576
				if(!animation || !d.length) {
2577
					/**
2578
					 * triggered when a node is opened and the animation is complete
2579
					 * @event
2580
					 * @name after_open.jstree
2581
					 * @param {Object} node the opened node
2582
					 */
2583
					this.trigger("after_open", { "node" : obj });
2584
				}
2585
				return true;
2586
			}
2587
		},
2588
		/**
2589
		 * opens every parent of a node (node should be loaded)
2590
		 * @name _open_to(obj)
2591
		 * @param {mixed} obj the node to reveal
2592
		 * @private
2593
		 */
2594
		_open_to : function (obj) {
2595
			obj = this.get_node(obj);
2596
			if(!obj || obj.id === $.jstree.root) {
2597
				return false;
2598
			}
2599
			var i, j, p = obj.parents;
2600
			for(i = 0, j = p.length; i < j; i+=1) {
2601
				if(i !== $.jstree.root) {
2602
					this.open_node(p[i], false, 0);
2603
				}
2604
			}
2605
			return $('#' + obj.id.replace($.jstree.idregex,'\\$&'), this.element);
2606
		},
2607
		/**
2608
		 * closes a node, hiding its children
2609
		 * @name close_node(obj [, animation])
2610
		 * @param {mixed} obj the node to close
2611
		 * @param {Number} animation the animation duration in milliseconds when closing the node (overrides the `core.animation` setting). Use `false` for no animation.
2612
		 * @trigger close_node.jstree, after_close.jstree
2613
		 */
2614
		close_node : function (obj, animation) {
2615
			var t1, t2, t, d;
2616
			if($.isArray(obj)) {
2617
				obj = obj.slice();
2618
				for(t1 = 0, t2 = obj.length; t1 < t2; t1++) {
2619
					this.close_node(obj[t1], animation);
2620
				}
2621
				return true;
2622
			}
2623
			obj = this.get_node(obj);
2624
			if(!obj || obj.id === $.jstree.root) {
2625
				return false;
2626
			}
2627
			if(this.is_closed(obj)) {
2628
				return false;
2629
			}
2630
			animation = animation === undefined ? this.settings.core.animation : animation;
2631
			t = this;
2632
			d = this.get_node(obj, true);
2633
			if(d.length) {
2634
				if(!animation) {
2635
					d[0].className = d[0].className.replace('jstree-open', 'jstree-closed');
2636
					d.attr("aria-expanded", false).children('.jstree-children').remove();
2637
				}
2638
				else {
2639
					d
2640
						.children(".jstree-children").attr("style","display:block !important").end()
2641
						.removeClass("jstree-open").addClass("jstree-closed").attr("aria-expanded", false)
2642
						.children(".jstree-children").stop(true, true).slideUp(animation, function () {
2643
							this.style.display = "";
2644
							d.children('.jstree-children').remove();
2645
							t.trigger("after_close", { "node" : obj });
2646
						});
2647
				}
2648
			}
2649
			obj.state.opened = false;
2650
			/**
2651
			 * triggered when a node is closed (if there is an animation it will not be complete yet)
2652
			 * @event
2653
			 * @name close_node.jstree
2654
			 * @param {Object} node the closed node
2655
			 */
2656
			this.trigger('close_node',{ "node" : obj });
2657
			if(!animation || !d.length) {
2658
				/**
2659
				 * triggered when a node is closed and the animation is complete
2660
				 * @event
2661
				 * @name after_close.jstree
2662
				 * @param {Object} node the closed node
2663
				 */
2664
				this.trigger("after_close", { "node" : obj });
2665
			}
2666
		},
2667
		/**
2668
		 * toggles a node - closing it if it is open, opening it if it is closed
2669
		 * @name toggle_node(obj)
2670
		 * @param {mixed} obj the node to toggle
2671
		 */
2672
		toggle_node : function (obj) {
2673
			var t1, t2;
2674
			if($.isArray(obj)) {
2675
				obj = obj.slice();
2676
				for(t1 = 0, t2 = obj.length; t1 < t2; t1++) {
2677
					this.toggle_node(obj[t1]);
2678
				}
2679
				return true;
2680
			}
2681
			if(this.is_closed(obj)) {
2682
				return this.open_node(obj);
2683
			}
2684
			if(this.is_open(obj)) {
2685
				return this.close_node(obj);
2686
			}
2687
		},
2688
		/**
2689
		 * 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.
2690
		 * @name open_all([obj, animation, original_obj])
2691
		 * @param {mixed} obj the node to open recursively, omit to open all nodes in the tree
2692
		 * @param {Number} animation the animation duration in milliseconds when opening the nodes, the default is no animation
2693
		 * @param {jQuery} reference to the node that started the process (internal use)
2694
		 * @trigger open_all.jstree
2695
		 */
2696
		open_all : function (obj, animation, original_obj) {
2697
			if(!obj) { obj = $.jstree.root; }
2698
			obj = this.get_node(obj);
2699
			if(!obj) { return false; }
2700
			var dom = obj.id === $.jstree.root ? this.get_container_ul() : this.get_node(obj, true), i, j, _this;
2701
			if(!dom.length) {
2702
				for(i = 0, j = obj.children_d.length; i < j; i++) {
2703
					if(this.is_closed(this._model.data[obj.children_d[i]])) {
2704
						this._model.data[obj.children_d[i]].state.opened = true;
2705
					}
2706
				}
2707
				return this.trigger('open_all', { "node" : obj });
2708
			}
2709
			original_obj = original_obj || dom;
2710
			_this = this;
2711
			dom = this.is_closed(obj) ? dom.find('.jstree-closed').addBack() : dom.find('.jstree-closed');
2712
			dom.each(function () {
2713
				_this.open_node(
2714
					this,
2715
					function(node, status) { if(status && this.is_parent(node)) { this.open_all(node, animation, original_obj); } },
2716
					animation || 0
2717
				);
2718
			});
2719
			if(original_obj.find('.jstree-closed').length === 0) {
2720
				/**
2721
				 * triggered when an `open_all` call completes
2722
				 * @event
2723
				 * @name open_all.jstree
2724
				 * @param {Object} node the opened node
2725
				 */
2726
				this.trigger('open_all', { "node" : this.get_node(original_obj) });
2727
			}
2728
		},
2729
		/**
2730
		 * closes all nodes within a node (or the tree), revaling their children
2731
		 * @name close_all([obj, animation])
2732
		 * @param {mixed} obj the node to close recursively, omit to close all nodes in the tree
2733
		 * @param {Number} animation the animation duration in milliseconds when closing the nodes, the default is no animation
2734
		 * @trigger close_all.jstree
2735
		 */
2736
		close_all : function (obj, animation) {
2737
			if(!obj) { obj = $.jstree.root; }
2738
			obj = this.get_node(obj);
2739
			if(!obj) { return false; }
2740
			var dom = obj.id === $.jstree.root ? this.get_container_ul() : this.get_node(obj, true),
2741
				_this = this, i, j;
2742
			if(dom.length) {
2743
				dom = this.is_open(obj) ? dom.find('.jstree-open').addBack() : dom.find('.jstree-open');
2744
				$(dom.get().reverse()).each(function () { _this.close_node(this, animation || 0); });
2745
			}
2746
			for(i = 0, j = obj.children_d.length; i < j; i++) {
2747
				this._model.data[obj.children_d[i]].state.opened = false;
2748
			}
2749
			/**
2750
			 * triggered when an `close_all` call completes
2751
			 * @event
2752
			 * @name close_all.jstree
2753
			 * @param {Object} node the closed node
2754
			 */
2755
			this.trigger('close_all', { "node" : obj });
2756
		},
2757
		/**
2758
		 * checks if a node is disabled (not selectable)
2759
		 * @name is_disabled(obj)
2760
		 * @param  {mixed} obj
2761
		 * @return {Boolean}
2762
		 */
2763
		is_disabled : function (obj) {
2764
			obj = this.get_node(obj);
2765
			return obj && obj.state && obj.state.disabled;
2766
		},
2767
		/**
2768
		 * enables a node - so that it can be selected
2769
		 * @name enable_node(obj)
2770
		 * @param {mixed} obj the node to enable
2771
		 * @trigger enable_node.jstree
2772
		 */
2773
		enable_node : function (obj) {
2774
			var t1, t2;
2775
			if($.isArray(obj)) {
2776
				obj = obj.slice();
2777
				for(t1 = 0, t2 = obj.length; t1 < t2; t1++) {
2778
					this.enable_node(obj[t1]);
2779
				}
2780
				return true;
2781
			}
2782
			obj = this.get_node(obj);
2783
			if(!obj || obj.id === $.jstree.root) {
2784
				return false;
2785
			}
2786
			obj.state.disabled = false;
2787
			this.get_node(obj,true).children('.jstree-anchor').removeClass('jstree-disabled').attr('aria-disabled', false);
2788
			/**
2789
			 * triggered when an node is enabled
2790
			 * @event
2791
			 * @name enable_node.jstree
2792
			 * @param {Object} node the enabled node
2793
			 */
2794
			this.trigger('enable_node', { 'node' : obj });
2795
		},
2796
		/**
2797
		 * disables a node - so that it can not be selected
2798
		 * @name disable_node(obj)
2799
		 * @param {mixed} obj the node to disable
2800
		 * @trigger disable_node.jstree
2801
		 */
2802
		disable_node : function (obj) {
2803
			var t1, t2;
2804
			if($.isArray(obj)) {
2805
				obj = obj.slice();
2806
				for(t1 = 0, t2 = obj.length; t1 < t2; t1++) {
2807
					this.disable_node(obj[t1]);
2808
				}
2809
				return true;
2810
			}
2811
			obj = this.get_node(obj);
2812
			if(!obj || obj.id === $.jstree.root) {
2813
				return false;
2814
			}
2815
			obj.state.disabled = true;
2816
			this.get_node(obj,true).children('.jstree-anchor').addClass('jstree-disabled').attr('aria-disabled', true);
2817
			/**
2818
			 * triggered when an node is disabled
2819
			 * @event
2820
			 * @name disable_node.jstree
2821
			 * @param {Object} node the disabled node
2822
			 */
2823
			this.trigger('disable_node', { 'node' : obj });
2824
		},
2825
		/**
2826
		 * hides a node - it is still in the structure but will not be visible
2827
		 * @name hide_node(obj)
2828
		 * @param {mixed} obj the node to hide
2829
		 * @param {Boolean} redraw internal parameter controlling if redraw is called
2830
		 * @trigger hide_node.jstree
2831
		 */
2832
		hide_node : function (obj, skip_redraw) {
2833
			var t1, t2;
2834
			if($.isArray(obj)) {
2835
				obj = obj.slice();
2836
				for(t1 = 0, t2 = obj.length; t1 < t2; t1++) {
2837
					this.hide_node(obj[t1], true);
2838
				}
2839
				this.redraw();
2840
				return true;
2841
			}
2842
			obj = this.get_node(obj);
2843
			if(!obj || obj.id === $.jstree.root) {
2844
				return false;
2845
			}
2846
			if(!obj.state.hidden) {
2847
				obj.state.hidden = true;
2848
				this._node_changed(obj.parent);
2849
				if(!skip_redraw) {
2850
					this.redraw();
2851
				}
2852
				/**
2853
				 * triggered when an node is hidden
2854
				 * @event
2855
				 * @name hide_node.jstree
2856
				 * @param {Object} node the hidden node
2857
				 */
2858
				this.trigger('hide_node', { 'node' : obj });
2859
			}
2860
		},
2861
		/**
2862
		 * shows a node
2863
		 * @name show_node(obj)
2864
		 * @param {mixed} obj the node to show
2865
		 * @param {Boolean} skip_redraw internal parameter controlling if redraw is called
2866
		 * @trigger show_node.jstree
2867
		 */
2868
		show_node : function (obj, skip_redraw) {
2869
			var t1, t2;
2870
			if($.isArray(obj)) {
2871
				obj = obj.slice();
2872
				for(t1 = 0, t2 = obj.length; t1 < t2; t1++) {
2873
					this.show_node(obj[t1], true);
2874
				}
2875
				this.redraw();
2876
				return true;
2877
			}
2878
			obj = this.get_node(obj);
2879
			if(!obj || obj.id === $.jstree.root) {
2880
				return false;
2881
			}
2882
			if(obj.state.hidden) {
2883
				obj.state.hidden = false;
2884
				this._node_changed(obj.parent);
2885
				if(!skip_redraw) {
2886
					this.redraw();
2887
				}
2888
				/**
2889
				 * triggered when an node is shown
2890
				 * @event
2891
				 * @name show_node.jstree
2892
				 * @param {Object} node the shown node
2893
				 */
2894
				this.trigger('show_node', { 'node' : obj });
2895
			}
2896
		},
2897
		/**
2898
		 * hides all nodes
2899
		 * @name hide_all()
2900
		 * @trigger hide_all.jstree
2901
		 */
2902
		hide_all : function (skip_redraw) {
2903
			var i, m = this._model.data, ids = [];
2904
			for(i in m) {
2905
				if(m.hasOwnProperty(i) && i !== $.jstree.root && !m[i].state.hidden) {
2906
					m[i].state.hidden = true;
2907
					ids.push(i);
2908
				}
2909
			}
2910
			this._model.force_full_redraw = true;
2911
			if(!skip_redraw) {
2912
				this.redraw();
2913
			}
2914
			/**
2915
			 * triggered when all nodes are hidden
2916
			 * @event
2917
			 * @name hide_all.jstree
2918
			 * @param {Array} nodes the IDs of all hidden nodes
2919
			 */
2920
			this.trigger('hide_all', { 'nodes' : ids });
2921
			return ids;
2922
		},
2923
		/**
2924
		 * shows all nodes
2925
		 * @name show_all()
2926
		 * @trigger show_all.jstree
2927
		 */
2928
		show_all : function (skip_redraw) {
2929
			var i, m = this._model.data, ids = [];
2930
			for(i in m) {
2931
				if(m.hasOwnProperty(i) && i !== $.jstree.root && m[i].state.hidden) {
2932
					m[i].state.hidden = false;
2933
					ids.push(i);
2934
				}
2935
			}
2936
			this._model.force_full_redraw = true;
2937
			if(!skip_redraw) {
2938
				this.redraw();
2939
			}
2940
			/**
2941
			 * triggered when all nodes are shown
2942
			 * @event
2943
			 * @name show_all.jstree
2944
			 * @param {Array} nodes the IDs of all shown nodes
2945
			 */
2946
			this.trigger('show_all', { 'nodes' : ids });
2947
			return ids;
2948
		},
2949
		/**
2950
		 * called when a node is selected by the user. Used internally.
2951
		 * @private
2952
		 * @name activate_node(obj, e)
2953
		 * @param {mixed} obj the node
2954
		 * @param {Object} e the related event
2955
		 * @trigger activate_node.jstree, changed.jstree
2956
		 */
2957
		activate_node : function (obj, e) {
2958
			if(this.is_disabled(obj)) {
2959
				return false;
2960
			}
2961
			if(!e || typeof e !== 'object') {
2962
				e = {};
2963
			}
2964
 
2965
			// 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
2966
			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;
2967
			if(this._data.core.last_clicked && !this._data.core.last_clicked.state.selected) { this._data.core.last_clicked = null; }
2968
			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]); }
2969
 
2970
			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 ) )) {
2971
				if(!this.settings.core.multiple && (e.metaKey || e.ctrlKey || e.shiftKey) && this.is_selected(obj)) {
2972
					this.deselect_node(obj, false, e);
2973
				}
2974
				else {
2975
					this.deselect_all(true);
2976
					this.select_node(obj, false, false, e);
2977
					this._data.core.last_clicked = this.get_node(obj);
2978
				}
2979
			}
2980
			else {
2981
				if(e.shiftKey) {
2982
					var o = this.get_node(obj).id,
2983
						l = this._data.core.last_clicked.id,
2984
						p = this.get_node(this._data.core.last_clicked.parent).children,
2985
						c = false,
2986
						i, j;
2987
					for(i = 0, j = p.length; i < j; i += 1) {
2988
						// separate IFs work whem o and l are the same
2989
						if(p[i] === o) {
2990
							c = !c;
2991
						}
2992
						if(p[i] === l) {
2993
							c = !c;
2994
						}
2995
						if(!this.is_disabled(p[i]) && (c || p[i] === o || p[i] === l)) {
2996
							this.select_node(p[i], true, false, e);
2997
						}
2998
						else {
2999
							this.deselect_node(p[i], true, e);
3000
						}
3001
					}
3002
					this.trigger('changed', { 'action' : 'select_node', 'node' : this.get_node(obj), 'selected' : this._data.core.selected, 'event' : e });
3003
				}
3004
				else {
3005
					if(!this.is_selected(obj)) {
3006
						this.select_node(obj, false, false, e);
3007
					}
3008
					else {
3009
						this.deselect_node(obj, false, e);
3010
					}
3011
				}
3012
			}
3013
			/**
3014
			 * triggered when an node is clicked or intercated with by the user
3015
			 * @event
3016
			 * @name activate_node.jstree
3017
			 * @param {Object} node
3018
			 * @param {Object} event the ooriginal event (if any) which triggered the call (may be an empty object)
3019
			 */
3020
			this.trigger('activate_node', { 'node' : this.get_node(obj), 'event' : e });
3021
		},
3022
		/**
3023
		 * applies the hover state on a node, called when a node is hovered by the user. Used internally.
3024
		 * @private
3025
		 * @name hover_node(obj)
3026
		 * @param {mixed} obj
3027
		 * @trigger hover_node.jstree
3028
		 */
3029
		hover_node : function (obj) {
3030
			obj = this.get_node(obj, true);
3031
			if(!obj || !obj.length || obj.children('.jstree-hovered').length) {
3032
				return false;
3033
			}
3034
			var o = this.element.find('.jstree-hovered'), t = this.element;
3035
			if(o && o.length) { this.dehover_node(o); }
3036
 
3037
			obj.children('.jstree-anchor').addClass('jstree-hovered');
3038
			/**
3039
			 * triggered when an node is hovered
3040
			 * @event
3041
			 * @name hover_node.jstree
3042
			 * @param {Object} node
3043
			 */
3044
			this.trigger('hover_node', { 'node' : this.get_node(obj) });
3045
			setTimeout(function () { t.attr('aria-activedescendant', obj[0].id); }, 0);
3046
		},
3047
		/**
3048
		 * removes the hover state from a nodecalled when a node is no longer hovered by the user. Used internally.
3049
		 * @private
3050
		 * @name dehover_node(obj)
3051
		 * @param {mixed} obj
3052
		 * @trigger dehover_node.jstree
3053
		 */
3054
		dehover_node : function (obj) {
3055
			obj = this.get_node(obj, true);
3056
			if(!obj || !obj.length || !obj.children('.jstree-hovered').length) {
3057
				return false;
3058
			}
3059
			obj.children('.jstree-anchor').removeClass('jstree-hovered');
3060
			/**
3061
			 * triggered when an node is no longer hovered
3062
			 * @event
3063
			 * @name dehover_node.jstree
3064
			 * @param {Object} node
3065
			 */
3066
			this.trigger('dehover_node', { 'node' : this.get_node(obj) });
3067
		},
3068
		/**
3069
		 * select a node
3070
		 * @name select_node(obj [, supress_event, prevent_open])
3071
		 * @param {mixed} obj an array can be used to select multiple nodes
3072
		 * @param {Boolean} supress_event if set to `true` the `changed.jstree` event won't be triggered
3073
		 * @param {Boolean} prevent_open if set to `true` parents of the selected node won't be opened
3074
		 * @trigger select_node.jstree, changed.jstree
3075
		 */
3076
		select_node : function (obj, supress_event, prevent_open, e) {
3077
			var dom, t1, t2, th;
3078
			if($.isArray(obj)) {
3079
				obj = obj.slice();
3080
				for(t1 = 0, t2 = obj.length; t1 < t2; t1++) {
3081
					this.select_node(obj[t1], supress_event, prevent_open, e);
3082
				}
3083
				return true;
3084
			}
3085
			obj = this.get_node(obj);
3086
			if(!obj || obj.id === $.jstree.root) {
3087
				return false;
3088
			}
3089
			dom = this.get_node(obj, true);
3090
			if(!obj.state.selected) {
3091
				obj.state.selected = true;
3092
				this._data.core.selected.push(obj.id);
3093
				if(!prevent_open) {
3094
					dom = this._open_to(obj);
3095
				}
3096
				if(dom && dom.length) {
3097
					dom.attr('aria-selected', true).children('.jstree-anchor').addClass('jstree-clicked');
3098
				}
3099
				/**
3100
				 * triggered when an node is selected
3101
				 * @event
3102
				 * @name select_node.jstree
3103
				 * @param {Object} node
3104
				 * @param {Array} selected the current selection
3105
				 * @param {Object} event the event (if any) that triggered this select_node
3106
				 */
3107
				this.trigger('select_node', { 'node' : obj, 'selected' : this._data.core.selected, 'event' : e });
3108
				if(!supress_event) {
3109
					/**
3110
					 * triggered when selection changes
3111
					 * @event
3112
					 * @name changed.jstree
3113
					 * @param {Object} node
3114
					 * @param {Object} action the action that caused the selection to change
3115
					 * @param {Array} selected the current selection
3116
					 * @param {Object} event the event (if any) that triggered this changed event
3117
					 */
3118
					this.trigger('changed', { 'action' : 'select_node', 'node' : obj, 'selected' : this._data.core.selected, 'event' : e });
3119
				}
3120
			}
3121
		},
3122
		/**
3123
		 * deselect a node
3124
		 * @name deselect_node(obj [, supress_event])
3125
		 * @param {mixed} obj an array can be used to deselect multiple nodes
3126
		 * @param {Boolean} supress_event if set to `true` the `changed.jstree` event won't be triggered
3127
		 * @trigger deselect_node.jstree, changed.jstree
3128
		 */
3129
		deselect_node : function (obj, supress_event, e) {
3130
			var t1, t2, dom;
3131
			if($.isArray(obj)) {
3132
				obj = obj.slice();
3133
				for(t1 = 0, t2 = obj.length; t1 < t2; t1++) {
3134
					this.deselect_node(obj[t1], supress_event, e);
3135
				}
3136
				return true;
3137
			}
3138
			obj = this.get_node(obj);
3139
			if(!obj || obj.id === $.jstree.root) {
3140
				return false;
3141
			}
3142
			dom = this.get_node(obj, true);
3143
			if(obj.state.selected) {
3144
				obj.state.selected = false;
3145
				this._data.core.selected = $.vakata.array_remove_item(this._data.core.selected, obj.id);
3146
				if(dom.length) {
3147
					dom.attr('aria-selected', false).children('.jstree-anchor').removeClass('jstree-clicked');
3148
				}
3149
				/**
3150
				 * triggered when an node is deselected
3151
				 * @event
3152
				 * @name deselect_node.jstree
3153
				 * @param {Object} node
3154
				 * @param {Array} selected the current selection
3155
				 * @param {Object} event the event (if any) that triggered this deselect_node
3156
				 */
3157
				this.trigger('deselect_node', { 'node' : obj, 'selected' : this._data.core.selected, 'event' : e });
3158
				if(!supress_event) {
3159
					this.trigger('changed', { 'action' : 'deselect_node', 'node' : obj, 'selected' : this._data.core.selected, 'event' : e });
3160
				}
3161
			}
3162
		},
3163
		/**
3164
		 * select all nodes in the tree
3165
		 * @name select_all([supress_event])
3166
		 * @param {Boolean} supress_event if set to `true` the `changed.jstree` event won't be triggered
3167
		 * @trigger select_all.jstree, changed.jstree
3168
		 */
3169
		select_all : function (supress_event) {
3170
			var tmp = this._data.core.selected.concat([]), i, j;
3171
			this._data.core.selected = this._model.data[$.jstree.root].children_d.concat();
3172
			for(i = 0, j = this._data.core.selected.length; i < j; i++) {
3173
				if(this._model.data[this._data.core.selected[i]]) {
3174
					this._model.data[this._data.core.selected[i]].state.selected = true;
3175
				}
3176
			}
3177
			this.redraw(true);
3178
			/**
3179
			 * triggered when all nodes are selected
3180
			 * @event
3181
			 * @name select_all.jstree
3182
			 * @param {Array} selected the current selection
3183
			 */
3184
			this.trigger('select_all', { 'selected' : this._data.core.selected });
3185
			if(!supress_event) {
3186
				this.trigger('changed', { 'action' : 'select_all', 'selected' : this._data.core.selected, 'old_selection' : tmp });
3187
			}
3188
		},
3189
		/**
3190
		 * deselect all selected nodes
3191
		 * @name deselect_all([supress_event])
3192
		 * @param {Boolean} supress_event if set to `true` the `changed.jstree` event won't be triggered
3193
		 * @trigger deselect_all.jstree, changed.jstree
3194
		 */
3195
		deselect_all : function (supress_event) {
3196
			var tmp = this._data.core.selected.concat([]), i, j;
3197
			for(i = 0, j = this._data.core.selected.length; i < j; i++) {
3198
				if(this._model.data[this._data.core.selected[i]]) {
3199
					this._model.data[this._data.core.selected[i]].state.selected = false;
3200
				}
3201
			}
3202
			this._data.core.selected = [];
3203
			this.element.find('.jstree-clicked').removeClass('jstree-clicked').parent().attr('aria-selected', false);
3204
			/**
3205
			 * triggered when all nodes are deselected
3206
			 * @event
3207
			 * @name deselect_all.jstree
3208
			 * @param {Object} node the previous selection
3209
			 * @param {Array} selected the current selection
3210
			 */
3211
			this.trigger('deselect_all', { 'selected' : this._data.core.selected, 'node' : tmp });
3212
			if(!supress_event) {
3213
				this.trigger('changed', { 'action' : 'deselect_all', 'selected' : this._data.core.selected, 'old_selection' : tmp });
3214
			}
3215
		},
3216
		/**
3217
		 * checks if a node is selected
3218
		 * @name is_selected(obj)
3219
		 * @param  {mixed}  obj
3220
		 * @return {Boolean}
3221
		 */
3222
		is_selected : function (obj) {
3223
			obj = this.get_node(obj);
3224
			if(!obj || obj.id === $.jstree.root) {
3225
				return false;
3226
			}
3227
			return obj.state.selected;
3228
		},
3229
		/**
3230
		 * get an array of all selected nodes
3231
		 * @name get_selected([full])
3232
		 * @param  {mixed}  full if set to `true` the returned array will consist of the full node objects, otherwise - only IDs will be returned
3233
		 * @return {Array}
3234
		 */
3235
		get_selected : function (full) {
3236
			return full ? $.map(this._data.core.selected, $.proxy(function (i) { return this.get_node(i); }, this)) : this._data.core.selected.slice();
3237
		},
3238
		/**
3239
		 * get an array of all top level selected nodes (ignoring children of selected nodes)
3240
		 * @name get_top_selected([full])
3241
		 * @param  {mixed}  full if set to `true` the returned array will consist of the full node objects, otherwise - only IDs will be returned
3242
		 * @return {Array}
3243
		 */
3244
		get_top_selected : function (full) {
3245
			var tmp = this.get_selected(true),
3246
				obj = {}, i, j, k, l;
3247
			for(i = 0, j = tmp.length; i < j; i++) {
3248
				obj[tmp[i].id] = tmp[i];
3249
			}
3250
			for(i = 0, j = tmp.length; i < j; i++) {
3251
				for(k = 0, l = tmp[i].children_d.length; k < l; k++) {
3252
					if(obj[tmp[i].children_d[k]]) {
3253
						delete obj[tmp[i].children_d[k]];
3254
					}
3255
				}
3256
			}
3257
			tmp = [];
3258
			for(i in obj) {
3259
				if(obj.hasOwnProperty(i)) {
3260
					tmp.push(i);
3261
				}
3262
			}
3263
			return full ? $.map(tmp, $.proxy(function (i) { return this.get_node(i); }, this)) : tmp;
3264
		},
3265
		/**
3266
		 * get an array of all bottom level selected nodes (ignoring selected parents)
3267
		 * @name get_bottom_selected([full])
3268
		 * @param  {mixed}  full if set to `true` the returned array will consist of the full node objects, otherwise - only IDs will be returned
3269
		 * @return {Array}
3270
		 */
3271
		get_bottom_selected : function (full) {
3272
			var tmp = this.get_selected(true),
3273
				obj = [], i, j;
3274
			for(i = 0, j = tmp.length; i < j; i++) {
3275
				if(!tmp[i].children.length) {
3276
					obj.push(tmp[i].id);
3277
				}
3278
			}
3279
			return full ? $.map(obj, $.proxy(function (i) { return this.get_node(i); }, this)) : obj;
3280
		},
3281
		/**
3282
		 * gets the current state of the tree so that it can be restored later with `set_state(state)`. Used internally.
3283
		 * @name get_state()
3284
		 * @private
3285
		 * @return {Object}
3286
		 */
3287
		get_state : function () {
3288
			var state	= {
3289
				'core' : {
3290
					'open' : [],
3291
					'scroll' : {
3292
						'left' : this.element.scrollLeft(),
3293
						'top' : this.element.scrollTop()
3294
					},
3295
					/*!
3296
					'themes' : {
3297
						'name' : this.get_theme(),
3298
						'icons' : this._data.core.themes.icons,
3299
						'dots' : this._data.core.themes.dots
3300
					},
3301
					*/
3302
					'selected' : []
3303
				}
3304
			}, i;
3305
			for(i in this._model.data) {
3306
				if(this._model.data.hasOwnProperty(i)) {
3307
					if(i !== $.jstree.root) {
3308
						if(this._model.data[i].state.opened) {
3309
							state.core.open.push(i);
3310
						}
3311
						if(this._model.data[i].state.selected) {
3312
							state.core.selected.push(i);
3313
						}
3314
					}
3315
				}
3316
			}
3317
			return state;
3318
		},
3319
		/**
3320
		 * sets the state of the tree. Used internally.
3321
		 * @name set_state(state [, callback])
3322
		 * @private
3323
		 * @param {Object} state the state to restore. Keep in mind this object is passed by reference and jstree will modify it.
3324
		 * @param {Function} callback an optional function to execute once the state is restored.
3325
		 * @trigger set_state.jstree
3326
		 */
3327
		set_state : function (state, callback) {
3328
			if(state) {
3329
				if(state.core) {
3330
					var res, n, t, _this, i;
3331
					if(state.core.open) {
3332
						if(!$.isArray(state.core.open) || !state.core.open.length) {
3333
							delete state.core.open;
3334
							this.set_state(state, callback);
3335
						}
3336
						else {
3337
							this._load_nodes(state.core.open, function (nodes) {
3338
								this.open_node(nodes, false, 0);
3339
								delete state.core.open;
3340
								this.set_state(state, callback);
3341
							}, true);
3342
						}
3343
						return false;
3344
					}
3345
					if(state.core.scroll) {
3346
						if(state.core.scroll && state.core.scroll.left !== undefined) {
3347
							this.element.scrollLeft(state.core.scroll.left);
3348
						}
3349
						if(state.core.scroll && state.core.scroll.top !== undefined) {
3350
							this.element.scrollTop(state.core.scroll.top);
3351
						}
3352
						delete state.core.scroll;
3353
						this.set_state(state, callback);
3354
						return false;
3355
					}
3356
					if(state.core.selected) {
3357
						_this = this;
3358
						this.deselect_all();
3359
						$.each(state.core.selected, function (i, v) {
3360
							_this.select_node(v, false, true);
3361
						});
3362
						delete state.core.selected;
3363
						this.set_state(state, callback);
3364
						return false;
3365
					}
3366
					for(i in state) {
3367
						if(state.hasOwnProperty(i) && i !== "core" && $.inArray(i, this.settings.plugins) === -1) {
3368
							delete state[i];
3369
						}
3370
					}
3371
					if($.isEmptyObject(state.core)) {
3372
						delete state.core;
3373
						this.set_state(state, callback);
3374
						return false;
3375
					}
3376
				}
3377
				if($.isEmptyObject(state)) {
3378
					state = null;
3379
					if(callback) { callback.call(this); }
3380
					/**
3381
					 * triggered when a `set_state` call completes
3382
					 * @event
3383
					 * @name set_state.jstree
3384
					 */
3385
					this.trigger('set_state');
3386
					return false;
3387
				}
3388
				return true;
3389
			}
3390
			return false;
3391
		},
3392
		/**
3393
		 * refreshes the tree - all nodes are reloaded with calls to `load_node`.
3394
		 * @name refresh()
3395
		 * @param {Boolean} skip_loading an option to skip showing the loading indicator
3396
		 * @param {Mixed} forget_state if set to `true` state will not be reapplied, if set to a function (receiving the current state as argument) the result of that function will be used as state
3397
		 * @trigger refresh.jstree
3398
		 */
3399
		refresh : function (skip_loading, forget_state) {
3400
			this._data.core.state = forget_state === true ? {} : this.get_state();
3401
			if(forget_state && $.isFunction(forget_state)) { this._data.core.state = forget_state.call(this, this._data.core.state); }
3402
			this._cnt = 0;
3403
			this._model.data = {};
3404
			this._model.data[$.jstree.root] = {
3405
				id : $.jstree.root,
3406
				parent : null,
3407
				parents : [],
3408
				children : [],
3409
				children_d : [],
3410
				state : { loaded : false }
3411
			};
3412
			this._data.core.selected = [];
3413
			this._data.core.last_clicked = null;
3414
			this._data.core.focused = null;
3415
 
3416
			var c = this.get_container_ul()[0].className;
3417
			if(!skip_loading) {
3418
				this.element.html("<"+"ul class='"+c+"' role='group'><"+"li class='jstree-initial-node jstree-loading jstree-leaf jstree-last' role='treeitem' id='j"+this._id+"_loading'><i class='jstree-icon jstree-ocl'></i><"+"a class='jstree-anchor' href='#'><i class='jstree-icon jstree-themeicon-hidden'></i>" + this.get_string("Loading ...") + "</a></li></ul>");
3419
				this.element.attr('aria-activedescendant','j'+this._id+'_loading');
3420
			}
3421
			this.load_node($.jstree.root, function (o, s) {
3422
				if(s) {
3423
					this.get_container_ul()[0].className = c;
3424
					if(this._firstChild(this.get_container_ul()[0])) {
3425
						this.element.attr('aria-activedescendant',this._firstChild(this.get_container_ul()[0]).id);
3426
					}
3427
					this.set_state($.extend(true, {}, this._data.core.state), function () {
3428
						/**
3429
						 * triggered when a `refresh` call completes
3430
						 * @event
3431
						 * @name refresh.jstree
3432
						 */
3433
						this.trigger('refresh');
3434
					});
3435
				}
3436
				this._data.core.state = null;
3437
			});
3438
		},
3439
		/**
3440
		 * refreshes a node in the tree (reload its children) all opened nodes inside that node are reloaded with calls to `load_node`.
3441
		 * @name refresh_node(obj)
3442
		 * @param  {mixed} obj the node
3443
		 * @trigger refresh_node.jstree
3444
		 */
3445
		refresh_node : function (obj) {
3446
			obj = this.get_node(obj);
3447
			if(!obj || obj.id === $.jstree.root) { return false; }
3448
			var opened = [], to_load = [], s = this._data.core.selected.concat([]);
3449
			to_load.push(obj.id);
3450
			if(obj.state.opened === true) { opened.push(obj.id); }
3451
			this.get_node(obj, true).find('.jstree-open').each(function() { opened.push(this.id); });
3452
			this._load_nodes(to_load, $.proxy(function (nodes) {
3453
				this.open_node(opened, false, 0);
3454
				this.select_node(this._data.core.selected);
3455
				/**
3456
				 * triggered when a node is refreshed
3457
				 * @event
3458
				 * @name refresh_node.jstree
3459
				 * @param {Object} node - the refreshed node
3460
				 * @param {Array} nodes - an array of the IDs of the nodes that were reloaded
3461
				 */
3462
				this.trigger('refresh_node', { 'node' : obj, 'nodes' : nodes });
3463
			}, this));
3464
		},
3465
		/**
3466
		 * set (change) the ID of a node
3467
		 * @name set_id(obj, id)
3468
		 * @param  {mixed} obj the node
3469
		 * @param  {String} id the new ID
3470
		 * @return {Boolean}
3471
		 */
3472
		set_id : function (obj, id) {
3473
			obj = this.get_node(obj);
3474
			if(!obj || obj.id === $.jstree.root) { return false; }
3475
			var i, j, m = this._model.data;
3476
			id = id.toString();
3477
			// update parents (replace current ID with new one in children and children_d)
3478
			m[obj.parent].children[$.inArray(obj.id, m[obj.parent].children)] = id;
3479
			for(i = 0, j = obj.parents.length; i < j; i++) {
3480
				m[obj.parents[i]].children_d[$.inArray(obj.id, m[obj.parents[i]].children_d)] = id;
3481
			}
3482
			// update children (replace current ID with new one in parent and parents)
3483
			for(i = 0, j = obj.children.length; i < j; i++) {
3484
				m[obj.children[i]].parent = id;
3485
			}
3486
			for(i = 0, j = obj.children_d.length; i < j; i++) {
3487
				m[obj.children_d[i]].parents[$.inArray(obj.id, m[obj.children_d[i]].parents)] = id;
3488
			}
3489
			i = $.inArray(obj.id, this._data.core.selected);
3490
			if(i !== -1) { this._data.core.selected[i] = id; }
3491
			// update model and obj itself (obj.id, this._model.data[KEY])
3492
			i = this.get_node(obj.id, true);
3493
			if(i) {
3494
				i.attr('id', id).children('.jstree-anchor').attr('id', id + '_anchor').end().attr('aria-labelledby', id + '_anchor');
3495
				if(this.element.attr('aria-activedescendant') === obj.id) {
3496
					this.element.attr('aria-activedescendant', id);
3497
				}
3498
			}
3499
			delete m[obj.id];
3500
			obj.id = id;
3501
			obj.li_attr.id = id;
3502
			m[id] = obj;
3503
			return true;
3504
		},
3505
		/**
3506
		 * get the text value of a node
3507
		 * @name get_text(obj)
3508
		 * @param  {mixed} obj the node
3509
		 * @return {String}
3510
		 */
3511
		get_text : function (obj) {
3512
			obj = this.get_node(obj);
3513
			return (!obj || obj.id === $.jstree.root) ? false : obj.text;
3514
		},
3515
		/**
3516
		 * set the text value of a node. Used internally, please use `rename_node(obj, val)`.
3517
		 * @private
3518
		 * @name set_text(obj, val)
3519
		 * @param  {mixed} obj the node, you can pass an array to set the text on multiple nodes
3520
		 * @param  {String} val the new text value
3521
		 * @return {Boolean}
3522
		 * @trigger set_text.jstree
3523
		 */
3524
		set_text : function (obj, val) {
3525
			var t1, t2;
3526
			if($.isArray(obj)) {
3527
				obj = obj.slice();
3528
				for(t1 = 0, t2 = obj.length; t1 < t2; t1++) {
3529
					this.set_text(obj[t1], val);
3530
				}
3531
				return true;
3532
			}
3533
			obj = this.get_node(obj);
3534
			if(!obj || obj.id === $.jstree.root) { return false; }
3535
			obj.text = val;
3536
			if(this.get_node(obj, true).length) {
3537
				this.redraw_node(obj.id);
3538
			}
3539
			/**
3540
			 * triggered when a node text value is changed
3541
			 * @event
3542
			 * @name set_text.jstree
3543
			 * @param {Object} obj
3544
			 * @param {String} text the new value
3545
			 */
3546
			this.trigger('set_text',{ "obj" : obj, "text" : val });
3547
			return true;
3548
		},
3549
		/**
3550
		 * gets a JSON representation of a node (or the whole tree)
3551
		 * @name get_json([obj, options])
3552
		 * @param  {mixed} obj
3553
		 * @param  {Object} options
3554
		 * @param  {Boolean} options.no_state do not return state information
3555
		 * @param  {Boolean} options.no_id do not return ID
3556
		 * @param  {Boolean} options.no_children do not include children
3557
		 * @param  {Boolean} options.no_data do not include node data
3558
		 * @param  {Boolean} options.flat return flat JSON instead of nested
3559
		 * @return {Object}
3560
		 */
3561
		get_json : function (obj, options, flat) {
3562
			obj = this.get_node(obj || $.jstree.root);
3563
			if(!obj) { return false; }
3564
			if(options && options.flat && !flat) { flat = []; }
3565
			var tmp = {
3566
				'id' : obj.id,
3567
				'text' : obj.text,
3568
				'icon' : this.get_icon(obj),
3569
				'li_attr' : $.extend(true, {}, obj.li_attr),
3570
				'a_attr' : $.extend(true, {}, obj.a_attr),
3571
				'state' : {},
3572
				'data' : options && options.no_data ? false : $.extend(true, {}, obj.data)
3573
				//( this.get_node(obj, true).length ? this.get_node(obj, true).data() : obj.data ),
3574
			}, i, j;
3575
			if(options && options.flat) {
3576
				tmp.parent = obj.parent;
3577
			}
3578
			else {
3579
				tmp.children = [];
3580
			}
3581
			if(!options || !options.no_state) {
3582
				for(i in obj.state) {
3583
					if(obj.state.hasOwnProperty(i)) {
3584
						tmp.state[i] = obj.state[i];
3585
					}
3586
				}
3587
			}
3588
			if(options && options.no_id) {
3589
				delete tmp.id;
3590
				if(tmp.li_attr && tmp.li_attr.id) {
3591
					delete tmp.li_attr.id;
3592
				}
3593
				if(tmp.a_attr && tmp.a_attr.id) {
3594
					delete tmp.a_attr.id;
3595
				}
3596
			}
3597
			if(options && options.flat && obj.id !== $.jstree.root) {
3598
				flat.push(tmp);
3599
			}
3600
			if(!options || !options.no_children) {
3601
				for(i = 0, j = obj.children.length; i < j; i++) {
3602
					if(options && options.flat) {
3603
						this.get_json(obj.children[i], options, flat);
3604
					}
3605
					else {
3606
						tmp.children.push(this.get_json(obj.children[i], options));
3607
					}
3608
				}
3609
			}
3610
			return options && options.flat ? flat : (obj.id === $.jstree.root ? tmp.children : tmp);
3611
		},
3612
		/**
3613
		 * create a new node (do not confuse with load_node)
3614
		 * @name create_node([obj, node, pos, callback, is_loaded])
3615
		 * @param  {mixed}   par       the parent node (to create a root node use either "#" (string) or `null`)
3616
		 * @param  {mixed}   node      the data for the new node (a valid JSON object, or a simple string with the name)
3617
		 * @param  {mixed}   pos       the index at which to insert the node, "first" and "last" are also supported, default is "last"
3618
		 * @param  {Function} callback a function to be called once the node is created
3619
		 * @param  {Boolean} is_loaded internal argument indicating if the parent node was succesfully loaded
3620
		 * @return {String}            the ID of the newly create node
3621
		 * @trigger model.jstree, create_node.jstree
3622
		 */
3623
		create_node : function (par, node, pos, callback, is_loaded) {
3624
			if(par === null) { par = $.jstree.root; }
3625
			par = this.get_node(par);
3626
			if(!par) { return false; }
3627
			pos = pos === undefined ? "last" : pos;
3628
			if(!pos.toString().match(/^(before|after)$/) && !is_loaded && !this.is_loaded(par)) {
3629
				return this.load_node(par, function () { this.create_node(par, node, pos, callback, true); });
3630
			}
3631
			if(!node) { node = { "text" : this.get_string('New node') }; }
3632
			if(typeof node === "string") { node = { "text" : node }; }
3633
			if(node.text === undefined) { node.text = this.get_string('New node'); }
3634
			var tmp, dpc, i, j;
3635
 
3636
			if(par.id === $.jstree.root) {
3637
				if(pos === "before") { pos = "first"; }
3638
				if(pos === "after") { pos = "last"; }
3639
			}
3640
			switch(pos) {
3641
				case "before":
3642
					tmp = this.get_node(par.parent);
3643
					pos = $.inArray(par.id, tmp.children);
3644
					par = tmp;
3645
					break;
3646
				case "after" :
3647
					tmp = this.get_node(par.parent);
3648
					pos = $.inArray(par.id, tmp.children) + 1;
3649
					par = tmp;
3650
					break;
3651
				case "inside":
3652
				case "first":
3653
					pos = 0;
3654
					break;
3655
				case "last":
3656
					pos = par.children.length;
3657
					break;
3658
				default:
3659
					if(!pos) { pos = 0; }
3660
					break;
3661
			}
3662
			if(pos > par.children.length) { pos = par.children.length; }
3663
			if(!node.id) { node.id = true; }
3664
			if(!this.check("create_node", node, par, pos)) {
3665
				this.settings.core.error.call(this, this._data.core.last_error);
3666
				return false;
3667
			}
3668
			if(node.id === true) { delete node.id; }
3669
			node = this._parse_model_from_json(node, par.id, par.parents.concat());
3670
			if(!node) { return false; }
3671
			tmp = this.get_node(node);
3672
			dpc = [];
3673
			dpc.push(node);
3674
			dpc = dpc.concat(tmp.children_d);
3675
			this.trigger('model', { "nodes" : dpc, "parent" : par.id });
3676
 
3677
			par.children_d = par.children_d.concat(dpc);
3678
			for(i = 0, j = par.parents.length; i < j; i++) {
3679
				this._model.data[par.parents[i]].children_d = this._model.data[par.parents[i]].children_d.concat(dpc);
3680
			}
3681
			node = tmp;
3682
			tmp = [];
3683
			for(i = 0, j = par.children.length; i < j; i++) {
3684
				tmp[i >= pos ? i+1 : i] = par.children[i];
3685
			}
3686
			tmp[pos] = node.id;
3687
			par.children = tmp;
3688
 
3689
			this.redraw_node(par, true);
3690
			if(callback) { callback.call(this, this.get_node(node)); }
3691
			/**
3692
			 * triggered when a node is created
3693
			 * @event
3694
			 * @name create_node.jstree
3695
			 * @param {Object} node
3696
			 * @param {String} parent the parent's ID
3697
			 * @param {Number} position the position of the new node among the parent's children
3698
			 */
3699
			this.trigger('create_node', { "node" : this.get_node(node), "parent" : par.id, "position" : pos });
3700
			return node.id;
3701
		},
3702
		/**
3703
		 * set the text value of a node
3704
		 * @name rename_node(obj, val)
3705
		 * @param  {mixed} obj the node, you can pass an array to rename multiple nodes to the same name
3706
		 * @param  {String} val the new text value
3707
		 * @return {Boolean}
3708
		 * @trigger rename_node.jstree
3709
		 */
3710
		rename_node : function (obj, val) {
3711
			var t1, t2, old;
3712
			if($.isArray(obj)) {
3713
				obj = obj.slice();
3714
				for(t1 = 0, t2 = obj.length; t1 < t2; t1++) {
3715
					this.rename_node(obj[t1], val);
3716
				}
3717
				return true;
3718
			}
3719
			obj = this.get_node(obj);
3720
			if(!obj || obj.id === $.jstree.root) { return false; }
3721
			old = obj.text;
3722
			if(!this.check("rename_node", obj, this.get_parent(obj), val)) {
3723
				this.settings.core.error.call(this, this._data.core.last_error);
3724
				return false;
3725
			}
3726
			this.set_text(obj, val); // .apply(this, Array.prototype.slice.call(arguments))
3727
			/**
3728
			 * triggered when a node is renamed
3729
			 * @event
3730
			 * @name rename_node.jstree
3731
			 * @param {Object} node
3732
			 * @param {String} text the new value
3733
			 * @param {String} old the old value
3734
			 */
3735
			this.trigger('rename_node', { "node" : obj, "text" : val, "old" : old });
3736
			return true;
3737
		},
3738
		/**
3739
		 * remove a node
3740
		 * @name delete_node(obj)
3741
		 * @param  {mixed} obj the node, you can pass an array to delete multiple nodes
3742
		 * @return {Boolean}
3743
		 * @trigger delete_node.jstree, changed.jstree
3744
		 */
3745
		delete_node : function (obj) {
3746
			var t1, t2, par, pos, tmp, i, j, k, l, c, top, lft;
3747
			if($.isArray(obj)) {
3748
				obj = obj.slice();
3749
				for(t1 = 0, t2 = obj.length; t1 < t2; t1++) {
3750
					this.delete_node(obj[t1]);
3751
				}
3752
				return true;
3753
			}
3754
			obj = this.get_node(obj);
3755
			if(!obj || obj.id === $.jstree.root) { return false; }
3756
			par = this.get_node(obj.parent);
3757
			pos = $.inArray(obj.id, par.children);
3758
			c = false;
3759
			if(!this.check("delete_node", obj, par, pos)) {
3760
				this.settings.core.error.call(this, this._data.core.last_error);
3761
				return false;
3762
			}
3763
			if(pos !== -1) {
3764
				par.children = $.vakata.array_remove(par.children, pos);
3765
			}
3766
			tmp = obj.children_d.concat([]);
3767
			tmp.push(obj.id);
3768
			for(k = 0, l = tmp.length; k < l; k++) {
3769
				for(i = 0, j = obj.parents.length; i < j; i++) {
3770
					pos = $.inArray(tmp[k], this._model.data[obj.parents[i]].children_d);
3771
					if(pos !== -1) {
3772
						this._model.data[obj.parents[i]].children_d = $.vakata.array_remove(this._model.data[obj.parents[i]].children_d, pos);
3773
					}
3774
				}
3775
				if(this._model.data[tmp[k]].state.selected) {
3776
					c = true;
3777
					pos = $.inArray(tmp[k], this._data.core.selected);
3778
					if(pos !== -1) {
3779
						this._data.core.selected = $.vakata.array_remove(this._data.core.selected, pos);
3780
					}
3781
				}
3782
			}
3783
			/**
3784
			 * triggered when a node is deleted
3785
			 * @event
3786
			 * @name delete_node.jstree
3787
			 * @param {Object} node
3788
			 * @param {String} parent the parent's ID
3789
			 */
3790
			this.trigger('delete_node', { "node" : obj, "parent" : par.id });
3791
			if(c) {
3792
				this.trigger('changed', { 'action' : 'delete_node', 'node' : obj, 'selected' : this._data.core.selected, 'parent' : par.id });
3793
			}
3794
			for(k = 0, l = tmp.length; k < l; k++) {
3795
				delete this._model.data[tmp[k]];
3796
			}
3797
			if($.inArray(this._data.core.focused, tmp) !== -1) {
3798
				this._data.core.focused = null;
3799
				top = this.element[0].scrollTop;
3800
				lft = this.element[0].scrollLeft;
3801
				if(par.id === $.jstree.root) {
3802
					this.get_node(this._model.data[$.jstree.root].children[0], true).children('.jstree-anchor').focus();
3803
				}
3804
				else {
3805
					this.get_node(par, true).children('.jstree-anchor').focus();
3806
				}
3807
				this.element[0].scrollTop  = top;
3808
				this.element[0].scrollLeft = lft;
3809
			}
3810
			this.redraw_node(par, true);
3811
			return true;
3812
		},
3813
		/**
3814
		 * check if an operation is premitted on the tree. Used internally.
3815
		 * @private
3816
		 * @name check(chk, obj, par, pos)
3817
		 * @param  {String} chk the operation to check, can be "create_node", "rename_node", "delete_node", "copy_node" or "move_node"
3818
		 * @param  {mixed} obj the node
3819
		 * @param  {mixed} par the parent
3820
		 * @param  {mixed} pos the position to insert at, or if "rename_node" - the new name
3821
		 * @param  {mixed} more some various additional information, for example if a "move_node" operations is triggered by DND this will be the hovered node
3822
		 * @return {Boolean}
3823
		 */
3824
		check : function (chk, obj, par, pos, more) {
3825
			obj = obj && obj.id ? obj : this.get_node(obj);
3826
			par = par && par.id ? par : this.get_node(par);
3827
			var tmp = chk.match(/^move_node|copy_node|create_node$/i) ? par : obj,
3828
				chc = this.settings.core.check_callback;
3829
			if(chk === "move_node" || chk === "copy_node") {
3830
				if((!more || !more.is_multi) && (obj.id === par.id || $.inArray(obj.id, par.children) === pos || $.inArray(par.id, obj.children_d) !== -1)) {
3831
					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 }) };
3832
					return false;
3833
				}
3834
			}
3835
			if(tmp && tmp.data) { tmp = tmp.data; }
3836
			if(tmp && tmp.functions && (tmp.functions[chk] === false || tmp.functions[chk] === true)) {
3837
				if(tmp.functions[chk] === false) {
3838
					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 }) };
3839
				}
3840
				return tmp.functions[chk];
3841
			}
3842
			if(chc === false || ($.isFunction(chc) && chc.call(this, chk, obj, par, pos, more) === false) || (chc && chc[chk] === false)) {
3843
				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 }) };
3844
				return false;
3845
			}
3846
			return true;
3847
		},
3848
		/**
3849
		 * get the last error
3850
		 * @name last_error()
3851
		 * @return {Object}
3852
		 */
3853
		last_error : function () {
3854
			return this._data.core.last_error;
3855
		},
3856
		/**
3857
		 * move a node to a new parent
3858
		 * @name move_node(obj, par [, pos, callback, is_loaded])
3859
		 * @param  {mixed} obj the node to move, pass an array to move multiple nodes
3860
		 * @param  {mixed} par the new parent
3861
		 * @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`
3862
		 * @param  {function} callback a function to call once the move is completed, receives 3 arguments - the node, the new parent and the position
3863
		 * @param  {Boolean} is_loaded internal parameter indicating if the parent node has been loaded
3864
		 * @param  {Boolean} skip_redraw internal parameter indicating if the tree should be redrawn
3865
		 * @param  {Boolean} instance internal parameter indicating if the node comes from another instance
3866
		 * @trigger move_node.jstree
3867
		 */
3868
		move_node : function (obj, par, pos, callback, is_loaded, skip_redraw, origin) {
3869
			var t1, t2, old_par, old_pos, new_par, old_ins, is_multi, dpc, tmp, i, j, k, l, p;
3870
 
3871
			par = this.get_node(par);
3872
			pos = pos === undefined ? 0 : pos;
3873
			if(!par) { return false; }
3874
			if(!pos.toString().match(/^(before|after)$/) && !is_loaded && !this.is_loaded(par)) {
3875
				return this.load_node(par, function () { this.move_node(obj, par, pos, callback, true, false, origin); });
3876
			}
3877
 
3878
			if($.isArray(obj)) {
3879
				if(obj.length === 1) {
3880
					obj = obj[0];
3881
				}
3882
				else {
3883
					//obj = obj.slice();
3884
					for(t1 = 0, t2 = obj.length; t1 < t2; t1++) {
3885
						if((tmp = this.move_node(obj[t1], par, pos, callback, is_loaded, false, origin))) {
3886
							par = tmp;
3887
							pos = "after";
3888
						}
3889
					}
3890
					this.redraw();
3891
					return true;
3892
				}
3893
			}
3894
			obj = obj && obj.id ? obj : this.get_node(obj);
3895
 
3896
			if(!obj || obj.id === $.jstree.root) { return false; }
3897
 
3898
			old_par = (obj.parent || $.jstree.root).toString();
3899
			new_par = (!pos.toString().match(/^(before|after)$/) || par.id === $.jstree.root) ? par : this.get_node(par.parent);
3900
			old_ins = origin ? origin : (this._model.data[obj.id] ? this : $.jstree.reference(obj.id));
3901
			is_multi = !old_ins || !old_ins._id || (this._id !== old_ins._id);
3902
			old_pos = old_ins && old_ins._id && old_par && old_ins._model.data[old_par] && old_ins._model.data[old_par].children ? $.inArray(obj.id, old_ins._model.data[old_par].children) : -1;
3903
			if(old_ins && old_ins._id) {
3904
				obj = old_ins._model.data[obj.id];
3905
			}
3906
 
3907
			if(is_multi) {
3908
				if((tmp = this.copy_node(obj, par, pos, callback, is_loaded, false, origin))) {
3909
					if(old_ins) { old_ins.delete_node(obj); }
3910
					return tmp;
3911
				}
3912
				return false;
3913
			}
3914
			//var m = this._model.data;
3915
			if(par.id === $.jstree.root) {
3916
				if(pos === "before") { pos = "first"; }
3917
				if(pos === "after") { pos = "last"; }
3918
			}
3919
			switch(pos) {
3920
				case "before":
3921
					pos = $.inArray(par.id, new_par.children);
3922
					break;
3923
				case "after" :
3924
					pos = $.inArray(par.id, new_par.children) + 1;
3925
					break;
3926
				case "inside":
3927
				case "first":
3928
					pos = 0;
3929
					break;
3930
				case "last":
3931
					pos = new_par.children.length;
3932
					break;
3933
				default:
3934
					if(!pos) { pos = 0; }
3935
					break;
3936
			}
3937
			if(pos > new_par.children.length) { pos = new_par.children.length; }
3938
			if(!this.check("move_node", obj, new_par, pos, { 'core' : true, 'origin' : origin, 'is_multi' : (old_ins && old_ins._id && old_ins._id !== this._id), 'is_foreign' : (!old_ins || !old_ins._id) })) {
3939
				this.settings.core.error.call(this, this._data.core.last_error);
3940
				return false;
3941
			}
3942
			if(obj.parent === new_par.id) {
3943
				dpc = new_par.children.concat();
3944
				tmp = $.inArray(obj.id, dpc);
3945
				if(tmp !== -1) {
3946
					dpc = $.vakata.array_remove(dpc, tmp);
3947
					if(pos > tmp) { pos--; }
3948
				}
3949
				tmp = [];
3950
				for(i = 0, j = dpc.length; i < j; i++) {
3951
					tmp[i >= pos ? i+1 : i] = dpc[i];
3952
				}
3953
				tmp[pos] = obj.id;
3954
				new_par.children = tmp;
3955
				this._node_changed(new_par.id);
3956
				this.redraw(new_par.id === $.jstree.root);
3957
			}
3958
			else {
3959
				// clean old parent and up
3960
				tmp = obj.children_d.concat();
3961
				tmp.push(obj.id);
3962
				for(i = 0, j = obj.parents.length; i < j; i++) {
3963
					dpc = [];
3964
					p = old_ins._model.data[obj.parents[i]].children_d;
3965
					for(k = 0, l = p.length; k < l; k++) {
3966
						if($.inArray(p[k], tmp) === -1) {
3967
							dpc.push(p[k]);
3968
						}
3969
					}
3970
					old_ins._model.data[obj.parents[i]].children_d = dpc;
3971
				}
3972
				old_ins._model.data[old_par].children = $.vakata.array_remove_item(old_ins._model.data[old_par].children, obj.id);
3973
 
3974
				// insert into new parent and up
3975
				for(i = 0, j = new_par.parents.length; i < j; i++) {
3976
					this._model.data[new_par.parents[i]].children_d = this._model.data[new_par.parents[i]].children_d.concat(tmp);
3977
				}
3978
				dpc = [];
3979
				for(i = 0, j = new_par.children.length; i < j; i++) {
3980
					dpc[i >= pos ? i+1 : i] = new_par.children[i];
3981
				}
3982
				dpc[pos] = obj.id;
3983
				new_par.children = dpc;
3984
				new_par.children_d.push(obj.id);
3985
				new_par.children_d = new_par.children_d.concat(obj.children_d);
3986
 
3987
				// update object
3988
				obj.parent = new_par.id;
3989
				tmp = new_par.parents.concat();
3990
				tmp.unshift(new_par.id);
3991
				p = obj.parents.length;
3992
				obj.parents = tmp;
3993
 
3994
				// update object children
3995
				tmp = tmp.concat();
3996
				for(i = 0, j = obj.children_d.length; i < j; i++) {
3997
					this._model.data[obj.children_d[i]].parents = this._model.data[obj.children_d[i]].parents.slice(0,p*-1);
3998
					Array.prototype.push.apply(this._model.data[obj.children_d[i]].parents, tmp);
3999
				}
4000
 
4001
				if(old_par === $.jstree.root || new_par.id === $.jstree.root) {
4002
					this._model.force_full_redraw = true;
4003
				}
4004
				if(!this._model.force_full_redraw) {
4005
					this._node_changed(old_par);
4006
					this._node_changed(new_par.id);
4007
				}
4008
				if(!skip_redraw) {
4009
					this.redraw();
4010
				}
4011
			}
4012
			if(callback) { callback.call(this, obj, new_par, pos); }
4013
			/**
4014
			 * triggered when a node is moved
4015
			 * @event
4016
			 * @name move_node.jstree
4017
			 * @param {Object} node
4018
			 * @param {String} parent the parent's ID
4019
			 * @param {Number} position the position of the node among the parent's children
4020
			 * @param {String} old_parent the old parent of the node
4021
			 * @param {Number} old_position the old position of the node
4022
			 * @param {Boolean} is_multi do the node and new parent belong to different instances
4023
			 * @param {jsTree} old_instance the instance the node came from
4024
			 * @param {jsTree} new_instance the instance of the new parent
4025
			 */
4026
			this.trigger('move_node', { "node" : obj, "parent" : new_par.id, "position" : pos, "old_parent" : old_par, "old_position" : old_pos, 'is_multi' : (old_ins && old_ins._id && old_ins._id !== this._id), 'is_foreign' : (!old_ins || !old_ins._id), 'old_instance' : old_ins, 'new_instance' : this });
4027
			return obj.id;
4028
		},
4029
		/**
4030
		 * copy a node to a new parent
4031
		 * @name copy_node(obj, par [, pos, callback, is_loaded])
4032
		 * @param  {mixed} obj the node to copy, pass an array to copy multiple nodes
4033
		 * @param  {mixed} par the new parent
4034
		 * @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`
4035
		 * @param  {function} callback a function to call once the move is completed, receives 3 arguments - the node, the new parent and the position
4036
		 * @param  {Boolean} is_loaded internal parameter indicating if the parent node has been loaded
4037
		 * @param  {Boolean} skip_redraw internal parameter indicating if the tree should be redrawn
4038
		 * @param  {Boolean} instance internal parameter indicating if the node comes from another instance
4039
		 * @trigger model.jstree copy_node.jstree
4040
		 */
4041
		copy_node : function (obj, par, pos, callback, is_loaded, skip_redraw, origin) {
4042
			var t1, t2, dpc, tmp, i, j, node, old_par, new_par, old_ins, is_multi;
4043
 
4044
			par = this.get_node(par);
4045
			pos = pos === undefined ? 0 : pos;
4046
			if(!par) { return false; }
4047
			if(!pos.toString().match(/^(before|after)$/) && !is_loaded && !this.is_loaded(par)) {
4048
				return this.load_node(par, function () { this.copy_node(obj, par, pos, callback, true, false, origin); });
4049
			}
4050
 
4051
			if($.isArray(obj)) {
4052
				if(obj.length === 1) {
4053
					obj = obj[0];
4054
				}
4055
				else {
4056
					//obj = obj.slice();
4057
					for(t1 = 0, t2 = obj.length; t1 < t2; t1++) {
4058
						if((tmp = this.copy_node(obj[t1], par, pos, callback, is_loaded, true, origin))) {
4059
							par = tmp;
4060
							pos = "after";
4061
						}
4062
					}
4063
					this.redraw();
4064
					return true;
4065
				}
4066
			}
4067
			obj = obj && obj.id ? obj : this.get_node(obj);
4068
			if(!obj || obj.id === $.jstree.root) { return false; }
4069
 
4070
			old_par = (obj.parent || $.jstree.root).toString();
4071
			new_par = (!pos.toString().match(/^(before|after)$/) || par.id === $.jstree.root) ? par : this.get_node(par.parent);
4072
			old_ins = origin ? origin : (this._model.data[obj.id] ? this : $.jstree.reference(obj.id));
4073
			is_multi = !old_ins || !old_ins._id || (this._id !== old_ins._id);
4074
 
4075
			if(old_ins && old_ins._id) {
4076
				obj = old_ins._model.data[obj.id];
4077
			}
4078
 
4079
			if(par.id === $.jstree.root) {
4080
				if(pos === "before") { pos = "first"; }
4081
				if(pos === "after") { pos = "last"; }
4082
			}
4083
			switch(pos) {
4084
				case "before":
4085
					pos = $.inArray(par.id, new_par.children);
4086
					break;
4087
				case "after" :
4088
					pos = $.inArray(par.id, new_par.children) + 1;
4089
					break;
4090
				case "inside":
4091
				case "first":
4092
					pos = 0;
4093
					break;
4094
				case "last":
4095
					pos = new_par.children.length;
4096
					break;
4097
				default:
4098
					if(!pos) { pos = 0; }
4099
					break;
4100
			}
4101
			if(pos > new_par.children.length) { pos = new_par.children.length; }
4102
			if(!this.check("copy_node", obj, new_par, pos, { 'core' : true, 'origin' : origin, 'is_multi' : (old_ins && old_ins._id && old_ins._id !== this._id), 'is_foreign' : (!old_ins || !old_ins._id) })) {
4103
				this.settings.core.error.call(this, this._data.core.last_error);
4104
				return false;
4105
			}
4106
			node = old_ins ? old_ins.get_json(obj, { no_id : true, no_data : true, no_state : true }) : obj;
4107
			if(!node) { return false; }
4108
			if(node.id === true) { delete node.id; }
4109
			node = this._parse_model_from_json(node, new_par.id, new_par.parents.concat());
4110
			if(!node) { return false; }
4111
			tmp = this.get_node(node);
4112
			if(obj && obj.state && obj.state.loaded === false) { tmp.state.loaded = false; }
4113
			dpc = [];
4114
			dpc.push(node);
4115
			dpc = dpc.concat(tmp.children_d);
4116
			this.trigger('model', { "nodes" : dpc, "parent" : new_par.id });
4117
 
4118
			// insert into new parent and up
4119
			for(i = 0, j = new_par.parents.length; i < j; i++) {
4120
				this._model.data[new_par.parents[i]].children_d = this._model.data[new_par.parents[i]].children_d.concat(dpc);
4121
			}
4122
			dpc = [];
4123
			for(i = 0, j = new_par.children.length; i < j; i++) {
4124
				dpc[i >= pos ? i+1 : i] = new_par.children[i];
4125
			}
4126
			dpc[pos] = tmp.id;
4127
			new_par.children = dpc;
4128
			new_par.children_d.push(tmp.id);
4129
			new_par.children_d = new_par.children_d.concat(tmp.children_d);
4130
 
4131
			if(new_par.id === $.jstree.root) {
4132
				this._model.force_full_redraw = true;
4133
			}
4134
			if(!this._model.force_full_redraw) {
4135
				this._node_changed(new_par.id);
4136
			}
4137
			if(!skip_redraw) {
4138
				this.redraw(new_par.id === $.jstree.root);
4139
			}
4140
			if(callback) { callback.call(this, tmp, new_par, pos); }
4141
			/**
4142
			 * triggered when a node is copied
4143
			 * @event
4144
			 * @name copy_node.jstree
4145
			 * @param {Object} node the copied node
4146
			 * @param {Object} original the original node
4147
			 * @param {String} parent the parent's ID
4148
			 * @param {Number} position the position of the node among the parent's children
4149
			 * @param {String} old_parent the old parent of the node
4150
			 * @param {Number} old_position the position of the original node
4151
			 * @param {Boolean} is_multi do the node and new parent belong to different instances
4152
			 * @param {jsTree} old_instance the instance the node came from
4153
			 * @param {jsTree} new_instance the instance of the new parent
4154
			 */
4155
			this.trigger('copy_node', { "node" : tmp, "original" : obj, "parent" : new_par.id, "position" : pos, "old_parent" : old_par, "old_position" : old_ins && old_ins._id && old_par && old_ins._model.data[old_par] && old_ins._model.data[old_par].children ? $.inArray(obj.id, old_ins._model.data[old_par].children) : -1,'is_multi' : (old_ins && old_ins._id && old_ins._id !== this._id), 'is_foreign' : (!old_ins || !old_ins._id), 'old_instance' : old_ins, 'new_instance' : this });
4156
			return tmp.id;
4157
		},
4158
		/**
4159
		 * cut a node (a later call to `paste(obj)` would move the node)
4160
		 * @name cut(obj)
4161
		 * @param  {mixed} obj multiple objects can be passed using an array
4162
		 * @trigger cut.jstree
4163
		 */
4164
		cut : function (obj) {
4165
			if(!obj) { obj = this._data.core.selected.concat(); }
4166
			if(!$.isArray(obj)) { obj = [obj]; }
4167
			if(!obj.length) { return false; }
4168
			var tmp = [], o, t1, t2;
4169
			for(t1 = 0, t2 = obj.length; t1 < t2; t1++) {
4170
				o = this.get_node(obj[t1]);
4171
				if(o && o.id && o.id !== $.jstree.root) { tmp.push(o); }
4172
			}
4173
			if(!tmp.length) { return false; }
4174
			ccp_node = tmp;
4175
			ccp_inst = this;
4176
			ccp_mode = 'move_node';
4177
			/**
4178
			 * triggered when nodes are added to the buffer for moving
4179
			 * @event
4180
			 * @name cut.jstree
4181
			 * @param {Array} node
4182
			 */
4183
			this.trigger('cut', { "node" : obj });
4184
		},
4185
		/**
4186
		 * copy a node (a later call to `paste(obj)` would copy the node)
4187
		 * @name copy(obj)
4188
		 * @param  {mixed} obj multiple objects can be passed using an array
4189
		 * @trigger copy.jstree
4190
		 */
4191
		copy : function (obj) {
4192
			if(!obj) { obj = this._data.core.selected.concat(); }
4193
			if(!$.isArray(obj)) { obj = [obj]; }
4194
			if(!obj.length) { return false; }
4195
			var tmp = [], o, t1, t2;
4196
			for(t1 = 0, t2 = obj.length; t1 < t2; t1++) {
4197
				o = this.get_node(obj[t1]);
4198
				if(o && o.id && o.id !== $.jstree.root) { tmp.push(o); }
4199
			}
4200
			if(!tmp.length) { return false; }
4201
			ccp_node = tmp;
4202
			ccp_inst = this;
4203
			ccp_mode = 'copy_node';
4204
			/**
4205
			 * triggered when nodes are added to the buffer for copying
4206
			 * @event
4207
			 * @name copy.jstree
4208
			 * @param {Array} node
4209
			 */
4210
			this.trigger('copy', { "node" : obj });
4211
		},
4212
		/**
4213
		 * get the current buffer (any nodes that are waiting for a paste operation)
4214
		 * @name get_buffer()
4215
		 * @return {Object} an object consisting of `mode` ("copy_node" or "move_node"), `node` (an array of objects) and `inst` (the instance)
4216
		 */
4217
		get_buffer : function () {
4218
			return { 'mode' : ccp_mode, 'node' : ccp_node, 'inst' : ccp_inst };
4219
		},
4220
		/**
4221
		 * check if there is something in the buffer to paste
4222
		 * @name can_paste()
4223
		 * @return {Boolean}
4224
		 */
4225
		can_paste : function () {
4226
			return ccp_mode !== false && ccp_node !== false; // && ccp_inst._model.data[ccp_node];
4227
		},
4228
		/**
4229
		 * copy or move the previously cut or copied nodes to a new parent
4230
		 * @name paste(obj [, pos])
4231
		 * @param  {mixed} obj the new parent
4232
		 * @param  {mixed} pos the position to insert at (besides integer, "first" and "last" are supported), defaults to integer `0`
4233
		 * @trigger paste.jstree
4234
		 */
4235
		paste : function (obj, pos) {
4236
			obj = this.get_node(obj);
4237
			if(!obj || !ccp_mode || !ccp_mode.match(/^(copy_node|move_node)$/) || !ccp_node) { return false; }
4238
			if(this[ccp_mode](ccp_node, obj, pos, false, false, false, ccp_inst)) {
4239
				/**
4240
				 * triggered when paste is invoked
4241
				 * @event
4242
				 * @name paste.jstree
4243
				 * @param {String} parent the ID of the receiving node
4244
				 * @param {Array} node the nodes in the buffer
4245
				 * @param {String} mode the performed operation - "copy_node" or "move_node"
4246
				 */
4247
				this.trigger('paste', { "parent" : obj.id, "node" : ccp_node, "mode" : ccp_mode });
4248
			}
4249
			ccp_node = false;
4250
			ccp_mode = false;
4251
			ccp_inst = false;
4252
		},
4253
		/**
4254
		 * clear the buffer of previously copied or cut nodes
4255
		 * @name clear_buffer()
4256
		 * @trigger clear_buffer.jstree
4257
		 */
4258
		clear_buffer : function () {
4259
			ccp_node = false;
4260
			ccp_mode = false;
4261
			ccp_inst = false;
4262
			/**
4263
			 * triggered when the copy / cut buffer is cleared
4264
			 * @event
4265
			 * @name clear_buffer.jstree
4266
			 */
4267
			this.trigger('clear_buffer');
4268
		},
4269
		/**
4270
		 * put a node in edit mode (input field to rename the node)
4271
		 * @name edit(obj [, default_text, callback])
4272
		 * @param  {mixed} obj
4273
		 * @param  {String} default_text the text to populate the input with (if omitted or set to a non-string value the node's text value is used)
4274
		 * @param  {Function} callback a function to be called once the text box is blurred, it is called in the instance's scope and receives the node, a status parameter (true if the rename is successful, false otherwise) and a boolean indicating if the user cancelled the edit. You can access the node's title using .text
4275
		 */
4276
		edit : function (obj, default_text, callback) {
4277
			var rtl, w, a, s, t, h1, h2, fn, tmp, cancel = false;
4278
			obj = this.get_node(obj);
4279
			if(!obj) { return false; }
4280
			if(this.settings.core.check_callback === false) {
4281
				this._data.core.last_error = { 'error' : 'check', 'plugin' : 'core', 'id' : 'core_07', 'reason' : 'Could not edit node because of check_callback' };
4282
				this.settings.core.error.call(this, this._data.core.last_error);
4283
				return false;
4284
			}
4285
			tmp = obj;
4286
			default_text = typeof default_text === 'string' ? default_text : obj.text;
4287
			this.set_text(obj, "");
4288
			obj = this._open_to(obj);
4289
			tmp.text = default_text;
4290
 
4291
			rtl = this._data.core.rtl;
4292
			w  = this.element.width();
4293
			this._data.core.focused = tmp.id;
4294
			a  = obj.children('.jstree-anchor').focus();
4295
			s  = $('<span>');
4296
			/*!
4297
			oi = obj.children("i:visible"),
4298
			ai = a.children("i:visible"),
4299
			w1 = oi.width() * oi.length,
4300
			w2 = ai.width() * ai.length,
4301
			*/
4302
			t  = default_text;
4303
			h1 = $("<"+"div />", { css : { "position" : "absolute", "top" : "-200px", "left" : (rtl ? "0px" : "-1000px"), "visibility" : "hidden" } }).appendTo("body");
4304
			h2 = $("<"+"input />", {
4305
						"value" : t,
4306
						"class" : "jstree-rename-input",
4307
						// "size" : t.length,
4308
						"css" : {
4309
							"padding" : "0",
4310
							"border" : "1px solid silver",
4311
							"box-sizing" : "border-box",
4312
							"display" : "inline-block",
4313
							"height" : (this._data.core.li_height) + "px",
4314
							"lineHeight" : (this._data.core.li_height) + "px",
4315
							"width" : "150px" // will be set a bit further down
4316
						},
4317
						"blur" : $.proxy(function (e) {
4318
							e.stopImmediatePropagation();
4319
							e.preventDefault();
4320
							var i = s.children(".jstree-rename-input"),
4321
								v = i.val(),
4322
								f = this.settings.core.force_text,
4323
								nv;
4324
							if(v === "") { v = t; }
4325
							h1.remove();
4326
							s.replaceWith(a);
4327
							s.remove();
4328
							t = f ? t : $('<div></div>').append($.parseHTML(t)).html();
4329
							this.set_text(obj, t);
4330
							nv = !!this.rename_node(obj, f ? $('<div></div>').text(v).text() : $('<div></div>').append($.parseHTML(v)).html());
4331
							if(!nv) {
4332
								this.set_text(obj, t); // move this up? and fix #483
4333
							}
4334
							this._data.core.focused = tmp.id;
4335
							setTimeout($.proxy(function () {
4336
								var node = this.get_node(tmp.id, true);
4337
								if(node.length) {
4338
									this._data.core.focused = tmp.id;
4339
									node.children('.jstree-anchor').focus();
4340
								}
4341
							}, this), 0);
4342
							if(callback) {
4343
								callback.call(this, tmp, nv, cancel);
4344
							}
4345
						}, this),
4346
						"keydown" : function (e) {
4347
							var key = e.which;
4348
							if(key === 27) {
4349
								cancel = true;
4350
								this.value = t;
4351
							}
4352
							if(key === 27 || key === 13 || key === 37 || key === 38 || key === 39 || key === 40 || key === 32) {
4353
								e.stopImmediatePropagation();
4354
							}
4355
							if(key === 27 || key === 13) {
4356
								e.preventDefault();
4357
								this.blur();
4358
							}
4359
						},
4360
						"click" : function (e) { e.stopImmediatePropagation(); },
4361
						"mousedown" : function (e) { e.stopImmediatePropagation(); },
4362
						"keyup" : function (e) {
4363
							h2.width(Math.min(h1.text("pW" + this.value).width(),w));
4364
						},
4365
						"keypress" : function(e) {
4366
							if(e.which === 13) { return false; }
4367
						}
4368
					});
4369
				fn = {
4370
						fontFamily		: a.css('fontFamily')		|| '',
4371
						fontSize		: a.css('fontSize')			|| '',
4372
						fontWeight		: a.css('fontWeight')		|| '',
4373
						fontStyle		: a.css('fontStyle')		|| '',
4374
						fontStretch		: a.css('fontStretch')		|| '',
4375
						fontVariant		: a.css('fontVariant')		|| '',
4376
						letterSpacing	: a.css('letterSpacing')	|| '',
4377
						wordSpacing		: a.css('wordSpacing')		|| ''
4378
				};
4379
			s.attr('class', a.attr('class')).append(a.contents().clone()).append(h2);
4380
			a.replaceWith(s);
4381
			h1.css(fn);
4382
			h2.css(fn).width(Math.min(h1.text("pW" + h2[0].value).width(),w))[0].select();
4383
		},
4384
 
4385
 
4386
		/**
4387
		 * changes the theme
4388
		 * @name set_theme(theme_name [, theme_url])
4389
		 * @param {String} theme_name the name of the new theme to apply
4390
		 * @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.
4391
		 * @trigger set_theme.jstree
4392
		 */
4393
		set_theme : function (theme_name, theme_url) {
4394
			if(!theme_name) { return false; }
4395
			if(theme_url === true) {
4396
				var dir = this.settings.core.themes.dir;
4397
				if(!dir) { dir = $.jstree.path + '/themes'; }
4398
				theme_url = dir + '/' + theme_name + '/style.css';
4399
			}
4400
			if(theme_url && $.inArray(theme_url, themes_loaded) === -1) {
4401
				$('head').append('<'+'link rel="stylesheet" href="' + theme_url + '" type="text/css" />');
4402
				themes_loaded.push(theme_url);
4403
			}
4404
			if(this._data.core.themes.name) {
4405
				this.element.removeClass('jstree-' + this._data.core.themes.name);
4406
			}
4407
			this._data.core.themes.name = theme_name;
4408
			this.element.addClass('jstree-' + theme_name);
4409
			this.element[this.settings.core.themes.responsive ? 'addClass' : 'removeClass' ]('jstree-' + theme_name + '-responsive');
4410
			/**
4411
			 * triggered when a theme is set
4412
			 * @event
4413
			 * @name set_theme.jstree
4414
			 * @param {String} theme the new theme
4415
			 */
4416
			this.trigger('set_theme', { 'theme' : theme_name });
4417
		},
4418
		/**
4419
		 * gets the name of the currently applied theme name
4420
		 * @name get_theme()
4421
		 * @return {String}
4422
		 */
4423
		get_theme : function () { return this._data.core.themes.name; },
4424
		/**
4425
		 * changes the theme variant (if the theme has variants)
4426
		 * @name set_theme_variant(variant_name)
4427
		 * @param {String|Boolean} variant_name the variant to apply (if `false` is used the current variant is removed)
4428
		 */
4429
		set_theme_variant : function (variant_name) {
4430
			if(this._data.core.themes.variant) {
4431
				this.element.removeClass('jstree-' + this._data.core.themes.name + '-' + this._data.core.themes.variant);
4432
			}
4433
			this._data.core.themes.variant = variant_name;
4434
			if(variant_name) {
4435
				this.element.addClass('jstree-' + this._data.core.themes.name + '-' + this._data.core.themes.variant);
4436
			}
4437
		},
4438
		/**
4439
		 * gets the name of the currently applied theme variant
4440
		 * @name get_theme()
4441
		 * @return {String}
4442
		 */
4443
		get_theme_variant : function () { return this._data.core.themes.variant; },
4444
		/**
4445
		 * shows a striped background on the container (if the theme supports it)
4446
		 * @name show_stripes()
4447
		 */
4448
		show_stripes : function () { this._data.core.themes.stripes = true; this.get_container_ul().addClass("jstree-striped"); },
4449
		/**
4450
		 * hides the striped background on the container
4451
		 * @name hide_stripes()
4452
		 */
4453
		hide_stripes : function () { this._data.core.themes.stripes = false; this.get_container_ul().removeClass("jstree-striped"); },
4454
		/**
4455
		 * toggles the striped background on the container
4456
		 * @name toggle_stripes()
4457
		 */
4458
		toggle_stripes : function () { if(this._data.core.themes.stripes) { this.hide_stripes(); } else { this.show_stripes(); } },
4459
		/**
4460
		 * shows the connecting dots (if the theme supports it)
4461
		 * @name show_dots()
4462
		 */
4463
		show_dots : function () { this._data.core.themes.dots = true; this.get_container_ul().removeClass("jstree-no-dots"); },
4464
		/**
4465
		 * hides the connecting dots
4466
		 * @name hide_dots()
4467
		 */
4468
		hide_dots : function () { this._data.core.themes.dots = false; this.get_container_ul().addClass("jstree-no-dots"); },
4469
		/**
4470
		 * toggles the connecting dots
4471
		 * @name toggle_dots()
4472
		 */
4473
		toggle_dots : function () { if(this._data.core.themes.dots) { this.hide_dots(); } else { this.show_dots(); } },
4474
		/**
4475
		 * show the node icons
4476
		 * @name show_icons()
4477
		 */
4478
		show_icons : function () { this._data.core.themes.icons = true; this.get_container_ul().removeClass("jstree-no-icons"); },
4479
		/**
4480
		 * hide the node icons
4481
		 * @name hide_icons()
4482
		 */
4483
		hide_icons : function () { this._data.core.themes.icons = false; this.get_container_ul().addClass("jstree-no-icons"); },
4484
		/**
4485
		 * toggle the node icons
4486
		 * @name toggle_icons()
4487
		 */
4488
		toggle_icons : function () { if(this._data.core.themes.icons) { this.hide_icons(); } else { this.show_icons(); } },
4489
		/**
4490
		 * set the node icon for a node
4491
		 * @name set_icon(obj, icon)
4492
		 * @param {mixed} obj
4493
		 * @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
4494
		 */
4495
		set_icon : function (obj, icon) {
4496
			var t1, t2, dom, old;
4497
			if($.isArray(obj)) {
4498
				obj = obj.slice();
4499
				for(t1 = 0, t2 = obj.length; t1 < t2; t1++) {
4500
					this.set_icon(obj[t1], icon);
4501
				}
4502
				return true;
4503
			}
4504
			obj = this.get_node(obj);
4505
			if(!obj || obj.id === $.jstree.root) { return false; }
4506
			old = obj.icon;
4507
			obj.icon = icon === true || icon === null || icon === undefined || icon === '' ? true : icon;
4508
			dom = this.get_node(obj, true).children(".jstree-anchor").children(".jstree-themeicon");
4509
			if(icon === false) {
4510
				this.hide_icon(obj);
4511
			}
4512
			else if(icon === true || icon === null || icon === undefined || icon === '') {
4513
				dom.removeClass('jstree-themeicon-custom ' + old).css("background","").removeAttr("rel");
4514
				if(old === false) { this.show_icon(obj); }
4515
			}
4516
			else if(icon.indexOf("/") === -1 && icon.indexOf(".") === -1) {
4517
				dom.removeClass(old).css("background","");
4518
				dom.addClass(icon + ' jstree-themeicon-custom').attr("rel",icon);
4519
				if(old === false) { this.show_icon(obj); }
4520
			}
4521
			else {
4522
				dom.removeClass(old).css("background","");
4523
				dom.addClass('jstree-themeicon-custom').css("background", "url('" + icon + "') center center no-repeat").attr("rel",icon);
4524
				if(old === false) { this.show_icon(obj); }
4525
			}
4526
			return true;
4527
		},
4528
		/**
4529
		 * get the node icon for a node
4530
		 * @name get_icon(obj)
4531
		 * @param {mixed} obj
4532
		 * @return {String}
4533
		 */
4534
		get_icon : function (obj) {
4535
			obj = this.get_node(obj);
4536
			return (!obj || obj.id === $.jstree.root) ? false : obj.icon;
4537
		},
4538
		/**
4539
		 * hide the icon on an individual node
4540
		 * @name hide_icon(obj)
4541
		 * @param {mixed} obj
4542
		 */
4543
		hide_icon : function (obj) {
4544
			var t1, t2;
4545
			if($.isArray(obj)) {
4546
				obj = obj.slice();
4547
				for(t1 = 0, t2 = obj.length; t1 < t2; t1++) {
4548
					this.hide_icon(obj[t1]);
4549
				}
4550
				return true;
4551
			}
4552
			obj = this.get_node(obj);
4553
			if(!obj || obj === $.jstree.root) { return false; }
4554
			obj.icon = false;
4555
			this.get_node(obj, true).children(".jstree-anchor").children(".jstree-themeicon").addClass('jstree-themeicon-hidden');
4556
			return true;
4557
		},
4558
		/**
4559
		 * show the icon on an individual node
4560
		 * @name show_icon(obj)
4561
		 * @param {mixed} obj
4562
		 */
4563
		show_icon : function (obj) {
4564
			var t1, t2, dom;
4565
			if($.isArray(obj)) {
4566
				obj = obj.slice();
4567
				for(t1 = 0, t2 = obj.length; t1 < t2; t1++) {
4568
					this.show_icon(obj[t1]);
4569
				}
4570
				return true;
4571
			}
4572
			obj = this.get_node(obj);
4573
			if(!obj || obj === $.jstree.root) { return false; }
4574
			dom = this.get_node(obj, true);
4575
			obj.icon = dom.length ? dom.children(".jstree-anchor").children(".jstree-themeicon").attr('rel') : true;
4576
			if(!obj.icon) { obj.icon = true; }
4577
			dom.children(".jstree-anchor").children(".jstree-themeicon").removeClass('jstree-themeicon-hidden');
4578
			return true;
4579
		}
4580
	};
4581
 
4582
	// helpers
4583
	$.vakata = {};
4584
	// collect attributes
4585
	$.vakata.attributes = function(node, with_values) {
4586
		node = $(node)[0];
4587
		var attr = with_values ? {} : [];
4588
		if(node && node.attributes) {
4589
			$.each(node.attributes, function (i, v) {
4590
				if($.inArray(v.name.toLowerCase(),['style','contenteditable','hasfocus','tabindex']) !== -1) { return; }
4591
				if(v.value !== null && $.trim(v.value) !== '') {
4592
					if(with_values) { attr[v.name] = v.value; }
4593
					else { attr.push(v.name); }
4594
				}
4595
			});
4596
		}
4597
		return attr;
4598
	};
4599
	$.vakata.array_unique = function(array) {
4600
		var a = [], i, j, l, o = {};
4601
		for(i = 0, l = array.length; i < l; i++) {
4602
			if(o[array[i]] === undefined) {
4603
				a.push(array[i]);
4604
				o[array[i]] = true;
4605
			}
4606
		}
4607
		return a;
4608
	};
4609
	// remove item from array
4610
	$.vakata.array_remove = function(array, from, to) {
4611
		var rest = array.slice((to || from) + 1 || array.length);
4612
		array.length = from < 0 ? array.length + from : from;
4613
		array.push.apply(array, rest);
4614
		return array;
4615
	};
4616
	// remove item from array
4617
	$.vakata.array_remove_item = function(array, item) {
4618
		var tmp = $.inArray(item, array);
4619
		return tmp !== -1 ? $.vakata.array_remove(array, tmp) : array;
4620
	};
4621
}));