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