comparison vakata-jstree-3.3.5/dist/jstree.js @ 0:dcf65671e56a draft

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