Mercurial > repos > mingchen0919 > aurora_star
comparison vakata-jstree-3.3.5/src/jstree.js @ 0:25602263cff0 draft default tip
planemo upload commit 841d8b22bf9f1aaed6bfe8344b60617f45b275b2-dirty
author | mingchen0919 |
---|---|
date | Sun, 30 Dec 2018 13:11:48 -0500 |
parents | |
children |
comparison
equal
deleted
inserted
replaced
-1:000000000000 | 0:25602263cff0 |
---|---|
1 /*! | |
2 * jsTree {{VERSION}} | |
3 * http://jstree.com/ | |
4 * | |
5 * Copyright (c) 2014 Ivan Bozhanov (http://vakata.com) | |
6 * | |
7 * Licensed same as jquery - under the terms of the MIT License | |
8 * http://www.opensource.org/licenses/mit-license.php | |
9 */ | |
10 /*! | |
11 * if using jslint please allow for the jQuery global and use following options: | |
12 * jslint: loopfunc: true, browser: true, ass: true, bitwise: true, continue: true, nomen: true, plusplus: true, regexp: true, unparam: true, todo: true, white: true | |
13 */ | |
14 /*jshint -W083 */ | |
15 /*globals jQuery, define, module, exports, require, window, document, postMessage */ | |
16 (function (factory) { | |
17 "use strict"; | |
18 if (typeof define === 'function' && define.amd) { | |
19 define(['jquery'], factory); | |
20 } | |
21 else if(typeof module !== 'undefined' && module.exports) { | |
22 module.exports = factory(require('jquery')); | |
23 } | |
24 else { | |
25 factory(jQuery); | |
26 } | |
27 }(function ($, undefined) { | |
28 "use strict"; | |
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 : '{{VERSION}}', | |
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 })); |