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