vendor: 1,796 bytes, lines 1-66
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.16 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.16
vendor: 152,556 bytes, lines 66-4585
66', 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 "
vendor: 1,762 bytes, lines 4585-4636
4585blur" : function (e) { 4586 e.stopImmediatePropagation(); 4587 e.preventDefault(); 4588 var i = s.children(".jstree-rename-input"), 4589 v = i.val(), 4590 f = this.settings.core.force_text, 4591 nv; 4592 if(v === "") { v = t; } 4593 h1.remove(); 4594 s.replaceWith(a); 4595 s.remove(); 4596 t = f ? t : $('<div></div>').append($.parseHTML(t)).html(); 4597 obj = this.get_node(obj); 4598 this.set_text(obj, t); 4599 nv = !!this.rename_node(obj, f ? $('<div></div>').text(v).text() : $('<div></div>').append($.parseHTML(v)).html()); 4600 if(!nv) { 4601 this.set_text(obj, t); // move this up? and fix #483 4602 } 4603 this._data.core.focused = tmp.id; 4604 setTimeout(function () { 4605 var node = this.get_node(tmp.id, true); 4606 if(node.length) { 4607 this._data.core.focused = tmp.id; 4608 node.children('.jstree-anchor').trigger('focus'); 4609 } 4610 }.bind(this), 0); 4611 if(callback) { 4612 callback.call(this, tmp, nv, cancel, v); 4613 } 4614 h2 = null; 4615 }.bind(this), 4616 "keydown" : function (e) { 4617 var key = e.which; 4618 if(key === 27) { 4619 cancel = true; 4620 this.value = t; 4621 } 4622 if(key === 27 || key === 13 || key === 37 || key === 38 || key === 39 || key === 40 || key === 32) { 4623 e.stopImmediatePropagation(); 4624 } 4625 if(key === 27 || key === 13) { 4626 e.preventDefault(); 4627 this.blur(); 4628 } 4629 }, 4630 "click" : function (e) { e.stopImmediatePropagation(); }, 4631 "mousedown" : function (e) { e.stopImmediatePropagation(); }, 4632 "keyup" : function (e) { 4633 h2.width(Math.min(h1.text("pW" + this.value).width(),w)); 4634 }, 4635 "keypress" : function(e) { 4636 if(e.which === 13) { return false;
vendor: 154,453 bytes, lines 4636-8743
4636} 4637 } 4638 }); 4639 fn = { 4640 fontFamily : a.css('fontFamily') || '', 4641 fontSize : a.css('fontSize') || '', 4642 fontWeight : a.css('fontWeight') || '', 4643 fontStyle : a.css('fontStyle') || '', 4644 fontStretch : a.css('fontStretch') || '', 4645 fontVariant : a.css('fontVariant') || '', 4646 letterSpacing : a.css('letterSpacing') || '', 4647 wordSpacing : a.css('wordSpacing') || '' 4648 }; 4649 s.attr('class', a.attr('class')).append(a.contents().clone()).append(h2); 4650 a.replaceWith(s); 4651 h1.css(fn); 4652 h2.css(fn).width(Math.min(h1.text("pW" + h2[0].value).width(),w))[0].select(); 4653 $(document).one('mousedown.jstree touchstart.jstree dnd_start.vakata', function (e) { 4654 if (h2 && e.target !== h2) { 4655 $(h2).trigger('blur'); 4656 } 4657 }); 4658 }, 4659 4660 4661 /** 4662 * changes the theme 4663 * @name set_theme(theme_name [, theme_url]) 4664 * @param {String} theme_name the name of the new theme to apply 4665 * @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. 4666 * @trigger set_theme.jstree 4667 */ 4668 set_theme : function (theme_name, theme_url) { 4669 if(!theme_name) { return false; } 4670 if(theme_url === true) { 4671 var dir = this.settings.core.themes.dir; 4672 if(!dir) { dir = $.jstree.path + '/themes'; } 4673 theme_url = dir + '/' + theme_name + '/style.css'; 4674 } 4675 if(theme_url && $.inArray(theme_url, themes_loaded) === -1) { 4676 $('head').append('<'+'link rel="stylesheet" href="' + theme_url + '" type="text/css" />'); 4677 themes_loaded.push(theme_url); 4678 } 4679 if(this._data.core.themes.name) { 4680 this.element.removeClass('jstree-' + this._data.core.themes.name); 4681 } 4682 this._data.core.themes.name = theme_name; 4683 this.element.addClass('jstree-' + theme_name); 4684 this.element[this.settings.core.themes.responsive ? 'addClass' : 'removeClass' ]('jstree-' + theme_name + '-responsive'); 4685 /** 4686 * triggered when a theme is set 4687 * @event 4688 * @name set_theme.jstree 4689 * @param {String} theme the new theme 4690 */ 4691 this.trigger('set_theme', { 'theme' : theme_name }); 4692 }, 4693 /** 4694 * gets the name of the currently applied theme name 4695 * @name get_theme() 4696 * @return {String} 4697 */ 4698 get_theme : function () { return this._data.core.themes.name; }, 4699 /** 4700 * changes the theme variant (if the theme has variants) 4701 * @name set_theme_variant(variant_name) 4702 * @param {String|Boolean} variant_name the variant to apply (if `false` is used the current variant is removed) 4703 */ 4704 set_theme_variant : function (variant_name) { 4705 if(this._data.core.themes.variant) { 4706 this.element.removeClass('jstree-' + this._data.core.themes.name + '-' + this._data.core.themes.variant); 4707 } 4708 this._data.core.themes.variant = variant_name; 4709 if(variant_name) { 4710 this.element.addClass('jstree-' + this._data.core.themes.name + '-' + this._data.core.themes.variant); 4711 } 4712 }, 4713 /** 4714 * gets the name of the currently applied theme variant 4715 * @name get_theme() 4716 * @return {String} 4717 */ 4718 get_theme_variant : function () { return this._data.core.themes.variant; }, 4719 /** 4720 * shows a striped background on the container (if the theme supports it) 4721 * @name show_stripes() 4722 */ 4723 show_stripes : function () { 4724 this._data.core.themes.stripes = true; 4725 this.get_container_ul().addClass("jstree-striped"); 4726 /** 4727 * triggered when stripes are shown 4728 * @event 4729 * @name show_stripes.jstree 4730 */ 4731 this.trigger('show_stripes'); 4732 }, 4733 /** 4734 * hides the striped background on the container 4735 * @name hide_stripes() 4736 */ 4737 hide_stripes : function () { 4738 this._data.core.themes.stripes = false; 4739 this.get_container_ul().removeClass("jstree-striped"); 4740 /** 4741 * triggered when stripes are hidden 4742 * @event 4743 * @name hide_stripes.jstree 4744 */ 4745 this.trigger('hide_stripes'); 4746 }, 4747 /** 4748 * toggles the striped background on the container 4749 * @name toggle_stripes() 4750 */ 4751 toggle_stripes : function () { if(this._data.core.themes.stripes) { this.hide_stripes(); } else { this.show_stripes(); } }, 4752 /** 4753 * shows the connecting dots (if the theme supports it) 4754 * @name show_dots() 4755 */ 4756 show_dots : function () { 4757 this._data.core.themes.dots = true; 4758 this.get_container_ul().removeClass("jstree-no-dots"); 4759 /** 4760 * triggered when dots are shown 4761 * @event 4762 * @name show_dots.jstree 4763 */ 4764 this.trigger('show_dots'); 4765 }, 4766 /** 4767 * hides the connecting dots 4768 * @name hide_dots() 4769 */ 4770 hide_dots : function () { 4771 this._data.core.themes.dots = false; 4772 this.get_container_ul().addClass("jstree-no-dots"); 4773 /** 4774 * triggered when dots are hidden 4775 * @event 4776 * @name hide_dots.jstree 4777 */ 4778 this.trigger('hide_dots'); 4779 }, 4780 /** 4781 * toggles the connecting dots 4782 * @name toggle_dots() 4783 */ 4784 toggle_dots : function () { if(this._data.core.themes.dots) { this.hide_dots(); } else { this.show_dots(); } }, 4785 /** 4786 * show the node icons 4787 * @name show_icons() 4788 */ 4789 show_icons : function () { 4790 this._data.core.themes.icons = true; 4791 this.get_container_ul().removeClass("jstree-no-icons"); 4792 /** 4793 * triggered when icons are shown 4794 * @event 4795 * @name show_icons.jstree 4796 */ 4797 this.trigger('show_icons'); 4798 }, 4799 /** 4800 * hide the node icons 4801 * @name hide_icons() 4802 */ 4803 hide_icons : function () { 4804 this._data.core.themes.icons = false; 4805 this.get_container_ul().addClass("jstree-no-icons"); 4806 /** 4807 * triggered when icons are hidden 4808 * @event 4809 * @name hide_icons.jstree 4810 */ 4811 this.trigger('hide_icons'); 4812 }, 4813 /** 4814 * toggle the node icons 4815 * @name toggle_icons() 4816 */ 4817 toggle_icons : function () { if(this._data.core.themes.icons) { this.hide_icons(); } else { this.show_icons(); } }, 4818 /** 4819 * show the node ellipsis 4820 * @name show_icons() 4821 */ 4822 show_ellipsis : function () { 4823 this._data.core.themes.ellipsis = true; 4824 this.get_container_ul().addClass("jstree-ellipsis"); 4825 /** 4826 * triggered when ellisis is shown 4827 * @event 4828 * @name show_ellipsis.jstree 4829 */ 4830 this.trigger('show_ellipsis'); 4831 }, 4832 /** 4833 * hide the node ellipsis 4834 * @name hide_ellipsis() 4835 */ 4836 hide_ellipsis : function () { 4837 this._data.core.themes.ellipsis = false; 4838 this.get_container_ul().removeClass("jstree-ellipsis"); 4839 /** 4840 * triggered when ellisis is hidden 4841 * @event 4842 * @name hide_ellipsis.jstree 4843 */ 4844 this.trigger('hide_ellipsis'); 4845 }, 4846 /** 4847 * toggle the node ellipsis 4848 * @name toggle_icons() 4849 */ 4850 toggle_ellipsis : function () { if(this._data.core.themes.ellipsis) { this.hide_ellipsis(); } else { this.show_ellipsis(); } }, 4851 /** 4852 * set the node icon for a node 4853 * @name set_icon(obj, icon) 4854 * @param {mixed} obj 4855 * @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 4856 */ 4857 set_icon : function (obj, icon) { 4858 var t1, t2, dom, old; 4859 if($.vakata.is_array(obj)) { 4860 obj = obj.slice(); 4861 for(t1 = 0, t2 = obj.length; t1 < t2; t1++) { 4862 this.set_icon(obj[t1], icon); 4863 } 4864 return true; 4865 } 4866 obj = this.get_node(obj); 4867 if(!obj || obj.id === $.jstree.root) { return false; } 4868 old = obj.icon; 4869 obj.icon = icon === true || icon === null || icon === undefined || icon === '' ? true : icon; 4870 dom = this.get_node(obj, true).children(".jstree-anchor").children(".jstree-themeicon"); 4871 if(icon === false) { 4872 dom.removeClass('jstree-themeicon-custom ' + old).css("background","").removeAttr("rel"); 4873 this.hide_icon(obj); 4874 } 4875 else if(icon === true || icon === null || icon === undefined || icon === '') { 4876 dom.removeClass('jstree-themeicon-custom ' + old).css("background","").removeAttr("rel"); 4877 if(old === false) { this.show_icon(obj); } 4878 } 4879 else if(icon.indexOf("/") === -1 && icon.indexOf(".") === -1) { 4880 dom.removeClass(old).css("background",""); 4881 dom.addClass(icon + ' jstree-themeicon-custom').attr("rel",icon); 4882 if(old === false) { this.show_icon(obj); } 4883 } 4884 else { 4885 dom.removeClass(old).css("background",""); 4886 dom.addClass('jstree-themeicon-custom').css("background", "url('" + icon + "') center center no-repeat").attr("rel",icon); 4887 if(old === false) { this.show_icon(obj); } 4888 } 4889 return true; 4890 }, 4891 /** 4892 * get the node icon for a node 4893 * @name get_icon(obj) 4894 * @param {mixed} obj 4895 * @return {String} 4896 */ 4897 get_icon : function (obj) { 4898 obj = this.get_node(obj); 4899 return (!obj || obj.id === $.jstree.root) ? false : obj.icon; 4900 }, 4901 /** 4902 * hide the icon on an individual node 4903 * @name hide_icon(obj) 4904 * @param {mixed} obj 4905 */ 4906 hide_icon : function (obj) { 4907 var t1, t2; 4908 if($.vakata.is_array(obj)) { 4909 obj = obj.slice(); 4910 for(t1 = 0, t2 = obj.length; t1 < t2; t1++) { 4911 this.hide_icon(obj[t1]); 4912 } 4913 return true; 4914 } 4915 obj = this.get_node(obj); 4916 if(!obj || obj === $.jstree.root) { return false; } 4917 obj.icon = false; 4918 this.get_node(obj, true).children(".jstree-anchor").children(".jstree-themeicon").addClass('jstree-themeicon-hidden'); 4919 return true; 4920 }, 4921 /** 4922 * show the icon on an individual node 4923 * @name show_icon(obj) 4924 * @param {mixed} obj 4925 */ 4926 show_icon : function (obj) { 4927 var t1, t2, dom; 4928 if($.vakata.is_array(obj)) { 4929 obj = obj.slice(); 4930 for(t1 = 0, t2 = obj.length; t1 < t2; t1++) { 4931 this.show_icon(obj[t1]); 4932 } 4933 return true; 4934 } 4935 obj = this.get_node(obj); 4936 if(!obj || obj === $.jstree.root) { return false; } 4937 dom = this.get_node(obj, true); 4938 obj.icon = dom.length ? dom.children(".jstree-anchor").children(".jstree-themeicon").attr('rel') : true; 4939 if(!obj.icon) { obj.icon = true; } 4940 dom.children(".jstree-anchor").children(".jstree-themeicon").removeClass('jstree-themeicon-hidden'); 4941 return true; 4942 } 4943 }; 4944 4945 // helpers 4946 $.vakata = {}; 4947 // collect attributes 4948 $.vakata.attributes = function(node, with_values) { 4949 node = $(node)[0]; 4950 var attr = with_values ? {} : []; 4951 if(node && node.attributes) { 4952 $.each(node.attributes, function (i, v) { 4953 if($.inArray(v.name.toLowerCase(),['style','contenteditable','hasfocus','tabindex']) !== -1) { return; } 4954 if(v.value !== null && $.vakata.trim(v.value) !== '') { 4955 if(with_values) { attr[v.name] = v.value; } 4956 else { attr.push(v.name); } 4957 } 4958 }); 4959 } 4960 return attr; 4961 }; 4962 $.vakata.array_unique = function(array) { 4963 var a = [], i, j, l, o = {}; 4964 for(i = 0, l = array.length; i < l; i++) { 4965 if(o[array[i]] === undefined) { 4966 a.push(array[i]); 4967 o[array[i]] = true; 4968 } 4969 } 4970 return a; 4971 }; 4972 // remove item from array 4973 $.vakata.array_remove = function(array, from) { 4974 array.splice(from, 1); 4975 return array; 4976 //var rest = array.slice((to || from) + 1 || array.length); 4977 //array.length = from < 0 ? array.length + from : from; 4978 //array.push.apply(array, rest); 4979 //return array; 4980 }; 4981 // remove item from array 4982 $.vakata.array_remove_item = function(array, item) { 4983 var tmp = $.inArray(item, array); 4984 return tmp !== -1 ? $.vakata.array_remove(array, tmp) : array; 4985 }; 4986 $.vakata.array_filter = function(c,a,b,d,e) { 4987 if (c.filter) { 4988 return c.filter(a, b); 4989 } 4990 d=[]; 4991 for (e in c) { 4992 if (~~e+''===e+'' && e>=0 && a.call(b,c[e],+e,c)) { 4993 d.push(c[e]); 4994 } 4995 } 4996 return d; 4997 }; 4998 $.vakata.trim = function (text) { 4999 return String.prototype.trim ? 5000 String.prototype.trim.call(text.toString()) : 5001 text.toString().replace(/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g, ''); 5002 }; 5003 $.vakata.is_function = function(obj) { 5004 return typeof obj === "function" && typeof obj.nodeType !== "number"; 5005 }; 5006 $.vakata.is_array = Array.isArray || function (obj) { 5007 return Object.prototype.toString.call(obj) === "[object Array]"; 5008 }; 5009 5010 // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_objects/Function/bind#polyfill 5011 if (!Function.prototype.bind) { 5012 Function.prototype.bind = function () { 5013 var thatFunc = this, thatArg = arguments[0]; 5014 var args = Array.prototype.slice.call(arguments, 1); 5015 if (typeof thatFunc !== 'function') { 5016 // closest thing possible to the ECMAScript 5 5017 // internal IsCallable function 5018 throw new TypeError('Function.prototype.bind - what is trying to be bound is not callable'); 5019 } 5020 return function(){ 5021 var funcArgs = args.concat(Array.prototype.slice.call(arguments)); 5022 return thatFunc.apply(thatArg, funcArgs); 5023 }; 5024 }; 5025 } 5026 5027 5028/** 5029 * ### Changed plugin 5030 * 5031 * 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. 5032 */ 5033 5034 $.jstree.plugins.changed = function (options, parent) { 5035 var last = []; 5036 this.trigger = function (ev, data) { 5037 var i, j; 5038 if(!data) { 5039 data = {}; 5040 } 5041 if(ev.replace('.jstree','') === 'changed') { 5042 data.changed = { selected : [], deselected : [] }; 5043 var tmp = {}; 5044 for(i = 0, j = last.length; i < j; i++) { 5045 tmp[last[i]] = 1; 5046 } 5047 for(i = 0, j = data.selected.length; i < j; i++) { 5048 if(!tmp[data.selected[i]]) { 5049 data.changed.selected.push(data.selected[i]); 5050 } 5051 else { 5052 tmp[data.selected[i]] = 2; 5053 } 5054 } 5055 for(i = 0, j = last.length; i < j; i++) { 5056 if(tmp[last[i]] === 1) { 5057 data.changed.deselected.push(last[i]); 5058 } 5059 } 5060 last = data.selected.slice(); 5061 } 5062 /** 5063 * triggered when selection changes (the "changed" plugin enhances the original event with more data) 5064 * @event 5065 * @name changed.jstree 5066 * @param {Object} node 5067 * @param {Object} action the action that caused the selection to change 5068 * @param {Array} selected the current selection 5069 * @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 5070 * @param {Object} event the event (if any) that triggered this changed event 5071 * @plugin changed 5072 */ 5073 parent.trigger.call(this, ev, data); 5074 }; 5075 this.refresh = function (skip_loading, forget_state) { 5076 last = []; 5077 return parent.refresh.apply(this, arguments); 5078 }; 5079 }; 5080 5081/** 5082 * ### Checkbox plugin 5083 * 5084 * This plugin renders checkbox icons in front of each node, making multiple selection much easier. 5085 * 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. 5086 */ 5087 5088 var _i = document.createElement('I'); 5089 _i.className = 'jstree-icon jstree-checkbox'; 5090 _i.setAttribute('role', 'presentation'); 5091 /** 5092 * stores all defaults for the checkbox plugin 5093 * @name $.jstree.defaults.checkbox 5094 * @plugin checkbox 5095 */ 5096 $.jstree.defaults.checkbox = { 5097 /** 5098 * a boolean indicating if checkboxes should be visible (can be changed at a later time using `show_checkboxes()` and `hide_checkboxes`). Defaults to `true`. 5099 * @name $.jstree.defaults.checkbox.visible 5100 * @plugin checkbox 5101 */ 5102 visible : true, 5103 /** 5104 * a boolean indicating if checkboxes should cascade down and have an undetermined state. Defaults to `true`. 5105 * @name $.jstree.defaults.checkbox.three_state 5106 * @plugin checkbox 5107 */ 5108 three_state : true, 5109 /** 5110 * a boolean indicating if clicking anywhere on the node should act as clicking on the checkbox. Defaults to `true`. 5111 * @name $.jstree.defaults.checkbox.whole_node 5112 * @plugin checkbox 5113 */ 5114 whole_node : true, 5115 /** 5116 * a boolean indicating if the selected style of a node should be kept, or removed. Defaults to `true`. 5117 * @name $.jstree.defaults.checkbox.keep_selected_style 5118 * @plugin checkbox 5119 */ 5120 keep_selected_style : true, 5121 /** 5122 * This setting controls how cascading and undetermined nodes are applied. 5123 * 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. 5124 * If `three_state` is set to `true` this setting is automatically set to 'up+down+undetermined'. Defaults to ''. 5125 * @name $.jstree.defaults.checkbox.cascade 5126 * @plugin checkbox 5127 */ 5128 cascade : '', 5129 /** 5130 * 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. 5131 * @name $.jstree.defaults.checkbox.tie_selection 5132 * @plugin checkbox 5133 */ 5134 tie_selection : true, 5135 5136 /** 5137 * This setting controls if cascading down affects disabled checkboxes 5138 * @name $.jstree.defaults.checkbox.cascade_to_disabled 5139 * @plugin checkbox 5140 */ 5141 cascade_to_disabled : true, 5142 5143 /** 5144 * This setting controls if cascading down affects hidden checkboxes 5145 * @name $.jstree.defaults.checkbox.cascade_to_hidden 5146 * @plugin checkbox 5147 */ 5148 cascade_to_hidden : true 5149 }; 5150 $.jstree.plugins.checkbox = function (options, parent) { 5151 this.bind = function () { 5152 parent.bind.call(this); 5153 this._data.checkbox.uto = false; 5154 this._data.checkbox.selected = []; 5155 if(this.settings.checkbox.three_state) { 5156 this.settings.checkbox.cascade = 'up+down+undetermined'; 5157 } 5158 this.element 5159 .on("init.jstree", function () { 5160 this._data.checkbox.visible = this.settings.checkbox.visible; 5161 if(!this.settings.checkbox.keep_selected_style) { 5162 this.element.addClass('jstree-checkbox-no-clicked'); 5163 } 5164 if(this.settings.checkbox.tie_selection) { 5165 this.element.addClass('jstree-checkbox-selection'); 5166 } 5167 }.bind(this)) 5168 .on("loading.jstree", function () { 5169 this[ this._data.checkbox.visible ? 'show_checkboxes' : 'hide_checkboxes' ](); 5170 }.bind(this)); 5171 if(this.settings.checkbox.cascade.indexOf('undetermined') !== -1) { 5172 this.element 5173 .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 () { 5174 // only if undetermined is in setting 5175 if(this._data.checkbox.uto) { clearTimeout(this._data.checkbox.uto); } 5176 this._data.checkbox.uto = setTimeout(this._undetermined.bind(this), 50); 5177 }.bind(this)); 5178 } 5179 if(!this.settings.checkbox.tie_selection) { 5180 this.element 5181 .on('model.jstree', function (e, data) { 5182 var m = this._model.data, 5183 p = m[data.parent], 5184 dpc = data.nodes, 5185 i, j; 5186 for(i = 0, j = dpc.length; i < j; i++) { 5187 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); 5188 if(m[dpc[i]].state.checked) { 5189 this._data.checkbox.selected.push(dpc[i]); 5190 } 5191 } 5192 }.bind(this)); 5193 } 5194 if(this.settings.checkbox.cascade.indexOf('up') !== -1 || this.settings.checkbox.cascade.indexOf('down') !== -1) { 5195 this.element 5196 .on('model.jstree', function (e, data) { 5197 var m = this._model.data, 5198 p = m[data.parent], 5199 dpc = data.nodes, 5200 chd = [], 5201 c, i, j, k, l, tmp, s = this.settings.checkbox.cascade, t = this.settings.checkbox.tie_selection; 5202 5203 if(s.indexOf('down') !== -1) { 5204 // apply down 5205 if(p.state[ t ? 'selected' : 'checked' ]) { 5206 for(i = 0, j = dpc.length; i < j; i++) { 5207 m[dpc[i]].state[ t ? 'selected' : 'checked' ] = true; 5208 } 5209 5210 this._data[ t ? 'core' : 'checkbox' ].selected = this._data[ t ? 'core' : 'checkbox' ].selected.concat(dpc); 5211 } 5212 else { 5213 for(i = 0, j = dpc.length; i < j; i++) { 5214 if(m[dpc[i]].state[ t ? 'selected' : 'checked' ]) { 5215 for(k = 0, l = m[dpc[i]].children_d.length; k < l; k++) { 5216 m[m[dpc[i]].children_d[k]].state[ t ? 'selected' : 'checked' ] = true; 5217 } 5218 this._data[ t ? 'core' : 'checkbox' ].selected = this._data[ t ? 'core' : 'checkbox' ].selected.concat(m[dpc[i]].children_d); 5219 } 5220 } 5221 } 5222 } 5223 5224 if(s.indexOf('up') !== -1) { 5225 // apply up 5226 for(i = 0, j = p.children_d.length; i < j; i++) { 5227 if(!m[p.children_d[i]].children.length) { 5228 chd.push(m[p.children_d[i]].parent); 5229 } 5230 } 5231 chd = $.vakata.array_unique(chd); 5232 for(k = 0, l = chd.length; k < l; k++) { 5233 p = m[chd[k]]; 5234 while(p && p.id !== $.jstree.root) { 5235 c = 0; 5236 for(i = 0, j = p.children.length; i < j; i++) { 5237 c += m[p.children[i]].state[ t ? 'selected' : 'checked' ]; 5238 } 5239 if(c === j) { 5240 p.state[ t ? 'selected' : 'checked' ] = true; 5241 this._data[ t ? 'core' : 'checkbox' ].selected.push(p.id); 5242 tmp = this.get_node(p, true); 5243 if(tmp && tmp.length) { 5244 tmp.children('.jstree-anchor').attr('aria-selected', true).addClass( t ? 'jstree-clicked' : 'jstree-checked'); 5245 } 5246 } 5247 else { 5248 break; 5249 } 5250 p = this.get_node(p.parent); 5251 } 5252 } 5253 } 5254 5255 this._data[ t ? 'core' : 'checkbox' ].selected = $.vakata.array_unique(this._data[ t ? 'core' : 'checkbox' ].selected); 5256 }.bind(this)) 5257 .on(this.settings.checkbox.tie_selection ? 'select_node.jstree' : 'check_node.jstree', function (e, data) { 5258 var self = this, 5259 obj = data.node, 5260 m = this._model.data, 5261 par = this.get_node(obj.parent), 5262 i, j, c, tmp, s = this.settings.checkbox.cascade, t = this.settings.checkbox.tie_selection, 5263 sel = {}, cur = this._data[ t ? 'core' : 'checkbox' ].selected; 5264 5265 for (i = 0, j = cur.length; i < j; i++) { 5266 sel[cur[i]] = true; 5267 } 5268 5269 // apply down 5270 if(s.indexOf('down') !== -1) { 5271 //this._data[ t ? 'core' : 'checkbox' ].selected = $.vakata.array_unique(this._data[ t ? 'core' : 'checkbox' ].selected.concat(obj.children_d)); 5272 var selectedIds = this._cascade_new_checked_state(obj.id, true); 5273 var temp = obj.children_d.concat(obj.id); 5274 for (i = 0, j = temp.length; i < j; i++) { 5275 if (selectedIds.indexOf(temp[i]) > -1) { 5276 sel[temp[i]] = true; 5277 } 5278 else { 5279 delete sel[temp[i]]; 5280 } 5281 } 5282 } 5283 5284 // apply up 5285 if(s.indexOf('up') !== -1) { 5286 while(par && par.id !== $.jstree.root) { 5287 c = 0; 5288 for(i = 0, j = par.children.length; i < j; i++) { 5289 c += m[par.children[i]].state[ t ? 'selected' : 'checked' ]; 5290 } 5291 if(c === j) { 5292 par.state[ t ? 'selected' : 'checked' ] = true; 5293 sel[par.id] = true; 5294 //this._data[ t ? 'core' : 'checkbox' ].selected.push(par.id); 5295 tmp = this.get_node(par, true); 5296 if(tmp && tmp.length) { 5297 tmp.children('.jstree-anchor').attr('aria-selected', true).addClass(t ? 'jstree-clicked' : 'jstree-checked'); 5298 } 5299 } 5300 else { 5301 break; 5302 } 5303 par = this.get_node(par.parent); 5304 } 5305 } 5306 5307 cur = []; 5308 for (i in sel) { 5309 if (sel.hasOwnProperty(i)) { 5310 cur.push(i); 5311 } 5312 } 5313 this._data[ t ? 'core' : 'checkbox' ].selected = cur; 5314 }.bind(this)) 5315 .on(this.settings.checkbox.tie_selection ? 'deselect_all.jstree' : 'uncheck_all.jstree', function (e, data) { 5316 var obj = this.get_node($.jstree.root), 5317 m = this._model.data, 5318 i, j, tmp; 5319 for(i = 0, j = obj.children_d.length; i < j; i++) { 5320 tmp = m[obj.children_d[i]]; 5321 if(tmp && tmp.original && tmp.original.state && tmp.original.state.undetermined) { 5322 tmp.original.state.undetermined = false; 5323 } 5324 } 5325 }.bind(this)) 5326 .on(this.settings.checkbox.tie_selection ? 'deselect_node.jstree' : 'uncheck_node.jstree', function (e, data) { 5327 var self = this, 5328 obj = data.node, 5329 dom = this.get_node(obj, true), 5330 i, j, tmp, s = this.settings.checkbox.cascade, t = this.settings.checkbox.tie_selection, 5331 cur = this._data[ t ? 'core' : 'checkbox' ].selected, sel = {}, 5332 stillSelectedIds = [], 5333 allIds = obj.children_d.concat(obj.id); 5334 5335 // apply down 5336 if(s.indexOf('down') !== -1) { 5337 var selectedIds = this._cascade_new_checked_state(obj.id, false); 5338 5339 cur = $.vakata.array_filter(cur, function(id) { 5340 return allIds.indexOf(id) === -1 || selectedIds.indexOf(id) > -1; 5341 }); 5342 } 5343 5344 // only apply up if cascade up is enabled and if this node is not selected 5345 // (if all child nodes are disabled and cascade_to_disabled === false then this node will till be selected). 5346 if(s.indexOf('up') !== -1 && cur.indexOf(obj.id) === -1) { 5347 for(i = 0, j = obj.parents.length; i < j; i++) { 5348 tmp = this._model.data[obj.parents[i]]; 5349 tmp.state[ t ? 'selected' : 'checked' ] = false; 5350 if(tmp && tmp.original && tmp.original.state && tmp.original.state.undetermined) { 5351 tmp.original.state.undetermined = false; 5352 } 5353 tmp = this.get_node(obj.parents[i], true); 5354 if(tmp && tmp.length) { 5355 tmp.children('.jstree-anchor').attr('aria-selected', false).removeClass(t ? 'jstree-clicked' : 'jstree-checked'); 5356 } 5357 } 5358 5359 cur = $.vakata.array_filter(cur, function(id) { 5360 return obj.parents.indexOf(id) === -1; 5361 }); 5362 } 5363 5364 this._data[ t ? 'core' : 'checkbox' ].selected = cur; 5365 }.bind(this)); 5366 } 5367 if(this.settings.checkbox.cascade.indexOf('up') !== -1) { 5368 this.element 5369 .on('delete_node.jstree', function (e, data) { 5370 // apply up (whole handler) 5371 var p = this.get_node(data.parent), 5372 m = this._model.data, 5373 i, j, c, tmp, t = this.settings.checkbox.tie_selection; 5374 while(p && p.id !== $.jstree.root && !p.state[ t ? 'selected' : 'checked' ]) { 5375 c = 0; 5376 for(i = 0, j = p.children.length; i < j; i++) { 5377 c += m[p.children[i]].state[ t ? 'selected' : 'checked' ]; 5378 } 5379 if(j > 0 && c === j) { 5380 p.state[ t ? 'selected' : 'checked' ] = true; 5381 this._data[ t ? 'core' : 'checkbox' ].selected.push(p.id); 5382 tmp = this.get_node(p, true); 5383 if(tmp && tmp.length) { 5384 tmp.children('.jstree-anchor').attr('aria-selected', true).addClass(t ? 'jstree-clicked' : 'jstree-checked'); 5385 } 5386 } 5387 else { 5388 break; 5389 } 5390 p = this.get_node(p.parent); 5391 } 5392 }.bind(this)) 5393 .on('move_node.jstree', function (e, data) { 5394 // apply up (whole handler) 5395 var is_multi = data.is_multi, 5396 old_par = data.old_parent, 5397 new_par = this.get_node(data.parent), 5398 m = this._model.data, 5399 p, c, i, j, tmp, t = this.settings.checkbox.tie_selection; 5400 if(!is_multi) { 5401 p = this.get_node(old_par); 5402 while(p && p.id !== $.jstree.root && !p.state[ t ? 'selected' : 'checked' ]) { 5403 c = 0; 5404 for(i = 0, j = p.children.length; i < j; i++) { 5405 c += m[p.children[i]].state[ t ? 'selected' : 'checked' ]; 5406 } 5407 if(j > 0 && c === j) { 5408 p.state[ t ? 'selected' : 'checked' ] = true; 5409 this._data[ t ? 'core' : 'checkbox' ].selected.push(p.id); 5410 tmp = this.get_node(p, true); 5411 if(tmp && tmp.length) { 5412 tmp.children('.jstree-anchor').attr('aria-selected', true).addClass(t ? 'jstree-clicked' : 'jstree-checked'); 5413 } 5414 } 5415 else { 5416 break; 5417 } 5418 p = this.get_node(p.parent); 5419 } 5420 } 5421 p = new_par; 5422 while(p && p.id !== $.jstree.root) { 5423 c = 0; 5424 for(i = 0, j = p.children.length; i < j; i++) { 5425 c += m[p.children[i]].state[ t ? 'selected' : 'checked' ]; 5426 } 5427 if(c === j) { 5428 if(!p.state[ t ? 'selected' : 'checked' ]) { 5429 p.state[ t ? 'selected' : 'checked' ] = true; 5430 this._data[ t ? 'core' : 'checkbox' ].selected.push(p.id); 5431 tmp = this.get_node(p, true); 5432 if(tmp && tmp.length) { 5433 tmp.children('.jstree-anchor').attr('aria-selected', true).addClass(t ? 'jstree-clicked' : 'jstree-checked'); 5434 } 5435 } 5436 } 5437 else { 5438 if(p.state[ t ? 'selected' : 'checked' ]) { 5439 p.state[ t ? 'selected' : 'checked' ] = false; 5440 this._data[ t ? 'core' : 'checkbox' ].selected = $.vakata.array_remove_item(this._data[ t ? 'core' : 'checkbox' ].selected, p.id); 5441 tmp = this.get_node(p, true); 5442 if(tmp && tmp.length) { 5443 tmp.children('.jstree-anchor').attr('aria-selected', false).removeClass(t ? 'jstree-clicked' : 'jstree-checked'); 5444 } 5445 } 5446 else { 5447 break; 5448 } 5449 } 5450 p = this.get_node(p.parent); 5451 } 5452 }.bind(this)); 5453 } 5454 }; 5455 /** 5456 * get an array of all nodes whose state is "undetermined" 5457 * @name get_undetermined([full]) 5458 * @param {boolean} full: if set to `true` the returned array will consist of the full node objects, otherwise - only IDs will be returned 5459 * @return {Array} 5460 * @plugin checkbox 5461 */ 5462 this.get_undetermined = function (full) { 5463 if (this.settings.checkbox.cascade.indexOf('undetermined') === -1) { 5464 return []; 5465 } 5466 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 = []; 5467 for(i = 0, j = s.length; i < j; i++) { 5468 if(m[s[i]] && m[s[i]].parents) { 5469 for(k = 0, l = m[s[i]].parents.length; k < l; k++) { 5470 if(o[m[s[i]].parents[k]] !== undefined) { 5471 break; 5472 } 5473 if(m[s[i]].parents[k] !== $.jstree.root) { 5474 o[m[s[i]].parents[k]] = true; 5475 p.push(m[s[i]].parents[k]); 5476 } 5477 } 5478 } 5479 } 5480 // attempt for server side undetermined state 5481 this.element.find('.jstree-closed').not(':has(.jstree-children)') 5482 .each(function () { 5483 var tmp = tt.get_node(this), tmp2; 5484 5485 if(!tmp) { return; } 5486 5487 if(!tmp.state.loaded) { 5488 if(tmp.original && tmp.original.state && tmp.original.state.undetermined && tmp.original.state.undetermined === true) { 5489 if(o[tmp.id] === undefined && tmp.id !== $.jstree.root) { 5490 o[tmp.id] = true; 5491 p.push(tmp.id); 5492 } 5493 for(k = 0, l = tmp.parents.length; k < l; k++) { 5494 if(o[tmp.parents[k]] === undefined && tmp.parents[k] !== $.jstree.root) { 5495 o[tmp.parents[k]] = true; 5496 p.push(tmp.parents[k]); 5497 } 5498 } 5499 } 5500 } 5501 else { 5502 for(i = 0, j = tmp.children_d.length; i < j; i++) { 5503 tmp2 = m[tmp.children_d[i]]; 5504 if(!tmp2.state.loaded && tmp2.original && tmp2.original.state && tmp2.original.state.undetermined && tmp2.original.state.undetermined === true) { 5505 if(o[tmp2.id] === undefined && tmp2.id !== $.jstree.root) { 5506 o[tmp2.id] = true; 5507 p.push(tmp2.id); 5508 } 5509 for(k = 0, l = tmp2.parents.length; k < l; k++) { 5510 if(o[tmp2.parents[k]] === undefined && tmp2.parents[k] !== $.jstree.root) { 5511 o[tmp2.parents[k]] = true; 5512 p.push(tmp2.parents[k]); 5513 } 5514 } 5515 } 5516 } 5517 } 5518 }); 5519 for (i = 0, j = p.length; i < j; i++) { 5520 if(!m[p[i]].state[ t ? 'selected' : 'checked' ]) { 5521 r.push(full ? m[p[i]] : p[i]); 5522 } 5523 } 5524 return r; 5525 }; 5526 /** 5527 * set the undetermined state where and if necessary. Used internally. 5528 * @private 5529 * @name _undetermined() 5530 * @plugin checkbox 5531 */ 5532 this._undetermined = function () { 5533 if(this.element === null) { return; } 5534 var p = this.get_undetermined(false), i, j, s; 5535 5536 this.element.find('.jstree-undetermined').removeClass('jstree-undetermined'); 5537 for (i = 0, j = p.length; i < j; i++) { 5538 s = this.get_node(p[i], true); 5539 if(s && s.length) { 5540 s.children('.jstree-anchor').children('.jstree-checkbox').addClass('jstree-undetermined'); 5541 } 5542 } 5543 }; 5544 this.redraw_node = function(obj, deep, is_callback, force_render) { 5545 obj = parent.redraw_node.apply(this, arguments); 5546 if(obj) { 5547 var i, j, tmp = null, icon = null; 5548 for(i = 0, j = obj.childNodes.length; i < j; i++) { 5549 if(obj.childNodes[i] && obj.childNodes[i].className && obj.childNodes[i].className.indexOf("jstree-anchor") !== -1) { 5550 tmp = obj.childNodes[i]; 5551 break; 5552 } 5553 } 5554 if(tmp) { 5555 if(!this.settings.checkbox.tie_selection && this._model.data[obj.id].state.checked) { tmp.className += ' jstree-checked'; } 5556 icon = _i.cloneNode(false); 5557 if(this._model.data[obj.id].state.checkbox_disabled) { icon.className += ' jstree-checkbox-disabled'; } 5558 tmp.insertBefore(icon, tmp.childNodes[0]); 5559 } 5560 } 5561 if(!is_callback && this.settings.checkbox.cascade.indexOf('undetermined') !== -1) { 5562 if(this._data.checkbox.uto) { clearTimeout(this._data.checkbox.uto); } 5563 this._data.checkbox.uto = setTimeout(this._undetermined.bind(this), 50); 5564 } 5565 return obj; 5566 }; 5567 /** 5568 * show the node checkbox icons 5569 * @name show_checkboxes() 5570 * @plugin checkbox 5571 */ 5572 this.show_checkboxes = function () { this._data.core.themes.checkboxes = true; this.get_container_ul().removeClass("jstree-no-checkboxes"); }; 5573 /** 5574 * hide the node checkbox icons 5575 * @name hide_checkboxes() 5576 * @plugin checkbox 5577 */ 5578 this.hide_checkboxes = function () { this._data.core.themes.checkboxes = false; this.get_container_ul().addClass("jstree-no-checkboxes"); }; 5579 /** 5580 * toggle the node icons 5581 * @name toggle_checkboxes() 5582 * @plugin checkbox 5583 */ 5584 this.toggle_checkboxes = function () { if(this._data.core.themes.checkboxes) { this.hide_checkboxes(); } else { this.show_checkboxes(); } }; 5585 /** 5586 * checks if a node is in an undetermined state 5587 * @name is_undetermined(obj) 5588 * @param {mixed} obj 5589 * @return {Boolean} 5590 */ 5591 this.is_undetermined = function (obj) { 5592 obj = this.get_node(obj); 5593 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; 5594 if(!obj || obj.state[ t ? 'selected' : 'checked' ] === true || s.indexOf('undetermined') === -1 || (s.indexOf('down') === -1 && s.indexOf('up') === -1)) { 5595 return false; 5596 } 5597 if(!obj.state.loaded && obj.original.state.undetermined === true) { 5598 return true; 5599 } 5600 for(i = 0, j = obj.children_d.length; i < j; i++) { 5601 if($.inArray(obj.children_d[i], d) !== -1 || (!m[obj.children_d[i]].state.loaded && m[obj.children_d[i]].original.state.undetermined)) { 5602 return true; 5603 } 5604 } 5605 return false; 5606 }; 5607 /** 5608 * disable a node's checkbox 5609 * @name disable_checkbox(obj) 5610 * @param {mixed} obj an array can be used too 5611 * @trigger disable_checkbox.jstree 5612 * @plugin checkbox 5613 */ 5614 this.disable_checkbox = function (obj) { 5615 var t1, t2, dom; 5616 if($.vakata.is_array(obj)) { 5617 obj = obj.slice(); 5618 for(t1 = 0, t2 = obj.length; t1 < t2; t1++) { 5619 this.disable_checkbox(obj[t1]); 5620 } 5621 return true; 5622 } 5623 obj = this.get_node(obj); 5624 if(!obj || obj.id === $.jstree.root) { 5625 return false; 5626 } 5627 dom = this.get_node(obj, true); 5628 if(!obj.state.checkbox_disabled) { 5629 obj.state.checkbox_disabled = true; 5630 if(dom && dom.length) { 5631 dom.children('.jstree-anchor').children('.jstree-checkbox').addClass('jstree-checkbox-disabled'); 5632 } 5633 /** 5634 * triggered when an node's checkbox is disabled 5635 * @event 5636 * @name disable_checkbox.jstree 5637 * @param {Object} node 5638 * @plugin checkbox 5639 */ 5640 this.trigger('disable_checkbox', { 'node' : obj }); 5641 } 5642 }; 5643 /** 5644 * enable a node's checkbox 5645 * @name enable_checkbox(obj) 5646 * @param {mixed} obj an array can be used too 5647 * @trigger enable_checkbox.jstree 5648 * @plugin checkbox 5649 */ 5650 this.enable_checkbox = function (obj) { 5651 var t1, t2, dom; 5652 if($.vakata.is_array(obj)) { 5653 obj = obj.slice(); 5654 for(t1 = 0, t2 = obj.length; t1 < t2; t1++) { 5655 this.enable_checkbox(obj[t1]); 5656 } 5657 return true; 5658 } 5659 obj = this.get_node(obj); 5660 if(!obj || obj.id === $.jstree.root) { 5661 return false; 5662 } 5663 dom = this.get_node(obj, true); 5664 if(obj.state.checkbox_disabled) { 5665 obj.state.checkbox_disabled = false; 5666 if(dom && dom.length) { 5667 dom.children('.jstree-anchor').children('.jstree-checkbox').removeClass('jstree-checkbox-disabled'); 5668 } 5669 /** 5670 * triggered when an node's checkbox is enabled 5671 * @event 5672 * @name enable_checkbox.jstree 5673 * @param {Object} node 5674 * @plugin checkbox 5675 */ 5676 this.trigger('enable_checkbox', { 'node' : obj }); 5677 } 5678 }; 5679 5680 this.activate_node = function (obj, e) { 5681 if($(e.target).hasClass('jstree-checkbox-disabled')) { 5682 return false; 5683 } 5684 if(this.settings.checkbox.tie_selection && (this.settings.checkbox.whole_node || $(e.target).hasClass('jstree-checkbox'))) { 5685 e.ctrlKey = true; 5686 } 5687 if(this.settings.checkbox.tie_selection || (!this.settings.checkbox.whole_node && !$(e.target).hasClass('jstree-checkbox'))) { 5688 return parent.activate_node.call(this, obj, e); 5689 } 5690 if(this.is_disabled(obj)) { 5691 return false; 5692 } 5693 if(this.is_checked(obj)) { 5694 this.uncheck_node(obj, e); 5695 } 5696 else { 5697 this.check_node(obj, e); 5698 } 5699 this.trigger('activate_node', { 'node' : this.get_node(obj) }); 5700 }; 5701 this.delete_node = function (obj) { 5702 if(this.settings.checkbox.tie_selection || $.vakata.is_array(obj)) { 5703 return parent.delete_node.call(this, obj); 5704 } 5705 var tmp, k, l, c = false; 5706 obj = this.get_node(obj); 5707 if(!obj || obj.id === $.jstree.root) { return false; } 5708 tmp = obj.children_d.concat([]); 5709 tmp.push(obj.id); 5710 for(k = 0, l = tmp.length; k < l; k++) { 5711 if(this._model.data[tmp[k]].state.checked) { 5712 c = true; 5713 break; 5714 } 5715 } 5716 if (c) { 5717 this._data.checkbox.selected = $.vakata.array_filter(this._data.checkbox.selected, function (v) { 5718 return $.inArray(v, tmp) === -1; 5719 }); 5720 } 5721 return parent.delete_node.call(this, obj); 5722 }; 5723 5724 /** 5725 * Cascades checked state to a node and all its descendants. This function does NOT affect hidden and disabled nodes (or their descendants). 5726 * However if these unaffected nodes are already selected their ids will be included in the returned array. 5727 * @private 5728 * @name _cascade_new_checked_state(id, checkedState) 5729 * @param {string} id the node ID 5730 * @param {bool} checkedState should the nodes be checked or not 5731 * @returns {Array} Array of all node id's (in this tree branch) that are checked. 5732 */ 5733 this._cascade_new_checked_state = function (id, checkedState) { 5734 var self = this; 5735 var t = this.settings.checkbox.tie_selection; 5736 var node = this._model.data[id]; 5737 var selectedNodeIds = []; 5738 var selectedChildrenIds = [], i, j, selectedChildIds; 5739 5740 if ( 5741 (this.settings.checkbox.cascade_to_disabled || !node.state.disabled) && 5742 (this.settings.checkbox.cascade_to_hidden || !node.state.hidden) 5743 ) { 5744 //First try and check/uncheck the children 5745 if (node.children) { 5746 for (i = 0, j = node.children.length; i < j; i++) { 5747 var childId = node.children[i]; 5748 selectedChildIds = self._cascade_new_checked_state(childId, checkedState); 5749 selectedNodeIds = selectedNodeIds.concat(selectedChildIds); 5750 if (selectedChildIds.indexOf(childId) > -1) { 5751 selectedChildrenIds.push(childId); 5752 } 5753 } 5754 } 5755 5756 var dom = self.get_node(node, true); 5757 5758 //A node's state is undetermined if some but not all of it's children are checked/selected . 5759 var undetermined = selectedChildrenIds.length > 0 && selectedChildrenIds.length < node.children.length; 5760 5761 if(node.original && node.original.state && node.original.state.undetermined) { 5762 node.original.state.undetermined = undetermined; 5763 } 5764 5765 //If a node is undetermined then remove selected class 5766 if (undetermined) { 5767 node.state[ t ? 'selected' : 'checked' ] = false; 5768 dom.children('.jstree-anchor').attr('aria-selected', false).removeClass(t ? 'jstree-clicked' : 'jstree-checked'); 5769 } 5770 //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), 5771 //check the node and style it correctly. 5772 else if (checkedState && selectedChildrenIds.length === node.children.length) { 5773 node.state[ t ? 'selected' : 'checked' ] = checkedState; 5774 selectedNodeIds.push(node.id); 5775 5776 dom.children('.jstree-anchor').attr('aria-selected', true).addClass(t ? 'jstree-clicked' : 'jstree-checked'); 5777 } 5778 else { 5779 node.state[ t ? 'selected' : 'checked' ] = false; 5780 dom.children('.jstree-anchor').attr('aria-selected', false).removeClass(t ? 'jstree-clicked' : 'jstree-checked'); 5781 } 5782 } 5783 else { 5784 selectedChildIds = this.get_checked_descendants(id); 5785 5786 if (node.state[ t ? 'selected' : 'checked' ]) { 5787 selectedChildIds.push(node.id); 5788 } 5789 5790 selectedNodeIds = selectedNodeIds.concat(selectedChildIds); 5791 } 5792 5793 return selectedNodeIds; 5794 }; 5795 5796 /** 5797 * Gets ids of nodes selected in branch (of tree) specified by id (does not include the node specified by id) 5798 * @name get_checked_descendants(obj) 5799 * @param {string} id the node ID 5800 * @return {Array} array of IDs 5801 * @plugin checkbox 5802 */ 5803 this.get_checked_descendants = function (id) { 5804 var self = this; 5805 var t = self.settings.checkbox.tie_selection; 5806 var node = self._model.data[id]; 5807 5808 return $.vakata.array_filter(node.children_d, function(_id) { 5809 return self._model.data[_id].state[ t ? 'selected' : 'checked' ]; 5810 }); 5811 }; 5812 5813 /** 5814 * check a node (only if tie_selection in checkbox settings is false, otherwise select_node will be called internally) 5815 * @name check_node(obj) 5816 * @param {mixed} obj an array can be used to check multiple nodes 5817 * @trigger check_node.jstree 5818 * @plugin checkbox 5819 */ 5820 this.check_node = function (obj, e) { 5821 if(this.settings.checkbox.tie_selection) { return this.select_node(obj, false, true, e); } 5822 var dom, t1, t2, th; 5823 if($.vakata.is_array(obj)) { 5824 obj = obj.slice(); 5825 for(t1 = 0, t2 = obj.length; t1 < t2; t1++) { 5826 this.check_node(obj[t1], e); 5827 } 5828 return true; 5829 } 5830 obj = this.get_node(obj); 5831 if(!obj || obj.id === $.jstree.root) { 5832 return false; 5833 } 5834 dom = this.get_node(obj, true); 5835 if(!obj.state.checked) { 5836 obj.state.checked = true; 5837 this._data.checkbox.selected.push(obj.id); 5838 if(dom && dom.length) { 5839 dom.children('.jstree-anchor').addClass('jstree-checked'); 5840 } 5841 /** 5842 * triggered when an node is checked (only if tie_selection in checkbox settings is false) 5843 * @event 5844 * @name check_node.jstree 5845 * @param {Object} node 5846 * @param {Array} selected the current selection 5847 * @param {Object} event the event (if any) that triggered this check_node 5848 * @plugin checkbox 5849 */ 5850 this.trigger('check_node', { 'node' : obj, 'selected' : this._data.checkbox.selected, 'event' : e }); 5851 } 5852 }; 5853 /** 5854 * uncheck a node (only if tie_selection in checkbox settings is false, otherwise deselect_node will be called internally) 5855 * @name uncheck_node(obj) 5856 * @param {mixed} obj an array can be used to uncheck multiple nodes 5857 * @trigger uncheck_node.jstree 5858 * @plugin checkbox 5859 */ 5860 this.uncheck_node = function (obj, e) { 5861 if(this.settings.checkbox.tie_selection) { return this.deselect_node(obj, false, e); } 5862 var t1, t2, dom; 5863 if($.vakata.is_array(obj)) { 5864 obj = obj.slice(); 5865 for(t1 = 0, t2 = obj.length; t1 < t2; t1++) { 5866 this.uncheck_node(obj[t1], e); 5867 } 5868 return true; 5869 } 5870 obj = this.get_node(obj); 5871 if(!obj || obj.id === $.jstree.root) { 5872 return false; 5873 } 5874 dom = this.get_node(obj, true); 5875 if(obj.state.checked) { 5876 obj.state.checked = false; 5877 this._data.checkbox.selected = $.vakata.array_remove_item(this._data.checkbox.selected, obj.id); 5878 if(dom.length) { 5879 dom.children('.jstree-anchor').removeClass('jstree-checked'); 5880 } 5881 /** 5882 * triggered when an node is unchecked (only if tie_selection in checkbox settings is false) 5883 * @event 5884 * @name uncheck_node.jstree 5885 * @param {Object} node 5886 * @param {Array} selected the current selection 5887 * @param {Object} event the event (if any) that triggered this uncheck_node 5888 * @plugin checkbox 5889 */ 5890 this.trigger('uncheck_node', { 'node' : obj, 'selected' : this._data.checkbox.selected, 'event' : e }); 5891 } 5892 }; 5893 5894 /** 5895 * checks all nodes in the tree (only if tie_selection in checkbox settings is false, otherwise select_all will be called internally) 5896 * @name check_all() 5897 * @trigger check_all.jstree, changed.jstree 5898 * @plugin checkbox 5899 */ 5900 this.check_all = function () { 5901 if(this.settings.checkbox.tie_selection) { return this.select_all(); } 5902 var tmp = this._data.checkbox.selected.concat([]), i, j; 5903 this._data.checkbox.selected = this._model.data[$.jstree.root].children_d.concat(); 5904 for(i = 0, j = this._data.checkbox.selected.length; i < j; i++) { 5905 if(this._model.data[this._data.checkbox.selected[i]]) { 5906 this._model.data[this._data.checkbox.selected[i]].state.checked = true; 5907 } 5908 } 5909 this.redraw(true); 5910 /** 5911 * triggered when all nodes are checked (only if tie_selection in checkbox settings is false) 5912 * @event 5913 * @name check_all.jstree 5914 * @param {Array} selected the current selection 5915 * @plugin checkbox 5916 */ 5917 this.trigger('check_all', { 'selected' : this._data.checkbox.selected }); 5918 }; 5919 /** 5920 * uncheck all checked nodes (only if tie_selection in checkbox settings is false, otherwise deselect_all will be called internally) 5921 * @name uncheck_all() 5922 * @trigger uncheck_all.jstree 5923 * @plugin checkbox 5924 */ 5925 this.uncheck_all = function () { 5926 if(this.settings.checkbox.tie_selection) { return this.deselect_all(); } 5927 var tmp = this._data.checkbox.selected.concat([]), i, j; 5928 for(i = 0, j = this._data.checkbox.selected.length; i < j; i++) { 5929 if(this._model.data[this._data.checkbox.selected[i]]) { 5930 this._model.data[this._data.checkbox.selected[i]].state.checked = false; 5931 } 5932 } 5933 this._data.checkbox.selected = []; 5934 this.element.find('.jstree-checked').removeClass('jstree-checked'); 5935 /** 5936 * triggered when all nodes are unchecked (only if tie_selection in checkbox settings is false) 5937 * @event 5938 * @name uncheck_all.jstree 5939 * @param {Object} node the previous selection 5940 * @param {Array} selected the current selection 5941 * @plugin checkbox 5942 */ 5943 this.trigger('uncheck_all', { 'selected' : this._data.checkbox.selected, 'node' : tmp }); 5944 }; 5945 /** 5946 * checks if a node is checked (if tie_selection is on in the settings this function will return the same as is_selected) 5947 * @name is_checked(obj) 5948 * @param {mixed} obj 5949 * @return {Boolean} 5950 * @plugin checkbox 5951 */ 5952 this.is_checked = function (obj) { 5953 if(this.settings.checkbox.tie_selection) { return this.is_selected(obj); } 5954 obj = this.get_node(obj); 5955 if(!obj || obj.id === $.jstree.root) { return false; } 5956 return obj.state.checked; 5957 }; 5958 /** 5959 * get an array of all checked nodes (if tie_selection is on in the settings this function will return the same as get_selected) 5960 * @name get_checked([full]) 5961 * @param {mixed} full if set to `true` the returned array will consist of the full node objects, otherwise - only IDs will be returned 5962 * @return {Array} 5963 * @plugin checkbox 5964 */ 5965 this.get_checked = function (full) { 5966 if(this.settings.checkbox.tie_selection) { return this.get_selected(full); } 5967 return full ? $.map(this._data.checkbox.selected, function (i) { return this.get_node(i); }.bind(this)) : this._data.checkbox.selected.slice(); 5968 }; 5969 /** 5970 * 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) 5971 * @name get_top_checked([full]) 5972 * @param {mixed} full if set to `true` the returned array will consist of the full node objects, otherwise - only IDs will be returned 5973 * @return {Array} 5974 * @plugin checkbox 5975 */ 5976 this.get_top_checked = function (full) { 5977 if(this.settings.checkbox.tie_selection) { return this.get_top_selected(full); } 5978 var tmp = this.get_checked(true), 5979 obj = {}, i, j, k, l; 5980 for(i = 0, j = tmp.length; i < j; i++) { 5981 obj[tmp[i].id] = tmp[i]; 5982 } 5983 for(i = 0, j = tmp.length; i < j; i++) { 5984 for(k = 0, l = tmp[i].children_d.length; k < l; k++) { 5985 if(obj[tmp[i].children_d[k]]) { 5986 delete obj[tmp[i].children_d[k]]; 5987 } 5988 } 5989 } 5990 tmp = []; 5991 for(i in obj) { 5992 if(obj.hasOwnProperty(i)) { 5993 tmp.push(i); 5994 } 5995 } 5996 return full ? $.map(tmp, function (i) { return this.get_node(i); }.bind(this)) : tmp; 5997 }; 5998 /** 5999 * 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) 6000 * @name get_bottom_checked([full]) 6001 * @param {mixed} full if set to `true` the returned array will consist of the full node objects, otherwise - only IDs will be returned 6002 * @return {Array} 6003 * @plugin checkbox 6004 */ 6005 this.get_bottom_checked = function (full) { 6006 if(this.settings.checkbox.tie_selection) { return this.get_bottom_selected(full); } 6007 var tmp = this.get_checked(true), 6008 obj = [], i, j; 6009 for(i = 0, j = tmp.length; i < j; i++) { 6010 if(!tmp[i].children.length) { 6011 obj.push(tmp[i].id); 6012 } 6013 } 6014 return full ? $.map(obj, function (i) { return this.get_node(i); }.bind(this)) : obj; 6015 }; 6016 this.load_node = function (obj, callback) { 6017 var k, l, i, j, c, tmp; 6018 if(!$.vakata.is_array(obj) && !this.settings.checkbox.tie_selection) { 6019 tmp = this.get_node(obj); 6020 if(tmp && tmp.state.loaded) { 6021 for(k = 0, l = tmp.children_d.length; k < l; k++) { 6022 if(this._model.data[tmp.children_d[k]].state.checked) { 6023 c = true; 6024 this._data.checkbox.selected = $.vakata.array_remove_item(this._data.checkbox.selected, tmp.children_d[k]); 6025 } 6026 } 6027 } 6028 } 6029 return parent.load_node.apply(this, arguments); 6030 }; 6031 this.get_state = function () { 6032 var state = parent.get_state.apply(this, arguments); 6033 if(this.settings.checkbox.tie_selection) { return state; } 6034 state.checkbox = this._data.checkbox.selected.slice(); 6035 return state; 6036 }; 6037 this.set_state = function (state, callback) { 6038 var res = parent.set_state.apply(this, arguments); 6039 if(res && state.checkbox) { 6040 if(!this.settings.checkbox.tie_selection) { 6041 this.uncheck_all(); 6042 var _this = this; 6043 $.each(state.checkbox, function (i, v) { 6044 _this.check_node(v); 6045 }); 6046 } 6047 delete state.checkbox; 6048 this.set_state(state, callback); 6049 return false; 6050 } 6051 return res; 6052 }; 6053 this.refresh = function (skip_loading, forget_state) { 6054 if(this.settings.checkbox.tie_selection) { 6055 this._data.checkbox.selected = []; 6056 } 6057 return parent.refresh.apply(this, arguments); 6058 }; 6059 }; 6060 6061 // include the checkbox plugin by default 6062 // $.jstree.defaults.plugins.push("checkbox"); 6063 6064 6065/** 6066 * ### Conditionalselect plugin 6067 * 6068 * This plugin allows defining a callback to allow or deny node selection by user input (activate node method). 6069 */ 6070 6071 /** 6072 * 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`. 6073 * @name $.jstree.defaults.checkbox.visible 6074 * @plugin checkbox 6075 */ 6076 $.jstree.defaults.conditionalselect = function () { return true; }; 6077 $.jstree.plugins.conditionalselect = function (options, parent) { 6078 // own function 6079 this.activate_node = function (obj, e) { 6080 if(this.settings.conditionalselect.call(this, this.get_node(obj), e)) { 6081 return parent.activate_node.call(this, obj, e); 6082 } 6083 }; 6084 }; 6085 6086 6087/** 6088 * ### Contextmenu plugin 6089 * 6090 * Shows a context menu when a node is right-clicked. 6091 */ 6092 6093 /** 6094 * stores all defaults for the contextmenu plugin 6095 * @name $.jstree.defaults.contextmenu 6096 * @plugin contextmenu 6097 */ 6098 $.jstree.defaults.contextmenu = { 6099 /** 6100 * a boolean indicating if the node should be selected when the context menu is invoked on it. Defaults to `true`. 6101 * @name $.jstree.defaults.contextmenu.select_node 6102 * @plugin contextmenu 6103 */ 6104 select_node : true, 6105 /** 6106 * a boolean indicating if the menu should be shown aligned with the node. Defaults to `true`, otherwise the mouse coordinates are used. 6107 * @name $.jstree.defaults.contextmenu.show_at_node 6108 * @plugin contextmenu 6109 */ 6110 show_at_node : true, 6111 /** 6112 * 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). 6113 * 6114 * 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. 6115 * 6116 * * `separator_before` - a boolean indicating if there should be a separator before this item 6117 * * `separator_after` - a boolean indicating if there should be a separator after this item 6118 * * `_disabled` - a boolean indicating if this action should be disabled 6119 * * `label` - a string - the name of the action (could be a function returning a string) 6120 * * `title` - a string - an optional tooltip for the item 6121 * * `action` - a function to be executed if this item is chosen, the function will receive 6122 * * `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 6123 * * `shortcut` - keyCode which will trigger the action if the menu is open (for example `113` for rename, which equals F2) 6124 * * `shortcut_label` - shortcut label (like for example `F2` for rename) 6125 * * `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 6126 * 6127 * @name $.jstree.defaults.contextmenu.items 6128 * @plugin contextmenu 6129 */ 6130 items : function (o, cb) { // Could be an object directly 6131 return { 6132 "create" : { 6133 "separator_before" : false, 6134 "separator_after" : true, 6135 "_disabled" : false, //(this.check("create_node", data.reference, {}, "last")), 6136 "label" : "Create", 6137 "action" : function (data) { 6138 var inst = $.jstree.reference(data.reference), 6139 obj = inst.get_node(data.reference); 6140 inst.create_node(obj, {}, "last", function (new_node) { 6141 try { 6142 inst.edit(new_node); 6143 } catch (ex) { 6144 setTimeout(function () { inst.edit(new_node); },0); 6145 } 6146 }); 6147 } 6148 }, 6149 "rename" : { 6150 "separator_before" : false, 6151 "separator_after" : false, 6152 "_disabled" : false, //(this.check("rename_node", data.reference, this.get_parent(data.reference), "")), 6153 "label" : "Rename", 6154 /*! 6155 "shortcut" : 113, 6156 "shortcut_label" : 'F2', 6157 "icon" : "glyphicon glyphicon-leaf", 6158 */ 6159 "action" : function (data) { 6160 var inst = $.jstree.reference(data.reference), 6161 obj = inst.get_node(data.reference); 6162 inst.edit(obj); 6163 } 6164 }, 6165 "remove" : { 6166 "separator_before" : false, 6167 "icon" : false, 6168 "separator_after" : false, 6169 "_disabled" : false, //(this.check("delete_node", data.reference, this.get_parent(data.reference), "")), 6170 "label" : "Delete", 6171 "action" : function (data) { 6172 var inst = $.jstree.reference(data.reference), 6173 obj = inst.get_node(data.reference); 6174 if(inst.is_selected(obj)) { 6175 inst.delete_node(inst.get_selected()); 6176 } 6177 else { 6178 inst.delete_node(obj); 6179 } 6180 } 6181 }, 6182 "ccp" : { 6183 "separator_before" : true, 6184 "icon" : false, 6185 "separator_after" : false, 6186 "label" : "Edit", 6187 "action" : false, 6188 "submenu" : { 6189 "cut" : { 6190 "separator_before" : false, 6191 "separator_after" : false, 6192 "label" : "Cut", 6193 "action" : function (data) { 6194 var inst = $.jstree.reference(data.reference), 6195 obj = inst.get_node(data.reference); 6196 if(inst.is_selected(obj)) { 6197 inst.cut(inst.get_top_selected()); 6198 } 6199 else { 6200 inst.cut(obj); 6201 } 6202 } 6203 }, 6204 "copy" : { 6205 "separator_before" : false, 6206 "icon" : false, 6207 "separator_after" : false, 6208 "label" : "Copy", 6209 "action" : function (data) { 6210 var inst = $.jstree.reference(data.reference), 6211 obj = inst.get_node(data.reference); 6212 if(inst.is_selected(obj)) { 6213 inst.copy(inst.get_top_selected()); 6214 } 6215 else { 6216 inst.copy(obj); 6217 } 6218 } 6219 }, 6220 "paste" : { 6221 "separator_before" : false, 6222 "icon" : false, 6223 "_disabled" : function (data) { 6224 return !$.jstree.reference(data.reference).can_paste(); 6225 }, 6226 "separator_after" : false, 6227 "label" : "Paste", 6228 "action" : function (data) { 6229 var inst = $.jstree.reference(data.reference), 6230 obj = inst.get_node(data.reference); 6231 inst.paste(obj); 6232 } 6233 } 6234 } 6235 } 6236 }; 6237 } 6238 }; 6239 6240 $.jstree.plugins.contextmenu = function (options, parent) { 6241 this.bind = function () { 6242 parent.bind.call(this); 6243 6244 var last_ts = 0, cto = null, ex, ey; 6245 this.element 6246 .on("init.jstree loading.jstree ready.jstree", function () { 6247 this.get_container_ul().addClass('jstree-contextmenu'); 6248 }.bind(this)) 6249 .on("contextmenu.jstree", ".jstree-anchor", function (e, data) { 6250 if (e.target.tagName.toLowerCase() === 'input') { 6251 return; 6252 } 6253 e.preventDefault(); 6254 last_ts = e.ctrlKey ? +new Date() : 0; 6255 if(data || cto) { 6256 last_ts = (+new Date()) + 10000; 6257 } 6258 if(cto) { 6259 clearTimeout(cto); 6260 } 6261 if(!this.is_loading(e.currentTarget)) { 6262 this.show_contextmenu(e.currentTarget, e.pageX, e.pageY, e); 6263 } 6264 }.bind(this)) 6265 .on("click.jstree", ".jstree-anchor", function (e) { 6266 if(this._data.contextmenu.visible && (!last_ts || (+new Date()) - last_ts > 250)) { // work around safari & macOS ctrl+click 6267 $.vakata.context.hide(); 6268 } 6269 last_ts = 0; 6270 }.bind(this)) 6271 .on("touchstart.jstree", ".jstree-anchor", function (e) { 6272 if(!e.originalEvent || !e.originalEvent.changedTouches || !e.originalEvent.changedTouches[0]) { 6273 return; 6274 } 6275 ex = e.originalEvent.changedTouches[0].clientX; 6276 ey = e.originalEvent.changedTouches[0].clientY; 6277 cto = setTimeout(function () { 6278 $(e.currentTarget).trigger('contextmenu', true); 6279 }, 750); 6280 }) 6281 .on('touchmove.vakata.jstree', function (e) { 6282 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)) { 6283 clearTimeout(cto); 6284 $.vakata.context.hide(); 6285 } 6286 }) 6287 .on('touchend.vakata.jstree', function (e) { 6288 if(cto) { 6289 clearTimeout(cto); 6290 } 6291 }); 6292 6293 /*! 6294 if(!('oncontextmenu' in document.body) && ('ontouchstart' in document.body)) { 6295 var el = null, tm = null; 6296 this.element 6297 .on("touchstart", ".jstree-anchor", function (e) { 6298 el = e.currentTarget; 6299 tm = +new Date(); 6300 $(document).one("touchend", function (e) { 6301 e.target = document.elementFromPoint(e.originalEvent.targetTouches[0].pageX - window.pageXOffset, e.originalEvent.targetTouches[0].pageY - window.pageYOffset); 6302 e.currentTarget = e.target; 6303 tm = ((+(new Date())) - tm); 6304 if(e.target === el && tm > 600 && tm < 1000) { 6305 e.preventDefault(); 6306 $(el).trigger('contextmenu', e); 6307 } 6308 el = null; 6309 tm = null; 6310 }); 6311 }); 6312 } 6313 */ 6314 $(document).on("context_hide.vakata.jstree", function (e, data) { 6315 this._data.contextmenu.visible = false; 6316 $(data.reference).removeClass('jstree-context'); 6317 }.bind(this)); 6318 }; 6319 this.teardown = function () { 6320 if(this._data.contextmenu.visible) { 6321 $.vakata.context.hide(); 6322 } 6323 $(document).off("context_hide.vakata.jstree"); 6324 parent.teardown.call(this); 6325 }; 6326 6327 /** 6328 * prepare and show the context menu for a node 6329 * @name show_contextmenu(obj [, x, y]) 6330 * @param {mixed} obj the node 6331 * @param {Number} x the x-coordinate relative to the document to show the menu at 6332 * @param {Number} y the y-coordinate relative to the document to show the menu at 6333 * @param {Object} e the event if available that triggered the contextmenu 6334 * @plugin contextmenu 6335 * @trigger show_contextmenu.jstree 6336 */ 6337 this.show_contextmenu = function (obj, x, y, e) { 6338 obj = this.get_node(obj); 6339 if(!obj || obj.id === $.jstree.root) { return false; } 6340 var s = this.settings.contextmenu, 6341 d = this.get_node(obj, true), 6342 a = d.children(".jstree-anchor"), 6343 o = false, 6344 i = false; 6345 if(s.show_at_node || x === undefined || y === undefined) { 6346 o = a.offset(); 6347 x = o.left; 6348 y = o.top + this._data.core.li_height; 6349 } 6350 if(this.settings.contextmenu.select_node && !this.is_selected(obj)) { 6351 this.activate_node(obj, e); 6352 } 6353 6354 i = s.items; 6355 if($.vakata.is_function(i)) { 6356 i = i.call(this, obj, function (i) { 6357 this._show_contextmenu(obj, x, y, i); 6358 }.bind(this)); 6359 } 6360 if($.isPlainObject(i)) { 6361 this._show_contextmenu(obj, x, y, i); 6362 } 6363 }; 6364 /** 6365 * show the prepared context menu for a node 6366 * @name _show_contextmenu(obj, x, y, i) 6367 * @param {mixed} obj the node 6368 * @param {Number} x the x-coordinate relative to the document to show the menu at 6369 * @param {Number} y the y-coordinate relative to the document to show the menu at 6370 * @param {Number} i the object of items to show 6371 * @plugin contextmenu 6372 * @trigger show_contextmenu.jstree 6373 * @private 6374 */ 6375 this._show_contextmenu = function (obj, x, y, i) { 6376 var d = this.get_node(obj, true), 6377 a = d.children(".jstree-anchor"); 6378 $(document).one("context_show.vakata.jstree", function (e, data) { 6379 var cls = 'jstree-contextmenu jstree-' + this.get_theme() + '-contextmenu'; 6380 $(data.element).addClass(cls); 6381 a.addClass('jstree-context'); 6382 }.bind(this)); 6383 this._data.contextmenu.visible = true; 6384 $.vakata.context.show(a, { 'x' : x, 'y' : y }, i); 6385 /** 6386 * triggered when the contextmenu is shown for a node 6387 * @event 6388 * @name show_contextmenu.jstree 6389 * @param {Object} node the node 6390 * @param {Number} x the x-coordinate of the menu relative to the document 6391 * @param {Number} y the y-coordinate of the menu relative to the document 6392 * @plugin contextmenu 6393 */ 6394 this.trigger('show_contextmenu', { "node" : obj, "x" : x, "y" : y }); 6395 }; 6396 }; 6397 6398 // contextmenu helper 6399 (function ($) { 6400 var right_to_left = false, 6401 vakata_context = { 6402 element : false, 6403 reference : false, 6404 position_x : 0, 6405 position_y : 0, 6406 items : [], 6407 html : "", 6408 is_visible : false 6409 }; 6410 6411 $.vakata.context = { 6412 settings : { 6413 hide_onmouseleave : 0, 6414 icons : true 6415 }, 6416 _trigger : function (event_name) { 6417 $(document).triggerHandler("context_" + event_name + ".vakata", { 6418 "reference" : vakata_context.reference, 6419 "element" : vakata_context.element, 6420 "position" : { 6421 "x" : vakata_context.position_x, 6422 "y" : vakata_context.position_y 6423 } 6424 }); 6425 }, 6426 _execute : function (i) { 6427 i = vakata_context.items[i]; 6428 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, { 6429 "item" : i, 6430 "reference" : vakata_context.reference, 6431 "element" : vakata_context.element, 6432 "position" : { 6433 "x" : vakata_context.position_x, 6434 "y" : vakata_context.position_y 6435 } 6436 }) : false; 6437 }, 6438 _parse : function (o, is_callback) { 6439 if(!o) { return false; } 6440 if(!is_callback) { 6441 vakata_context.html = ""; 6442 vakata_context.items = []; 6443 } 6444 var str = "", 6445 sep = false, 6446 tmp; 6447 6448 if(is_callback) { str += "<"+"ul>"; } 6449 $.each(o, function (i, val) { 6450 if(!val) { return true; } 6451 vakata_context.items.push(val); 6452 if(!sep && val.separator_before) { 6453 str += "<"+"li class='vakata-context-separator'><"+"a href='#' " + ($.vakata.context.settings.icons ? '' : 'class="vakata-context-no-icons"') + "> <"+"/a><"+"/li>"; 6454 } 6455 sep = false; 6456 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+"' ":'')+">"; 6457 str += "<"+"a href='#' rel='" + (vakata_context.items.length - 1) + "' " + (val.title ? "title='" + val.title + "'" : "") + ">"; 6458 if($.vakata.context.settings.icons) { 6459 str += "<"+"i "; 6460 if(val.icon) { 6461 if(val.icon.indexOf("/") !== -1 || val.icon.indexOf(".") !== -1) { str += " style='background:url(\"" + val.icon + "\") center center no-repeat' "; } 6462 else { str += " class='" + val.icon + "' "; } 6463 } 6464 str += "><"+"/i><"+"span class='vakata-contextmenu-sep'> <"+"/span>"; 6465 } 6466 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>"; 6467 if(val.submenu) { 6468 tmp = $.vakata.context._parse(val.submenu, true); 6469 if(tmp) { str += tmp; } 6470 } 6471 str += "<"+"/li>"; 6472 if(val.separator_after) { 6473 str += "<"+"li class='vakata-context-separator'><"+"a href='#' " + ($.vakata.context.settings.icons ? '' : 'class="vakata-context-no-icons"') + "> <"+"/a><"+"/li>"; 6474 sep = true; 6475 } 6476 }); 6477 str = str.replace(/<li class\='vakata-context-separator'\><\/li\>$/,""); 6478 if(is_callback) { str += "</ul>"; } 6479 /** 6480 * triggered on the document when the contextmenu is parsed (HTML is built) 6481 * @event 6482 * @plugin contextmenu 6483 * @name context_parse.vakata 6484 * @param {jQuery} reference the element that was right clicked 6485 * @param {jQuery} element the DOM element of the menu itself 6486 * @param {Object} position the x & y coordinates of the menu 6487 */ 6488 if(!is_callback) { vakata_context.html = str; $.vakata.context._trigger("parse"); } 6489 return str.length > 10 ? str : false; 6490 }, 6491 _show_submenu : function (o) { 6492 o = $(o); 6493 if(!o.length || !o.children("ul").length) { return; } 6494 var e = o.children("ul"), 6495 xl = o.offset().left, 6496 x = xl + o.outerWidth(), 6497 y = o.offset().top, 6498 w = e.width(), 6499 h = e.height(), 6500 dw = $(window).width() + $(window).scrollLeft(), 6501 dh = $(window).height() + $(window).scrollTop(); 6502 // може да Ñе ÑпеÑÑи е една пÑовеÑка - дали нÑма нÑкой Ð¾Ñ ÐºÐ»Ð°ÑовеÑе веÑе нагоÑе 6503 if(right_to_left) { 6504 o[x - (w + 10 + o.outerWidth()) < 0 ? "addClass" : "removeClass"]("vakata-context-left"); 6505 } 6506 else { 6507 o[x + w > dw && xl > dw - x ? "addClass" : "removeClass"]("vakata-context-right"); 6508 } 6509 if(y + h + 10 > dh) { 6510 e.css("bottom","-1px"); 6511 } 6512 6513 //if does not fit - stick it to the side 6514 if (o.hasClass('vakata-context-right')) { 6515 if (xl < w) { 6516 e.css("margin-right", xl - w); 6517 } 6518 } else { 6519 if (dw - x < w) { 6520 e.css("margin-left", dw - x - w); 6521 } 6522 } 6523 6524 e.show(); 6525 }, 6526 show : function (reference, position, data) { 6527 var o, e, x, y, w, h, dw, dh, cond = true; 6528 if(vakata_context.element && vakata_context.element.length) { 6529 vakata_context.element.width(''); 6530 } 6531 switch(cond) { 6532 case (!position && !reference): 6533 return false; 6534 case (!!position && !!reference): 6535 vakata_context.reference = reference; 6536 vakata_context.position_x = position.x; 6537 vakata_context.position_y = position.y; 6538 break; 6539 case (!position && !!reference): 6540 vakata_context.reference = reference; 6541 o = reference.offset(); 6542 vakata_context.position_x = o.left + reference.outerHeight(); 6543 vakata_context.position_y = o.top; 6544 break; 6545 case (!!position && !reference): 6546 vakata_context.position_x = position.x; 6547 vakata_context.position_y = position.y; 6548 break; 6549 } 6550 if(!!reference && !data && $(reference).data('vakata_contextmenu')) { 6551 data = $(reference).data('vakata_contextmenu'); 6552 } 6553 if($.vakata.context._parse(data)) { 6554 vakata_context.element.html(vakata_context.html); 6555 } 6556 if(vakata_context.items.length) { 6557 vakata_context.element.appendTo(document.body); 6558 e = vakata_context.element; 6559 x = vakata_context.position_x; 6560 y = vakata_context.position_y; 6561 w = e.width(); 6562 h = e.height(); 6563 dw = $(window).width() + $(window).scrollLeft(); 6564 dh = $(window).height() + $(window).scrollTop(); 6565 if(right_to_left) { 6566 x -= (e.outerWidth() - $(reference).outerWidth()); 6567 if(x < $(window).scrollLeft() + 20) { 6568 x = $(window).scrollLeft() + 20; 6569 } 6570 } 6571 if(x + w + 20 > dw) { 6572 x = dw - (w + 20); 6573 } 6574 if(y + h + 20 > dh) { 6575 y = dh - (h + 20); 6576 } 6577 6578 vakata_context.element 6579 .css({ "left" : x, "top" : y }) 6580 .show() 6581 .find('a').first().trigger('focus').parent().addClass("vakata-context-hover"); 6582 vakata_context.is_visible = true; 6583 /** 6584 * triggered on the document when the contextmenu is shown 6585 * @event 6586 * @plugin contextmenu 6587 * @name context_show.vakata 6588 * @param {jQuery} reference the element that was right clicked 6589 * @param {jQuery} element the DOM element of the menu itself 6590 * @param {Object} position the x & y coordinates of the menu 6591 */ 6592 $.vakata.context._trigger("show"); 6593 } 6594 }, 6595 hide : function () { 6596 if(vakata_context.is_visible) { 6597 vakata_context.element.hide().find("ul").hide().end().find(':focus').trigger('blur').end().detach(); 6598 vakata_context.is_visible = false; 6599 /** 6600 * triggered on the document when the contextmenu is hidden 6601 * @event 6602 * @plugin contextmenu 6603 * @name context_hide.vakata 6604 * @param {jQuery} reference the element that was right clicked 6605 * @param {jQuery} element the DOM element of the menu itself 6606 * @param {Object} position the x & y coordinates of the menu 6607 */ 6608 $.vakata.context._trigger("hide"); 6609 } 6610 } 6611 }; 6612 $(function () { 6613 right_to_left = $(document.body).css("direction") === "rtl"; 6614 var to = false; 6615 6616 vakata_context.element = $("<ul class='vakata-context'></ul>"); 6617 vakata_context.element 6618 .on("mouseenter", "li", function (e) { 6619 e.stopImmediatePropagation(); 6620 6621 if($.contains(this, e.relatedTarget)) { 6622 // пÑÐµÐ¼Ð°Ñ Ð½Ð°Ñо заÑади delegate mouseleave по-Ð´Ð¾Ð»Ñ 6623 // $(this).find(".vakata-context-hover").removeClass("vakata-context-hover"); 6624 return; 6625 } 6626 6627 if(to) { clearTimeout(to); } 6628 vakata_context.element.find(".vakata-context-hover").removeClass("vakata-context-hover").end(); 6629 6630 $(this) 6631 .siblings().find("ul").hide().end().end() 6632 .parentsUntil(".vakata-context", "li").addBack().addClass("vakata-context-hover"); 6633 $.vakata.context._show_submenu(this); 6634 }) 6635 // ÑеÑÑово - дали не наÑоваÑва? 6636 .on("mouseleave", "li", function (e) { 6637 if($.contains(this, e.relatedTarget)) { return; } 6638 $(this).find(".vakata-context-hover").addBack().removeClass("vakata-context-hover"); 6639 }) 6640 .on("mouseleave", function (e) { 6641 $(this).find(".vakata-context-hover").removeClass("vakata-context-hover"); 6642 if($.vakata.context.settings.hide_onmouseleave) { 6643 to = setTimeout( 6644 (function (t) { 6645 return function () { $.vakata.context.hide(); }; 6646 }(this)), $.vakata.context.settings.hide_onmouseleave); 6647 } 6648 }) 6649 .on("click", "a", function (e) { 6650 e.preventDefault(); 6651 //}) 6652 //.on("mouseup", "a", function (e) { 6653 if(!$(this).trigger('blur').parent().hasClass("vakata-context-disabled") && $.vakata.context._execute($(this).attr("rel")) !== false) { 6654 $.vakata.context.hide(); 6655 } 6656 }) 6657 .on('keydown', 'a', function (e) { 6658 var o = null; 6659 switch(e.which) { 6660 case 13: 6661 case 32: 6662 e.type = "click"; 6663 e.preventDefault(); 6664 $(e.currentTarget).trigger(e); 6665 break; 6666 case 37: 6667 if(vakata_context.is_visible) { 6668 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'); 6669 e.stopImmediatePropagation(); 6670 e.preventDefault(); 6671 } 6672 break; 6673 case 38: 6674 if(vakata_context.is_visible) { 6675 o = vakata_context.element.find("ul:visible").addBack().last().children(".vakata-context-hover").removeClass("vakata-context-hover").prevAll("li:not(.vakata-context-separator)").first(); 6676 if(!o.length) { o = vakata_context.element.find("ul:visible").addBack().last().children("li:not(.vakata-context-separator)").last(); } 6677 o.addClass("vakata-context-hover").children('a').trigger('focus'); 6678 e.stopImmediatePropagation(); 6679 e.preventDefault(); 6680 } 6681 break; 6682 case 39: 6683 if(vakata_context.is_visible) { 6684 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'); 6685 e.stopImmediatePropagation(); 6686 e.preventDefault(); 6687 } 6688 break; 6689 case 40: 6690 if(vakata_context.is_visible) { 6691 o = vakata_context.element.find("ul:visible").addBack().last().children(".vakata-context-hover").removeClass("vakata-context-hover").nextAll("li:not(.vakata-context-separator)").first(); 6692 if(!o.length) { o = vakata_context.element.find("ul:visible").addBack().last().children("li:not(.vakata-context-separator)").first(); } 6693 o.addClass("vakata-context-hover").children('a').trigger('focus'); 6694 e.stopImmediatePropagation(); 6695 e.preventDefault(); 6696 } 6697 break; 6698 case 27: 6699 $.vakata.context.hide(); 6700 e.preventDefault(); 6701 break; 6702 default: 6703 //console.log(e.which); 6704 break; 6705 } 6706 }) 6707 .on('keydown', function (e) { 6708 e.preventDefault(); 6709 var a = vakata_context.element.find('.vakata-contextmenu-shortcut-' + e.which).parent(); 6710 if(a.parent().not('.vakata-context-disabled')) { 6711 a.trigger('click'); 6712 } 6713 }); 6714 6715 $(document) 6716 .on("mousedown.vakata.jstree", function (e) { 6717 if(vakata_context.is_visible && vakata_context.element[0] !== e.target && !$.contains(vakata_context.element[0], e.target)) { 6718 $.vakata.context.hide(); 6719 } 6720 }) 6721 .on("context_show.vakata.jstree", function (e, data) { 6722 vakata_context.element.find("li:has(ul)").children("a").addClass("vakata-context-parent"); 6723 if(right_to_left) { 6724 vakata_context.element.addClass("vakata-context-rtl").css("direction", "rtl"); 6725 } 6726 // also apply a RTL class? 6727 vakata_context.element.find("ul").hide().end(); 6728 }); 6729 }); 6730 }($)); 6731 // $.jstree.defaults.plugins.push("contextmenu"); 6732 6733 6734/** 6735 * ### Drag'n'drop plugin 6736 * 6737 * Enables dragging and dropping of nodes in the tree, resulting in a move or copy operations. 6738 */ 6739 6740 /** 6741 * stores all defaults for the drag'n'drop plugin 6742 * @name $.jstree.defaults.dnd 6743 * @plugin dnd 6744 */ 6745 $.jstree.defaults.dnd = { 6746 /** 6747 * a boolean indicating if a copy should be possible while dragging (by pressint the meta key or Ctrl). Defaults to `true`. 6748 * @name $.jstree.defaults.dnd.copy 6749 * @plugin dnd 6750 */ 6751 copy : true, 6752 /** 6753 * a number indicating how long a node should remain hovered while dragging to be opened. Defaults to `500`. 6754 * @name $.jstree.defaults.dnd.open_timeout 6755 * @plugin dnd 6756 */ 6757 open_timeout : 500, 6758 /** 6759 * 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 6760 * @name $.jstree.defaults.dnd.is_draggable 6761 * @plugin dnd 6762 */ 6763 is_draggable : true, 6764 /** 6765 * 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` 6766 * @name $.jstree.defaults.dnd.check_while_dragging 6767 * @plugin dnd 6768 */ 6769 check_while_dragging : true, 6770 /** 6771 * a boolean indicating if nodes from this tree should only be copied with dnd (as opposed to moved), default is `false` 6772 * @name $.jstree.defaults.dnd.always_copy 6773 * @plugin dnd 6774 */ 6775 always_copy : false, 6776 /** 6777 * 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` 6778 * @name $.jstree.defaults.dnd.inside_pos 6779 * @plugin dnd 6780 */ 6781 inside_pos : 0, 6782 /** 6783 * 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 6784 * @name $.jstree.defaults.dnd.drag_selection 6785 * @plugin dnd 6786 */ 6787 drag_selection : true, 6788 /** 6789 * 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. 6790 * @name $.jstree.defaults.dnd.touch 6791 * @plugin dnd 6792 */ 6793 touch : true, 6794 /** 6795 * 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. 6796 * @name $.jstree.defaults.dnd.large_drop_target 6797 * @plugin dnd 6798 */ 6799 large_drop_target : false, 6800 /** 6801 * 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". 6802 * @name $.jstree.defaults.dnd.large_drag_target 6803 * @plugin dnd 6804 */ 6805 large_drag_target : false, 6806 /** 6807 * controls whether use HTML5 dnd api instead of classical. That will allow better integration of dnd events with other HTML5 controls. 6808 * @reference http://caniuse.com/#feat=dragndrop 6809 * @name $.jstree.defaults.dnd.use_html5 6810 * @plugin dnd 6811 */ 6812 use_html5: false, 6813 /** 6814 * controls whether items can be dropped anywhere on the tree. 6815 * @name $.jstree.defaults.dnd.blank_space_drop 6816 * @plugin dnd 6817 */ 6818 blank_space_drop: false 6819 }; 6820 var drg, elm; 6821 // TODO: now check works by checking for each node individually, how about max_children, unique, etc? 6822 $.jstree.plugins.dnd = function (options, parent) { 6823 this.init = function (el, options) { 6824 parent.init.call(this, el, options); 6825 this.settings.dnd.use_html5 = this.settings.dnd.use_html5 && ('draggable' in document.createElement('span')); 6826 }; 6827 this.bind = function () { 6828 parent.bind.call(this); 6829 6830 this.element 6831 .on(this.settings.dnd.use_html5 ? 'dragstart.jstree' : 'mousedown.jstree touchstart.jstree', this.settings.dnd.large_drag_target ? '.jstree-node' : '.jstree-anchor', function (e) { 6832 if(this.settings.dnd.large_drag_target && $(e.target).closest('.jstree-node')[0] !== e.currentTarget) { 6833 return true; 6834 } 6835 if(e.type === "touchstart" && (!this.settings.dnd.touch || (this.settings.dnd.touch === 'selected' && !$(e.currentTarget).closest('.jstree-node').children('.jstree-anchor').hasClass('jstree-clicked')))) { 6836 return true; 6837 } 6838 var obj = this.get_node(e.target), 6839 mlt = this.is_selected(obj) && this.settings.dnd.drag_selection ? this.get_top_selected().length : 1, 6840 txt = (mlt > 1 ? mlt + ' ' + this.get_string('nodes') : this.get_text(e.currentTarget)); 6841 if(this.settings.core.force_text) { 6842 txt = $.vakata.html.escape(txt); 6843 } 6844 if(obj && (obj.id || obj.id === 0) && obj.id !== $.jstree.root && (e.which === 1 || e.type === "touchstart" || e.type === "dragstart") && 6845 (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))) 6846 ) { 6847 drg = { 'jstree' : true, 'origin' : this, 'obj' : this.get_node(obj,true), 'nodes' : mlt > 1 ? this.get_top_selected() : [obj.id] }; 6848 elm = e.currentTarget; 6849 if (this.settings.dnd.use_html5) { 6850 $.vakata.dnd._trigger('start', e, { 'helper': $(), 'element': elm, 'data': drg }); 6851 } else { 6852 this.element.trigger('mousedown.jstree'); 6853 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>'); 6854 } 6855 } 6856 }.bind(this)); 6857 if (this.settings.dnd.use_html5) { 6858 this.element 6859 .on('dragover.jstree', function (e) { 6860 e.preventDefault(); 6861 $.vakata.dnd._trigger('move', e, { 'helper': $(), 'element': elm, 'data': drg }); 6862 return false; 6863 }) 6864 //.on('dragenter.jstree', this.settings.dnd.large_drop_target ? '.jstree-node' : '.jstree-anchor', $.proxy(function (e) { 6865 // e.preventDefault(); 6866 // $.vakata.dnd._trigger('move', e, { 'helper': $(), 'element': elm, 'data': drg }); 6867 // return false; 6868 // }, this)) 6869 .on('drop.jstree', function (e) { 6870 e.preventDefault(); 6871 $.vakata.dnd._trigger('stop', e, { 'helper': $(), 'element': elm, 'data': drg }); 6872 return false; 6873 }.bind(this)); 6874 } 6875 }; 6876 this.redraw_node = function(obj, deep, callback, force_render) { 6877 obj = parent.redraw_node.apply(this, arguments); 6878 if (obj && this.settings.dnd.use_html5) { 6879 if (this.settings.dnd.large_drag_target) { 6880 obj.setAttribute('draggable', true); 6881 } else { 6882 var i, j, tmp = null; 6883 for(i = 0, j = obj.childNodes.length; i < j; i++) { 6884 if(obj.childNodes[i] && obj.childNodes[i].className && obj.childNodes[i].className.indexOf("jstree-anchor") !== -1) { 6885 tmp = obj.childNodes[i]; 6886 break; 6887 } 6888 } 6889 if(tmp) { 6890 tmp.setAttribute('draggable', true); 6891 } 6892 } 6893 } 6894 return obj; 6895 }; 6896 }; 6897 6898 $(function() { 6899 // bind only once for all instances 6900 var lastmv = false, 6901 laster = false, 6902 lastev = false, 6903 opento = false, 6904 marker = $('<div id="jstree-marker"> </div>').hide(); //.appendTo('body'); 6905 6906 $(document) 6907 .on('dragover.vakata.jstree', function (e) { 6908 if (elm) { 6909 $.vakata.dnd._trigger('move', e, { 'helper': $(), 'element': elm, 'data': drg }); 6910 } 6911 }) 6912 .on('drop.vakata.jstree', function (e) { 6913 if (elm) { 6914 $.vakata.dnd._trigger('stop', e, { 'helper': $(), 'element': elm, 'data': drg }); 6915 elm = null; 6916 drg = null; 6917 } 6918 }) 6919 .on('dnd_start.vakata.jstree', function (e, data) { 6920 lastmv = false; 6921 lastev = false; 6922 if(!data || !data.data || !data.data.jstree) { return; } 6923 marker.appendTo(document.body); //.show(); 6924 }) 6925 .on('dnd_move.vakata.jstree', function (e, data) { 6926 var isDifferentNode = data.event.target !== lastev.target; 6927 if(opento) { 6928 if (!data.event || data.event.type !== 'dragover' || isDifferentNode) { 6929 clearTimeout(opento); 6930 } 6931 } 6932 if(!data || !data.data || !data.data.jstree) { return; } 6933 6934 // if we are hovering the marker image do nothing (can happen on "inside" drags) 6935 if(data.event.target.id && data.event.target.id === 'jstree-marker') { 6936 return; 6937 } 6938 lastev = data.event; 6939 6940 var ins = $.jstree.reference(data.event.target), 6941 ref = false, 6942 off = false, 6943 rel = false, 6944 tmp, l, t, h, p, i, o, ok, t1, t2, op, ps, pr, ip, tm, is_copy, pn, c; 6945 // if we are over an instance 6946 if(ins && ins._data && ins._data.dnd) { 6947 marker.attr('class', 'jstree-' + ins.get_theme() + ( ins.settings.core.themes.responsive ? ' jstree-dnd-responsive' : '' )); 6948 is_copy = data.data.origin && (data.data.origin.settings.dnd.always_copy || (data.data.origin.settings.dnd.copy && (data.event.metaKey || data.event.ctrlKey))); 6949 data.helper 6950 .children().attr('class', 'jstree-' + ins.get_theme() + ' jstree-' + ins.get_theme() + '-' + ins.get_theme_variant() + ' ' + ( ins.settings.core.themes.responsive ? ' jstree-dnd-responsive' : '' )) 6951 .find('.jstree-copy').first()[ is_copy ? 'show' : 'hide' ](); 6952 6953 // if are hovering the container itself add a new root node 6954 //console.log(data.event); 6955 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)) { 6956 ok = true; 6957 for(t1 = 0, t2 = data.data.nodes.length; t1 < t2; t1++) { 6958 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) }); 6959 if(!ok) { break; } 6960 } 6961 if(ok) { 6962 lastmv = { 'ins' : ins, 'par' : $.jstree.root, 'pos' : 'last' }; 6963 marker.hide(); 6964 data.helper.find('.jstree-icon').first().removeClass('jstree-er').addClass('jstree-ok'); 6965 if (data.event.originalEvent && data.event.originalEvent.dataTransfer) { 6966 data.event.originalEvent.dataTransfer.dropEffect = is_copy ? 'copy' : 'move'; 6967 } 6968 return; 6969 } 6970 } 6971 else { 6972 // if we are hovering a tree node 6973 ref = ins.settings.dnd.large_drop_target ? $(data.event.target).closest('.jstree-node').children('.jstree-anchor') : $(data.event.target).closest('.jstree-anchor'); 6974 6975 if(ref && ref.length && ref.parent().is('.jstree-closed, .jstree-open, .jstree-leaf')) { 6976 off = ref.offset(); 6977 rel = (data.event.pageY !== undefined ? data.event.pageY : data.event.originalEvent.pageY) - off.top; 6978 h = ref.outerHeight(); 6979 if(rel < h / 3) { 6980 o = ['b', 'i', 'a']; 6981 } 6982 else if(rel > h - h / 3) { 6983 o = ['a', 'i', 'b']; 6984 } 6985 else { 6986 o = rel > h / 2 ? ['i', 'a', 'b'] : ['i', 'b', 'a']; 6987 } 6988 $.each(o, function (j, v) { 6989 switch(v) { 6990 case 'b': 6991 l = off.left - 6; 6992 t = off.top; 6993 p = ins.get_parent(ref); 6994 i = ref.parent().index(); 6995 c = 'jstree-below'; 6996 break; 6997 case 'i': 6998 ip = ins.settings.dnd.inside_pos; 6999 tm = ins.get_node(ref.parent()); 7000 l = off.left - 2; 7001 t = off.top + h / 2 + 1; 7002 p = tm.id; 7003 i = ip === 'first' ? 0 : (ip === 'last' ? tm.children.length : Math.min(ip, tm.children.length)); 7004 c = 'jstree-inside'; 7005 break; 7006 case 'a': 7007 l = off.left - 6; 7008 t = off.top + h; 7009 p = ins.get_parent(ref); 7010 i = ref.parent().index() + 1; 7011 c = 'jstree-above'; 7012 break; 7013 } 7014 ok = true; 7015 for(t1 = 0, t2 = data.data.nodes.length; t1 < t2; t1++) { 7016 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"; 7017 ps = i; 7018 if(op === "move_node" && v === 'a' && (data.data.origin && data.data.origin === ins) && p === ins.get_parent(data.data.nodes[t1])) { 7019 pr = ins.get_node(p); 7020 if(ps > $.inArray(data.data.nodes[t1], pr.children)) { 7021 ps -= 1; 7022 } 7023 } 7024 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) }) ); 7025 if(!ok) { 7026 if(ins && ins.last_error) { laster = ins.last_error(); } 7027 break; 7028 } 7029 } 7030 if(v === 'i' && ref.parent().is('.jstree-closed') && ins.settings.dnd.open_timeout) { 7031 if (!data.event || data.event.type !== 'dragover' || isDifferentNode) { 7032 if (opento) { clearTimeout(opento); } 7033 opento = setTimeout((function (x, z) { return function () { x.open_node(z); }; }(ins, ref)), ins.settings.dnd.open_timeout); 7034 } 7035 } 7036 if(ok) { 7037 pn = ins.get_node(p, true); 7038 if (!pn.hasClass('.jstree-dnd-parent')) { 7039 $('.jstree-dnd-parent').removeClass('jstree-dnd-parent'); 7040 pn.addClass('jstree-dnd-parent'); 7041 } 7042 lastmv = { 'ins' : ins, 'par' : p, 'pos' : v === 'i' && ip === 'last' && i === 0 && !ins.is_loaded(tm) ? 'last' : i }; 7043 marker.css({ 'left' : l + 'px', 'top' : t + 'px' }).show(); 7044 marker.removeClass('jstree-above jstree-inside jstree-below').addClass(c); 7045 data.helper.find('.jstree-icon').first().removeClass('jstree-er').addClass('jstree-ok'); 7046 if (data.event.originalEvent && data.event.originalEvent.dataTransfer) { 7047 data.event.originalEvent.dataTransfer.dropEffect = is_copy ? 'copy' : 'move'; 7048 } 7049 laster = {}; 7050 o = true; 7051 return false; 7052 } 7053 }); 7054 if(o === true) { return; } 7055 } 7056 } 7057 } 7058 $('.jstree-dnd-parent').removeClass('jstree-dnd-parent'); 7059 lastmv = false; 7060 data.helper.find('.jstree-icon').removeClass('jstree-ok').addClass('jstree-er'); 7061 if (data.event.originalEvent && data.event.originalEvent.dataTransfer) { 7062 //data.event.originalEvent.dataTransfer.dropEffect = 'none'; 7063 } 7064 marker.hide(); 7065 }) 7066 .on('dnd_scroll.vakata.jstree', function (e, data) { 7067 if(!data || !data.data || !data.data.jstree) { return; } 7068 marker.hide(); 7069 lastmv = false; 7070 lastev = false; 7071 data.helper.find('.jstree-icon').first().removeClass('jstree-ok').addClass('jstree-er'); 7072 }) 7073 .on('dnd_stop.vakata.jstree', function (e, data) { 7074 $('.jstree-dnd-parent').removeClass('jstree-dnd-parent'); 7075 if(opento) { clearTimeout(opento); } 7076 if(!data || !data.data || !data.data.jstree) { return; } 7077 marker.hide().detach(); 7078 var i, j, nodes = []; 7079 if(lastmv) { 7080 for(i = 0, j = data.data.nodes.length; i < j; i++) { 7081 nodes[i] = data.data.origin ? data.data.origin.get_node(data.data.nodes[i]) : data.data.nodes[i]; 7082 } 7083 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); 7084 } 7085 else { 7086 i = $(data.event.target).closest('.jstree'); 7087 if(i.length && laster && laster.error && laster.error === 'check') { 7088 i = i.jstree(true); 7089 if(i) { 7090 i.settings.core.error.call(this, laster); 7091 } 7092 } 7093 } 7094 lastev = false; 7095 lastmv = false; 7096 }) 7097 .on('keyup.jstree keydown.jstree', function (e, data) { 7098 data = $.vakata.dnd._get(); 7099 if(data && data.data && data.data.jstree) { 7100 if (e.type === "keyup" && e.which === 27) { 7101 if (opento) { clearTimeout(opento); } 7102 lastmv = false; 7103 laster = false; 7104 lastev = false; 7105 opento = false; 7106 marker.hide().detach(); 7107 $.vakata.dnd._clean(); 7108 } else { 7109 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' ](); 7110 if(lastev) { 7111 lastev.metaKey = e.metaKey; 7112 lastev.ctrlKey = e.ctrlKey; 7113 $.vakata.dnd._trigger('move', lastev); 7114 } 7115 } 7116 } 7117 }); 7118 }); 7119 7120 // helpers 7121 (function ($) { 7122 $.vakata.html = { 7123 div : $('<div></div>'), 7124 escape : function (str) { 7125 return $.vakata.html.div.text(str).html(); 7126 }, 7127 strip : function (str) { 7128 return $.vakata.html.div.empty().append($.parseHTML(str)).text(); 7129 } 7130 }; 7131 // private variable 7132 var vakata_dnd = { 7133 element : false, 7134 target : false, 7135 is_down : false, 7136 is_drag : false, 7137 helper : false, 7138 helper_w: 0, 7139 data : false, 7140 init_x : 0, 7141 init_y : 0, 7142 scroll_l: 0, 7143 scroll_t: 0, 7144 scroll_e: false, 7145 scroll_i: false, 7146 is_touch: false 7147 }; 7148 $.vakata.dnd = { 7149 settings : { 7150 scroll_speed : 10, 7151 scroll_proximity : 20, 7152 helper_left : 5, 7153 helper_top : 10, 7154 threshold : 5, 7155 threshold_touch : 10 7156 }, 7157 _trigger : function (event_name, e, data) { 7158 if (data === undefined) { 7159 data = $.vakata.dnd._get(); 7160 } 7161 data.event = e; 7162 $(document).triggerHandler("dnd_" + event_name + ".vakata", data); 7163 }, 7164 _get : function () { 7165 return { 7166 "data" : vakata_dnd.data, 7167 "element" : vakata_dnd.element, 7168 "helper" : vakata_dnd.helper 7169 }; 7170 }, 7171 _clean : function () { 7172 if(vakata_dnd.helper) { vakata_dnd.helper.remove(); } 7173 if(vakata_dnd.scroll_i) { clearInterval(vakata_dnd.scroll_i); vakata_dnd.scroll_i = false; } 7174 vakata_dnd = { 7175 element : false, 7176 target : false, 7177 is_down : false, 7178 is_drag : false, 7179 helper : false, 7180 helper_w: 0, 7181 data : false, 7182 init_x : 0, 7183 init_y : 0, 7184 scroll_l: 0, 7185 scroll_t: 0, 7186 scroll_e: false, 7187 scroll_i: false, 7188 is_touch: false 7189 }; 7190 elm = null; 7191 $(document).off("mousemove.vakata.jstree touchmove.vakata.jstree", $.vakata.dnd.drag); 7192 $(document).off("mouseup.vakata.jstree touchend.vakata.jstree", $.vakata.dnd.stop); 7193 }, 7194 _scroll : function (init_only) { 7195 if(!vakata_dnd.scroll_e || (!vakata_dnd.scroll_l && !vakata_dnd.scroll_t)) { 7196 if(vakata_dnd.scroll_i) { clearInterval(vakata_dnd.scroll_i); vakata_dnd.scroll_i = false; } 7197 return false; 7198 } 7199 if(!vakata_dnd.scroll_i) { 7200 vakata_dnd.scroll_i = setInterval($.vakata.dnd._scroll, 100); 7201 return false; 7202 } 7203 if(init_only === true) { return false; } 7204 7205 var i = vakata_dnd.scroll_e.scrollTop(), 7206 j = vakata_dnd.scroll_e.scrollLeft(); 7207 vakata_dnd.scroll_e.scrollTop(i + vakata_dnd.scroll_t * $.vakata.dnd.settings.scroll_speed); 7208 vakata_dnd.scroll_e.scrollLeft(j + vakata_dnd.scroll_l * $.vakata.dnd.settings.scroll_speed); 7209 if(i !== vakata_dnd.scroll_e.scrollTop() || j !== vakata_dnd.scroll_e.scrollLeft()) { 7210 /** 7211 * triggered on the document when a drag causes an element to scroll 7212 * @event 7213 * @plugin dnd 7214 * @name dnd_scroll.vakata 7215 * @param {Mixed} data any data supplied with the call to $.vakata.dnd.start 7216 * @param {DOM} element the DOM element being dragged 7217 * @param {jQuery} helper the helper shown next to the mouse 7218 * @param {jQuery} event the element that is scrolling 7219 */ 7220 $.vakata.dnd._trigger("scroll", vakata_dnd.scroll_e); 7221 } 7222 }, 7223 start : function (e, data, html) { 7224 if(e.type === "touchstart" && e.originalEvent && e.originalEvent.changedTouches && e.originalEvent.changedTouches[0]) { 7225 e.pageX = e.originalEvent.changedTouches[0].pageX; 7226 e.pageY = e.originalEvent.changedTouches[0].pageY; 7227 e.target = document.elementFromPoint(e.originalEvent.changedTouches[0].pageX - window.pageXOffset, e.originalEvent.changedTouches[0].pageY - window.pageYOffset); 7228 } 7229 if(vakata_dnd.is_drag) { $.vakata.dnd.stop({}); } 7230 try { 7231 e.currentTarget.unselectable = "on"; 7232 e.currentTarget.onselectstart = function() { return false; }; 7233 if(e.currentTarget.style) { 7234 e.currentTarget.style.touchAction = "none"; 7235 e.currentTarget.style.msTouchAction = "none"; 7236 e.currentTarget.style.MozUserSelect = "none"; 7237 } 7238 } catch(ignore) { } 7239 vakata_dnd.init_x = e.pageX; 7240 vakata_dnd.init_y = e.pageY; 7241 vakata_dnd.data = data; 7242 vakata_dnd.is_down = true; 7243 vakata_dnd.element = e.currentTarget; 7244 vakata_dnd.target = e.target; 7245 vakata_dnd.is_touch = e.type === "touchstart"; 7246 if(html !== false) { 7247 vakata_dnd.helper = $("<div id='vakata-dnd'></div>").html(html).css({ 7248 "display" : "block", 7249 "margin" : "0", 7250 "padding" : "0", 7251 "position" : "absolute", 7252 "top" : "-2000px", 7253 "lineHeight" : "16px", 7254 "zIndex" : "10000" 7255 }); 7256 } 7257 $(document).on("mousemove.vakata.jstree touchmove.vakata.jstree", $.vakata.dnd.drag); 7258 $(document).on("mouseup.vakata.jstree touchend.vakata.jstree", $.vakata.dnd.stop); 7259 return false; 7260 }, 7261 drag : function (e) { 7262 if(e.type === "touchmove" && e.originalEvent && e.originalEvent.changedTouches && e.originalEvent.changedTouches[0]) { 7263 e.pageX = e.originalEvent.changedTouches[0].pageX; 7264 e.pageY = e.originalEvent.changedTouches[0].pageY; 7265 e.target = document.elementFromPoint(e.originalEvent.changedTouches[0].pageX - window.pageXOffset, e.originalEvent.changedTouches[0].pageY - window.pageYOffset); 7266 } 7267 if(!vakata_dnd.is_down) { return; } 7268 if(!vakata_dnd.is_drag) { 7269 if( 7270 Math.abs(e.pageX - vakata_dnd.init_x) > (vakata_dnd.is_touch ? $.vakata.dnd.settings.threshold_touch : $.vakata.dnd.settings.threshold) || 7271 Math.abs(e.pageY - vakata_dnd.init_y) > (vakata_dnd.is_touch ? $.vakata.dnd.settings.threshold_touch : $.vakata.dnd.settings.threshold) 7272 ) { 7273 if(vakata_dnd.helper) { 7274 vakata_dnd.helper.appendTo(document.body); 7275 vakata_dnd.helper_w = vakata_dnd.helper.outerWidth(); 7276 } 7277 vakata_dnd.is_drag = true; 7278 $(vakata_dnd.target).one('click.vakata', false); 7279 /** 7280 * triggered on the document when a drag starts 7281 * @event 7282 * @plugin dnd 7283 * @name dnd_start.vakata 7284 * @param {Mixed} data any data supplied with the call to $.vakata.dnd.start 7285 * @param {DOM} element the DOM element being dragged 7286 * @param {jQuery} helper the helper shown next to the mouse 7287 * @param {Object} event the event that caused the start (probably mousemove) 7288 */ 7289 $.vakata.dnd._trigger("start", e); 7290 } 7291 else { return; } 7292 } 7293 7294 var d = false, w = false, 7295 dh = false, wh = false, 7296 dw = false, ww = false, 7297 dt = false, dl = false, 7298 ht = false, hl = false; 7299 7300 vakata_dnd.scroll_t = 0; 7301 vakata_dnd.scroll_l = 0; 7302 vakata_dnd.scroll_e = false; 7303 $($(e.target).parentsUntil("body").addBack().get().reverse()) 7304 .filter(function () { 7305 return this.ownerDocument && (/^auto|scroll$/).test($(this).css("overflow")) && 7306 (this.scrollHeight > this.offsetHeight || this.scrollWidth > this.offsetWidth); 7307 }) 7308 .each(function () { 7309 var t = $(this), o = t.offset(); 7310 if(this.scrollHeight > this.offsetHeight) { 7311 if(o.top + t.height() - e.pageY < $.vakata.dnd.settings.scroll_proximity) { vakata_dnd.scroll_t = 1; } 7312 if(e.pageY - o.top < $.vakata.dnd.settings.scroll_proximity) { vakata_dnd.scroll_t = -1; } 7313 } 7314 if(this.scrollWidth > this.offsetWidth) { 7315 if(o.left + t.width() - e.pageX < $.vakata.dnd.settings.scroll_proximity) { vakata_dnd.scroll_l = 1; } 7316 if(e.pageX - o.left < $.vakata.dnd.settings.scroll_proximity) { vakata_dnd.scroll_l = -1; } 7317 } 7318 if(vakata_dnd.scroll_t || vakata_dnd.scroll_l) { 7319 vakata_dnd.scroll_e = $(this); 7320 return false; 7321 } 7322 }); 7323 7324 if(!vakata_dnd.scroll_e) { 7325 d = $(document); w = $(window); 7326 dh = d.height(); wh = w.height(); 7327 dw = d.width(); ww = w.width(); 7328 dt = d.scrollTop(); dl = d.scrollLeft(); 7329 if(dh > wh && e.pageY - dt < $.vakata.dnd.settings.scroll_proximity) { vakata_dnd.scroll_t = -1; } 7330 if(dh > wh && wh - (e.pageY - dt) < $.vakata.dnd.settings.scroll_proximity) { vakata_dnd.scroll_t = 1; } 7331 if(dw > ww && e.pageX - dl < $.vakata.dnd.settings.scroll_proximity) { vakata_dnd.scroll_l = -1; } 7332 if(dw > ww && ww - (e.pageX - dl) < $.vakata.dnd.settings.scroll_proximity) { vakata_dnd.scroll_l = 1; } 7333 if(vakata_dnd.scroll_t || vakata_dnd.scroll_l) { 7334 vakata_dnd.scroll_e = d; 7335 } 7336 } 7337 if(vakata_dnd.scroll_e) { $.vakata.dnd._scroll(true); } 7338 7339 if(vakata_dnd.helper) { 7340 ht = parseInt(e.pageY + $.vakata.dnd.settings.helper_top, 10); 7341 hl = parseInt(e.pageX + $.vakata.dnd.settings.helper_left, 10); 7342 if(dh && ht + 25 > dh) { ht = dh - 50; } 7343 if(dw && hl + vakata_dnd.helper_w > dw) { hl = dw - (vakata_dnd.helper_w + 2); } 7344 vakata_dnd.helper.css({ 7345 left : hl + "px", 7346 top : ht + "px" 7347 }); 7348 } 7349 /** 7350 * triggered on the document when a drag is in progress 7351 * @event 7352 * @plugin dnd 7353 * @name dnd_move.vakata 7354 * @param {Mixed} data any data supplied with the call to $.vakata.dnd.start 7355 * @param {DOM} element the DOM element being dragged 7356 * @param {jQuery} helper the helper shown next to the mouse 7357 * @param {Object} event the event that caused this to trigger (most likely mousemove) 7358 */ 7359 $.vakata.dnd._trigger("move", e); 7360 return false; 7361 }, 7362 stop : function (e) { 7363 if(e.type === "touchend" && e.originalEvent && e.originalEvent.changedTouches && e.originalEvent.changedTouches[0]) { 7364 e.pageX = e.originalEvent.changedTouches[0].pageX; 7365 e.pageY = e.originalEvent.changedTouches[0].pageY; 7366 e.target = document.elementFromPoint(e.originalEvent.changedTouches[0].pageX - window.pageXOffset, e.originalEvent.changedTouches[0].pageY - window.pageYOffset); 7367 } 7368 if(vakata_dnd.is_drag) { 7369 /** 7370 * triggered on the document when a drag stops (the dragged element is dropped) 7371 * @event 7372 * @plugin dnd 7373 * @name dnd_stop.vakata 7374 * @param {Mixed} data any data supplied with the call to $.vakata.dnd.start 7375 * @param {DOM} element the DOM element being dragged 7376 * @param {jQuery} helper the helper shown next to the mouse 7377 * @param {Object} event the event that caused the stop 7378 */ 7379 if (e.target !== vakata_dnd.target) { 7380 $(vakata_dnd.target).off('click.vakata'); 7381 } 7382 $.vakata.dnd._trigger("stop", e); 7383 } 7384 else { 7385 if(e.type === "touchend" && e.target === vakata_dnd.target) { 7386 var to = setTimeout(function () { $(e.target).trigger('click'); }, 100); 7387 $(e.target).one('click', function() { if(to) { clearTimeout(to); } }); 7388 } 7389 } 7390 $.vakata.dnd._clean(); 7391 return false; 7392 } 7393 }; 7394 }($)); 7395 7396 // include the dnd plugin by default 7397 // $.jstree.defaults.plugins.push("dnd"); 7398 7399 7400/** 7401 * ### Massload plugin 7402 * 7403 * Adds massload functionality to jsTree, so that multiple nodes can be loaded in a single request (only useful with lazy loading). 7404 */ 7405 7406 /** 7407 * massload configuration 7408 * 7409 * It is possible to set this to a standard jQuery-like AJAX config. 7410 * 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. 7411 * 7412 * 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. 7413 * 7414 * 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. 7415 * 7416 * { 7417 * "id1" : [{ "text" : "Child of ID1", "id" : "c1" }, { "text" : "Another child of ID1", "id" : "c2" }], 7418 * "id2" : [{ "text" : "Child of ID2", "id" : "c3" }] 7419 * } 7420 * 7421 * @name $.jstree.defaults.massload 7422 * @plugin massload 7423 */ 7424 $.jstree.defaults.massload = null; 7425 $.jstree.plugins.massload = function (options, parent) { 7426 this.init = function (el, options) { 7427 this._data.massload = {}; 7428 parent.init.call(this, el, options); 7429 }; 7430 this._load_nodes = function (nodes, callback, is_callback, force_reload) { 7431 var s = this.settings.massload, 7432 toLoad = [], 7433 m = this._model.data, 7434 i, j, dom; 7435 if (!is_callback) { 7436 for(i = 0, j = nodes.length; i < j; i++) { 7437 if(!m[nodes[i]] || ( (!m[nodes[i]].state.loaded && !m[nodes[i]].state.failed) || force_reload) ) { 7438 toLoad.push(nodes[i]); 7439 dom = this.get_node(nodes[i], true); 7440 if (dom && dom.length) { 7441 dom.addClass("jstree-loading").attr('aria-busy',true); 7442 } 7443 } 7444 } 7445 this._data.massload = {}; 7446 if (toLoad.length) { 7447 if($.vakata.is_function(s)) { 7448 return s.call(this, toLoad, function (data) { 7449 var i, j; 7450 if(data) { 7451 for(i in data) { 7452 if(data.hasOwnProperty(i)) { 7453 this._data.massload[i] = data[i]; 7454 } 7455 } 7456 } 7457 for(i = 0, j = nodes.length; i < j; i++) { 7458 dom = this.get_node(nodes[i], true); 7459 if (dom && dom.length) { 7460 dom.removeClass("jstree-loading").attr('aria-busy',false); 7461 } 7462 } 7463 parent._load_nodes.call(this, nodes, callback, is_callback, force_reload); 7464 }.bind(this)); 7465 } 7466 if(typeof s === 'object' && s && s.url) { 7467 s = $.extend(true, {}, s); 7468 if($.vakata.is_function(s.url)) { 7469 s.url = s.url.call(this, toLoad); 7470 } 7471 if($.vakata.is_function(s.data)) { 7472 s.data = s.data.call(this, toLoad); 7473 } 7474 return $.ajax(s) 7475 .done(function (data,t,x) { 7476 var i, j; 7477 if(data) { 7478 for(i in data) { 7479 if(data.hasOwnProperty(i)) { 7480 this._data.massload[i] = data[i]; 7481 } 7482 } 7483 } 7484 for(i = 0, j = nodes.length; i < j; i++) { 7485 dom = this.get_node(nodes[i], true); 7486 if (dom && dom.length) { 7487 dom.removeClass("jstree-loading").attr('aria-busy',false); 7488 } 7489 } 7490 parent._load_nodes.call(this, nodes, callback, is_callback, force_reload); 7491 }.bind(this)) 7492 .fail(function (f) { 7493 parent._load_nodes.call(this, nodes, callback, is_callback, force_reload); 7494 }.bind(this)); 7495 } 7496 } 7497 } 7498 return parent._load_nodes.call(this, nodes, callback, is_callback, force_reload); 7499 }; 7500 this._load_node = function (obj, callback) { 7501 var data = this._data.massload[obj.id], 7502 rslt = null, dom; 7503 if(data) { 7504 rslt = this[typeof data === 'string' ? '_append_html_data' : '_append_json_data']( 7505 obj, 7506 typeof data === 'string' ? $($.parseHTML(data)).filter(function () { return this.nodeType !== 3; }) : data, 7507 function (status) { callback.call(this, status); } 7508 ); 7509 dom = this.get_node(obj.id, true); 7510 if (dom && dom.length) { 7511 dom.removeClass("jstree-loading").attr('aria-busy',false); 7512 } 7513 delete this._data.massload[obj.id]; 7514 return rslt; 7515 } 7516 return parent._load_node.call(this, obj, callback); 7517 }; 7518 }; 7519 7520 7521/** 7522 * ### Search plugin 7523 * 7524 * Adds search functionality to jsTree. 7525 */ 7526 7527 /** 7528 * stores all defaults for the search plugin 7529 * @name $.jstree.defaults.search 7530 * @plugin search 7531 */ 7532 $.jstree.defaults.search = { 7533 /** 7534 * a jQuery-like AJAX config, which jstree uses if a server should be queried for results. 7535 * 7536 * 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. 7537 * 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 7538 * @name $.jstree.defaults.search.ajax 7539 * @plugin search 7540 */ 7541 ajax : false, 7542 /** 7543 * Indicates if the search should be fuzzy or not (should `chnd3` match `child node 3`). Default is `false`. 7544 * @name $.jstree.defaults.search.fuzzy 7545 * @plugin search 7546 */ 7547 fuzzy : false, 7548 /** 7549 * Indicates if the search should be case sensitive. Default is `false`. 7550 * @name $.jstree.defaults.search.case_sensitive 7551 * @plugin search 7552 */ 7553 case_sensitive : false, 7554 /** 7555 * 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). 7556 * This setting can be changed at runtime when calling the search method. Default is `false`. 7557 * @name $.jstree.defaults.search.show_only_matches 7558 * @plugin search 7559 */ 7560 show_only_matches : false, 7561 /** 7562 * Indicates if the children of matched element are shown (when show_only_matches is true) 7563 * This setting can be changed at runtime when calling the search method. Default is `false`. 7564 * @name $.jstree.defaults.search.show_only_matches_children 7565 * @plugin search 7566 */ 7567 show_only_matches_children : false, 7568 /** 7569 * 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`. 7570 * @name $.jstree.defaults.search.close_opened_onclear 7571 * @plugin search 7572 */ 7573 close_opened_onclear : true, 7574 /** 7575 * Indicates if only leaf nodes should be included in search results. Default is `false`. 7576 * @name $.jstree.defaults.search.search_leaves_only 7577 * @plugin search 7578 */ 7579 search_leaves_only : false, 7580 /** 7581 * 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). 7582 * 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`. 7583 * @name $.jstree.defaults.search.search_callback 7584 * @plugin search 7585 */ 7586 search_callback : false 7587 }; 7588 7589 $.jstree.plugins.search = function (options, parent) { 7590 this.bind = function () { 7591 parent.bind.call(this); 7592 7593 this._data.search.str = ""; 7594 this._data.search.dom = $(); 7595 this._data.search.res = []; 7596 this._data.search.opn = []; 7597 this._data.search.som = false; 7598 this._data.search.smc = false; 7599 this._data.search.hdn = []; 7600 7601 this.element 7602 .on("search.jstree", function (e, data) { 7603 if(this._data.search.som && data.res.length) { 7604 var m = this._model.data, i, j, p = [], k, l; 7605 for(i = 0, j = data.res.length; i < j; i++) { 7606 if(m[data.res[i]] && !m[data.res[i]].state.hidden) { 7607 p.push(data.res[i]); 7608 p = p.concat(m[data.res[i]].parents); 7609 if(this._data.search.smc) { 7610 for (k = 0, l = m[data.res[i]].children_d.length; k < l; k++) { 7611 if (m[m[data.res[i]].children_d[k]] && !m[m[data.res[i]].children_d[k]].state.hidden) { 7612 p.push(m[data.res[i]].children_d[k]); 7613 } 7614 } 7615 } 7616 } 7617 } 7618 p = $.vakata.array_remove_item($.vakata.array_unique(p), $.jstree.root); 7619 this._data.search.hdn = this.hide_all(true); 7620 this.show_node(p, true); 7621 this.redraw(true); 7622 } 7623 }.bind(this)) 7624 .on("clear_search.jstree", function (e, data) { 7625 if(this._data.search.som && data.res.length) { 7626 this.show_node(this._data.search.hdn, true); 7627 this.redraw(true); 7628 } 7629 }.bind(this)); 7630 }; 7631 /** 7632 * used to search the tree nodes for a given string 7633 * @name search(str [, skip_async]) 7634 * @param {String} str the search string 7635 * @param {Boolean} skip_async if set to true server will not be queried even if configured 7636 * @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) 7637 * @param {mixed} inside an optional node to whose children to limit the search 7638 * @param {Boolean} append if set to true the results of this search are appended to the previous search 7639 * @plugin search 7640 * @trigger search.jstree 7641 */ 7642 this.search = function (str, skip_async, show_only_matches, inside, append, show_only_matches_children) { 7643 if(str === false || $.vakata.trim(str.toString()) === "") { 7644 return this.clear_search(); 7645 } 7646 inside = this.get_node(inside); 7647 inside = inside && (inside.id || inside.id === 0) ? inside.id : null; 7648 str = str.toString(); 7649 var s = this.settings.search, 7650 a = s.ajax ? s.ajax : false, 7651 m = this._model.data, 7652 f = null, 7653 r = [], 7654 p = [], i, j; 7655 if(this._data.search.res.length && !append) { 7656 this.clear_search(); 7657 } 7658 if(show_only_matches === undefined) { 7659 show_only_matches = s.show_only_matches; 7660 } 7661 if(show_only_matches_children === undefined) { 7662 show_only_matches_children = s.show_only_matches_children; 7663 } 7664 if(!skip_async && a !== false) { 7665 if($.vakata.is_function(a)) { 7666 return a.call(this, str, function (d) { 7667 if(d && d.d) { d = d.d; } 7668 this._load_nodes(!$.vakata.is_array(d) ? [] : $.vakata.array_unique(d), function () { 7669 this.search(str, true, show_only_matches, inside, append, show_only_matches_children); 7670 }); 7671 }.bind(this), inside); 7672 } 7673 else { 7674 a = $.extend({}, a); 7675 if(!a.data) { a.data = {}; } 7676 a.data.str = str; 7677 if(inside) { 7678 a.data.inside = inside; 7679 } 7680 if (this._data.search.lastRequest) { 7681 this._data.search.lastRequest.abort(); 7682 } 7683 this._data.search.lastRequest = $.ajax(a) 7684 .fail(function () { 7685 this._data.core.last_error = { 'error' : 'ajax', 'plugin' : 'search', 'id' : 'search_01', 'reason' : 'Could not load search parents', 'data' : JSON.stringify(a) }; 7686 this.settings.core.error.call(this, this._data.core.last_error); 7687 }.bind(this)) 7688 .done(function (d) { 7689 if(d && d.d) { d = d.d; } 7690 this._load_nodes(!$.vakata.is_array(d) ? [] : $.vakata.array_unique(d), function () { 7691 this.search(str, true, show_only_matches, inside, append, show_only_matches_children); 7692 }); 7693 }.bind(this)); 7694 return this._data.search.lastRequest; 7695 } 7696 } 7697 if(!append) { 7698 this._data.search.str = str; 7699 this._data.search.dom = $(); 7700 this._data.search.res = []; 7701 this._data.search.opn = []; 7702 this._data.search.som = show_only_matches; 7703 this._data.search.smc = show_only_matches_children; 7704 } 7705 7706 f = new $.vakata.search(str, true, { caseSensitive : s.case_sensitive, fuzzy : s.fuzzy }); 7707 $.each(m[inside ? inside : $.jstree.root].children_d, function (ii, i) { 7708 var v = m[i]; 7709 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) ) ) { 7710 r.push(i); 7711 p = p.concat(v.parents); 7712 } 7713 }); 7714 if(r.length) { 7715 p = $.vakata.array_unique(p); 7716 for(i = 0, j = p.length; i < j; i++) { 7717 if(p[i] !== $.jstree.root && m[p[i]] && this.open_node(p[i], null, 0) === true) { 7718 this._data.search.opn.push(p[i]); 7719 } 7720 } 7721 if(!append) { 7722 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(', #'))); 7723 this._data.search.res = r; 7724 } 7725 else { 7726 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(', #')))); 7727 this._data.search.res = $.vakata.array_unique(this._data.search.res.concat(r)); 7728 } 7729 this._data.search.dom.children(".jstree-anchor").addClass('jstree-search'); 7730 } 7731 /** 7732 * triggered after search is complete 7733 * @event 7734 * @name search.jstree 7735 * @param {jQuery} nodes a jQuery collection of matching nodes 7736 * @param {String} str the search string 7737 * @param {Array} res a collection of objects represeing the matching nodes 7738 * @plugin search 7739 */ 7740 this.trigger('search', { nodes : this._data.search.dom, str : str, res : this._data.search.res, show_only_matches : show_only_matches }); 7741 }; 7742 /** 7743 * used to clear the last search (removes classes and shows all nodes if filtering is on) 7744 * @name clear_search() 7745 * @plugin search 7746 * @trigger clear_search.jstree 7747 */ 7748 this.clear_search = function () { 7749 if(this.settings.search.close_opened_onclear) { 7750 this.close_node(this._data.search.opn, 0); 7751 } 7752 /** 7753 * triggered after search is complete 7754 * @event 7755 * @name clear_search.jstree 7756 * @param {jQuery} nodes a jQuery collection of matching nodes (the result from the last search) 7757 * @param {String} str the search string (the last search string) 7758 * @param {Array} res a collection of objects represeing the matching nodes (the result from the last search) 7759 * @plugin search 7760 */ 7761 this.trigger('clear_search', { 'nodes' : this._data.search.dom, str : this._data.search.str, res : this._data.search.res }); 7762 if(this._data.search.res.length) { 7763 this._data.search.dom = $(this.element[0].querySelectorAll('#' + $.map(this._data.search.res, function (v) { 7764 return "0123456789".indexOf(v[0]) !== -1 ? '\\3' + v[0] + ' ' + v.substr(1).replace($.jstree.idregex,'\\$&') : v.replace($.jstree.idregex,'\\$&'); 7765 }).join(', #'))); 7766 this._data.search.dom.children(".jstree-anchor").removeClass("jstree-search"); 7767 } 7768 this._data.search.str = ""; 7769 this._data.search.res = []; 7770 this._data.search.opn = []; 7771 this._data.search.dom = $(); 7772 }; 7773 7774 this.redraw_node = function(obj, deep, callback, force_render) { 7775 obj = parent.redraw_node.apply(this, arguments); 7776 if(obj) { 7777 if($.inArray(obj.id, this._data.search.res) !== -1) { 7778 var i, j, tmp = null; 7779 for(i = 0, j = obj.childNodes.length; i < j; i++) { 7780 if(obj.childNodes[i] && obj.childNodes[i].className && obj.childNodes[i].className.indexOf("jstree-anchor") !== -1) { 7781 tmp = obj.childNodes[i]; 7782 break; 7783 } 7784 } 7785 if(tmp) { 7786 tmp.className += ' jstree-search'; 7787 } 7788 } 7789 } 7790 return obj; 7791 }; 7792 }; 7793 7794 // helpers 7795 (function ($) { 7796 // from http://kiro.me/projects/fuse.html 7797 $.vakata.search = function(pattern, txt, options) { 7798 options = options || {}; 7799 options = $.extend({}, $.vakata.search.defaults, options); 7800 if(options.fuzzy !== false) { 7801 options.fuzzy = true; 7802 } 7803 pattern = options.caseSensitive ? pattern : pattern.toLowerCase(); 7804 var MATCH_LOCATION = options.location, 7805 MATCH_DISTANCE = options.distance, 7806 MATCH_THRESHOLD = options.threshold, 7807 patternLen = pattern.length, 7808 matchmask, pattern_alphabet, match_bitapScore, search; 7809 if(patternLen > 32) { 7810 options.fuzzy = false; 7811 } 7812 if(options.fuzzy) { 7813 matchmask = 1 << (patternLen - 1); 7814 pattern_alphabet = (function () { 7815 var mask = {}, 7816 i = 0; 7817 for (i = 0; i < patternLen; i++) { 7818 mask[pattern.charAt(i)] = 0; 7819 } 7820 for (i = 0; i < patternLen; i++) { 7821 mask[pattern.charAt(i)] |= 1 << (patternLen - i - 1); 7822 } 7823 return mask; 7824 }()); 7825 match_bitapScore = function (e, x) { 7826 var accuracy = e / patternLen, 7827 proximity = Math.abs(MATCH_LOCATION - x); 7828 if(!MATCH_DISTANCE) { 7829 return proximity ? 1.0 : accuracy; 7830 } 7831 return accuracy + (proximity / MATCH_DISTANCE); 7832 }; 7833 } 7834 search = function (text) { 7835 text = options.caseSensitive ? text.toString() : text.toString().toLowerCase(); 7836 if(pattern === text || text.indexOf(pattern) !== -1) { 7837 return { 7838 isMatch: true, 7839 score: 0 7840 }; 7841 } 7842 if(!options.fuzzy) { 7843 return { 7844 isMatch: false, 7845 score: 1 7846 }; 7847 } 7848 var i, j, 7849 textLen = text.length, 7850 scoreThreshold = MATCH_THRESHOLD, 7851 bestLoc = text.indexOf(pattern, MATCH_LOCATION), 7852 binMin, binMid, 7853 binMax = patternLen + textLen, 7854 lastRd, start, finish, rd, charMatch, 7855 score = 1, 7856 locations = []; 7857 if (bestLoc !== -1) { 7858 scoreThreshold = Math.min(match_bitapScore(0, bestLoc), scoreThreshold); 7859 bestLoc = text.lastIndexOf(pattern, MATCH_LOCATION + patternLen); 7860 if (bestLoc !== -1) { 7861 scoreThreshold = Math.min(match_bitapScore(0, bestLoc), scoreThreshold); 7862 } 7863 } 7864 bestLoc = -1; 7865 for (i = 0; i < patternLen; i++) { 7866 binMin = 0; 7867 binMid = binMax; 7868 while (binMin < binMid) { 7869 if (match_bitapScore(i, MATCH_LOCATION + binMid) <= scoreThreshold) { 7870 binMin = binMid; 7871 } else { 7872 binMax = binMid; 7873 } 7874 binMid = Math.floor((binMax - binMin) / 2 + binMin); 7875 } 7876 binMax = binMid; 7877 start = Math.max(1, MATCH_LOCATION - binMid + 1); 7878 finish = Math.min(MATCH_LOCATION + binMid, textLen) + patternLen; 7879 rd = new Array(finish + 2); 7880 rd[finish + 1] = (1 << i) - 1; 7881 for (j = finish; j >= start; j--) { 7882 charMatch = pattern_alphabet[text.charAt(j - 1)]; 7883 if (i === 0) { 7884 rd[j] = ((rd[j + 1] << 1) | 1) & charMatch; 7885 } else { 7886 rd[j] = ((rd[j + 1] << 1) | 1) & charMatch | (((lastRd[j + 1] | lastRd[j]) << 1) | 1) | lastRd[j + 1]; 7887 } 7888 if (rd[j] & matchmask) { 7889 score = match_bitapScore(i, j - 1); 7890 if (score <= scoreThreshold) { 7891 scoreThreshold = score; 7892 bestLoc = j - 1; 7893 locations.push(bestLoc); 7894 if (bestLoc > MATCH_LOCATION) { 7895 start = Math.max(1, 2 * MATCH_LOCATION - bestLoc); 7896 } else { 7897 break; 7898 } 7899 } 7900 } 7901 } 7902 if (match_bitapScore(i + 1, MATCH_LOCATION) > scoreThreshold) { 7903 break; 7904 } 7905 lastRd = rd; 7906 } 7907 return { 7908 isMatch: bestLoc >= 0, 7909 score: score 7910 }; 7911 }; 7912 return txt === true ? { 'search' : search } : search(txt); 7913 }; 7914 $.vakata.search.defaults = { 7915 location : 0, 7916 distance : 100, 7917 threshold : 0.6, 7918 fuzzy : false, 7919 caseSensitive : false 7920 }; 7921 }($)); 7922 7923 // include the search plugin by default 7924 // $.jstree.defaults.plugins.push("search"); 7925 7926 7927/** 7928 * ### Sort plugin 7929 * 7930 * Automatically sorts all siblings in the tree according to a sorting function. 7931 */ 7932 7933 /** 7934 * the settings function used to sort the nodes. 7935 * It is executed in the tree's context, accepts two nodes as arguments and should return `1` or `-1`. 7936 * @name $.jstree.defaults.sort 7937 * @plugin sort 7938 */ 7939 $.jstree.defaults.sort = function (a, b) { 7940 //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); 7941 return this.get_text(a) > this.get_text(b) ? 1 : -1; 7942 }; 7943 $.jstree.plugins.sort = function (options, parent) { 7944 this.bind = function () { 7945 parent.bind.call(this); 7946 this.element 7947 .on("model.jstree", function (e, data) { 7948 this.sort(data.parent, true); 7949 }.bind(this)) 7950 .on("rename_node.jstree create_node.jstree", function (e, data) { 7951 this.sort(data.parent || data.node.parent, false); 7952 this.redraw_node(data.parent || data.node.parent, true); 7953 }.bind(this)) 7954 .on("move_node.jstree copy_node.jstree", function (e, data) { 7955 this.sort(data.parent, false); 7956 this.redraw_node(data.parent, true); 7957 }.bind(this)); 7958 }; 7959 /** 7960 * used to sort a node's children 7961 * @private 7962 * @name sort(obj [, deep]) 7963 * @param {mixed} obj the node 7964 * @param {Boolean} deep if set to `true` nodes are sorted recursively. 7965 * @plugin sort 7966 * @trigger search.jstree 7967 */ 7968 this.sort = function (obj, deep) { 7969 var i, j; 7970 obj = this.get_node(obj); 7971 if(obj && obj.children && obj.children.length) { 7972 obj.children.sort(this.settings.sort.bind(this)); 7973 if(deep) { 7974 for(i = 0, j = obj.children_d.length; i < j; i++) { 7975 this.sort(obj.children_d[i], false); 7976 } 7977 } 7978 } 7979 }; 7980 }; 7981 7982 // include the sort plugin by default 7983 // $.jstree.defaults.plugins.push("sort"); 7984 7985/** 7986 * ### State plugin 7987 * 7988 * Saves the state of the tree (selected nodes, opened nodes) on the user's computer using available options (localStorage, cookies, etc) 7989 */ 7990 7991 var to = false; 7992 /** 7993 * stores all defaults for the state plugin 7994 * @name $.jstree.defaults.state 7995 * @plugin state 7996 */ 7997 $.jstree.defaults.state = { 7998 /** 7999 * A string for the key to use when saving the current tree (change if using multiple trees in your project). Defaults to `jstree`. 8000 * @name $.jstree.defaults.state.key 8001 * @plugin state 8002 */ 8003 key : 'jstree', 8004 /** 8005 * A space separated list of events that trigger a state save. Defaults to `changed.jstree open_node.jstree close_node.jstree`. 8006 * @name $.jstree.defaults.state.events 8007 * @plugin state 8008 */ 8009 events : 'changed.jstree open_node.jstree close_node.jstree check_node.jstree uncheck_node.jstree', 8010 /** 8011 * Time in milliseconds after which the state will expire. Defaults to 'false' meaning - no expire. 8012 * @name $.jstree.defaults.state.ttl 8013 * @plugin state 8014 */ 8015 ttl : false, 8016 /** 8017 * 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. 8018 * @name $.jstree.defaults.state.filter 8019 * @plugin state 8020 */ 8021 filter : false, 8022 /** 8023 * 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` 8024 * @name $.jstree.defaults.state.preserve_loaded 8025 * @plugin state 8026 */ 8027 preserve_loaded : false 8028 }; 8029 $.jstree.plugins.state = function (options, parent) { 8030 this.bind = function () { 8031 parent.bind.call(this); 8032 var bind = function () { 8033 this.element.on(this.settings.state.events, function () { 8034 if(to) { clearTimeout(to); } 8035 to = setTimeout(function () { this.save_state(); }.bind(this), 100); 8036 }.bind(this)); 8037 /** 8038 * triggered when the state plugin is finished restoring the state (and immediately after ready if there is no state to restore). 8039 * @event 8040 * @name state_ready.jstree 8041 * @plugin state 8042 */ 8043 this.trigger('state_ready'); 8044 }.bind(this); 8045 this.element 8046 .on("ready.jstree", function (e, data) { 8047 this.element.one("restore_state.jstree", bind); 8048 if(!this.restore_state()) { bind(); } 8049 }.bind(this)); 8050 }; 8051 /** 8052 * save the state 8053 * @name save_state() 8054 * @plugin state 8055 */ 8056 this.save_state = function () { 8057 var tm = this.get_state(); 8058 if (!this.settings.state.preserve_loaded) { 8059 delete tm.core.loaded; 8060 } 8061 var st = { 'state' : tm, 'ttl' : this.settings.state.ttl, 'sec' : +(new Date()) }; 8062 $.vakata.storage.set(this.settings.state.key, JSON.stringify(st)); 8063 }; 8064 /** 8065 * restore the state from the user's computer 8066 * @name restore_state() 8067 * @plugin state 8068 */ 8069 this.restore_state = function () { 8070 var k = $.vakata.storage.get(this.settings.state.key); 8071 if(!!k) { try { k = JSON.parse(k); } catch(ex) { return false; } } 8072 if(!!k && k.ttl && k.sec && +(new Date()) - k.sec > k.ttl) { return false; } 8073 if(!!k && k.state) { k = k.state; } 8074 if(!!k && $.vakata.is_function(this.settings.state.filter)) { k = this.settings.state.filter.call(this, k); } 8075 if(!!k) { 8076 if (!this.settings.state.preserve_loaded) { 8077 delete k.core.loaded; 8078 } 8079 this.element.one("set_state.jstree", function (e, data) { data.instance.trigger('restore_state', { 'state' : $.extend(true, {}, k) }); }); 8080 this.set_state(k); 8081 return true; 8082 } 8083 return false; 8084 }; 8085 /** 8086 * clear the state on the user's computer 8087 * @name clear_state() 8088 * @plugin state 8089 */ 8090 this.clear_state = function () { 8091 return $.vakata.storage.del(this.settings.state.key); 8092 }; 8093 }; 8094 8095 (function ($, undefined) { 8096 $.vakata.storage = { 8097 // simply specifying the functions in FF throws an error 8098 set : function (key, val) { return window.localStorage.setItem(key, val); }, 8099 get : function (key) { return window.localStorage.getItem(key); }, 8100 del : function (key) { return window.localStorage.removeItem(key); } 8101 }; 8102 }($)); 8103 8104 // include the state plugin by default 8105 // $.jstree.defaults.plugins.push("state"); 8106 8107/** 8108 * ### Types plugin 8109 * 8110 * 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. 8111 */ 8112 8113 /** 8114 * 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). 8115 * 8116 * * `max_children` the maximum number of immediate children this node type can have. Do not specify or set to `-1` for unlimited. 8117 * * `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. 8118 * * `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. 8119 * * `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. 8120 * * `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) 8121 * * `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) 8122 * 8123 * There are two predefined types: 8124 * 8125 * * `#` represents the root of the tree, for example `max_children` would control the maximum number of root nodes. 8126 * * `default` represents the default node - any settings here will be applied to all nodes that do not have a type specified. 8127 * 8128 * @name $.jstree.defaults.types 8129 * @plugin types 8130 */ 8131 $.jstree.defaults.types = { 8132 'default' : {} 8133 }; 8134 $.jstree.defaults.types[$.jstree.root] = {}; 8135 8136 $.jstree.plugins.types = function (options, parent) { 8137 this.init = function (el, options) { 8138 var i, j; 8139 if(options && options.types && options.types['default']) { 8140 for(i in options.types) { 8141 if(i !== "default" && i !== $.jstree.root && options.types.hasOwnProperty(i)) { 8142 for(j in options.types['default']) { 8143 if(options.types['default'].hasOwnProperty(j) && options.types[i][j] === undefined) { 8144 options.types[i][j] = options.types['default'][j]; 8145 } 8146 } 8147 } 8148 } 8149 } 8150 parent.init.call(this, el, options); 8151 this._model.data[$.jstree.root].type = $.jstree.root; 8152 }; 8153 this.refresh = function (skip_loading, forget_state) { 8154 parent.refresh.call(this, skip_loading, forget_state); 8155 this._model.data[$.jstree.root].type = $.jstree.root; 8156 }; 8157 this.bind = function () { 8158 this.element 8159 .on('model.jstree', function (e, data) { 8160 var m = this._model.data, 8161 dpc = data.nodes, 8162 t = this.settings.types, 8163 i, j, c = 'default', k; 8164 for(i = 0, j = dpc.length; i < j; i++) { 8165 c = 'default'; 8166 if(m[dpc[i]].original && m[dpc[i]].original.type && t[m[dpc[i]].original.type]) { 8167 c = m[dpc[i]].original.type; 8168 } 8169 if(m[dpc[i]].data && m[dpc[i]].data.jstree && m[dpc[i]].data.jstree.type && t[m[dpc[i]].data.jstree.type]) { 8170 c = m[dpc[i]].data.jstree.type; 8171 } 8172 m[dpc[i]].type = c; 8173 if(m[dpc[i]].icon === true && t[c].icon !== undefined) { 8174 m[dpc[i]].icon = t[c].icon; 8175 } 8176 if(t[c].li_attr !== undefined && typeof t[c].li_attr === 'object') { 8177 for (k in t[c].li_attr) { 8178 if (t[c].li_attr.hasOwnProperty(k)) { 8179 if (k === 'id') { 8180 continue; 8181 } 8182 else if (m[dpc[i]].li_attr[k] === undefined) { 8183 m[dpc[i]].li_attr[k] = t[c].li_attr[k]; 8184 } 8185 else if (k === 'class') { 8186 m[dpc[i]].li_attr['class'] = t[c].li_attr['class'] + ' ' + m[dpc[i]].li_attr['class']; 8187 } 8188 } 8189 } 8190 } 8191 if(t[c].a_attr !== undefined && typeof t[c].a_attr === 'object') { 8192 for (k in t[c].a_attr) { 8193 if (t[c].a_attr.hasOwnProperty(k)) { 8194 if (k === 'id') { 8195 continue; 8196 } 8197 else if (m[dpc[i]].a_attr[k] === undefined) { 8198 m[dpc[i]].a_attr[k] = t[c].a_attr[k]; 8199 } 8200 else if (k === 'href' && m[dpc[i]].a_attr[k] === '#') { 8201 m[dpc[i]].a_attr['href'] = t[c].a_attr['href']; 8202 } 8203 else if (k === 'class') { 8204 m[dpc[i]].a_attr['class'] = t[c].a_attr['class'] + ' ' + m[dpc[i]].a_attr['class']; 8205 } 8206 } 8207 } 8208 } 8209 } 8210 m[$.jstree.root].type = $.jstree.root; 8211 }.bind(this)); 8212 parent.bind.call(this); 8213 }; 8214 this.get_json = function (obj, options, flat) { 8215 var i, j, 8216 m = this._model.data, 8217 opt = options ? $.extend(true, {}, options, {no_id:false}) : {}, 8218 tmp = parent.get_json.call(this, obj, opt, flat); 8219 if(tmp === false) { return false; } 8220 if($.vakata.is_array(tmp)) { 8221 for(i = 0, j = tmp.length; i < j; i++) { 8222 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"; 8223 if(options && options.no_id) { 8224 delete tmp[i].id; 8225 if(tmp[i].li_attr && tmp[i].li_attr.id) { 8226 delete tmp[i].li_attr.id; 8227 } 8228 if(tmp[i].a_attr && tmp[i].a_attr.id) { 8229 delete tmp[i].a_attr.id; 8230 } 8231 } 8232 } 8233 } 8234 else { 8235 tmp.type = (tmp.id || tmp.id === 0) && m[tmp.id] && m[tmp.id].type ? m[tmp.id].type : "default"; 8236 if(options && options.no_id) { 8237 tmp = this._delete_ids(tmp); 8238 } 8239 } 8240 return tmp; 8241 }; 8242 this._delete_ids = function (tmp) { 8243 if($.vakata.is_array(tmp)) { 8244 for(var i = 0, j = tmp.length; i < j; i++) { 8245 tmp[i] = this._delete_ids(tmp[i]); 8246 } 8247 return tmp; 8248 } 8249 delete tmp.id; 8250 if(tmp.li_attr && tmp.li_attr.id) { 8251 delete tmp.li_attr.id; 8252 } 8253 if(tmp.a_attr && tmp.a_attr.id) { 8254 delete tmp.a_attr.id; 8255 } 8256 if(tmp.children && $.vakata.is_array(tmp.children)) { 8257 tmp.children = this._delete_ids(tmp.children); 8258 } 8259 return tmp; 8260 }; 8261 this.check = function (chk, obj, par, pos, more) { 8262 if(parent.check.call(this, chk, obj, par, pos, more) === false) { return false; } 8263 obj = obj && (obj.id || obj.id === 0) ? obj : this.get_node(obj); 8264 par = par && (par.id || par.id === 0) ? par : this.get_node(par); 8265 var m = obj && (obj.id || obj.id === 0) ? (more && more.origin ? more.origin : $.jstree.reference(obj.id)) : null, tmp, d, i, j; 8266 m = m && m._model && m._model.data ? m._model.data : null; 8267 switch(chk) { 8268 case "create_node": 8269 case "move_node": 8270 case "copy_node": 8271 if(chk !== 'move_node' || $.inArray(obj.id, par.children) === -1) { 8272 tmp = this.get_rules(par); 8273 if(tmp.max_children !== undefined && tmp.max_children !== -1 && tmp.max_children === par.children.length) { 8274 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 }) }; 8275 return false; 8276 } 8277 if(tmp.valid_children !== undefined && tmp.valid_children !== -1 && $.inArray((obj.type || 'default'), tmp.valid_children) === -1) { 8278 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 }) }; 8279 return false; 8280 } 8281 if(m && obj.children_d && obj.parents) { 8282 d = 0; 8283 for(i = 0, j = obj.children_d.length; i < j; i++) { 8284 d = Math.max(d, m[obj.children_d[i]].parents.length); 8285 } 8286 d = d - obj.parents.length + 1; 8287 } 8288 if(d <= 0 || d === undefined) { d = 1; } 8289 do { 8290 if(tmp.max_depth !== undefined && tmp.max_depth !== -1 && tmp.max_depth < d) { 8291 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 }) }; 8292 return false; 8293 } 8294 par = this.get_node(par.parent); 8295 tmp = this.get_rules(par); 8296 d++; 8297 } while(par); 8298 } 8299 break; 8300 } 8301 return true; 8302 }; 8303 /** 8304 * used to retrieve the type settings object for a node 8305 * @name get_rules(obj) 8306 * @param {mixed} obj the node to find the rules for 8307 * @return {Object} 8308 * @plugin types 8309 */ 8310 this.get_rules = function (obj) { 8311 obj = this.get_node(obj); 8312 if(!obj) { return false; } 8313 var tmp = this.get_type(obj, true); 8314 if(tmp.max_depth === undefined) { tmp.max_depth = -1; } 8315 if(tmp.max_children === undefined) { tmp.max_children = -1; } 8316 if(tmp.valid_children === undefined) { tmp.valid_children = -1; } 8317 return tmp; 8318 }; 8319 /** 8320 * used to retrieve the type string or settings object for a node 8321 * @name get_type(obj [, rules]) 8322 * @param {mixed} obj the node to find the rules for 8323 * @param {Boolean} rules if set to `true` instead of a string the settings object will be returned 8324 * @return {String|Object} 8325 * @plugin types 8326 */ 8327 this.get_type = function (obj, rules) { 8328 obj = this.get_node(obj); 8329 return (!obj) ? false : ( rules ? $.extend({ 'type' : obj.type }, this.settings.types[obj.type]) : obj.type); 8330 }; 8331 /** 8332 * used to change a node's type 8333 * @name set_type(obj, type) 8334 * @param {mixed} obj the node to change 8335 * @param {String} type the new type 8336 * @plugin types 8337 */ 8338 this.set_type = function (obj, type) { 8339 var m = this._model.data, t, t1, t2, old_type, old_icon, k, d, a; 8340 if($.vakata.is_array(obj)) { 8341 obj = obj.slice(); 8342 for(t1 = 0, t2 = obj.length; t1 < t2; t1++) { 8343 this.set_type(obj[t1], type); 8344 } 8345 return true; 8346 } 8347 t = this.settings.types; 8348 obj = this.get_node(obj); 8349 if(!t[type] || !obj) { return false; } 8350 d = this.get_node(obj, true); 8351 if (d && d.length) { 8352 a = d.children('.jstree-anchor'); 8353 } 8354 old_type = obj.type; 8355 old_icon = this.get_icon(obj); 8356 obj.type = type; 8357 if(old_icon === true || !t[old_type] || (t[old_type].icon !== undefined && old_icon === t[old_type].icon)) { 8358 this.set_icon(obj, t[type].icon !== undefined ? t[type].icon : true); 8359 } 8360 8361 // remove old type props 8362 if(t[old_type] && t[old_type].li_attr !== undefined && typeof t[old_type].li_attr === 'object') { 8363 for (k in t[old_type].li_attr) { 8364 if (t[old_type].li_attr.hasOwnProperty(k)) { 8365 if (k === 'id') { 8366 continue; 8367 } 8368 else if (k === 'class') { 8369 m[obj.id].li_attr['class'] = (m[obj.id].li_attr['class'] || '').replace(t[old_type].li_attr[k], ''); 8370 if (d) { d.removeClass(t[old_type].li_attr[k]); } 8371 } 8372 else if (m[obj.id].li_attr[k] === t[old_type].li_attr[k]) { 8373 m[obj.id].li_attr[k] = null; 8374 if (d) { d.removeAttr(k); } 8375 } 8376 } 8377 } 8378 } 8379 if(t[old_type] && t[old_type].a_attr !== undefined && typeof t[old_type].a_attr === 'object') { 8380 for (k in t[old_type].a_attr) { 8381 if (t[old_type].a_attr.hasOwnProperty(k)) { 8382 if (k === 'id') { 8383 continue; 8384 } 8385 else if (k === 'class') { 8386 m[obj.id].a_attr['class'] = (m[obj.id].a_attr['class'] || '').replace(t[old_type].a_attr[k], ''); 8387 if (a) { a.removeClass(t[old_type].a_attr[k]); } 8388 } 8389 else if (m[obj.id].a_attr[k] === t[old_type].a_attr[k]) { 8390 if (k === 'href') { 8391 m[obj.id].a_attr[k] = '#'; 8392 if (a) { a.attr('href', '#'); } 8393 } 8394 else { 8395 delete m[obj.id].a_attr[k]; 8396 if (a) { a.removeAttr(k); } 8397 } 8398 } 8399 } 8400 } 8401 } 8402 8403 // add new props 8404 if(t[type].li_attr !== undefined && typeof t[type].li_attr === 'object') { 8405 for (k in t[type].li_attr) { 8406 if (t[type].li_attr.hasOwnProperty(k)) { 8407 if (k === 'id') { 8408 continue; 8409 } 8410 else if (m[obj.id].li_attr[k] === undefined) { 8411 m[obj.id].li_attr[k] = t[type].li_attr[k]; 8412 if (d) { 8413 if (k === 'class') { 8414 d.addClass(t[type].li_attr[k]); 8415 } 8416 else { 8417 d.attr(k, t[type].li_attr[k]); 8418 } 8419 } 8420 } 8421 else if (k === 'class') { 8422 m[obj.id].li_attr['class'] = t[type].li_attr[k] + ' ' + m[obj.id].li_attr['class']; 8423 if (d) { d.addClass(t[type].li_attr[k]); } 8424 } 8425 } 8426 } 8427 } 8428 if(t[type].a_attr !== undefined && typeof t[type].a_attr === 'object') { 8429 for (k in t[type].a_attr) { 8430 if (t[type].a_attr.hasOwnProperty(k)) { 8431 if (k === 'id') { 8432 continue; 8433 } 8434 else if (m[obj.id].a_attr[k] === undefined) { 8435 m[obj.id].a_attr[k] = t[type].a_attr[k]; 8436 if (a) { 8437 if (k === 'class') { 8438 a.addClass(t[type].a_attr[k]); 8439 } 8440 else { 8441 a.attr(k, t[type].a_attr[k]); 8442 } 8443 } 8444 } 8445 else if (k === 'href' && m[obj.id].a_attr[k] === '#') { 8446 m[obj.id].a_attr['href'] = t[type].a_attr['href']; 8447 if (a) { a.attr('href', t[type].a_attr['href']); } 8448 } 8449 else if (k === 'class') { 8450 m[obj.id].a_attr['class'] = t[type].a_attr['class'] + ' ' + m[obj.id].a_attr['class']; 8451 if (a) { a.addClass(t[type].a_attr[k]); } 8452 } 8453 } 8454 } 8455 } 8456 8457 return true; 8458 }; 8459 }; 8460 // include the types plugin by default 8461 // $.jstree.defaults.plugins.push("types"); 8462 8463 8464/** 8465 * ### Unique plugin 8466 * 8467 * Enforces that no nodes with the same name can coexist as siblings. 8468 */ 8469 8470 /** 8471 * stores all defaults for the unique plugin 8472 * @name $.jstree.defaults.unique 8473 * @plugin unique 8474 */ 8475 $.jstree.defaults.unique = { 8476 /** 8477 * Indicates if the comparison should be case sensitive. Default is `false`. 8478 * @name $.jstree.defaults.unique.case_sensitive 8479 * @plugin unique 8480 */ 8481 case_sensitive : false, 8482 /** 8483 * Indicates if white space should be trimmed before the comparison. Default is `false`. 8484 * @name $.jstree.defaults.unique.trim_whitespace 8485 * @plugin unique 8486 */ 8487 trim_whitespace : false, 8488 /** 8489 * 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)`. 8490 * @name $.jstree.defaults.unique.duplicate 8491 * @plugin unique 8492 */ 8493 duplicate : function (name, counter) { 8494 return name + ' (' + counter + ')'; 8495 } 8496 }; 8497 8498 $.jstree.plugins.unique = function (options, parent) { 8499 this.check = function (chk, obj, par, pos, more) { 8500 if(parent.check.call(this, chk, obj, par, pos, more) === false) { return false; } 8501 obj = obj && (obj.id || obj.id === 0) ? obj : this.get_node(obj); 8502 par = par && (par.id || par.id === 0) ? par : this.get_node(par); 8503 if(!par || !par.children) { return true; } 8504 var n = chk === "rename_node" ? pos : obj.text, 8505 c = [], 8506 s = this.settings.unique.case_sensitive, 8507 w = this.settings.unique.trim_whitespace, 8508 m = this._model.data, i, j, t; 8509 for(i = 0, j = par.children.length; i < j; i++) { 8510 t = m[par.children[i]].text; 8511 if (!s) { 8512 t = t.toLowerCase(); 8513 } 8514 if (w) { 8515 t = t.replace(/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g, ''); 8516 } 8517 c.push(t); 8518 } 8519 if(!s) { n = n.toLowerCase(); } 8520 if (w) { n = n.replace(/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g, ''); } 8521 switch(chk) { 8522 case "delete_node": 8523 return true; 8524 case "rename_node": 8525 t = obj.text || ''; 8526 if (!s) { 8527 t = t.toLowerCase(); 8528 } 8529 if (w) { 8530 t = t.replace(/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g, ''); 8531 } 8532 i = ($.inArray(n, c) === -1 || (obj.text && t === n)); 8533 if(!i) { 8534 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 }) }; 8535 } 8536 return i; 8537 case "create_node": 8538 i = ($.inArray(n, c) === -1); 8539 if(!i) { 8540 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 }) }; 8541 } 8542 return i; 8543 case "copy_node": 8544 i = ($.inArray(n, c) === -1); 8545 if(!i) { 8546 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 }) }; 8547 } 8548 return i; 8549 case "move_node": 8550 i = ( (obj.parent === par.id && (!more || !more.is_multi)) || $.inArray(n, c) === -1); 8551 if(!i) { 8552 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 }) }; 8553 } 8554 return i; 8555 } 8556 return true; 8557 }; 8558 this.create_node = function (par, node, pos, callback, is_loaded) { 8559 if(!node || (typeof node === 'object' && node.text === undefined)) { 8560 if(par === null) { 8561 par = $.jstree.root; 8562 } 8563 par = this.get_node(par); 8564 if(!par) { 8565 return parent.create_node.call(this, par, node, pos, callback, is_loaded); 8566 } 8567 pos = pos === undefined ? "last" : pos; 8568 if(!pos.toString().match(/^(before|after)$/) && !is_loaded && !this.is_loaded(par)) { 8569 return parent.create_node.call(this, par, node, pos, callback, is_loaded); 8570 } 8571 if(!node) { node = {}; } 8572 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; 8573 n = tmp = this.get_string('New node'); 8574 dpc = []; 8575 for(i = 0, j = par.children.length; i < j; i++) { 8576 t = m[par.children[i]].text; 8577 if (!s) { 8578 t = t.toLowerCase(); 8579 } 8580 if (w) { 8581 t = t.replace(/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g, ''); 8582 } 8583 dpc.push(t); 8584 } 8585 i = 1; 8586 t = n; 8587 if (!s) { 8588 t = t.toLowerCase(); 8589 } 8590 if (w) { 8591 t = t.replace(/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g, ''); 8592 } 8593 while($.inArray(t, dpc) !== -1) { 8594 n = cb.call(this, tmp, (++i)).toString(); 8595 t = n; 8596 if (!s) { 8597 t = t.toLowerCase(); 8598 } 8599 if (w) { 8600 t = t.replace(/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g, ''); 8601 } 8602 } 8603 node.text = n; 8604 } 8605 return parent.create_node.call(this, par, node, pos, callback, is_loaded); 8606 }; 8607 }; 8608 8609 // include the unique plugin by default 8610 // $.jstree.defaults.plugins.push("unique"); 8611 8612 8613/** 8614 * ### Wholerow plugin 8615 * 8616 * Makes each node appear block level. Making selection easier. May cause slow down for large trees in old browsers. 8617 */ 8618 8619 var div = document.createElement('DIV'); 8620 div.setAttribute('unselectable','on'); 8621 div.setAttribute('role','presentation'); 8622 div.className = 'jstree-wholerow'; 8623 div.innerHTML = ' '; 8624 $.jstree.plugins.wholerow = function (options, parent) { 8625 this.bind = function () { 8626 parent.bind.call(this); 8627 8628 this.element 8629 .on('ready.jstree set_state.jstree', function () { 8630 this.hide_dots(); 8631 }.bind(this)) 8632 .on("init.jstree loading.jstree ready.jstree", function () { 8633 //div.style.height = this._data.core.li_height + 'px'; 8634 this.get_container_ul().addClass('jstree-wholerow-ul'); 8635 }.bind(this)) 8636 .on("deselect_all.jstree", function (e, data) { 8637 this.element.find('.jstree-wholerow-clicked').removeClass('jstree-wholerow-clicked'); 8638 }.bind(this)) 8639 .on("changed.jstree", function (e, data) { 8640 this.element.find('.jstree-wholerow-clicked').removeClass('jstree-wholerow-clicked'); 8641 var tmp = false, i, j; 8642 for(i = 0, j = data.selected.length; i < j; i++) { 8643 tmp = this.get_node(data.selected[i], true); 8644 if(tmp && tmp.length) { 8645 tmp.children('.jstree-wholerow').addClass('jstree-wholerow-clicked'); 8646 } 8647 } 8648 }.bind(this)) 8649 .on("open_node.jstree", function (e, data) { 8650 this.get_node(data.node, true).find('.jstree-clicked').parent().children('.jstree-wholerow').addClass('jstree-wholerow-clicked'); 8651 }.bind(this)) 8652 .on("hover_node.jstree dehover_node.jstree", function (e, data) { 8653 if(e.type === "hover_node" && this.is_disabled(data.node)) { return; } 8654 this.get_node(data.node, true).children('.jstree-wholerow')[e.type === "hover_node"?"addClass":"removeClass"]('jstree-wholerow-hovered'); 8655 }.bind(this)) 8656 .on("contextmenu.jstree", ".jstree-wholerow", function (e) { 8657 if (this._data.contextmenu) { 8658 e.preventDefault(); 8659 var tmp = $.Event('contextmenu', { metaKey : e.metaKey, ctrlKey : e.ctrlKey, altKey : e.altKey, shiftKey : e.shiftKey, pageX : e.pageX, pageY : e.pageY }); 8660 $(e.currentTarget).closest(".jstree-node").children(".jstree-anchor").first().trigger(tmp); 8661 } 8662 }.bind(this)) 8663 /*! 8664 .on("mousedown.jstree touchstart.jstree", ".jstree-wholerow", function (e) { 8665 if(e.target === e.currentTarget) { 8666 var a = $(e.currentTarget).closest(".jstree-node").children(".jstree-anchor"); 8667 e.target = a[0]; 8668 a.trigger(e); 8669 } 8670 }) 8671 */ 8672 .on("click.jstree", ".jstree-wholerow", function (e) { 8673 e.stopImmediatePropagation(); 8674 var tmp = $.Event('click', { metaKey : e.metaKey, ctrlKey : e.ctrlKey, altKey : e.altKey, shiftKey : e.shiftKey }); 8675 $(e.currentTarget).closest(".jstree-node").children(".jstree-anchor").first().trigger(tmp).trigger('focus'); 8676 }) 8677 .on("dblclick.jstree", ".jstree-wholerow", function (e) { 8678 e.stopImmediatePropagation(); 8679 var tmp = $.Event('dblclick', { metaKey : e.metaKey, ctrlKey : e.ctrlKey, altKey : e.altKey, shiftKey : e.shiftKey }); 8680 $(e.currentTarget).closest(".jstree-node").children(".jstree-anchor").first().trigger(tmp).trigger('focus'); 8681 }) 8682 .on("click.jstree", ".jstree-leaf > .jstree-ocl", function (e) { 8683 e.stopImmediatePropagation(); 8684 var tmp = $.Event('click', { metaKey : e.metaKey, ctrlKey : e.ctrlKey, altKey : e.altKey, shiftKey : e.shiftKey }); 8685 $(e.currentTarget).closest(".jstree-node").children(".jstree-anchor").first().trigger(tmp).trigger('focus'); 8686 }.bind(this)) 8687 .on("mouseover.jstree", ".jstree-wholerow, .jstree-icon", function (e) { 8688 e.stopImmediatePropagation(); 8689 if(!this.is_disabled(e.currentTarget)) { 8690 this.hover_node(e.currentTarget); 8691 } 8692 return false; 8693 }.bind(this)) 8694 .on("mouseleave.jstree", ".jstree-node", function (e) { 8695 this.dehover_node(e.currentTarget); 8696 }.bind(this)); 8697 }; 8698 this.teardown = function () { 8699 if(this.settings.wholerow) { 8700 this.element.find(".jstree-wholerow").remove(); 8701 } 8702 parent.teardown.call(this); 8703 }; 8704 this.redraw_node = function(obj, deep, callback, force_render) { 8705 obj = parent.redraw_node.apply(this, arguments); 8706 if(obj) { 8707 var tmp = div.cloneNode(true); 8708 //tmp.style.height = this._data.core.li_height + 'px'; 8709 if($.inArray(obj.id, this._data.core.selected) !== -1) { tmp.className += ' jstree-wholerow-clicked'; } 8710 if(this._data.core.focused && this._data.core.focused === obj.id) { tmp.className += ' jstree-wholerow-hovered'; } 8711 obj.insertBefore(tmp, obj.childNodes[0]); 8712 } 8713 return obj; 8714 }; 8715 }; 8716 // include the wholerow plugin by default 8717 // $.jstree.defaults.plugins.push("wholerow"); 8718 if(window.customElements && Object && Object.create) { 8719 var proto = Object.create(HTMLElement.prototype); 8720 proto.createdCallback = function () { 8721 var c = { core : {}, plugins : [] }, i; 8722 for(i in $.jstree.plugins) { 8723 if($.jstree.plugins.hasOwnProperty(i) && this.attributes[i]) { 8724 c.plugins.push(i); 8725 if(this.getAttribute(i) && JSON.parse(this.getAttribute(i))) { 8726 c[i] = JSON.parse(this.getAttribute(i)); 8727 } 8728 } 8729 } 8730 for(i in $.jstree.defaults.core) { 8731 if($.jstree.defaults.core.hasOwnProperty(i) && this.attributes[i]) { 8732 c.core[i] = JSON.parse(this.getAttribute(i)) || this.getAttribute(i); 8733 } 8734 } 8735 $(this).jstree(c); 8736 }; 8737 // proto.attributeChangedCallback = function (name, previous, value) { }; 8738 try { 8739 window.customElements.define("vakata-jstree", function() {}, { prototype: proto }); 8740 } catch (ignore) { } 8741 } 8742 8743}));
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.