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