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