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