Mercurial > repos > mingchen0919 > aurora_htseq_site
comparison vakata-jstree-3.3.5/dist/jstree.js @ 0:dcf65671e56a draft
planemo upload commit 841d8b22bf9f1aaed6bfe8344b60617f45b275b2-dirty
author | mingchen0919 |
---|---|
date | Sun, 30 Dec 2018 12:52:51 -0500 |
parents | |
children |
comparison
equal
deleted
inserted
replaced
-1:000000000000 | 0:dcf65671e56a |
---|---|
1 /*globals jQuery, define, module, exports, require, window, document, postMessage */ | |
2 (function (factory) { | |
3 "use strict"; | |
4 if (typeof define === 'function' && define.amd) { | |
5 define(['jquery'], factory); | |
6 } | |
7 else if(typeof module !== 'undefined' && module.exports) { | |
8 module.exports = factory(require('jquery')); | |
9 } | |
10 else { | |
11 factory(jQuery); | |
12 } | |
13 }(function ($, undefined) { | |
14 "use strict"; | |
15 /*! | |
16 * jsTree 3.3.5 | |
17 * http://jstree.com/ | |
18 * | |
19 * Copyright (c) 2014 Ivan Bozhanov (http://vakata.com) | |
20 * | |
21 * Licensed same as jquery - under the terms of the MIT License | |
22 * http://www.opensource.org/licenses/mit-license.php | |
23 */ | |
24 /*! | |
25 * if using jslint please allow for the jQuery global and use following options: | |
26 * jslint: loopfunc: true, browser: true, ass: true, bitwise: true, continue: true, nomen: true, plusplus: true, regexp: true, unparam: true, todo: true, white: true | |
27 */ | |
28 /*jshint -W083 */ | |
29 | |
30 // prevent another load? maybe there is a better way? | |
31 if($.jstree) { | |
32 return; | |
33 } | |
34 | |
35 /** | |
36 * ### jsTree core functionality | |
37 */ | |
38 | |
39 // internal variables | |
40 var instance_counter = 0, | |
41 ccp_node = false, | |
42 ccp_mode = false, | |
43 ccp_inst = false, | |
44 themes_loaded = [], | |
45 src = $('script:last').attr('src'), | |
46 document = window.document; // local variable is always faster to access then a global | |
47 | |
48 /** | |
49 * holds all jstree related functions and variables, including the actual class and methods to create, access and manipulate instances. | |
50 * @name $.jstree | |
51 */ | |
52 $.jstree = { | |
53 /** | |
54 * specifies the jstree version in use | |
55 * @name $.jstree.version | |
56 */ | |
57 version : '3.3.5', | |
58 /** | |
59 * holds all the default options used when creating new instances | |
60 * @name $.jstree.defaults | |
61 */ | |
62 defaults : { | |
63 /** | |
64 * configure which plugins will be active on an instance. Should be an array of strings, where each element is a plugin name. The default is `[]` | |
65 * @name $.jstree.defaults.plugins | |
66 */ | |
67 plugins : [] | |
68 }, | |
69 /** | |
70 * stores all loaded jstree plugins (used internally) | |
71 * @name $.jstree.plugins | |
72 */ | |
73 plugins : {}, | |
74 path : src && src.indexOf('/') !== -1 ? src.replace(/\/[^\/]+$/,'') : '', | |
75 idregex : /[\\:&!^|()\[\]<>@*'+~#";.,=\- \/${}%?`]/g, | |
76 root : '#' | |
77 }; | |
78 | |
79 /** | |
80 * creates a jstree instance | |
81 * @name $.jstree.create(el [, options]) | |
82 * @param {DOMElement|jQuery|String} el the element to create the instance on, can be jQuery extended or a selector | |
83 * @param {Object} options options for this instance (extends `$.jstree.defaults`) | |
84 * @return {jsTree} the new instance | |
85 */ | |
86 $.jstree.create = function (el, options) { | |
87 var tmp = new $.jstree.core(++instance_counter), | |
88 opt = options; | |
89 options = $.extend(true, {}, $.jstree.defaults, options); | |
90 if(opt && opt.plugins) { | |
91 options.plugins = opt.plugins; | |
92 } | |
93 $.each(options.plugins, function (i, k) { | |
94 if(i !== 'core') { | |
95 tmp = tmp.plugin(k, options[k]); | |
96 } | |
97 }); | |
98 $(el).data('jstree', tmp); | |
99 tmp.init(el, options); | |
100 return tmp; | |
101 }; | |
102 /** | |
103 * remove all traces of jstree from the DOM and destroy all instances | |
104 * @name $.jstree.destroy() | |
105 */ | |
106 $.jstree.destroy = function () { | |
107 $('.jstree:jstree').jstree('destroy'); | |
108 $(document).off('.jstree'); | |
109 }; | |
110 /** | |
111 * the jstree class constructor, used only internally | |
112 * @private | |
113 * @name $.jstree.core(id) | |
114 * @param {Number} id this instance's index | |
115 */ | |
116 $.jstree.core = function (id) { | |
117 this._id = id; | |
118 this._cnt = 0; | |
119 this._wrk = null; | |
120 this._data = { | |
121 core : { | |
122 themes : { | |
123 name : false, | |
124 dots : false, | |
125 icons : false, | |
126 ellipsis : false | |
127 }, | |
128 selected : [], | |
129 last_error : {}, | |
130 working : false, | |
131 worker_queue : [], | |
132 focused : null | |
133 } | |
134 }; | |
135 }; | |
136 /** | |
137 * get a reference to an existing instance | |
138 * | |
139 * __Examples__ | |
140 * | |
141 * // provided a container with an ID of "tree", and a nested node with an ID of "branch" | |
142 * // all of there will return the same instance | |
143 * $.jstree.reference('tree'); | |
144 * $.jstree.reference('#tree'); | |
145 * $.jstree.reference($('#tree')); | |
146 * $.jstree.reference(document.getElementByID('tree')); | |
147 * $.jstree.reference('branch'); | |
148 * $.jstree.reference('#branch'); | |
149 * $.jstree.reference($('#branch')); | |
150 * $.jstree.reference(document.getElementByID('branch')); | |
151 * | |
152 * @name $.jstree.reference(needle) | |
153 * @param {DOMElement|jQuery|String} needle | |
154 * @return {jsTree|null} the instance or `null` if not found | |
155 */ | |
156 $.jstree.reference = function (needle) { | |
157 var tmp = null, | |
158 obj = null; | |
159 if(needle && needle.id && (!needle.tagName || !needle.nodeType)) { needle = needle.id; } | |
160 | |
161 if(!obj || !obj.length) { | |
162 try { obj = $(needle); } catch (ignore) { } | |
163 } | |
164 if(!obj || !obj.length) { | |
165 try { obj = $('#' + needle.replace($.jstree.idregex,'\\$&')); } catch (ignore) { } | |
166 } | |
167 if(obj && obj.length && (obj = obj.closest('.jstree')).length && (obj = obj.data('jstree'))) { | |
168 tmp = obj; | |
169 } | |
170 else { | |
171 $('.jstree').each(function () { | |
172 var inst = $(this).data('jstree'); | |
173 if(inst && inst._model.data[needle]) { | |
174 tmp = inst; | |
175 return false; | |
176 } | |
177 }); | |
178 } | |
179 return tmp; | |
180 }; | |
181 /** | |
182 * Create an instance, get an instance or invoke a command on a instance. | |
183 * | |
184 * If there is no instance associated with the current node a new one is created and `arg` is used to extend `$.jstree.defaults` for this new instance. There would be no return value (chaining is not broken). | |
185 * | |
186 * If there is an existing instance and `arg` is a string the command specified by `arg` is executed on the instance, with any additional arguments passed to the function. If the function returns a value it will be returned (chaining could break depending on function). | |
187 * | |
188 * If there is an existing instance and `arg` is not a string the instance itself is returned (similar to `$.jstree.reference`). | |
189 * | |
190 * In any other case - nothing is returned and chaining is not broken. | |
191 * | |
192 * __Examples__ | |
193 * | |
194 * $('#tree1').jstree(); // creates an instance | |
195 * $('#tree2').jstree({ plugins : [] }); // create an instance with some options | |
196 * $('#tree1').jstree('open_node', '#branch_1'); // call a method on an existing instance, passing additional arguments | |
197 * $('#tree2').jstree(); // get an existing instance (or create an instance) | |
198 * $('#tree2').jstree(true); // get an existing instance (will not create new instance) | |
199 * $('#branch_1').jstree().select_node('#branch_1'); // get an instance (using a nested element and call a method) | |
200 * | |
201 * @name $().jstree([arg]) | |
202 * @param {String|Object} arg | |
203 * @return {Mixed} | |
204 */ | |
205 $.fn.jstree = function (arg) { | |
206 // check for string argument | |
207 var is_method = (typeof arg === 'string'), | |
208 args = Array.prototype.slice.call(arguments, 1), | |
209 result = null; | |
210 if(arg === true && !this.length) { return false; } | |
211 this.each(function () { | |
212 // get the instance (if there is one) and method (if it exists) | |
213 var instance = $.jstree.reference(this), | |
214 method = is_method && instance ? instance[arg] : null; | |
215 // if calling a method, and method is available - execute on the instance | |
216 result = is_method && method ? | |
217 method.apply(instance, args) : | |
218 null; | |
219 // if there is no instance and no method is being called - create one | |
220 if(!instance && !is_method && (arg === undefined || $.isPlainObject(arg))) { | |
221 $.jstree.create(this, arg); | |
222 } | |
223 // if there is an instance and no method is called - return the instance | |
224 if( (instance && !is_method) || arg === true ) { | |
225 result = instance || false; | |
226 } | |
227 // if there was a method call which returned a result - break and return the value | |
228 if(result !== null && result !== undefined) { | |
229 return false; | |
230 } | |
231 }); | |
232 // if there was a method call with a valid return value - return that, otherwise continue the chain | |
233 return result !== null && result !== undefined ? | |
234 result : this; | |
235 }; | |
236 /** | |
237 * used to find elements containing an instance | |
238 * | |
239 * __Examples__ | |
240 * | |
241 * $('div:jstree').each(function () { | |
242 * $(this).jstree('destroy'); | |
243 * }); | |
244 * | |
245 * @name $(':jstree') | |
246 * @return {jQuery} | |
247 */ | |
248 $.expr.pseudos.jstree = $.expr.createPseudo(function(search) { | |
249 return function(a) { | |
250 return $(a).hasClass('jstree') && | |
251 $(a).data('jstree') !== undefined; | |
252 }; | |
253 }); | |
254 | |
255 /** | |
256 * stores all defaults for the core | |
257 * @name $.jstree.defaults.core | |
258 */ | |
259 $.jstree.defaults.core = { | |
260 /** | |
261 * data configuration | |
262 * | |
263 * If left as `false` the HTML inside the jstree container element is used to populate the tree (that should be an unordered list with list items). | |
264 * | |
265 * You can also pass in a HTML string or a JSON array here. | |
266 * | |
267 * It is possible to pass in a standard jQuery-like AJAX config and jstree will automatically determine if the response is JSON or HTML and use that to populate the tree. | |
268 * In addition to the standard jQuery ajax options here you can suppy functions for `data` and `url`, the functions will be run in the current instance's scope and a param will be passed indicating which node is being loaded, the return value of those functions will be used. | |
269 * | |
270 * The last option is to specify a function, that function will receive the node being loaded as argument and a second param which is a function which should be called with the result. | |
271 * | |
272 * __Examples__ | |
273 * | |
274 * // AJAX | |
275 * $('#tree').jstree({ | |
276 * 'core' : { | |
277 * 'data' : { | |
278 * 'url' : '/get/children/', | |
279 * 'data' : function (node) { | |
280 * return { 'id' : node.id }; | |
281 * } | |
282 * } | |
283 * }); | |
284 * | |
285 * // direct data | |
286 * $('#tree').jstree({ | |
287 * 'core' : { | |
288 * 'data' : [ | |
289 * 'Simple root node', | |
290 * { | |
291 * 'id' : 'node_2', | |
292 * 'text' : 'Root node with options', | |
293 * 'state' : { 'opened' : true, 'selected' : true }, | |
294 * 'children' : [ { 'text' : 'Child 1' }, 'Child 2'] | |
295 * } | |
296 * ] | |
297 * } | |
298 * }); | |
299 * | |
300 * // function | |
301 * $('#tree').jstree({ | |
302 * 'core' : { | |
303 * 'data' : function (obj, callback) { | |
304 * callback.call(this, ['Root 1', 'Root 2']); | |
305 * } | |
306 * }); | |
307 * | |
308 * @name $.jstree.defaults.core.data | |
309 */ | |
310 data : false, | |
311 /** | |
312 * configure the various strings used throughout the tree | |
313 * | |
314 * You can use an object where the key is the string you need to replace and the value is your replacement. | |
315 * Another option is to specify a function which will be called with an argument of the needed string and should return the replacement. | |
316 * If left as `false` no replacement is made. | |
317 * | |
318 * __Examples__ | |
319 * | |
320 * $('#tree').jstree({ | |
321 * 'core' : { | |
322 * 'strings' : { | |
323 * 'Loading ...' : 'Please wait ...' | |
324 * } | |
325 * } | |
326 * }); | |
327 * | |
328 * @name $.jstree.defaults.core.strings | |
329 */ | |
330 strings : false, | |
331 /** | |
332 * determines what happens when a user tries to modify the structure of the tree | |
333 * If left as `false` all operations like create, rename, delete, move or copy are prevented. | |
334 * You can set this to `true` to allow all interactions or use a function to have better control. | |
335 * | |
336 * __Examples__ | |
337 * | |
338 * $('#tree').jstree({ | |
339 * 'core' : { | |
340 * 'check_callback' : function (operation, node, node_parent, node_position, more) { | |
341 * // operation can be 'create_node', 'rename_node', 'delete_node', 'move_node', 'copy_node' or 'edit' | |
342 * // in case of 'rename_node' node_position is filled with the new node name | |
343 * return operation === 'rename_node' ? true : false; | |
344 * } | |
345 * } | |
346 * }); | |
347 * | |
348 * @name $.jstree.defaults.core.check_callback | |
349 */ | |
350 check_callback : false, | |
351 /** | |
352 * a callback called with a single object parameter in the instance's scope when something goes wrong (operation prevented, ajax failed, etc) | |
353 * @name $.jstree.defaults.core.error | |
354 */ | |
355 error : $.noop, | |
356 /** | |
357 * the open / close animation duration in milliseconds - set this to `false` to disable the animation (default is `200`) | |
358 * @name $.jstree.defaults.core.animation | |
359 */ | |
360 animation : 200, | |
361 /** | |
362 * a boolean indicating if multiple nodes can be selected | |
363 * @name $.jstree.defaults.core.multiple | |
364 */ | |
365 multiple : true, | |
366 /** | |
367 * theme configuration object | |
368 * @name $.jstree.defaults.core.themes | |
369 */ | |
370 themes : { | |
371 /** | |
372 * the name of the theme to use (if left as `false` the default theme is used) | |
373 * @name $.jstree.defaults.core.themes.name | |
374 */ | |
375 name : false, | |
376 /** | |
377 * the URL of the theme's CSS file, leave this as `false` if you have manually included the theme CSS (recommended). You can set this to `true` too which will try to autoload the theme. | |
378 * @name $.jstree.defaults.core.themes.url | |
379 */ | |
380 url : false, | |
381 /** | |
382 * the location of all jstree themes - only used if `url` is set to `true` | |
383 * @name $.jstree.defaults.core.themes.dir | |
384 */ | |
385 dir : false, | |
386 /** | |
387 * a boolean indicating if connecting dots are shown | |
388 * @name $.jstree.defaults.core.themes.dots | |
389 */ | |
390 dots : true, | |
391 /** | |
392 * a boolean indicating if node icons are shown | |
393 * @name $.jstree.defaults.core.themes.icons | |
394 */ | |
395 icons : true, | |
396 /** | |
397 * a boolean indicating if node ellipsis should be shown - this only works with a fixed with on the container | |
398 * @name $.jstree.defaults.core.themes.ellipsis | |
399 */ | |
400 ellipsis : false, | |
401 /** | |
402 * a boolean indicating if the tree background is striped | |
403 * @name $.jstree.defaults.core.themes.stripes | |
404 */ | |
405 stripes : false, | |
406 /** | |
407 * a string (or boolean `false`) specifying the theme variant to use (if the theme supports variants) | |
408 * @name $.jstree.defaults.core.themes.variant | |
409 */ | |
410 variant : false, | |
411 /** | |
412 * a boolean specifying if a reponsive version of the theme should kick in on smaller screens (if the theme supports it). Defaults to `false`. | |
413 * @name $.jstree.defaults.core.themes.responsive | |
414 */ | |
415 responsive : false | |
416 }, | |
417 /** | |
418 * if left as `true` all parents of all selected nodes will be opened once the tree loads (so that all selected nodes are visible to the user) | |
419 * @name $.jstree.defaults.core.expand_selected_onload | |
420 */ | |
421 expand_selected_onload : true, | |
422 /** | |
423 * if left as `true` web workers will be used to parse incoming JSON data where possible, so that the UI will not be blocked by large requests. Workers are however about 30% slower. Defaults to `true` | |
424 * @name $.jstree.defaults.core.worker | |
425 */ | |
426 worker : true, | |
427 /** | |
428 * Force node text to plain text (and escape HTML). Defaults to `false` | |
429 * @name $.jstree.defaults.core.force_text | |
430 */ | |
431 force_text : false, | |
432 /** | |
433 * Should the node should be toggled if the text is double clicked . Defaults to `true` | |
434 * @name $.jstree.defaults.core.dblclick_toggle | |
435 */ | |
436 dblclick_toggle : true, | |
437 /** | |
438 * Should the loaded nodes be part of the state. Defaults to `false` | |
439 * @name $.jstree.defaults.core.loaded_state | |
440 */ | |
441 loaded_state : false, | |
442 /** | |
443 * Should the last active node be focused when the tree container is blurred and the focused again. This helps working with screen readers. Defaults to `true` | |
444 * @name $.jstree.defaults.core.restore_focus | |
445 */ | |
446 restore_focus : true, | |
447 /** | |
448 * Default keyboard shortcuts (an object where each key is the button name or combo - like 'enter', 'ctrl-space', 'p', etc and the value is the function to execute in the instance's scope) | |
449 * @name $.jstree.defaults.core.keyboard | |
450 */ | |
451 keyboard : { | |
452 'ctrl-space': function (e) { | |
453 // aria defines space only with Ctrl | |
454 e.type = "click"; | |
455 $(e.currentTarget).trigger(e); | |
456 }, | |
457 'enter': function (e) { | |
458 // enter | |
459 e.type = "click"; | |
460 $(e.currentTarget).trigger(e); | |
461 }, | |
462 'left': function (e) { | |
463 // left | |
464 e.preventDefault(); | |
465 if(this.is_open(e.currentTarget)) { | |
466 this.close_node(e.currentTarget); | |
467 } | |
468 else { | |
469 var o = this.get_parent(e.currentTarget); | |
470 if(o && o.id !== $.jstree.root) { this.get_node(o, true).children('.jstree-anchor').focus(); } | |
471 } | |
472 }, | |
473 'up': function (e) { | |
474 // up | |
475 e.preventDefault(); | |
476 var o = this.get_prev_dom(e.currentTarget); | |
477 if(o && o.length) { o.children('.jstree-anchor').focus(); } | |
478 }, | |
479 'right': function (e) { | |
480 // right | |
481 e.preventDefault(); | |
482 if(this.is_closed(e.currentTarget)) { | |
483 this.open_node(e.currentTarget, function (o) { this.get_node(o, true).children('.jstree-anchor').focus(); }); | |
484 } | |
485 else if (this.is_open(e.currentTarget)) { | |
486 var o = this.get_node(e.currentTarget, true).children('.jstree-children')[0]; | |
487 if(o) { $(this._firstChild(o)).children('.jstree-anchor').focus(); } | |
488 } | |
489 }, | |
490 'down': function (e) { | |
491 // down | |
492 e.preventDefault(); | |
493 var o = this.get_next_dom(e.currentTarget); | |
494 if(o && o.length) { o.children('.jstree-anchor').focus(); } | |
495 }, | |
496 '*': function (e) { | |
497 // aria defines * on numpad as open_all - not very common | |
498 this.open_all(); | |
499 }, | |
500 'home': function (e) { | |
501 // home | |
502 e.preventDefault(); | |
503 var o = this._firstChild(this.get_container_ul()[0]); | |
504 if(o) { $(o).children('.jstree-anchor').filter(':visible').focus(); } | |
505 }, | |
506 'end': function (e) { | |
507 // end | |
508 e.preventDefault(); | |
509 this.element.find('.jstree-anchor').filter(':visible').last().focus(); | |
510 }, | |
511 'f2': function (e) { | |
512 // f2 - safe to include - if check_callback is false it will fail | |
513 e.preventDefault(); | |
514 this.edit(e.currentTarget); | |
515 } | |
516 } | |
517 }; | |
518 $.jstree.core.prototype = { | |
519 /** | |
520 * used to decorate an instance with a plugin. Used internally. | |
521 * @private | |
522 * @name plugin(deco [, opts]) | |
523 * @param {String} deco the plugin to decorate with | |
524 * @param {Object} opts options for the plugin | |
525 * @return {jsTree} | |
526 */ | |
527 plugin : function (deco, opts) { | |
528 var Child = $.jstree.plugins[deco]; | |
529 if(Child) { | |
530 this._data[deco] = {}; | |
531 Child.prototype = this; | |
532 return new Child(opts, this); | |
533 } | |
534 return this; | |
535 }, | |
536 /** | |
537 * initialize the instance. Used internally. | |
538 * @private | |
539 * @name init(el, optons) | |
540 * @param {DOMElement|jQuery|String} el the element we are transforming | |
541 * @param {Object} options options for this instance | |
542 * @trigger init.jstree, loading.jstree, loaded.jstree, ready.jstree, changed.jstree | |
543 */ | |
544 init : function (el, options) { | |
545 this._model = { | |
546 data : {}, | |
547 changed : [], | |
548 force_full_redraw : false, | |
549 redraw_timeout : false, | |
550 default_state : { | |
551 loaded : true, | |
552 opened : false, | |
553 selected : false, | |
554 disabled : false | |
555 } | |
556 }; | |
557 this._model.data[$.jstree.root] = { | |
558 id : $.jstree.root, | |
559 parent : null, | |
560 parents : [], | |
561 children : [], | |
562 children_d : [], | |
563 state : { loaded : false } | |
564 }; | |
565 | |
566 this.element = $(el).addClass('jstree jstree-' + this._id); | |
567 this.settings = options; | |
568 | |
569 this._data.core.ready = false; | |
570 this._data.core.loaded = false; | |
571 this._data.core.rtl = (this.element.css("direction") === "rtl"); | |
572 this.element[this._data.core.rtl ? 'addClass' : 'removeClass']("jstree-rtl"); | |
573 this.element.attr('role','tree'); | |
574 if(this.settings.core.multiple) { | |
575 this.element.attr('aria-multiselectable', true); | |
576 } | |
577 if(!this.element.attr('tabindex')) { | |
578 this.element.attr('tabindex','0'); | |
579 } | |
580 | |
581 this.bind(); | |
582 /** | |
583 * triggered after all events are bound | |
584 * @event | |
585 * @name init.jstree | |
586 */ | |
587 this.trigger("init"); | |
588 | |
589 this._data.core.original_container_html = this.element.find(" > ul > li").clone(true); | |
590 this._data.core.original_container_html | |
591 .find("li").addBack() | |
592 .contents().filter(function() { | |
593 return this.nodeType === 3 && (!this.nodeValue || /^\s+$/.test(this.nodeValue)); | |
594 }) | |
595 .remove(); | |
596 this.element.html("<"+"ul class='jstree-container-ul jstree-children' role='group'><"+"li id='j"+this._id+"_loading' class='jstree-initial-node jstree-loading jstree-leaf jstree-last' role='tree-item'><i class='jstree-icon jstree-ocl'></i><"+"a class='jstree-anchor' href='#'><i class='jstree-icon jstree-themeicon-hidden'></i>" + this.get_string("Loading ...") + "</a></li></ul>"); | |
597 this.element.attr('aria-activedescendant','j' + this._id + '_loading'); | |
598 this._data.core.li_height = this.get_container_ul().children("li").first().outerHeight() || 24; | |
599 this._data.core.node = this._create_prototype_node(); | |
600 /** | |
601 * triggered after the loading text is shown and before loading starts | |
602 * @event | |
603 * @name loading.jstree | |
604 */ | |
605 this.trigger("loading"); | |
606 this.load_node($.jstree.root); | |
607 }, | |
608 /** | |
609 * destroy an instance | |
610 * @name destroy() | |
611 * @param {Boolean} keep_html if not set to `true` the container will be emptied, otherwise the current DOM elements will be kept intact | |
612 */ | |
613 destroy : function (keep_html) { | |
614 /** | |
615 * triggered before the tree is destroyed | |
616 * @event | |
617 * @name destroy.jstree | |
618 */ | |
619 this.trigger("destroy"); | |
620 if(this._wrk) { | |
621 try { | |
622 window.URL.revokeObjectURL(this._wrk); | |
623 this._wrk = null; | |
624 } | |
625 catch (ignore) { } | |
626 } | |
627 if(!keep_html) { this.element.empty(); } | |
628 this.teardown(); | |
629 }, | |
630 /** | |
631 * Create a prototype node | |
632 * @name _create_prototype_node() | |
633 * @return {DOMElement} | |
634 */ | |
635 _create_prototype_node : function () { | |
636 var _node = document.createElement('LI'), _temp1, _temp2; | |
637 _node.setAttribute('role', 'treeitem'); | |
638 _temp1 = document.createElement('I'); | |
639 _temp1.className = 'jstree-icon jstree-ocl'; | |
640 _temp1.setAttribute('role', 'presentation'); | |
641 _node.appendChild(_temp1); | |
642 _temp1 = document.createElement('A'); | |
643 _temp1.className = 'jstree-anchor'; | |
644 _temp1.setAttribute('href','#'); | |
645 _temp1.setAttribute('tabindex','-1'); | |
646 _temp2 = document.createElement('I'); | |
647 _temp2.className = 'jstree-icon jstree-themeicon'; | |
648 _temp2.setAttribute('role', 'presentation'); | |
649 _temp1.appendChild(_temp2); | |
650 _node.appendChild(_temp1); | |
651 _temp1 = _temp2 = null; | |
652 | |
653 return _node; | |
654 }, | |
655 _kbevent_to_func : function (e) { | |
656 var keys = { | |
657 8: "Backspace", 9: "Tab", 13: "Return", 19: "Pause", 27: "Esc", | |
658 32: "Space", 33: "PageUp", 34: "PageDown", 35: "End", 36: "Home", | |
659 37: "Left", 38: "Up", 39: "Right", 40: "Down", 44: "Print", 45: "Insert", | |
660 46: "Delete", 96: "Numpad0", 97: "Numpad1", 98: "Numpad2", 99 : "Numpad3", | |
661 100: "Numpad4", 101: "Numpad5", 102: "Numpad6", 103: "Numpad7", | |
662 104: "Numpad8", 105: "Numpad9", '-13': "NumpadEnter", 112: "F1", | |
663 113: "F2", 114: "F3", 115: "F4", 116: "F5", 117: "F6", 118: "F7", | |
664 119: "F8", 120: "F9", 121: "F10", 122: "F11", 123: "F12", 144: "Numlock", | |
665 145: "Scrolllock", 16: 'Shift', 17: 'Ctrl', 18: 'Alt', | |
666 48: '0', 49: '1', 50: '2', 51: '3', 52: '4', 53: '5', | |
667 54: '6', 55: '7', 56: '8', 57: '9', 59: ';', 61: '=', 65: 'a', | |
668 66: 'b', 67: 'c', 68: 'd', 69: 'e', 70: 'f', 71: 'g', 72: 'h', | |
669 73: 'i', 74: 'j', 75: 'k', 76: 'l', 77: 'm', 78: 'n', 79: 'o', | |
670 80: 'p', 81: 'q', 82: 'r', 83: 's', 84: 't', 85: 'u', 86: 'v', | |
671 87: 'w', 88: 'x', 89: 'y', 90: 'z', 107: '+', 109: '-', 110: '.', | |
672 186: ';', 187: '=', 188: ',', 189: '-', 190: '.', 191: '/', 192: '`', | |
673 219: '[', 220: '\\',221: ']', 222: "'", 111: '/', 106: '*', 173: '-' | |
674 }; | |
675 var parts = []; | |
676 if (e.ctrlKey) { parts.push('ctrl'); } | |
677 if (e.altKey) { parts.push('alt'); } | |
678 if (e.shiftKey) { parts.push('shift'); } | |
679 parts.push(keys[e.which] || e.which); | |
680 parts = parts.sort().join('-').toLowerCase(); | |
681 | |
682 var kb = this.settings.core.keyboard, i, tmp; | |
683 for (i in kb) { | |
684 if (kb.hasOwnProperty(i)) { | |
685 tmp = i; | |
686 if (tmp !== '-' && tmp !== '+') { | |
687 tmp = tmp.replace('--', '-MINUS').replace('+-', '-MINUS').replace('++', '-PLUS').replace('-+', '-PLUS'); | |
688 tmp = tmp.split(/-|\+/).sort().join('-').replace('MINUS', '-').replace('PLUS', '+').toLowerCase(); | |
689 } | |
690 if (tmp === parts) { | |
691 return kb[i]; | |
692 } | |
693 } | |
694 } | |
695 return null; | |
696 }, | |
697 /** | |
698 * part of the destroying of an instance. Used internally. | |
699 * @private | |
700 * @name teardown() | |
701 */ | |
702 teardown : function () { | |
703 this.unbind(); | |
704 this.element | |
705 .removeClass('jstree') | |
706 .removeData('jstree') | |
707 .find("[class^='jstree']") | |
708 .addBack() | |
709 .attr("class", function () { return this.className.replace(/jstree[^ ]*|$/ig,''); }); | |
710 this.element = null; | |
711 }, | |
712 /** | |
713 * bind all events. Used internally. | |
714 * @private | |
715 * @name bind() | |
716 */ | |
717 bind : function () { | |
718 var word = '', | |
719 tout = null, | |
720 was_click = 0; | |
721 this.element | |
722 .on("dblclick.jstree", function (e) { | |
723 if(e.target.tagName && e.target.tagName.toLowerCase() === "input") { return true; } | |
724 if(document.selection && document.selection.empty) { | |
725 document.selection.empty(); | |
726 } | |
727 else { | |
728 if(window.getSelection) { | |
729 var sel = window.getSelection(); | |
730 try { | |
731 sel.removeAllRanges(); | |
732 sel.collapse(); | |
733 } catch (ignore) { } | |
734 } | |
735 } | |
736 }) | |
737 .on("mousedown.jstree", $.proxy(function (e) { | |
738 if(e.target === this.element[0]) { | |
739 e.preventDefault(); // prevent losing focus when clicking scroll arrows (FF, Chrome) | |
740 was_click = +(new Date()); // ie does not allow to prevent losing focus | |
741 } | |
742 }, this)) | |
743 .on("mousedown.jstree", ".jstree-ocl", function (e) { | |
744 e.preventDefault(); // prevent any node inside from losing focus when clicking the open/close icon | |
745 }) | |
746 .on("click.jstree", ".jstree-ocl", $.proxy(function (e) { | |
747 this.toggle_node(e.target); | |
748 }, this)) | |
749 .on("dblclick.jstree", ".jstree-anchor", $.proxy(function (e) { | |
750 if(e.target.tagName && e.target.tagName.toLowerCase() === "input") { return true; } | |
751 if(this.settings.core.dblclick_toggle) { | |
752 this.toggle_node(e.target); | |
753 } | |
754 }, this)) | |
755 .on("click.jstree", ".jstree-anchor", $.proxy(function (e) { | |
756 e.preventDefault(); | |
757 if(e.currentTarget !== document.activeElement) { $(e.currentTarget).focus(); } | |
758 this.activate_node(e.currentTarget, e); | |
759 }, this)) | |
760 .on('keydown.jstree', '.jstree-anchor', $.proxy(function (e) { | |
761 if(e.target.tagName && e.target.tagName.toLowerCase() === "input") { return true; } | |
762 if(this._data.core.rtl) { | |
763 if(e.which === 37) { e.which = 39; } | |
764 else if(e.which === 39) { e.which = 37; } | |
765 } | |
766 var f = this._kbevent_to_func(e); | |
767 if (f) { | |
768 var r = f.call(this, e); | |
769 if (r === false || r === true) { | |
770 return r; | |
771 } | |
772 } | |
773 }, this)) | |
774 .on("load_node.jstree", $.proxy(function (e, data) { | |
775 if(data.status) { | |
776 if(data.node.id === $.jstree.root && !this._data.core.loaded) { | |
777 this._data.core.loaded = true; | |
778 if(this._firstChild(this.get_container_ul()[0])) { | |
779 this.element.attr('aria-activedescendant',this._firstChild(this.get_container_ul()[0]).id); | |
780 } | |
781 /** | |
782 * triggered after the root node is loaded for the first time | |
783 * @event | |
784 * @name loaded.jstree | |
785 */ | |
786 this.trigger("loaded"); | |
787 } | |
788 if(!this._data.core.ready) { | |
789 setTimeout($.proxy(function() { | |
790 if(this.element && !this.get_container_ul().find('.jstree-loading').length) { | |
791 this._data.core.ready = true; | |
792 if(this._data.core.selected.length) { | |
793 if(this.settings.core.expand_selected_onload) { | |
794 var tmp = [], i, j; | |
795 for(i = 0, j = this._data.core.selected.length; i < j; i++) { | |
796 tmp = tmp.concat(this._model.data[this._data.core.selected[i]].parents); | |
797 } | |
798 tmp = $.vakata.array_unique(tmp); | |
799 for(i = 0, j = tmp.length; i < j; i++) { | |
800 this.open_node(tmp[i], false, 0); | |
801 } | |
802 } | |
803 this.trigger('changed', { 'action' : 'ready', 'selected' : this._data.core.selected }); | |
804 } | |
805 /** | |
806 * triggered after all nodes are finished loading | |
807 * @event | |
808 * @name ready.jstree | |
809 */ | |
810 this.trigger("ready"); | |
811 } | |
812 }, this), 0); | |
813 } | |
814 } | |
815 }, this)) | |
816 // quick searching when the tree is focused | |
817 .on('keypress.jstree', $.proxy(function (e) { | |
818 if(e.target.tagName && e.target.tagName.toLowerCase() === "input") { return true; } | |
819 if(tout) { clearTimeout(tout); } | |
820 tout = setTimeout(function () { | |
821 word = ''; | |
822 }, 500); | |
823 | |
824 var chr = String.fromCharCode(e.which).toLowerCase(), | |
825 col = this.element.find('.jstree-anchor').filter(':visible'), | |
826 ind = col.index(document.activeElement) || 0, | |
827 end = false; | |
828 word += chr; | |
829 | |
830 // match for whole word from current node down (including the current node) | |
831 if(word.length > 1) { | |
832 col.slice(ind).each($.proxy(function (i, v) { | |
833 if($(v).text().toLowerCase().indexOf(word) === 0) { | |
834 $(v).focus(); | |
835 end = true; | |
836 return false; | |
837 } | |
838 }, this)); | |
839 if(end) { return; } | |
840 | |
841 // match for whole word from the beginning of the tree | |
842 col.slice(0, ind).each($.proxy(function (i, v) { | |
843 if($(v).text().toLowerCase().indexOf(word) === 0) { | |
844 $(v).focus(); | |
845 end = true; | |
846 return false; | |
847 } | |
848 }, this)); | |
849 if(end) { return; } | |
850 } | |
851 // list nodes that start with that letter (only if word consists of a single char) | |
852 if(new RegExp('^' + chr.replace(/[-\/\\^$*+?.()|[\]{}]/g, '\\$&') + '+$').test(word)) { | |
853 // search for the next node starting with that letter | |
854 col.slice(ind + 1).each($.proxy(function (i, v) { | |
855 if($(v).text().toLowerCase().charAt(0) === chr) { | |
856 $(v).focus(); | |
857 end = true; | |
858 return false; | |
859 } | |
860 }, this)); | |
861 if(end) { return; } | |
862 | |
863 // search from the beginning | |
864 col.slice(0, ind + 1).each($.proxy(function (i, v) { | |
865 if($(v).text().toLowerCase().charAt(0) === chr) { | |
866 $(v).focus(); | |
867 end = true; | |
868 return false; | |
869 } | |
870 }, this)); | |
871 if(end) { return; } | |
872 } | |
873 }, this)) | |
874 // THEME RELATED | |
875 .on("init.jstree", $.proxy(function () { | |
876 var s = this.settings.core.themes; | |
877 this._data.core.themes.dots = s.dots; | |
878 this._data.core.themes.stripes = s.stripes; | |
879 this._data.core.themes.icons = s.icons; | |
880 this._data.core.themes.ellipsis = s.ellipsis; | |
881 this.set_theme(s.name || "default", s.url); | |
882 this.set_theme_variant(s.variant); | |
883 }, this)) | |
884 .on("loading.jstree", $.proxy(function () { | |
885 this[ this._data.core.themes.dots ? "show_dots" : "hide_dots" ](); | |
886 this[ this._data.core.themes.icons ? "show_icons" : "hide_icons" ](); | |
887 this[ this._data.core.themes.stripes ? "show_stripes" : "hide_stripes" ](); | |
888 this[ this._data.core.themes.ellipsis ? "show_ellipsis" : "hide_ellipsis" ](); | |
889 }, this)) | |
890 .on('blur.jstree', '.jstree-anchor', $.proxy(function (e) { | |
891 this._data.core.focused = null; | |
892 $(e.currentTarget).filter('.jstree-hovered').mouseleave(); | |
893 this.element.attr('tabindex', '0'); | |
894 }, this)) | |
895 .on('focus.jstree', '.jstree-anchor', $.proxy(function (e) { | |
896 var tmp = this.get_node(e.currentTarget); | |
897 if(tmp && tmp.id) { | |
898 this._data.core.focused = tmp.id; | |
899 } | |
900 this.element.find('.jstree-hovered').not(e.currentTarget).mouseleave(); | |
901 $(e.currentTarget).mouseenter(); | |
902 this.element.attr('tabindex', '-1'); | |
903 }, this)) | |
904 .on('focus.jstree', $.proxy(function () { | |
905 if(+(new Date()) - was_click > 500 && !this._data.core.focused && this.settings.core.restore_focus) { | |
906 was_click = 0; | |
907 var act = this.get_node(this.element.attr('aria-activedescendant'), true); | |
908 if(act) { | |
909 act.find('> .jstree-anchor').focus(); | |
910 } | |
911 } | |
912 }, this)) | |
913 .on('mouseenter.jstree', '.jstree-anchor', $.proxy(function (e) { | |
914 this.hover_node(e.currentTarget); | |
915 }, this)) | |
916 .on('mouseleave.jstree', '.jstree-anchor', $.proxy(function (e) { | |
917 this.dehover_node(e.currentTarget); | |
918 }, this)); | |
919 }, | |
920 /** | |
921 * part of the destroying of an instance. Used internally. | |
922 * @private | |
923 * @name unbind() | |
924 */ | |
925 unbind : function () { | |
926 this.element.off('.jstree'); | |
927 $(document).off('.jstree-' + this._id); | |
928 }, | |
929 /** | |
930 * trigger an event. Used internally. | |
931 * @private | |
932 * @name trigger(ev [, data]) | |
933 * @param {String} ev the name of the event to trigger | |
934 * @param {Object} data additional data to pass with the event | |
935 */ | |
936 trigger : function (ev, data) { | |
937 if(!data) { | |
938 data = {}; | |
939 } | |
940 data.instance = this; | |
941 this.element.triggerHandler(ev.replace('.jstree','') + '.jstree', data); | |
942 }, | |
943 /** | |
944 * returns the jQuery extended instance container | |
945 * @name get_container() | |
946 * @return {jQuery} | |
947 */ | |
948 get_container : function () { | |
949 return this.element; | |
950 }, | |
951 /** | |
952 * returns the jQuery extended main UL node inside the instance container. Used internally. | |
953 * @private | |
954 * @name get_container_ul() | |
955 * @return {jQuery} | |
956 */ | |
957 get_container_ul : function () { | |
958 return this.element.children(".jstree-children").first(); | |
959 }, | |
960 /** | |
961 * gets string replacements (localization). Used internally. | |
962 * @private | |
963 * @name get_string(key) | |
964 * @param {String} key | |
965 * @return {String} | |
966 */ | |
967 get_string : function (key) { | |
968 var a = this.settings.core.strings; | |
969 if($.isFunction(a)) { return a.call(this, key); } | |
970 if(a && a[key]) { return a[key]; } | |
971 return key; | |
972 }, | |
973 /** | |
974 * gets the first child of a DOM node. Used internally. | |
975 * @private | |
976 * @name _firstChild(dom) | |
977 * @param {DOMElement} dom | |
978 * @return {DOMElement} | |
979 */ | |
980 _firstChild : function (dom) { | |
981 dom = dom ? dom.firstChild : null; | |
982 while(dom !== null && dom.nodeType !== 1) { | |
983 dom = dom.nextSibling; | |
984 } | |
985 return dom; | |
986 }, | |
987 /** | |
988 * gets the next sibling of a DOM node. Used internally. | |
989 * @private | |
990 * @name _nextSibling(dom) | |
991 * @param {DOMElement} dom | |
992 * @return {DOMElement} | |
993 */ | |
994 _nextSibling : function (dom) { | |
995 dom = dom ? dom.nextSibling : null; | |
996 while(dom !== null && dom.nodeType !== 1) { | |
997 dom = dom.nextSibling; | |
998 } | |
999 return dom; | |
1000 }, | |
1001 /** | |
1002 * gets the previous sibling of a DOM node. Used internally. | |
1003 * @private | |
1004 * @name _previousSibling(dom) | |
1005 * @param {DOMElement} dom | |
1006 * @return {DOMElement} | |
1007 */ | |
1008 _previousSibling : function (dom) { | |
1009 dom = dom ? dom.previousSibling : null; | |
1010 while(dom !== null && dom.nodeType !== 1) { | |
1011 dom = dom.previousSibling; | |
1012 } | |
1013 return dom; | |
1014 }, | |
1015 /** | |
1016 * get the JSON representation of a node (or the actual jQuery extended DOM node) by using any input (child DOM element, ID string, selector, etc) | |
1017 * @name get_node(obj [, as_dom]) | |
1018 * @param {mixed} obj | |
1019 * @param {Boolean} as_dom | |
1020 * @return {Object|jQuery} | |
1021 */ | |
1022 get_node : function (obj, as_dom) { | |
1023 if(obj && obj.id) { | |
1024 obj = obj.id; | |
1025 } | |
1026 var dom; | |
1027 try { | |
1028 if(this._model.data[obj]) { | |
1029 obj = this._model.data[obj]; | |
1030 } | |
1031 else if(typeof obj === "string" && this._model.data[obj.replace(/^#/, '')]) { | |
1032 obj = this._model.data[obj.replace(/^#/, '')]; | |
1033 } | |
1034 else if(typeof obj === "string" && (dom = $('#' + obj.replace($.jstree.idregex,'\\$&'), this.element)).length && this._model.data[dom.closest('.jstree-node').attr('id')]) { | |
1035 obj = this._model.data[dom.closest('.jstree-node').attr('id')]; | |
1036 } | |
1037 else if((dom = $(obj, this.element)).length && this._model.data[dom.closest('.jstree-node').attr('id')]) { | |
1038 obj = this._model.data[dom.closest('.jstree-node').attr('id')]; | |
1039 } | |
1040 else if((dom = $(obj, this.element)).length && dom.hasClass('jstree')) { | |
1041 obj = this._model.data[$.jstree.root]; | |
1042 } | |
1043 else { | |
1044 return false; | |
1045 } | |
1046 | |
1047 if(as_dom) { | |
1048 obj = obj.id === $.jstree.root ? this.element : $('#' + obj.id.replace($.jstree.idregex,'\\$&'), this.element); | |
1049 } | |
1050 return obj; | |
1051 } catch (ex) { return false; } | |
1052 }, | |
1053 /** | |
1054 * get the path to a node, either consisting of node texts, or of node IDs, optionally glued together (otherwise an array) | |
1055 * @name get_path(obj [, glue, ids]) | |
1056 * @param {mixed} obj the node | |
1057 * @param {String} glue if you want the path as a string - pass the glue here (for example '/'), if a falsy value is supplied here, an array is returned | |
1058 * @param {Boolean} ids if set to true build the path using ID, otherwise node text is used | |
1059 * @return {mixed} | |
1060 */ | |
1061 get_path : function (obj, glue, ids) { | |
1062 obj = obj.parents ? obj : this.get_node(obj); | |
1063 if(!obj || obj.id === $.jstree.root || !obj.parents) { | |
1064 return false; | |
1065 } | |
1066 var i, j, p = []; | |
1067 p.push(ids ? obj.id : obj.text); | |
1068 for(i = 0, j = obj.parents.length; i < j; i++) { | |
1069 p.push(ids ? obj.parents[i] : this.get_text(obj.parents[i])); | |
1070 } | |
1071 p = p.reverse().slice(1); | |
1072 return glue ? p.join(glue) : p; | |
1073 }, | |
1074 /** | |
1075 * get the next visible node that is below the `obj` node. If `strict` is set to `true` only sibling nodes are returned. | |
1076 * @name get_next_dom(obj [, strict]) | |
1077 * @param {mixed} obj | |
1078 * @param {Boolean} strict | |
1079 * @return {jQuery} | |
1080 */ | |
1081 get_next_dom : function (obj, strict) { | |
1082 var tmp; | |
1083 obj = this.get_node(obj, true); | |
1084 if(obj[0] === this.element[0]) { | |
1085 tmp = this._firstChild(this.get_container_ul()[0]); | |
1086 while (tmp && tmp.offsetHeight === 0) { | |
1087 tmp = this._nextSibling(tmp); | |
1088 } | |
1089 return tmp ? $(tmp) : false; | |
1090 } | |
1091 if(!obj || !obj.length) { | |
1092 return false; | |
1093 } | |
1094 if(strict) { | |
1095 tmp = obj[0]; | |
1096 do { | |
1097 tmp = this._nextSibling(tmp); | |
1098 } while (tmp && tmp.offsetHeight === 0); | |
1099 return tmp ? $(tmp) : false; | |
1100 } | |
1101 if(obj.hasClass("jstree-open")) { | |
1102 tmp = this._firstChild(obj.children('.jstree-children')[0]); | |
1103 while (tmp && tmp.offsetHeight === 0) { | |
1104 tmp = this._nextSibling(tmp); | |
1105 } | |
1106 if(tmp !== null) { | |
1107 return $(tmp); | |
1108 } | |
1109 } | |
1110 tmp = obj[0]; | |
1111 do { | |
1112 tmp = this._nextSibling(tmp); | |
1113 } while (tmp && tmp.offsetHeight === 0); | |
1114 if(tmp !== null) { | |
1115 return $(tmp); | |
1116 } | |
1117 return obj.parentsUntil(".jstree",".jstree-node").nextAll(".jstree-node:visible").first(); | |
1118 }, | |
1119 /** | |
1120 * get the previous visible node that is above the `obj` node. If `strict` is set to `true` only sibling nodes are returned. | |
1121 * @name get_prev_dom(obj [, strict]) | |
1122 * @param {mixed} obj | |
1123 * @param {Boolean} strict | |
1124 * @return {jQuery} | |
1125 */ | |
1126 get_prev_dom : function (obj, strict) { | |
1127 var tmp; | |
1128 obj = this.get_node(obj, true); | |
1129 if(obj[0] === this.element[0]) { | |
1130 tmp = this.get_container_ul()[0].lastChild; | |
1131 while (tmp && tmp.offsetHeight === 0) { | |
1132 tmp = this._previousSibling(tmp); | |
1133 } | |
1134 return tmp ? $(tmp) : false; | |
1135 } | |
1136 if(!obj || !obj.length) { | |
1137 return false; | |
1138 } | |
1139 if(strict) { | |
1140 tmp = obj[0]; | |
1141 do { | |
1142 tmp = this._previousSibling(tmp); | |
1143 } while (tmp && tmp.offsetHeight === 0); | |
1144 return tmp ? $(tmp) : false; | |
1145 } | |
1146 tmp = obj[0]; | |
1147 do { | |
1148 tmp = this._previousSibling(tmp); | |
1149 } while (tmp && tmp.offsetHeight === 0); | |
1150 if(tmp !== null) { | |
1151 obj = $(tmp); | |
1152 while(obj.hasClass("jstree-open")) { | |
1153 obj = obj.children(".jstree-children").first().children(".jstree-node:visible:last"); | |
1154 } | |
1155 return obj; | |
1156 } | |
1157 tmp = obj[0].parentNode.parentNode; | |
1158 return tmp && tmp.className && tmp.className.indexOf('jstree-node') !== -1 ? $(tmp) : false; | |
1159 }, | |
1160 /** | |
1161 * get the parent ID of a node | |
1162 * @name get_parent(obj) | |
1163 * @param {mixed} obj | |
1164 * @return {String} | |
1165 */ | |
1166 get_parent : function (obj) { | |
1167 obj = this.get_node(obj); | |
1168 if(!obj || obj.id === $.jstree.root) { | |
1169 return false; | |
1170 } | |
1171 return obj.parent; | |
1172 }, | |
1173 /** | |
1174 * get a jQuery collection of all the children of a node (node must be rendered), returns false on error | |
1175 * @name get_children_dom(obj) | |
1176 * @param {mixed} obj | |
1177 * @return {jQuery} | |
1178 */ | |
1179 get_children_dom : function (obj) { | |
1180 obj = this.get_node(obj, true); | |
1181 if(obj[0] === this.element[0]) { | |
1182 return this.get_container_ul().children(".jstree-node"); | |
1183 } | |
1184 if(!obj || !obj.length) { | |
1185 return false; | |
1186 } | |
1187 return obj.children(".jstree-children").children(".jstree-node"); | |
1188 }, | |
1189 /** | |
1190 * checks if a node has children | |
1191 * @name is_parent(obj) | |
1192 * @param {mixed} obj | |
1193 * @return {Boolean} | |
1194 */ | |
1195 is_parent : function (obj) { | |
1196 obj = this.get_node(obj); | |
1197 return obj && (obj.state.loaded === false || obj.children.length > 0); | |
1198 }, | |
1199 /** | |
1200 * checks if a node is loaded (its children are available) | |
1201 * @name is_loaded(obj) | |
1202 * @param {mixed} obj | |
1203 * @return {Boolean} | |
1204 */ | |
1205 is_loaded : function (obj) { | |
1206 obj = this.get_node(obj); | |
1207 return obj && obj.state.loaded; | |
1208 }, | |
1209 /** | |
1210 * check if a node is currently loading (fetching children) | |
1211 * @name is_loading(obj) | |
1212 * @param {mixed} obj | |
1213 * @return {Boolean} | |
1214 */ | |
1215 is_loading : function (obj) { | |
1216 obj = this.get_node(obj); | |
1217 return obj && obj.state && obj.state.loading; | |
1218 }, | |
1219 /** | |
1220 * check if a node is opened | |
1221 * @name is_open(obj) | |
1222 * @param {mixed} obj | |
1223 * @return {Boolean} | |
1224 */ | |
1225 is_open : function (obj) { | |
1226 obj = this.get_node(obj); | |
1227 return obj && obj.state.opened; | |
1228 }, | |
1229 /** | |
1230 * check if a node is in a closed state | |
1231 * @name is_closed(obj) | |
1232 * @param {mixed} obj | |
1233 * @return {Boolean} | |
1234 */ | |
1235 is_closed : function (obj) { | |
1236 obj = this.get_node(obj); | |
1237 return obj && this.is_parent(obj) && !obj.state.opened; | |
1238 }, | |
1239 /** | |
1240 * check if a node has no children | |
1241 * @name is_leaf(obj) | |
1242 * @param {mixed} obj | |
1243 * @return {Boolean} | |
1244 */ | |
1245 is_leaf : function (obj) { | |
1246 return !this.is_parent(obj); | |
1247 }, | |
1248 /** | |
1249 * loads a node (fetches its children using the `core.data` setting). Multiple nodes can be passed to by using an array. | |
1250 * @name load_node(obj [, callback]) | |
1251 * @param {mixed} obj | |
1252 * @param {function} callback a function to be executed once loading is complete, the function is executed in the instance's scope and receives two arguments - the node and a boolean status | |
1253 * @return {Boolean} | |
1254 * @trigger load_node.jstree | |
1255 */ | |
1256 load_node : function (obj, callback) { | |
1257 var k, l, i, j, c; | |
1258 if($.isArray(obj)) { | |
1259 this._load_nodes(obj.slice(), callback); | |
1260 return true; | |
1261 } | |
1262 obj = this.get_node(obj); | |
1263 if(!obj) { | |
1264 if(callback) { callback.call(this, obj, false); } | |
1265 return false; | |
1266 } | |
1267 // if(obj.state.loading) { } // the node is already loading - just wait for it to load and invoke callback? but if called implicitly it should be loaded again? | |
1268 if(obj.state.loaded) { | |
1269 obj.state.loaded = false; | |
1270 for(i = 0, j = obj.parents.length; i < j; i++) { | |
1271 this._model.data[obj.parents[i]].children_d = $.vakata.array_filter(this._model.data[obj.parents[i]].children_d, function (v) { | |
1272 return $.inArray(v, obj.children_d) === -1; | |
1273 }); | |
1274 } | |
1275 for(k = 0, l = obj.children_d.length; k < l; k++) { | |
1276 if(this._model.data[obj.children_d[k]].state.selected) { | |
1277 c = true; | |
1278 } | |
1279 delete this._model.data[obj.children_d[k]]; | |
1280 } | |
1281 if (c) { | |
1282 this._data.core.selected = $.vakata.array_filter(this._data.core.selected, function (v) { | |
1283 return $.inArray(v, obj.children_d) === -1; | |
1284 }); | |
1285 } | |
1286 obj.children = []; | |
1287 obj.children_d = []; | |
1288 if(c) { | |
1289 this.trigger('changed', { 'action' : 'load_node', 'node' : obj, 'selected' : this._data.core.selected }); | |
1290 } | |
1291 } | |
1292 obj.state.failed = false; | |
1293 obj.state.loading = true; | |
1294 this.get_node(obj, true).addClass("jstree-loading").attr('aria-busy',true); | |
1295 this._load_node(obj, $.proxy(function (status) { | |
1296 obj = this._model.data[obj.id]; | |
1297 obj.state.loading = false; | |
1298 obj.state.loaded = status; | |
1299 obj.state.failed = !obj.state.loaded; | |
1300 var dom = this.get_node(obj, true), i = 0, j = 0, m = this._model.data, has_children = false; | |
1301 for(i = 0, j = obj.children.length; i < j; i++) { | |
1302 if(m[obj.children[i]] && !m[obj.children[i]].state.hidden) { | |
1303 has_children = true; | |
1304 break; | |
1305 } | |
1306 } | |
1307 if(obj.state.loaded && dom && dom.length) { | |
1308 dom.removeClass('jstree-closed jstree-open jstree-leaf'); | |
1309 if (!has_children) { | |
1310 dom.addClass('jstree-leaf'); | |
1311 } | |
1312 else { | |
1313 if (obj.id !== '#') { | |
1314 dom.addClass(obj.state.opened ? 'jstree-open' : 'jstree-closed'); | |
1315 } | |
1316 } | |
1317 } | |
1318 dom.removeClass("jstree-loading").attr('aria-busy',false); | |
1319 /** | |
1320 * triggered after a node is loaded | |
1321 * @event | |
1322 * @name load_node.jstree | |
1323 * @param {Object} node the node that was loading | |
1324 * @param {Boolean} status was the node loaded successfully | |
1325 */ | |
1326 this.trigger('load_node', { "node" : obj, "status" : status }); | |
1327 if(callback) { | |
1328 callback.call(this, obj, status); | |
1329 } | |
1330 }, this)); | |
1331 return true; | |
1332 }, | |
1333 /** | |
1334 * load an array of nodes (will also load unavailable nodes as soon as the appear in the structure). Used internally. | |
1335 * @private | |
1336 * @name _load_nodes(nodes [, callback]) | |
1337 * @param {array} nodes | |
1338 * @param {function} callback a function to be executed once loading is complete, the function is executed in the instance's scope and receives one argument - the array passed to _load_nodes | |
1339 */ | |
1340 _load_nodes : function (nodes, callback, is_callback, force_reload) { | |
1341 var r = true, | |
1342 c = function () { this._load_nodes(nodes, callback, true); }, | |
1343 m = this._model.data, i, j, tmp = []; | |
1344 for(i = 0, j = nodes.length; i < j; i++) { | |
1345 if(m[nodes[i]] && ( (!m[nodes[i]].state.loaded && !m[nodes[i]].state.failed) || (!is_callback && force_reload) )) { | |
1346 if(!this.is_loading(nodes[i])) { | |
1347 this.load_node(nodes[i], c); | |
1348 } | |
1349 r = false; | |
1350 } | |
1351 } | |
1352 if(r) { | |
1353 for(i = 0, j = nodes.length; i < j; i++) { | |
1354 if(m[nodes[i]] && m[nodes[i]].state.loaded) { | |
1355 tmp.push(nodes[i]); | |
1356 } | |
1357 } | |
1358 if(callback && !callback.done) { | |
1359 callback.call(this, tmp); | |
1360 callback.done = true; | |
1361 } | |
1362 } | |
1363 }, | |
1364 /** | |
1365 * loads all unloaded nodes | |
1366 * @name load_all([obj, callback]) | |
1367 * @param {mixed} obj the node to load recursively, omit to load all nodes in the tree | |
1368 * @param {function} callback a function to be executed once loading all the nodes is complete, | |
1369 * @trigger load_all.jstree | |
1370 */ | |
1371 load_all : function (obj, callback) { | |
1372 if(!obj) { obj = $.jstree.root; } | |
1373 obj = this.get_node(obj); | |
1374 if(!obj) { return false; } | |
1375 var to_load = [], | |
1376 m = this._model.data, | |
1377 c = m[obj.id].children_d, | |
1378 i, j; | |
1379 if(obj.state && !obj.state.loaded) { | |
1380 to_load.push(obj.id); | |
1381 } | |
1382 for(i = 0, j = c.length; i < j; i++) { | |
1383 if(m[c[i]] && m[c[i]].state && !m[c[i]].state.loaded) { | |
1384 to_load.push(c[i]); | |
1385 } | |
1386 } | |
1387 if(to_load.length) { | |
1388 this._load_nodes(to_load, function () { | |
1389 this.load_all(obj, callback); | |
1390 }); | |
1391 } | |
1392 else { | |
1393 /** | |
1394 * triggered after a load_all call completes | |
1395 * @event | |
1396 * @name load_all.jstree | |
1397 * @param {Object} node the recursively loaded node | |
1398 */ | |
1399 if(callback) { callback.call(this, obj); } | |
1400 this.trigger('load_all', { "node" : obj }); | |
1401 } | |
1402 }, | |
1403 /** | |
1404 * handles the actual loading of a node. Used only internally. | |
1405 * @private | |
1406 * @name _load_node(obj [, callback]) | |
1407 * @param {mixed} obj | |
1408 * @param {function} callback a function to be executed once loading is complete, the function is executed in the instance's scope and receives one argument - a boolean status | |
1409 * @return {Boolean} | |
1410 */ | |
1411 _load_node : function (obj, callback) { | |
1412 var s = this.settings.core.data, t; | |
1413 var notTextOrCommentNode = function notTextOrCommentNode () { | |
1414 return this.nodeType !== 3 && this.nodeType !== 8; | |
1415 }; | |
1416 // use original HTML | |
1417 if(!s) { | |
1418 if(obj.id === $.jstree.root) { | |
1419 return this._append_html_data(obj, this._data.core.original_container_html.clone(true), function (status) { | |
1420 callback.call(this, status); | |
1421 }); | |
1422 } | |
1423 else { | |
1424 return callback.call(this, false); | |
1425 } | |
1426 // return callback.call(this, obj.id === $.jstree.root ? this._append_html_data(obj, this._data.core.original_container_html.clone(true)) : false); | |
1427 } | |
1428 if($.isFunction(s)) { | |
1429 return s.call(this, obj, $.proxy(function (d) { | |
1430 if(d === false) { | |
1431 callback.call(this, false); | |
1432 } | |
1433 else { | |
1434 this[typeof d === 'string' ? '_append_html_data' : '_append_json_data'](obj, typeof d === 'string' ? $($.parseHTML(d)).filter(notTextOrCommentNode) : d, function (status) { | |
1435 callback.call(this, status); | |
1436 }); | |
1437 } | |
1438 // return d === false ? callback.call(this, false) : callback.call(this, this[typeof d === 'string' ? '_append_html_data' : '_append_json_data'](obj, typeof d === 'string' ? $(d) : d)); | |
1439 }, this)); | |
1440 } | |
1441 if(typeof s === 'object') { | |
1442 if(s.url) { | |
1443 s = $.extend(true, {}, s); | |
1444 if($.isFunction(s.url)) { | |
1445 s.url = s.url.call(this, obj); | |
1446 } | |
1447 if($.isFunction(s.data)) { | |
1448 s.data = s.data.call(this, obj); | |
1449 } | |
1450 return $.ajax(s) | |
1451 .done($.proxy(function (d,t,x) { | |
1452 var type = x.getResponseHeader('Content-Type'); | |
1453 if((type && type.indexOf('json') !== -1) || typeof d === "object") { | |
1454 return this._append_json_data(obj, d, function (status) { callback.call(this, status); }); | |
1455 //return callback.call(this, this._append_json_data(obj, d)); | |
1456 } | |
1457 if((type && type.indexOf('html') !== -1) || typeof d === "string") { | |
1458 return this._append_html_data(obj, $($.parseHTML(d)).filter(notTextOrCommentNode), function (status) { callback.call(this, status); }); | |
1459 // return callback.call(this, this._append_html_data(obj, $(d))); | |
1460 } | |
1461 this._data.core.last_error = { 'error' : 'ajax', 'plugin' : 'core', 'id' : 'core_04', 'reason' : 'Could not load node', 'data' : JSON.stringify({ 'id' : obj.id, 'xhr' : x }) }; | |
1462 this.settings.core.error.call(this, this._data.core.last_error); | |
1463 return callback.call(this, false); | |
1464 }, this)) | |
1465 .fail($.proxy(function (f) { | |
1466 this._data.core.last_error = { 'error' : 'ajax', 'plugin' : 'core', 'id' : 'core_04', 'reason' : 'Could not load node', 'data' : JSON.stringify({ 'id' : obj.id, 'xhr' : f }) }; | |
1467 callback.call(this, false); | |
1468 this.settings.core.error.call(this, this._data.core.last_error); | |
1469 }, this)); | |
1470 } | |
1471 if ($.isArray(s)) { | |
1472 t = $.extend(true, [], s); | |
1473 } else if ($.isPlainObject(s)) { | |
1474 t = $.extend(true, {}, s); | |
1475 } else { | |
1476 t = s; | |
1477 } | |
1478 if(obj.id === $.jstree.root) { | |
1479 return this._append_json_data(obj, t, function (status) { | |
1480 callback.call(this, status); | |
1481 }); | |
1482 } | |
1483 else { | |
1484 this._data.core.last_error = { 'error' : 'nodata', 'plugin' : 'core', 'id' : 'core_05', 'reason' : 'Could not load node', 'data' : JSON.stringify({ 'id' : obj.id }) }; | |
1485 this.settings.core.error.call(this, this._data.core.last_error); | |
1486 return callback.call(this, false); | |
1487 } | |
1488 //return callback.call(this, (obj.id === $.jstree.root ? this._append_json_data(obj, t) : false) ); | |
1489 } | |
1490 if(typeof s === 'string') { | |
1491 if(obj.id === $.jstree.root) { | |
1492 return this._append_html_data(obj, $($.parseHTML(s)).filter(notTextOrCommentNode), function (status) { | |
1493 callback.call(this, status); | |
1494 }); | |
1495 } | |
1496 else { | |
1497 this._data.core.last_error = { 'error' : 'nodata', 'plugin' : 'core', 'id' : 'core_06', 'reason' : 'Could not load node', 'data' : JSON.stringify({ 'id' : obj.id }) }; | |
1498 this.settings.core.error.call(this, this._data.core.last_error); | |
1499 return callback.call(this, false); | |
1500 } | |
1501 //return callback.call(this, (obj.id === $.jstree.root ? this._append_html_data(obj, $(s)) : false) ); | |
1502 } | |
1503 return callback.call(this, false); | |
1504 }, | |
1505 /** | |
1506 * adds a node to the list of nodes to redraw. Used only internally. | |
1507 * @private | |
1508 * @name _node_changed(obj [, callback]) | |
1509 * @param {mixed} obj | |
1510 */ | |
1511 _node_changed : function (obj) { | |
1512 obj = this.get_node(obj); | |
1513 if (obj && $.inArray(obj.id, this._model.changed) === -1) { | |
1514 this._model.changed.push(obj.id); | |
1515 } | |
1516 }, | |
1517 /** | |
1518 * appends HTML content to the tree. Used internally. | |
1519 * @private | |
1520 * @name _append_html_data(obj, data) | |
1521 * @param {mixed} obj the node to append to | |
1522 * @param {String} data the HTML string to parse and append | |
1523 * @trigger model.jstree, changed.jstree | |
1524 */ | |
1525 _append_html_data : function (dom, data, cb) { | |
1526 dom = this.get_node(dom); | |
1527 dom.children = []; | |
1528 dom.children_d = []; | |
1529 var dat = data.is('ul') ? data.children() : data, | |
1530 par = dom.id, | |
1531 chd = [], | |
1532 dpc = [], | |
1533 m = this._model.data, | |
1534 p = m[par], | |
1535 s = this._data.core.selected.length, | |
1536 tmp, i, j; | |
1537 dat.each($.proxy(function (i, v) { | |
1538 tmp = this._parse_model_from_html($(v), par, p.parents.concat()); | |
1539 if(tmp) { | |
1540 chd.push(tmp); | |
1541 dpc.push(tmp); | |
1542 if(m[tmp].children_d.length) { | |
1543 dpc = dpc.concat(m[tmp].children_d); | |
1544 } | |
1545 } | |
1546 }, this)); | |
1547 p.children = chd; | |
1548 p.children_d = dpc; | |
1549 for(i = 0, j = p.parents.length; i < j; i++) { | |
1550 m[p.parents[i]].children_d = m[p.parents[i]].children_d.concat(dpc); | |
1551 } | |
1552 /** | |
1553 * triggered when new data is inserted to the tree model | |
1554 * @event | |
1555 * @name model.jstree | |
1556 * @param {Array} nodes an array of node IDs | |
1557 * @param {String} parent the parent ID of the nodes | |
1558 */ | |
1559 this.trigger('model', { "nodes" : dpc, 'parent' : par }); | |
1560 if(par !== $.jstree.root) { | |
1561 this._node_changed(par); | |
1562 this.redraw(); | |
1563 } | |
1564 else { | |
1565 this.get_container_ul().children('.jstree-initial-node').remove(); | |
1566 this.redraw(true); | |
1567 } | |
1568 if(this._data.core.selected.length !== s) { | |
1569 this.trigger('changed', { 'action' : 'model', 'selected' : this._data.core.selected }); | |
1570 } | |
1571 cb.call(this, true); | |
1572 }, | |
1573 /** | |
1574 * appends JSON content to the tree. Used internally. | |
1575 * @private | |
1576 * @name _append_json_data(obj, data) | |
1577 * @param {mixed} obj the node to append to | |
1578 * @param {String} data the JSON object to parse and append | |
1579 * @param {Boolean} force_processing internal param - do not set | |
1580 * @trigger model.jstree, changed.jstree | |
1581 */ | |
1582 _append_json_data : function (dom, data, cb, force_processing) { | |
1583 if(this.element === null) { return; } | |
1584 dom = this.get_node(dom); | |
1585 dom.children = []; | |
1586 dom.children_d = []; | |
1587 // *%$@!!! | |
1588 if(data.d) { | |
1589 data = data.d; | |
1590 if(typeof data === "string") { | |
1591 data = JSON.parse(data); | |
1592 } | |
1593 } | |
1594 if(!$.isArray(data)) { data = [data]; } | |
1595 var w = null, | |
1596 args = { | |
1597 'df' : this._model.default_state, | |
1598 'dat' : data, | |
1599 'par' : dom.id, | |
1600 'm' : this._model.data, | |
1601 't_id' : this._id, | |
1602 't_cnt' : this._cnt, | |
1603 'sel' : this._data.core.selected | |
1604 }, | |
1605 func = function (data, undefined) { | |
1606 if(data.data) { data = data.data; } | |
1607 var dat = data.dat, | |
1608 par = data.par, | |
1609 chd = [], | |
1610 dpc = [], | |
1611 add = [], | |
1612 df = data.df, | |
1613 t_id = data.t_id, | |
1614 t_cnt = data.t_cnt, | |
1615 m = data.m, | |
1616 p = m[par], | |
1617 sel = data.sel, | |
1618 tmp, i, j, rslt, | |
1619 parse_flat = function (d, p, ps) { | |
1620 if(!ps) { ps = []; } | |
1621 else { ps = ps.concat(); } | |
1622 if(p) { ps.unshift(p); } | |
1623 var tid = d.id.toString(), | |
1624 i, j, c, e, | |
1625 tmp = { | |
1626 id : tid, | |
1627 text : d.text || '', | |
1628 icon : d.icon !== undefined ? d.icon : true, | |
1629 parent : p, | |
1630 parents : ps, | |
1631 children : d.children || [], | |
1632 children_d : d.children_d || [], | |
1633 data : d.data, | |
1634 state : { }, | |
1635 li_attr : { id : false }, | |
1636 a_attr : { href : '#' }, | |
1637 original : false | |
1638 }; | |
1639 for(i in df) { | |
1640 if(df.hasOwnProperty(i)) { | |
1641 tmp.state[i] = df[i]; | |
1642 } | |
1643 } | |
1644 if(d && d.data && d.data.jstree && d.data.jstree.icon) { | |
1645 tmp.icon = d.data.jstree.icon; | |
1646 } | |
1647 if(tmp.icon === undefined || tmp.icon === null || tmp.icon === "") { | |
1648 tmp.icon = true; | |
1649 } | |
1650 if(d && d.data) { | |
1651 tmp.data = d.data; | |
1652 if(d.data.jstree) { | |
1653 for(i in d.data.jstree) { | |
1654 if(d.data.jstree.hasOwnProperty(i)) { | |
1655 tmp.state[i] = d.data.jstree[i]; | |
1656 } | |
1657 } | |
1658 } | |
1659 } | |
1660 if(d && typeof d.state === 'object') { | |
1661 for (i in d.state) { | |
1662 if(d.state.hasOwnProperty(i)) { | |
1663 tmp.state[i] = d.state[i]; | |
1664 } | |
1665 } | |
1666 } | |
1667 if(d && typeof d.li_attr === 'object') { | |
1668 for (i in d.li_attr) { | |
1669 if(d.li_attr.hasOwnProperty(i)) { | |
1670 tmp.li_attr[i] = d.li_attr[i]; | |
1671 } | |
1672 } | |
1673 } | |
1674 if(!tmp.li_attr.id) { | |
1675 tmp.li_attr.id = tid; | |
1676 } | |
1677 if(d && typeof d.a_attr === 'object') { | |
1678 for (i in d.a_attr) { | |
1679 if(d.a_attr.hasOwnProperty(i)) { | |
1680 tmp.a_attr[i] = d.a_attr[i]; | |
1681 } | |
1682 } | |
1683 } | |
1684 if(d && d.children && d.children === true) { | |
1685 tmp.state.loaded = false; | |
1686 tmp.children = []; | |
1687 tmp.children_d = []; | |
1688 } | |
1689 m[tmp.id] = tmp; | |
1690 for(i = 0, j = tmp.children.length; i < j; i++) { | |
1691 c = parse_flat(m[tmp.children[i]], tmp.id, ps); | |
1692 e = m[c]; | |
1693 tmp.children_d.push(c); | |
1694 if(e.children_d.length) { | |
1695 tmp.children_d = tmp.children_d.concat(e.children_d); | |
1696 } | |
1697 } | |
1698 delete d.data; | |
1699 delete d.children; | |
1700 m[tmp.id].original = d; | |
1701 if(tmp.state.selected) { | |
1702 add.push(tmp.id); | |
1703 } | |
1704 return tmp.id; | |
1705 }, | |
1706 parse_nest = function (d, p, ps) { | |
1707 if(!ps) { ps = []; } | |
1708 else { ps = ps.concat(); } | |
1709 if(p) { ps.unshift(p); } | |
1710 var tid = false, i, j, c, e, tmp; | |
1711 do { | |
1712 tid = 'j' + t_id + '_' + (++t_cnt); | |
1713 } while(m[tid]); | |
1714 | |
1715 tmp = { | |
1716 id : false, | |
1717 text : typeof d === 'string' ? d : '', | |
1718 icon : typeof d === 'object' && d.icon !== undefined ? d.icon : true, | |
1719 parent : p, | |
1720 parents : ps, | |
1721 children : [], | |
1722 children_d : [], | |
1723 data : null, | |
1724 state : { }, | |
1725 li_attr : { id : false }, | |
1726 a_attr : { href : '#' }, | |
1727 original : false | |
1728 }; | |
1729 for(i in df) { | |
1730 if(df.hasOwnProperty(i)) { | |
1731 tmp.state[i] = df[i]; | |
1732 } | |
1733 } | |
1734 if(d && d.id) { tmp.id = d.id.toString(); } | |
1735 if(d && d.text) { tmp.text = d.text; } | |
1736 if(d && d.data && d.data.jstree && d.data.jstree.icon) { | |
1737 tmp.icon = d.data.jstree.icon; | |
1738 } | |
1739 if(tmp.icon === undefined || tmp.icon === null || tmp.icon === "") { | |
1740 tmp.icon = true; | |
1741 } | |
1742 if(d && d.data) { | |
1743 tmp.data = d.data; | |
1744 if(d.data.jstree) { | |
1745 for(i in d.data.jstree) { | |
1746 if(d.data.jstree.hasOwnProperty(i)) { | |
1747 tmp.state[i] = d.data.jstree[i]; | |
1748 } | |
1749 } | |
1750 } | |
1751 } | |
1752 if(d && typeof d.state === 'object') { | |
1753 for (i in d.state) { | |
1754 if(d.state.hasOwnProperty(i)) { | |
1755 tmp.state[i] = d.state[i]; | |
1756 } | |
1757 } | |
1758 } | |
1759 if(d && typeof d.li_attr === 'object') { | |
1760 for (i in d.li_attr) { | |
1761 if(d.li_attr.hasOwnProperty(i)) { | |
1762 tmp.li_attr[i] = d.li_attr[i]; | |
1763 } | |
1764 } | |
1765 } | |
1766 if(tmp.li_attr.id && !tmp.id) { | |
1767 tmp.id = tmp.li_attr.id.toString(); | |
1768 } | |
1769 if(!tmp.id) { | |
1770 tmp.id = tid; | |
1771 } | |
1772 if(!tmp.li_attr.id) { | |
1773 tmp.li_attr.id = tmp.id; | |
1774 } | |
1775 if(d && typeof d.a_attr === 'object') { | |
1776 for (i in d.a_attr) { | |
1777 if(d.a_attr.hasOwnProperty(i)) { | |
1778 tmp.a_attr[i] = d.a_attr[i]; | |
1779 } | |
1780 } | |
1781 } | |
1782 if(d && d.children && d.children.length) { | |
1783 for(i = 0, j = d.children.length; i < j; i++) { | |
1784 c = parse_nest(d.children[i], tmp.id, ps); | |
1785 e = m[c]; | |
1786 tmp.children.push(c); | |
1787 if(e.children_d.length) { | |
1788 tmp.children_d = tmp.children_d.concat(e.children_d); | |
1789 } | |
1790 } | |
1791 tmp.children_d = tmp.children_d.concat(tmp.children); | |
1792 } | |
1793 if(d && d.children && d.children === true) { | |
1794 tmp.state.loaded = false; | |
1795 tmp.children = []; | |
1796 tmp.children_d = []; | |
1797 } | |
1798 delete d.data; | |
1799 delete d.children; | |
1800 tmp.original = d; | |
1801 m[tmp.id] = tmp; | |
1802 if(tmp.state.selected) { | |
1803 add.push(tmp.id); | |
1804 } | |
1805 return tmp.id; | |
1806 }; | |
1807 | |
1808 if(dat.length && dat[0].id !== undefined && dat[0].parent !== undefined) { | |
1809 // Flat JSON support (for easy import from DB): | |
1810 // 1) convert to object (foreach) | |
1811 for(i = 0, j = dat.length; i < j; i++) { | |
1812 if(!dat[i].children) { | |
1813 dat[i].children = []; | |
1814 } | |
1815 if(!dat[i].state) { | |
1816 dat[i].state = {}; | |
1817 } | |
1818 m[dat[i].id.toString()] = dat[i]; | |
1819 } | |
1820 // 2) populate children (foreach) | |
1821 for(i = 0, j = dat.length; i < j; i++) { | |
1822 if (!m[dat[i].parent.toString()]) { | |
1823 this._data.core.last_error = { 'error' : 'parse', 'plugin' : 'core', 'id' : 'core_07', 'reason' : 'Node with invalid parent', 'data' : JSON.stringify({ 'id' : dat[i].id.toString(), 'parent' : dat[i].parent.toString() }) }; | |
1824 this.settings.core.error.call(this, this._data.core.last_error); | |
1825 continue; | |
1826 } | |
1827 | |
1828 m[dat[i].parent.toString()].children.push(dat[i].id.toString()); | |
1829 // populate parent.children_d | |
1830 p.children_d.push(dat[i].id.toString()); | |
1831 } | |
1832 // 3) normalize && populate parents and children_d with recursion | |
1833 for(i = 0, j = p.children.length; i < j; i++) { | |
1834 tmp = parse_flat(m[p.children[i]], par, p.parents.concat()); | |
1835 dpc.push(tmp); | |
1836 if(m[tmp].children_d.length) { | |
1837 dpc = dpc.concat(m[tmp].children_d); | |
1838 } | |
1839 } | |
1840 for(i = 0, j = p.parents.length; i < j; i++) { | |
1841 m[p.parents[i]].children_d = m[p.parents[i]].children_d.concat(dpc); | |
1842 } | |
1843 // ?) three_state selection - p.state.selected && t - (if three_state foreach(dat => ch) -> foreach(parents) if(parent.selected) child.selected = true; | |
1844 rslt = { | |
1845 'cnt' : t_cnt, | |
1846 'mod' : m, | |
1847 'sel' : sel, | |
1848 'par' : par, | |
1849 'dpc' : dpc, | |
1850 'add' : add | |
1851 }; | |
1852 } | |
1853 else { | |
1854 for(i = 0, j = dat.length; i < j; i++) { | |
1855 tmp = parse_nest(dat[i], par, p.parents.concat()); | |
1856 if(tmp) { | |
1857 chd.push(tmp); | |
1858 dpc.push(tmp); | |
1859 if(m[tmp].children_d.length) { | |
1860 dpc = dpc.concat(m[tmp].children_d); | |
1861 } | |
1862 } | |
1863 } | |
1864 p.children = chd; | |
1865 p.children_d = dpc; | |
1866 for(i = 0, j = p.parents.length; i < j; i++) { | |
1867 m[p.parents[i]].children_d = m[p.parents[i]].children_d.concat(dpc); | |
1868 } | |
1869 rslt = { | |
1870 'cnt' : t_cnt, | |
1871 'mod' : m, | |
1872 'sel' : sel, | |
1873 'par' : par, | |
1874 'dpc' : dpc, | |
1875 'add' : add | |
1876 }; | |
1877 } | |
1878 if(typeof window === 'undefined' || typeof window.document === 'undefined') { | |
1879 postMessage(rslt); | |
1880 } | |
1881 else { | |
1882 return rslt; | |
1883 } | |
1884 }, | |
1885 rslt = function (rslt, worker) { | |
1886 if(this.element === null) { return; } | |
1887 this._cnt = rslt.cnt; | |
1888 var i, m = this._model.data; | |
1889 for (i in m) { | |
1890 if (m.hasOwnProperty(i) && m[i].state && m[i].state.loading && rslt.mod[i]) { | |
1891 rslt.mod[i].state.loading = true; | |
1892 } | |
1893 } | |
1894 this._model.data = rslt.mod; // breaks the reference in load_node - careful | |
1895 | |
1896 if(worker) { | |
1897 var j, a = rslt.add, r = rslt.sel, s = this._data.core.selected.slice(); | |
1898 m = this._model.data; | |
1899 // if selection was changed while calculating in worker | |
1900 if(r.length !== s.length || $.vakata.array_unique(r.concat(s)).length !== r.length) { | |
1901 // deselect nodes that are no longer selected | |
1902 for(i = 0, j = r.length; i < j; i++) { | |
1903 if($.inArray(r[i], a) === -1 && $.inArray(r[i], s) === -1) { | |
1904 m[r[i]].state.selected = false; | |
1905 } | |
1906 } | |
1907 // select nodes that were selected in the mean time | |
1908 for(i = 0, j = s.length; i < j; i++) { | |
1909 if($.inArray(s[i], r) === -1) { | |
1910 m[s[i]].state.selected = true; | |
1911 } | |
1912 } | |
1913 } | |
1914 } | |
1915 if(rslt.add.length) { | |
1916 this._data.core.selected = this._data.core.selected.concat(rslt.add); | |
1917 } | |
1918 | |
1919 this.trigger('model', { "nodes" : rslt.dpc, 'parent' : rslt.par }); | |
1920 | |
1921 if(rslt.par !== $.jstree.root) { | |
1922 this._node_changed(rslt.par); | |
1923 this.redraw(); | |
1924 } | |
1925 else { | |
1926 // this.get_container_ul().children('.jstree-initial-node').remove(); | |
1927 this.redraw(true); | |
1928 } | |
1929 if(rslt.add.length) { | |
1930 this.trigger('changed', { 'action' : 'model', 'selected' : this._data.core.selected }); | |
1931 } | |
1932 cb.call(this, true); | |
1933 }; | |
1934 if(this.settings.core.worker && window.Blob && window.URL && window.Worker) { | |
1935 try { | |
1936 if(this._wrk === null) { | |
1937 this._wrk = window.URL.createObjectURL( | |
1938 new window.Blob( | |
1939 ['self.onmessage = ' + func.toString()], | |
1940 {type:"text/javascript"} | |
1941 ) | |
1942 ); | |
1943 } | |
1944 if(!this._data.core.working || force_processing) { | |
1945 this._data.core.working = true; | |
1946 w = new window.Worker(this._wrk); | |
1947 w.onmessage = $.proxy(function (e) { | |
1948 rslt.call(this, e.data, true); | |
1949 try { w.terminate(); w = null; } catch(ignore) { } | |
1950 if(this._data.core.worker_queue.length) { | |
1951 this._append_json_data.apply(this, this._data.core.worker_queue.shift()); | |
1952 } | |
1953 else { | |
1954 this._data.core.working = false; | |
1955 } | |
1956 }, this); | |
1957 if(!args.par) { | |
1958 if(this._data.core.worker_queue.length) { | |
1959 this._append_json_data.apply(this, this._data.core.worker_queue.shift()); | |
1960 } | |
1961 else { | |
1962 this._data.core.working = false; | |
1963 } | |
1964 } | |
1965 else { | |
1966 w.postMessage(args); | |
1967 } | |
1968 } | |
1969 else { | |
1970 this._data.core.worker_queue.push([dom, data, cb, true]); | |
1971 } | |
1972 } | |
1973 catch(e) { | |
1974 rslt.call(this, func(args), false); | |
1975 if(this._data.core.worker_queue.length) { | |
1976 this._append_json_data.apply(this, this._data.core.worker_queue.shift()); | |
1977 } | |
1978 else { | |
1979 this._data.core.working = false; | |
1980 } | |
1981 } | |
1982 } | |
1983 else { | |
1984 rslt.call(this, func(args), false); | |
1985 } | |
1986 }, | |
1987 /** | |
1988 * parses a node from a jQuery object and appends them to the in memory tree model. Used internally. | |
1989 * @private | |
1990 * @name _parse_model_from_html(d [, p, ps]) | |
1991 * @param {jQuery} d the jQuery object to parse | |
1992 * @param {String} p the parent ID | |
1993 * @param {Array} ps list of all parents | |
1994 * @return {String} the ID of the object added to the model | |
1995 */ | |
1996 _parse_model_from_html : function (d, p, ps) { | |
1997 if(!ps) { ps = []; } | |
1998 else { ps = [].concat(ps); } | |
1999 if(p) { ps.unshift(p); } | |
2000 var c, e, m = this._model.data, | |
2001 data = { | |
2002 id : false, | |
2003 text : false, | |
2004 icon : true, | |
2005 parent : p, | |
2006 parents : ps, | |
2007 children : [], | |
2008 children_d : [], | |
2009 data : null, | |
2010 state : { }, | |
2011 li_attr : { id : false }, | |
2012 a_attr : { href : '#' }, | |
2013 original : false | |
2014 }, i, tmp, tid; | |
2015 for(i in this._model.default_state) { | |
2016 if(this._model.default_state.hasOwnProperty(i)) { | |
2017 data.state[i] = this._model.default_state[i]; | |
2018 } | |
2019 } | |
2020 tmp = $.vakata.attributes(d, true); | |
2021 $.each(tmp, function (i, v) { | |
2022 v = $.trim(v); | |
2023 if(!v.length) { return true; } | |
2024 data.li_attr[i] = v; | |
2025 if(i === 'id') { | |
2026 data.id = v.toString(); | |
2027 } | |
2028 }); | |
2029 tmp = d.children('a').first(); | |
2030 if(tmp.length) { | |
2031 tmp = $.vakata.attributes(tmp, true); | |
2032 $.each(tmp, function (i, v) { | |
2033 v = $.trim(v); | |
2034 if(v.length) { | |
2035 data.a_attr[i] = v; | |
2036 } | |
2037 }); | |
2038 } | |
2039 tmp = d.children("a").first().length ? d.children("a").first().clone() : d.clone(); | |
2040 tmp.children("ins, i, ul").remove(); | |
2041 tmp = tmp.html(); | |
2042 tmp = $('<div />').html(tmp); | |
2043 data.text = this.settings.core.force_text ? tmp.text() : tmp.html(); | |
2044 tmp = d.data(); | |
2045 data.data = tmp ? $.extend(true, {}, tmp) : null; | |
2046 data.state.opened = d.hasClass('jstree-open'); | |
2047 data.state.selected = d.children('a').hasClass('jstree-clicked'); | |
2048 data.state.disabled = d.children('a').hasClass('jstree-disabled'); | |
2049 if(data.data && data.data.jstree) { | |
2050 for(i in data.data.jstree) { | |
2051 if(data.data.jstree.hasOwnProperty(i)) { | |
2052 data.state[i] = data.data.jstree[i]; | |
2053 } | |
2054 } | |
2055 } | |
2056 tmp = d.children("a").children(".jstree-themeicon"); | |
2057 if(tmp.length) { | |
2058 data.icon = tmp.hasClass('jstree-themeicon-hidden') ? false : tmp.attr('rel'); | |
2059 } | |
2060 if(data.state.icon !== undefined) { | |
2061 data.icon = data.state.icon; | |
2062 } | |
2063 if(data.icon === undefined || data.icon === null || data.icon === "") { | |
2064 data.icon = true; | |
2065 } | |
2066 tmp = d.children("ul").children("li"); | |
2067 do { | |
2068 tid = 'j' + this._id + '_' + (++this._cnt); | |
2069 } while(m[tid]); | |
2070 data.id = data.li_attr.id ? data.li_attr.id.toString() : tid; | |
2071 if(tmp.length) { | |
2072 tmp.each($.proxy(function (i, v) { | |
2073 c = this._parse_model_from_html($(v), data.id, ps); | |
2074 e = this._model.data[c]; | |
2075 data.children.push(c); | |
2076 if(e.children_d.length) { | |
2077 data.children_d = data.children_d.concat(e.children_d); | |
2078 } | |
2079 }, this)); | |
2080 data.children_d = data.children_d.concat(data.children); | |
2081 } | |
2082 else { | |
2083 if(d.hasClass('jstree-closed')) { | |
2084 data.state.loaded = false; | |
2085 } | |
2086 } | |
2087 if(data.li_attr['class']) { | |
2088 data.li_attr['class'] = data.li_attr['class'].replace('jstree-closed','').replace('jstree-open',''); | |
2089 } | |
2090 if(data.a_attr['class']) { | |
2091 data.a_attr['class'] = data.a_attr['class'].replace('jstree-clicked','').replace('jstree-disabled',''); | |
2092 } | |
2093 m[data.id] = data; | |
2094 if(data.state.selected) { | |
2095 this._data.core.selected.push(data.id); | |
2096 } | |
2097 return data.id; | |
2098 }, | |
2099 /** | |
2100 * parses a node from a JSON object (used when dealing with flat data, which has no nesting of children, but has id and parent properties) and appends it to the in memory tree model. Used internally. | |
2101 * @private | |
2102 * @name _parse_model_from_flat_json(d [, p, ps]) | |
2103 * @param {Object} d the JSON object to parse | |
2104 * @param {String} p the parent ID | |
2105 * @param {Array} ps list of all parents | |
2106 * @return {String} the ID of the object added to the model | |
2107 */ | |
2108 _parse_model_from_flat_json : function (d, p, ps) { | |
2109 if(!ps) { ps = []; } | |
2110 else { ps = ps.concat(); } | |
2111 if(p) { ps.unshift(p); } | |
2112 var tid = d.id.toString(), | |
2113 m = this._model.data, | |
2114 df = this._model.default_state, | |
2115 i, j, c, e, | |
2116 tmp = { | |
2117 id : tid, | |
2118 text : d.text || '', | |
2119 icon : d.icon !== undefined ? d.icon : true, | |
2120 parent : p, | |
2121 parents : ps, | |
2122 children : d.children || [], | |
2123 children_d : d.children_d || [], | |
2124 data : d.data, | |
2125 state : { }, | |
2126 li_attr : { id : false }, | |
2127 a_attr : { href : '#' }, | |
2128 original : false | |
2129 }; | |
2130 for(i in df) { | |
2131 if(df.hasOwnProperty(i)) { | |
2132 tmp.state[i] = df[i]; | |
2133 } | |
2134 } | |
2135 if(d && d.data && d.data.jstree && d.data.jstree.icon) { | |
2136 tmp.icon = d.data.jstree.icon; | |
2137 } | |
2138 if(tmp.icon === undefined || tmp.icon === null || tmp.icon === "") { | |
2139 tmp.icon = true; | |
2140 } | |
2141 if(d && d.data) { | |
2142 tmp.data = d.data; | |
2143 if(d.data.jstree) { | |
2144 for(i in d.data.jstree) { | |
2145 if(d.data.jstree.hasOwnProperty(i)) { | |
2146 tmp.state[i] = d.data.jstree[i]; | |
2147 } | |
2148 } | |
2149 } | |
2150 } | |
2151 if(d && typeof d.state === 'object') { | |
2152 for (i in d.state) { | |
2153 if(d.state.hasOwnProperty(i)) { | |
2154 tmp.state[i] = d.state[i]; | |
2155 } | |
2156 } | |
2157 } | |
2158 if(d && typeof d.li_attr === 'object') { | |
2159 for (i in d.li_attr) { | |
2160 if(d.li_attr.hasOwnProperty(i)) { | |
2161 tmp.li_attr[i] = d.li_attr[i]; | |
2162 } | |
2163 } | |
2164 } | |
2165 if(!tmp.li_attr.id) { | |
2166 tmp.li_attr.id = tid; | |
2167 } | |
2168 if(d && typeof d.a_attr === 'object') { | |
2169 for (i in d.a_attr) { | |
2170 if(d.a_attr.hasOwnProperty(i)) { | |
2171 tmp.a_attr[i] = d.a_attr[i]; | |
2172 } | |
2173 } | |
2174 } | |
2175 if(d && d.children && d.children === true) { | |
2176 tmp.state.loaded = false; | |
2177 tmp.children = []; | |
2178 tmp.children_d = []; | |
2179 } | |
2180 m[tmp.id] = tmp; | |
2181 for(i = 0, j = tmp.children.length; i < j; i++) { | |
2182 c = this._parse_model_from_flat_json(m[tmp.children[i]], tmp.id, ps); | |
2183 e = m[c]; | |
2184 tmp.children_d.push(c); | |
2185 if(e.children_d.length) { | |
2186 tmp.children_d = tmp.children_d.concat(e.children_d); | |
2187 } | |
2188 } | |
2189 delete d.data; | |
2190 delete d.children; | |
2191 m[tmp.id].original = d; | |
2192 if(tmp.state.selected) { | |
2193 this._data.core.selected.push(tmp.id); | |
2194 } | |
2195 return tmp.id; | |
2196 }, | |
2197 /** | |
2198 * parses a node from a JSON object and appends it to the in memory tree model. Used internally. | |
2199 * @private | |
2200 * @name _parse_model_from_json(d [, p, ps]) | |
2201 * @param {Object} d the JSON object to parse | |
2202 * @param {String} p the parent ID | |
2203 * @param {Array} ps list of all parents | |
2204 * @return {String} the ID of the object added to the model | |
2205 */ | |
2206 _parse_model_from_json : function (d, p, ps) { | |
2207 if(!ps) { ps = []; } | |
2208 else { ps = ps.concat(); } | |
2209 if(p) { ps.unshift(p); } | |
2210 var tid = false, i, j, c, e, m = this._model.data, df = this._model.default_state, tmp; | |
2211 do { | |
2212 tid = 'j' + this._id + '_' + (++this._cnt); | |
2213 } while(m[tid]); | |
2214 | |
2215 tmp = { | |
2216 id : false, | |
2217 text : typeof d === 'string' ? d : '', | |
2218 icon : typeof d === 'object' && d.icon !== undefined ? d.icon : true, | |
2219 parent : p, | |
2220 parents : ps, | |
2221 children : [], | |
2222 children_d : [], | |
2223 data : null, | |
2224 state : { }, | |
2225 li_attr : { id : false }, | |
2226 a_attr : { href : '#' }, | |
2227 original : false | |
2228 }; | |
2229 for(i in df) { | |
2230 if(df.hasOwnProperty(i)) { | |
2231 tmp.state[i] = df[i]; | |
2232 } | |
2233 } | |
2234 if(d && d.id) { tmp.id = d.id.toString(); } | |
2235 if(d && d.text) { tmp.text = d.text; } | |
2236 if(d && d.data && d.data.jstree && d.data.jstree.icon) { | |
2237 tmp.icon = d.data.jstree.icon; | |
2238 } | |
2239 if(tmp.icon === undefined || tmp.icon === null || tmp.icon === "") { | |
2240 tmp.icon = true; | |
2241 } | |
2242 if(d && d.data) { | |
2243 tmp.data = d.data; | |
2244 if(d.data.jstree) { | |
2245 for(i in d.data.jstree) { | |
2246 if(d.data.jstree.hasOwnProperty(i)) { | |
2247 tmp.state[i] = d.data.jstree[i]; | |
2248 } | |
2249 } | |
2250 } | |
2251 } | |
2252 if(d && typeof d.state === 'object') { | |
2253 for (i in d.state) { | |
2254 if(d.state.hasOwnProperty(i)) { | |
2255 tmp.state[i] = d.state[i]; | |
2256 } | |
2257 } | |
2258 } | |
2259 if(d && typeof d.li_attr === 'object') { | |
2260 for (i in d.li_attr) { | |
2261 if(d.li_attr.hasOwnProperty(i)) { | |
2262 tmp.li_attr[i] = d.li_attr[i]; | |
2263 } | |
2264 } | |
2265 } | |
2266 if(tmp.li_attr.id && !tmp.id) { | |
2267 tmp.id = tmp.li_attr.id.toString(); | |
2268 } | |
2269 if(!tmp.id) { | |
2270 tmp.id = tid; | |
2271 } | |
2272 if(!tmp.li_attr.id) { | |
2273 tmp.li_attr.id = tmp.id; | |
2274 } | |
2275 if(d && typeof d.a_attr === 'object') { | |
2276 for (i in d.a_attr) { | |
2277 if(d.a_attr.hasOwnProperty(i)) { | |
2278 tmp.a_attr[i] = d.a_attr[i]; | |
2279 } | |
2280 } | |
2281 } | |
2282 if(d && d.children && d.children.length) { | |
2283 for(i = 0, j = d.children.length; i < j; i++) { | |
2284 c = this._parse_model_from_json(d.children[i], tmp.id, ps); | |
2285 e = m[c]; | |
2286 tmp.children.push(c); | |
2287 if(e.children_d.length) { | |
2288 tmp.children_d = tmp.children_d.concat(e.children_d); | |
2289 } | |
2290 } | |
2291 tmp.children_d = tmp.children_d.concat(tmp.children); | |
2292 } | |
2293 if(d && d.children && d.children === true) { | |
2294 tmp.state.loaded = false; | |
2295 tmp.children = []; | |
2296 tmp.children_d = []; | |
2297 } | |
2298 delete d.data; | |
2299 delete d.children; | |
2300 tmp.original = d; | |
2301 m[tmp.id] = tmp; | |
2302 if(tmp.state.selected) { | |
2303 this._data.core.selected.push(tmp.id); | |
2304 } | |
2305 return tmp.id; | |
2306 }, | |
2307 /** | |
2308 * redraws all nodes that need to be redrawn. Used internally. | |
2309 * @private | |
2310 * @name _redraw() | |
2311 * @trigger redraw.jstree | |
2312 */ | |
2313 _redraw : function () { | |
2314 var nodes = this._model.force_full_redraw ? this._model.data[$.jstree.root].children.concat([]) : this._model.changed.concat([]), | |
2315 f = document.createElement('UL'), tmp, i, j, fe = this._data.core.focused; | |
2316 for(i = 0, j = nodes.length; i < j; i++) { | |
2317 tmp = this.redraw_node(nodes[i], true, this._model.force_full_redraw); | |
2318 if(tmp && this._model.force_full_redraw) { | |
2319 f.appendChild(tmp); | |
2320 } | |
2321 } | |
2322 if(this._model.force_full_redraw) { | |
2323 f.className = this.get_container_ul()[0].className; | |
2324 f.setAttribute('role','group'); | |
2325 this.element.empty().append(f); | |
2326 //this.get_container_ul()[0].appendChild(f); | |
2327 } | |
2328 if(fe !== null) { | |
2329 tmp = this.get_node(fe, true); | |
2330 if(tmp && tmp.length && tmp.children('.jstree-anchor')[0] !== document.activeElement) { | |
2331 tmp.children('.jstree-anchor').focus(); | |
2332 } | |
2333 else { | |
2334 this._data.core.focused = null; | |
2335 } | |
2336 } | |
2337 this._model.force_full_redraw = false; | |
2338 this._model.changed = []; | |
2339 /** | |
2340 * triggered after nodes are redrawn | |
2341 * @event | |
2342 * @name redraw.jstree | |
2343 * @param {array} nodes the redrawn nodes | |
2344 */ | |
2345 this.trigger('redraw', { "nodes" : nodes }); | |
2346 }, | |
2347 /** | |
2348 * redraws all nodes that need to be redrawn or optionally - the whole tree | |
2349 * @name redraw([full]) | |
2350 * @param {Boolean} full if set to `true` all nodes are redrawn. | |
2351 */ | |
2352 redraw : function (full) { | |
2353 if(full) { | |
2354 this._model.force_full_redraw = true; | |
2355 } | |
2356 //if(this._model.redraw_timeout) { | |
2357 // clearTimeout(this._model.redraw_timeout); | |
2358 //} | |
2359 //this._model.redraw_timeout = setTimeout($.proxy(this._redraw, this),0); | |
2360 this._redraw(); | |
2361 }, | |
2362 /** | |
2363 * redraws a single node's children. Used internally. | |
2364 * @private | |
2365 * @name draw_children(node) | |
2366 * @param {mixed} node the node whose children will be redrawn | |
2367 */ | |
2368 draw_children : function (node) { | |
2369 var obj = this.get_node(node), | |
2370 i = false, | |
2371 j = false, | |
2372 k = false, | |
2373 d = document; | |
2374 if(!obj) { return false; } | |
2375 if(obj.id === $.jstree.root) { return this.redraw(true); } | |
2376 node = this.get_node(node, true); | |
2377 if(!node || !node.length) { return false; } // TODO: quick toggle | |
2378 | |
2379 node.children('.jstree-children').remove(); | |
2380 node = node[0]; | |
2381 if(obj.children.length && obj.state.loaded) { | |
2382 k = d.createElement('UL'); | |
2383 k.setAttribute('role', 'group'); | |
2384 k.className = 'jstree-children'; | |
2385 for(i = 0, j = obj.children.length; i < j; i++) { | |
2386 k.appendChild(this.redraw_node(obj.children[i], true, true)); | |
2387 } | |
2388 node.appendChild(k); | |
2389 } | |
2390 }, | |
2391 /** | |
2392 * redraws a single node. Used internally. | |
2393 * @private | |
2394 * @name redraw_node(node, deep, is_callback, force_render) | |
2395 * @param {mixed} node the node to redraw | |
2396 * @param {Boolean} deep should child nodes be redrawn too | |
2397 * @param {Boolean} is_callback is this a recursion call | |
2398 * @param {Boolean} force_render should children of closed parents be drawn anyway | |
2399 */ | |
2400 redraw_node : function (node, deep, is_callback, force_render) { | |
2401 var obj = this.get_node(node), | |
2402 par = false, | |
2403 ind = false, | |
2404 old = false, | |
2405 i = false, | |
2406 j = false, | |
2407 k = false, | |
2408 c = '', | |
2409 d = document, | |
2410 m = this._model.data, | |
2411 f = false, | |
2412 s = false, | |
2413 tmp = null, | |
2414 t = 0, | |
2415 l = 0, | |
2416 has_children = false, | |
2417 last_sibling = false; | |
2418 if(!obj) { return false; } | |
2419 if(obj.id === $.jstree.root) { return this.redraw(true); } | |
2420 deep = deep || obj.children.length === 0; | |
2421 node = !document.querySelector ? document.getElementById(obj.id) : this.element[0].querySelector('#' + ("0123456789".indexOf(obj.id[0]) !== -1 ? '\\3' + obj.id[0] + ' ' + obj.id.substr(1).replace($.jstree.idregex,'\\$&') : obj.id.replace($.jstree.idregex,'\\$&')) ); //, this.element); | |
2422 if(!node) { | |
2423 deep = true; | |
2424 //node = d.createElement('LI'); | |
2425 if(!is_callback) { | |
2426 par = obj.parent !== $.jstree.root ? $('#' + obj.parent.replace($.jstree.idregex,'\\$&'), this.element)[0] : null; | |
2427 if(par !== null && (!par || !m[obj.parent].state.opened)) { | |
2428 return false; | |
2429 } | |
2430 ind = $.inArray(obj.id, par === null ? m[$.jstree.root].children : m[obj.parent].children); | |
2431 } | |
2432 } | |
2433 else { | |
2434 node = $(node); | |
2435 if(!is_callback) { | |
2436 par = node.parent().parent()[0]; | |
2437 if(par === this.element[0]) { | |
2438 par = null; | |
2439 } | |
2440 ind = node.index(); | |
2441 } | |
2442 // m[obj.id].data = node.data(); // use only node's data, no need to touch jquery storage | |
2443 if(!deep && obj.children.length && !node.children('.jstree-children').length) { | |
2444 deep = true; | |
2445 } | |
2446 if(!deep) { | |
2447 old = node.children('.jstree-children')[0]; | |
2448 } | |
2449 f = node.children('.jstree-anchor')[0] === document.activeElement; | |
2450 node.remove(); | |
2451 //node = d.createElement('LI'); | |
2452 //node = node[0]; | |
2453 } | |
2454 node = this._data.core.node.cloneNode(true); | |
2455 // node is DOM, deep is boolean | |
2456 | |
2457 c = 'jstree-node '; | |
2458 for(i in obj.li_attr) { | |
2459 if(obj.li_attr.hasOwnProperty(i)) { | |
2460 if(i === 'id') { continue; } | |
2461 if(i !== 'class') { | |
2462 node.setAttribute(i, obj.li_attr[i]); | |
2463 } | |
2464 else { | |
2465 c += obj.li_attr[i]; | |
2466 } | |
2467 } | |
2468 } | |
2469 if(!obj.a_attr.id) { | |
2470 obj.a_attr.id = obj.id + '_anchor'; | |
2471 } | |
2472 node.setAttribute('aria-selected', !!obj.state.selected); | |
2473 node.setAttribute('aria-level', obj.parents.length); | |
2474 node.setAttribute('aria-labelledby', obj.a_attr.id); | |
2475 if(obj.state.disabled) { | |
2476 node.setAttribute('aria-disabled', true); | |
2477 } | |
2478 | |
2479 for(i = 0, j = obj.children.length; i < j; i++) { | |
2480 if(!m[obj.children[i]].state.hidden) { | |
2481 has_children = true; | |
2482 break; | |
2483 } | |
2484 } | |
2485 if(obj.parent !== null && m[obj.parent] && !obj.state.hidden) { | |
2486 i = $.inArray(obj.id, m[obj.parent].children); | |
2487 last_sibling = obj.id; | |
2488 if(i !== -1) { | |
2489 i++; | |
2490 for(j = m[obj.parent].children.length; i < j; i++) { | |
2491 if(!m[m[obj.parent].children[i]].state.hidden) { | |
2492 last_sibling = m[obj.parent].children[i]; | |
2493 } | |
2494 if(last_sibling !== obj.id) { | |
2495 break; | |
2496 } | |
2497 } | |
2498 } | |
2499 } | |
2500 | |
2501 if(obj.state.hidden) { | |
2502 c += ' jstree-hidden'; | |
2503 } | |
2504 if (obj.state.loading) { | |
2505 c += ' jstree-loading'; | |
2506 } | |
2507 if(obj.state.loaded && !has_children) { | |
2508 c += ' jstree-leaf'; | |
2509 } | |
2510 else { | |
2511 c += obj.state.opened && obj.state.loaded ? ' jstree-open' : ' jstree-closed'; | |
2512 node.setAttribute('aria-expanded', (obj.state.opened && obj.state.loaded) ); | |
2513 } | |
2514 if(last_sibling === obj.id) { | |
2515 c += ' jstree-last'; | |
2516 } | |
2517 node.id = obj.id; | |
2518 node.className = c; | |
2519 c = ( obj.state.selected ? ' jstree-clicked' : '') + ( obj.state.disabled ? ' jstree-disabled' : ''); | |
2520 for(j in obj.a_attr) { | |
2521 if(obj.a_attr.hasOwnProperty(j)) { | |
2522 if(j === 'href' && obj.a_attr[j] === '#') { continue; } | |
2523 if(j !== 'class') { | |
2524 node.childNodes[1].setAttribute(j, obj.a_attr[j]); | |
2525 } | |
2526 else { | |
2527 c += ' ' + obj.a_attr[j]; | |
2528 } | |
2529 } | |
2530 } | |
2531 if(c.length) { | |
2532 node.childNodes[1].className = 'jstree-anchor ' + c; | |
2533 } | |
2534 if((obj.icon && obj.icon !== true) || obj.icon === false) { | |
2535 if(obj.icon === false) { | |
2536 node.childNodes[1].childNodes[0].className += ' jstree-themeicon-hidden'; | |
2537 } | |
2538 else if(obj.icon.indexOf('/') === -1 && obj.icon.indexOf('.') === -1) { | |
2539 node.childNodes[1].childNodes[0].className += ' ' + obj.icon + ' jstree-themeicon-custom'; | |
2540 } | |
2541 else { | |
2542 node.childNodes[1].childNodes[0].style.backgroundImage = 'url("'+obj.icon+'")'; | |
2543 node.childNodes[1].childNodes[0].style.backgroundPosition = 'center center'; | |
2544 node.childNodes[1].childNodes[0].style.backgroundSize = 'auto'; | |
2545 node.childNodes[1].childNodes[0].className += ' jstree-themeicon-custom'; | |
2546 } | |
2547 } | |
2548 | |
2549 if(this.settings.core.force_text) { | |
2550 node.childNodes[1].appendChild(d.createTextNode(obj.text)); | |
2551 } | |
2552 else { | |
2553 node.childNodes[1].innerHTML += obj.text; | |
2554 } | |
2555 | |
2556 | |
2557 if(deep && obj.children.length && (obj.state.opened || force_render) && obj.state.loaded) { | |
2558 k = d.createElement('UL'); | |
2559 k.setAttribute('role', 'group'); | |
2560 k.className = 'jstree-children'; | |
2561 for(i = 0, j = obj.children.length; i < j; i++) { | |
2562 k.appendChild(this.redraw_node(obj.children[i], deep, true)); | |
2563 } | |
2564 node.appendChild(k); | |
2565 } | |
2566 if(old) { | |
2567 node.appendChild(old); | |
2568 } | |
2569 if(!is_callback) { | |
2570 // append back using par / ind | |
2571 if(!par) { | |
2572 par = this.element[0]; | |
2573 } | |
2574 for(i = 0, j = par.childNodes.length; i < j; i++) { | |
2575 if(par.childNodes[i] && par.childNodes[i].className && par.childNodes[i].className.indexOf('jstree-children') !== -1) { | |
2576 tmp = par.childNodes[i]; | |
2577 break; | |
2578 } | |
2579 } | |
2580 if(!tmp) { | |
2581 tmp = d.createElement('UL'); | |
2582 tmp.setAttribute('role', 'group'); | |
2583 tmp.className = 'jstree-children'; | |
2584 par.appendChild(tmp); | |
2585 } | |
2586 par = tmp; | |
2587 | |
2588 if(ind < par.childNodes.length) { | |
2589 par.insertBefore(node, par.childNodes[ind]); | |
2590 } | |
2591 else { | |
2592 par.appendChild(node); | |
2593 } | |
2594 if(f) { | |
2595 t = this.element[0].scrollTop; | |
2596 l = this.element[0].scrollLeft; | |
2597 node.childNodes[1].focus(); | |
2598 this.element[0].scrollTop = t; | |
2599 this.element[0].scrollLeft = l; | |
2600 } | |
2601 } | |
2602 if(obj.state.opened && !obj.state.loaded) { | |
2603 obj.state.opened = false; | |
2604 setTimeout($.proxy(function () { | |
2605 this.open_node(obj.id, false, 0); | |
2606 }, this), 0); | |
2607 } | |
2608 return node; | |
2609 }, | |
2610 /** | |
2611 * opens a node, revaling its children. If the node is not loaded it will be loaded and opened once ready. | |
2612 * @name open_node(obj [, callback, animation]) | |
2613 * @param {mixed} obj the node to open | |
2614 * @param {Function} callback a function to execute once the node is opened | |
2615 * @param {Number} animation the animation duration in milliseconds when opening the node (overrides the `core.animation` setting). Use `false` for no animation. | |
2616 * @trigger open_node.jstree, after_open.jstree, before_open.jstree | |
2617 */ | |
2618 open_node : function (obj, callback, animation) { | |
2619 var t1, t2, d, t; | |
2620 if($.isArray(obj)) { | |
2621 obj = obj.slice(); | |
2622 for(t1 = 0, t2 = obj.length; t1 < t2; t1++) { | |
2623 this.open_node(obj[t1], callback, animation); | |
2624 } | |
2625 return true; | |
2626 } | |
2627 obj = this.get_node(obj); | |
2628 if(!obj || obj.id === $.jstree.root) { | |
2629 return false; | |
2630 } | |
2631 animation = animation === undefined ? this.settings.core.animation : animation; | |
2632 if(!this.is_closed(obj)) { | |
2633 if(callback) { | |
2634 callback.call(this, obj, false); | |
2635 } | |
2636 return false; | |
2637 } | |
2638 if(!this.is_loaded(obj)) { | |
2639 if(this.is_loading(obj)) { | |
2640 return setTimeout($.proxy(function () { | |
2641 this.open_node(obj, callback, animation); | |
2642 }, this), 500); | |
2643 } | |
2644 this.load_node(obj, function (o, ok) { | |
2645 return ok ? this.open_node(o, callback, animation) : (callback ? callback.call(this, o, false) : false); | |
2646 }); | |
2647 } | |
2648 else { | |
2649 d = this.get_node(obj, true); | |
2650 t = this; | |
2651 if(d.length) { | |
2652 if(animation && d.children(".jstree-children").length) { | |
2653 d.children(".jstree-children").stop(true, true); | |
2654 } | |
2655 if(obj.children.length && !this._firstChild(d.children('.jstree-children')[0])) { | |
2656 this.draw_children(obj); | |
2657 //d = this.get_node(obj, true); | |
2658 } | |
2659 if(!animation) { | |
2660 this.trigger('before_open', { "node" : obj }); | |
2661 d[0].className = d[0].className.replace('jstree-closed', 'jstree-open'); | |
2662 d[0].setAttribute("aria-expanded", true); | |
2663 } | |
2664 else { | |
2665 this.trigger('before_open', { "node" : obj }); | |
2666 d | |
2667 .children(".jstree-children").css("display","none").end() | |
2668 .removeClass("jstree-closed").addClass("jstree-open").attr("aria-expanded", true) | |
2669 .children(".jstree-children").stop(true, true) | |
2670 .slideDown(animation, function () { | |
2671 this.style.display = ""; | |
2672 if (t.element) { | |
2673 t.trigger("after_open", { "node" : obj }); | |
2674 } | |
2675 }); | |
2676 } | |
2677 } | |
2678 obj.state.opened = true; | |
2679 if(callback) { | |
2680 callback.call(this, obj, true); | |
2681 } | |
2682 if(!d.length) { | |
2683 /** | |
2684 * triggered when a node is about to be opened (if the node is supposed to be in the DOM, it will be, but it won't be visible yet) | |
2685 * @event | |
2686 * @name before_open.jstree | |
2687 * @param {Object} node the opened node | |
2688 */ | |
2689 this.trigger('before_open', { "node" : obj }); | |
2690 } | |
2691 /** | |
2692 * triggered when a node is opened (if there is an animation it will not be completed yet) | |
2693 * @event | |
2694 * @name open_node.jstree | |
2695 * @param {Object} node the opened node | |
2696 */ | |
2697 this.trigger('open_node', { "node" : obj }); | |
2698 if(!animation || !d.length) { | |
2699 /** | |
2700 * triggered when a node is opened and the animation is complete | |
2701 * @event | |
2702 * @name after_open.jstree | |
2703 * @param {Object} node the opened node | |
2704 */ | |
2705 this.trigger("after_open", { "node" : obj }); | |
2706 } | |
2707 return true; | |
2708 } | |
2709 }, | |
2710 /** | |
2711 * opens every parent of a node (node should be loaded) | |
2712 * @name _open_to(obj) | |
2713 * @param {mixed} obj the node to reveal | |
2714 * @private | |
2715 */ | |
2716 _open_to : function (obj) { | |
2717 obj = this.get_node(obj); | |
2718 if(!obj || obj.id === $.jstree.root) { | |
2719 return false; | |
2720 } | |
2721 var i, j, p = obj.parents; | |
2722 for(i = 0, j = p.length; i < j; i+=1) { | |
2723 if(i !== $.jstree.root) { | |
2724 this.open_node(p[i], false, 0); | |
2725 } | |
2726 } | |
2727 return $('#' + obj.id.replace($.jstree.idregex,'\\$&'), this.element); | |
2728 }, | |
2729 /** | |
2730 * closes a node, hiding its children | |
2731 * @name close_node(obj [, animation]) | |
2732 * @param {mixed} obj the node to close | |
2733 * @param {Number} animation the animation duration in milliseconds when closing the node (overrides the `core.animation` setting). Use `false` for no animation. | |
2734 * @trigger close_node.jstree, after_close.jstree | |
2735 */ | |
2736 close_node : function (obj, animation) { | |
2737 var t1, t2, t, d; | |
2738 if($.isArray(obj)) { | |
2739 obj = obj.slice(); | |
2740 for(t1 = 0, t2 = obj.length; t1 < t2; t1++) { | |
2741 this.close_node(obj[t1], animation); | |
2742 } | |
2743 return true; | |
2744 } | |
2745 obj = this.get_node(obj); | |
2746 if(!obj || obj.id === $.jstree.root) { | |
2747 return false; | |
2748 } | |
2749 if(this.is_closed(obj)) { | |
2750 return false; | |
2751 } | |
2752 animation = animation === undefined ? this.settings.core.animation : animation; | |
2753 t = this; | |
2754 d = this.get_node(obj, true); | |
2755 | |
2756 obj.state.opened = false; | |
2757 /** | |
2758 * triggered when a node is closed (if there is an animation it will not be complete yet) | |
2759 * @event | |
2760 * @name close_node.jstree | |
2761 * @param {Object} node the closed node | |
2762 */ | |
2763 this.trigger('close_node',{ "node" : obj }); | |
2764 if(!d.length) { | |
2765 /** | |
2766 * triggered when a node is closed and the animation is complete | |
2767 * @event | |
2768 * @name after_close.jstree | |
2769 * @param {Object} node the closed node | |
2770 */ | |
2771 this.trigger("after_close", { "node" : obj }); | |
2772 } | |
2773 else { | |
2774 if(!animation) { | |
2775 d[0].className = d[0].className.replace('jstree-open', 'jstree-closed'); | |
2776 d.attr("aria-expanded", false).children('.jstree-children').remove(); | |
2777 this.trigger("after_close", { "node" : obj }); | |
2778 } | |
2779 else { | |
2780 d | |
2781 .children(".jstree-children").attr("style","display:block !important").end() | |
2782 .removeClass("jstree-open").addClass("jstree-closed").attr("aria-expanded", false) | |
2783 .children(".jstree-children").stop(true, true).slideUp(animation, function () { | |
2784 this.style.display = ""; | |
2785 d.children('.jstree-children').remove(); | |
2786 if (t.element) { | |
2787 t.trigger("after_close", { "node" : obj }); | |
2788 } | |
2789 }); | |
2790 } | |
2791 } | |
2792 }, | |
2793 /** | |
2794 * toggles a node - closing it if it is open, opening it if it is closed | |
2795 * @name toggle_node(obj) | |
2796 * @param {mixed} obj the node to toggle | |
2797 */ | |
2798 toggle_node : function (obj) { | |
2799 var t1, t2; | |
2800 if($.isArray(obj)) { | |
2801 obj = obj.slice(); | |
2802 for(t1 = 0, t2 = obj.length; t1 < t2; t1++) { | |
2803 this.toggle_node(obj[t1]); | |
2804 } | |
2805 return true; | |
2806 } | |
2807 if(this.is_closed(obj)) { | |
2808 return this.open_node(obj); | |
2809 } | |
2810 if(this.is_open(obj)) { | |
2811 return this.close_node(obj); | |
2812 } | |
2813 }, | |
2814 /** | |
2815 * opens all nodes within a node (or the tree), revaling their children. If the node is not loaded it will be loaded and opened once ready. | |
2816 * @name open_all([obj, animation, original_obj]) | |
2817 * @param {mixed} obj the node to open recursively, omit to open all nodes in the tree | |
2818 * @param {Number} animation the animation duration in milliseconds when opening the nodes, the default is no animation | |
2819 * @param {jQuery} reference to the node that started the process (internal use) | |
2820 * @trigger open_all.jstree | |
2821 */ | |
2822 open_all : function (obj, animation, original_obj) { | |
2823 if(!obj) { obj = $.jstree.root; } | |
2824 obj = this.get_node(obj); | |
2825 if(!obj) { return false; } | |
2826 var dom = obj.id === $.jstree.root ? this.get_container_ul() : this.get_node(obj, true), i, j, _this; | |
2827 if(!dom.length) { | |
2828 for(i = 0, j = obj.children_d.length; i < j; i++) { | |
2829 if(this.is_closed(this._model.data[obj.children_d[i]])) { | |
2830 this._model.data[obj.children_d[i]].state.opened = true; | |
2831 } | |
2832 } | |
2833 return this.trigger('open_all', { "node" : obj }); | |
2834 } | |
2835 original_obj = original_obj || dom; | |
2836 _this = this; | |
2837 dom = this.is_closed(obj) ? dom.find('.jstree-closed').addBack() : dom.find('.jstree-closed'); | |
2838 dom.each(function () { | |
2839 _this.open_node( | |
2840 this, | |
2841 function(node, status) { if(status && this.is_parent(node)) { this.open_all(node, animation, original_obj); } }, | |
2842 animation || 0 | |
2843 ); | |
2844 }); | |
2845 if(original_obj.find('.jstree-closed').length === 0) { | |
2846 /** | |
2847 * triggered when an `open_all` call completes | |
2848 * @event | |
2849 * @name open_all.jstree | |
2850 * @param {Object} node the opened node | |
2851 */ | |
2852 this.trigger('open_all', { "node" : this.get_node(original_obj) }); | |
2853 } | |
2854 }, | |
2855 /** | |
2856 * closes all nodes within a node (or the tree), revaling their children | |
2857 * @name close_all([obj, animation]) | |
2858 * @param {mixed} obj the node to close recursively, omit to close all nodes in the tree | |
2859 * @param {Number} animation the animation duration in milliseconds when closing the nodes, the default is no animation | |
2860 * @trigger close_all.jstree | |
2861 */ | |
2862 close_all : function (obj, animation) { | |
2863 if(!obj) { obj = $.jstree.root; } | |
2864 obj = this.get_node(obj); | |
2865 if(!obj) { return false; } | |
2866 var dom = obj.id === $.jstree.root ? this.get_container_ul() : this.get_node(obj, true), | |
2867 _this = this, i, j; | |
2868 if(dom.length) { | |
2869 dom = this.is_open(obj) ? dom.find('.jstree-open').addBack() : dom.find('.jstree-open'); | |
2870 $(dom.get().reverse()).each(function () { _this.close_node(this, animation || 0); }); | |
2871 } | |
2872 for(i = 0, j = obj.children_d.length; i < j; i++) { | |
2873 this._model.data[obj.children_d[i]].state.opened = false; | |
2874 } | |
2875 /** | |
2876 * triggered when an `close_all` call completes | |
2877 * @event | |
2878 * @name close_all.jstree | |
2879 * @param {Object} node the closed node | |
2880 */ | |
2881 this.trigger('close_all', { "node" : obj }); | |
2882 }, | |
2883 /** | |
2884 * checks if a node is disabled (not selectable) | |
2885 * @name is_disabled(obj) | |
2886 * @param {mixed} obj | |
2887 * @return {Boolean} | |
2888 */ | |
2889 is_disabled : function (obj) { | |
2890 obj = this.get_node(obj); | |
2891 return obj && obj.state && obj.state.disabled; | |
2892 }, | |
2893 /** | |
2894 * enables a node - so that it can be selected | |
2895 * @name enable_node(obj) | |
2896 * @param {mixed} obj the node to enable | |
2897 * @trigger enable_node.jstree | |
2898 */ | |
2899 enable_node : function (obj) { | |
2900 var t1, t2; | |
2901 if($.isArray(obj)) { | |
2902 obj = obj.slice(); | |
2903 for(t1 = 0, t2 = obj.length; t1 < t2; t1++) { | |
2904 this.enable_node(obj[t1]); | |
2905 } | |
2906 return true; | |
2907 } | |
2908 obj = this.get_node(obj); | |
2909 if(!obj || obj.id === $.jstree.root) { | |
2910 return false; | |
2911 } | |
2912 obj.state.disabled = false; | |
2913 this.get_node(obj,true).children('.jstree-anchor').removeClass('jstree-disabled').attr('aria-disabled', false); | |
2914 /** | |
2915 * triggered when an node is enabled | |
2916 * @event | |
2917 * @name enable_node.jstree | |
2918 * @param {Object} node the enabled node | |
2919 */ | |
2920 this.trigger('enable_node', { 'node' : obj }); | |
2921 }, | |
2922 /** | |
2923 * disables a node - so that it can not be selected | |
2924 * @name disable_node(obj) | |
2925 * @param {mixed} obj the node to disable | |
2926 * @trigger disable_node.jstree | |
2927 */ | |
2928 disable_node : function (obj) { | |
2929 var t1, t2; | |
2930 if($.isArray(obj)) { | |
2931 obj = obj.slice(); | |
2932 for(t1 = 0, t2 = obj.length; t1 < t2; t1++) { | |
2933 this.disable_node(obj[t1]); | |
2934 } | |
2935 return true; | |
2936 } | |
2937 obj = this.get_node(obj); | |
2938 if(!obj || obj.id === $.jstree.root) { | |
2939 return false; | |
2940 } | |
2941 obj.state.disabled = true; | |
2942 this.get_node(obj,true).children('.jstree-anchor').addClass('jstree-disabled').attr('aria-disabled', true); | |
2943 /** | |
2944 * triggered when an node is disabled | |
2945 * @event | |
2946 * @name disable_node.jstree | |
2947 * @param {Object} node the disabled node | |
2948 */ | |
2949 this.trigger('disable_node', { 'node' : obj }); | |
2950 }, | |
2951 /** | |
2952 * determines if a node is hidden | |
2953 * @name is_hidden(obj) | |
2954 * @param {mixed} obj the node | |
2955 */ | |
2956 is_hidden : function (obj) { | |
2957 obj = this.get_node(obj); | |
2958 return obj.state.hidden === true; | |
2959 }, | |
2960 /** | |
2961 * hides a node - it is still in the structure but will not be visible | |
2962 * @name hide_node(obj) | |
2963 * @param {mixed} obj the node to hide | |
2964 * @param {Boolean} skip_redraw internal parameter controlling if redraw is called | |
2965 * @trigger hide_node.jstree | |
2966 */ | |
2967 hide_node : function (obj, skip_redraw) { | |
2968 var t1, t2; | |
2969 if($.isArray(obj)) { | |
2970 obj = obj.slice(); | |
2971 for(t1 = 0, t2 = obj.length; t1 < t2; t1++) { | |
2972 this.hide_node(obj[t1], true); | |
2973 } | |
2974 if (!skip_redraw) { | |
2975 this.redraw(); | |
2976 } | |
2977 return true; | |
2978 } | |
2979 obj = this.get_node(obj); | |
2980 if(!obj || obj.id === $.jstree.root) { | |
2981 return false; | |
2982 } | |
2983 if(!obj.state.hidden) { | |
2984 obj.state.hidden = true; | |
2985 this._node_changed(obj.parent); | |
2986 if(!skip_redraw) { | |
2987 this.redraw(); | |
2988 } | |
2989 /** | |
2990 * triggered when an node is hidden | |
2991 * @event | |
2992 * @name hide_node.jstree | |
2993 * @param {Object} node the hidden node | |
2994 */ | |
2995 this.trigger('hide_node', { 'node' : obj }); | |
2996 } | |
2997 }, | |
2998 /** | |
2999 * shows a node | |
3000 * @name show_node(obj) | |
3001 * @param {mixed} obj the node to show | |
3002 * @param {Boolean} skip_redraw internal parameter controlling if redraw is called | |
3003 * @trigger show_node.jstree | |
3004 */ | |
3005 show_node : function (obj, skip_redraw) { | |
3006 var t1, t2; | |
3007 if($.isArray(obj)) { | |
3008 obj = obj.slice(); | |
3009 for(t1 = 0, t2 = obj.length; t1 < t2; t1++) { | |
3010 this.show_node(obj[t1], true); | |
3011 } | |
3012 if (!skip_redraw) { | |
3013 this.redraw(); | |
3014 } | |
3015 return true; | |
3016 } | |
3017 obj = this.get_node(obj); | |
3018 if(!obj || obj.id === $.jstree.root) { | |
3019 return false; | |
3020 } | |
3021 if(obj.state.hidden) { | |
3022 obj.state.hidden = false; | |
3023 this._node_changed(obj.parent); | |
3024 if(!skip_redraw) { | |
3025 this.redraw(); | |
3026 } | |
3027 /** | |
3028 * triggered when an node is shown | |
3029 * @event | |
3030 * @name show_node.jstree | |
3031 * @param {Object} node the shown node | |
3032 */ | |
3033 this.trigger('show_node', { 'node' : obj }); | |
3034 } | |
3035 }, | |
3036 /** | |
3037 * hides all nodes | |
3038 * @name hide_all() | |
3039 * @trigger hide_all.jstree | |
3040 */ | |
3041 hide_all : function (skip_redraw) { | |
3042 var i, m = this._model.data, ids = []; | |
3043 for(i in m) { | |
3044 if(m.hasOwnProperty(i) && i !== $.jstree.root && !m[i].state.hidden) { | |
3045 m[i].state.hidden = true; | |
3046 ids.push(i); | |
3047 } | |
3048 } | |
3049 this._model.force_full_redraw = true; | |
3050 if(!skip_redraw) { | |
3051 this.redraw(); | |
3052 } | |
3053 /** | |
3054 * triggered when all nodes are hidden | |
3055 * @event | |
3056 * @name hide_all.jstree | |
3057 * @param {Array} nodes the IDs of all hidden nodes | |
3058 */ | |
3059 this.trigger('hide_all', { 'nodes' : ids }); | |
3060 return ids; | |
3061 }, | |
3062 /** | |
3063 * shows all nodes | |
3064 * @name show_all() | |
3065 * @trigger show_all.jstree | |
3066 */ | |
3067 show_all : function (skip_redraw) { | |
3068 var i, m = this._model.data, ids = []; | |
3069 for(i in m) { | |
3070 if(m.hasOwnProperty(i) && i !== $.jstree.root && m[i].state.hidden) { | |
3071 m[i].state.hidden = false; | |
3072 ids.push(i); | |
3073 } | |
3074 } | |
3075 this._model.force_full_redraw = true; | |
3076 if(!skip_redraw) { | |
3077 this.redraw(); | |
3078 } | |
3079 /** | |
3080 * triggered when all nodes are shown | |
3081 * @event | |
3082 * @name show_all.jstree | |
3083 * @param {Array} nodes the IDs of all shown nodes | |
3084 */ | |
3085 this.trigger('show_all', { 'nodes' : ids }); | |
3086 return ids; | |
3087 }, | |
3088 /** | |
3089 * called when a node is selected by the user. Used internally. | |
3090 * @private | |
3091 * @name activate_node(obj, e) | |
3092 * @param {mixed} obj the node | |
3093 * @param {Object} e the related event | |
3094 * @trigger activate_node.jstree, changed.jstree | |
3095 */ | |
3096 activate_node : function (obj, e) { | |
3097 if(this.is_disabled(obj)) { | |
3098 return false; | |
3099 } | |
3100 if(!e || typeof e !== 'object') { | |
3101 e = {}; | |
3102 } | |
3103 | |
3104 // ensure last_clicked is still in the DOM, make it fresh (maybe it was moved?) and make sure it is still selected, if not - make last_clicked the last selected node | |
3105 this._data.core.last_clicked = this._data.core.last_clicked && this._data.core.last_clicked.id !== undefined ? this.get_node(this._data.core.last_clicked.id) : null; | |
3106 if(this._data.core.last_clicked && !this._data.core.last_clicked.state.selected) { this._data.core.last_clicked = null; } | |
3107 if(!this._data.core.last_clicked && this._data.core.selected.length) { this._data.core.last_clicked = this.get_node(this._data.core.selected[this._data.core.selected.length - 1]); } | |
3108 | |
3109 if(!this.settings.core.multiple || (!e.metaKey && !e.ctrlKey && !e.shiftKey) || (e.shiftKey && (!this._data.core.last_clicked || !this.get_parent(obj) || this.get_parent(obj) !== this._data.core.last_clicked.parent ) )) { | |
3110 if(!this.settings.core.multiple && (e.metaKey || e.ctrlKey || e.shiftKey) && this.is_selected(obj)) { | |
3111 this.deselect_node(obj, false, e); | |
3112 } | |
3113 else { | |
3114 this.deselect_all(true); | |
3115 this.select_node(obj, false, false, e); | |
3116 this._data.core.last_clicked = this.get_node(obj); | |
3117 } | |
3118 } | |
3119 else { | |
3120 if(e.shiftKey) { | |
3121 var o = this.get_node(obj).id, | |
3122 l = this._data.core.last_clicked.id, | |
3123 p = this.get_node(this._data.core.last_clicked.parent).children, | |
3124 c = false, | |
3125 i, j; | |
3126 for(i = 0, j = p.length; i < j; i += 1) { | |
3127 // separate IFs work whem o and l are the same | |
3128 if(p[i] === o) { | |
3129 c = !c; | |
3130 } | |
3131 if(p[i] === l) { | |
3132 c = !c; | |
3133 } | |
3134 if(!this.is_disabled(p[i]) && (c || p[i] === o || p[i] === l)) { | |
3135 if (!this.is_hidden(p[i])) { | |
3136 this.select_node(p[i], true, false, e); | |
3137 } | |
3138 } | |
3139 else { | |
3140 this.deselect_node(p[i], true, e); | |
3141 } | |
3142 } | |
3143 this.trigger('changed', { 'action' : 'select_node', 'node' : this.get_node(obj), 'selected' : this._data.core.selected, 'event' : e }); | |
3144 } | |
3145 else { | |
3146 if(!this.is_selected(obj)) { | |
3147 this.select_node(obj, false, false, e); | |
3148 } | |
3149 else { | |
3150 this.deselect_node(obj, false, e); | |
3151 } | |
3152 } | |
3153 } | |
3154 /** | |
3155 * triggered when an node is clicked or intercated with by the user | |
3156 * @event | |
3157 * @name activate_node.jstree | |
3158 * @param {Object} node | |
3159 * @param {Object} event the ooriginal event (if any) which triggered the call (may be an empty object) | |
3160 */ | |
3161 this.trigger('activate_node', { 'node' : this.get_node(obj), 'event' : e }); | |
3162 }, | |
3163 /** | |
3164 * applies the hover state on a node, called when a node is hovered by the user. Used internally. | |
3165 * @private | |
3166 * @name hover_node(obj) | |
3167 * @param {mixed} obj | |
3168 * @trigger hover_node.jstree | |
3169 */ | |
3170 hover_node : function (obj) { | |
3171 obj = this.get_node(obj, true); | |
3172 if(!obj || !obj.length || obj.children('.jstree-hovered').length) { | |
3173 return false; | |
3174 } | |
3175 var o = this.element.find('.jstree-hovered'), t = this.element; | |
3176 if(o && o.length) { this.dehover_node(o); } | |
3177 | |
3178 obj.children('.jstree-anchor').addClass('jstree-hovered'); | |
3179 /** | |
3180 * triggered when an node is hovered | |
3181 * @event | |
3182 * @name hover_node.jstree | |
3183 * @param {Object} node | |
3184 */ | |
3185 this.trigger('hover_node', { 'node' : this.get_node(obj) }); | |
3186 setTimeout(function () { t.attr('aria-activedescendant', obj[0].id); }, 0); | |
3187 }, | |
3188 /** | |
3189 * removes the hover state from a nodecalled when a node is no longer hovered by the user. Used internally. | |
3190 * @private | |
3191 * @name dehover_node(obj) | |
3192 * @param {mixed} obj | |
3193 * @trigger dehover_node.jstree | |
3194 */ | |
3195 dehover_node : function (obj) { | |
3196 obj = this.get_node(obj, true); | |
3197 if(!obj || !obj.length || !obj.children('.jstree-hovered').length) { | |
3198 return false; | |
3199 } | |
3200 obj.children('.jstree-anchor').removeClass('jstree-hovered'); | |
3201 /** | |
3202 * triggered when an node is no longer hovered | |
3203 * @event | |
3204 * @name dehover_node.jstree | |
3205 * @param {Object} node | |
3206 */ | |
3207 this.trigger('dehover_node', { 'node' : this.get_node(obj) }); | |
3208 }, | |
3209 /** | |
3210 * select a node | |
3211 * @name select_node(obj [, supress_event, prevent_open]) | |
3212 * @param {mixed} obj an array can be used to select multiple nodes | |
3213 * @param {Boolean} supress_event if set to `true` the `changed.jstree` event won't be triggered | |
3214 * @param {Boolean} prevent_open if set to `true` parents of the selected node won't be opened | |
3215 * @trigger select_node.jstree, changed.jstree | |
3216 */ | |
3217 select_node : function (obj, supress_event, prevent_open, e) { | |
3218 var dom, t1, t2, th; | |
3219 if($.isArray(obj)) { | |
3220 obj = obj.slice(); | |
3221 for(t1 = 0, t2 = obj.length; t1 < t2; t1++) { | |
3222 this.select_node(obj[t1], supress_event, prevent_open, e); | |
3223 } | |
3224 return true; | |
3225 } | |
3226 obj = this.get_node(obj); | |
3227 if(!obj || obj.id === $.jstree.root) { | |
3228 return false; | |
3229 } | |
3230 dom = this.get_node(obj, true); | |
3231 if(!obj.state.selected) { | |
3232 obj.state.selected = true; | |
3233 this._data.core.selected.push(obj.id); | |
3234 if(!prevent_open) { | |
3235 dom = this._open_to(obj); | |
3236 } | |
3237 if(dom && dom.length) { | |
3238 dom.attr('aria-selected', true).children('.jstree-anchor').addClass('jstree-clicked'); | |
3239 } | |
3240 /** | |
3241 * triggered when an node is selected | |
3242 * @event | |
3243 * @name select_node.jstree | |
3244 * @param {Object} node | |
3245 * @param {Array} selected the current selection | |
3246 * @param {Object} event the event (if any) that triggered this select_node | |
3247 */ | |
3248 this.trigger('select_node', { 'node' : obj, 'selected' : this._data.core.selected, 'event' : e }); | |
3249 if(!supress_event) { | |
3250 /** | |
3251 * triggered when selection changes | |
3252 * @event | |
3253 * @name changed.jstree | |
3254 * @param {Object} node | |
3255 * @param {Object} action the action that caused the selection to change | |
3256 * @param {Array} selected the current selection | |
3257 * @param {Object} event the event (if any) that triggered this changed event | |
3258 */ | |
3259 this.trigger('changed', { 'action' : 'select_node', 'node' : obj, 'selected' : this._data.core.selected, 'event' : e }); | |
3260 } | |
3261 } | |
3262 }, | |
3263 /** | |
3264 * deselect a node | |
3265 * @name deselect_node(obj [, supress_event]) | |
3266 * @param {mixed} obj an array can be used to deselect multiple nodes | |
3267 * @param {Boolean} supress_event if set to `true` the `changed.jstree` event won't be triggered | |
3268 * @trigger deselect_node.jstree, changed.jstree | |
3269 */ | |
3270 deselect_node : function (obj, supress_event, e) { | |
3271 var t1, t2, dom; | |
3272 if($.isArray(obj)) { | |
3273 obj = obj.slice(); | |
3274 for(t1 = 0, t2 = obj.length; t1 < t2; t1++) { | |
3275 this.deselect_node(obj[t1], supress_event, e); | |
3276 } | |
3277 return true; | |
3278 } | |
3279 obj = this.get_node(obj); | |
3280 if(!obj || obj.id === $.jstree.root) { | |
3281 return false; | |
3282 } | |
3283 dom = this.get_node(obj, true); | |
3284 if(obj.state.selected) { | |
3285 obj.state.selected = false; | |
3286 this._data.core.selected = $.vakata.array_remove_item(this._data.core.selected, obj.id); | |
3287 if(dom.length) { | |
3288 dom.attr('aria-selected', false).children('.jstree-anchor').removeClass('jstree-clicked'); | |
3289 } | |
3290 /** | |
3291 * triggered when an node is deselected | |
3292 * @event | |
3293 * @name deselect_node.jstree | |
3294 * @param {Object} node | |
3295 * @param {Array} selected the current selection | |
3296 * @param {Object} event the event (if any) that triggered this deselect_node | |
3297 */ | |
3298 this.trigger('deselect_node', { 'node' : obj, 'selected' : this._data.core.selected, 'event' : e }); | |
3299 if(!supress_event) { | |
3300 this.trigger('changed', { 'action' : 'deselect_node', 'node' : obj, 'selected' : this._data.core.selected, 'event' : e }); | |
3301 } | |
3302 } | |
3303 }, | |
3304 /** | |
3305 * select all nodes in the tree | |
3306 * @name select_all([supress_event]) | |
3307 * @param {Boolean} supress_event if set to `true` the `changed.jstree` event won't be triggered | |
3308 * @trigger select_all.jstree, changed.jstree | |
3309 */ | |
3310 select_all : function (supress_event) { | |
3311 var tmp = this._data.core.selected.concat([]), i, j; | |
3312 this._data.core.selected = this._model.data[$.jstree.root].children_d.concat(); | |
3313 for(i = 0, j = this._data.core.selected.length; i < j; i++) { | |
3314 if(this._model.data[this._data.core.selected[i]]) { | |
3315 this._model.data[this._data.core.selected[i]].state.selected = true; | |
3316 } | |
3317 } | |
3318 this.redraw(true); | |
3319 /** | |
3320 * triggered when all nodes are selected | |
3321 * @event | |
3322 * @name select_all.jstree | |
3323 * @param {Array} selected the current selection | |
3324 */ | |
3325 this.trigger('select_all', { 'selected' : this._data.core.selected }); | |
3326 if(!supress_event) { | |
3327 this.trigger('changed', { 'action' : 'select_all', 'selected' : this._data.core.selected, 'old_selection' : tmp }); | |
3328 } | |
3329 }, | |
3330 /** | |
3331 * deselect all selected nodes | |
3332 * @name deselect_all([supress_event]) | |
3333 * @param {Boolean} supress_event if set to `true` the `changed.jstree` event won't be triggered | |
3334 * @trigger deselect_all.jstree, changed.jstree | |
3335 */ | |
3336 deselect_all : function (supress_event) { | |
3337 var tmp = this._data.core.selected.concat([]), i, j; | |
3338 for(i = 0, j = this._data.core.selected.length; i < j; i++) { | |
3339 if(this._model.data[this._data.core.selected[i]]) { | |
3340 this._model.data[this._data.core.selected[i]].state.selected = false; | |
3341 } | |
3342 } | |
3343 this._data.core.selected = []; | |
3344 this.element.find('.jstree-clicked').removeClass('jstree-clicked').parent().attr('aria-selected', false); | |
3345 /** | |
3346 * triggered when all nodes are deselected | |
3347 * @event | |
3348 * @name deselect_all.jstree | |
3349 * @param {Object} node the previous selection | |
3350 * @param {Array} selected the current selection | |
3351 */ | |
3352 this.trigger('deselect_all', { 'selected' : this._data.core.selected, 'node' : tmp }); | |
3353 if(!supress_event) { | |
3354 this.trigger('changed', { 'action' : 'deselect_all', 'selected' : this._data.core.selected, 'old_selection' : tmp }); | |
3355 } | |
3356 }, | |
3357 /** | |
3358 * checks if a node is selected | |
3359 * @name is_selected(obj) | |
3360 * @param {mixed} obj | |
3361 * @return {Boolean} | |
3362 */ | |
3363 is_selected : function (obj) { | |
3364 obj = this.get_node(obj); | |
3365 if(!obj || obj.id === $.jstree.root) { | |
3366 return false; | |
3367 } | |
3368 return obj.state.selected; | |
3369 }, | |
3370 /** | |
3371 * get an array of all selected nodes | |
3372 * @name get_selected([full]) | |
3373 * @param {mixed} full if set to `true` the returned array will consist of the full node objects, otherwise - only IDs will be returned | |
3374 * @return {Array} | |
3375 */ | |
3376 get_selected : function (full) { | |
3377 return full ? $.map(this._data.core.selected, $.proxy(function (i) { return this.get_node(i); }, this)) : this._data.core.selected.slice(); | |
3378 }, | |
3379 /** | |
3380 * get an array of all top level selected nodes (ignoring children of selected nodes) | |
3381 * @name get_top_selected([full]) | |
3382 * @param {mixed} full if set to `true` the returned array will consist of the full node objects, otherwise - only IDs will be returned | |
3383 * @return {Array} | |
3384 */ | |
3385 get_top_selected : function (full) { | |
3386 var tmp = this.get_selected(true), | |
3387 obj = {}, i, j, k, l; | |
3388 for(i = 0, j = tmp.length; i < j; i++) { | |
3389 obj[tmp[i].id] = tmp[i]; | |
3390 } | |
3391 for(i = 0, j = tmp.length; i < j; i++) { | |
3392 for(k = 0, l = tmp[i].children_d.length; k < l; k++) { | |
3393 if(obj[tmp[i].children_d[k]]) { | |
3394 delete obj[tmp[i].children_d[k]]; | |
3395 } | |
3396 } | |
3397 } | |
3398 tmp = []; | |
3399 for(i in obj) { | |
3400 if(obj.hasOwnProperty(i)) { | |
3401 tmp.push(i); | |
3402 } | |
3403 } | |
3404 return full ? $.map(tmp, $.proxy(function (i) { return this.get_node(i); }, this)) : tmp; | |
3405 }, | |
3406 /** | |
3407 * get an array of all bottom level selected nodes (ignoring selected parents) | |
3408 * @name get_bottom_selected([full]) | |
3409 * @param {mixed} full if set to `true` the returned array will consist of the full node objects, otherwise - only IDs will be returned | |
3410 * @return {Array} | |
3411 */ | |
3412 get_bottom_selected : function (full) { | |
3413 var tmp = this.get_selected(true), | |
3414 obj = [], i, j; | |
3415 for(i = 0, j = tmp.length; i < j; i++) { | |
3416 if(!tmp[i].children.length) { | |
3417 obj.push(tmp[i].id); | |
3418 } | |
3419 } | |
3420 return full ? $.map(obj, $.proxy(function (i) { return this.get_node(i); }, this)) : obj; | |
3421 }, | |
3422 /** | |
3423 * gets the current state of the tree so that it can be restored later with `set_state(state)`. Used internally. | |
3424 * @name get_state() | |
3425 * @private | |
3426 * @return {Object} | |
3427 */ | |
3428 get_state : function () { | |
3429 var state = { | |
3430 'core' : { | |
3431 'open' : [], | |
3432 'loaded' : [], | |
3433 'scroll' : { | |
3434 'left' : this.element.scrollLeft(), | |
3435 'top' : this.element.scrollTop() | |
3436 }, | |
3437 /*! | |
3438 'themes' : { | |
3439 'name' : this.get_theme(), | |
3440 'icons' : this._data.core.themes.icons, | |
3441 'dots' : this._data.core.themes.dots | |
3442 }, | |
3443 */ | |
3444 'selected' : [] | |
3445 } | |
3446 }, i; | |
3447 for(i in this._model.data) { | |
3448 if(this._model.data.hasOwnProperty(i)) { | |
3449 if(i !== $.jstree.root) { | |
3450 if(this._model.data[i].state.loaded && this.settings.core.loaded_state) { | |
3451 state.core.loaded.push(i); | |
3452 } | |
3453 if(this._model.data[i].state.opened) { | |
3454 state.core.open.push(i); | |
3455 } | |
3456 if(this._model.data[i].state.selected) { | |
3457 state.core.selected.push(i); | |
3458 } | |
3459 } | |
3460 } | |
3461 } | |
3462 return state; | |
3463 }, | |
3464 /** | |
3465 * sets the state of the tree. Used internally. | |
3466 * @name set_state(state [, callback]) | |
3467 * @private | |
3468 * @param {Object} state the state to restore. Keep in mind this object is passed by reference and jstree will modify it. | |
3469 * @param {Function} callback an optional function to execute once the state is restored. | |
3470 * @trigger set_state.jstree | |
3471 */ | |
3472 set_state : function (state, callback) { | |
3473 if(state) { | |
3474 if(state.core && state.core.selected && state.core.initial_selection === undefined) { | |
3475 state.core.initial_selection = this._data.core.selected.concat([]).sort().join(','); | |
3476 } | |
3477 if(state.core) { | |
3478 var res, n, t, _this, i; | |
3479 if(state.core.loaded) { | |
3480 if(!this.settings.core.loaded_state || !$.isArray(state.core.loaded) || !state.core.loaded.length) { | |
3481 delete state.core.loaded; | |
3482 this.set_state(state, callback); | |
3483 } | |
3484 else { | |
3485 this._load_nodes(state.core.loaded, function (nodes) { | |
3486 delete state.core.loaded; | |
3487 this.set_state(state, callback); | |
3488 }); | |
3489 } | |
3490 return false; | |
3491 } | |
3492 if(state.core.open) { | |
3493 if(!$.isArray(state.core.open) || !state.core.open.length) { | |
3494 delete state.core.open; | |
3495 this.set_state(state, callback); | |
3496 } | |
3497 else { | |
3498 this._load_nodes(state.core.open, function (nodes) { | |
3499 this.open_node(nodes, false, 0); | |
3500 delete state.core.open; | |
3501 this.set_state(state, callback); | |
3502 }); | |
3503 } | |
3504 return false; | |
3505 } | |
3506 if(state.core.scroll) { | |
3507 if(state.core.scroll && state.core.scroll.left !== undefined) { | |
3508 this.element.scrollLeft(state.core.scroll.left); | |
3509 } | |
3510 if(state.core.scroll && state.core.scroll.top !== undefined) { | |
3511 this.element.scrollTop(state.core.scroll.top); | |
3512 } | |
3513 delete state.core.scroll; | |
3514 this.set_state(state, callback); | |
3515 return false; | |
3516 } | |
3517 if(state.core.selected) { | |
3518 _this = this; | |
3519 if (state.core.initial_selection === undefined || | |
3520 state.core.initial_selection === this._data.core.selected.concat([]).sort().join(',') | |
3521 ) { | |
3522 this.deselect_all(); | |
3523 $.each(state.core.selected, function (i, v) { | |
3524 _this.select_node(v, false, true); | |
3525 }); | |
3526 } | |
3527 delete state.core.initial_selection; | |
3528 delete state.core.selected; | |
3529 this.set_state(state, callback); | |
3530 return false; | |
3531 } | |
3532 for(i in state) { | |
3533 if(state.hasOwnProperty(i) && i !== "core" && $.inArray(i, this.settings.plugins) === -1) { | |
3534 delete state[i]; | |
3535 } | |
3536 } | |
3537 if($.isEmptyObject(state.core)) { | |
3538 delete state.core; | |
3539 this.set_state(state, callback); | |
3540 return false; | |
3541 } | |
3542 } | |
3543 if($.isEmptyObject(state)) { | |
3544 state = null; | |
3545 if(callback) { callback.call(this); } | |
3546 /** | |
3547 * triggered when a `set_state` call completes | |
3548 * @event | |
3549 * @name set_state.jstree | |
3550 */ | |
3551 this.trigger('set_state'); | |
3552 return false; | |
3553 } | |
3554 return true; | |
3555 } | |
3556 return false; | |
3557 }, | |
3558 /** | |
3559 * refreshes the tree - all nodes are reloaded with calls to `load_node`. | |
3560 * @name refresh() | |
3561 * @param {Boolean} skip_loading an option to skip showing the loading indicator | |
3562 * @param {Mixed} forget_state if set to `true` state will not be reapplied, if set to a function (receiving the current state as argument) the result of that function will be used as state | |
3563 * @trigger refresh.jstree | |
3564 */ | |
3565 refresh : function (skip_loading, forget_state) { | |
3566 this._data.core.state = forget_state === true ? {} : this.get_state(); | |
3567 if(forget_state && $.isFunction(forget_state)) { this._data.core.state = forget_state.call(this, this._data.core.state); } | |
3568 this._cnt = 0; | |
3569 this._model.data = {}; | |
3570 this._model.data[$.jstree.root] = { | |
3571 id : $.jstree.root, | |
3572 parent : null, | |
3573 parents : [], | |
3574 children : [], | |
3575 children_d : [], | |
3576 state : { loaded : false } | |
3577 }; | |
3578 this._data.core.selected = []; | |
3579 this._data.core.last_clicked = null; | |
3580 this._data.core.focused = null; | |
3581 | |
3582 var c = this.get_container_ul()[0].className; | |
3583 if(!skip_loading) { | |
3584 this.element.html("<"+"ul class='"+c+"' role='group'><"+"li class='jstree-initial-node jstree-loading jstree-leaf jstree-last' role='treeitem' id='j"+this._id+"_loading'><i class='jstree-icon jstree-ocl'></i><"+"a class='jstree-anchor' href='#'><i class='jstree-icon jstree-themeicon-hidden'></i>" + this.get_string("Loading ...") + "</a></li></ul>"); | |
3585 this.element.attr('aria-activedescendant','j'+this._id+'_loading'); | |
3586 } | |
3587 this.load_node($.jstree.root, function (o, s) { | |
3588 if(s) { | |
3589 this.get_container_ul()[0].className = c; | |
3590 if(this._firstChild(this.get_container_ul()[0])) { | |
3591 this.element.attr('aria-activedescendant',this._firstChild(this.get_container_ul()[0]).id); | |
3592 } | |
3593 this.set_state($.extend(true, {}, this._data.core.state), function () { | |
3594 /** | |
3595 * triggered when a `refresh` call completes | |
3596 * @event | |
3597 * @name refresh.jstree | |
3598 */ | |
3599 this.trigger('refresh'); | |
3600 }); | |
3601 } | |
3602 this._data.core.state = null; | |
3603 }); | |
3604 }, | |
3605 /** | |
3606 * refreshes a node in the tree (reload its children) all opened nodes inside that node are reloaded with calls to `load_node`. | |
3607 * @name refresh_node(obj) | |
3608 * @param {mixed} obj the node | |
3609 * @trigger refresh_node.jstree | |
3610 */ | |
3611 refresh_node : function (obj) { | |
3612 obj = this.get_node(obj); | |
3613 if(!obj || obj.id === $.jstree.root) { return false; } | |
3614 var opened = [], to_load = [], s = this._data.core.selected.concat([]); | |
3615 to_load.push(obj.id); | |
3616 if(obj.state.opened === true) { opened.push(obj.id); } | |
3617 this.get_node(obj, true).find('.jstree-open').each(function() { to_load.push(this.id); opened.push(this.id); }); | |
3618 this._load_nodes(to_load, $.proxy(function (nodes) { | |
3619 this.open_node(opened, false, 0); | |
3620 this.select_node(s); | |
3621 /** | |
3622 * triggered when a node is refreshed | |
3623 * @event | |
3624 * @name refresh_node.jstree | |
3625 * @param {Object} node - the refreshed node | |
3626 * @param {Array} nodes - an array of the IDs of the nodes that were reloaded | |
3627 */ | |
3628 this.trigger('refresh_node', { 'node' : obj, 'nodes' : nodes }); | |
3629 }, this), false, true); | |
3630 }, | |
3631 /** | |
3632 * set (change) the ID of a node | |
3633 * @name set_id(obj, id) | |
3634 * @param {mixed} obj the node | |
3635 * @param {String} id the new ID | |
3636 * @return {Boolean} | |
3637 * @trigger set_id.jstree | |
3638 */ | |
3639 set_id : function (obj, id) { | |
3640 obj = this.get_node(obj); | |
3641 if(!obj || obj.id === $.jstree.root) { return false; } | |
3642 var i, j, m = this._model.data, old = obj.id; | |
3643 id = id.toString(); | |
3644 // update parents (replace current ID with new one in children and children_d) | |
3645 m[obj.parent].children[$.inArray(obj.id, m[obj.parent].children)] = id; | |
3646 for(i = 0, j = obj.parents.length; i < j; i++) { | |
3647 m[obj.parents[i]].children_d[$.inArray(obj.id, m[obj.parents[i]].children_d)] = id; | |
3648 } | |
3649 // update children (replace current ID with new one in parent and parents) | |
3650 for(i = 0, j = obj.children.length; i < j; i++) { | |
3651 m[obj.children[i]].parent = id; | |
3652 } | |
3653 for(i = 0, j = obj.children_d.length; i < j; i++) { | |
3654 m[obj.children_d[i]].parents[$.inArray(obj.id, m[obj.children_d[i]].parents)] = id; | |
3655 } | |
3656 i = $.inArray(obj.id, this._data.core.selected); | |
3657 if(i !== -1) { this._data.core.selected[i] = id; } | |
3658 // update model and obj itself (obj.id, this._model.data[KEY]) | |
3659 i = this.get_node(obj.id, true); | |
3660 if(i) { | |
3661 i.attr('id', id); //.children('.jstree-anchor').attr('id', id + '_anchor').end().attr('aria-labelledby', id + '_anchor'); | |
3662 if(this.element.attr('aria-activedescendant') === obj.id) { | |
3663 this.element.attr('aria-activedescendant', id); | |
3664 } | |
3665 } | |
3666 delete m[obj.id]; | |
3667 obj.id = id; | |
3668 obj.li_attr.id = id; | |
3669 m[id] = obj; | |
3670 /** | |
3671 * triggered when a node id value is changed | |
3672 * @event | |
3673 * @name set_id.jstree | |
3674 * @param {Object} node | |
3675 * @param {String} old the old id | |
3676 */ | |
3677 this.trigger('set_id',{ "node" : obj, "new" : obj.id, "old" : old }); | |
3678 return true; | |
3679 }, | |
3680 /** | |
3681 * get the text value of a node | |
3682 * @name get_text(obj) | |
3683 * @param {mixed} obj the node | |
3684 * @return {String} | |
3685 */ | |
3686 get_text : function (obj) { | |
3687 obj = this.get_node(obj); | |
3688 return (!obj || obj.id === $.jstree.root) ? false : obj.text; | |
3689 }, | |
3690 /** | |
3691 * set the text value of a node. Used internally, please use `rename_node(obj, val)`. | |
3692 * @private | |
3693 * @name set_text(obj, val) | |
3694 * @param {mixed} obj the node, you can pass an array to set the text on multiple nodes | |
3695 * @param {String} val the new text value | |
3696 * @return {Boolean} | |
3697 * @trigger set_text.jstree | |
3698 */ | |
3699 set_text : function (obj, val) { | |
3700 var t1, t2; | |
3701 if($.isArray(obj)) { | |
3702 obj = obj.slice(); | |
3703 for(t1 = 0, t2 = obj.length; t1 < t2; t1++) { | |
3704 this.set_text(obj[t1], val); | |
3705 } | |
3706 return true; | |
3707 } | |
3708 obj = this.get_node(obj); | |
3709 if(!obj || obj.id === $.jstree.root) { return false; } | |
3710 obj.text = val; | |
3711 if(this.get_node(obj, true).length) { | |
3712 this.redraw_node(obj.id); | |
3713 } | |
3714 /** | |
3715 * triggered when a node text value is changed | |
3716 * @event | |
3717 * @name set_text.jstree | |
3718 * @param {Object} obj | |
3719 * @param {String} text the new value | |
3720 */ | |
3721 this.trigger('set_text',{ "obj" : obj, "text" : val }); | |
3722 return true; | |
3723 }, | |
3724 /** | |
3725 * gets a JSON representation of a node (or the whole tree) | |
3726 * @name get_json([obj, options]) | |
3727 * @param {mixed} obj | |
3728 * @param {Object} options | |
3729 * @param {Boolean} options.no_state do not return state information | |
3730 * @param {Boolean} options.no_id do not return ID | |
3731 * @param {Boolean} options.no_children do not include children | |
3732 * @param {Boolean} options.no_data do not include node data | |
3733 * @param {Boolean} options.no_li_attr do not include LI attributes | |
3734 * @param {Boolean} options.no_a_attr do not include A attributes | |
3735 * @param {Boolean} options.flat return flat JSON instead of nested | |
3736 * @return {Object} | |
3737 */ | |
3738 get_json : function (obj, options, flat) { | |
3739 obj = this.get_node(obj || $.jstree.root); | |
3740 if(!obj) { return false; } | |
3741 if(options && options.flat && !flat) { flat = []; } | |
3742 var tmp = { | |
3743 'id' : obj.id, | |
3744 'text' : obj.text, | |
3745 'icon' : this.get_icon(obj), | |
3746 'li_attr' : $.extend(true, {}, obj.li_attr), | |
3747 'a_attr' : $.extend(true, {}, obj.a_attr), | |
3748 'state' : {}, | |
3749 'data' : options && options.no_data ? false : $.extend(true, $.isArray(obj.data)?[]:{}, obj.data) | |
3750 //( this.get_node(obj, true).length ? this.get_node(obj, true).data() : obj.data ), | |
3751 }, i, j; | |
3752 if(options && options.flat) { | |
3753 tmp.parent = obj.parent; | |
3754 } | |
3755 else { | |
3756 tmp.children = []; | |
3757 } | |
3758 if(!options || !options.no_state) { | |
3759 for(i in obj.state) { | |
3760 if(obj.state.hasOwnProperty(i)) { | |
3761 tmp.state[i] = obj.state[i]; | |
3762 } | |
3763 } | |
3764 } else { | |
3765 delete tmp.state; | |
3766 } | |
3767 if(options && options.no_li_attr) { | |
3768 delete tmp.li_attr; | |
3769 } | |
3770 if(options && options.no_a_attr) { | |
3771 delete tmp.a_attr; | |
3772 } | |
3773 if(options && options.no_id) { | |
3774 delete tmp.id; | |
3775 if(tmp.li_attr && tmp.li_attr.id) { | |
3776 delete tmp.li_attr.id; | |
3777 } | |
3778 if(tmp.a_attr && tmp.a_attr.id) { | |
3779 delete tmp.a_attr.id; | |
3780 } | |
3781 } | |
3782 if(options && options.flat && obj.id !== $.jstree.root) { | |
3783 flat.push(tmp); | |
3784 } | |
3785 if(!options || !options.no_children) { | |
3786 for(i = 0, j = obj.children.length; i < j; i++) { | |
3787 if(options && options.flat) { | |
3788 this.get_json(obj.children[i], options, flat); | |
3789 } | |
3790 else { | |
3791 tmp.children.push(this.get_json(obj.children[i], options)); | |
3792 } | |
3793 } | |
3794 } | |
3795 return options && options.flat ? flat : (obj.id === $.jstree.root ? tmp.children : tmp); | |
3796 }, | |
3797 /** | |
3798 * create a new node (do not confuse with load_node) | |
3799 * @name create_node([par, node, pos, callback, is_loaded]) | |
3800 * @param {mixed} par the parent node (to create a root node use either "#" (string) or `null`) | |
3801 * @param {mixed} node the data for the new node (a valid JSON object, or a simple string with the name) | |
3802 * @param {mixed} pos the index at which to insert the node, "first" and "last" are also supported, default is "last" | |
3803 * @param {Function} callback a function to be called once the node is created | |
3804 * @param {Boolean} is_loaded internal argument indicating if the parent node was succesfully loaded | |
3805 * @return {String} the ID of the newly create node | |
3806 * @trigger model.jstree, create_node.jstree | |
3807 */ | |
3808 create_node : function (par, node, pos, callback, is_loaded) { | |
3809 if(par === null) { par = $.jstree.root; } | |
3810 par = this.get_node(par); | |
3811 if(!par) { return false; } | |
3812 pos = pos === undefined ? "last" : pos; | |
3813 if(!pos.toString().match(/^(before|after)$/) && !is_loaded && !this.is_loaded(par)) { | |
3814 return this.load_node(par, function () { this.create_node(par, node, pos, callback, true); }); | |
3815 } | |
3816 if(!node) { node = { "text" : this.get_string('New node') }; } | |
3817 if(typeof node === "string") { | |
3818 node = { "text" : node }; | |
3819 } else { | |
3820 node = $.extend(true, {}, node); | |
3821 } | |
3822 if(node.text === undefined) { node.text = this.get_string('New node'); } | |
3823 var tmp, dpc, i, j; | |
3824 | |
3825 if(par.id === $.jstree.root) { | |
3826 if(pos === "before") { pos = "first"; } | |
3827 if(pos === "after") { pos = "last"; } | |
3828 } | |
3829 switch(pos) { | |
3830 case "before": | |
3831 tmp = this.get_node(par.parent); | |
3832 pos = $.inArray(par.id, tmp.children); | |
3833 par = tmp; | |
3834 break; | |
3835 case "after" : | |
3836 tmp = this.get_node(par.parent); | |
3837 pos = $.inArray(par.id, tmp.children) + 1; | |
3838 par = tmp; | |
3839 break; | |
3840 case "inside": | |
3841 case "first": | |
3842 pos = 0; | |
3843 break; | |
3844 case "last": | |
3845 pos = par.children.length; | |
3846 break; | |
3847 default: | |
3848 if(!pos) { pos = 0; } | |
3849 break; | |
3850 } | |
3851 if(pos > par.children.length) { pos = par.children.length; } | |
3852 if(!node.id) { node.id = true; } | |
3853 if(!this.check("create_node", node, par, pos)) { | |
3854 this.settings.core.error.call(this, this._data.core.last_error); | |
3855 return false; | |
3856 } | |
3857 if(node.id === true) { delete node.id; } | |
3858 node = this._parse_model_from_json(node, par.id, par.parents.concat()); | |
3859 if(!node) { return false; } | |
3860 tmp = this.get_node(node); | |
3861 dpc = []; | |
3862 dpc.push(node); | |
3863 dpc = dpc.concat(tmp.children_d); | |
3864 this.trigger('model', { "nodes" : dpc, "parent" : par.id }); | |
3865 | |
3866 par.children_d = par.children_d.concat(dpc); | |
3867 for(i = 0, j = par.parents.length; i < j; i++) { | |
3868 this._model.data[par.parents[i]].children_d = this._model.data[par.parents[i]].children_d.concat(dpc); | |
3869 } | |
3870 node = tmp; | |
3871 tmp = []; | |
3872 for(i = 0, j = par.children.length; i < j; i++) { | |
3873 tmp[i >= pos ? i+1 : i] = par.children[i]; | |
3874 } | |
3875 tmp[pos] = node.id; | |
3876 par.children = tmp; | |
3877 | |
3878 this.redraw_node(par, true); | |
3879 /** | |
3880 * triggered when a node is created | |
3881 * @event | |
3882 * @name create_node.jstree | |
3883 * @param {Object} node | |
3884 * @param {String} parent the parent's ID | |
3885 * @param {Number} position the position of the new node among the parent's children | |
3886 */ | |
3887 this.trigger('create_node', { "node" : this.get_node(node), "parent" : par.id, "position" : pos }); | |
3888 if(callback) { callback.call(this, this.get_node(node)); } | |
3889 return node.id; | |
3890 }, | |
3891 /** | |
3892 * set the text value of a node | |
3893 * @name rename_node(obj, val) | |
3894 * @param {mixed} obj the node, you can pass an array to rename multiple nodes to the same name | |
3895 * @param {String} val the new text value | |
3896 * @return {Boolean} | |
3897 * @trigger rename_node.jstree | |
3898 */ | |
3899 rename_node : function (obj, val) { | |
3900 var t1, t2, old; | |
3901 if($.isArray(obj)) { | |
3902 obj = obj.slice(); | |
3903 for(t1 = 0, t2 = obj.length; t1 < t2; t1++) { | |
3904 this.rename_node(obj[t1], val); | |
3905 } | |
3906 return true; | |
3907 } | |
3908 obj = this.get_node(obj); | |
3909 if(!obj || obj.id === $.jstree.root) { return false; } | |
3910 old = obj.text; | |
3911 if(!this.check("rename_node", obj, this.get_parent(obj), val)) { | |
3912 this.settings.core.error.call(this, this._data.core.last_error); | |
3913 return false; | |
3914 } | |
3915 this.set_text(obj, val); // .apply(this, Array.prototype.slice.call(arguments)) | |
3916 /** | |
3917 * triggered when a node is renamed | |
3918 * @event | |
3919 * @name rename_node.jstree | |
3920 * @param {Object} node | |
3921 * @param {String} text the new value | |
3922 * @param {String} old the old value | |
3923 */ | |
3924 this.trigger('rename_node', { "node" : obj, "text" : val, "old" : old }); | |
3925 return true; | |
3926 }, | |
3927 /** | |
3928 * remove a node | |
3929 * @name delete_node(obj) | |
3930 * @param {mixed} obj the node, you can pass an array to delete multiple nodes | |
3931 * @return {Boolean} | |
3932 * @trigger delete_node.jstree, changed.jstree | |
3933 */ | |
3934 delete_node : function (obj) { | |
3935 var t1, t2, par, pos, tmp, i, j, k, l, c, top, lft; | |
3936 if($.isArray(obj)) { | |
3937 obj = obj.slice(); | |
3938 for(t1 = 0, t2 = obj.length; t1 < t2; t1++) { | |
3939 this.delete_node(obj[t1]); | |
3940 } | |
3941 return true; | |
3942 } | |
3943 obj = this.get_node(obj); | |
3944 if(!obj || obj.id === $.jstree.root) { return false; } | |
3945 par = this.get_node(obj.parent); | |
3946 pos = $.inArray(obj.id, par.children); | |
3947 c = false; | |
3948 if(!this.check("delete_node", obj, par, pos)) { | |
3949 this.settings.core.error.call(this, this._data.core.last_error); | |
3950 return false; | |
3951 } | |
3952 if(pos !== -1) { | |
3953 par.children = $.vakata.array_remove(par.children, pos); | |
3954 } | |
3955 tmp = obj.children_d.concat([]); | |
3956 tmp.push(obj.id); | |
3957 for(i = 0, j = obj.parents.length; i < j; i++) { | |
3958 this._model.data[obj.parents[i]].children_d = $.vakata.array_filter(this._model.data[obj.parents[i]].children_d, function (v) { | |
3959 return $.inArray(v, tmp) === -1; | |
3960 }); | |
3961 } | |
3962 for(k = 0, l = tmp.length; k < l; k++) { | |
3963 if(this._model.data[tmp[k]].state.selected) { | |
3964 c = true; | |
3965 break; | |
3966 } | |
3967 } | |
3968 if (c) { | |
3969 this._data.core.selected = $.vakata.array_filter(this._data.core.selected, function (v) { | |
3970 return $.inArray(v, tmp) === -1; | |
3971 }); | |
3972 } | |
3973 /** | |
3974 * triggered when a node is deleted | |
3975 * @event | |
3976 * @name delete_node.jstree | |
3977 * @param {Object} node | |
3978 * @param {String} parent the parent's ID | |
3979 */ | |
3980 this.trigger('delete_node', { "node" : obj, "parent" : par.id }); | |
3981 if(c) { | |
3982 this.trigger('changed', { 'action' : 'delete_node', 'node' : obj, 'selected' : this._data.core.selected, 'parent' : par.id }); | |
3983 } | |
3984 for(k = 0, l = tmp.length; k < l; k++) { | |
3985 delete this._model.data[tmp[k]]; | |
3986 } | |
3987 if($.inArray(this._data.core.focused, tmp) !== -1) { | |
3988 this._data.core.focused = null; | |
3989 top = this.element[0].scrollTop; | |
3990 lft = this.element[0].scrollLeft; | |
3991 if(par.id === $.jstree.root) { | |
3992 if (this._model.data[$.jstree.root].children[0]) { | |
3993 this.get_node(this._model.data[$.jstree.root].children[0], true).children('.jstree-anchor').focus(); | |
3994 } | |
3995 } | |
3996 else { | |
3997 this.get_node(par, true).children('.jstree-anchor').focus(); | |
3998 } | |
3999 this.element[0].scrollTop = top; | |
4000 this.element[0].scrollLeft = lft; | |
4001 } | |
4002 this.redraw_node(par, true); | |
4003 return true; | |
4004 }, | |
4005 /** | |
4006 * check if an operation is premitted on the tree. Used internally. | |
4007 * @private | |
4008 * @name check(chk, obj, par, pos) | |
4009 * @param {String} chk the operation to check, can be "create_node", "rename_node", "delete_node", "copy_node" or "move_node" | |
4010 * @param {mixed} obj the node | |
4011 * @param {mixed} par the parent | |
4012 * @param {mixed} pos the position to insert at, or if "rename_node" - the new name | |
4013 * @param {mixed} more some various additional information, for example if a "move_node" operations is triggered by DND this will be the hovered node | |
4014 * @return {Boolean} | |
4015 */ | |
4016 check : function (chk, obj, par, pos, more) { | |
4017 obj = obj && obj.id ? obj : this.get_node(obj); | |
4018 par = par && par.id ? par : this.get_node(par); | |
4019 var tmp = chk.match(/^move_node|copy_node|create_node$/i) ? par : obj, | |
4020 chc = this.settings.core.check_callback; | |
4021 if(chk === "move_node" || chk === "copy_node") { | |
4022 if((!more || !more.is_multi) && (obj.id === par.id || (chk === "move_node" && $.inArray(obj.id, par.children) === pos) || $.inArray(par.id, obj.children_d) !== -1)) { | |
4023 this._data.core.last_error = { 'error' : 'check', 'plugin' : 'core', 'id' : 'core_01', 'reason' : 'Moving parent inside child', 'data' : JSON.stringify({ 'chk' : chk, 'pos' : pos, 'obj' : obj && obj.id ? obj.id : false, 'par' : par && par.id ? par.id : false }) }; | |
4024 return false; | |
4025 } | |
4026 } | |
4027 if(tmp && tmp.data) { tmp = tmp.data; } | |
4028 if(tmp && tmp.functions && (tmp.functions[chk] === false || tmp.functions[chk] === true)) { | |
4029 if(tmp.functions[chk] === false) { | |
4030 this._data.core.last_error = { 'error' : 'check', 'plugin' : 'core', 'id' : 'core_02', 'reason' : 'Node data prevents function: ' + chk, 'data' : JSON.stringify({ 'chk' : chk, 'pos' : pos, 'obj' : obj && obj.id ? obj.id : false, 'par' : par && par.id ? par.id : false }) }; | |
4031 } | |
4032 return tmp.functions[chk]; | |
4033 } | |
4034 if(chc === false || ($.isFunction(chc) && chc.call(this, chk, obj, par, pos, more) === false) || (chc && chc[chk] === false)) { | |
4035 this._data.core.last_error = { 'error' : 'check', 'plugin' : 'core', 'id' : 'core_03', 'reason' : 'User config for core.check_callback prevents function: ' + chk, 'data' : JSON.stringify({ 'chk' : chk, 'pos' : pos, 'obj' : obj && obj.id ? obj.id : false, 'par' : par && par.id ? par.id : false }) }; | |
4036 return false; | |
4037 } | |
4038 return true; | |
4039 }, | |
4040 /** | |
4041 * get the last error | |
4042 * @name last_error() | |
4043 * @return {Object} | |
4044 */ | |
4045 last_error : function () { | |
4046 return this._data.core.last_error; | |
4047 }, | |
4048 /** | |
4049 * move a node to a new parent | |
4050 * @name move_node(obj, par [, pos, callback, is_loaded]) | |
4051 * @param {mixed} obj the node to move, pass an array to move multiple nodes | |
4052 * @param {mixed} par the new parent | |
4053 * @param {mixed} pos the position to insert at (besides integer values, "first" and "last" are supported, as well as "before" and "after"), defaults to integer `0` | |
4054 * @param {function} callback a function to call once the move is completed, receives 3 arguments - the node, the new parent and the position | |
4055 * @param {Boolean} is_loaded internal parameter indicating if the parent node has been loaded | |
4056 * @param {Boolean} skip_redraw internal parameter indicating if the tree should be redrawn | |
4057 * @param {Boolean} instance internal parameter indicating if the node comes from another instance | |
4058 * @trigger move_node.jstree | |
4059 */ | |
4060 move_node : function (obj, par, pos, callback, is_loaded, skip_redraw, origin) { | |
4061 var t1, t2, old_par, old_pos, new_par, old_ins, is_multi, dpc, tmp, i, j, k, l, p; | |
4062 | |
4063 par = this.get_node(par); | |
4064 pos = pos === undefined ? 0 : pos; | |
4065 if(!par) { return false; } | |
4066 if(!pos.toString().match(/^(before|after)$/) && !is_loaded && !this.is_loaded(par)) { | |
4067 return this.load_node(par, function () { this.move_node(obj, par, pos, callback, true, false, origin); }); | |
4068 } | |
4069 | |
4070 if($.isArray(obj)) { | |
4071 if(obj.length === 1) { | |
4072 obj = obj[0]; | |
4073 } | |
4074 else { | |
4075 //obj = obj.slice(); | |
4076 for(t1 = 0, t2 = obj.length; t1 < t2; t1++) { | |
4077 if((tmp = this.move_node(obj[t1], par, pos, callback, is_loaded, false, origin))) { | |
4078 par = tmp; | |
4079 pos = "after"; | |
4080 } | |
4081 } | |
4082 this.redraw(); | |
4083 return true; | |
4084 } | |
4085 } | |
4086 obj = obj && obj.id ? obj : this.get_node(obj); | |
4087 | |
4088 if(!obj || obj.id === $.jstree.root) { return false; } | |
4089 | |
4090 old_par = (obj.parent || $.jstree.root).toString(); | |
4091 new_par = (!pos.toString().match(/^(before|after)$/) || par.id === $.jstree.root) ? par : this.get_node(par.parent); | |
4092 old_ins = origin ? origin : (this._model.data[obj.id] ? this : $.jstree.reference(obj.id)); | |
4093 is_multi = !old_ins || !old_ins._id || (this._id !== old_ins._id); | |
4094 old_pos = old_ins && old_ins._id && old_par && old_ins._model.data[old_par] && old_ins._model.data[old_par].children ? $.inArray(obj.id, old_ins._model.data[old_par].children) : -1; | |
4095 if(old_ins && old_ins._id) { | |
4096 obj = old_ins._model.data[obj.id]; | |
4097 } | |
4098 | |
4099 if(is_multi) { | |
4100 if((tmp = this.copy_node(obj, par, pos, callback, is_loaded, false, origin))) { | |
4101 if(old_ins) { old_ins.delete_node(obj); } | |
4102 return tmp; | |
4103 } | |
4104 return false; | |
4105 } | |
4106 //var m = this._model.data; | |
4107 if(par.id === $.jstree.root) { | |
4108 if(pos === "before") { pos = "first"; } | |
4109 if(pos === "after") { pos = "last"; } | |
4110 } | |
4111 switch(pos) { | |
4112 case "before": | |
4113 pos = $.inArray(par.id, new_par.children); | |
4114 break; | |
4115 case "after" : | |
4116 pos = $.inArray(par.id, new_par.children) + 1; | |
4117 break; | |
4118 case "inside": | |
4119 case "first": | |
4120 pos = 0; | |
4121 break; | |
4122 case "last": | |
4123 pos = new_par.children.length; | |
4124 break; | |
4125 default: | |
4126 if(!pos) { pos = 0; } | |
4127 break; | |
4128 } | |
4129 if(pos > new_par.children.length) { pos = new_par.children.length; } | |
4130 if(!this.check("move_node", obj, new_par, pos, { 'core' : true, 'origin' : origin, 'is_multi' : (old_ins && old_ins._id && old_ins._id !== this._id), 'is_foreign' : (!old_ins || !old_ins._id) })) { | |
4131 this.settings.core.error.call(this, this._data.core.last_error); | |
4132 return false; | |
4133 } | |
4134 if(obj.parent === new_par.id) { | |
4135 dpc = new_par.children.concat(); | |
4136 tmp = $.inArray(obj.id, dpc); | |
4137 if(tmp !== -1) { | |
4138 dpc = $.vakata.array_remove(dpc, tmp); | |
4139 if(pos > tmp) { pos--; } | |
4140 } | |
4141 tmp = []; | |
4142 for(i = 0, j = dpc.length; i < j; i++) { | |
4143 tmp[i >= pos ? i+1 : i] = dpc[i]; | |
4144 } | |
4145 tmp[pos] = obj.id; | |
4146 new_par.children = tmp; | |
4147 this._node_changed(new_par.id); | |
4148 this.redraw(new_par.id === $.jstree.root); | |
4149 } | |
4150 else { | |
4151 // clean old parent and up | |
4152 tmp = obj.children_d.concat(); | |
4153 tmp.push(obj.id); | |
4154 for(i = 0, j = obj.parents.length; i < j; i++) { | |
4155 dpc = []; | |
4156 p = old_ins._model.data[obj.parents[i]].children_d; | |
4157 for(k = 0, l = p.length; k < l; k++) { | |
4158 if($.inArray(p[k], tmp) === -1) { | |
4159 dpc.push(p[k]); | |
4160 } | |
4161 } | |
4162 old_ins._model.data[obj.parents[i]].children_d = dpc; | |
4163 } | |
4164 old_ins._model.data[old_par].children = $.vakata.array_remove_item(old_ins._model.data[old_par].children, obj.id); | |
4165 | |
4166 // insert into new parent and up | |
4167 for(i = 0, j = new_par.parents.length; i < j; i++) { | |
4168 this._model.data[new_par.parents[i]].children_d = this._model.data[new_par.parents[i]].children_d.concat(tmp); | |
4169 } | |
4170 dpc = []; | |
4171 for(i = 0, j = new_par.children.length; i < j; i++) { | |
4172 dpc[i >= pos ? i+1 : i] = new_par.children[i]; | |
4173 } | |
4174 dpc[pos] = obj.id; | |
4175 new_par.children = dpc; | |
4176 new_par.children_d.push(obj.id); | |
4177 new_par.children_d = new_par.children_d.concat(obj.children_d); | |
4178 | |
4179 // update object | |
4180 obj.parent = new_par.id; | |
4181 tmp = new_par.parents.concat(); | |
4182 tmp.unshift(new_par.id); | |
4183 p = obj.parents.length; | |
4184 obj.parents = tmp; | |
4185 | |
4186 // update object children | |
4187 tmp = tmp.concat(); | |
4188 for(i = 0, j = obj.children_d.length; i < j; i++) { | |
4189 this._model.data[obj.children_d[i]].parents = this._model.data[obj.children_d[i]].parents.slice(0,p*-1); | |
4190 Array.prototype.push.apply(this._model.data[obj.children_d[i]].parents, tmp); | |
4191 } | |
4192 | |
4193 if(old_par === $.jstree.root || new_par.id === $.jstree.root) { | |
4194 this._model.force_full_redraw = true; | |
4195 } | |
4196 if(!this._model.force_full_redraw) { | |
4197 this._node_changed(old_par); | |
4198 this._node_changed(new_par.id); | |
4199 } | |
4200 if(!skip_redraw) { | |
4201 this.redraw(); | |
4202 } | |
4203 } | |
4204 if(callback) { callback.call(this, obj, new_par, pos); } | |
4205 /** | |
4206 * triggered when a node is moved | |
4207 * @event | |
4208 * @name move_node.jstree | |
4209 * @param {Object} node | |
4210 * @param {String} parent the parent's ID | |
4211 * @param {Number} position the position of the node among the parent's children | |
4212 * @param {String} old_parent the old parent of the node | |
4213 * @param {Number} old_position the old position of the node | |
4214 * @param {Boolean} is_multi do the node and new parent belong to different instances | |
4215 * @param {jsTree} old_instance the instance the node came from | |
4216 * @param {jsTree} new_instance the instance of the new parent | |
4217 */ | |
4218 this.trigger('move_node', { "node" : obj, "parent" : new_par.id, "position" : pos, "old_parent" : old_par, "old_position" : old_pos, 'is_multi' : (old_ins && old_ins._id && old_ins._id !== this._id), 'is_foreign' : (!old_ins || !old_ins._id), 'old_instance' : old_ins, 'new_instance' : this }); | |
4219 return obj.id; | |
4220 }, | |
4221 /** | |
4222 * copy a node to a new parent | |
4223 * @name copy_node(obj, par [, pos, callback, is_loaded]) | |
4224 * @param {mixed} obj the node to copy, pass an array to copy multiple nodes | |
4225 * @param {mixed} par the new parent | |
4226 * @param {mixed} pos the position to insert at (besides integer values, "first" and "last" are supported, as well as "before" and "after"), defaults to integer `0` | |
4227 * @param {function} callback a function to call once the move is completed, receives 3 arguments - the node, the new parent and the position | |
4228 * @param {Boolean} is_loaded internal parameter indicating if the parent node has been loaded | |
4229 * @param {Boolean} skip_redraw internal parameter indicating if the tree should be redrawn | |
4230 * @param {Boolean} instance internal parameter indicating if the node comes from another instance | |
4231 * @trigger model.jstree copy_node.jstree | |
4232 */ | |
4233 copy_node : function (obj, par, pos, callback, is_loaded, skip_redraw, origin) { | |
4234 var t1, t2, dpc, tmp, i, j, node, old_par, new_par, old_ins, is_multi; | |
4235 | |
4236 par = this.get_node(par); | |
4237 pos = pos === undefined ? 0 : pos; | |
4238 if(!par) { return false; } | |
4239 if(!pos.toString().match(/^(before|after)$/) && !is_loaded && !this.is_loaded(par)) { | |
4240 return this.load_node(par, function () { this.copy_node(obj, par, pos, callback, true, false, origin); }); | |
4241 } | |
4242 | |
4243 if($.isArray(obj)) { | |
4244 if(obj.length === 1) { | |
4245 obj = obj[0]; | |
4246 } | |
4247 else { | |
4248 //obj = obj.slice(); | |
4249 for(t1 = 0, t2 = obj.length; t1 < t2; t1++) { | |
4250 if((tmp = this.copy_node(obj[t1], par, pos, callback, is_loaded, true, origin))) { | |
4251 par = tmp; | |
4252 pos = "after"; | |
4253 } | |
4254 } | |
4255 this.redraw(); | |
4256 return true; | |
4257 } | |
4258 } | |
4259 obj = obj && obj.id ? obj : this.get_node(obj); | |
4260 if(!obj || obj.id === $.jstree.root) { return false; } | |
4261 | |
4262 old_par = (obj.parent || $.jstree.root).toString(); | |
4263 new_par = (!pos.toString().match(/^(before|after)$/) || par.id === $.jstree.root) ? par : this.get_node(par.parent); | |
4264 old_ins = origin ? origin : (this._model.data[obj.id] ? this : $.jstree.reference(obj.id)); | |
4265 is_multi = !old_ins || !old_ins._id || (this._id !== old_ins._id); | |
4266 | |
4267 if(old_ins && old_ins._id) { | |
4268 obj = old_ins._model.data[obj.id]; | |
4269 } | |
4270 | |
4271 if(par.id === $.jstree.root) { | |
4272 if(pos === "before") { pos = "first"; } | |
4273 if(pos === "after") { pos = "last"; } | |
4274 } | |
4275 switch(pos) { | |
4276 case "before": | |
4277 pos = $.inArray(par.id, new_par.children); | |
4278 break; | |
4279 case "after" : | |
4280 pos = $.inArray(par.id, new_par.children) + 1; | |
4281 break; | |
4282 case "inside": | |
4283 case "first": | |
4284 pos = 0; | |
4285 break; | |
4286 case "last": | |
4287 pos = new_par.children.length; | |
4288 break; | |
4289 default: | |
4290 if(!pos) { pos = 0; } | |
4291 break; | |
4292 } | |
4293 if(pos > new_par.children.length) { pos = new_par.children.length; } | |
4294 if(!this.check("copy_node", obj, new_par, pos, { 'core' : true, 'origin' : origin, 'is_multi' : (old_ins && old_ins._id && old_ins._id !== this._id), 'is_foreign' : (!old_ins || !old_ins._id) })) { | |
4295 this.settings.core.error.call(this, this._data.core.last_error); | |
4296 return false; | |
4297 } | |
4298 node = old_ins ? old_ins.get_json(obj, { no_id : true, no_data : true, no_state : true }) : obj; | |
4299 if(!node) { return false; } | |
4300 if(node.id === true) { delete node.id; } | |
4301 node = this._parse_model_from_json(node, new_par.id, new_par.parents.concat()); | |
4302 if(!node) { return false; } | |
4303 tmp = this.get_node(node); | |
4304 if(obj && obj.state && obj.state.loaded === false) { tmp.state.loaded = false; } | |
4305 dpc = []; | |
4306 dpc.push(node); | |
4307 dpc = dpc.concat(tmp.children_d); | |
4308 this.trigger('model', { "nodes" : dpc, "parent" : new_par.id }); | |
4309 | |
4310 // insert into new parent and up | |
4311 for(i = 0, j = new_par.parents.length; i < j; i++) { | |
4312 this._model.data[new_par.parents[i]].children_d = this._model.data[new_par.parents[i]].children_d.concat(dpc); | |
4313 } | |
4314 dpc = []; | |
4315 for(i = 0, j = new_par.children.length; i < j; i++) { | |
4316 dpc[i >= pos ? i+1 : i] = new_par.children[i]; | |
4317 } | |
4318 dpc[pos] = tmp.id; | |
4319 new_par.children = dpc; | |
4320 new_par.children_d.push(tmp.id); | |
4321 new_par.children_d = new_par.children_d.concat(tmp.children_d); | |
4322 | |
4323 if(new_par.id === $.jstree.root) { | |
4324 this._model.force_full_redraw = true; | |
4325 } | |
4326 if(!this._model.force_full_redraw) { | |
4327 this._node_changed(new_par.id); | |
4328 } | |
4329 if(!skip_redraw) { | |
4330 this.redraw(new_par.id === $.jstree.root); | |
4331 } | |
4332 if(callback) { callback.call(this, tmp, new_par, pos); } | |
4333 /** | |
4334 * triggered when a node is copied | |
4335 * @event | |
4336 * @name copy_node.jstree | |
4337 * @param {Object} node the copied node | |
4338 * @param {Object} original the original node | |
4339 * @param {String} parent the parent's ID | |
4340 * @param {Number} position the position of the node among the parent's children | |
4341 * @param {String} old_parent the old parent of the node | |
4342 * @param {Number} old_position the position of the original node | |
4343 * @param {Boolean} is_multi do the node and new parent belong to different instances | |
4344 * @param {jsTree} old_instance the instance the node came from | |
4345 * @param {jsTree} new_instance the instance of the new parent | |
4346 */ | |
4347 this.trigger('copy_node', { "node" : tmp, "original" : obj, "parent" : new_par.id, "position" : pos, "old_parent" : old_par, "old_position" : old_ins && old_ins._id && old_par && old_ins._model.data[old_par] && old_ins._model.data[old_par].children ? $.inArray(obj.id, old_ins._model.data[old_par].children) : -1,'is_multi' : (old_ins && old_ins._id && old_ins._id !== this._id), 'is_foreign' : (!old_ins || !old_ins._id), 'old_instance' : old_ins, 'new_instance' : this }); | |
4348 return tmp.id; | |
4349 }, | |
4350 /** | |
4351 * cut a node (a later call to `paste(obj)` would move the node) | |
4352 * @name cut(obj) | |
4353 * @param {mixed} obj multiple objects can be passed using an array | |
4354 * @trigger cut.jstree | |
4355 */ | |
4356 cut : function (obj) { | |
4357 if(!obj) { obj = this._data.core.selected.concat(); } | |
4358 if(!$.isArray(obj)) { obj = [obj]; } | |
4359 if(!obj.length) { return false; } | |
4360 var tmp = [], o, t1, t2; | |
4361 for(t1 = 0, t2 = obj.length; t1 < t2; t1++) { | |
4362 o = this.get_node(obj[t1]); | |
4363 if(o && o.id && o.id !== $.jstree.root) { tmp.push(o); } | |
4364 } | |
4365 if(!tmp.length) { return false; } | |
4366 ccp_node = tmp; | |
4367 ccp_inst = this; | |
4368 ccp_mode = 'move_node'; | |
4369 /** | |
4370 * triggered when nodes are added to the buffer for moving | |
4371 * @event | |
4372 * @name cut.jstree | |
4373 * @param {Array} node | |
4374 */ | |
4375 this.trigger('cut', { "node" : obj }); | |
4376 }, | |
4377 /** | |
4378 * copy a node (a later call to `paste(obj)` would copy the node) | |
4379 * @name copy(obj) | |
4380 * @param {mixed} obj multiple objects can be passed using an array | |
4381 * @trigger copy.jstree | |
4382 */ | |
4383 copy : function (obj) { | |
4384 if(!obj) { obj = this._data.core.selected.concat(); } | |
4385 if(!$.isArray(obj)) { obj = [obj]; } | |
4386 if(!obj.length) { return false; } | |
4387 var tmp = [], o, t1, t2; | |
4388 for(t1 = 0, t2 = obj.length; t1 < t2; t1++) { | |
4389 o = this.get_node(obj[t1]); | |
4390 if(o && o.id && o.id !== $.jstree.root) { tmp.push(o); } | |
4391 } | |
4392 if(!tmp.length) { return false; } | |
4393 ccp_node = tmp; | |
4394 ccp_inst = this; | |
4395 ccp_mode = 'copy_node'; | |
4396 /** | |
4397 * triggered when nodes are added to the buffer for copying | |
4398 * @event | |
4399 * @name copy.jstree | |
4400 * @param {Array} node | |
4401 */ | |
4402 this.trigger('copy', { "node" : obj }); | |
4403 }, | |
4404 /** | |
4405 * get the current buffer (any nodes that are waiting for a paste operation) | |
4406 * @name get_buffer() | |
4407 * @return {Object} an object consisting of `mode` ("copy_node" or "move_node"), `node` (an array of objects) and `inst` (the instance) | |
4408 */ | |
4409 get_buffer : function () { | |
4410 return { 'mode' : ccp_mode, 'node' : ccp_node, 'inst' : ccp_inst }; | |
4411 }, | |
4412 /** | |
4413 * check if there is something in the buffer to paste | |
4414 * @name can_paste() | |
4415 * @return {Boolean} | |
4416 */ | |
4417 can_paste : function () { | |
4418 return ccp_mode !== false && ccp_node !== false; // && ccp_inst._model.data[ccp_node]; | |
4419 }, | |
4420 /** | |
4421 * copy or move the previously cut or copied nodes to a new parent | |
4422 * @name paste(obj [, pos]) | |
4423 * @param {mixed} obj the new parent | |
4424 * @param {mixed} pos the position to insert at (besides integer, "first" and "last" are supported), defaults to integer `0` | |
4425 * @trigger paste.jstree | |
4426 */ | |
4427 paste : function (obj, pos) { | |
4428 obj = this.get_node(obj); | |
4429 if(!obj || !ccp_mode || !ccp_mode.match(/^(copy_node|move_node)$/) || !ccp_node) { return false; } | |
4430 if(this[ccp_mode](ccp_node, obj, pos, false, false, false, ccp_inst)) { | |
4431 /** | |
4432 * triggered when paste is invoked | |
4433 * @event | |
4434 * @name paste.jstree | |
4435 * @param {String} parent the ID of the receiving node | |
4436 * @param {Array} node the nodes in the buffer | |
4437 * @param {String} mode the performed operation - "copy_node" or "move_node" | |
4438 */ | |
4439 this.trigger('paste', { "parent" : obj.id, "node" : ccp_node, "mode" : ccp_mode }); | |
4440 } | |
4441 ccp_node = false; | |
4442 ccp_mode = false; | |
4443 ccp_inst = false; | |
4444 }, | |
4445 /** | |
4446 * clear the buffer of previously copied or cut nodes | |
4447 * @name clear_buffer() | |
4448 * @trigger clear_buffer.jstree | |
4449 */ | |
4450 clear_buffer : function () { | |
4451 ccp_node = false; | |
4452 ccp_mode = false; | |
4453 ccp_inst = false; | |
4454 /** | |
4455 * triggered when the copy / cut buffer is cleared | |
4456 * @event | |
4457 * @name clear_buffer.jstree | |
4458 */ | |
4459 this.trigger('clear_buffer'); | |
4460 }, | |
4461 /** | |
4462 * put a node in edit mode (input field to rename the node) | |
4463 * @name edit(obj [, default_text, callback]) | |
4464 * @param {mixed} obj | |
4465 * @param {String} default_text the text to populate the input with (if omitted or set to a non-string value the node's text value is used) | |
4466 * @param {Function} callback a function to be called once the text box is blurred, it is called in the instance's scope and receives the node, a status parameter (true if the rename is successful, false otherwise) and a boolean indicating if the user cancelled the edit. You can access the node's title using .text | |
4467 */ | |
4468 edit : function (obj, default_text, callback) { | |
4469 var rtl, w, a, s, t, h1, h2, fn, tmp, cancel = false; | |
4470 obj = this.get_node(obj); | |
4471 if(!obj) { return false; } | |
4472 if(!this.check("edit", obj, this.get_parent(obj))) { | |
4473 this.settings.core.error.call(this, this._data.core.last_error); | |
4474 return false; | |
4475 } | |
4476 tmp = obj; | |
4477 default_text = typeof default_text === 'string' ? default_text : obj.text; | |
4478 this.set_text(obj, ""); | |
4479 obj = this._open_to(obj); | |
4480 tmp.text = default_text; | |
4481 | |
4482 rtl = this._data.core.rtl; | |
4483 w = this.element.width(); | |
4484 this._data.core.focused = tmp.id; | |
4485 a = obj.children('.jstree-anchor').focus(); | |
4486 s = $('<span>'); | |
4487 /*! | |
4488 oi = obj.children("i:visible"), | |
4489 ai = a.children("i:visible"), | |
4490 w1 = oi.width() * oi.length, | |
4491 w2 = ai.width() * ai.length, | |
4492 */ | |
4493 t = default_text; | |
4494 h1 = $("<"+"div />", { css : { "position" : "absolute", "top" : "-200px", "left" : (rtl ? "0px" : "-1000px"), "visibility" : "hidden" } }).appendTo("body"); | |
4495 h2 = $("<"+"input />", { | |
4496 "value" : t, | |
4497 "class" : "jstree-rename-input", | |
4498 // "size" : t.length, | |
4499 "css" : { | |
4500 "padding" : "0", | |
4501 "border" : "1px solid silver", | |
4502 "box-sizing" : "border-box", | |
4503 "display" : "inline-block", | |
4504 "height" : (this._data.core.li_height) + "px", | |
4505 "lineHeight" : (this._data.core.li_height) + "px", | |
4506 "width" : "150px" // will be set a bit further down | |
4507 }, | |
4508 "blur" : $.proxy(function (e) { | |
4509 e.stopImmediatePropagation(); | |
4510 e.preventDefault(); | |
4511 var i = s.children(".jstree-rename-input"), | |
4512 v = i.val(), | |
4513 f = this.settings.core.force_text, | |
4514 nv; | |
4515 if(v === "") { v = t; } | |
4516 h1.remove(); | |
4517 s.replaceWith(a); | |
4518 s.remove(); | |
4519 t = f ? t : $('<div></div>').append($.parseHTML(t)).html(); | |
4520 this.set_text(obj, t); | |
4521 nv = !!this.rename_node(obj, f ? $('<div></div>').text(v).text() : $('<div></div>').append($.parseHTML(v)).html()); | |
4522 if(!nv) { | |
4523 this.set_text(obj, t); // move this up? and fix #483 | |
4524 } | |
4525 this._data.core.focused = tmp.id; | |
4526 setTimeout($.proxy(function () { | |
4527 var node = this.get_node(tmp.id, true); | |
4528 if(node.length) { | |
4529 this._data.core.focused = tmp.id; | |
4530 node.children('.jstree-anchor').focus(); | |
4531 } | |
4532 }, this), 0); | |
4533 if(callback) { | |
4534 callback.call(this, tmp, nv, cancel); | |
4535 } | |
4536 h2 = null; | |
4537 }, this), | |
4538 "keydown" : function (e) { | |
4539 var key = e.which; | |
4540 if(key === 27) { | |
4541 cancel = true; | |
4542 this.value = t; | |
4543 } | |
4544 if(key === 27 || key === 13 || key === 37 || key === 38 || key === 39 || key === 40 || key === 32) { | |
4545 e.stopImmediatePropagation(); | |
4546 } | |
4547 if(key === 27 || key === 13) { | |
4548 e.preventDefault(); | |
4549 this.blur(); | |
4550 } | |
4551 }, | |
4552 "click" : function (e) { e.stopImmediatePropagation(); }, | |
4553 "mousedown" : function (e) { e.stopImmediatePropagation(); }, | |
4554 "keyup" : function (e) { | |
4555 h2.width(Math.min(h1.text("pW" + this.value).width(),w)); | |
4556 }, | |
4557 "keypress" : function(e) { | |
4558 if(e.which === 13) { return false; } | |
4559 } | |
4560 }); | |
4561 fn = { | |
4562 fontFamily : a.css('fontFamily') || '', | |
4563 fontSize : a.css('fontSize') || '', | |
4564 fontWeight : a.css('fontWeight') || '', | |
4565 fontStyle : a.css('fontStyle') || '', | |
4566 fontStretch : a.css('fontStretch') || '', | |
4567 fontVariant : a.css('fontVariant') || '', | |
4568 letterSpacing : a.css('letterSpacing') || '', | |
4569 wordSpacing : a.css('wordSpacing') || '' | |
4570 }; | |
4571 s.attr('class', a.attr('class')).append(a.contents().clone()).append(h2); | |
4572 a.replaceWith(s); | |
4573 h1.css(fn); | |
4574 h2.css(fn).width(Math.min(h1.text("pW" + h2[0].value).width(),w))[0].select(); | |
4575 $(document).one('mousedown.jstree touchstart.jstree dnd_start.vakata', function (e) { | |
4576 if (h2 && e.target !== h2) { | |
4577 $(h2).blur(); | |
4578 } | |
4579 }); | |
4580 }, | |
4581 | |
4582 | |
4583 /** | |
4584 * changes the theme | |
4585 * @name set_theme(theme_name [, theme_url]) | |
4586 * @param {String} theme_name the name of the new theme to apply | |
4587 * @param {mixed} theme_url the location of the CSS file for this theme. Omit or set to `false` if you manually included the file. Set to `true` to autoload from the `core.themes.dir` directory. | |
4588 * @trigger set_theme.jstree | |
4589 */ | |
4590 set_theme : function (theme_name, theme_url) { | |
4591 if(!theme_name) { return false; } | |
4592 if(theme_url === true) { | |
4593 var dir = this.settings.core.themes.dir; | |
4594 if(!dir) { dir = $.jstree.path + '/themes'; } | |
4595 theme_url = dir + '/' + theme_name + '/style.css'; | |
4596 } | |
4597 if(theme_url && $.inArray(theme_url, themes_loaded) === -1) { | |
4598 $('head').append('<'+'link rel="stylesheet" href="' + theme_url + '" type="text/css" />'); | |
4599 themes_loaded.push(theme_url); | |
4600 } | |
4601 if(this._data.core.themes.name) { | |
4602 this.element.removeClass('jstree-' + this._data.core.themes.name); | |
4603 } | |
4604 this._data.core.themes.name = theme_name; | |
4605 this.element.addClass('jstree-' + theme_name); | |
4606 this.element[this.settings.core.themes.responsive ? 'addClass' : 'removeClass' ]('jstree-' + theme_name + '-responsive'); | |
4607 /** | |
4608 * triggered when a theme is set | |
4609 * @event | |
4610 * @name set_theme.jstree | |
4611 * @param {String} theme the new theme | |
4612 */ | |
4613 this.trigger('set_theme', { 'theme' : theme_name }); | |
4614 }, | |
4615 /** | |
4616 * gets the name of the currently applied theme name | |
4617 * @name get_theme() | |
4618 * @return {String} | |
4619 */ | |
4620 get_theme : function () { return this._data.core.themes.name; }, | |
4621 /** | |
4622 * changes the theme variant (if the theme has variants) | |
4623 * @name set_theme_variant(variant_name) | |
4624 * @param {String|Boolean} variant_name the variant to apply (if `false` is used the current variant is removed) | |
4625 */ | |
4626 set_theme_variant : function (variant_name) { | |
4627 if(this._data.core.themes.variant) { | |
4628 this.element.removeClass('jstree-' + this._data.core.themes.name + '-' + this._data.core.themes.variant); | |
4629 } | |
4630 this._data.core.themes.variant = variant_name; | |
4631 if(variant_name) { | |
4632 this.element.addClass('jstree-' + this._data.core.themes.name + '-' + this._data.core.themes.variant); | |
4633 } | |
4634 }, | |
4635 /** | |
4636 * gets the name of the currently applied theme variant | |
4637 * @name get_theme() | |
4638 * @return {String} | |
4639 */ | |
4640 get_theme_variant : function () { return this._data.core.themes.variant; }, | |
4641 /** | |
4642 * shows a striped background on the container (if the theme supports it) | |
4643 * @name show_stripes() | |
4644 */ | |
4645 show_stripes : function () { | |
4646 this._data.core.themes.stripes = true; | |
4647 this.get_container_ul().addClass("jstree-striped"); | |
4648 /** | |
4649 * triggered when stripes are shown | |
4650 * @event | |
4651 * @name show_stripes.jstree | |
4652 */ | |
4653 this.trigger('show_stripes'); | |
4654 }, | |
4655 /** | |
4656 * hides the striped background on the container | |
4657 * @name hide_stripes() | |
4658 */ | |
4659 hide_stripes : function () { | |
4660 this._data.core.themes.stripes = false; | |
4661 this.get_container_ul().removeClass("jstree-striped"); | |
4662 /** | |
4663 * triggered when stripes are hidden | |
4664 * @event | |
4665 * @name hide_stripes.jstree | |
4666 */ | |
4667 this.trigger('hide_stripes'); | |
4668 }, | |
4669 /** | |
4670 * toggles the striped background on the container | |
4671 * @name toggle_stripes() | |
4672 */ | |
4673 toggle_stripes : function () { if(this._data.core.themes.stripes) { this.hide_stripes(); } else { this.show_stripes(); } }, | |
4674 /** | |
4675 * shows the connecting dots (if the theme supports it) | |
4676 * @name show_dots() | |
4677 */ | |
4678 show_dots : function () { | |
4679 this._data.core.themes.dots = true; | |
4680 this.get_container_ul().removeClass("jstree-no-dots"); | |
4681 /** | |
4682 * triggered when dots are shown | |
4683 * @event | |
4684 * @name show_dots.jstree | |
4685 */ | |
4686 this.trigger('show_dots'); | |
4687 }, | |
4688 /** | |
4689 * hides the connecting dots | |
4690 * @name hide_dots() | |
4691 */ | |
4692 hide_dots : function () { | |
4693 this._data.core.themes.dots = false; | |
4694 this.get_container_ul().addClass("jstree-no-dots"); | |
4695 /** | |
4696 * triggered when dots are hidden | |
4697 * @event | |
4698 * @name hide_dots.jstree | |
4699 */ | |
4700 this.trigger('hide_dots'); | |
4701 }, | |
4702 /** | |
4703 * toggles the connecting dots | |
4704 * @name toggle_dots() | |
4705 */ | |
4706 toggle_dots : function () { if(this._data.core.themes.dots) { this.hide_dots(); } else { this.show_dots(); } }, | |
4707 /** | |
4708 * show the node icons | |
4709 * @name show_icons() | |
4710 */ | |
4711 show_icons : function () { | |
4712 this._data.core.themes.icons = true; | |
4713 this.get_container_ul().removeClass("jstree-no-icons"); | |
4714 /** | |
4715 * triggered when icons are shown | |
4716 * @event | |
4717 * @name show_icons.jstree | |
4718 */ | |
4719 this.trigger('show_icons'); | |
4720 }, | |
4721 /** | |
4722 * hide the node icons | |
4723 * @name hide_icons() | |
4724 */ | |
4725 hide_icons : function () { | |
4726 this._data.core.themes.icons = false; | |
4727 this.get_container_ul().addClass("jstree-no-icons"); | |
4728 /** | |
4729 * triggered when icons are hidden | |
4730 * @event | |
4731 * @name hide_icons.jstree | |
4732 */ | |
4733 this.trigger('hide_icons'); | |
4734 }, | |
4735 /** | |
4736 * toggle the node icons | |
4737 * @name toggle_icons() | |
4738 */ | |
4739 toggle_icons : function () { if(this._data.core.themes.icons) { this.hide_icons(); } else { this.show_icons(); } }, | |
4740 /** | |
4741 * show the node ellipsis | |
4742 * @name show_icons() | |
4743 */ | |
4744 show_ellipsis : function () { | |
4745 this._data.core.themes.ellipsis = true; | |
4746 this.get_container_ul().addClass("jstree-ellipsis"); | |
4747 /** | |
4748 * triggered when ellisis is shown | |
4749 * @event | |
4750 * @name show_ellipsis.jstree | |
4751 */ | |
4752 this.trigger('show_ellipsis'); | |
4753 }, | |
4754 /** | |
4755 * hide the node ellipsis | |
4756 * @name hide_ellipsis() | |
4757 */ | |
4758 hide_ellipsis : function () { | |
4759 this._data.core.themes.ellipsis = false; | |
4760 this.get_container_ul().removeClass("jstree-ellipsis"); | |
4761 /** | |
4762 * triggered when ellisis is hidden | |
4763 * @event | |
4764 * @name hide_ellipsis.jstree | |
4765 */ | |
4766 this.trigger('hide_ellipsis'); | |
4767 }, | |
4768 /** | |
4769 * toggle the node ellipsis | |
4770 * @name toggle_icons() | |
4771 */ | |
4772 toggle_ellipsis : function () { if(this._data.core.themes.ellipsis) { this.hide_ellipsis(); } else { this.show_ellipsis(); } }, | |
4773 /** | |
4774 * set the node icon for a node | |
4775 * @name set_icon(obj, icon) | |
4776 * @param {mixed} obj | |
4777 * @param {String} icon the new icon - can be a path to an icon or a className, if using an image that is in the current directory use a `./` prefix, otherwise it will be detected as a class | |
4778 */ | |
4779 set_icon : function (obj, icon) { | |
4780 var t1, t2, dom, old; | |
4781 if($.isArray(obj)) { | |
4782 obj = obj.slice(); | |
4783 for(t1 = 0, t2 = obj.length; t1 < t2; t1++) { | |
4784 this.set_icon(obj[t1], icon); | |
4785 } | |
4786 return true; | |
4787 } | |
4788 obj = this.get_node(obj); | |
4789 if(!obj || obj.id === $.jstree.root) { return false; } | |
4790 old = obj.icon; | |
4791 obj.icon = icon === true || icon === null || icon === undefined || icon === '' ? true : icon; | |
4792 dom = this.get_node(obj, true).children(".jstree-anchor").children(".jstree-themeicon"); | |
4793 if(icon === false) { | |
4794 dom.removeClass('jstree-themeicon-custom ' + old).css("background","").removeAttr("rel"); | |
4795 this.hide_icon(obj); | |
4796 } | |
4797 else if(icon === true || icon === null || icon === undefined || icon === '') { | |
4798 dom.removeClass('jstree-themeicon-custom ' + old).css("background","").removeAttr("rel"); | |
4799 if(old === false) { this.show_icon(obj); } | |
4800 } | |
4801 else if(icon.indexOf("/") === -1 && icon.indexOf(".") === -1) { | |
4802 dom.removeClass(old).css("background",""); | |
4803 dom.addClass(icon + ' jstree-themeicon-custom').attr("rel",icon); | |
4804 if(old === false) { this.show_icon(obj); } | |
4805 } | |
4806 else { | |
4807 dom.removeClass(old).css("background",""); | |
4808 dom.addClass('jstree-themeicon-custom').css("background", "url('" + icon + "') center center no-repeat").attr("rel",icon); | |
4809 if(old === false) { this.show_icon(obj); } | |
4810 } | |
4811 return true; | |
4812 }, | |
4813 /** | |
4814 * get the node icon for a node | |
4815 * @name get_icon(obj) | |
4816 * @param {mixed} obj | |
4817 * @return {String} | |
4818 */ | |
4819 get_icon : function (obj) { | |
4820 obj = this.get_node(obj); | |
4821 return (!obj || obj.id === $.jstree.root) ? false : obj.icon; | |
4822 }, | |
4823 /** | |
4824 * hide the icon on an individual node | |
4825 * @name hide_icon(obj) | |
4826 * @param {mixed} obj | |
4827 */ | |
4828 hide_icon : function (obj) { | |
4829 var t1, t2; | |
4830 if($.isArray(obj)) { | |
4831 obj = obj.slice(); | |
4832 for(t1 = 0, t2 = obj.length; t1 < t2; t1++) { | |
4833 this.hide_icon(obj[t1]); | |
4834 } | |
4835 return true; | |
4836 } | |
4837 obj = this.get_node(obj); | |
4838 if(!obj || obj === $.jstree.root) { return false; } | |
4839 obj.icon = false; | |
4840 this.get_node(obj, true).children(".jstree-anchor").children(".jstree-themeicon").addClass('jstree-themeicon-hidden'); | |
4841 return true; | |
4842 }, | |
4843 /** | |
4844 * show the icon on an individual node | |
4845 * @name show_icon(obj) | |
4846 * @param {mixed} obj | |
4847 */ | |
4848 show_icon : function (obj) { | |
4849 var t1, t2, dom; | |
4850 if($.isArray(obj)) { | |
4851 obj = obj.slice(); | |
4852 for(t1 = 0, t2 = obj.length; t1 < t2; t1++) { | |
4853 this.show_icon(obj[t1]); | |
4854 } | |
4855 return true; | |
4856 } | |
4857 obj = this.get_node(obj); | |
4858 if(!obj || obj === $.jstree.root) { return false; } | |
4859 dom = this.get_node(obj, true); | |
4860 obj.icon = dom.length ? dom.children(".jstree-anchor").children(".jstree-themeicon").attr('rel') : true; | |
4861 if(!obj.icon) { obj.icon = true; } | |
4862 dom.children(".jstree-anchor").children(".jstree-themeicon").removeClass('jstree-themeicon-hidden'); | |
4863 return true; | |
4864 } | |
4865 }; | |
4866 | |
4867 // helpers | |
4868 $.vakata = {}; | |
4869 // collect attributes | |
4870 $.vakata.attributes = function(node, with_values) { | |
4871 node = $(node)[0]; | |
4872 var attr = with_values ? {} : []; | |
4873 if(node && node.attributes) { | |
4874 $.each(node.attributes, function (i, v) { | |
4875 if($.inArray(v.name.toLowerCase(),['style','contenteditable','hasfocus','tabindex']) !== -1) { return; } | |
4876 if(v.value !== null && $.trim(v.value) !== '') { | |
4877 if(with_values) { attr[v.name] = v.value; } | |
4878 else { attr.push(v.name); } | |
4879 } | |
4880 }); | |
4881 } | |
4882 return attr; | |
4883 }; | |
4884 $.vakata.array_unique = function(array) { | |
4885 var a = [], i, j, l, o = {}; | |
4886 for(i = 0, l = array.length; i < l; i++) { | |
4887 if(o[array[i]] === undefined) { | |
4888 a.push(array[i]); | |
4889 o[array[i]] = true; | |
4890 } | |
4891 } | |
4892 return a; | |
4893 }; | |
4894 // remove item from array | |
4895 $.vakata.array_remove = function(array, from) { | |
4896 array.splice(from, 1); | |
4897 return array; | |
4898 //var rest = array.slice((to || from) + 1 || array.length); | |
4899 //array.length = from < 0 ? array.length + from : from; | |
4900 //array.push.apply(array, rest); | |
4901 //return array; | |
4902 }; | |
4903 // remove item from array | |
4904 $.vakata.array_remove_item = function(array, item) { | |
4905 var tmp = $.inArray(item, array); | |
4906 return tmp !== -1 ? $.vakata.array_remove(array, tmp) : array; | |
4907 }; | |
4908 $.vakata.array_filter = function(c,a,b,d,e) { | |
4909 if (c.filter) { | |
4910 return c.filter(a, b); | |
4911 } | |
4912 d=[]; | |
4913 for (e in c) { | |
4914 if (~~e+''===e+'' && e>=0 && a.call(b,c[e],+e,c)) { | |
4915 d.push(c[e]); | |
4916 } | |
4917 } | |
4918 return d; | |
4919 }; | |
4920 | |
4921 | |
4922 /** | |
4923 * ### Changed plugin | |
4924 * | |
4925 * This plugin adds more information to the `changed.jstree` event. The new data is contained in the `changed` event data property, and contains a lists of `selected` and `deselected` nodes. | |
4926 */ | |
4927 | |
4928 $.jstree.plugins.changed = function (options, parent) { | |
4929 var last = []; | |
4930 this.trigger = function (ev, data) { | |
4931 var i, j; | |
4932 if(!data) { | |
4933 data = {}; | |
4934 } | |
4935 if(ev.replace('.jstree','') === 'changed') { | |
4936 data.changed = { selected : [], deselected : [] }; | |
4937 var tmp = {}; | |
4938 for(i = 0, j = last.length; i < j; i++) { | |
4939 tmp[last[i]] = 1; | |
4940 } | |
4941 for(i = 0, j = data.selected.length; i < j; i++) { | |
4942 if(!tmp[data.selected[i]]) { | |
4943 data.changed.selected.push(data.selected[i]); | |
4944 } | |
4945 else { | |
4946 tmp[data.selected[i]] = 2; | |
4947 } | |
4948 } | |
4949 for(i = 0, j = last.length; i < j; i++) { | |
4950 if(tmp[last[i]] === 1) { | |
4951 data.changed.deselected.push(last[i]); | |
4952 } | |
4953 } | |
4954 last = data.selected.slice(); | |
4955 } | |
4956 /** | |
4957 * triggered when selection changes (the "changed" plugin enhances the original event with more data) | |
4958 * @event | |
4959 * @name changed.jstree | |
4960 * @param {Object} node | |
4961 * @param {Object} action the action that caused the selection to change | |
4962 * @param {Array} selected the current selection | |
4963 * @param {Object} changed an object containing two properties `selected` and `deselected` - both arrays of node IDs, which were selected or deselected since the last changed event | |
4964 * @param {Object} event the event (if any) that triggered this changed event | |
4965 * @plugin changed | |
4966 */ | |
4967 parent.trigger.call(this, ev, data); | |
4968 }; | |
4969 this.refresh = function (skip_loading, forget_state) { | |
4970 last = []; | |
4971 return parent.refresh.apply(this, arguments); | |
4972 }; | |
4973 }; | |
4974 | |
4975 /** | |
4976 * ### Checkbox plugin | |
4977 * | |
4978 * This plugin renders checkbox icons in front of each node, making multiple selection much easier. | |
4979 * It also supports tri-state behavior, meaning that if a node has a few of its children checked it will be rendered as undetermined, and state will be propagated up. | |
4980 */ | |
4981 | |
4982 var _i = document.createElement('I'); | |
4983 _i.className = 'jstree-icon jstree-checkbox'; | |
4984 _i.setAttribute('role', 'presentation'); | |
4985 /** | |
4986 * stores all defaults for the checkbox plugin | |
4987 * @name $.jstree.defaults.checkbox | |
4988 * @plugin checkbox | |
4989 */ | |
4990 $.jstree.defaults.checkbox = { | |
4991 /** | |
4992 * a boolean indicating if checkboxes should be visible (can be changed at a later time using `show_checkboxes()` and `hide_checkboxes`). Defaults to `true`. | |
4993 * @name $.jstree.defaults.checkbox.visible | |
4994 * @plugin checkbox | |
4995 */ | |
4996 visible : true, | |
4997 /** | |
4998 * a boolean indicating if checkboxes should cascade down and have an undetermined state. Defaults to `true`. | |
4999 * @name $.jstree.defaults.checkbox.three_state | |
5000 * @plugin checkbox | |
5001 */ | |
5002 three_state : true, | |
5003 /** | |
5004 * a boolean indicating if clicking anywhere on the node should act as clicking on the checkbox. Defaults to `true`. | |
5005 * @name $.jstree.defaults.checkbox.whole_node | |
5006 * @plugin checkbox | |
5007 */ | |
5008 whole_node : true, | |
5009 /** | |
5010 * a boolean indicating if the selected style of a node should be kept, or removed. Defaults to `true`. | |
5011 * @name $.jstree.defaults.checkbox.keep_selected_style | |
5012 * @plugin checkbox | |
5013 */ | |
5014 keep_selected_style : true, | |
5015 /** | |
5016 * This setting controls how cascading and undetermined nodes are applied. | |
5017 * If 'up' is in the string - cascading up is enabled, if 'down' is in the string - cascading down is enabled, if 'undetermined' is in the string - undetermined nodes will be used. | |
5018 * If `three_state` is set to `true` this setting is automatically set to 'up+down+undetermined'. Defaults to ''. | |
5019 * @name $.jstree.defaults.checkbox.cascade | |
5020 * @plugin checkbox | |
5021 */ | |
5022 cascade : '', | |
5023 /** | |
5024 * This setting controls if checkbox are bound to the general tree selection or to an internal array maintained by the checkbox plugin. Defaults to `true`, only set to `false` if you know exactly what you are doing. | |
5025 * @name $.jstree.defaults.checkbox.tie_selection | |
5026 * @plugin checkbox | |
5027 */ | |
5028 tie_selection : true, | |
5029 | |
5030 /** | |
5031 * This setting controls if cascading down affects disabled checkboxes | |
5032 * @name $.jstree.defaults.checkbox.cascade_to_disabled | |
5033 * @plugin checkbox | |
5034 */ | |
5035 cascade_to_disabled : true, | |
5036 | |
5037 /** | |
5038 * This setting controls if cascading down affects hidden checkboxes | |
5039 * @name $.jstree.defaults.checkbox.cascade_to_hidden | |
5040 * @plugin checkbox | |
5041 */ | |
5042 cascade_to_hidden : true | |
5043 }; | |
5044 $.jstree.plugins.checkbox = function (options, parent) { | |
5045 this.bind = function () { | |
5046 parent.bind.call(this); | |
5047 this._data.checkbox.uto = false; | |
5048 this._data.checkbox.selected = []; | |
5049 if(this.settings.checkbox.three_state) { | |
5050 this.settings.checkbox.cascade = 'up+down+undetermined'; | |
5051 } | |
5052 this.element | |
5053 .on("init.jstree", $.proxy(function () { | |
5054 this._data.checkbox.visible = this.settings.checkbox.visible; | |
5055 if(!this.settings.checkbox.keep_selected_style) { | |
5056 this.element.addClass('jstree-checkbox-no-clicked'); | |
5057 } | |
5058 if(this.settings.checkbox.tie_selection) { | |
5059 this.element.addClass('jstree-checkbox-selection'); | |
5060 } | |
5061 }, this)) | |
5062 .on("loading.jstree", $.proxy(function () { | |
5063 this[ this._data.checkbox.visible ? 'show_checkboxes' : 'hide_checkboxes' ](); | |
5064 }, this)); | |
5065 if(this.settings.checkbox.cascade.indexOf('undetermined') !== -1) { | |
5066 this.element | |
5067 .on('changed.jstree uncheck_node.jstree check_node.jstree uncheck_all.jstree check_all.jstree move_node.jstree copy_node.jstree redraw.jstree open_node.jstree', $.proxy(function () { | |
5068 // only if undetermined is in setting | |
5069 if(this._data.checkbox.uto) { clearTimeout(this._data.checkbox.uto); } | |
5070 this._data.checkbox.uto = setTimeout($.proxy(this._undetermined, this), 50); | |
5071 }, this)); | |
5072 } | |
5073 if(!this.settings.checkbox.tie_selection) { | |
5074 this.element | |
5075 .on('model.jstree', $.proxy(function (e, data) { | |
5076 var m = this._model.data, | |
5077 p = m[data.parent], | |
5078 dpc = data.nodes, | |
5079 i, j; | |
5080 for(i = 0, j = dpc.length; i < j; i++) { | |
5081 m[dpc[i]].state.checked = m[dpc[i]].state.checked || (m[dpc[i]].original && m[dpc[i]].original.state && m[dpc[i]].original.state.checked); | |
5082 if(m[dpc[i]].state.checked) { | |
5083 this._data.checkbox.selected.push(dpc[i]); | |
5084 } | |
5085 } | |
5086 }, this)); | |
5087 } | |
5088 if(this.settings.checkbox.cascade.indexOf('up') !== -1 || this.settings.checkbox.cascade.indexOf('down') !== -1) { | |
5089 this.element | |
5090 .on('model.jstree', $.proxy(function (e, data) { | |
5091 var m = this._model.data, | |
5092 p = m[data.parent], | |
5093 dpc = data.nodes, | |
5094 chd = [], | |
5095 c, i, j, k, l, tmp, s = this.settings.checkbox.cascade, t = this.settings.checkbox.tie_selection; | |
5096 | |
5097 if(s.indexOf('down') !== -1) { | |
5098 // apply down | |
5099 if(p.state[ t ? 'selected' : 'checked' ]) { | |
5100 for(i = 0, j = dpc.length; i < j; i++) { | |
5101 m[dpc[i]].state[ t ? 'selected' : 'checked' ] = true; | |
5102 } | |
5103 | |
5104 this._data[ t ? 'core' : 'checkbox' ].selected = this._data[ t ? 'core' : 'checkbox' ].selected.concat(dpc); | |
5105 } | |
5106 else { | |
5107 for(i = 0, j = dpc.length; i < j; i++) { | |
5108 if(m[dpc[i]].state[ t ? 'selected' : 'checked' ]) { | |
5109 for(k = 0, l = m[dpc[i]].children_d.length; k < l; k++) { | |
5110 m[m[dpc[i]].children_d[k]].state[ t ? 'selected' : 'checked' ] = true; | |
5111 } | |
5112 this._data[ t ? 'core' : 'checkbox' ].selected = this._data[ t ? 'core' : 'checkbox' ].selected.concat(m[dpc[i]].children_d); | |
5113 } | |
5114 } | |
5115 } | |
5116 } | |
5117 | |
5118 if(s.indexOf('up') !== -1) { | |
5119 // apply up | |
5120 for(i = 0, j = p.children_d.length; i < j; i++) { | |
5121 if(!m[p.children_d[i]].children.length) { | |
5122 chd.push(m[p.children_d[i]].parent); | |
5123 } | |
5124 } | |
5125 chd = $.vakata.array_unique(chd); | |
5126 for(k = 0, l = chd.length; k < l; k++) { | |
5127 p = m[chd[k]]; | |
5128 while(p && p.id !== $.jstree.root) { | |
5129 c = 0; | |
5130 for(i = 0, j = p.children.length; i < j; i++) { | |
5131 c += m[p.children[i]].state[ t ? 'selected' : 'checked' ]; | |
5132 } | |
5133 if(c === j) { | |
5134 p.state[ t ? 'selected' : 'checked' ] = true; | |
5135 this._data[ t ? 'core' : 'checkbox' ].selected.push(p.id); | |
5136 tmp = this.get_node(p, true); | |
5137 if(tmp && tmp.length) { | |
5138 tmp.attr('aria-selected', true).children('.jstree-anchor').addClass( t ? 'jstree-clicked' : 'jstree-checked'); | |
5139 } | |
5140 } | |
5141 else { | |
5142 break; | |
5143 } | |
5144 p = this.get_node(p.parent); | |
5145 } | |
5146 } | |
5147 } | |
5148 | |
5149 this._data[ t ? 'core' : 'checkbox' ].selected = $.vakata.array_unique(this._data[ t ? 'core' : 'checkbox' ].selected); | |
5150 }, this)) | |
5151 .on(this.settings.checkbox.tie_selection ? 'select_node.jstree' : 'check_node.jstree', $.proxy(function (e, data) { | |
5152 var self = this, | |
5153 obj = data.node, | |
5154 m = this._model.data, | |
5155 par = this.get_node(obj.parent), | |
5156 i, j, c, tmp, s = this.settings.checkbox.cascade, t = this.settings.checkbox.tie_selection, | |
5157 sel = {}, cur = this._data[ t ? 'core' : 'checkbox' ].selected; | |
5158 | |
5159 for (i = 0, j = cur.length; i < j; i++) { | |
5160 sel[cur[i]] = true; | |
5161 } | |
5162 | |
5163 // apply down | |
5164 if(s.indexOf('down') !== -1) { | |
5165 //this._data[ t ? 'core' : 'checkbox' ].selected = $.vakata.array_unique(this._data[ t ? 'core' : 'checkbox' ].selected.concat(obj.children_d)); | |
5166 var selectedIds = this._cascade_new_checked_state(obj.id, true); | |
5167 var temp = obj.children_d.concat(obj.id); | |
5168 for (i = 0, j = temp.length; i < j; i++) { | |
5169 if (selectedIds.indexOf(temp[i]) > -1) { | |
5170 sel[temp[i]] = true; | |
5171 } | |
5172 else { | |
5173 delete sel[temp[i]]; | |
5174 } | |
5175 } | |
5176 } | |
5177 | |
5178 // apply up | |
5179 if(s.indexOf('up') !== -1) { | |
5180 while(par && par.id !== $.jstree.root) { | |
5181 c = 0; | |
5182 for(i = 0, j = par.children.length; i < j; i++) { | |
5183 c += m[par.children[i]].state[ t ? 'selected' : 'checked' ]; | |
5184 } | |
5185 if(c === j) { | |
5186 par.state[ t ? 'selected' : 'checked' ] = true; | |
5187 sel[par.id] = true; | |
5188 //this._data[ t ? 'core' : 'checkbox' ].selected.push(par.id); | |
5189 tmp = this.get_node(par, true); | |
5190 if(tmp && tmp.length) { | |
5191 tmp.attr('aria-selected', true).children('.jstree-anchor').addClass(t ? 'jstree-clicked' : 'jstree-checked'); | |
5192 } | |
5193 } | |
5194 else { | |
5195 break; | |
5196 } | |
5197 par = this.get_node(par.parent); | |
5198 } | |
5199 } | |
5200 | |
5201 cur = []; | |
5202 for (i in sel) { | |
5203 if (sel.hasOwnProperty(i)) { | |
5204 cur.push(i); | |
5205 } | |
5206 } | |
5207 this._data[ t ? 'core' : 'checkbox' ].selected = cur; | |
5208 }, this)) | |
5209 .on(this.settings.checkbox.tie_selection ? 'deselect_all.jstree' : 'uncheck_all.jstree', $.proxy(function (e, data) { | |
5210 var obj = this.get_node($.jstree.root), | |
5211 m = this._model.data, | |
5212 i, j, tmp; | |
5213 for(i = 0, j = obj.children_d.length; i < j; i++) { | |
5214 tmp = m[obj.children_d[i]]; | |
5215 if(tmp && tmp.original && tmp.original.state && tmp.original.state.undetermined) { | |
5216 tmp.original.state.undetermined = false; | |
5217 } | |
5218 } | |
5219 }, this)) | |
5220 .on(this.settings.checkbox.tie_selection ? 'deselect_node.jstree' : 'uncheck_node.jstree', $.proxy(function (e, data) { | |
5221 var self = this, | |
5222 obj = data.node, | |
5223 dom = this.get_node(obj, true), | |
5224 i, j, tmp, s = this.settings.checkbox.cascade, t = this.settings.checkbox.tie_selection, | |
5225 cur = this._data[ t ? 'core' : 'checkbox' ].selected, sel = {}, | |
5226 stillSelectedIds = [], | |
5227 allIds = obj.children_d.concat(obj.id); | |
5228 | |
5229 // apply down | |
5230 if(s.indexOf('down') !== -1) { | |
5231 var selectedIds = this._cascade_new_checked_state(obj.id, false); | |
5232 | |
5233 cur = cur.filter(function(id) { | |
5234 return allIds.indexOf(id) === -1 || selectedIds.indexOf(id) > -1; | |
5235 }); | |
5236 } | |
5237 | |
5238 // only apply up if cascade up is enabled and if this node is not selected | |
5239 // (if all child nodes are disabled and cascade_to_disabled === false then this node will till be selected). | |
5240 if(s.indexOf('up') !== -1 && cur.indexOf(obj.id) === -1) { | |
5241 for(i = 0, j = obj.parents.length; i < j; i++) { | |
5242 tmp = this._model.data[obj.parents[i]]; | |
5243 tmp.state[ t ? 'selected' : 'checked' ] = false; | |
5244 if(tmp && tmp.original && tmp.original.state && tmp.original.state.undetermined) { | |
5245 tmp.original.state.undetermined = false; | |
5246 } | |
5247 tmp = this.get_node(obj.parents[i], true); | |
5248 if(tmp && tmp.length) { | |
5249 tmp.attr('aria-selected', false).children('.jstree-anchor').removeClass(t ? 'jstree-clicked' : 'jstree-checked'); | |
5250 } | |
5251 } | |
5252 | |
5253 cur = cur.filter(function(id) { | |
5254 return obj.parents.indexOf(id) === -1; | |
5255 }); | |
5256 } | |
5257 | |
5258 this._data[ t ? 'core' : 'checkbox' ].selected = cur; | |
5259 }, this)); | |
5260 } | |
5261 if(this.settings.checkbox.cascade.indexOf('up') !== -1) { | |
5262 this.element | |
5263 .on('delete_node.jstree', $.proxy(function (e, data) { | |
5264 // apply up (whole handler) | |
5265 var p = this.get_node(data.parent), | |
5266 m = this._model.data, | |
5267 i, j, c, tmp, t = this.settings.checkbox.tie_selection; | |
5268 while(p && p.id !== $.jstree.root && !p.state[ t ? 'selected' : 'checked' ]) { | |
5269 c = 0; | |
5270 for(i = 0, j = p.children.length; i < j; i++) { | |
5271 c += m[p.children[i]].state[ t ? 'selected' : 'checked' ]; | |
5272 } | |
5273 if(j > 0 && c === j) { | |
5274 p.state[ t ? 'selected' : 'checked' ] = true; | |
5275 this._data[ t ? 'core' : 'checkbox' ].selected.push(p.id); | |
5276 tmp = this.get_node(p, true); | |
5277 if(tmp && tmp.length) { | |
5278 tmp.attr('aria-selected', true).children('.jstree-anchor').addClass(t ? 'jstree-clicked' : 'jstree-checked'); | |
5279 } | |
5280 } | |
5281 else { | |
5282 break; | |
5283 } | |
5284 p = this.get_node(p.parent); | |
5285 } | |
5286 }, this)) | |
5287 .on('move_node.jstree', $.proxy(function (e, data) { | |
5288 // apply up (whole handler) | |
5289 var is_multi = data.is_multi, | |
5290 old_par = data.old_parent, | |
5291 new_par = this.get_node(data.parent), | |
5292 m = this._model.data, | |
5293 p, c, i, j, tmp, t = this.settings.checkbox.tie_selection; | |
5294 if(!is_multi) { | |
5295 p = this.get_node(old_par); | |
5296 while(p && p.id !== $.jstree.root && !p.state[ t ? 'selected' : 'checked' ]) { | |
5297 c = 0; | |
5298 for(i = 0, j = p.children.length; i < j; i++) { | |
5299 c += m[p.children[i]].state[ t ? 'selected' : 'checked' ]; | |
5300 } | |
5301 if(j > 0 && c === j) { | |
5302 p.state[ t ? 'selected' : 'checked' ] = true; | |
5303 this._data[ t ? 'core' : 'checkbox' ].selected.push(p.id); | |
5304 tmp = this.get_node(p, true); | |
5305 if(tmp && tmp.length) { | |
5306 tmp.attr('aria-selected', true).children('.jstree-anchor').addClass(t ? 'jstree-clicked' : 'jstree-checked'); | |
5307 } | |
5308 } | |
5309 else { | |
5310 break; | |
5311 } | |
5312 p = this.get_node(p.parent); | |
5313 } | |
5314 } | |
5315 p = new_par; | |
5316 while(p && p.id !== $.jstree.root) { | |
5317 c = 0; | |
5318 for(i = 0, j = p.children.length; i < j; i++) { | |
5319 c += m[p.children[i]].state[ t ? 'selected' : 'checked' ]; | |
5320 } | |
5321 if(c === j) { | |
5322 if(!p.state[ t ? 'selected' : 'checked' ]) { | |
5323 p.state[ t ? 'selected' : 'checked' ] = true; | |
5324 this._data[ t ? 'core' : 'checkbox' ].selected.push(p.id); | |
5325 tmp = this.get_node(p, true); | |
5326 if(tmp && tmp.length) { | |
5327 tmp.attr('aria-selected', true).children('.jstree-anchor').addClass(t ? 'jstree-clicked' : 'jstree-checked'); | |
5328 } | |
5329 } | |
5330 } | |
5331 else { | |
5332 if(p.state[ t ? 'selected' : 'checked' ]) { | |
5333 p.state[ t ? 'selected' : 'checked' ] = false; | |
5334 this._data[ t ? 'core' : 'checkbox' ].selected = $.vakata.array_remove_item(this._data[ t ? 'core' : 'checkbox' ].selected, p.id); | |
5335 tmp = this.get_node(p, true); | |
5336 if(tmp && tmp.length) { | |
5337 tmp.attr('aria-selected', false).children('.jstree-anchor').removeClass(t ? 'jstree-clicked' : 'jstree-checked'); | |
5338 } | |
5339 } | |
5340 else { | |
5341 break; | |
5342 } | |
5343 } | |
5344 p = this.get_node(p.parent); | |
5345 } | |
5346 }, this)); | |
5347 } | |
5348 }; | |
5349 /** | |
5350 * get an array of all nodes whose state is "undetermined" | |
5351 * @name get_undetermined([full]) | |
5352 * @param {boolean} full: if set to `true` the returned array will consist of the full node objects, otherwise - only IDs will be returned | |
5353 * @return {Array} | |
5354 * @plugin checkbox | |
5355 */ | |
5356 this.get_undetermined = function (full) { | |
5357 if (this.settings.checkbox.cascade.indexOf('undetermined') === -1) { | |
5358 return []; | |
5359 } | |
5360 var i, j, k, l, o = {}, m = this._model.data, t = this.settings.checkbox.tie_selection, s = this._data[ t ? 'core' : 'checkbox' ].selected, p = [], tt = this, r = []; | |
5361 for(i = 0, j = s.length; i < j; i++) { | |
5362 if(m[s[i]] && m[s[i]].parents) { | |
5363 for(k = 0, l = m[s[i]].parents.length; k < l; k++) { | |
5364 if(o[m[s[i]].parents[k]] !== undefined) { | |
5365 break; | |
5366 } | |
5367 if(m[s[i]].parents[k] !== $.jstree.root) { | |
5368 o[m[s[i]].parents[k]] = true; | |
5369 p.push(m[s[i]].parents[k]); | |
5370 } | |
5371 } | |
5372 } | |
5373 } | |
5374 // attempt for server side undetermined state | |
5375 this.element.find('.jstree-closed').not(':has(.jstree-children)') | |
5376 .each(function () { | |
5377 var tmp = tt.get_node(this), tmp2; | |
5378 | |
5379 if(!tmp) { return; } | |
5380 | |
5381 if(!tmp.state.loaded) { | |
5382 if(tmp.original && tmp.original.state && tmp.original.state.undetermined && tmp.original.state.undetermined === true) { | |
5383 if(o[tmp.id] === undefined && tmp.id !== $.jstree.root) { | |
5384 o[tmp.id] = true; | |
5385 p.push(tmp.id); | |
5386 } | |
5387 for(k = 0, l = tmp.parents.length; k < l; k++) { | |
5388 if(o[tmp.parents[k]] === undefined && tmp.parents[k] !== $.jstree.root) { | |
5389 o[tmp.parents[k]] = true; | |
5390 p.push(tmp.parents[k]); | |
5391 } | |
5392 } | |
5393 } | |
5394 } | |
5395 else { | |
5396 for(i = 0, j = tmp.children_d.length; i < j; i++) { | |
5397 tmp2 = m[tmp.children_d[i]]; | |
5398 if(!tmp2.state.loaded && tmp2.original && tmp2.original.state && tmp2.original.state.undetermined && tmp2.original.state.undetermined === true) { | |
5399 if(o[tmp2.id] === undefined && tmp2.id !== $.jstree.root) { | |
5400 o[tmp2.id] = true; | |
5401 p.push(tmp2.id); | |
5402 } | |
5403 for(k = 0, l = tmp2.parents.length; k < l; k++) { | |
5404 if(o[tmp2.parents[k]] === undefined && tmp2.parents[k] !== $.jstree.root) { | |
5405 o[tmp2.parents[k]] = true; | |
5406 p.push(tmp2.parents[k]); | |
5407 } | |
5408 } | |
5409 } | |
5410 } | |
5411 } | |
5412 }); | |
5413 for (i = 0, j = p.length; i < j; i++) { | |
5414 if(!m[p[i]].state[ t ? 'selected' : 'checked' ]) { | |
5415 r.push(full ? m[p[i]] : p[i]); | |
5416 } | |
5417 } | |
5418 return r; | |
5419 }; | |
5420 /** | |
5421 * set the undetermined state where and if necessary. Used internally. | |
5422 * @private | |
5423 * @name _undetermined() | |
5424 * @plugin checkbox | |
5425 */ | |
5426 this._undetermined = function () { | |
5427 if(this.element === null) { return; } | |
5428 var p = this.get_undetermined(false), i, j, s; | |
5429 | |
5430 this.element.find('.jstree-undetermined').removeClass('jstree-undetermined'); | |
5431 for (i = 0, j = p.length; i < j; i++) { | |
5432 s = this.get_node(p[i], true); | |
5433 if(s && s.length) { | |
5434 s.children('.jstree-anchor').children('.jstree-checkbox').addClass('jstree-undetermined'); | |
5435 } | |
5436 } | |
5437 }; | |
5438 this.redraw_node = function(obj, deep, is_callback, force_render) { | |
5439 obj = parent.redraw_node.apply(this, arguments); | |
5440 if(obj) { | |
5441 var i, j, tmp = null, icon = null; | |
5442 for(i = 0, j = obj.childNodes.length; i < j; i++) { | |
5443 if(obj.childNodes[i] && obj.childNodes[i].className && obj.childNodes[i].className.indexOf("jstree-anchor") !== -1) { | |
5444 tmp = obj.childNodes[i]; | |
5445 break; | |
5446 } | |
5447 } | |
5448 if(tmp) { | |
5449 if(!this.settings.checkbox.tie_selection && this._model.data[obj.id].state.checked) { tmp.className += ' jstree-checked'; } | |
5450 icon = _i.cloneNode(false); | |
5451 if(this._model.data[obj.id].state.checkbox_disabled) { icon.className += ' jstree-checkbox-disabled'; } | |
5452 tmp.insertBefore(icon, tmp.childNodes[0]); | |
5453 } | |
5454 } | |
5455 if(!is_callback && this.settings.checkbox.cascade.indexOf('undetermined') !== -1) { | |
5456 if(this._data.checkbox.uto) { clearTimeout(this._data.checkbox.uto); } | |
5457 this._data.checkbox.uto = setTimeout($.proxy(this._undetermined, this), 50); | |
5458 } | |
5459 return obj; | |
5460 }; | |
5461 /** | |
5462 * show the node checkbox icons | |
5463 * @name show_checkboxes() | |
5464 * @plugin checkbox | |
5465 */ | |
5466 this.show_checkboxes = function () { this._data.core.themes.checkboxes = true; this.get_container_ul().removeClass("jstree-no-checkboxes"); }; | |
5467 /** | |
5468 * hide the node checkbox icons | |
5469 * @name hide_checkboxes() | |
5470 * @plugin checkbox | |
5471 */ | |
5472 this.hide_checkboxes = function () { this._data.core.themes.checkboxes = false; this.get_container_ul().addClass("jstree-no-checkboxes"); }; | |
5473 /** | |
5474 * toggle the node icons | |
5475 * @name toggle_checkboxes() | |
5476 * @plugin checkbox | |
5477 */ | |
5478 this.toggle_checkboxes = function () { if(this._data.core.themes.checkboxes) { this.hide_checkboxes(); } else { this.show_checkboxes(); } }; | |
5479 /** | |
5480 * checks if a node is in an undetermined state | |
5481 * @name is_undetermined(obj) | |
5482 * @param {mixed} obj | |
5483 * @return {Boolean} | |
5484 */ | |
5485 this.is_undetermined = function (obj) { | |
5486 obj = this.get_node(obj); | |
5487 var s = this.settings.checkbox.cascade, i, j, t = this.settings.checkbox.tie_selection, d = this._data[ t ? 'core' : 'checkbox' ].selected, m = this._model.data; | |
5488 if(!obj || obj.state[ t ? 'selected' : 'checked' ] === true || s.indexOf('undetermined') === -1 || (s.indexOf('down') === -1 && s.indexOf('up') === -1)) { | |
5489 return false; | |
5490 } | |
5491 if(!obj.state.loaded && obj.original.state.undetermined === true) { | |
5492 return true; | |
5493 } | |
5494 for(i = 0, j = obj.children_d.length; i < j; i++) { | |
5495 if($.inArray(obj.children_d[i], d) !== -1 || (!m[obj.children_d[i]].state.loaded && m[obj.children_d[i]].original.state.undetermined)) { | |
5496 return true; | |
5497 } | |
5498 } | |
5499 return false; | |
5500 }; | |
5501 /** | |
5502 * disable a node's checkbox | |
5503 * @name disable_checkbox(obj) | |
5504 * @param {mixed} obj an array can be used too | |
5505 * @trigger disable_checkbox.jstree | |
5506 * @plugin checkbox | |
5507 */ | |
5508 this.disable_checkbox = function (obj) { | |
5509 var t1, t2, dom; | |
5510 if($.isArray(obj)) { | |
5511 obj = obj.slice(); | |
5512 for(t1 = 0, t2 = obj.length; t1 < t2; t1++) { | |
5513 this.disable_checkbox(obj[t1]); | |
5514 } | |
5515 return true; | |
5516 } | |
5517 obj = this.get_node(obj); | |
5518 if(!obj || obj.id === $.jstree.root) { | |
5519 return false; | |
5520 } | |
5521 dom = this.get_node(obj, true); | |
5522 if(!obj.state.checkbox_disabled) { | |
5523 obj.state.checkbox_disabled = true; | |
5524 if(dom && dom.length) { | |
5525 dom.children('.jstree-anchor').children('.jstree-checkbox').addClass('jstree-checkbox-disabled'); | |
5526 } | |
5527 /** | |
5528 * triggered when an node's checkbox is disabled | |
5529 * @event | |
5530 * @name disable_checkbox.jstree | |
5531 * @param {Object} node | |
5532 * @plugin checkbox | |
5533 */ | |
5534 this.trigger('disable_checkbox', { 'node' : obj }); | |
5535 } | |
5536 }; | |
5537 /** | |
5538 * enable a node's checkbox | |
5539 * @name disable_checkbox(obj) | |
5540 * @param {mixed} obj an array can be used too | |
5541 * @trigger enable_checkbox.jstree | |
5542 * @plugin checkbox | |
5543 */ | |
5544 this.enable_checkbox = function (obj) { | |
5545 var t1, t2, dom; | |
5546 if($.isArray(obj)) { | |
5547 obj = obj.slice(); | |
5548 for(t1 = 0, t2 = obj.length; t1 < t2; t1++) { | |
5549 this.enable_checkbox(obj[t1]); | |
5550 } | |
5551 return true; | |
5552 } | |
5553 obj = this.get_node(obj); | |
5554 if(!obj || obj.id === $.jstree.root) { | |
5555 return false; | |
5556 } | |
5557 dom = this.get_node(obj, true); | |
5558 if(obj.state.checkbox_disabled) { | |
5559 obj.state.checkbox_disabled = false; | |
5560 if(dom && dom.length) { | |
5561 dom.children('.jstree-anchor').children('.jstree-checkbox').removeClass('jstree-checkbox-disabled'); | |
5562 } | |
5563 /** | |
5564 * triggered when an node's checkbox is enabled | |
5565 * @event | |
5566 * @name enable_checkbox.jstree | |
5567 * @param {Object} node | |
5568 * @plugin checkbox | |
5569 */ | |
5570 this.trigger('enable_checkbox', { 'node' : obj }); | |
5571 } | |
5572 }; | |
5573 | |
5574 this.activate_node = function (obj, e) { | |
5575 if($(e.target).hasClass('jstree-checkbox-disabled')) { | |
5576 return false; | |
5577 } | |
5578 if(this.settings.checkbox.tie_selection && (this.settings.checkbox.whole_node || $(e.target).hasClass('jstree-checkbox'))) { | |
5579 e.ctrlKey = true; | |
5580 } | |
5581 if(this.settings.checkbox.tie_selection || (!this.settings.checkbox.whole_node && !$(e.target).hasClass('jstree-checkbox'))) { | |
5582 return parent.activate_node.call(this, obj, e); | |
5583 } | |
5584 if(this.is_disabled(obj)) { | |
5585 return false; | |
5586 } | |
5587 if(this.is_checked(obj)) { | |
5588 this.uncheck_node(obj, e); | |
5589 } | |
5590 else { | |
5591 this.check_node(obj, e); | |
5592 } | |
5593 this.trigger('activate_node', { 'node' : this.get_node(obj) }); | |
5594 }; | |
5595 | |
5596 /** | |
5597 * Cascades checked state to a node and all its descendants. This function does NOT affect hidden and disabled nodes (or their descendants). | |
5598 * However if these unaffected nodes are already selected their ids will be included in the returned array. | |
5599 * @private | |
5600 * @param {string} id the node ID | |
5601 * @param {bool} checkedState should the nodes be checked or not | |
5602 * @returns {Array} Array of all node id's (in this tree branch) that are checked. | |
5603 */ | |
5604 this._cascade_new_checked_state = function (id, checkedState) { | |
5605 var self = this; | |
5606 var t = this.settings.checkbox.tie_selection; | |
5607 var node = this._model.data[id]; | |
5608 var selectedNodeIds = []; | |
5609 var selectedChildrenIds = [], i, j, selectedChildIds; | |
5610 | |
5611 if ( | |
5612 (this.settings.checkbox.cascade_to_disabled || !node.state.disabled) && | |
5613 (this.settings.checkbox.cascade_to_hidden || !node.state.hidden) | |
5614 ) { | |
5615 //First try and check/uncheck the children | |
5616 if (node.children) { | |
5617 for (i = 0, j = node.children.length; i < j; i++) { | |
5618 var childId = node.children[i]; | |
5619 selectedChildIds = self._cascade_new_checked_state(childId, checkedState); | |
5620 selectedNodeIds = selectedNodeIds.concat(selectedChildIds); | |
5621 if (selectedChildIds.indexOf(childId) > -1) { | |
5622 selectedChildrenIds.push(childId); | |
5623 } | |
5624 } | |
5625 } | |
5626 | |
5627 var dom = self.get_node(node, true); | |
5628 | |
5629 //A node's state is undetermined if some but not all of it's children are checked/selected . | |
5630 var undetermined = selectedChildrenIds.length > 0 && selectedChildrenIds.length < node.children.length; | |
5631 | |
5632 if(node.original && node.original.state && node.original.state.undetermined) { | |
5633 node.original.state.undetermined = undetermined; | |
5634 } | |
5635 | |
5636 //If a node is undetermined then remove selected class | |
5637 if (undetermined) { | |
5638 node.state[ t ? 'selected' : 'checked' ] = false; | |
5639 dom.attr('aria-selected', false).children('.jstree-anchor').removeClass(t ? 'jstree-clicked' : 'jstree-checked'); | |
5640 } | |
5641 //Otherwise, if the checkedState === true (i.e. the node is being checked now) and all of the node's children are checked (if it has any children), | |
5642 //check the node and style it correctly. | |
5643 else if (checkedState && selectedChildrenIds.length === node.children.length) { | |
5644 node.state[ t ? 'selected' : 'checked' ] = checkedState; | |
5645 selectedNodeIds.push(node.id); | |
5646 | |
5647 dom.attr('aria-selected', true).children('.jstree-anchor').addClass(t ? 'jstree-clicked' : 'jstree-checked'); | |
5648 } | |
5649 else { | |
5650 node.state[ t ? 'selected' : 'checked' ] = false; | |
5651 dom.attr('aria-selected', false).children('.jstree-anchor').removeClass(t ? 'jstree-clicked' : 'jstree-checked'); | |
5652 } | |
5653 } | |
5654 else { | |
5655 selectedChildIds = this.get_checked_descendants(id); | |
5656 | |
5657 if (node.state[ t ? 'selected' : 'checked' ]) { | |
5658 selectedChildIds.push(node.id); | |
5659 } | |
5660 | |
5661 selectedNodeIds = selectedNodeIds.concat(selectedChildIds); | |
5662 } | |
5663 | |
5664 return selectedNodeIds; | |
5665 }; | |
5666 | |
5667 /** | |
5668 * Gets ids of nodes selected in branch (of tree) specified by id (does not include the node specified by id) | |
5669 * @name get_checked_descendants(obj) | |
5670 * @param {string} id the node ID | |
5671 * @return {Array} array of IDs | |
5672 * @plugin checkbox | |
5673 */ | |
5674 this.get_checked_descendants = function (id) { | |
5675 var self = this; | |
5676 var t = self.settings.checkbox.tie_selection; | |
5677 var node = self._model.data[id]; | |
5678 | |
5679 return node.children_d.filter(function(_id) { | |
5680 return self._model.data[_id].state[ t ? 'selected' : 'checked' ]; | |
5681 }); | |
5682 }; | |
5683 | |
5684 /** | |
5685 * check a node (only if tie_selection in checkbox settings is false, otherwise select_node will be called internally) | |
5686 * @name check_node(obj) | |
5687 * @param {mixed} obj an array can be used to check multiple nodes | |
5688 * @trigger check_node.jstree | |
5689 * @plugin checkbox | |
5690 */ | |
5691 this.check_node = function (obj, e) { | |
5692 if(this.settings.checkbox.tie_selection) { return this.select_node(obj, false, true, e); } | |
5693 var dom, t1, t2, th; | |
5694 if($.isArray(obj)) { | |
5695 obj = obj.slice(); | |
5696 for(t1 = 0, t2 = obj.length; t1 < t2; t1++) { | |
5697 this.check_node(obj[t1], e); | |
5698 } | |
5699 return true; | |
5700 } | |
5701 obj = this.get_node(obj); | |
5702 if(!obj || obj.id === $.jstree.root) { | |
5703 return false; | |
5704 } | |
5705 dom = this.get_node(obj, true); | |
5706 if(!obj.state.checked) { | |
5707 obj.state.checked = true; | |
5708 this._data.checkbox.selected.push(obj.id); | |
5709 if(dom && dom.length) { | |
5710 dom.children('.jstree-anchor').addClass('jstree-checked'); | |
5711 } | |
5712 /** | |
5713 * triggered when an node is checked (only if tie_selection in checkbox settings is false) | |
5714 * @event | |
5715 * @name check_node.jstree | |
5716 * @param {Object} node | |
5717 * @param {Array} selected the current selection | |
5718 * @param {Object} event the event (if any) that triggered this check_node | |
5719 * @plugin checkbox | |
5720 */ | |
5721 this.trigger('check_node', { 'node' : obj, 'selected' : this._data.checkbox.selected, 'event' : e }); | |
5722 } | |
5723 }; | |
5724 /** | |
5725 * uncheck a node (only if tie_selection in checkbox settings is false, otherwise deselect_node will be called internally) | |
5726 * @name uncheck_node(obj) | |
5727 * @param {mixed} obj an array can be used to uncheck multiple nodes | |
5728 * @trigger uncheck_node.jstree | |
5729 * @plugin checkbox | |
5730 */ | |
5731 this.uncheck_node = function (obj, e) { | |
5732 if(this.settings.checkbox.tie_selection) { return this.deselect_node(obj, false, e); } | |
5733 var t1, t2, dom; | |
5734 if($.isArray(obj)) { | |
5735 obj = obj.slice(); | |
5736 for(t1 = 0, t2 = obj.length; t1 < t2; t1++) { | |
5737 this.uncheck_node(obj[t1], e); | |
5738 } | |
5739 return true; | |
5740 } | |
5741 obj = this.get_node(obj); | |
5742 if(!obj || obj.id === $.jstree.root) { | |
5743 return false; | |
5744 } | |
5745 dom = this.get_node(obj, true); | |
5746 if(obj.state.checked) { | |
5747 obj.state.checked = false; | |
5748 this._data.checkbox.selected = $.vakata.array_remove_item(this._data.checkbox.selected, obj.id); | |
5749 if(dom.length) { | |
5750 dom.children('.jstree-anchor').removeClass('jstree-checked'); | |
5751 } | |
5752 /** | |
5753 * triggered when an node is unchecked (only if tie_selection in checkbox settings is false) | |
5754 * @event | |
5755 * @name uncheck_node.jstree | |
5756 * @param {Object} node | |
5757 * @param {Array} selected the current selection | |
5758 * @param {Object} event the event (if any) that triggered this uncheck_node | |
5759 * @plugin checkbox | |
5760 */ | |
5761 this.trigger('uncheck_node', { 'node' : obj, 'selected' : this._data.checkbox.selected, 'event' : e }); | |
5762 } | |
5763 }; | |
5764 | |
5765 /** | |
5766 * checks all nodes in the tree (only if tie_selection in checkbox settings is false, otherwise select_all will be called internally) | |
5767 * @name check_all() | |
5768 * @trigger check_all.jstree, changed.jstree | |
5769 * @plugin checkbox | |
5770 */ | |
5771 this.check_all = function () { | |
5772 if(this.settings.checkbox.tie_selection) { return this.select_all(); } | |
5773 var tmp = this._data.checkbox.selected.concat([]), i, j; | |
5774 this._data.checkbox.selected = this._model.data[$.jstree.root].children_d.concat(); | |
5775 for(i = 0, j = this._data.checkbox.selected.length; i < j; i++) { | |
5776 if(this._model.data[this._data.checkbox.selected[i]]) { | |
5777 this._model.data[this._data.checkbox.selected[i]].state.checked = true; | |
5778 } | |
5779 } | |
5780 this.redraw(true); | |
5781 /** | |
5782 * triggered when all nodes are checked (only if tie_selection in checkbox settings is false) | |
5783 * @event | |
5784 * @name check_all.jstree | |
5785 * @param {Array} selected the current selection | |
5786 * @plugin checkbox | |
5787 */ | |
5788 this.trigger('check_all', { 'selected' : this._data.checkbox.selected }); | |
5789 }; | |
5790 /** | |
5791 * uncheck all checked nodes (only if tie_selection in checkbox settings is false, otherwise deselect_all will be called internally) | |
5792 * @name uncheck_all() | |
5793 * @trigger uncheck_all.jstree | |
5794 * @plugin checkbox | |
5795 */ | |
5796 this.uncheck_all = function () { | |
5797 if(this.settings.checkbox.tie_selection) { return this.deselect_all(); } | |
5798 var tmp = this._data.checkbox.selected.concat([]), i, j; | |
5799 for(i = 0, j = this._data.checkbox.selected.length; i < j; i++) { | |
5800 if(this._model.data[this._data.checkbox.selected[i]]) { | |
5801 this._model.data[this._data.checkbox.selected[i]].state.checked = false; | |
5802 } | |
5803 } | |
5804 this._data.checkbox.selected = []; | |
5805 this.element.find('.jstree-checked').removeClass('jstree-checked'); | |
5806 /** | |
5807 * triggered when all nodes are unchecked (only if tie_selection in checkbox settings is false) | |
5808 * @event | |
5809 * @name uncheck_all.jstree | |
5810 * @param {Object} node the previous selection | |
5811 * @param {Array} selected the current selection | |
5812 * @plugin checkbox | |
5813 */ | |
5814 this.trigger('uncheck_all', { 'selected' : this._data.checkbox.selected, 'node' : tmp }); | |
5815 }; | |
5816 /** | |
5817 * checks if a node is checked (if tie_selection is on in the settings this function will return the same as is_selected) | |
5818 * @name is_checked(obj) | |
5819 * @param {mixed} obj | |
5820 * @return {Boolean} | |
5821 * @plugin checkbox | |
5822 */ | |
5823 this.is_checked = function (obj) { | |
5824 if(this.settings.checkbox.tie_selection) { return this.is_selected(obj); } | |
5825 obj = this.get_node(obj); | |
5826 if(!obj || obj.id === $.jstree.root) { return false; } | |
5827 return obj.state.checked; | |
5828 }; | |
5829 /** | |
5830 * get an array of all checked nodes (if tie_selection is on in the settings this function will return the same as get_selected) | |
5831 * @name get_checked([full]) | |
5832 * @param {mixed} full if set to `true` the returned array will consist of the full node objects, otherwise - only IDs will be returned | |
5833 * @return {Array} | |
5834 * @plugin checkbox | |
5835 */ | |
5836 this.get_checked = function (full) { | |
5837 if(this.settings.checkbox.tie_selection) { return this.get_selected(full); } | |
5838 return full ? $.map(this._data.checkbox.selected, $.proxy(function (i) { return this.get_node(i); }, this)) : this._data.checkbox.selected; | |
5839 }; | |
5840 /** | |
5841 * get an array of all top level checked nodes (ignoring children of checked nodes) (if tie_selection is on in the settings this function will return the same as get_top_selected) | |
5842 * @name get_top_checked([full]) | |
5843 * @param {mixed} full if set to `true` the returned array will consist of the full node objects, otherwise - only IDs will be returned | |
5844 * @return {Array} | |
5845 * @plugin checkbox | |
5846 */ | |
5847 this.get_top_checked = function (full) { | |
5848 if(this.settings.checkbox.tie_selection) { return this.get_top_selected(full); } | |
5849 var tmp = this.get_checked(true), | |
5850 obj = {}, i, j, k, l; | |
5851 for(i = 0, j = tmp.length; i < j; i++) { | |
5852 obj[tmp[i].id] = tmp[i]; | |
5853 } | |
5854 for(i = 0, j = tmp.length; i < j; i++) { | |
5855 for(k = 0, l = tmp[i].children_d.length; k < l; k++) { | |
5856 if(obj[tmp[i].children_d[k]]) { | |
5857 delete obj[tmp[i].children_d[k]]; | |
5858 } | |
5859 } | |
5860 } | |
5861 tmp = []; | |
5862 for(i in obj) { | |
5863 if(obj.hasOwnProperty(i)) { | |
5864 tmp.push(i); | |
5865 } | |
5866 } | |
5867 return full ? $.map(tmp, $.proxy(function (i) { return this.get_node(i); }, this)) : tmp; | |
5868 }; | |
5869 /** | |
5870 * get an array of all bottom level checked nodes (ignoring selected parents) (if tie_selection is on in the settings this function will return the same as get_bottom_selected) | |
5871 * @name get_bottom_checked([full]) | |
5872 * @param {mixed} full if set to `true` the returned array will consist of the full node objects, otherwise - only IDs will be returned | |
5873 * @return {Array} | |
5874 * @plugin checkbox | |
5875 */ | |
5876 this.get_bottom_checked = function (full) { | |
5877 if(this.settings.checkbox.tie_selection) { return this.get_bottom_selected(full); } | |
5878 var tmp = this.get_checked(true), | |
5879 obj = [], i, j; | |
5880 for(i = 0, j = tmp.length; i < j; i++) { | |
5881 if(!tmp[i].children.length) { | |
5882 obj.push(tmp[i].id); | |
5883 } | |
5884 } | |
5885 return full ? $.map(obj, $.proxy(function (i) { return this.get_node(i); }, this)) : obj; | |
5886 }; | |
5887 this.load_node = function (obj, callback) { | |
5888 var k, l, i, j, c, tmp; | |
5889 if(!$.isArray(obj) && !this.settings.checkbox.tie_selection) { | |
5890 tmp = this.get_node(obj); | |
5891 if(tmp && tmp.state.loaded) { | |
5892 for(k = 0, l = tmp.children_d.length; k < l; k++) { | |
5893 if(this._model.data[tmp.children_d[k]].state.checked) { | |
5894 c = true; | |
5895 this._data.checkbox.selected = $.vakata.array_remove_item(this._data.checkbox.selected, tmp.children_d[k]); | |
5896 } | |
5897 } | |
5898 } | |
5899 } | |
5900 return parent.load_node.apply(this, arguments); | |
5901 }; | |
5902 this.get_state = function () { | |
5903 var state = parent.get_state.apply(this, arguments); | |
5904 if(this.settings.checkbox.tie_selection) { return state; } | |
5905 state.checkbox = this._data.checkbox.selected.slice(); | |
5906 return state; | |
5907 }; | |
5908 this.set_state = function (state, callback) { | |
5909 var res = parent.set_state.apply(this, arguments); | |
5910 if(res && state.checkbox) { | |
5911 if(!this.settings.checkbox.tie_selection) { | |
5912 this.uncheck_all(); | |
5913 var _this = this; | |
5914 $.each(state.checkbox, function (i, v) { | |
5915 _this.check_node(v); | |
5916 }); | |
5917 } | |
5918 delete state.checkbox; | |
5919 this.set_state(state, callback); | |
5920 return false; | |
5921 } | |
5922 return res; | |
5923 }; | |
5924 this.refresh = function (skip_loading, forget_state) { | |
5925 if(!this.settings.checkbox.tie_selection) { | |
5926 this._data.checkbox.selected = []; | |
5927 } | |
5928 return parent.refresh.apply(this, arguments); | |
5929 }; | |
5930 }; | |
5931 | |
5932 // include the checkbox plugin by default | |
5933 // $.jstree.defaults.plugins.push("checkbox"); | |
5934 | |
5935 | |
5936 /** | |
5937 * ### Conditionalselect plugin | |
5938 * | |
5939 * This plugin allows defining a callback to allow or deny node selection by user input (activate node method). | |
5940 */ | |
5941 | |
5942 /** | |
5943 * a callback (function) which is invoked in the instance's scope and receives two arguments - the node and the event that triggered the `activate_node` call. Returning false prevents working with the node, returning true allows invoking activate_node. Defaults to returning `true`. | |
5944 * @name $.jstree.defaults.checkbox.visible | |
5945 * @plugin checkbox | |
5946 */ | |
5947 $.jstree.defaults.conditionalselect = function () { return true; }; | |
5948 $.jstree.plugins.conditionalselect = function (options, parent) { | |
5949 // own function | |
5950 this.activate_node = function (obj, e) { | |
5951 if(this.settings.conditionalselect.call(this, this.get_node(obj), e)) { | |
5952 return parent.activate_node.call(this, obj, e); | |
5953 } | |
5954 }; | |
5955 }; | |
5956 | |
5957 | |
5958 /** | |
5959 * ### Contextmenu plugin | |
5960 * | |
5961 * Shows a context menu when a node is right-clicked. | |
5962 */ | |
5963 | |
5964 /** | |
5965 * stores all defaults for the contextmenu plugin | |
5966 * @name $.jstree.defaults.contextmenu | |
5967 * @plugin contextmenu | |
5968 */ | |
5969 $.jstree.defaults.contextmenu = { | |
5970 /** | |
5971 * a boolean indicating if the node should be selected when the context menu is invoked on it. Defaults to `true`. | |
5972 * @name $.jstree.defaults.contextmenu.select_node | |
5973 * @plugin contextmenu | |
5974 */ | |
5975 select_node : true, | |
5976 /** | |
5977 * a boolean indicating if the menu should be shown aligned with the node. Defaults to `true`, otherwise the mouse coordinates are used. | |
5978 * @name $.jstree.defaults.contextmenu.show_at_node | |
5979 * @plugin contextmenu | |
5980 */ | |
5981 show_at_node : true, | |
5982 /** | |
5983 * an object of actions, or a function that accepts a node and a callback function and calls the callback function with an object of actions available for that node (you can also return the items too). | |
5984 * | |
5985 * Each action consists of a key (a unique name) and a value which is an object with the following properties (only label and action are required). Once a menu item is activated the `action` function will be invoked with an object containing the following keys: item - the contextmenu item definition as seen below, reference - the DOM node that was used (the tree node), element - the contextmenu DOM element, position - an object with x/y properties indicating the position of the menu. | |
5986 * | |
5987 * * `separator_before` - a boolean indicating if there should be a separator before this item | |
5988 * * `separator_after` - a boolean indicating if there should be a separator after this item | |
5989 * * `_disabled` - a boolean indicating if this action should be disabled | |
5990 * * `label` - a string - the name of the action (could be a function returning a string) | |
5991 * * `title` - a string - an optional tooltip for the item | |
5992 * * `action` - a function to be executed if this item is chosen, the function will receive | |
5993 * * `icon` - a string, can be a path to an icon or a className, if using an image that is in the current directory use a `./` prefix, otherwise it will be detected as a class | |
5994 * * `shortcut` - keyCode which will trigger the action if the menu is open (for example `113` for rename, which equals F2) | |
5995 * * `shortcut_label` - shortcut label (like for example `F2` for rename) | |
5996 * * `submenu` - an object with the same structure as $.jstree.defaults.contextmenu.items which can be used to create a submenu - each key will be rendered as a separate option in a submenu that will appear once the current item is hovered | |
5997 * | |
5998 * @name $.jstree.defaults.contextmenu.items | |
5999 * @plugin contextmenu | |
6000 */ | |
6001 items : function (o, cb) { // Could be an object directly | |
6002 return { | |
6003 "create" : { | |
6004 "separator_before" : false, | |
6005 "separator_after" : true, | |
6006 "_disabled" : false, //(this.check("create_node", data.reference, {}, "last")), | |
6007 "label" : "Create", | |
6008 "action" : function (data) { | |
6009 var inst = $.jstree.reference(data.reference), | |
6010 obj = inst.get_node(data.reference); | |
6011 inst.create_node(obj, {}, "last", function (new_node) { | |
6012 try { | |
6013 inst.edit(new_node); | |
6014 } catch (ex) { | |
6015 setTimeout(function () { inst.edit(new_node); },0); | |
6016 } | |
6017 }); | |
6018 } | |
6019 }, | |
6020 "rename" : { | |
6021 "separator_before" : false, | |
6022 "separator_after" : false, | |
6023 "_disabled" : false, //(this.check("rename_node", data.reference, this.get_parent(data.reference), "")), | |
6024 "label" : "Rename", | |
6025 /*! | |
6026 "shortcut" : 113, | |
6027 "shortcut_label" : 'F2', | |
6028 "icon" : "glyphicon glyphicon-leaf", | |
6029 */ | |
6030 "action" : function (data) { | |
6031 var inst = $.jstree.reference(data.reference), | |
6032 obj = inst.get_node(data.reference); | |
6033 inst.edit(obj); | |
6034 } | |
6035 }, | |
6036 "remove" : { | |
6037 "separator_before" : false, | |
6038 "icon" : false, | |
6039 "separator_after" : false, | |
6040 "_disabled" : false, //(this.check("delete_node", data.reference, this.get_parent(data.reference), "")), | |
6041 "label" : "Delete", | |
6042 "action" : function (data) { | |
6043 var inst = $.jstree.reference(data.reference), | |
6044 obj = inst.get_node(data.reference); | |
6045 if(inst.is_selected(obj)) { | |
6046 inst.delete_node(inst.get_selected()); | |
6047 } | |
6048 else { | |
6049 inst.delete_node(obj); | |
6050 } | |
6051 } | |
6052 }, | |
6053 "ccp" : { | |
6054 "separator_before" : true, | |
6055 "icon" : false, | |
6056 "separator_after" : false, | |
6057 "label" : "Edit", | |
6058 "action" : false, | |
6059 "submenu" : { | |
6060 "cut" : { | |
6061 "separator_before" : false, | |
6062 "separator_after" : false, | |
6063 "label" : "Cut", | |
6064 "action" : function (data) { | |
6065 var inst = $.jstree.reference(data.reference), | |
6066 obj = inst.get_node(data.reference); | |
6067 if(inst.is_selected(obj)) { | |
6068 inst.cut(inst.get_top_selected()); | |
6069 } | |
6070 else { | |
6071 inst.cut(obj); | |
6072 } | |
6073 } | |
6074 }, | |
6075 "copy" : { | |
6076 "separator_before" : false, | |
6077 "icon" : false, | |
6078 "separator_after" : false, | |
6079 "label" : "Copy", | |
6080 "action" : function (data) { | |
6081 var inst = $.jstree.reference(data.reference), | |
6082 obj = inst.get_node(data.reference); | |
6083 if(inst.is_selected(obj)) { | |
6084 inst.copy(inst.get_top_selected()); | |
6085 } | |
6086 else { | |
6087 inst.copy(obj); | |
6088 } | |
6089 } | |
6090 }, | |
6091 "paste" : { | |
6092 "separator_before" : false, | |
6093 "icon" : false, | |
6094 "_disabled" : function (data) { | |
6095 return !$.jstree.reference(data.reference).can_paste(); | |
6096 }, | |
6097 "separator_after" : false, | |
6098 "label" : "Paste", | |
6099 "action" : function (data) { | |
6100 var inst = $.jstree.reference(data.reference), | |
6101 obj = inst.get_node(data.reference); | |
6102 inst.paste(obj); | |
6103 } | |
6104 } | |
6105 } | |
6106 } | |
6107 }; | |
6108 } | |
6109 }; | |
6110 | |
6111 $.jstree.plugins.contextmenu = function (options, parent) { | |
6112 this.bind = function () { | |
6113 parent.bind.call(this); | |
6114 | |
6115 var last_ts = 0, cto = null, ex, ey; | |
6116 this.element | |
6117 .on("init.jstree loading.jstree ready.jstree", $.proxy(function () { | |
6118 this.get_container_ul().addClass('jstree-contextmenu'); | |
6119 }, this)) | |
6120 .on("contextmenu.jstree", ".jstree-anchor", $.proxy(function (e, data) { | |
6121 if (e.target.tagName.toLowerCase() === 'input') { | |
6122 return; | |
6123 } | |
6124 e.preventDefault(); | |
6125 last_ts = e.ctrlKey ? +new Date() : 0; | |
6126 if(data || cto) { | |
6127 last_ts = (+new Date()) + 10000; | |
6128 } | |
6129 if(cto) { | |
6130 clearTimeout(cto); | |
6131 } | |
6132 if(!this.is_loading(e.currentTarget)) { | |
6133 this.show_contextmenu(e.currentTarget, e.pageX, e.pageY, e); | |
6134 } | |
6135 }, this)) | |
6136 .on("click.jstree", ".jstree-anchor", $.proxy(function (e) { | |
6137 if(this._data.contextmenu.visible && (!last_ts || (+new Date()) - last_ts > 250)) { // work around safari & macOS ctrl+click | |
6138 $.vakata.context.hide(); | |
6139 } | |
6140 last_ts = 0; | |
6141 }, this)) | |
6142 .on("touchstart.jstree", ".jstree-anchor", function (e) { | |
6143 if(!e.originalEvent || !e.originalEvent.changedTouches || !e.originalEvent.changedTouches[0]) { | |
6144 return; | |
6145 } | |
6146 ex = e.originalEvent.changedTouches[0].clientX; | |
6147 ey = e.originalEvent.changedTouches[0].clientY; | |
6148 cto = setTimeout(function () { | |
6149 $(e.currentTarget).trigger('contextmenu', true); | |
6150 }, 750); | |
6151 }) | |
6152 .on('touchmove.vakata.jstree', function (e) { | |
6153 if(cto && e.originalEvent && e.originalEvent.changedTouches && e.originalEvent.changedTouches[0] && (Math.abs(ex - e.originalEvent.changedTouches[0].clientX) > 10 || Math.abs(ey - e.originalEvent.changedTouches[0].clientY) > 10)) { | |
6154 clearTimeout(cto); | |
6155 $.vakata.context.hide(); | |
6156 } | |
6157 }) | |
6158 .on('touchend.vakata.jstree', function (e) { | |
6159 if(cto) { | |
6160 clearTimeout(cto); | |
6161 } | |
6162 }); | |
6163 | |
6164 /*! | |
6165 if(!('oncontextmenu' in document.body) && ('ontouchstart' in document.body)) { | |
6166 var el = null, tm = null; | |
6167 this.element | |
6168 .on("touchstart", ".jstree-anchor", function (e) { | |
6169 el = e.currentTarget; | |
6170 tm = +new Date(); | |
6171 $(document).one("touchend", function (e) { | |
6172 e.target = document.elementFromPoint(e.originalEvent.targetTouches[0].pageX - window.pageXOffset, e.originalEvent.targetTouches[0].pageY - window.pageYOffset); | |
6173 e.currentTarget = e.target; | |
6174 tm = ((+(new Date())) - tm); | |
6175 if(e.target === el && tm > 600 && tm < 1000) { | |
6176 e.preventDefault(); | |
6177 $(el).trigger('contextmenu', e); | |
6178 } | |
6179 el = null; | |
6180 tm = null; | |
6181 }); | |
6182 }); | |
6183 } | |
6184 */ | |
6185 $(document).on("context_hide.vakata.jstree", $.proxy(function (e, data) { | |
6186 this._data.contextmenu.visible = false; | |
6187 $(data.reference).removeClass('jstree-context'); | |
6188 }, this)); | |
6189 }; | |
6190 this.teardown = function () { | |
6191 if(this._data.contextmenu.visible) { | |
6192 $.vakata.context.hide(); | |
6193 } | |
6194 parent.teardown.call(this); | |
6195 }; | |
6196 | |
6197 /** | |
6198 * prepare and show the context menu for a node | |
6199 * @name show_contextmenu(obj [, x, y]) | |
6200 * @param {mixed} obj the node | |
6201 * @param {Number} x the x-coordinate relative to the document to show the menu at | |
6202 * @param {Number} y the y-coordinate relative to the document to show the menu at | |
6203 * @param {Object} e the event if available that triggered the contextmenu | |
6204 * @plugin contextmenu | |
6205 * @trigger show_contextmenu.jstree | |
6206 */ | |
6207 this.show_contextmenu = function (obj, x, y, e) { | |
6208 obj = this.get_node(obj); | |
6209 if(!obj || obj.id === $.jstree.root) { return false; } | |
6210 var s = this.settings.contextmenu, | |
6211 d = this.get_node(obj, true), | |
6212 a = d.children(".jstree-anchor"), | |
6213 o = false, | |
6214 i = false; | |
6215 if(s.show_at_node || x === undefined || y === undefined) { | |
6216 o = a.offset(); | |
6217 x = o.left; | |
6218 y = o.top + this._data.core.li_height; | |
6219 } | |
6220 if(this.settings.contextmenu.select_node && !this.is_selected(obj)) { | |
6221 this.activate_node(obj, e); | |
6222 } | |
6223 | |
6224 i = s.items; | |
6225 if($.isFunction(i)) { | |
6226 i = i.call(this, obj, $.proxy(function (i) { | |
6227 this._show_contextmenu(obj, x, y, i); | |
6228 }, this)); | |
6229 } | |
6230 if($.isPlainObject(i)) { | |
6231 this._show_contextmenu(obj, x, y, i); | |
6232 } | |
6233 }; | |
6234 /** | |
6235 * show the prepared context menu for a node | |
6236 * @name _show_contextmenu(obj, x, y, i) | |
6237 * @param {mixed} obj the node | |
6238 * @param {Number} x the x-coordinate relative to the document to show the menu at | |
6239 * @param {Number} y the y-coordinate relative to the document to show the menu at | |
6240 * @param {Number} i the object of items to show | |
6241 * @plugin contextmenu | |
6242 * @trigger show_contextmenu.jstree | |
6243 * @private | |
6244 */ | |
6245 this._show_contextmenu = function (obj, x, y, i) { | |
6246 var d = this.get_node(obj, true), | |
6247 a = d.children(".jstree-anchor"); | |
6248 $(document).one("context_show.vakata.jstree", $.proxy(function (e, data) { | |
6249 var cls = 'jstree-contextmenu jstree-' + this.get_theme() + '-contextmenu'; | |
6250 $(data.element).addClass(cls); | |
6251 a.addClass('jstree-context'); | |
6252 }, this)); | |
6253 this._data.contextmenu.visible = true; | |
6254 $.vakata.context.show(a, { 'x' : x, 'y' : y }, i); | |
6255 /** | |
6256 * triggered when the contextmenu is shown for a node | |
6257 * @event | |
6258 * @name show_contextmenu.jstree | |
6259 * @param {Object} node the node | |
6260 * @param {Number} x the x-coordinate of the menu relative to the document | |
6261 * @param {Number} y the y-coordinate of the menu relative to the document | |
6262 * @plugin contextmenu | |
6263 */ | |
6264 this.trigger('show_contextmenu', { "node" : obj, "x" : x, "y" : y }); | |
6265 }; | |
6266 }; | |
6267 | |
6268 // contextmenu helper | |
6269 (function ($) { | |
6270 var right_to_left = false, | |
6271 vakata_context = { | |
6272 element : false, | |
6273 reference : false, | |
6274 position_x : 0, | |
6275 position_y : 0, | |
6276 items : [], | |
6277 html : "", | |
6278 is_visible : false | |
6279 }; | |
6280 | |
6281 $.vakata.context = { | |
6282 settings : { | |
6283 hide_onmouseleave : 0, | |
6284 icons : true | |
6285 }, | |
6286 _trigger : function (event_name) { | |
6287 $(document).triggerHandler("context_" + event_name + ".vakata", { | |
6288 "reference" : vakata_context.reference, | |
6289 "element" : vakata_context.element, | |
6290 "position" : { | |
6291 "x" : vakata_context.position_x, | |
6292 "y" : vakata_context.position_y | |
6293 } | |
6294 }); | |
6295 }, | |
6296 _execute : function (i) { | |
6297 i = vakata_context.items[i]; | |
6298 return i && (!i._disabled || ($.isFunction(i._disabled) && !i._disabled({ "item" : i, "reference" : vakata_context.reference, "element" : vakata_context.element }))) && i.action ? i.action.call(null, { | |
6299 "item" : i, | |
6300 "reference" : vakata_context.reference, | |
6301 "element" : vakata_context.element, | |
6302 "position" : { | |
6303 "x" : vakata_context.position_x, | |
6304 "y" : vakata_context.position_y | |
6305 } | |
6306 }) : false; | |
6307 }, | |
6308 _parse : function (o, is_callback) { | |
6309 if(!o) { return false; } | |
6310 if(!is_callback) { | |
6311 vakata_context.html = ""; | |
6312 vakata_context.items = []; | |
6313 } | |
6314 var str = "", | |
6315 sep = false, | |
6316 tmp; | |
6317 | |
6318 if(is_callback) { str += "<"+"ul>"; } | |
6319 $.each(o, function (i, val) { | |
6320 if(!val) { return true; } | |
6321 vakata_context.items.push(val); | |
6322 if(!sep && val.separator_before) { | |
6323 str += "<"+"li class='vakata-context-separator'><"+"a href='#' " + ($.vakata.context.settings.icons ? '' : 'style="margin-left:0px;"') + "> <"+"/a><"+"/li>"; | |
6324 } | |
6325 sep = false; | |
6326 str += "<"+"li class='" + (val._class || "") + (val._disabled === true || ($.isFunction(val._disabled) && val._disabled({ "item" : val, "reference" : vakata_context.reference, "element" : vakata_context.element })) ? " vakata-contextmenu-disabled " : "") + "' "+(val.shortcut?" data-shortcut='"+val.shortcut+"' ":'')+">"; | |
6327 str += "<"+"a href='#' rel='" + (vakata_context.items.length - 1) + "' " + (val.title ? "title='" + val.title + "'" : "") + ">"; | |
6328 if($.vakata.context.settings.icons) { | |
6329 str += "<"+"i "; | |
6330 if(val.icon) { | |
6331 if(val.icon.indexOf("/") !== -1 || val.icon.indexOf(".") !== -1) { str += " style='background:url(\"" + val.icon + "\") center center no-repeat' "; } | |
6332 else { str += " class='" + val.icon + "' "; } | |
6333 } | |
6334 str += "><"+"/i><"+"span class='vakata-contextmenu-sep'> <"+"/span>"; | |
6335 } | |
6336 str += ($.isFunction(val.label) ? val.label({ "item" : i, "reference" : vakata_context.reference, "element" : vakata_context.element }) : val.label) + (val.shortcut?' <span class="vakata-contextmenu-shortcut vakata-contextmenu-shortcut-'+val.shortcut+'">'+ (val.shortcut_label || '') +'</span>':'') + "<"+"/a>"; | |
6337 if(val.submenu) { | |
6338 tmp = $.vakata.context._parse(val.submenu, true); | |
6339 if(tmp) { str += tmp; } | |
6340 } | |
6341 str += "<"+"/li>"; | |
6342 if(val.separator_after) { | |
6343 str += "<"+"li class='vakata-context-separator'><"+"a href='#' " + ($.vakata.context.settings.icons ? '' : 'style="margin-left:0px;"') + "> <"+"/a><"+"/li>"; | |
6344 sep = true; | |
6345 } | |
6346 }); | |
6347 str = str.replace(/<li class\='vakata-context-separator'\><\/li\>$/,""); | |
6348 if(is_callback) { str += "</ul>"; } | |
6349 /** | |
6350 * triggered on the document when the contextmenu is parsed (HTML is built) | |
6351 * @event | |
6352 * @plugin contextmenu | |
6353 * @name context_parse.vakata | |
6354 * @param {jQuery} reference the element that was right clicked | |
6355 * @param {jQuery} element the DOM element of the menu itself | |
6356 * @param {Object} position the x & y coordinates of the menu | |
6357 */ | |
6358 if(!is_callback) { vakata_context.html = str; $.vakata.context._trigger("parse"); } | |
6359 return str.length > 10 ? str : false; | |
6360 }, | |
6361 _show_submenu : function (o) { | |
6362 o = $(o); | |
6363 if(!o.length || !o.children("ul").length) { return; } | |
6364 var e = o.children("ul"), | |
6365 xl = o.offset().left, | |
6366 x = xl + o.outerWidth(), | |
6367 y = o.offset().top, | |
6368 w = e.width(), | |
6369 h = e.height(), | |
6370 dw = $(window).width() + $(window).scrollLeft(), | |
6371 dh = $(window).height() + $(window).scrollTop(); | |
6372 // може да се спести е една проверка - дали няма някой от класовете вече нагоре | |
6373 if(right_to_left) { | |
6374 o[x - (w + 10 + o.outerWidth()) < 0 ? "addClass" : "removeClass"]("vakata-context-left"); | |
6375 } | |
6376 else { | |
6377 o[x + w > dw && xl > dw - x ? "addClass" : "removeClass"]("vakata-context-right"); | |
6378 } | |
6379 if(y + h + 10 > dh) { | |
6380 e.css("bottom","-1px"); | |
6381 } | |
6382 | |
6383 //if does not fit - stick it to the side | |
6384 if (o.hasClass('vakata-context-right')) { | |
6385 if (xl < w) { | |
6386 e.css("margin-right", xl - w); | |
6387 } | |
6388 } else { | |
6389 if (dw - x < w) { | |
6390 e.css("margin-left", dw - x - w); | |
6391 } | |
6392 } | |
6393 | |
6394 e.show(); | |
6395 }, | |
6396 show : function (reference, position, data) { | |
6397 var o, e, x, y, w, h, dw, dh, cond = true; | |
6398 if(vakata_context.element && vakata_context.element.length) { | |
6399 vakata_context.element.width(''); | |
6400 } | |
6401 switch(cond) { | |
6402 case (!position && !reference): | |
6403 return false; | |
6404 case (!!position && !!reference): | |
6405 vakata_context.reference = reference; | |
6406 vakata_context.position_x = position.x; | |
6407 vakata_context.position_y = position.y; | |
6408 break; | |
6409 case (!position && !!reference): | |
6410 vakata_context.reference = reference; | |
6411 o = reference.offset(); | |
6412 vakata_context.position_x = o.left + reference.outerHeight(); | |
6413 vakata_context.position_y = o.top; | |
6414 break; | |
6415 case (!!position && !reference): | |
6416 vakata_context.position_x = position.x; | |
6417 vakata_context.position_y = position.y; | |
6418 break; | |
6419 } | |
6420 if(!!reference && !data && $(reference).data('vakata_contextmenu')) { | |
6421 data = $(reference).data('vakata_contextmenu'); | |
6422 } | |
6423 if($.vakata.context._parse(data)) { | |
6424 vakata_context.element.html(vakata_context.html); | |
6425 } | |
6426 if(vakata_context.items.length) { | |
6427 vakata_context.element.appendTo("body"); | |
6428 e = vakata_context.element; | |
6429 x = vakata_context.position_x; | |
6430 y = vakata_context.position_y; | |
6431 w = e.width(); | |
6432 h = e.height(); | |
6433 dw = $(window).width() + $(window).scrollLeft(); | |
6434 dh = $(window).height() + $(window).scrollTop(); | |
6435 if(right_to_left) { | |
6436 x -= (e.outerWidth() - $(reference).outerWidth()); | |
6437 if(x < $(window).scrollLeft() + 20) { | |
6438 x = $(window).scrollLeft() + 20; | |
6439 } | |
6440 } | |
6441 if(x + w + 20 > dw) { | |
6442 x = dw - (w + 20); | |
6443 } | |
6444 if(y + h + 20 > dh) { | |
6445 y = dh - (h + 20); | |
6446 } | |
6447 | |
6448 vakata_context.element | |
6449 .css({ "left" : x, "top" : y }) | |
6450 .show() | |
6451 .find('a').first().focus().parent().addClass("vakata-context-hover"); | |
6452 vakata_context.is_visible = true; | |
6453 /** | |
6454 * triggered on the document when the contextmenu is shown | |
6455 * @event | |
6456 * @plugin contextmenu | |
6457 * @name context_show.vakata | |
6458 * @param {jQuery} reference the element that was right clicked | |
6459 * @param {jQuery} element the DOM element of the menu itself | |
6460 * @param {Object} position the x & y coordinates of the menu | |
6461 */ | |
6462 $.vakata.context._trigger("show"); | |
6463 } | |
6464 }, | |
6465 hide : function () { | |
6466 if(vakata_context.is_visible) { | |
6467 vakata_context.element.hide().find("ul").hide().end().find(':focus').blur().end().detach(); | |
6468 vakata_context.is_visible = false; | |
6469 /** | |
6470 * triggered on the document when the contextmenu is hidden | |
6471 * @event | |
6472 * @plugin contextmenu | |
6473 * @name context_hide.vakata | |
6474 * @param {jQuery} reference the element that was right clicked | |
6475 * @param {jQuery} element the DOM element of the menu itself | |
6476 * @param {Object} position the x & y coordinates of the menu | |
6477 */ | |
6478 $.vakata.context._trigger("hide"); | |
6479 } | |
6480 } | |
6481 }; | |
6482 $(function () { | |
6483 right_to_left = $("body").css("direction") === "rtl"; | |
6484 var to = false; | |
6485 | |
6486 vakata_context.element = $("<ul class='vakata-context'></ul>"); | |
6487 vakata_context.element | |
6488 .on("mouseenter", "li", function (e) { | |
6489 e.stopImmediatePropagation(); | |
6490 | |
6491 if($.contains(this, e.relatedTarget)) { | |
6492 // премахнато заради delegate mouseleave по-долу | |
6493 // $(this).find(".vakata-context-hover").removeClass("vakata-context-hover"); | |
6494 return; | |
6495 } | |
6496 | |
6497 if(to) { clearTimeout(to); } | |
6498 vakata_context.element.find(".vakata-context-hover").removeClass("vakata-context-hover").end(); | |
6499 | |
6500 $(this) | |
6501 .siblings().find("ul").hide().end().end() | |
6502 .parentsUntil(".vakata-context", "li").addBack().addClass("vakata-context-hover"); | |
6503 $.vakata.context._show_submenu(this); | |
6504 }) | |
6505 // тестово - дали не натоварва? | |
6506 .on("mouseleave", "li", function (e) { | |
6507 if($.contains(this, e.relatedTarget)) { return; } | |
6508 $(this).find(".vakata-context-hover").addBack().removeClass("vakata-context-hover"); | |
6509 }) | |
6510 .on("mouseleave", function (e) { | |
6511 $(this).find(".vakata-context-hover").removeClass("vakata-context-hover"); | |
6512 if($.vakata.context.settings.hide_onmouseleave) { | |
6513 to = setTimeout( | |
6514 (function (t) { | |
6515 return function () { $.vakata.context.hide(); }; | |
6516 }(this)), $.vakata.context.settings.hide_onmouseleave); | |
6517 } | |
6518 }) | |
6519 .on("click", "a", function (e) { | |
6520 e.preventDefault(); | |
6521 //}) | |
6522 //.on("mouseup", "a", function (e) { | |
6523 if(!$(this).blur().parent().hasClass("vakata-context-disabled") && $.vakata.context._execute($(this).attr("rel")) !== false) { | |
6524 $.vakata.context.hide(); | |
6525 } | |
6526 }) | |
6527 .on('keydown', 'a', function (e) { | |
6528 var o = null; | |
6529 switch(e.which) { | |
6530 case 13: | |
6531 case 32: | |
6532 e.type = "click"; | |
6533 e.preventDefault(); | |
6534 $(e.currentTarget).trigger(e); | |
6535 break; | |
6536 case 37: | |
6537 if(vakata_context.is_visible) { | |
6538 vakata_context.element.find(".vakata-context-hover").last().closest("li").first().find("ul").hide().find(".vakata-context-hover").removeClass("vakata-context-hover").end().end().children('a').focus(); | |
6539 e.stopImmediatePropagation(); | |
6540 e.preventDefault(); | |
6541 } | |
6542 break; | |
6543 case 38: | |
6544 if(vakata_context.is_visible) { | |
6545 o = vakata_context.element.find("ul:visible").addBack().last().children(".vakata-context-hover").removeClass("vakata-context-hover").prevAll("li:not(.vakata-context-separator)").first(); | |
6546 if(!o.length) { o = vakata_context.element.find("ul:visible").addBack().last().children("li:not(.vakata-context-separator)").last(); } | |
6547 o.addClass("vakata-context-hover").children('a').focus(); | |
6548 e.stopImmediatePropagation(); | |
6549 e.preventDefault(); | |
6550 } | |
6551 break; | |
6552 case 39: | |
6553 if(vakata_context.is_visible) { | |
6554 vakata_context.element.find(".vakata-context-hover").last().children("ul").show().children("li:not(.vakata-context-separator)").removeClass("vakata-context-hover").first().addClass("vakata-context-hover").children('a').focus(); | |
6555 e.stopImmediatePropagation(); | |
6556 e.preventDefault(); | |
6557 } | |
6558 break; | |
6559 case 40: | |
6560 if(vakata_context.is_visible) { | |
6561 o = vakata_context.element.find("ul:visible").addBack().last().children(".vakata-context-hover").removeClass("vakata-context-hover").nextAll("li:not(.vakata-context-separator)").first(); | |
6562 if(!o.length) { o = vakata_context.element.find("ul:visible").addBack().last().children("li:not(.vakata-context-separator)").first(); } | |
6563 o.addClass("vakata-context-hover").children('a').focus(); | |
6564 e.stopImmediatePropagation(); | |
6565 e.preventDefault(); | |
6566 } | |
6567 break; | |
6568 case 27: | |
6569 $.vakata.context.hide(); | |
6570 e.preventDefault(); | |
6571 break; | |
6572 default: | |
6573 //console.log(e.which); | |
6574 break; | |
6575 } | |
6576 }) | |
6577 .on('keydown', function (e) { | |
6578 e.preventDefault(); | |
6579 var a = vakata_context.element.find('.vakata-contextmenu-shortcut-' + e.which).parent(); | |
6580 if(a.parent().not('.vakata-context-disabled')) { | |
6581 a.click(); | |
6582 } | |
6583 }); | |
6584 | |
6585 $(document) | |
6586 .on("mousedown.vakata.jstree", function (e) { | |
6587 if(vakata_context.is_visible && vakata_context.element[0] !== e.target && !$.contains(vakata_context.element[0], e.target)) { | |
6588 $.vakata.context.hide(); | |
6589 } | |
6590 }) | |
6591 .on("context_show.vakata.jstree", function (e, data) { | |
6592 vakata_context.element.find("li:has(ul)").children("a").addClass("vakata-context-parent"); | |
6593 if(right_to_left) { | |
6594 vakata_context.element.addClass("vakata-context-rtl").css("direction", "rtl"); | |
6595 } | |
6596 // also apply a RTL class? | |
6597 vakata_context.element.find("ul").hide().end(); | |
6598 }); | |
6599 }); | |
6600 }($)); | |
6601 // $.jstree.defaults.plugins.push("contextmenu"); | |
6602 | |
6603 | |
6604 /** | |
6605 * ### Drag'n'drop plugin | |
6606 * | |
6607 * Enables dragging and dropping of nodes in the tree, resulting in a move or copy operations. | |
6608 */ | |
6609 | |
6610 /** | |
6611 * stores all defaults for the drag'n'drop plugin | |
6612 * @name $.jstree.defaults.dnd | |
6613 * @plugin dnd | |
6614 */ | |
6615 $.jstree.defaults.dnd = { | |
6616 /** | |
6617 * a boolean indicating if a copy should be possible while dragging (by pressint the meta key or Ctrl). Defaults to `true`. | |
6618 * @name $.jstree.defaults.dnd.copy | |
6619 * @plugin dnd | |
6620 */ | |
6621 copy : true, | |
6622 /** | |
6623 * a number indicating how long a node should remain hovered while dragging to be opened. Defaults to `500`. | |
6624 * @name $.jstree.defaults.dnd.open_timeout | |
6625 * @plugin dnd | |
6626 */ | |
6627 open_timeout : 500, | |
6628 /** | |
6629 * a function invoked each time a node is about to be dragged, invoked in the tree's scope and receives the nodes about to be dragged as an argument (array) and the event that started the drag - return `false` to prevent dragging | |
6630 * @name $.jstree.defaults.dnd.is_draggable | |
6631 * @plugin dnd | |
6632 */ | |
6633 is_draggable : true, | |
6634 /** | |
6635 * a boolean indicating if checks should constantly be made while the user is dragging the node (as opposed to checking only on drop), default is `true` | |
6636 * @name $.jstree.defaults.dnd.check_while_dragging | |
6637 * @plugin dnd | |
6638 */ | |
6639 check_while_dragging : true, | |
6640 /** | |
6641 * a boolean indicating if nodes from this tree should only be copied with dnd (as opposed to moved), default is `false` | |
6642 * @name $.jstree.defaults.dnd.always_copy | |
6643 * @plugin dnd | |
6644 */ | |
6645 always_copy : false, | |
6646 /** | |
6647 * when dropping a node "inside", this setting indicates the position the node should go to - it can be an integer or a string: "first" (same as 0) or "last", default is `0` | |
6648 * @name $.jstree.defaults.dnd.inside_pos | |
6649 * @plugin dnd | |
6650 */ | |
6651 inside_pos : 0, | |
6652 /** | |
6653 * when starting the drag on a node that is selected this setting controls if all selected nodes are dragged or only the single node, default is `true`, which means all selected nodes are dragged when the drag is started on a selected node | |
6654 * @name $.jstree.defaults.dnd.drag_selection | |
6655 * @plugin dnd | |
6656 */ | |
6657 drag_selection : true, | |
6658 /** | |
6659 * controls whether dnd works on touch devices. If left as boolean true dnd will work the same as in desktop browsers, which in some cases may impair scrolling. If set to boolean false dnd will not work on touch devices. There is a special third option - string "selected" which means only selected nodes can be dragged on touch devices. | |
6660 * @name $.jstree.defaults.dnd.touch | |
6661 * @plugin dnd | |
6662 */ | |
6663 touch : true, | |
6664 /** | |
6665 * controls whether items can be dropped anywhere on the node, not just on the anchor, by default only the node anchor is a valid drop target. Works best with the wholerow plugin. If enabled on mobile depending on the interface it might be hard for the user to cancel the drop, since the whole tree container will be a valid drop target. | |
6666 * @name $.jstree.defaults.dnd.large_drop_target | |
6667 * @plugin dnd | |
6668 */ | |
6669 large_drop_target : false, | |
6670 /** | |
6671 * controls whether a drag can be initiated from any part of the node and not just the text/icon part, works best with the wholerow plugin. Keep in mind it can cause problems with tree scrolling on mobile depending on the interface - in that case set the touch option to "selected". | |
6672 * @name $.jstree.defaults.dnd.large_drag_target | |
6673 * @plugin dnd | |
6674 */ | |
6675 large_drag_target : false, | |
6676 /** | |
6677 * controls whether use HTML5 dnd api instead of classical. That will allow better integration of dnd events with other HTML5 controls. | |
6678 * @reference http://caniuse.com/#feat=dragndrop | |
6679 * @name $.jstree.defaults.dnd.use_html5 | |
6680 * @plugin dnd | |
6681 */ | |
6682 use_html5: false | |
6683 }; | |
6684 var drg, elm; | |
6685 // TODO: now check works by checking for each node individually, how about max_children, unique, etc? | |
6686 $.jstree.plugins.dnd = function (options, parent) { | |
6687 this.init = function (el, options) { | |
6688 parent.init.call(this, el, options); | |
6689 this.settings.dnd.use_html5 = this.settings.dnd.use_html5 && ('draggable' in document.createElement('span')); | |
6690 }; | |
6691 this.bind = function () { | |
6692 parent.bind.call(this); | |
6693 | |
6694 this.element | |
6695 .on(this.settings.dnd.use_html5 ? 'dragstart.jstree' : 'mousedown.jstree touchstart.jstree', this.settings.dnd.large_drag_target ? '.jstree-node' : '.jstree-anchor', $.proxy(function (e) { | |
6696 if(this.settings.dnd.large_drag_target && $(e.target).closest('.jstree-node')[0] !== e.currentTarget) { | |
6697 return true; | |
6698 } | |
6699 if(e.type === "touchstart" && (!this.settings.dnd.touch || (this.settings.dnd.touch === 'selected' && !$(e.currentTarget).closest('.jstree-node').children('.jstree-anchor').hasClass('jstree-clicked')))) { | |
6700 return true; | |
6701 } | |
6702 var obj = this.get_node(e.target), | |
6703 mlt = this.is_selected(obj) && this.settings.dnd.drag_selection ? this.get_top_selected().length : 1, | |
6704 txt = (mlt > 1 ? mlt + ' ' + this.get_string('nodes') : this.get_text(e.currentTarget)); | |
6705 if(this.settings.core.force_text) { | |
6706 txt = $.vakata.html.escape(txt); | |
6707 } | |
6708 if(obj && obj.id && obj.id !== $.jstree.root && (e.which === 1 || e.type === "touchstart" || e.type === "dragstart") && | |
6709 (this.settings.dnd.is_draggable === true || ($.isFunction(this.settings.dnd.is_draggable) && this.settings.dnd.is_draggable.call(this, (mlt > 1 ? this.get_top_selected(true) : [obj]), e))) | |
6710 ) { | |
6711 drg = { 'jstree' : true, 'origin' : this, 'obj' : this.get_node(obj,true), 'nodes' : mlt > 1 ? this.get_top_selected() : [obj.id] }; | |
6712 elm = e.currentTarget; | |
6713 if (this.settings.dnd.use_html5) { | |
6714 $.vakata.dnd._trigger('start', e, { 'helper': $(), 'element': elm, 'data': drg }); | |
6715 } else { | |
6716 this.element.trigger('mousedown.jstree'); | |
6717 return $.vakata.dnd.start(e, drg, '<div id="jstree-dnd" class="jstree-' + this.get_theme() + ' jstree-' + this.get_theme() + '-' + this.get_theme_variant() + ' ' + ( this.settings.core.themes.responsive ? ' jstree-dnd-responsive' : '' ) + '"><i class="jstree-icon jstree-er"></i>' + txt + '<ins class="jstree-copy" style="display:none;">+</ins></div>'); | |
6718 } | |
6719 } | |
6720 }, this)); | |
6721 if (this.settings.dnd.use_html5) { | |
6722 this.element | |
6723 .on('dragover.jstree', function (e) { | |
6724 e.preventDefault(); | |
6725 $.vakata.dnd._trigger('move', e, { 'helper': $(), 'element': elm, 'data': drg }); | |
6726 return false; | |
6727 }) | |
6728 //.on('dragenter.jstree', this.settings.dnd.large_drop_target ? '.jstree-node' : '.jstree-anchor', $.proxy(function (e) { | |
6729 // e.preventDefault(); | |
6730 // $.vakata.dnd._trigger('move', e, { 'helper': $(), 'element': elm, 'data': drg }); | |
6731 // return false; | |
6732 // }, this)) | |
6733 .on('drop.jstree', $.proxy(function (e) { | |
6734 e.preventDefault(); | |
6735 $.vakata.dnd._trigger('stop', e, { 'helper': $(), 'element': elm, 'data': drg }); | |
6736 return false; | |
6737 }, this)); | |
6738 } | |
6739 }; | |
6740 this.redraw_node = function(obj, deep, callback, force_render) { | |
6741 obj = parent.redraw_node.apply(this, arguments); | |
6742 if (obj && this.settings.dnd.use_html5) { | |
6743 if (this.settings.dnd.large_drag_target) { | |
6744 obj.setAttribute('draggable', true); | |
6745 } else { | |
6746 var i, j, tmp = null; | |
6747 for(i = 0, j = obj.childNodes.length; i < j; i++) { | |
6748 if(obj.childNodes[i] && obj.childNodes[i].className && obj.childNodes[i].className.indexOf("jstree-anchor") !== -1) { | |
6749 tmp = obj.childNodes[i]; | |
6750 break; | |
6751 } | |
6752 } | |
6753 if(tmp) { | |
6754 tmp.setAttribute('draggable', true); | |
6755 } | |
6756 } | |
6757 } | |
6758 return obj; | |
6759 }; | |
6760 }; | |
6761 | |
6762 $(function() { | |
6763 // bind only once for all instances | |
6764 var lastmv = false, | |
6765 laster = false, | |
6766 lastev = false, | |
6767 opento = false, | |
6768 marker = $('<div id="jstree-marker"> </div>').hide(); //.appendTo('body'); | |
6769 | |
6770 $(document) | |
6771 .on('dnd_start.vakata.jstree', function (e, data) { | |
6772 lastmv = false; | |
6773 lastev = false; | |
6774 if(!data || !data.data || !data.data.jstree) { return; } | |
6775 marker.appendTo('body'); //.show(); | |
6776 }) | |
6777 .on('dnd_move.vakata.jstree', function (e, data) { | |
6778 var isDifferentNode = data.event.target !== lastev.target; | |
6779 if(opento) { | |
6780 if (!data.event || data.event.type !== 'dragover' || isDifferentNode) { | |
6781 clearTimeout(opento); | |
6782 } | |
6783 } | |
6784 if(!data || !data.data || !data.data.jstree) { return; } | |
6785 | |
6786 // if we are hovering the marker image do nothing (can happen on "inside" drags) | |
6787 if(data.event.target.id && data.event.target.id === 'jstree-marker') { | |
6788 return; | |
6789 } | |
6790 lastev = data.event; | |
6791 | |
6792 var ins = $.jstree.reference(data.event.target), | |
6793 ref = false, | |
6794 off = false, | |
6795 rel = false, | |
6796 tmp, l, t, h, p, i, o, ok, t1, t2, op, ps, pr, ip, tm, is_copy, pn; | |
6797 // if we are over an instance | |
6798 if(ins && ins._data && ins._data.dnd) { | |
6799 marker.attr('class', 'jstree-' + ins.get_theme() + ( ins.settings.core.themes.responsive ? ' jstree-dnd-responsive' : '' )); | |
6800 is_copy = data.data.origin && (data.data.origin.settings.dnd.always_copy || (data.data.origin.settings.dnd.copy && (data.event.metaKey || data.event.ctrlKey))); | |
6801 data.helper | |
6802 .children().attr('class', 'jstree-' + ins.get_theme() + ' jstree-' + ins.get_theme() + '-' + ins.get_theme_variant() + ' ' + ( ins.settings.core.themes.responsive ? ' jstree-dnd-responsive' : '' )) | |
6803 .find('.jstree-copy').first()[ is_copy ? 'show' : 'hide' ](); | |
6804 | |
6805 // if are hovering the container itself add a new root node | |
6806 //console.log(data.event); | |
6807 if( (data.event.target === ins.element[0] || data.event.target === ins.get_container_ul()[0]) && ins.get_container_ul().children().length === 0) { | |
6808 ok = true; | |
6809 for(t1 = 0, t2 = data.data.nodes.length; t1 < t2; t1++) { | |
6810 ok = ok && ins.check( (data.data.origin && (data.data.origin.settings.dnd.always_copy || (data.data.origin.settings.dnd.copy && (data.event.metaKey || data.event.ctrlKey)) ) ? "copy_node" : "move_node"), (data.data.origin && data.data.origin !== ins ? data.data.origin.get_node(data.data.nodes[t1]) : data.data.nodes[t1]), $.jstree.root, 'last', { 'dnd' : true, 'ref' : ins.get_node($.jstree.root), 'pos' : 'i', 'origin' : data.data.origin, 'is_multi' : (data.data.origin && data.data.origin !== ins), 'is_foreign' : (!data.data.origin) }); | |
6811 if(!ok) { break; } | |
6812 } | |
6813 if(ok) { | |
6814 lastmv = { 'ins' : ins, 'par' : $.jstree.root, 'pos' : 'last' }; | |
6815 marker.hide(); | |
6816 data.helper.find('.jstree-icon').first().removeClass('jstree-er').addClass('jstree-ok'); | |
6817 if (data.event.originalEvent && data.event.originalEvent.dataTransfer) { | |
6818 data.event.originalEvent.dataTransfer.dropEffect = is_copy ? 'copy' : 'move'; | |
6819 } | |
6820 return; | |
6821 } | |
6822 } | |
6823 else { | |
6824 // if we are hovering a tree node | |
6825 ref = ins.settings.dnd.large_drop_target ? $(data.event.target).closest('.jstree-node').children('.jstree-anchor') : $(data.event.target).closest('.jstree-anchor'); | |
6826 if(ref && ref.length && ref.parent().is('.jstree-closed, .jstree-open, .jstree-leaf')) { | |
6827 off = ref.offset(); | |
6828 rel = (data.event.pageY !== undefined ? data.event.pageY : data.event.originalEvent.pageY) - off.top; | |
6829 h = ref.outerHeight(); | |
6830 if(rel < h / 3) { | |
6831 o = ['b', 'i', 'a']; | |
6832 } | |
6833 else if(rel > h - h / 3) { | |
6834 o = ['a', 'i', 'b']; | |
6835 } | |
6836 else { | |
6837 o = rel > h / 2 ? ['i', 'a', 'b'] : ['i', 'b', 'a']; | |
6838 } | |
6839 $.each(o, function (j, v) { | |
6840 switch(v) { | |
6841 case 'b': | |
6842 l = off.left - 6; | |
6843 t = off.top; | |
6844 p = ins.get_parent(ref); | |
6845 i = ref.parent().index(); | |
6846 break; | |
6847 case 'i': | |
6848 ip = ins.settings.dnd.inside_pos; | |
6849 tm = ins.get_node(ref.parent()); | |
6850 l = off.left - 2; | |
6851 t = off.top + h / 2 + 1; | |
6852 p = tm.id; | |
6853 i = ip === 'first' ? 0 : (ip === 'last' ? tm.children.length : Math.min(ip, tm.children.length)); | |
6854 break; | |
6855 case 'a': | |
6856 l = off.left - 6; | |
6857 t = off.top + h; | |
6858 p = ins.get_parent(ref); | |
6859 i = ref.parent().index() + 1; | |
6860 break; | |
6861 } | |
6862 ok = true; | |
6863 for(t1 = 0, t2 = data.data.nodes.length; t1 < t2; t1++) { | |
6864 op = data.data.origin && (data.data.origin.settings.dnd.always_copy || (data.data.origin.settings.dnd.copy && (data.event.metaKey || data.event.ctrlKey))) ? "copy_node" : "move_node"; | |
6865 ps = i; | |
6866 if(op === "move_node" && v === 'a' && (data.data.origin && data.data.origin === ins) && p === ins.get_parent(data.data.nodes[t1])) { | |
6867 pr = ins.get_node(p); | |
6868 if(ps > $.inArray(data.data.nodes[t1], pr.children)) { | |
6869 ps -= 1; | |
6870 } | |
6871 } | |
6872 ok = ok && ( (ins && ins.settings && ins.settings.dnd && ins.settings.dnd.check_while_dragging === false) || ins.check(op, (data.data.origin && data.data.origin !== ins ? data.data.origin.get_node(data.data.nodes[t1]) : data.data.nodes[t1]), p, ps, { 'dnd' : true, 'ref' : ins.get_node(ref.parent()), 'pos' : v, 'origin' : data.data.origin, 'is_multi' : (data.data.origin && data.data.origin !== ins), 'is_foreign' : (!data.data.origin) }) ); | |
6873 if(!ok) { | |
6874 if(ins && ins.last_error) { laster = ins.last_error(); } | |
6875 break; | |
6876 } | |
6877 } | |
6878 if(v === 'i' && ref.parent().is('.jstree-closed') && ins.settings.dnd.open_timeout) { | |
6879 if (!data.event || data.event.type !== 'dragover' || isDifferentNode) { | |
6880 if (opento) { clearTimeout(opento); } | |
6881 opento = setTimeout((function (x, z) { return function () { x.open_node(z); }; }(ins, ref)), ins.settings.dnd.open_timeout); | |
6882 } | |
6883 } | |
6884 if(ok) { | |
6885 pn = ins.get_node(p, true); | |
6886 if (!pn.hasClass('.jstree-dnd-parent')) { | |
6887 $('.jstree-dnd-parent').removeClass('jstree-dnd-parent'); | |
6888 pn.addClass('jstree-dnd-parent'); | |
6889 } | |
6890 lastmv = { 'ins' : ins, 'par' : p, 'pos' : v === 'i' && ip === 'last' && i === 0 && !ins.is_loaded(tm) ? 'last' : i }; | |
6891 marker.css({ 'left' : l + 'px', 'top' : t + 'px' }).show(); | |
6892 data.helper.find('.jstree-icon').first().removeClass('jstree-er').addClass('jstree-ok'); | |
6893 if (data.event.originalEvent && data.event.originalEvent.dataTransfer) { | |
6894 data.event.originalEvent.dataTransfer.dropEffect = is_copy ? 'copy' : 'move'; | |
6895 } | |
6896 laster = {}; | |
6897 o = true; | |
6898 return false; | |
6899 } | |
6900 }); | |
6901 if(o === true) { return; } | |
6902 } | |
6903 } | |
6904 } | |
6905 $('.jstree-dnd-parent').removeClass('jstree-dnd-parent'); | |
6906 lastmv = false; | |
6907 data.helper.find('.jstree-icon').removeClass('jstree-ok').addClass('jstree-er'); | |
6908 if (data.event.originalEvent && data.event.originalEvent.dataTransfer) { | |
6909 data.event.originalEvent.dataTransfer.dropEffect = 'none'; | |
6910 } | |
6911 marker.hide(); | |
6912 }) | |
6913 .on('dnd_scroll.vakata.jstree', function (e, data) { | |
6914 if(!data || !data.data || !data.data.jstree) { return; } | |
6915 marker.hide(); | |
6916 lastmv = false; | |
6917 lastev = false; | |
6918 data.helper.find('.jstree-icon').first().removeClass('jstree-ok').addClass('jstree-er'); | |
6919 }) | |
6920 .on('dnd_stop.vakata.jstree', function (e, data) { | |
6921 $('.jstree-dnd-parent').removeClass('jstree-dnd-parent'); | |
6922 if(opento) { clearTimeout(opento); } | |
6923 if(!data || !data.data || !data.data.jstree) { return; } | |
6924 marker.hide().detach(); | |
6925 var i, j, nodes = []; | |
6926 if(lastmv) { | |
6927 for(i = 0, j = data.data.nodes.length; i < j; i++) { | |
6928 nodes[i] = data.data.origin ? data.data.origin.get_node(data.data.nodes[i]) : data.data.nodes[i]; | |
6929 } | |
6930 lastmv.ins[ data.data.origin && (data.data.origin.settings.dnd.always_copy || (data.data.origin.settings.dnd.copy && (data.event.metaKey || data.event.ctrlKey))) ? 'copy_node' : 'move_node' ](nodes, lastmv.par, lastmv.pos, false, false, false, data.data.origin); | |
6931 } | |
6932 else { | |
6933 i = $(data.event.target).closest('.jstree'); | |
6934 if(i.length && laster && laster.error && laster.error === 'check') { | |
6935 i = i.jstree(true); | |
6936 if(i) { | |
6937 i.settings.core.error.call(this, laster); | |
6938 } | |
6939 } | |
6940 } | |
6941 lastev = false; | |
6942 lastmv = false; | |
6943 }) | |
6944 .on('keyup.jstree keydown.jstree', function (e, data) { | |
6945 data = $.vakata.dnd._get(); | |
6946 if(data && data.data && data.data.jstree) { | |
6947 if (e.type === "keyup" && e.which === 27) { | |
6948 if (opento) { clearTimeout(opento); } | |
6949 lastmv = false; | |
6950 laster = false; | |
6951 lastev = false; | |
6952 opento = false; | |
6953 marker.hide().detach(); | |
6954 $.vakata.dnd._clean(); | |
6955 } else { | |
6956 data.helper.find('.jstree-copy').first()[ data.data.origin && (data.data.origin.settings.dnd.always_copy || (data.data.origin.settings.dnd.copy && (e.metaKey || e.ctrlKey))) ? 'show' : 'hide' ](); | |
6957 if(lastev) { | |
6958 lastev.metaKey = e.metaKey; | |
6959 lastev.ctrlKey = e.ctrlKey; | |
6960 $.vakata.dnd._trigger('move', lastev); | |
6961 } | |
6962 } | |
6963 } | |
6964 }); | |
6965 }); | |
6966 | |
6967 // helpers | |
6968 (function ($) { | |
6969 $.vakata.html = { | |
6970 div : $('<div />'), | |
6971 escape : function (str) { | |
6972 return $.vakata.html.div.text(str).html(); | |
6973 }, | |
6974 strip : function (str) { | |
6975 return $.vakata.html.div.empty().append($.parseHTML(str)).text(); | |
6976 } | |
6977 }; | |
6978 // private variable | |
6979 var vakata_dnd = { | |
6980 element : false, | |
6981 target : false, | |
6982 is_down : false, | |
6983 is_drag : false, | |
6984 helper : false, | |
6985 helper_w: 0, | |
6986 data : false, | |
6987 init_x : 0, | |
6988 init_y : 0, | |
6989 scroll_l: 0, | |
6990 scroll_t: 0, | |
6991 scroll_e: false, | |
6992 scroll_i: false, | |
6993 is_touch: false | |
6994 }; | |
6995 $.vakata.dnd = { | |
6996 settings : { | |
6997 scroll_speed : 10, | |
6998 scroll_proximity : 20, | |
6999 helper_left : 5, | |
7000 helper_top : 10, | |
7001 threshold : 5, | |
7002 threshold_touch : 10 | |
7003 }, | |
7004 _trigger : function (event_name, e, data) { | |
7005 if (data === undefined) { | |
7006 data = $.vakata.dnd._get(); | |
7007 } | |
7008 data.event = e; | |
7009 $(document).triggerHandler("dnd_" + event_name + ".vakata", data); | |
7010 }, | |
7011 _get : function () { | |
7012 return { | |
7013 "data" : vakata_dnd.data, | |
7014 "element" : vakata_dnd.element, | |
7015 "helper" : vakata_dnd.helper | |
7016 }; | |
7017 }, | |
7018 _clean : function () { | |
7019 if(vakata_dnd.helper) { vakata_dnd.helper.remove(); } | |
7020 if(vakata_dnd.scroll_i) { clearInterval(vakata_dnd.scroll_i); vakata_dnd.scroll_i = false; } | |
7021 vakata_dnd = { | |
7022 element : false, | |
7023 target : false, | |
7024 is_down : false, | |
7025 is_drag : false, | |
7026 helper : false, | |
7027 helper_w: 0, | |
7028 data : false, | |
7029 init_x : 0, | |
7030 init_y : 0, | |
7031 scroll_l: 0, | |
7032 scroll_t: 0, | |
7033 scroll_e: false, | |
7034 scroll_i: false, | |
7035 is_touch: false | |
7036 }; | |
7037 $(document).off("mousemove.vakata.jstree touchmove.vakata.jstree", $.vakata.dnd.drag); | |
7038 $(document).off("mouseup.vakata.jstree touchend.vakata.jstree", $.vakata.dnd.stop); | |
7039 }, | |
7040 _scroll : function (init_only) { | |
7041 if(!vakata_dnd.scroll_e || (!vakata_dnd.scroll_l && !vakata_dnd.scroll_t)) { | |
7042 if(vakata_dnd.scroll_i) { clearInterval(vakata_dnd.scroll_i); vakata_dnd.scroll_i = false; } | |
7043 return false; | |
7044 } | |
7045 if(!vakata_dnd.scroll_i) { | |
7046 vakata_dnd.scroll_i = setInterval($.vakata.dnd._scroll, 100); | |
7047 return false; | |
7048 } | |
7049 if(init_only === true) { return false; } | |
7050 | |
7051 var i = vakata_dnd.scroll_e.scrollTop(), | |
7052 j = vakata_dnd.scroll_e.scrollLeft(); | |
7053 vakata_dnd.scroll_e.scrollTop(i + vakata_dnd.scroll_t * $.vakata.dnd.settings.scroll_speed); | |
7054 vakata_dnd.scroll_e.scrollLeft(j + vakata_dnd.scroll_l * $.vakata.dnd.settings.scroll_speed); | |
7055 if(i !== vakata_dnd.scroll_e.scrollTop() || j !== vakata_dnd.scroll_e.scrollLeft()) { | |
7056 /** | |
7057 * triggered on the document when a drag causes an element to scroll | |
7058 * @event | |
7059 * @plugin dnd | |
7060 * @name dnd_scroll.vakata | |
7061 * @param {Mixed} data any data supplied with the call to $.vakata.dnd.start | |
7062 * @param {DOM} element the DOM element being dragged | |
7063 * @param {jQuery} helper the helper shown next to the mouse | |
7064 * @param {jQuery} event the element that is scrolling | |
7065 */ | |
7066 $.vakata.dnd._trigger("scroll", vakata_dnd.scroll_e); | |
7067 } | |
7068 }, | |
7069 start : function (e, data, html) { | |
7070 if(e.type === "touchstart" && e.originalEvent && e.originalEvent.changedTouches && e.originalEvent.changedTouches[0]) { | |
7071 e.pageX = e.originalEvent.changedTouches[0].pageX; | |
7072 e.pageY = e.originalEvent.changedTouches[0].pageY; | |
7073 e.target = document.elementFromPoint(e.originalEvent.changedTouches[0].pageX - window.pageXOffset, e.originalEvent.changedTouches[0].pageY - window.pageYOffset); | |
7074 } | |
7075 if(vakata_dnd.is_drag) { $.vakata.dnd.stop({}); } | |
7076 try { | |
7077 e.currentTarget.unselectable = "on"; | |
7078 e.currentTarget.onselectstart = function() { return false; }; | |
7079 if(e.currentTarget.style) { | |
7080 e.currentTarget.style.touchAction = "none"; | |
7081 e.currentTarget.style.msTouchAction = "none"; | |
7082 e.currentTarget.style.MozUserSelect = "none"; | |
7083 } | |
7084 } catch(ignore) { } | |
7085 vakata_dnd.init_x = e.pageX; | |
7086 vakata_dnd.init_y = e.pageY; | |
7087 vakata_dnd.data = data; | |
7088 vakata_dnd.is_down = true; | |
7089 vakata_dnd.element = e.currentTarget; | |
7090 vakata_dnd.target = e.target; | |
7091 vakata_dnd.is_touch = e.type === "touchstart"; | |
7092 if(html !== false) { | |
7093 vakata_dnd.helper = $("<div id='vakata-dnd'></div>").html(html).css({ | |
7094 "display" : "block", | |
7095 "margin" : "0", | |
7096 "padding" : "0", | |
7097 "position" : "absolute", | |
7098 "top" : "-2000px", | |
7099 "lineHeight" : "16px", | |
7100 "zIndex" : "10000" | |
7101 }); | |
7102 } | |
7103 $(document).on("mousemove.vakata.jstree touchmove.vakata.jstree", $.vakata.dnd.drag); | |
7104 $(document).on("mouseup.vakata.jstree touchend.vakata.jstree", $.vakata.dnd.stop); | |
7105 return false; | |
7106 }, | |
7107 drag : function (e) { | |
7108 if(e.type === "touchmove" && e.originalEvent && e.originalEvent.changedTouches && e.originalEvent.changedTouches[0]) { | |
7109 e.pageX = e.originalEvent.changedTouches[0].pageX; | |
7110 e.pageY = e.originalEvent.changedTouches[0].pageY; | |
7111 e.target = document.elementFromPoint(e.originalEvent.changedTouches[0].pageX - window.pageXOffset, e.originalEvent.changedTouches[0].pageY - window.pageYOffset); | |
7112 } | |
7113 if(!vakata_dnd.is_down) { return; } | |
7114 if(!vakata_dnd.is_drag) { | |
7115 if( | |
7116 Math.abs(e.pageX - vakata_dnd.init_x) > (vakata_dnd.is_touch ? $.vakata.dnd.settings.threshold_touch : $.vakata.dnd.settings.threshold) || | |
7117 Math.abs(e.pageY - vakata_dnd.init_y) > (vakata_dnd.is_touch ? $.vakata.dnd.settings.threshold_touch : $.vakata.dnd.settings.threshold) | |
7118 ) { | |
7119 if(vakata_dnd.helper) { | |
7120 vakata_dnd.helper.appendTo("body"); | |
7121 vakata_dnd.helper_w = vakata_dnd.helper.outerWidth(); | |
7122 } | |
7123 vakata_dnd.is_drag = true; | |
7124 $(vakata_dnd.target).one('click.vakata', false); | |
7125 /** | |
7126 * triggered on the document when a drag starts | |
7127 * @event | |
7128 * @plugin dnd | |
7129 * @name dnd_start.vakata | |
7130 * @param {Mixed} data any data supplied with the call to $.vakata.dnd.start | |
7131 * @param {DOM} element the DOM element being dragged | |
7132 * @param {jQuery} helper the helper shown next to the mouse | |
7133 * @param {Object} event the event that caused the start (probably mousemove) | |
7134 */ | |
7135 $.vakata.dnd._trigger("start", e); | |
7136 } | |
7137 else { return; } | |
7138 } | |
7139 | |
7140 var d = false, w = false, | |
7141 dh = false, wh = false, | |
7142 dw = false, ww = false, | |
7143 dt = false, dl = false, | |
7144 ht = false, hl = false; | |
7145 | |
7146 vakata_dnd.scroll_t = 0; | |
7147 vakata_dnd.scroll_l = 0; | |
7148 vakata_dnd.scroll_e = false; | |
7149 $($(e.target).parentsUntil("body").addBack().get().reverse()) | |
7150 .filter(function () { | |
7151 return (/^auto|scroll$/).test($(this).css("overflow")) && | |
7152 (this.scrollHeight > this.offsetHeight || this.scrollWidth > this.offsetWidth); | |
7153 }) | |
7154 .each(function () { | |
7155 var t = $(this), o = t.offset(); | |
7156 if(this.scrollHeight > this.offsetHeight) { | |
7157 if(o.top + t.height() - e.pageY < $.vakata.dnd.settings.scroll_proximity) { vakata_dnd.scroll_t = 1; } | |
7158 if(e.pageY - o.top < $.vakata.dnd.settings.scroll_proximity) { vakata_dnd.scroll_t = -1; } | |
7159 } | |
7160 if(this.scrollWidth > this.offsetWidth) { | |
7161 if(o.left + t.width() - e.pageX < $.vakata.dnd.settings.scroll_proximity) { vakata_dnd.scroll_l = 1; } | |
7162 if(e.pageX - o.left < $.vakata.dnd.settings.scroll_proximity) { vakata_dnd.scroll_l = -1; } | |
7163 } | |
7164 if(vakata_dnd.scroll_t || vakata_dnd.scroll_l) { | |
7165 vakata_dnd.scroll_e = $(this); | |
7166 return false; | |
7167 } | |
7168 }); | |
7169 | |
7170 if(!vakata_dnd.scroll_e) { | |
7171 d = $(document); w = $(window); | |
7172 dh = d.height(); wh = w.height(); | |
7173 dw = d.width(); ww = w.width(); | |
7174 dt = d.scrollTop(); dl = d.scrollLeft(); | |
7175 if(dh > wh && e.pageY - dt < $.vakata.dnd.settings.scroll_proximity) { vakata_dnd.scroll_t = -1; } | |
7176 if(dh > wh && wh - (e.pageY - dt) < $.vakata.dnd.settings.scroll_proximity) { vakata_dnd.scroll_t = 1; } | |
7177 if(dw > ww && e.pageX - dl < $.vakata.dnd.settings.scroll_proximity) { vakata_dnd.scroll_l = -1; } | |
7178 if(dw > ww && ww - (e.pageX - dl) < $.vakata.dnd.settings.scroll_proximity) { vakata_dnd.scroll_l = 1; } | |
7179 if(vakata_dnd.scroll_t || vakata_dnd.scroll_l) { | |
7180 vakata_dnd.scroll_e = d; | |
7181 } | |
7182 } | |
7183 if(vakata_dnd.scroll_e) { $.vakata.dnd._scroll(true); } | |
7184 | |
7185 if(vakata_dnd.helper) { | |
7186 ht = parseInt(e.pageY + $.vakata.dnd.settings.helper_top, 10); | |
7187 hl = parseInt(e.pageX + $.vakata.dnd.settings.helper_left, 10); | |
7188 if(dh && ht + 25 > dh) { ht = dh - 50; } | |
7189 if(dw && hl + vakata_dnd.helper_w > dw) { hl = dw - (vakata_dnd.helper_w + 2); } | |
7190 vakata_dnd.helper.css({ | |
7191 left : hl + "px", | |
7192 top : ht + "px" | |
7193 }); | |
7194 } | |
7195 /** | |
7196 * triggered on the document when a drag is in progress | |
7197 * @event | |
7198 * @plugin dnd | |
7199 * @name dnd_move.vakata | |
7200 * @param {Mixed} data any data supplied with the call to $.vakata.dnd.start | |
7201 * @param {DOM} element the DOM element being dragged | |
7202 * @param {jQuery} helper the helper shown next to the mouse | |
7203 * @param {Object} event the event that caused this to trigger (most likely mousemove) | |
7204 */ | |
7205 $.vakata.dnd._trigger("move", e); | |
7206 return false; | |
7207 }, | |
7208 stop : function (e) { | |
7209 if(e.type === "touchend" && e.originalEvent && e.originalEvent.changedTouches && e.originalEvent.changedTouches[0]) { | |
7210 e.pageX = e.originalEvent.changedTouches[0].pageX; | |
7211 e.pageY = e.originalEvent.changedTouches[0].pageY; | |
7212 e.target = document.elementFromPoint(e.originalEvent.changedTouches[0].pageX - window.pageXOffset, e.originalEvent.changedTouches[0].pageY - window.pageYOffset); | |
7213 } | |
7214 if(vakata_dnd.is_drag) { | |
7215 /** | |
7216 * triggered on the document when a drag stops (the dragged element is dropped) | |
7217 * @event | |
7218 * @plugin dnd | |
7219 * @name dnd_stop.vakata | |
7220 * @param {Mixed} data any data supplied with the call to $.vakata.dnd.start | |
7221 * @param {DOM} element the DOM element being dragged | |
7222 * @param {jQuery} helper the helper shown next to the mouse | |
7223 * @param {Object} event the event that caused the stop | |
7224 */ | |
7225 if (e.target !== vakata_dnd.target) { | |
7226 $(vakata_dnd.target).off('click.vakata'); | |
7227 } | |
7228 $.vakata.dnd._trigger("stop", e); | |
7229 } | |
7230 else { | |
7231 if(e.type === "touchend" && e.target === vakata_dnd.target) { | |
7232 var to = setTimeout(function () { $(e.target).click(); }, 100); | |
7233 $(e.target).one('click', function() { if(to) { clearTimeout(to); } }); | |
7234 } | |
7235 } | |
7236 $.vakata.dnd._clean(); | |
7237 return false; | |
7238 } | |
7239 }; | |
7240 }($)); | |
7241 | |
7242 // include the dnd plugin by default | |
7243 // $.jstree.defaults.plugins.push("dnd"); | |
7244 | |
7245 | |
7246 /** | |
7247 * ### Massload plugin | |
7248 * | |
7249 * Adds massload functionality to jsTree, so that multiple nodes can be loaded in a single request (only useful with lazy loading). | |
7250 */ | |
7251 | |
7252 /** | |
7253 * massload configuration | |
7254 * | |
7255 * It is possible to set this to a standard jQuery-like AJAX config. | |
7256 * In addition to the standard jQuery ajax options here you can supply functions for `data` and `url`, the functions will be run in the current instance's scope and a param will be passed indicating which node IDs need to be loaded, the return value of those functions will be used. | |
7257 * | |
7258 * You can also set this to a function, that function will receive the node IDs being loaded as argument and a second param which is a function (callback) which should be called with the result. | |
7259 * | |
7260 * Both the AJAX and the function approach rely on the same return value - an object where the keys are the node IDs, and the value is the children of that node as an array. | |
7261 * | |
7262 * { | |
7263 * "id1" : [{ "text" : "Child of ID1", "id" : "c1" }, { "text" : "Another child of ID1", "id" : "c2" }], | |
7264 * "id2" : [{ "text" : "Child of ID2", "id" : "c3" }] | |
7265 * } | |
7266 * | |
7267 * @name $.jstree.defaults.massload | |
7268 * @plugin massload | |
7269 */ | |
7270 $.jstree.defaults.massload = null; | |
7271 $.jstree.plugins.massload = function (options, parent) { | |
7272 this.init = function (el, options) { | |
7273 this._data.massload = {}; | |
7274 parent.init.call(this, el, options); | |
7275 }; | |
7276 this._load_nodes = function (nodes, callback, is_callback, force_reload) { | |
7277 var s = this.settings.massload, | |
7278 nodesString = JSON.stringify(nodes), | |
7279 toLoad = [], | |
7280 m = this._model.data, | |
7281 i, j, dom; | |
7282 if (!is_callback) { | |
7283 for(i = 0, j = nodes.length; i < j; i++) { | |
7284 if(!m[nodes[i]] || ( (!m[nodes[i]].state.loaded && !m[nodes[i]].state.failed) || force_reload) ) { | |
7285 toLoad.push(nodes[i]); | |
7286 dom = this.get_node(nodes[i], true); | |
7287 if (dom && dom.length) { | |
7288 dom.addClass("jstree-loading").attr('aria-busy',true); | |
7289 } | |
7290 } | |
7291 } | |
7292 this._data.massload = {}; | |
7293 if (toLoad.length) { | |
7294 if($.isFunction(s)) { | |
7295 return s.call(this, toLoad, $.proxy(function (data) { | |
7296 var i, j; | |
7297 if(data) { | |
7298 for(i in data) { | |
7299 if(data.hasOwnProperty(i)) { | |
7300 this._data.massload[i] = data[i]; | |
7301 } | |
7302 } | |
7303 } | |
7304 for(i = 0, j = nodes.length; i < j; i++) { | |
7305 dom = this.get_node(nodes[i], true); | |
7306 if (dom && dom.length) { | |
7307 dom.removeClass("jstree-loading").attr('aria-busy',false); | |
7308 } | |
7309 } | |
7310 parent._load_nodes.call(this, nodes, callback, is_callback, force_reload); | |
7311 }, this)); | |
7312 } | |
7313 if(typeof s === 'object' && s && s.url) { | |
7314 s = $.extend(true, {}, s); | |
7315 if($.isFunction(s.url)) { | |
7316 s.url = s.url.call(this, toLoad); | |
7317 } | |
7318 if($.isFunction(s.data)) { | |
7319 s.data = s.data.call(this, toLoad); | |
7320 } | |
7321 return $.ajax(s) | |
7322 .done($.proxy(function (data,t,x) { | |
7323 var i, j; | |
7324 if(data) { | |
7325 for(i in data) { | |
7326 if(data.hasOwnProperty(i)) { | |
7327 this._data.massload[i] = data[i]; | |
7328 } | |
7329 } | |
7330 } | |
7331 for(i = 0, j = nodes.length; i < j; i++) { | |
7332 dom = this.get_node(nodes[i], true); | |
7333 if (dom && dom.length) { | |
7334 dom.removeClass("jstree-loading").attr('aria-busy',false); | |
7335 } | |
7336 } | |
7337 parent._load_nodes.call(this, nodes, callback, is_callback, force_reload); | |
7338 }, this)) | |
7339 .fail($.proxy(function (f) { | |
7340 parent._load_nodes.call(this, nodes, callback, is_callback, force_reload); | |
7341 }, this)); | |
7342 } | |
7343 } | |
7344 } | |
7345 return parent._load_nodes.call(this, nodes, callback, is_callback, force_reload); | |
7346 }; | |
7347 this._load_node = function (obj, callback) { | |
7348 var data = this._data.massload[obj.id], | |
7349 rslt = null, dom; | |
7350 if(data) { | |
7351 rslt = this[typeof data === 'string' ? '_append_html_data' : '_append_json_data']( | |
7352 obj, | |
7353 typeof data === 'string' ? $($.parseHTML(data)).filter(function () { return this.nodeType !== 3; }) : data, | |
7354 function (status) { callback.call(this, status); } | |
7355 ); | |
7356 dom = this.get_node(obj.id, true); | |
7357 if (dom && dom.length) { | |
7358 dom.removeClass("jstree-loading").attr('aria-busy',false); | |
7359 } | |
7360 delete this._data.massload[obj.id]; | |
7361 return rslt; | |
7362 } | |
7363 return parent._load_node.call(this, obj, callback); | |
7364 }; | |
7365 }; | |
7366 | |
7367 /** | |
7368 * ### Search plugin | |
7369 * | |
7370 * Adds search functionality to jsTree. | |
7371 */ | |
7372 | |
7373 /** | |
7374 * stores all defaults for the search plugin | |
7375 * @name $.jstree.defaults.search | |
7376 * @plugin search | |
7377 */ | |
7378 $.jstree.defaults.search = { | |
7379 /** | |
7380 * a jQuery-like AJAX config, which jstree uses if a server should be queried for results. | |
7381 * | |
7382 * A `str` (which is the search string) parameter will be added with the request, an optional `inside` parameter will be added if the search is limited to a node id. The expected result is a JSON array with nodes that need to be opened so that matching nodes will be revealed. | |
7383 * Leave this setting as `false` to not query the server. You can also set this to a function, which will be invoked in the instance's scope and receive 3 parameters - the search string, the callback to call with the array of nodes to load, and the optional node ID to limit the search to | |
7384 * @name $.jstree.defaults.search.ajax | |
7385 * @plugin search | |
7386 */ | |
7387 ajax : false, | |
7388 /** | |
7389 * Indicates if the search should be fuzzy or not (should `chnd3` match `child node 3`). Default is `false`. | |
7390 * @name $.jstree.defaults.search.fuzzy | |
7391 * @plugin search | |
7392 */ | |
7393 fuzzy : false, | |
7394 /** | |
7395 * Indicates if the search should be case sensitive. Default is `false`. | |
7396 * @name $.jstree.defaults.search.case_sensitive | |
7397 * @plugin search | |
7398 */ | |
7399 case_sensitive : false, | |
7400 /** | |
7401 * Indicates if the tree should be filtered (by default) to show only matching nodes (keep in mind this can be a heavy on large trees in old browsers). | |
7402 * This setting can be changed at runtime when calling the search method. Default is `false`. | |
7403 * @name $.jstree.defaults.search.show_only_matches | |
7404 * @plugin search | |
7405 */ | |
7406 show_only_matches : false, | |
7407 /** | |
7408 * Indicates if the children of matched element are shown (when show_only_matches is true) | |
7409 * This setting can be changed at runtime when calling the search method. Default is `false`. | |
7410 * @name $.jstree.defaults.search.show_only_matches_children | |
7411 * @plugin search | |
7412 */ | |
7413 show_only_matches_children : false, | |
7414 /** | |
7415 * Indicates if all nodes opened to reveal the search result, should be closed when the search is cleared or a new search is performed. Default is `true`. | |
7416 * @name $.jstree.defaults.search.close_opened_onclear | |
7417 * @plugin search | |
7418 */ | |
7419 close_opened_onclear : true, | |
7420 /** | |
7421 * Indicates if only leaf nodes should be included in search results. Default is `false`. | |
7422 * @name $.jstree.defaults.search.search_leaves_only | |
7423 * @plugin search | |
7424 */ | |
7425 search_leaves_only : false, | |
7426 /** | |
7427 * If set to a function it wil be called in the instance's scope with two arguments - search string and node (where node will be every node in the structure, so use with caution). | |
7428 * If the function returns a truthy value the node will be considered a match (it might not be displayed if search_only_leaves is set to true and the node is not a leaf). Default is `false`. | |
7429 * @name $.jstree.defaults.search.search_callback | |
7430 * @plugin search | |
7431 */ | |
7432 search_callback : false | |
7433 }; | |
7434 | |
7435 $.jstree.plugins.search = function (options, parent) { | |
7436 this.bind = function () { | |
7437 parent.bind.call(this); | |
7438 | |
7439 this._data.search.str = ""; | |
7440 this._data.search.dom = $(); | |
7441 this._data.search.res = []; | |
7442 this._data.search.opn = []; | |
7443 this._data.search.som = false; | |
7444 this._data.search.smc = false; | |
7445 this._data.search.hdn = []; | |
7446 | |
7447 this.element | |
7448 .on("search.jstree", $.proxy(function (e, data) { | |
7449 if(this._data.search.som && data.res.length) { | |
7450 var m = this._model.data, i, j, p = [], k, l; | |
7451 for(i = 0, j = data.res.length; i < j; i++) { | |
7452 if(m[data.res[i]] && !m[data.res[i]].state.hidden) { | |
7453 p.push(data.res[i]); | |
7454 p = p.concat(m[data.res[i]].parents); | |
7455 if(this._data.search.smc) { | |
7456 for (k = 0, l = m[data.res[i]].children_d.length; k < l; k++) { | |
7457 if (m[m[data.res[i]].children_d[k]] && !m[m[data.res[i]].children_d[k]].state.hidden) { | |
7458 p.push(m[data.res[i]].children_d[k]); | |
7459 } | |
7460 } | |
7461 } | |
7462 } | |
7463 } | |
7464 p = $.vakata.array_remove_item($.vakata.array_unique(p), $.jstree.root); | |
7465 this._data.search.hdn = this.hide_all(true); | |
7466 this.show_node(p, true); | |
7467 this.redraw(true); | |
7468 } | |
7469 }, this)) | |
7470 .on("clear_search.jstree", $.proxy(function (e, data) { | |
7471 if(this._data.search.som && data.res.length) { | |
7472 this.show_node(this._data.search.hdn, true); | |
7473 this.redraw(true); | |
7474 } | |
7475 }, this)); | |
7476 }; | |
7477 /** | |
7478 * used to search the tree nodes for a given string | |
7479 * @name search(str [, skip_async]) | |
7480 * @param {String} str the search string | |
7481 * @param {Boolean} skip_async if set to true server will not be queried even if configured | |
7482 * @param {Boolean} show_only_matches if set to true only matching nodes will be shown (keep in mind this can be very slow on large trees or old browsers) | |
7483 * @param {mixed} inside an optional node to whose children to limit the search | |
7484 * @param {Boolean} append if set to true the results of this search are appended to the previous search | |
7485 * @plugin search | |
7486 * @trigger search.jstree | |
7487 */ | |
7488 this.search = function (str, skip_async, show_only_matches, inside, append, show_only_matches_children) { | |
7489 if(str === false || $.trim(str.toString()) === "") { | |
7490 return this.clear_search(); | |
7491 } | |
7492 inside = this.get_node(inside); | |
7493 inside = inside && inside.id ? inside.id : null; | |
7494 str = str.toString(); | |
7495 var s = this.settings.search, | |
7496 a = s.ajax ? s.ajax : false, | |
7497 m = this._model.data, | |
7498 f = null, | |
7499 r = [], | |
7500 p = [], i, j; | |
7501 if(this._data.search.res.length && !append) { | |
7502 this.clear_search(); | |
7503 } | |
7504 if(show_only_matches === undefined) { | |
7505 show_only_matches = s.show_only_matches; | |
7506 } | |
7507 if(show_only_matches_children === undefined) { | |
7508 show_only_matches_children = s.show_only_matches_children; | |
7509 } | |
7510 if(!skip_async && a !== false) { | |
7511 if($.isFunction(a)) { | |
7512 return a.call(this, str, $.proxy(function (d) { | |
7513 if(d && d.d) { d = d.d; } | |
7514 this._load_nodes(!$.isArray(d) ? [] : $.vakata.array_unique(d), function () { | |
7515 this.search(str, true, show_only_matches, inside, append, show_only_matches_children); | |
7516 }); | |
7517 }, this), inside); | |
7518 } | |
7519 else { | |
7520 a = $.extend({}, a); | |
7521 if(!a.data) { a.data = {}; } | |
7522 a.data.str = str; | |
7523 if(inside) { | |
7524 a.data.inside = inside; | |
7525 } | |
7526 if (this._data.search.lastRequest) { | |
7527 this._data.search.lastRequest.abort(); | |
7528 } | |
7529 this._data.search.lastRequest = $.ajax(a) | |
7530 .fail($.proxy(function () { | |
7531 this._data.core.last_error = { 'error' : 'ajax', 'plugin' : 'search', 'id' : 'search_01', 'reason' : 'Could not load search parents', 'data' : JSON.stringify(a) }; | |
7532 this.settings.core.error.call(this, this._data.core.last_error); | |
7533 }, this)) | |
7534 .done($.proxy(function (d) { | |
7535 if(d && d.d) { d = d.d; } | |
7536 this._load_nodes(!$.isArray(d) ? [] : $.vakata.array_unique(d), function () { | |
7537 this.search(str, true, show_only_matches, inside, append, show_only_matches_children); | |
7538 }); | |
7539 }, this)); | |
7540 return this._data.search.lastRequest; | |
7541 } | |
7542 } | |
7543 if(!append) { | |
7544 this._data.search.str = str; | |
7545 this._data.search.dom = $(); | |
7546 this._data.search.res = []; | |
7547 this._data.search.opn = []; | |
7548 this._data.search.som = show_only_matches; | |
7549 this._data.search.smc = show_only_matches_children; | |
7550 } | |
7551 | |
7552 f = new $.vakata.search(str, true, { caseSensitive : s.case_sensitive, fuzzy : s.fuzzy }); | |
7553 $.each(m[inside ? inside : $.jstree.root].children_d, function (ii, i) { | |
7554 var v = m[i]; | |
7555 if(v.text && !v.state.hidden && (!s.search_leaves_only || (v.state.loaded && v.children.length === 0)) && ( (s.search_callback && s.search_callback.call(this, str, v)) || (!s.search_callback && f.search(v.text).isMatch) ) ) { | |
7556 r.push(i); | |
7557 p = p.concat(v.parents); | |
7558 } | |
7559 }); | |
7560 if(r.length) { | |
7561 p = $.vakata.array_unique(p); | |
7562 for(i = 0, j = p.length; i < j; i++) { | |
7563 if(p[i] !== $.jstree.root && m[p[i]] && this.open_node(p[i], null, 0) === true) { | |
7564 this._data.search.opn.push(p[i]); | |
7565 } | |
7566 } | |
7567 if(!append) { | |
7568 this._data.search.dom = $(this.element[0].querySelectorAll('#' + $.map(r, function (v) { return "0123456789".indexOf(v[0]) !== -1 ? '\\3' + v[0] + ' ' + v.substr(1).replace($.jstree.idregex,'\\$&') : v.replace($.jstree.idregex,'\\$&'); }).join(', #'))); | |
7569 this._data.search.res = r; | |
7570 } | |
7571 else { | |
7572 this._data.search.dom = this._data.search.dom.add($(this.element[0].querySelectorAll('#' + $.map(r, function (v) { return "0123456789".indexOf(v[0]) !== -1 ? '\\3' + v[0] + ' ' + v.substr(1).replace($.jstree.idregex,'\\$&') : v.replace($.jstree.idregex,'\\$&'); }).join(', #')))); | |
7573 this._data.search.res = $.vakata.array_unique(this._data.search.res.concat(r)); | |
7574 } | |
7575 this._data.search.dom.children(".jstree-anchor").addClass('jstree-search'); | |
7576 } | |
7577 /** | |
7578 * triggered after search is complete | |
7579 * @event | |
7580 * @name search.jstree | |
7581 * @param {jQuery} nodes a jQuery collection of matching nodes | |
7582 * @param {String} str the search string | |
7583 * @param {Array} res a collection of objects represeing the matching nodes | |
7584 * @plugin search | |
7585 */ | |
7586 this.trigger('search', { nodes : this._data.search.dom, str : str, res : this._data.search.res, show_only_matches : show_only_matches }); | |
7587 }; | |
7588 /** | |
7589 * used to clear the last search (removes classes and shows all nodes if filtering is on) | |
7590 * @name clear_search() | |
7591 * @plugin search | |
7592 * @trigger clear_search.jstree | |
7593 */ | |
7594 this.clear_search = function () { | |
7595 if(this.settings.search.close_opened_onclear) { | |
7596 this.close_node(this._data.search.opn, 0); | |
7597 } | |
7598 /** | |
7599 * triggered after search is complete | |
7600 * @event | |
7601 * @name clear_search.jstree | |
7602 * @param {jQuery} nodes a jQuery collection of matching nodes (the result from the last search) | |
7603 * @param {String} str the search string (the last search string) | |
7604 * @param {Array} res a collection of objects represeing the matching nodes (the result from the last search) | |
7605 * @plugin search | |
7606 */ | |
7607 this.trigger('clear_search', { 'nodes' : this._data.search.dom, str : this._data.search.str, res : this._data.search.res }); | |
7608 if(this._data.search.res.length) { | |
7609 this._data.search.dom = $(this.element[0].querySelectorAll('#' + $.map(this._data.search.res, function (v) { | |
7610 return "0123456789".indexOf(v[0]) !== -1 ? '\\3' + v[0] + ' ' + v.substr(1).replace($.jstree.idregex,'\\$&') : v.replace($.jstree.idregex,'\\$&'); | |
7611 }).join(', #'))); | |
7612 this._data.search.dom.children(".jstree-anchor").removeClass("jstree-search"); | |
7613 } | |
7614 this._data.search.str = ""; | |
7615 this._data.search.res = []; | |
7616 this._data.search.opn = []; | |
7617 this._data.search.dom = $(); | |
7618 }; | |
7619 | |
7620 this.redraw_node = function(obj, deep, callback, force_render) { | |
7621 obj = parent.redraw_node.apply(this, arguments); | |
7622 if(obj) { | |
7623 if($.inArray(obj.id, this._data.search.res) !== -1) { | |
7624 var i, j, tmp = null; | |
7625 for(i = 0, j = obj.childNodes.length; i < j; i++) { | |
7626 if(obj.childNodes[i] && obj.childNodes[i].className && obj.childNodes[i].className.indexOf("jstree-anchor") !== -1) { | |
7627 tmp = obj.childNodes[i]; | |
7628 break; | |
7629 } | |
7630 } | |
7631 if(tmp) { | |
7632 tmp.className += ' jstree-search'; | |
7633 } | |
7634 } | |
7635 } | |
7636 return obj; | |
7637 }; | |
7638 }; | |
7639 | |
7640 // helpers | |
7641 (function ($) { | |
7642 // from http://kiro.me/projects/fuse.html | |
7643 $.vakata.search = function(pattern, txt, options) { | |
7644 options = options || {}; | |
7645 options = $.extend({}, $.vakata.search.defaults, options); | |
7646 if(options.fuzzy !== false) { | |
7647 options.fuzzy = true; | |
7648 } | |
7649 pattern = options.caseSensitive ? pattern : pattern.toLowerCase(); | |
7650 var MATCH_LOCATION = options.location, | |
7651 MATCH_DISTANCE = options.distance, | |
7652 MATCH_THRESHOLD = options.threshold, | |
7653 patternLen = pattern.length, | |
7654 matchmask, pattern_alphabet, match_bitapScore, search; | |
7655 if(patternLen > 32) { | |
7656 options.fuzzy = false; | |
7657 } | |
7658 if(options.fuzzy) { | |
7659 matchmask = 1 << (patternLen - 1); | |
7660 pattern_alphabet = (function () { | |
7661 var mask = {}, | |
7662 i = 0; | |
7663 for (i = 0; i < patternLen; i++) { | |
7664 mask[pattern.charAt(i)] = 0; | |
7665 } | |
7666 for (i = 0; i < patternLen; i++) { | |
7667 mask[pattern.charAt(i)] |= 1 << (patternLen - i - 1); | |
7668 } | |
7669 return mask; | |
7670 }()); | |
7671 match_bitapScore = function (e, x) { | |
7672 var accuracy = e / patternLen, | |
7673 proximity = Math.abs(MATCH_LOCATION - x); | |
7674 if(!MATCH_DISTANCE) { | |
7675 return proximity ? 1.0 : accuracy; | |
7676 } | |
7677 return accuracy + (proximity / MATCH_DISTANCE); | |
7678 }; | |
7679 } | |
7680 search = function (text) { | |
7681 text = options.caseSensitive ? text : text.toLowerCase(); | |
7682 if(pattern === text || text.indexOf(pattern) !== -1) { | |
7683 return { | |
7684 isMatch: true, | |
7685 score: 0 | |
7686 }; | |
7687 } | |
7688 if(!options.fuzzy) { | |
7689 return { | |
7690 isMatch: false, | |
7691 score: 1 | |
7692 }; | |
7693 } | |
7694 var i, j, | |
7695 textLen = text.length, | |
7696 scoreThreshold = MATCH_THRESHOLD, | |
7697 bestLoc = text.indexOf(pattern, MATCH_LOCATION), | |
7698 binMin, binMid, | |
7699 binMax = patternLen + textLen, | |
7700 lastRd, start, finish, rd, charMatch, | |
7701 score = 1, | |
7702 locations = []; | |
7703 if (bestLoc !== -1) { | |
7704 scoreThreshold = Math.min(match_bitapScore(0, bestLoc), scoreThreshold); | |
7705 bestLoc = text.lastIndexOf(pattern, MATCH_LOCATION + patternLen); | |
7706 if (bestLoc !== -1) { | |
7707 scoreThreshold = Math.min(match_bitapScore(0, bestLoc), scoreThreshold); | |
7708 } | |
7709 } | |
7710 bestLoc = -1; | |
7711 for (i = 0; i < patternLen; i++) { | |
7712 binMin = 0; | |
7713 binMid = binMax; | |
7714 while (binMin < binMid) { | |
7715 if (match_bitapScore(i, MATCH_LOCATION + binMid) <= scoreThreshold) { | |
7716 binMin = binMid; | |
7717 } else { | |
7718 binMax = binMid; | |
7719 } | |
7720 binMid = Math.floor((binMax - binMin) / 2 + binMin); | |
7721 } | |
7722 binMax = binMid; | |
7723 start = Math.max(1, MATCH_LOCATION - binMid + 1); | |
7724 finish = Math.min(MATCH_LOCATION + binMid, textLen) + patternLen; | |
7725 rd = new Array(finish + 2); | |
7726 rd[finish + 1] = (1 << i) - 1; | |
7727 for (j = finish; j >= start; j--) { | |
7728 charMatch = pattern_alphabet[text.charAt(j - 1)]; | |
7729 if (i === 0) { | |
7730 rd[j] = ((rd[j + 1] << 1) | 1) & charMatch; | |
7731 } else { | |
7732 rd[j] = ((rd[j + 1] << 1) | 1) & charMatch | (((lastRd[j + 1] | lastRd[j]) << 1) | 1) | lastRd[j + 1]; | |
7733 } | |
7734 if (rd[j] & matchmask) { | |
7735 score = match_bitapScore(i, j - 1); | |
7736 if (score <= scoreThreshold) { | |
7737 scoreThreshold = score; | |
7738 bestLoc = j - 1; | |
7739 locations.push(bestLoc); | |
7740 if (bestLoc > MATCH_LOCATION) { | |
7741 start = Math.max(1, 2 * MATCH_LOCATION - bestLoc); | |
7742 } else { | |
7743 break; | |
7744 } | |
7745 } | |
7746 } | |
7747 } | |
7748 if (match_bitapScore(i + 1, MATCH_LOCATION) > scoreThreshold) { | |
7749 break; | |
7750 } | |
7751 lastRd = rd; | |
7752 } | |
7753 return { | |
7754 isMatch: bestLoc >= 0, | |
7755 score: score | |
7756 }; | |
7757 }; | |
7758 return txt === true ? { 'search' : search } : search(txt); | |
7759 }; | |
7760 $.vakata.search.defaults = { | |
7761 location : 0, | |
7762 distance : 100, | |
7763 threshold : 0.6, | |
7764 fuzzy : false, | |
7765 caseSensitive : false | |
7766 }; | |
7767 }($)); | |
7768 | |
7769 // include the search plugin by default | |
7770 // $.jstree.defaults.plugins.push("search"); | |
7771 | |
7772 | |
7773 /** | |
7774 * ### Sort plugin | |
7775 * | |
7776 * Automatically sorts all siblings in the tree according to a sorting function. | |
7777 */ | |
7778 | |
7779 /** | |
7780 * the settings function used to sort the nodes. | |
7781 * It is executed in the tree's context, accepts two nodes as arguments and should return `1` or `-1`. | |
7782 * @name $.jstree.defaults.sort | |
7783 * @plugin sort | |
7784 */ | |
7785 $.jstree.defaults.sort = function (a, b) { | |
7786 //return this.get_type(a) === this.get_type(b) ? (this.get_text(a) > this.get_text(b) ? 1 : -1) : this.get_type(a) >= this.get_type(b); | |
7787 return this.get_text(a) > this.get_text(b) ? 1 : -1; | |
7788 }; | |
7789 $.jstree.plugins.sort = function (options, parent) { | |
7790 this.bind = function () { | |
7791 parent.bind.call(this); | |
7792 this.element | |
7793 .on("model.jstree", $.proxy(function (e, data) { | |
7794 this.sort(data.parent, true); | |
7795 }, this)) | |
7796 .on("rename_node.jstree create_node.jstree", $.proxy(function (e, data) { | |
7797 this.sort(data.parent || data.node.parent, false); | |
7798 this.redraw_node(data.parent || data.node.parent, true); | |
7799 }, this)) | |
7800 .on("move_node.jstree copy_node.jstree", $.proxy(function (e, data) { | |
7801 this.sort(data.parent, false); | |
7802 this.redraw_node(data.parent, true); | |
7803 }, this)); | |
7804 }; | |
7805 /** | |
7806 * used to sort a node's children | |
7807 * @private | |
7808 * @name sort(obj [, deep]) | |
7809 * @param {mixed} obj the node | |
7810 * @param {Boolean} deep if set to `true` nodes are sorted recursively. | |
7811 * @plugin sort | |
7812 * @trigger search.jstree | |
7813 */ | |
7814 this.sort = function (obj, deep) { | |
7815 var i, j; | |
7816 obj = this.get_node(obj); | |
7817 if(obj && obj.children && obj.children.length) { | |
7818 obj.children.sort($.proxy(this.settings.sort, this)); | |
7819 if(deep) { | |
7820 for(i = 0, j = obj.children_d.length; i < j; i++) { | |
7821 this.sort(obj.children_d[i], false); | |
7822 } | |
7823 } | |
7824 } | |
7825 }; | |
7826 }; | |
7827 | |
7828 // include the sort plugin by default | |
7829 // $.jstree.defaults.plugins.push("sort"); | |
7830 | |
7831 /** | |
7832 * ### State plugin | |
7833 * | |
7834 * Saves the state of the tree (selected nodes, opened nodes) on the user's computer using available options (localStorage, cookies, etc) | |
7835 */ | |
7836 | |
7837 var to = false; | |
7838 /** | |
7839 * stores all defaults for the state plugin | |
7840 * @name $.jstree.defaults.state | |
7841 * @plugin state | |
7842 */ | |
7843 $.jstree.defaults.state = { | |
7844 /** | |
7845 * A string for the key to use when saving the current tree (change if using multiple trees in your project). Defaults to `jstree`. | |
7846 * @name $.jstree.defaults.state.key | |
7847 * @plugin state | |
7848 */ | |
7849 key : 'jstree', | |
7850 /** | |
7851 * A space separated list of events that trigger a state save. Defaults to `changed.jstree open_node.jstree close_node.jstree`. | |
7852 * @name $.jstree.defaults.state.events | |
7853 * @plugin state | |
7854 */ | |
7855 events : 'changed.jstree open_node.jstree close_node.jstree check_node.jstree uncheck_node.jstree', | |
7856 /** | |
7857 * Time in milliseconds after which the state will expire. Defaults to 'false' meaning - no expire. | |
7858 * @name $.jstree.defaults.state.ttl | |
7859 * @plugin state | |
7860 */ | |
7861 ttl : false, | |
7862 /** | |
7863 * A function that will be executed prior to restoring state with one argument - the state object. Can be used to clear unwanted parts of the state. | |
7864 * @name $.jstree.defaults.state.filter | |
7865 * @plugin state | |
7866 */ | |
7867 filter : false, | |
7868 /** | |
7869 * Should loaded nodes be restored (setting this to true means that it is possible that the whole tree will be loaded for some users - use with caution). Defaults to `false` | |
7870 * @name $.jstree.defaults.state.preserve_loaded | |
7871 * @plugin state | |
7872 */ | |
7873 preserve_loaded : false | |
7874 }; | |
7875 $.jstree.plugins.state = function (options, parent) { | |
7876 this.bind = function () { | |
7877 parent.bind.call(this); | |
7878 var bind = $.proxy(function () { | |
7879 this.element.on(this.settings.state.events, $.proxy(function () { | |
7880 if(to) { clearTimeout(to); } | |
7881 to = setTimeout($.proxy(function () { this.save_state(); }, this), 100); | |
7882 }, this)); | |
7883 /** | |
7884 * triggered when the state plugin is finished restoring the state (and immediately after ready if there is no state to restore). | |
7885 * @event | |
7886 * @name state_ready.jstree | |
7887 * @plugin state | |
7888 */ | |
7889 this.trigger('state_ready'); | |
7890 }, this); | |
7891 this.element | |
7892 .on("ready.jstree", $.proxy(function (e, data) { | |
7893 this.element.one("restore_state.jstree", bind); | |
7894 if(!this.restore_state()) { bind(); } | |
7895 }, this)); | |
7896 }; | |
7897 /** | |
7898 * save the state | |
7899 * @name save_state() | |
7900 * @plugin state | |
7901 */ | |
7902 this.save_state = function () { | |
7903 var tm = this.get_state(); | |
7904 if (!this.settings.state.preserve_loaded) { | |
7905 delete tm.core.loaded; | |
7906 } | |
7907 var st = { 'state' : tm, 'ttl' : this.settings.state.ttl, 'sec' : +(new Date()) }; | |
7908 $.vakata.storage.set(this.settings.state.key, JSON.stringify(st)); | |
7909 }; | |
7910 /** | |
7911 * restore the state from the user's computer | |
7912 * @name restore_state() | |
7913 * @plugin state | |
7914 */ | |
7915 this.restore_state = function () { | |
7916 var k = $.vakata.storage.get(this.settings.state.key); | |
7917 if(!!k) { try { k = JSON.parse(k); } catch(ex) { return false; } } | |
7918 if(!!k && k.ttl && k.sec && +(new Date()) - k.sec > k.ttl) { return false; } | |
7919 if(!!k && k.state) { k = k.state; } | |
7920 if(!!k && $.isFunction(this.settings.state.filter)) { k = this.settings.state.filter.call(this, k); } | |
7921 if(!!k) { | |
7922 if (!this.settings.state.preserve_loaded) { | |
7923 delete k.core.loaded; | |
7924 } | |
7925 this.element.one("set_state.jstree", function (e, data) { data.instance.trigger('restore_state', { 'state' : $.extend(true, {}, k) }); }); | |
7926 this.set_state(k); | |
7927 return true; | |
7928 } | |
7929 return false; | |
7930 }; | |
7931 /** | |
7932 * clear the state on the user's computer | |
7933 * @name clear_state() | |
7934 * @plugin state | |
7935 */ | |
7936 this.clear_state = function () { | |
7937 return $.vakata.storage.del(this.settings.state.key); | |
7938 }; | |
7939 }; | |
7940 | |
7941 (function ($, undefined) { | |
7942 $.vakata.storage = { | |
7943 // simply specifying the functions in FF throws an error | |
7944 set : function (key, val) { return window.localStorage.setItem(key, val); }, | |
7945 get : function (key) { return window.localStorage.getItem(key); }, | |
7946 del : function (key) { return window.localStorage.removeItem(key); } | |
7947 }; | |
7948 }($)); | |
7949 | |
7950 // include the state plugin by default | |
7951 // $.jstree.defaults.plugins.push("state"); | |
7952 | |
7953 /** | |
7954 * ### Types plugin | |
7955 * | |
7956 * Makes it possible to add predefined types for groups of nodes, which make it possible to easily control nesting rules and icon for each group. | |
7957 */ | |
7958 | |
7959 /** | |
7960 * An object storing all types as key value pairs, where the key is the type name and the value is an object that could contain following keys (all optional). | |
7961 * | |
7962 * * `max_children` the maximum number of immediate children this node type can have. Do not specify or set to `-1` for unlimited. | |
7963 * * `max_depth` the maximum number of nesting this node type can have. A value of `1` would mean that the node can have children, but no grandchildren. Do not specify or set to `-1` for unlimited. | |
7964 * * `valid_children` an array of node type strings, that nodes of this type can have as children. Do not specify or set to `-1` for no limits. | |
7965 * * `icon` a string - can be a path to an icon or a className, if using an image that is in the current directory use a `./` prefix, otherwise it will be detected as a class. Omit to use the default icon from your theme. | |
7966 * * `li_attr` an object of values which will be used to add HTML attributes on the resulting LI DOM node (merged with the node's own data) | |
7967 * * `a_attr` an object of values which will be used to add HTML attributes on the resulting A DOM node (merged with the node's own data) | |
7968 * | |
7969 * There are two predefined types: | |
7970 * | |
7971 * * `#` represents the root of the tree, for example `max_children` would control the maximum number of root nodes. | |
7972 * * `default` represents the default node - any settings here will be applied to all nodes that do not have a type specified. | |
7973 * | |
7974 * @name $.jstree.defaults.types | |
7975 * @plugin types | |
7976 */ | |
7977 $.jstree.defaults.types = { | |
7978 'default' : {} | |
7979 }; | |
7980 $.jstree.defaults.types[$.jstree.root] = {}; | |
7981 | |
7982 $.jstree.plugins.types = function (options, parent) { | |
7983 this.init = function (el, options) { | |
7984 var i, j; | |
7985 if(options && options.types && options.types['default']) { | |
7986 for(i in options.types) { | |
7987 if(i !== "default" && i !== $.jstree.root && options.types.hasOwnProperty(i)) { | |
7988 for(j in options.types['default']) { | |
7989 if(options.types['default'].hasOwnProperty(j) && options.types[i][j] === undefined) { | |
7990 options.types[i][j] = options.types['default'][j]; | |
7991 } | |
7992 } | |
7993 } | |
7994 } | |
7995 } | |
7996 parent.init.call(this, el, options); | |
7997 this._model.data[$.jstree.root].type = $.jstree.root; | |
7998 }; | |
7999 this.refresh = function (skip_loading, forget_state) { | |
8000 parent.refresh.call(this, skip_loading, forget_state); | |
8001 this._model.data[$.jstree.root].type = $.jstree.root; | |
8002 }; | |
8003 this.bind = function () { | |
8004 this.element | |
8005 .on('model.jstree', $.proxy(function (e, data) { | |
8006 var m = this._model.data, | |
8007 dpc = data.nodes, | |
8008 t = this.settings.types, | |
8009 i, j, c = 'default', k; | |
8010 for(i = 0, j = dpc.length; i < j; i++) { | |
8011 c = 'default'; | |
8012 if(m[dpc[i]].original && m[dpc[i]].original.type && t[m[dpc[i]].original.type]) { | |
8013 c = m[dpc[i]].original.type; | |
8014 } | |
8015 if(m[dpc[i]].data && m[dpc[i]].data.jstree && m[dpc[i]].data.jstree.type && t[m[dpc[i]].data.jstree.type]) { | |
8016 c = m[dpc[i]].data.jstree.type; | |
8017 } | |
8018 m[dpc[i]].type = c; | |
8019 if(m[dpc[i]].icon === true && t[c].icon !== undefined) { | |
8020 m[dpc[i]].icon = t[c].icon; | |
8021 } | |
8022 if(t[c].li_attr !== undefined && typeof t[c].li_attr === 'object') { | |
8023 for (k in t[c].li_attr) { | |
8024 if (t[c].li_attr.hasOwnProperty(k)) { | |
8025 if (k === 'id') { | |
8026 continue; | |
8027 } | |
8028 else if (m[dpc[i]].li_attr[k] === undefined) { | |
8029 m[dpc[i]].li_attr[k] = t[c].li_attr[k]; | |
8030 } | |
8031 else if (k === 'class') { | |
8032 m[dpc[i]].li_attr['class'] = t[c].li_attr['class'] + ' ' + m[dpc[i]].li_attr['class']; | |
8033 } | |
8034 } | |
8035 } | |
8036 } | |
8037 if(t[c].a_attr !== undefined && typeof t[c].a_attr === 'object') { | |
8038 for (k in t[c].a_attr) { | |
8039 if (t[c].a_attr.hasOwnProperty(k)) { | |
8040 if (k === 'id') { | |
8041 continue; | |
8042 } | |
8043 else if (m[dpc[i]].a_attr[k] === undefined) { | |
8044 m[dpc[i]].a_attr[k] = t[c].a_attr[k]; | |
8045 } | |
8046 else if (k === 'href' && m[dpc[i]].a_attr[k] === '#') { | |
8047 m[dpc[i]].a_attr['href'] = t[c].a_attr['href']; | |
8048 } | |
8049 else if (k === 'class') { | |
8050 m[dpc[i]].a_attr['class'] = t[c].a_attr['class'] + ' ' + m[dpc[i]].a_attr['class']; | |
8051 } | |
8052 } | |
8053 } | |
8054 } | |
8055 } | |
8056 m[$.jstree.root].type = $.jstree.root; | |
8057 }, this)); | |
8058 parent.bind.call(this); | |
8059 }; | |
8060 this.get_json = function (obj, options, flat) { | |
8061 var i, j, | |
8062 m = this._model.data, | |
8063 opt = options ? $.extend(true, {}, options, {no_id:false}) : {}, | |
8064 tmp = parent.get_json.call(this, obj, opt, flat); | |
8065 if(tmp === false) { return false; } | |
8066 if($.isArray(tmp)) { | |
8067 for(i = 0, j = tmp.length; i < j; i++) { | |
8068 tmp[i].type = tmp[i].id && m[tmp[i].id] && m[tmp[i].id].type ? m[tmp[i].id].type : "default"; | |
8069 if(options && options.no_id) { | |
8070 delete tmp[i].id; | |
8071 if(tmp[i].li_attr && tmp[i].li_attr.id) { | |
8072 delete tmp[i].li_attr.id; | |
8073 } | |
8074 if(tmp[i].a_attr && tmp[i].a_attr.id) { | |
8075 delete tmp[i].a_attr.id; | |
8076 } | |
8077 } | |
8078 } | |
8079 } | |
8080 else { | |
8081 tmp.type = tmp.id && m[tmp.id] && m[tmp.id].type ? m[tmp.id].type : "default"; | |
8082 if(options && options.no_id) { | |
8083 tmp = this._delete_ids(tmp); | |
8084 } | |
8085 } | |
8086 return tmp; | |
8087 }; | |
8088 this._delete_ids = function (tmp) { | |
8089 if($.isArray(tmp)) { | |
8090 for(var i = 0, j = tmp.length; i < j; i++) { | |
8091 tmp[i] = this._delete_ids(tmp[i]); | |
8092 } | |
8093 return tmp; | |
8094 } | |
8095 delete tmp.id; | |
8096 if(tmp.li_attr && tmp.li_attr.id) { | |
8097 delete tmp.li_attr.id; | |
8098 } | |
8099 if(tmp.a_attr && tmp.a_attr.id) { | |
8100 delete tmp.a_attr.id; | |
8101 } | |
8102 if(tmp.children && $.isArray(tmp.children)) { | |
8103 tmp.children = this._delete_ids(tmp.children); | |
8104 } | |
8105 return tmp; | |
8106 }; | |
8107 this.check = function (chk, obj, par, pos, more) { | |
8108 if(parent.check.call(this, chk, obj, par, pos, more) === false) { return false; } | |
8109 obj = obj && obj.id ? obj : this.get_node(obj); | |
8110 par = par && par.id ? par : this.get_node(par); | |
8111 var m = obj && obj.id ? (more && more.origin ? more.origin : $.jstree.reference(obj.id)) : null, tmp, d, i, j; | |
8112 m = m && m._model && m._model.data ? m._model.data : null; | |
8113 switch(chk) { | |
8114 case "create_node": | |
8115 case "move_node": | |
8116 case "copy_node": | |
8117 if(chk !== 'move_node' || $.inArray(obj.id, par.children) === -1) { | |
8118 tmp = this.get_rules(par); | |
8119 if(tmp.max_children !== undefined && tmp.max_children !== -1 && tmp.max_children === par.children.length) { | |
8120 this._data.core.last_error = { 'error' : 'check', 'plugin' : 'types', 'id' : 'types_01', 'reason' : 'max_children prevents function: ' + chk, 'data' : JSON.stringify({ 'chk' : chk, 'pos' : pos, 'obj' : obj && obj.id ? obj.id : false, 'par' : par && par.id ? par.id : false }) }; | |
8121 return false; | |
8122 } | |
8123 if(tmp.valid_children !== undefined && tmp.valid_children !== -1 && $.inArray((obj.type || 'default'), tmp.valid_children) === -1) { | |
8124 this._data.core.last_error = { 'error' : 'check', 'plugin' : 'types', 'id' : 'types_02', 'reason' : 'valid_children prevents function: ' + chk, 'data' : JSON.stringify({ 'chk' : chk, 'pos' : pos, 'obj' : obj && obj.id ? obj.id : false, 'par' : par && par.id ? par.id : false }) }; | |
8125 return false; | |
8126 } | |
8127 if(m && obj.children_d && obj.parents) { | |
8128 d = 0; | |
8129 for(i = 0, j = obj.children_d.length; i < j; i++) { | |
8130 d = Math.max(d, m[obj.children_d[i]].parents.length); | |
8131 } | |
8132 d = d - obj.parents.length + 1; | |
8133 } | |
8134 if(d <= 0 || d === undefined) { d = 1; } | |
8135 do { | |
8136 if(tmp.max_depth !== undefined && tmp.max_depth !== -1 && tmp.max_depth < d) { | |
8137 this._data.core.last_error = { 'error' : 'check', 'plugin' : 'types', 'id' : 'types_03', 'reason' : 'max_depth prevents function: ' + chk, 'data' : JSON.stringify({ 'chk' : chk, 'pos' : pos, 'obj' : obj && obj.id ? obj.id : false, 'par' : par && par.id ? par.id : false }) }; | |
8138 return false; | |
8139 } | |
8140 par = this.get_node(par.parent); | |
8141 tmp = this.get_rules(par); | |
8142 d++; | |
8143 } while(par); | |
8144 } | |
8145 break; | |
8146 } | |
8147 return true; | |
8148 }; | |
8149 /** | |
8150 * used to retrieve the type settings object for a node | |
8151 * @name get_rules(obj) | |
8152 * @param {mixed} obj the node to find the rules for | |
8153 * @return {Object} | |
8154 * @plugin types | |
8155 */ | |
8156 this.get_rules = function (obj) { | |
8157 obj = this.get_node(obj); | |
8158 if(!obj) { return false; } | |
8159 var tmp = this.get_type(obj, true); | |
8160 if(tmp.max_depth === undefined) { tmp.max_depth = -1; } | |
8161 if(tmp.max_children === undefined) { tmp.max_children = -1; } | |
8162 if(tmp.valid_children === undefined) { tmp.valid_children = -1; } | |
8163 return tmp; | |
8164 }; | |
8165 /** | |
8166 * used to retrieve the type string or settings object for a node | |
8167 * @name get_type(obj [, rules]) | |
8168 * @param {mixed} obj the node to find the rules for | |
8169 * @param {Boolean} rules if set to `true` instead of a string the settings object will be returned | |
8170 * @return {String|Object} | |
8171 * @plugin types | |
8172 */ | |
8173 this.get_type = function (obj, rules) { | |
8174 obj = this.get_node(obj); | |
8175 return (!obj) ? false : ( rules ? $.extend({ 'type' : obj.type }, this.settings.types[obj.type]) : obj.type); | |
8176 }; | |
8177 /** | |
8178 * used to change a node's type | |
8179 * @name set_type(obj, type) | |
8180 * @param {mixed} obj the node to change | |
8181 * @param {String} type the new type | |
8182 * @plugin types | |
8183 */ | |
8184 this.set_type = function (obj, type) { | |
8185 var m = this._model.data, t, t1, t2, old_type, old_icon, k, d, a; | |
8186 if($.isArray(obj)) { | |
8187 obj = obj.slice(); | |
8188 for(t1 = 0, t2 = obj.length; t1 < t2; t1++) { | |
8189 this.set_type(obj[t1], type); | |
8190 } | |
8191 return true; | |
8192 } | |
8193 t = this.settings.types; | |
8194 obj = this.get_node(obj); | |
8195 if(!t[type] || !obj) { return false; } | |
8196 d = this.get_node(obj, true); | |
8197 if (d && d.length) { | |
8198 a = d.children('.jstree-anchor'); | |
8199 } | |
8200 old_type = obj.type; | |
8201 old_icon = this.get_icon(obj); | |
8202 obj.type = type; | |
8203 if(old_icon === true || !t[old_type] || (t[old_type].icon !== undefined && old_icon === t[old_type].icon)) { | |
8204 this.set_icon(obj, t[type].icon !== undefined ? t[type].icon : true); | |
8205 } | |
8206 | |
8207 // remove old type props | |
8208 if(t[old_type] && t[old_type].li_attr !== undefined && typeof t[old_type].li_attr === 'object') { | |
8209 for (k in t[old_type].li_attr) { | |
8210 if (t[old_type].li_attr.hasOwnProperty(k)) { | |
8211 if (k === 'id') { | |
8212 continue; | |
8213 } | |
8214 else if (k === 'class') { | |
8215 m[obj.id].li_attr['class'] = (m[obj.id].li_attr['class'] || '').replace(t[old_type].li_attr[k], ''); | |
8216 if (d) { d.removeClass(t[old_type].li_attr[k]); } | |
8217 } | |
8218 else if (m[obj.id].li_attr[k] === t[old_type].li_attr[k]) { | |
8219 m[obj.id].li_attr[k] = null; | |
8220 if (d) { d.removeAttr(k); } | |
8221 } | |
8222 } | |
8223 } | |
8224 } | |
8225 if(t[old_type] && t[old_type].a_attr !== undefined && typeof t[old_type].a_attr === 'object') { | |
8226 for (k in t[old_type].a_attr) { | |
8227 if (t[old_type].a_attr.hasOwnProperty(k)) { | |
8228 if (k === 'id') { | |
8229 continue; | |
8230 } | |
8231 else if (k === 'class') { | |
8232 m[obj.id].a_attr['class'] = (m[obj.id].a_attr['class'] || '').replace(t[old_type].a_attr[k], ''); | |
8233 if (a) { a.removeClass(t[old_type].a_attr[k]); } | |
8234 } | |
8235 else if (m[obj.id].a_attr[k] === t[old_type].a_attr[k]) { | |
8236 if (k === 'href') { | |
8237 m[obj.id].a_attr[k] = '#'; | |
8238 if (a) { a.attr('href', '#'); } | |
8239 } | |
8240 else { | |
8241 delete m[obj.id].a_attr[k]; | |
8242 if (a) { a.removeAttr(k); } | |
8243 } | |
8244 } | |
8245 } | |
8246 } | |
8247 } | |
8248 | |
8249 // add new props | |
8250 if(t[type].li_attr !== undefined && typeof t[type].li_attr === 'object') { | |
8251 for (k in t[type].li_attr) { | |
8252 if (t[type].li_attr.hasOwnProperty(k)) { | |
8253 if (k === 'id') { | |
8254 continue; | |
8255 } | |
8256 else if (m[obj.id].li_attr[k] === undefined) { | |
8257 m[obj.id].li_attr[k] = t[type].li_attr[k]; | |
8258 if (d) { | |
8259 if (k === 'class') { | |
8260 d.addClass(t[type].li_attr[k]); | |
8261 } | |
8262 else { | |
8263 d.attr(k, t[type].li_attr[k]); | |
8264 } | |
8265 } | |
8266 } | |
8267 else if (k === 'class') { | |
8268 m[obj.id].li_attr['class'] = t[type].li_attr[k] + ' ' + m[obj.id].li_attr['class']; | |
8269 if (d) { d.addClass(t[type].li_attr[k]); } | |
8270 } | |
8271 } | |
8272 } | |
8273 } | |
8274 if(t[type].a_attr !== undefined && typeof t[type].a_attr === 'object') { | |
8275 for (k in t[type].a_attr) { | |
8276 if (t[type].a_attr.hasOwnProperty(k)) { | |
8277 if (k === 'id') { | |
8278 continue; | |
8279 } | |
8280 else if (m[obj.id].a_attr[k] === undefined) { | |
8281 m[obj.id].a_attr[k] = t[type].a_attr[k]; | |
8282 if (a) { | |
8283 if (k === 'class') { | |
8284 a.addClass(t[type].a_attr[k]); | |
8285 } | |
8286 else { | |
8287 a.attr(k, t[type].a_attr[k]); | |
8288 } | |
8289 } | |
8290 } | |
8291 else if (k === 'href' && m[obj.id].a_attr[k] === '#') { | |
8292 m[obj.id].a_attr['href'] = t[type].a_attr['href']; | |
8293 if (a) { a.attr('href', t[type].a_attr['href']); } | |
8294 } | |
8295 else if (k === 'class') { | |
8296 m[obj.id].a_attr['class'] = t[type].a_attr['class'] + ' ' + m[obj.id].a_attr['class']; | |
8297 if (a) { a.addClass(t[type].a_attr[k]); } | |
8298 } | |
8299 } | |
8300 } | |
8301 } | |
8302 | |
8303 return true; | |
8304 }; | |
8305 }; | |
8306 // include the types plugin by default | |
8307 // $.jstree.defaults.plugins.push("types"); | |
8308 | |
8309 | |
8310 /** | |
8311 * ### Unique plugin | |
8312 * | |
8313 * Enforces that no nodes with the same name can coexist as siblings. | |
8314 */ | |
8315 | |
8316 /** | |
8317 * stores all defaults for the unique plugin | |
8318 * @name $.jstree.defaults.unique | |
8319 * @plugin unique | |
8320 */ | |
8321 $.jstree.defaults.unique = { | |
8322 /** | |
8323 * Indicates if the comparison should be case sensitive. Default is `false`. | |
8324 * @name $.jstree.defaults.unique.case_sensitive | |
8325 * @plugin unique | |
8326 */ | |
8327 case_sensitive : false, | |
8328 /** | |
8329 * Indicates if white space should be trimmed before the comparison. Default is `false`. | |
8330 * @name $.jstree.defaults.unique.trim_whitespace | |
8331 * @plugin unique | |
8332 */ | |
8333 trim_whitespace : false, | |
8334 /** | |
8335 * A callback executed in the instance's scope when a new node is created and the name is already taken, the two arguments are the conflicting name and the counter. The default will produce results like `New node (2)`. | |
8336 * @name $.jstree.defaults.unique.duplicate | |
8337 * @plugin unique | |
8338 */ | |
8339 duplicate : function (name, counter) { | |
8340 return name + ' (' + counter + ')'; | |
8341 } | |
8342 }; | |
8343 | |
8344 $.jstree.plugins.unique = function (options, parent) { | |
8345 this.check = function (chk, obj, par, pos, more) { | |
8346 if(parent.check.call(this, chk, obj, par, pos, more) === false) { return false; } | |
8347 obj = obj && obj.id ? obj : this.get_node(obj); | |
8348 par = par && par.id ? par : this.get_node(par); | |
8349 if(!par || !par.children) { return true; } | |
8350 var n = chk === "rename_node" ? pos : obj.text, | |
8351 c = [], | |
8352 s = this.settings.unique.case_sensitive, | |
8353 w = this.settings.unique.trim_whitespace, | |
8354 m = this._model.data, i, j, t; | |
8355 for(i = 0, j = par.children.length; i < j; i++) { | |
8356 t = m[par.children[i]].text; | |
8357 if (!s) { | |
8358 t = t.toLowerCase(); | |
8359 } | |
8360 if (w) { | |
8361 t = t.replace(/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g, ''); | |
8362 } | |
8363 c.push(t); | |
8364 } | |
8365 if(!s) { n = n.toLowerCase(); } | |
8366 if (w) { n = n.replace(/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g, ''); } | |
8367 switch(chk) { | |
8368 case "delete_node": | |
8369 return true; | |
8370 case "rename_node": | |
8371 t = obj.text || ''; | |
8372 if (!s) { | |
8373 t = t.toLowerCase(); | |
8374 } | |
8375 if (w) { | |
8376 t = t.replace(/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g, ''); | |
8377 } | |
8378 i = ($.inArray(n, c) === -1 || (obj.text && t === n)); | |
8379 if(!i) { | |
8380 this._data.core.last_error = { 'error' : 'check', 'plugin' : 'unique', 'id' : 'unique_01', 'reason' : 'Child with name ' + n + ' already exists. Preventing: ' + chk, 'data' : JSON.stringify({ 'chk' : chk, 'pos' : pos, 'obj' : obj && obj.id ? obj.id : false, 'par' : par && par.id ? par.id : false }) }; | |
8381 } | |
8382 return i; | |
8383 case "create_node": | |
8384 i = ($.inArray(n, c) === -1); | |
8385 if(!i) { | |
8386 this._data.core.last_error = { 'error' : 'check', 'plugin' : 'unique', 'id' : 'unique_04', 'reason' : 'Child with name ' + n + ' already exists. Preventing: ' + chk, 'data' : JSON.stringify({ 'chk' : chk, 'pos' : pos, 'obj' : obj && obj.id ? obj.id : false, 'par' : par && par.id ? par.id : false }) }; | |
8387 } | |
8388 return i; | |
8389 case "copy_node": | |
8390 i = ($.inArray(n, c) === -1); | |
8391 if(!i) { | |
8392 this._data.core.last_error = { 'error' : 'check', 'plugin' : 'unique', 'id' : 'unique_02', 'reason' : 'Child with name ' + n + ' already exists. Preventing: ' + chk, 'data' : JSON.stringify({ 'chk' : chk, 'pos' : pos, 'obj' : obj && obj.id ? obj.id : false, 'par' : par && par.id ? par.id : false }) }; | |
8393 } | |
8394 return i; | |
8395 case "move_node": | |
8396 i = ( (obj.parent === par.id && (!more || !more.is_multi)) || $.inArray(n, c) === -1); | |
8397 if(!i) { | |
8398 this._data.core.last_error = { 'error' : 'check', 'plugin' : 'unique', 'id' : 'unique_03', 'reason' : 'Child with name ' + n + ' already exists. Preventing: ' + chk, 'data' : JSON.stringify({ 'chk' : chk, 'pos' : pos, 'obj' : obj && obj.id ? obj.id : false, 'par' : par && par.id ? par.id : false }) }; | |
8399 } | |
8400 return i; | |
8401 } | |
8402 return true; | |
8403 }; | |
8404 this.create_node = function (par, node, pos, callback, is_loaded) { | |
8405 if(!node || node.text === undefined) { | |
8406 if(par === null) { | |
8407 par = $.jstree.root; | |
8408 } | |
8409 par = this.get_node(par); | |
8410 if(!par) { | |
8411 return parent.create_node.call(this, par, node, pos, callback, is_loaded); | |
8412 } | |
8413 pos = pos === undefined ? "last" : pos; | |
8414 if(!pos.toString().match(/^(before|after)$/) && !is_loaded && !this.is_loaded(par)) { | |
8415 return parent.create_node.call(this, par, node, pos, callback, is_loaded); | |
8416 } | |
8417 if(!node) { node = {}; } | |
8418 var tmp, n, dpc, i, j, m = this._model.data, s = this.settings.unique.case_sensitive, w = this.settings.unique.trim_whitespace, cb = this.settings.unique.duplicate, t; | |
8419 n = tmp = this.get_string('New node'); | |
8420 dpc = []; | |
8421 for(i = 0, j = par.children.length; i < j; i++) { | |
8422 t = m[par.children[i]].text; | |
8423 if (!s) { | |
8424 t = t.toLowerCase(); | |
8425 } | |
8426 if (w) { | |
8427 t = t.replace(/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g, ''); | |
8428 } | |
8429 dpc.push(t); | |
8430 } | |
8431 i = 1; | |
8432 t = n; | |
8433 if (!s) { | |
8434 t = t.toLowerCase(); | |
8435 } | |
8436 if (w) { | |
8437 t = t.replace(/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g, ''); | |
8438 } | |
8439 while($.inArray(t, dpc) !== -1) { | |
8440 n = cb.call(this, tmp, (++i)).toString(); | |
8441 t = n; | |
8442 if (!s) { | |
8443 t = t.toLowerCase(); | |
8444 } | |
8445 if (w) { | |
8446 t = t.replace(/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g, ''); | |
8447 } | |
8448 } | |
8449 node.text = n; | |
8450 } | |
8451 return parent.create_node.call(this, par, node, pos, callback, is_loaded); | |
8452 }; | |
8453 }; | |
8454 | |
8455 // include the unique plugin by default | |
8456 // $.jstree.defaults.plugins.push("unique"); | |
8457 | |
8458 | |
8459 /** | |
8460 * ### Wholerow plugin | |
8461 * | |
8462 * Makes each node appear block level. Making selection easier. May cause slow down for large trees in old browsers. | |
8463 */ | |
8464 | |
8465 var div = document.createElement('DIV'); | |
8466 div.setAttribute('unselectable','on'); | |
8467 div.setAttribute('role','presentation'); | |
8468 div.className = 'jstree-wholerow'; | |
8469 div.innerHTML = ' '; | |
8470 $.jstree.plugins.wholerow = function (options, parent) { | |
8471 this.bind = function () { | |
8472 parent.bind.call(this); | |
8473 | |
8474 this.element | |
8475 .on('ready.jstree set_state.jstree', $.proxy(function () { | |
8476 this.hide_dots(); | |
8477 }, this)) | |
8478 .on("init.jstree loading.jstree ready.jstree", $.proxy(function () { | |
8479 //div.style.height = this._data.core.li_height + 'px'; | |
8480 this.get_container_ul().addClass('jstree-wholerow-ul'); | |
8481 }, this)) | |
8482 .on("deselect_all.jstree", $.proxy(function (e, data) { | |
8483 this.element.find('.jstree-wholerow-clicked').removeClass('jstree-wholerow-clicked'); | |
8484 }, this)) | |
8485 .on("changed.jstree", $.proxy(function (e, data) { | |
8486 this.element.find('.jstree-wholerow-clicked').removeClass('jstree-wholerow-clicked'); | |
8487 var tmp = false, i, j; | |
8488 for(i = 0, j = data.selected.length; i < j; i++) { | |
8489 tmp = this.get_node(data.selected[i], true); | |
8490 if(tmp && tmp.length) { | |
8491 tmp.children('.jstree-wholerow').addClass('jstree-wholerow-clicked'); | |
8492 } | |
8493 } | |
8494 }, this)) | |
8495 .on("open_node.jstree", $.proxy(function (e, data) { | |
8496 this.get_node(data.node, true).find('.jstree-clicked').parent().children('.jstree-wholerow').addClass('jstree-wholerow-clicked'); | |
8497 }, this)) | |
8498 .on("hover_node.jstree dehover_node.jstree", $.proxy(function (e, data) { | |
8499 if(e.type === "hover_node" && this.is_disabled(data.node)) { return; } | |
8500 this.get_node(data.node, true).children('.jstree-wholerow')[e.type === "hover_node"?"addClass":"removeClass"]('jstree-wholerow-hovered'); | |
8501 }, this)) | |
8502 .on("contextmenu.jstree", ".jstree-wholerow", $.proxy(function (e) { | |
8503 if (this._data.contextmenu) { | |
8504 e.preventDefault(); | |
8505 var tmp = $.Event('contextmenu', { metaKey : e.metaKey, ctrlKey : e.ctrlKey, altKey : e.altKey, shiftKey : e.shiftKey, pageX : e.pageX, pageY : e.pageY }); | |
8506 $(e.currentTarget).closest(".jstree-node").children(".jstree-anchor").first().trigger(tmp); | |
8507 } | |
8508 }, this)) | |
8509 /*! | |
8510 .on("mousedown.jstree touchstart.jstree", ".jstree-wholerow", function (e) { | |
8511 if(e.target === e.currentTarget) { | |
8512 var a = $(e.currentTarget).closest(".jstree-node").children(".jstree-anchor"); | |
8513 e.target = a[0]; | |
8514 a.trigger(e); | |
8515 } | |
8516 }) | |
8517 */ | |
8518 .on("click.jstree", ".jstree-wholerow", function (e) { | |
8519 e.stopImmediatePropagation(); | |
8520 var tmp = $.Event('click', { metaKey : e.metaKey, ctrlKey : e.ctrlKey, altKey : e.altKey, shiftKey : e.shiftKey }); | |
8521 $(e.currentTarget).closest(".jstree-node").children(".jstree-anchor").first().trigger(tmp).focus(); | |
8522 }) | |
8523 .on("dblclick.jstree", ".jstree-wholerow", function (e) { | |
8524 e.stopImmediatePropagation(); | |
8525 var tmp = $.Event('dblclick', { metaKey : e.metaKey, ctrlKey : e.ctrlKey, altKey : e.altKey, shiftKey : e.shiftKey }); | |
8526 $(e.currentTarget).closest(".jstree-node").children(".jstree-anchor").first().trigger(tmp).focus(); | |
8527 }) | |
8528 .on("click.jstree", ".jstree-leaf > .jstree-ocl", $.proxy(function (e) { | |
8529 e.stopImmediatePropagation(); | |
8530 var tmp = $.Event('click', { metaKey : e.metaKey, ctrlKey : e.ctrlKey, altKey : e.altKey, shiftKey : e.shiftKey }); | |
8531 $(e.currentTarget).closest(".jstree-node").children(".jstree-anchor").first().trigger(tmp).focus(); | |
8532 }, this)) | |
8533 .on("mouseover.jstree", ".jstree-wholerow, .jstree-icon", $.proxy(function (e) { | |
8534 e.stopImmediatePropagation(); | |
8535 if(!this.is_disabled(e.currentTarget)) { | |
8536 this.hover_node(e.currentTarget); | |
8537 } | |
8538 return false; | |
8539 }, this)) | |
8540 .on("mouseleave.jstree", ".jstree-node", $.proxy(function (e) { | |
8541 this.dehover_node(e.currentTarget); | |
8542 }, this)); | |
8543 }; | |
8544 this.teardown = function () { | |
8545 if(this.settings.wholerow) { | |
8546 this.element.find(".jstree-wholerow").remove(); | |
8547 } | |
8548 parent.teardown.call(this); | |
8549 }; | |
8550 this.redraw_node = function(obj, deep, callback, force_render) { | |
8551 obj = parent.redraw_node.apply(this, arguments); | |
8552 if(obj) { | |
8553 var tmp = div.cloneNode(true); | |
8554 //tmp.style.height = this._data.core.li_height + 'px'; | |
8555 if($.inArray(obj.id, this._data.core.selected) !== -1) { tmp.className += ' jstree-wholerow-clicked'; } | |
8556 if(this._data.core.focused && this._data.core.focused === obj.id) { tmp.className += ' jstree-wholerow-hovered'; } | |
8557 obj.insertBefore(tmp, obj.childNodes[0]); | |
8558 } | |
8559 return obj; | |
8560 }; | |
8561 }; | |
8562 // include the wholerow plugin by default | |
8563 // $.jstree.defaults.plugins.push("wholerow"); | |
8564 if(document.registerElement && Object && Object.create) { | |
8565 var proto = Object.create(HTMLElement.prototype); | |
8566 proto.createdCallback = function () { | |
8567 var c = { core : {}, plugins : [] }, i; | |
8568 for(i in $.jstree.plugins) { | |
8569 if($.jstree.plugins.hasOwnProperty(i) && this.attributes[i]) { | |
8570 c.plugins.push(i); | |
8571 if(this.getAttribute(i) && JSON.parse(this.getAttribute(i))) { | |
8572 c[i] = JSON.parse(this.getAttribute(i)); | |
8573 } | |
8574 } | |
8575 } | |
8576 for(i in $.jstree.defaults.core) { | |
8577 if($.jstree.defaults.core.hasOwnProperty(i) && this.attributes[i]) { | |
8578 c.core[i] = JSON.parse(this.getAttribute(i)) || this.getAttribute(i); | |
8579 } | |
8580 } | |
8581 $(this).jstree(c); | |
8582 }; | |
8583 // proto.attributeChangedCallback = function (name, previous, value) { }; | |
8584 try { | |
8585 document.registerElement("vakata-jstree", { prototype: proto }); | |
8586 } catch(ignore) { } | |
8587 } | |
8588 | |
8589 })); |