Subversion Repositories oidplus

Rev

Rev 1101 | Details | Compare with Previous | Last modification | View Log | RSS feed

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