Subversion Repositories Integrator Subversion

Rev

Details | Last modification | View Log | RSS feed

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