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