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