1/*! **************************************************************************** 2 jquery.dynatree.js 3 Dynamic tree view control, with support for lazy loading of branches. 4 5 Copyright (c) 2006-2013, Martin Wendt (http://wwWendt.de) 6 Dual licensed under the MIT or GPL Version 2 licenses. 7 http://code.google.com/p/dynatree/wiki/LicenseInfo 8 9 A current version and some documentation is available at 10 http://dynatree.googlecode.com/ 11 12 @version: 1.2.5 13 @date: 2013-11-19T07:42 14 15 @depends: jquery.js 16 @depends: jquery.ui.core.js 17 @depends: jquery.cookie.js 18*******************************************************************************/ 19 20/* jsHint options*/ 21// Note: We currently allow eval() to parse the 'data' attribtes, when initializing from HTML. 22// TODO: pass jsHint with the options given in grunt.js only. 23// The following should not be required: 24/*global alert */ 25/*jshint nomen:false, smarttabs:true, eqeqeq:false, evil:true, regexp:false */ 26 27/************************************************************************* 28 * Debug functions 29 */ 30 31var _canLog = true; 32 33function _log(mode, msg) { 34 /** 35 * Usage: logMsg("%o was toggled", this); 36 */ 37 if( !_canLog ){ 38 return; 39 } 40 // Remove first argument 41 var args = Array.prototype.slice.apply(arguments, [1]); 42 // Prepend timestamp 43 var dt = new Date(); 44 var tag = dt.getHours() + ":" + dt.getMinutes() + ":" + 45 dt.getSeconds() + "." + dt.getMilliseconds(); 46 args[0] = tag + " - " + args[0]; 47 48 try { 49 switch( mode ) { 50 case "info": 51 window.console.info.apply(window.console, args); 52 break; 53 case "warn": 54 window.console.warn.apply(window.console, args); 55 break; 56 default: 57 window.console.log.apply(window.console, args); 58 break; 59 } 60 } catch(e) { 61 if( !window.console ){ 62 _canLog = false; // Permanently disable, when logging is not supported by the browser 63 }else if(e.number === -2146827850){ 64 // fix for IE8, where window.console.log() exists, but does not support .apply() 65 window.console.log(args.join(", ")); 66 } 67 } 68} 69 70 71function logMsg(msg) { 72 Array.prototype.unshift.apply(arguments, ["debug"]); 73 _log.apply(this, arguments); 74} 75 76 77// Forward declaration 78var getDynaTreePersistData = null; 79 80 81 82/************************************************************************* 83 * Constants 84 */ 85var DTNodeStatus_Error = -1; 86var DTNodeStatus_Loading = 1; 87var DTNodeStatus_Ok = 0; 88 89 90// Start of local namespace 91(function($) { 92 93/************************************************************************* 94 * Common tool functions. 95 */ 96 97var Class = { 98 create: function() { 99 return function() { 100 this.initialize.apply(this, arguments); 101 }; 102 } 103}; 104 105// Tool function to get dtnode from the event target: 106function getDtNodeFromElement(el) { 107 alert("getDtNodeFromElement is deprecated"); 108 return $.ui.dynatree.getNode(el); 109/* 110 var iMax = 5; 111 while( el && iMax-- ) { 112 if(el.dtnode) { return el.dtnode; } 113 el = el.parentNode; 114 } 115 return null; 116*/ 117} 118 119function noop() { 120} 121 122 123/* Convert number to string and prepend +/-; return empty string for 0.*/ 124function offsetString(n){ 125 return n === 0 ? "" : (( n > 0 ) ? ("+" + n) : ("" + n)); 126} 127 128 129/* Check browser version, since $.browser was removed in jQuery 1.9 */ 130function _checkBrowser(){ 131 var matched, browser; 132 function uaMatch( ua ) { 133 ua = ua.toLowerCase(); 134 var match = /(chrome)[ \/]([\w.]+)/.exec( ua ) || 135 /(webkit)[ \/]([\w.]+)/.exec( ua ) || 136 /(opera)(?:.*version|)[ \/]([\w.]+)/.exec( ua ) || 137 /(msie) ([\w.]+)/.exec( ua ) || 138 ua.indexOf("compatible") < 0 && /(mozilla)(?:.*? rv:([\w.]+)|)/.exec( ua ) || 139 []; 140 return { 141 browser: match[ 1 ] || "", 142 version: match[ 2 ] || "0" 143 }; 144 } 145 matched = uaMatch( navigator.userAgent ); 146 browser = {}; 147 if ( matched.browser ) { 148 browser[ matched.browser ] = true; 149 browser.version = matched.version; 150 } 151 if ( browser.chrome ) { 152 browser.webkit = true; 153 } else if ( browser.webkit ) { 154 browser.safari = true; 155 } 156 return browser; 157} 158 159 160/** Compare two dotted version strings (like '10.2.3'). 161 * @returns {Integer} 0: v1 == v2, -1: v1 < v2, 1: v1 > v2 162 */ 163function versionCompare(v1, v2) { 164 var v1parts = ("" + v1).split("."), 165 v2parts = ("" + v2).split("."), 166 minLength = Math.min(v1parts.length, v2parts.length), 167 p1, p2, i;
168 // Compare tuple pair-by-pair. 169 for(i = 0; i < minLength; i++) { 170 // Convert to integer if possible, because "8" > "10". 171 p1 = parseInt(v1parts[i], 10); 172 p2 = parseInt(v2parts[i], 10); 173 if (isNaN(p1)){ p1 = v1parts[i]; } 174 if (isNaN(p2)){ p2 = v2parts[i]; } 175 if (p1 == p2) { 176 continue; 177 }else if (p1 > p2) { 178 return 1; 179 }else if (p1 < p2) { 180 return -1; 181 } 182 // one operand is NaN 183 return NaN; 184 } 185 // The longer tuple is always considered 'greater' 186 if (v1parts.length === v2parts.length) { 187 return 0; 188 } 189 return (v1parts.length < v2parts.length) ? -1 : 1; 190} 191 192 193//var BROWSER = jQuery.browser || _checkBrowser(); 194var BROWSER = _checkBrowser(); // issue 440 195var jquerySupports = { 196 // http://jqueryui.com/upgrade-guide/1.9/#deprecated-offset-option-merged-into-my-and-at 197 positionMyOfs: versionCompare($.ui.version, "1.9") >= 0 //isVersionAtLeast($.ui.version, 1, 9) 198 }; 199 200 201/************************************************************************* 202 * Class DynaTreeNode 203 */ 204var DynaTreeNode = Class.create(); 205 206DynaTreeNode.prototype = { 207 initialize: function(parent, tree, data) { 208 /** 209 * @constructor 210 */ 211 this.parent = parent; 212 this.tree = tree; 213 if ( typeof data === "string" ){ 214 data = { title: data }; 215 } 216// if( !data.key ){ 217 if( data.key == null ){ // test for null OR undefined (issue 420) 218 data.key = "_" + tree._nodeCount++; 219 }else{ 220 data.key = "" + data.key; // issue 371 221 } 222 this.data = $.extend({}, $.ui.dynatree.nodedatadefaults, data); 223 this.li = null; // not yet created 224 this.span = null; // not yet created 225 this.ul = null; // not yet created 226 this.childList = null; // no subnodes yet 227 this._isLoading = false; // Lazy content is being loaded 228 this.hasSubSel = false; 229 this.bExpanded = false; 230 this.bSelected = false; 231 232 }, 233 234 toString: function() { 235 return "DynaTreeNode<" + this.data.key + ">: '" + this.data.title + "'"; 236 }, 237 238 toDict: function(recursive, callback) { 239 var node, 240 dict = $.extend({}, this.data); 241 dict.activate = ( this.tree.activeNode === this ); 242 dict.focus = ( this.tree.focusNode === this ); 243 dict.expand = this.bExpanded; 244 dict.select = this.bSelected; 245 if( callback ){ 246 callback(dict); 247 } 248 if( recursive && this.childList ) { 249 dict.children = []; 250 for(var i=0, l=this.childList.length; i<l; i++ ){ 251 node = this.childList[i]; 252 if( !node.isStatusNode() ){ 253 dict.children.push(node.toDict(true, callback)); 254 } 255 } 256 } else { 257 delete dict.children; 258 } 259 return dict; 260 }, 261 262 fromDict: function(dict) { 263 /** 264 * Update node data. If dict contains 'children', then also replace 265 * the hole sub tree. 266 */ 267 var children = dict.children; 268 if(children === undefined){ 269 this.data = $.extend(this.data, dict); 270 this.render(); 271 return; 272 } 273 dict = $.extend({}, dict); 274 dict.children = undefined; 275 this.data = $.extend(this.data, dict); 276 this.removeChildren(); 277 this.addChild(children); 278 }, 279 280 _getInnerHtml: function() { 281 var tree = this.tree, 282 opts = tree.options, 283 cache = tree.cache, 284 level = this.getLevel(), 285 data = this.data, 286 res = "", 287 imageSrc; 288 // connector (expanded, expandable or simple) 289 if( level < opts.minExpandLevel ) { 290 if(level > 1){ 291 res += cache.tagConnector; 292 } 293 // .. else (i.e. for root level) skip expander/connector altogether 294 } else if( this.hasChildren() !== false ) { 295 res += cache.tagExpander; 296 } else { 297 res += cache.tagConnector; 298 } 299 // Checkbox mode 300 if( opts.checkbox && data.hideCheckbox !== true && !data.isStatusNode ) { 301 res += cache.tagCheckbox; 302 } 303 // folder or doctype icon 304 if ( data.icon ) { 305 if (data.icon.charAt(0) === "/"){ 306 imageSrc = data.icon; 307 }else{ 308 imageSrc = opts.imagePath + data.icon; 309 } 310 res += "<img src='" + imageSrc + "' alt='' />"; 311 } else if ( data.icon === false ) { 312 // icon == false means 'no icon' 313// noop(); // keep JSLint happy 314 } else if ( data.iconClass ) { 315 res += "<span class='" + " " + data.iconClass + "'></span>"; 316 } else { 317 // icon == null means 'default icon' 318 res += cache.tagNodeIcon; 319 } 320 // node title 321 var nodeTitle = ""; 322 if ( opts.onCustomRender ){ 323 nodeTitle = opts.onCustomRender.call(tree, this) || ""; 324 } 325 if(!nodeTitle){ 326 var tooltip = data.tooltip ? ' title="' + data.tooltip.replace(/\"/g, '"') + '"' : '', 327 href = data.href || "#"; 328 if( opts.noLink || data.noLink ) { 329 nodeTitle = '<span style="display:inline-block;" class="' + opts.classNames.title + '"' + tooltip + '>' + data.title + '</span>'; 330// this.tree.logDebug("nodeTitle: " + nodeTitle); 331 } else { 332 nodeTitle = '<a href="' + href + '" class="' + opts.classNames.title + '"' + tooltip + '>' + data.title + '</a>'; 333 } 334 } 335 res += nodeTitle; 336 return res; 337 }, 338 339 340 _fixOrder: function() { 341 /** 342 * Make sure, that <li> order matches childList order. 343 */ 344 var cl = this.childList; 345 if( !cl || !this.ul ){ 346 return; 347 } 348 var childLI = this.ul.firstChild; 349 for(var i=0, l=cl.length-1; i<l; i++) { 350 var childNode1 = cl[i]; 351 var childNode2 = childLI.dtnode; 352 if( childNode1 !== childNode2 ) { 353 this.tree.logDebug("_fixOrder: mismatch at index " + i + ": " + childNode1 + " != " + childNode2); 354 this.ul.insertBefore(childNode1.li, childNode2.li); 355 } else { 356 childLI = childLI.nextSibling; 357 } 358 } 359 }, 360 361 362 render: function(useEffects, includeInvisible) { 363 /** 364 * Create <li><span>..</span> .. </li> tags for this node. 365 * 366 * <li id='KEY' dtnode=NODE> // This div contains the node's span and list of child div's. 367 * <span class='title'>S S S A</span>
367 // Span contains graphic spans and title <a> tag 368 * <ul> // only present, when node has children 369 * <li id='KEY' dtnode=NODE>child1</li> 370 * <li id='KEY' dtnode=NODE>child2</li> 371 * </ul> 372 * </li> 373 */ 374// this.tree.logDebug("%s.render(%s)", this, useEffects); 375 // --- 376 var tree = this.tree, 377 parent = this.parent, 378 data = this.data, 379 opts = tree.options, 380 cn = opts.classNames, 381 isLastSib = this.isLastSibling(), 382 firstTime = false; 383 384 if( !parent && !this.ul ) { 385 // Root node has only a <ul> 386 this.li = this.span = null; 387 this.ul = document.createElement("ul"); 388 if( opts.minExpandLevel > 1 ){ 389 this.ul.className = cn.container + " " + cn.noConnector; 390 }else{ 391 this.ul.className = cn.container; 392 } 393 } else if( parent ) { 394 // Create <li><span /> </li> 395 if( ! this.li ) { 396 firstTime = true; 397 this.li = document.createElement("li"); 398 this.li.dtnode = this; 399 if( data.key && opts.generateIds ){ 400 this.li.id = opts.idPrefix + data.key; 401 } 402 this.span = document.createElement("span"); 403 this.span.className = cn.title; 404 this.li.appendChild(this.span); 405 406 if( !parent.ul ) { 407 // This is the parent's first child: create UL tag 408 // (Hidden, because it will be 409 parent.ul = document.createElement("ul"); 410 parent.ul.style.display = "none"; 411 parent.li.appendChild(parent.ul); 412// if( opts.minExpandLevel > this.getLevel() ){ 413// parent.ul.className = cn.noConnector; 414// } 415 } 416 // set node connector images, links and text 417// this.span.innerHTML = this._getInnerHtml(); 418 419 parent.ul.appendChild(this.li); 420 } 421 // set node connector images, links and text 422 this.span.innerHTML = this._getInnerHtml(); 423 // Set classes for current status 424 var cnList = []; 425 cnList.push(cn.node); 426 if( data.isFolder ){ 427 cnList.push(cn.folder); 428 } 429 if( this.bExpanded ){ 430 cnList.push(cn.expanded); 431 } 432 if( this.hasChildren() !== false ){ 433 cnList.push(cn.hasChildren); 434 } 435 if( data.isLazy && this.childList === null ){ 436 cnList.push(cn.lazy); 437 } 438 if( isLastSib ){ 439 cnList.push(cn.lastsib); 440 } 441 if( this.bSelected ){ 442 cnList.push(cn.selected); 443 } 444 if( this.hasSubSel ){ 445 cnList.push(cn.partsel); 446 } 447 if( tree.activeNode === this ){ 448 cnList.push(cn.active); 449 } 450 if( data.addClass ){ 451 cnList.push(data.addClass); 452 } 453 // IE6 doesn't correctly evaluate multiple class names, 454 // so we create combined class names that can be used in the CSS 455 cnList.push(cn.combinedExpanderPrefix 456 + (this.bExpanded ? "e" : "c") 457 + (data.isLazy && this.childList === null ? "d" : "") 458 + (isLastSib ? "l" : "") 459 ); 460 cnList.push(cn.combinedIconPrefix 461 + (this.bExpanded ? "e" : "c") 462 + (data.isFolder ? "f" : "") 463 ); 464 this.span.className = cnList.join(" "); 465 466 // TODO: we should not set this in the <span> tag also, if we set it here: 467 this.li.className = isLastSib ? cn.lastsib : ""; 468 469 // Allow tweaking, binding, after node was created for the first time 470 if(firstTime && opts.onCreate){ 471 opts.onCreate.call(tree, this, this.span); 472 } 473 // Hide children, if node is collapsed 474// this.ul.style.display = ( this.bExpanded || !parent ) ? "" : "none"; 475 // Allow tweaking after node state was rendered 476 if(opts.onRender){ 477 opts.onRender.call(tree, this, this.span); 478 } 479 } 480 // Visit child nodes 481 if( (this.bExpanded || includeInvisible === true) && this.childList ) { 482 for(var i=0, l=this.childList.length; i<l; i++) { 483 this.childList[i].render(false, includeInvisible); 484 } 485 // Make sure the tag order matches the child array 486 this._fixOrder(); 487 } 488 // Hide children, if node is collapsed 489 if( this.ul ) { 490 var isHidden = (this.ul.style.display === "none"); 491 var isExpanded = !!this.bExpanded; 492// logMsg("isHidden:%s", isHidden); 493 if( useEffects && opts.fx && (isHidden === isExpanded) ) { 494 var duration = opts.fx.duration || 200; 495 $(this.ul).animate(opts.fx, duration); 496 } else { 497 this.ul.style.display = ( this.bExpanded || !parent ) ? "" : "none"; 498 } 499 } 500 }, 501 /** Return '/id1/id2/id3'. */ 502 getKeyPath: function(excludeSelf) { 503 var path = []; 504 this.visitParents(function(node){ 505 if(node.parent){ 506 path.unshift(node.data.key); 507 } 508 }, !excludeSelf); 509 return "/" + path.join(this.tree.options.keyPathSeparator); 510 }, 511 512 getParent: function() { 513 return this.parent; 514 }, 515 516 getChildren: function() { 517 if(this.hasChildren() === undefined){ 518 return undefined; // Lazy node: unloaded, currently loading, or load error 519 } 520 return this.childList; 521 }, 522 523 /** Check if node has children (returns undefined, if not sure). */ 524 hasChildren: function() { 525 if(this.data.isLazy){ 526 if(this.childList === null || this.childList === undefined){ 527 // Not yet loaded 528 return undefined; 529 }else if(this.childList.length === 0){ 530 // Loaded, but response was empty 531 return false; 532 }else if(this.childList.length === 1 && this.childList[0].isStatusNode()){ 533 // Currently loading or load error 534 return undefined; 535 } 536 return true; 537 } 538 return !!this.childList; 539 }, 540 541 isFirstSibling: function() { 542 var p = this.parent; 543 return !p || p.childList[0] === this; 544 }, 545 546 isLastSibling: function() { 547 var p = this.parent; 548 return !p || p.childList[p.childList.length-1] === this; 549 }, 550 551 isLoading: function() { 552 return !!this._isLoading; 553 }, 554 555 getPrevSibling: function() { 556 if( !this.parent ){ 557 return null; 558 } 559 var ac = this.parent.childList; 560 for(var i=1, l=ac.length; i<l; i++){ // start with 1, so prev(first) = null 561 if( ac[i] === this ){ 562 return ac[i-1]; 563 } 564 } 565 return null; 566 }, 567 568 getNextSibling: function() { 569 if( !this.parent ){ 570 return null; 571 } 572 var ac = this.parent.childList; 573 for(var i=0, l=ac.length-1; i<l; i++){ // up to length-2, so next(last) = null 574 if( ac[i] === this ){ 575 return ac[i+1]; 576 } 577 } 578 return null; 579 }, 580 581 isStatusNode: function() { 582 return (this.data.isStatusNode === true); 583 }, 584 585 isChildOf: function(otherNode) { 586 return (this.parent && this.parent === otherNode); 587 }, 588 589 isDescendantOf: function(otherNode) { 590 if(!otherNode){ 591 return false; 592 } 593 var p = this.parent; 594 while( p ) { 595 if( p === otherNode ){ 596 return true; 597 } 598 p = p.parent; 599 } 600 return false; 601 }, 602 603 countChildren: function() { 604 var cl = this.childList; 605 if( !cl ){ 606 return 0; 607 } 608 var n = cl.length; 609 for(var i=0, l=n; i<l; i++){ 610 var child = cl[i]; 611 n += child.countChildren();
612 } 613 return n; 614 }, 615 616 /**Sort child list by title. 617 * cmd: optional compare function. 618 * deep: optional: pass true to sort all descendant nodes. 619 */ 620 sortChildren: function(cmp, deep) { 621 var cl = this.childList; 622 if( !cl ){ 623 return; 624 } 625 cmp = cmp || function(a, b) { 626// return a.data.title === b.data.title ? 0 : a.data.title > b.data.title ? 1 : -1; 627 var x = a.data.title.toLowerCase(), 628 y = b.data.title.toLowerCase(); 629 return x === y ? 0 : x > y ? 1 : -1; 630 }; 631 cl.sort(cmp); 632 if( deep ){ 633 for(var i=0, l=cl.length; i<l; i++){ 634 if( cl[i].childList ){ 635 cl[i].sortChildren(cmp, "$norender$"); 636 } 637 } 638 } 639 if( deep !== "$norender$" ){ 640 this.render(); 641 } 642 }, 643 644 _setStatusNode: function(data) { 645 // Create, modify or remove the status child node (pass 'null', to remove it). 646 var firstChild = ( this.childList ? this.childList[0] : null ); 647 if( !data ) { 648 if ( firstChild && firstChild.isStatusNode()) { 649 try{ 650 // I've seen exceptions here with loadKeyPath... 651 if(this.ul){ 652 this.ul.removeChild(firstChild.li); 653 firstChild.li = null; // avoid leaks (issue 215) 654 } 655 }catch(e){} 656 if( this.childList.length === 1 ){ 657 this.childList = []; 658 }else{ 659 this.childList.shift(); 660 } 661 } 662 } else if ( firstChild ) { 663 data.isStatusNode = true; 664 data.key = "_statusNode"; 665 firstChild.data = data; 666 firstChild.render(); 667 } else { 668 data.isStatusNode = true; 669 data.key = "_statusNode"; 670 firstChild = this.addChild(data); 671 } 672 }, 673 674 setLazyNodeStatus: function(lts, opts) { 675 var tooltip = (opts && opts.tooltip) ? opts.tooltip : null, 676 info = (opts && opts.info) ? " (" + opts.info + ")" : ""; 677 switch( lts ) { 678 case DTNodeStatus_Ok: 679 this._setStatusNode(null); 680 $(this.span).removeClass(this.tree.options.classNames.nodeLoading); 681 this._isLoading = false; 682// this.render(); 683 if( this.tree.options.autoFocus ) { 684 if( this === this.tree.tnRoot && this.childList && this.childList.length > 0) { 685 // special case: using ajaxInit 686 this.childList[0].focus(); 687 } else { 688 this.focus(); 689 } 690 } 691 break; 692 case DTNodeStatus_Loading: 693 this._isLoading = true; 694 $(this.span).addClass(this.tree.options.classNames.nodeLoading); 695 // The root is hidden, so we set a temporary status child 696 if(!this.parent){ 697 this._setStatusNode({ 698 title: this.tree.options.strings.loading + info, 699 tooltip: tooltip, 700 addClass: this.tree.options.classNames.nodeWait 701 }); 702 } 703 break; 704 case DTNodeStatus_Error: 705 this._isLoading = false; 706// $(this.span).addClass(this.tree.options.classNames.nodeError); 707 this._setStatusNode({ 708 title: this.tree.options.strings.loadError + info, 709 tooltip: tooltip, 710 addClass: this.tree.options.classNames.nodeError 711 }); 712 break; 713 default: 714 throw "Bad LazyNodeStatus: '" + lts + "'."; 715 } 716 }, 717 718 _parentList: function(includeRoot, includeSelf) { 719 var l = []; 720 var dtn = includeSelf ? this : this.parent; 721 while( dtn ) { 722 if( includeRoot || dtn.parent ){ 723 l.unshift(dtn); 724 } 725 dtn = dtn.parent; 726 } 727 return l; 728 }, 729 getLevel: function() { 730 /** 731 * Return node depth. 0: System root node, 1: visible top-level node. 732 */ 733 var level = 0; 734 var dtn = this.parent; 735 while( dtn ) { 736 level++; 737 dtn = dtn.parent; 738 } 739 return level; 740 }, 741 742 _getTypeForOuterNodeEvent: function(event) { 743 /** Return the inner node span (title, checkbox or expander) if 744 * event.target points to the outer span. 745 * This function should fix issue #93: 746 * FF2 ignores empty spans, when generating events (returning the parent instead). 747 */ 748 var cns = this.tree.options.classNames; 749 var target = event.target; 750 // Only process clicks on an outer node span (probably due to a FF2 event handling bug) 751 if( target.className.indexOf(cns.node) < 0 ) { 752 return null; 753 } 754 // Event coordinates, relative to outer node span: 755 var eventX = event.pageX - target.offsetLeft; 756 var eventY = event.pageY - target.offsetTop; 757 758 for(var i=0, l=target.childNodes.length; i<l; i++) { 759 var cn = target.childNodes[i];
760 var x = cn.offsetLeft - target.offsetLeft; 761 var y = cn.offsetTop - target.offsetTop; 762 var nx = cn.clientWidth, ny = cn.clientHeight; 763// alert (cn.className + ": " + x + ", " + y + ", s:" + nx + ", " + ny); 764 if( eventX >= x && eventX <= (x+nx) && eventY >= y && eventY <= (y+ny) ) { 765// alert("HIT "+ cn.className); 766 if( cn.className==cns.title ){ 767 return "title"; 768 }else if( cn.className==cns.expander ){ 769 return "expander"; 770 }else if( cn.className==cns.checkbox ){ 771 return "checkbox"; 772 }else if( cn.className==cns.nodeIcon ){ 773 return "icon"; 774 } 775 } 776 } 777 return "prefix"; 778 }, 779 780 getEventTargetType: function(event) { 781 // Return the part of a node, that a click event occured on. 782 // Note: there is no check, if the event was fired on THIS node. 783 var tcn = event && event.target ? event.target.className : "", 784 cns = this.tree.options.classNames; 785 786 if( tcn.indexOf(cns.title) >= 0 ){ 787 return "title"; 788 }else if( tcn.indexOf(cns.expander) >= 0 ){ 789 return "expander"; 790 }else if( tcn.indexOf(cns.checkbox) >= 0 ){ 791 return "checkbox"; 792 }else if( tcn.indexOf(cns.nodeIcon) >= 0 ){ 793 return "icon"; 794 }else if( tcn.indexOf(cns.empty) >= 0 || tcn.indexOf(cns.vline) >= 0 || tcn.indexOf(cns.connector) >= 0 ){ 795 return "prefix"; 796 }else if( tcn.indexOf(cns.node) >= 0 ){ 797 // FIX issue #93 798 return this._getTypeForOuterNodeEvent(event); 799 } 800 return null; 801 }, 802 803 isVisible: function() { 804 // Return true, if all parents are expanded. 805 var parents = this._parentList(true, false); 806 for(var i=0, l=parents.length; i<l; i++){ 807 if( ! parents[i].bExpanded ){ return false; } 808 } 809 return true; 810 }, 811 812 makeVisible: function() { 813 // Make sure, all parents are expanded 814 var parents = this._parentList(true, false); 815 for(var i=0, l=parents.length; i<l; i++){ 816 parents[i]._expand(true); 817 } 818 }, 819 820 focus: function() { 821 // TODO: check, if we already have focus 822// this.tree.logDebug("dtnode.focus(): %o", this); 823 this.makeVisible(); 824 try { 825 $(this.span).find(">a").focus(); 826 } catch(e) { } 827 }, 828 829 isFocused: function() { 830 return (this.tree.tnFocused === this); 831 }, 832 833 _activate: function(flag, fireEvents) { 834 // (De)Activate - but not focus - this node. 835 this.tree.logDebug("dtnode._activate(%o, fireEvents=%o) - %o", flag, fireEvents, this); 836 var opts = this.tree.options; 837 if( this.data.isStatusNode ){ 838 return; 839 } 840 if ( fireEvents && opts.onQueryActivate && opts.onQueryActivate.call(this.tree, flag, this) === false ){ 841 return; // Callback returned false 842 } 843 if( flag ) { 844 // Activate 845 if( this.tree.activeNode ) { 846 if( this.tree.activeNode === this ){ 847 return; 848 } 849 this.tree.activeNode.deactivate(); 850 } 851 if( opts.activeVisible ){ 852 this.makeVisible(); 853 } 854 this.tree.activeNode = this; 855 if( opts.persist ){ 856 $.cookie(opts.cookieId + "-active", this.data.key, opts.cookie); 857 } 858 this.tree.persistence.activeKey = this.data.key; 859 $(this.span).addClass(opts.classNames.active); 860 if ( fireEvents && opts.onActivate ){ 861 opts.onActivate.call(this.tree, this); 862 } 863 } else { 864 // Deactivate 865 if( this.tree.activeNode === this ) { 866 if ( opts.onQueryActivate && opts.onQueryActivate.call(this.tree, false, this) === false ){ 867 return; // Callback returned false 868 } 869 $(this.span).removeClass(opts.classNames.active); 870 if( opts.persist ) { 871 // Note: we don't pass null, but ''. So the cookie is not deleted. 872 // If we pass null, we also have to pass a COPY of opts, because $cookie will override opts.expires (issue 84) 873 $.cookie(opts.cookieId + "-active", "", opts.cookie); 874 } 875 this.tree.persistence.activeKey = null; 876 this.tree.activeNode = null; 877 if ( fireEvents && opts.onDeactivate ){ 878 opts.onDeactivate.call(this.tree, this); 879 } 880 } 881 } 882 }, 883 884 activate: function() { 885 // Select - but not focus - this node. 886// this.tree.logDebug("dtnode.activate(): %o", this); 887 this._activate(true, true); 888 }, 889 890 activateSilently: function() { 891 this._activate(true, false); 892 }, 893 894 deactivate: function() { 895// this.tree.logDebug("dtnode.deactivate(): %o", this); 896 this._activate(false, true); 897 }, 898 899 isActive: function() { 900 return (this.tree.activeNode === this); 901 }, 902 903 _userActivate: function() { 904 // Handle user click / [space] / [enter], according to clickFolderMode. 905 var activate = true; 906 var expand = false; 907 if ( this.data.isFolder ) { 908 switch( this.tree.options.clickFolderMode ) { 909 case 2: 910 activate = false; 911 expand = true; 912 break; 913 case 3: 914 activate = expand = true; 915 break; 916 } 917 } 918 if( this.parent === null ) { 919 expand = false; 920 } 921 if( expand ) { 922 this.toggleExpand(); 923 this.focus(); 924 } 925 if( activate ) { 926 this.activate(); 927 } 928 }, 929 930 _setSubSel: function(hasSubSel) { 931 if( hasSubSel ) { 932 this.hasSubSel = true; 933 $(this.span).addClass(this.tree.options.classNames.partsel); 934 } else { 935 this.hasSubSel = false; 936 $(this.span).removeClass(this.tree.options.classNames.partsel); 937 } 938 }, 939 /** 940 * Fix selection and partsel status, of parent nodes, according to current status of 941 * end nodes. 942 */ 943 _updatePartSelectionState: function() { 944// alert("_updatePartSelectionState " + this); 945// this.tree.logDebug("_updatePartSelectionState() - %o", this); 946 var sel; 947 // Return `true` or `false` for end nodes and remove part-sel flag 948 if( ! this.hasChildren() ){ 949 sel = (this.bSelected && !this.data.unselectable && !this.data.isStatusNode); 950 this._setSubSel(false); 951 return sel; 952 } 953 // Return `true`, `false`, or `undefined` for parent nodes 954 var i, l, 955 cl = this.childList, 956 allSelected = true, 957 allDeselected = true; 958 for(i=0, l=cl.length; i<l; i++) { 959 var n = cl[i], 960 s = n._updatePartSelectionState(); 961 if( s !== false){ 962 allDeselected = false; 963 } 964 if( s !== true){ 965 allSelected = false; 966 } 967 } 968 if( allSelected ){ 969 sel = true; 970 } else if ( allDeselected ){ 971 sel = false; 972 } else { 973 sel = undefined; 974 } 975 this._setSubSel(sel === undefined); 976 this.bSelected = (sel === true); 977 return sel; 978 }, 979 980 /** 981 * Fix selection status, after this node was (de)selected in multi-hier mode. 982 * This includes (de)selecting all children. 983 */ 984 _fixSelectionState: function() { 985// alert("_fixSelectionState " + this); 986// this.tree.logDebug("_fixSelectionState(%s) - %o", this.bSelected, this); 987 var p, i, l; 988 if( this.bSelected ) { 989 // Select all children 990 this.visit(function(node){ 991 node.parent._setSubSel(true); 992 if(!node.data.unselectable){ 993 node._select(true, false, false); 994 } 995 }); 996 // Select parents, if all children are selected 997 p = this.parent; 998 while( p ) { 999 p._setSubSel(true); 1000 var allChildsSelected = true; 1001 for(i=0, l=p.childList.length; i<l; i++) { 1002 var n = p.childList[i]; 1003 if( !n.bSelected && !n.data.isStatusNode && !n.data.unselectable) { 1004 // issue 305 proposes this: 1005// if( !n.bSelected && !n.data.isStatusNode ) { 1006 allChildsSelected = false; 1007 break; 1008 } 1009 } 1010 if( allChildsSelected ){ 1011 p._select(true, false, false); 1012 } 1013 p = p.parent; 1014 } 1015 } else { 1016 // Deselect all children 1017 this._setSubSel(false); 1018 this.visit(function(node){ 1019 node._setSubSel(false); 1020 node._select(false, false, false); 1021 }); 1022 // Deselect parents, and recalc hasSubSel 1023 p = this.parent; 1024 while( p ) { 1025 p._select(false, false, false); 1026 var isPartSel = false; 1027 for(i=0, l=p.childList.length; i<l; i++) { 1028 if( p.childList[i].bSelected || p.childList[i].hasSubSel ) { 1029 isPartSel = true; 1030 break; 1031 } 1032 } 1033 p._setSubSel(isPartSel); 1034 p = p.parent; 1035 } 1036 } 1037 }, 1038 1039 _select: function(sel, fireEvents, deep) { 1040 // Select - but not focus - this node.
1041// this.tree.logDebug("dtnode._select(%o) - %o", sel, this); 1042 var opts = this.tree.options; 1043 if( this.data.isStatusNode ){ 1044 return; 1045 } 1046 // 1047 if( this.bSelected === sel ) { 1048// this.tree.logDebug("dtnode._select(%o) IGNORED - %o", sel, this); 1049 return; 1050 } 1051 // Allow event listener to abort selection 1052 if ( fireEvents && opts.onQuerySelect && opts.onQuerySelect.call(this.tree, sel, this) === false ){ 1053 return; // Callback returned false 1054 } 1055 // Force single-selection 1056 if( opts.selectMode==1 && sel ) { 1057 this.tree.visit(function(node){ 1058 if( node.bSelected ) { 1059 // Deselect; assuming that in selectMode:1 there's max. one other selected node 1060 node._select(false, false, false); 1061 return false; 1062 } 1063 }); 1064 } 1065 1066 this.bSelected = sel; 1067// this.tree._changeNodeList("select", this, sel); 1068 1069 if( sel ) { 1070 if( opts.persist ){ 1071 this.tree.persistence.addSelect(this.data.key); 1072 } 1073 $(this.span).addClass(opts.classNames.selected); 1074 1075 if( deep && opts.selectMode === 3 ){ 1076 this._fixSelectionState(); 1077 } 1078 if ( fireEvents && opts.onSelect ){ 1079 opts.onSelect.call(this.tree, true, this); 1080 } 1081 } else { 1082 if( opts.persist ){ 1083 this.tree.persistence.clearSelect(this.data.key); 1084 } 1085 $(this.span).removeClass(opts.classNames.selected); 1086 1087 if( deep && opts.selectMode === 3 ){ 1088 this._fixSelectionState(); 1089 } 1090 if ( fireEvents && opts.onSelect ){ 1091 opts.onSelect.call(this.tree, false, this); 1092 } 1093 } 1094 }, 1095 1096 select: function(sel) { 1097 // Select - but not focus - this node. 1098// this.tree.logDebug("dtnode.select(%o) - %o", sel, this); 1099 if( this.data.unselectable ){ 1100 return this.bSelected; 1101 } 1102 return this._select(sel!==false, true, true); 1103 }, 1104 1105 toggleSelect: function() { 1106// this.tree.logDebug("dtnode.toggleSelect() - %o", this); 1107 return this.select(!this.bSelected); 1108 }, 1109 1110 isSelected: function() { 1111 return this.bSelected; 1112 }, 1113 1114 isLazy: function() { 1115 return !!this.data.isLazy; 1116 }, 1117 1118 _loadContent: function() { 1119 try { 1120 var opts = this.tree.options; 1121 this.tree.logDebug("_loadContent: start - %o", this); 1122 this.setLazyNodeStatus(DTNodeStatus_Loading); 1123 if( true === opts.onLazyRead.call(this.tree, this) ) { 1124 // If function returns 'true', we assume that the loading is done: 1125 this.setLazyNodeStatus(DTNodeStatus_Ok); 1126 // Otherwise (i.e. if the loading was started as an asynchronous process) 1127 // the onLazyRead(dtnode) handler is expected to call dtnode.setLazyNodeStatus(DTNodeStatus_Ok/_Error) when done. 1128 this.tree.logDebug("_loadContent: succeeded - %o", this); 1129 } 1130 } catch(e) { 1131 this.tree.logWarning("_loadContent: failed - %o", e); 1132 this.setLazyNodeStatus(DTNodeStatus_Error, {tooltip: ""+e}); 1133 } 1134 }, 1135 1136 _expand: function(bExpand, forceSync) { 1137 if( this.bExpanded === bExpand ) { 1138 this.tree.logDebug("dtnode._expand(%o) IGNORED - %o", bExpand, this); 1139 return; 1140 } 1141 this.tree.logDebug("dtnode._expand(%o) - %o", bExpand, this); 1142 var opts = this.tree.options; 1143 if( !bExpand && this.getLevel() < opts.minExpandLevel ) { 1144 this.tree.logDebug("dtnode._expand(%o) prevented collapse - %o", bExpand, this); 1145 return; 1146 } 1147 if ( opts.onQueryExpand && opts.onQueryExpand.call(this.tree, bExpand, this) === false ){ 1148 return; // Callback returned false 1149 } 1150 this.bExpanded = bExpand; 1151 1152 // Persist expand state 1153 if( opts.persist ) { 1154 if( bExpand ){ 1155 this.tree.persistence.addExpand(this.data.key); 1156 }else{ 1157 this.tree.persistence.clearExpand(this.data.key); 1158 } 1159 } 1160 // Do not apply animations in init phase, or before lazy-loading 1161 var allowEffects = !(this.data.isLazy && this.childList === null) 1162 && !this._isLoading 1163 && !forceSync; 1164 this.render(allowEffects); 1165 1166 // Auto-collapse mode: collapse all siblings 1167 if( this.bExpanded && this.parent && opts.autoCollapse ) { 1168 var parents = this._parentList(false, true); 1169 for(var i=0, l=parents.length; i<l; i++){ 1170 parents[i].collapseSiblings(); 1171 } 1172 } 1173 // If the currently active node is now hidden, deactivate it 1174 if( opts.activeVisible && this.tree.activeNode && ! this.tree.activeNode.isVisible() ) { 1175 this.tree.activeNode.deactivate(); 1176 } 1177 // Expanding a lazy node: set 'loading...' and call callback 1178 if( bExpand && this.data.isLazy && this.childList === null && !this._isLoading ) { 1179 this._loadContent(); 1180 return; 1181 } 1182 if ( opts.onExpand ){ 1183 opts.onExpand.call(this.tree, bExpand, this); 1184 } 1185 }, 1186 1187 isExpanded: function() { 1188 return this.bExpanded; 1189 }, 1190 1191 expand: function(flag) { 1192 flag = (flag !== false); 1193 if( !this.childList && !this.data.isLazy && flag ){ 1194 return; // Prevent expanding empty nodes 1195 } else if( this.parent === null && !flag ){ 1196 return; // Prevent collapsing the root 1197 } 1198 this._expand(flag); 1199 }, 1200 1201 scheduleAction: function(mode, ms) {
1202 /** Schedule activity for delayed execution (cancel any pending request). 1203 * scheduleAction('cancel') will cancel the request. 1204 */ 1205 if( this.tree.timer ) { 1206 clearTimeout(this.tree.timer); 1207 this.tree.logDebug("clearTimeout(%o)", this.tree.timer); 1208 } 1209 var self = this; // required for closures 1210 switch (mode) { 1211 case "cancel": 1212 // Simply made sure that timer was cleared 1213 break; 1214 case "expand": 1215 this.tree.timer = setTimeout(function(){ 1216 self.tree.logDebug("setTimeout: trigger expand"); 1217 self.expand(true); 1218 }, ms); 1219 break; 1220 case "activate": 1221 this.tree.timer = setTimeout(function(){ 1222 self.tree.logDebug("setTimeout: trigger activate"); 1223 self.activate(); 1224 }, ms); 1225 break; 1226 default: 1227 throw "Invalid mode " + mode; 1228 } 1229 this.tree.logDebug("setTimeout(%s, %s): %s", mode, ms, this.tree.timer); 1230 }, 1231 1232 toggleExpand: function() { 1233 this.expand(!this.bExpanded); 1234 }, 1235 1236 collapseSiblings: function() { 1237 if( this.parent === null ){ 1238 return; 1239 } 1240 var ac = this.parent.childList; 1241 for (var i=0, l=ac.length; i<l; i++) { 1242 if ( ac[i] !== this && ac[i].bExpanded ){ 1243 ac[i]._expand(false); 1244 } 1245 } 1246 }, 1247 1248 _onClick: function(event) { 1249// this.tree.logDebug("dtnode.onClick(" + event.type + "): dtnode:" + this + ", button:" + event.button + ", which: " + event.which); 1250 var targetType = this.getEventTargetType(event); 1251 if( targetType === "expander" ) { 1252 // Clicking the expander icon always expands/collapses 1253 this.toggleExpand(); 1254 this.focus(); // issue 95 1255 } else if( targetType === "checkbox" ) { 1256 // Clicking the checkbox always (de)selects 1257 this.toggleSelect(); 1258 this.focus(); // issue 95 1259 } else { 1260 this._userActivate(); 1261 var aTag = this.span.getElementsByTagName("a"); 1262 if(aTag[0]){ 1263 // issue 154, 313 1264 if(!(BROWSER.msie && parseInt(BROWSER.version, 10) < 9)){ 1265 aTag[0].focus(); 1266 } 1267 }else{ 1268 // 'noLink' option was set 1269 return true; 1270 } 1271 } 1272 // Make sure that clicks stop, otherwise <a href='#'> jumps to the top 1273 event.preventDefault(); 1274 }, 1275 1276 _onDblClick: function(event) { 1277// this.tree.logDebug("dtnode.onDblClick(" + event.type + "): dtnode:" + this + ", button:" + event.button + ", which: " + event.which); 1278 }, 1279 1280 _onKeydown: function(event) { 1281// this.tree.logDebug("dtnode.onKeydown(" + event.type + "): dtnode:" + this + ", charCode:" + event.charCode + ", keyCode: " + event.keyCode + ", which: " + event.which); 1282 var handled = true, 1283 sib; 1284// alert("keyDown" + event.which); 1285 1286 switch( event.which ) { 1287 // charCodes: 1288// case 43: // '+' 1289 case 107: // '+' 1290 case 187: // '+' @ Chrome, Safari 1291 if( !this.bExpanded ){ this.toggleExpand(); } 1292 break; 1293// case 45: // '-' 1294 case 109: // '-' 1295 case 189: // '+' @ Chrome, Safari 1296 if( this.bExpanded ){ this.toggleExpand(); } 1297 break; 1298 //~ case 42: // '*' 1299 //~ break; 1300 //~ case 47: // '/' 1301 //~ break; 1302 // case 13: // <enter> 1303 // <enter> on a focused <a> tag seems to generate a click-event. 1304 // this._userActivate(); 1305 // break; 1306 case 32: // <space> 1307 this._userActivate(); 1308 break; 1309 case 8: // <backspace> 1310 if( this.parent ){ 1311 this.parent.focus(); 1312 } 1313 break; 1314 case 37: // <left> 1315 if( this.bExpanded ) { 1316 this.toggleExpand(); 1317 this.focus(); 1318// } else if( this.parent && (this.tree.options.rootVisible || this.parent.parent) ) { 1319 } else if( this.parent && this.parent.parent ) { 1320 this.parent.focus(); 1321 } 1322 break; 1323 case 39: // <right> 1324 if( !this.bExpanded && (this.childList || this.data.isLazy) ) { 1325 this.toggleExpand(); 1326 this.focus(); 1327 } else if( this.childList ) { 1328 this.childList[0].focus(); 1329 } 1330 break; 1331 case 38: // <up> 1332 sib = this.getPrevSibling(); 1333 while( sib && sib.bExpanded && sib.childList ){ 1334 sib = sib.childList[sib.childList.length-1]; 1335 } 1336// if( !sib && this.parent && (this.tree.options.rootVisible || this.parent.parent) ) 1337 if( !sib && this.parent && this.parent.parent ){ 1338 sib = this.parent; 1339 } 1340 if( sib ){ 1341 sib.focus(); 1342 } 1343 break; 1344 case 40: // <down> 1345 if( this.bExpanded && this.childList ) { 1346 sib = this.childList[0]; 1347 } else { 1348 var parents = this._parentList(false, true); 1349 for(var i=parents.length-1; i>=0; i--) { 1350 sib = parents[i].getNextSibling(); 1351 if( sib ){ break; } 1352 } 1353 } 1354 if( sib ){ 1355 sib.focus(); 1356 } 1357 break; 1358 default: 1359 handled = false; 1360 } 1361 // Return false, if handled, to prevent default processing 1362// return !handled; 1363 if(handled){ 1364 event.preventDefault(); 1365 } 1366 }, 1367 1368 _onKeypress: function(event) { 1369 // onKeypress is only hooked to allow user callbacks.
1370 // We don't process it, because IE and Safari don't fire keypress for cursor keys. 1371// this.tree.logDebug("dtnode.onKeypress(" + event.type + "): dtnode:" + this + ", charCode:" + event.charCode + ", keyCode: " + event.keyCode + ", which: " + event.which); 1372 }, 1373 1374 _onFocus: function(event) { 1375 // Handles blur and focus events. 1376// this.tree.logDebug("dtnode._onFocus(%o): %o", event, this); 1377 var opts = this.tree.options; 1378 if ( event.type == "blur" || event.type == "focusout" ) { 1379 if ( opts.onBlur ){ 1380 opts.onBlur.call(this.tree, this); 1381 } 1382 if( this.tree.tnFocused ){ 1383 $(this.tree.tnFocused.span).removeClass(opts.classNames.focused); 1384 } 1385 this.tree.tnFocused = null; 1386 if( opts.persist ){ 1387 $.cookie(opts.cookieId + "-focus", "", opts.cookie); 1388 } 1389 } else if ( event.type=="focus" || event.type=="focusin") { 1390 // Fix: sometimes the blur event is not generated 1391 if( this.tree.tnFocused && this.tree.tnFocused !== this ) { 1392 this.tree.logDebug("dtnode.onFocus: out of sync: curFocus: %o", this.tree.tnFocused); 1393 $(this.tree.tnFocused.span).removeClass(opts.classNames.focused); 1394 } 1395 this.tree.tnFocused = this; 1396 if ( opts.onFocus ){ 1397 opts.onFocus.call(this.tree, this); 1398 } 1399 $(this.tree.tnFocused.span).addClass(opts.classNames.focused); 1400 if( opts.persist ){ 1401 $.cookie(opts.cookieId + "-focus", this.data.key, opts.cookie); 1402 } 1403 } 1404 // TODO: return anything? 1405// return false; 1406 }, 1407 1408 visit: function(fn, includeSelf) { 1409 // Call fn(node) for all child nodes. Stop iteration, if fn() returns false. 1410 var res = true; 1411 if( includeSelf === true ) { 1412 res = fn(this); 1413 if( res === false || res === "skip" ){ 1414 return res; 1415 } 1416 } 1417 if(this.childList){ 1418 for(var i=0, l=this.childList.length; i<l; i++){ 1419 res = this.childList[i].visit(fn, true); 1420 if( res === false ){ 1421 break; 1422 } 1423 } 1424 } 1425 return res; 1426 }, 1427 1428 visitParents: function(fn, includeSelf) { 1429 // Visit parent nodes (bottom up) 1430 if(includeSelf && fn(this) === false){ 1431 return false; 1432 } 1433 var p = this.parent; 1434 while( p ) { 1435 if(fn(p) === false){ 1436 return false; 1437 } 1438 p = p.parent; 1439 } 1440 return true; 1441 }, 1442 1443 remove: function() { 1444 // Remove this node 1445// this.tree.logDebug ("%s.remove()", this); 1446 if ( this === this.tree.root ){ 1447 throw "Cannot remove system root"; 1448 } 1449 return this.parent.removeChild(this); 1450 }, 1451 1452 removeChild: function(tn) { 1453 // Remove tn from list of direct children. 1454 var ac = this.childList; 1455 if( ac.length === 1 ) { 1456 if( tn !== ac[0] ){ 1457 throw "removeChild: invalid child"; 1458 } 1459 return this.removeChildren(); 1460 } 1461 if( tn === this.tree.activeNode ){ 1462 tn.deactivate(); 1463 } 1464 if( this.tree.options.persist ) { 1465 if( tn.bSelected ){ 1466 this.tree.persistence.clearSelect(tn.data.key); 1467 } 1468 if ( tn.bExpanded ){ 1469 this.tree.persistence.clearExpand(tn.data.key); 1470 } 1471 } 1472 tn.removeChildren(true); 1473 if(this.ul && tn.li ){ 1474// $("li", $(this.ul)).remove(); // issue 399 1475 this.ul.removeChild(tn.li); // issue 402 1476 } 1477 for(var i=0, l=ac.length; i<l; i++) { 1478 if( ac[i] === tn ) { 1479 this.childList.splice(i, 1); 1480// delete tn; // JSLint complained 1481 break; 1482 } 1483 } 1484 }, 1485 1486 removeChildren: function(isRecursiveCall, retainPersistence) { 1487 // Remove all child nodes (more efficiently than recursive remove()) 1488 this.tree.logDebug("%s.removeChildren(%o)", this, isRecursiveCall); 1489 var tree = this.tree; 1490 var ac = this.childList; 1491 if( ac ) { 1492 for(var i=0, l=ac.length; i<l; i++) { 1493 var tn = ac[i]; 1494 if ( tn === tree.activeNode && !retainPersistence ){ 1495 tn.deactivate(); 1496 } 1497 if( this.tree.options.persist && !retainPersistence ) { 1498 if( tn.bSelected ){ 1499 this.tree.persistence.clearSelect(tn.data.key); 1500 } 1501 if ( tn.bExpanded ){ 1502 this.tree.persistence.clearExpand(tn.data.key); 1503 } 1504 } 1505 tn.removeChildren(true, retainPersistence); 1506 if(this.ul && tn.li){ 1507// this.ul.removeChild(tn.li); 1508 $("li", $(this.ul)).remove(); // issue 231 1509 } 1510// delete tn; JSLint complained 1511 } 1512 // Set to 'null' which is interpreted as 'not yet loaded' for lazy 1513 // nodes 1514 this.childList = null; 1515 } 1516 if( ! isRecursiveCall ) { 1517// this._expand(false); 1518// this.isRead = false; 1519 this._isLoading = false; 1520 this.render(); 1521 } 1522 }, 1523 1524 setTitle: function(title) { 1525 this.fromDict({title: title});
1526 }, 1527 1528 reload: function(force) { 1529 throw "Use reloadChildren() instead"; 1530 }, 1531 1532 reloadChildren: function(callback) { 1533 // Reload lazy content (expansion state is maintained). 1534 if( this.parent === null ){ 1535 throw "Use tree.reload() instead"; 1536 }else if( ! this.data.isLazy ){ 1537 throw "node.reloadChildren() requires lazy nodes."; 1538 } 1539 // appendAjax triggers 'nodeLoaded' event. 1540 // We listen to this, if a callback was passed to reloadChildren 1541 if(callback){ 1542 var self = this; 1543 var eventType = "nodeLoaded.dynatree." + this.tree.$tree.attr("id") 1544 + "." + this.data.key; 1545 this.tree.$tree.bind(eventType, function(e, node, isOk){ 1546 self.tree.$tree.unbind(eventType); 1547 self.tree.logDebug("loaded %o, %o, %o", e, node, isOk); 1548 if(node !== self){ 1549 throw "got invalid load event"; 1550 } 1551 callback.call(self.tree, node, isOk); 1552 }); 1553 } 1554 // The expansion state is maintained 1555 this.removeChildren(); 1556 this._loadContent(); 1557// if( this.bExpanded ) { 1558// // Remove children first, to prevent effects being applied 1559// this.removeChildren(); 1560// // then force re-expand to trigger lazy loading 1561//// this.expand(false); 1562//// this.expand(true); 1563// this._loadContent(); 1564// } else { 1565// this.removeChildren(); 1566// this._loadContent(); 1567// } 1568 }, 1569 1570 /** 1571 * Make sure the node with a given key path is available in the tree. 1572 */ 1573 _loadKeyPath: function(keyPath, callback) { 1574 var tree = this.tree; 1575 tree.logDebug("%s._loadKeyPath(%s)", this, keyPath); 1576 if(keyPath === ""){ 1577 throw "Key path must not be empty"; 1578 } 1579 var segList = keyPath.split(tree.options.keyPathSeparator); 1580 if(segList[0] === ""){ 1581 throw "Key path must be relative (don't start with '/')"; 1582 } 1583 var seg = segList.shift(); 1584 if(this.childList){ 1585 for(var i=0, l=this.childList.length; i < l; i++){ 1586 var child = this.childList[i]; 1587 if( child.data.key === seg ){ 1588 if(segList.length === 0) { 1589 // Found the end node 1590 callback.call(tree, child, "ok"); 1591 1592 }else if(child.data.isLazy && (child.childList === null || child.childList === undefined)){ 1593 tree.logDebug("%s._loadKeyPath(%s) -> reloading %s...", this, keyPath, child); 1594 var self = this; 1595 // Note: this line gives a JSLint warning (Don't make functions within a loop) 1596 /*jshint loopfunc:true */ 1597 child.reloadChildren(function(node, isOk){ 1598 // After loading, look for direct child with that key 1599 if(isOk){ 1600 tree.logDebug("%s._loadKeyPath(%s) -> reloaded %s.", node, keyPath, node); 1601 callback.call(tree, child, "loaded"); 1602 node._loadKeyPath(segList.join(tree.options.keyPathSeparator), callback); 1603 }else{ 1604 tree.logWarning("%s._loadKeyPath(%s) -> reloadChildren() failed.", self, keyPath); 1605 callback.call(tree, child, "error"); 1606 } 1607 }); 1608 // we can ignore it, since it will only be exectuted once, the the loop is ended 1609 // See also http://stackoverflow.com/questions/3037598/how-to-get-around-the-jslint-error-dont-make-functions-within-a-loop 1610 } else { 1611 callback.call(tree, child, "loaded"); 1612 // Look for direct child with that key 1613 child._loadKeyPath(segList.join(tree.options.keyPathSeparator), callback); 1614 } 1615 return; 1616 } 1617 } 1618 } 1619 // Could not find key 1620 // Callback params: child: undefined, the segment, isEndNode (segList.length === 0) 1621 callback.call(tree, undefined, "notfound", seg, segList.length === 0); 1622 tree.logWarning("Node not found: " + seg); 1623 return; 1624 }, 1625 1626 resetLazy: function() { 1627 // Discard lazy content. 1628 if( this.parent === null ){ 1629 throw "Use tree.reload() instead"; 1630 }else if( ! this.data.isLazy ){ 1631 throw "node.resetLazy() requires lazy nodes."; 1632 } 1633 this.expand(false); 1634 this.removeChildren(); 1635 }, 1636 1637 _addChildNode: function(dtnode, beforeNode) { 1638 /** 1639 * Internal function to add one single DynatreeNode as a child. 1640 * 1641 */ 1642 var tree = this.tree, 1643 opts = tree.options, 1644 pers = tree.persistence; 1645 1646// tree.logDebug("%s._addChildNode(%o)", this, dtnode); 1647 1648 // --- Update and fix dtnode attributes if necessary 1649 dtnode.parent = this; 1650// if( beforeNode && (beforeNode.parent !== this || beforeNode === dtnode ) ) 1651// throw "<beforeNode> must be another child of <this>"; 1652 1653 // --- Add dtnode as a child 1654 if ( this.childList === null ) { 1655 this.childList = []; 1656 } else if( ! beforeNode ) { 1657 // Fix 'lastsib' 1658 if(this.childList.length > 0) { 1659 $(this.childList[this.childList.length-1].span).removeClass(opts.classNames.lastsib); 1660 } 1661 } 1662 if( beforeNode ) { 1663 var iBefore = $.inArray(beforeNode, this.childList); 1664 if( iBefore < 0 ){ 1665 throw "<beforeNode> must be a child of <this>"; 1666 } 1667 this.childList.splice(iBefore, 0, dtnode); 1668 } else { 1669 // Append node 1670 this.childList.push(dtnode); 1671 } 1672 1673 // --- Handle persistence 1674 // Initial status is read from cookies, if persistence is active and 1675 // cookies are already present. 1676 // Otherwise the status is read from the data attributes and then persisted. 1677 var isInitializing = tree.isInitializing(); 1678 if( opts.persist && pers.cookiesFound && isInitializing ) { 1679 // Init status from cookies 1680// tree.logDebug("init from cookie, pa=%o, dk=%o", pers.activeKey, dtnode.data.key); 1681 if( pers.activeKey === dtnode.data.key ){ 1682 tree.activeNode = dtnode; 1683 } 1684 if( pers.focusedKey === dtnode.data.key ){ 1685 tree.focusNode = dtnode; 1686 } 1687 dtnode.bExpanded = ($.inArray(dtnode.data.key, pers.expandedKeyList) >= 0); 1688 dtnode.bSelected = ($.inArray(dtnode.data.key, pers.selectedKeyList) >= 0); 1689// tree.logDebug(" key=%o, bSelected=%o", dtnode.data.key, dtnode.bSelected); 1690 } else { 1691 // Init status from data (Note: we write the cookies after the init phase) 1692// tree.logDebug("init from data"); 1693 if( dtnode.data.activate ) { 1694 tree.activeNode = dtnode; 1695 if( opts.persist ){ 1696 pers.activeKey = dtnode.data.key; 1697 } 1698 } 1699 if( dtnode.data.focus ) { 1700 tree.focusNode = dtnode; 1701 if( opts.persist ){ 1702 pers.focusedKey = dtnode.data.key; 1703 } 1704 } 1705 dtnode.bExpanded = ( dtnode.data.expand === true ); // Collapsed by default 1706 if( dtnode.bExpanded && opts.persist ){ 1707 pers.addExpand(dtnode.data.key); 1708 } 1709 dtnode.bSelected = ( dtnode.data.select === true ); // Deselected by default 1710/* 1711 Doesn't work, cause pers.selectedKeyList may be null 1712 if( dtnode.bSelected && opts.selectMode==1 1713 && pers.selectedKeyList && pers.selectedKeyList.length>0 ) { 1714 tree.logWarning("Ignored multi-selection in single-mode for %o", dtnode); 1715 dtnode.bSelected = false; // Fixing bad input data (multi selection for mode:1) 1716 } 1717*/ 1718 if( dtnode.bSelected && opts.persist ){ 1719 pers.addSelect(dtnode.data.key); 1720 } 1721 } 1722 1723 // Always expand, if it's below minExpandLevel 1724// tree.logDebug ("%s._addChildNode(%o), l=%o", this, dtnode, dtnode.getLevel()); 1725 if ( opts.minExpandLevel >= dtnode.getLevel() ) { 1726// tree.logDebug ("Force expand for %o", dtnode); 1727 this.bExpanded = true; 1728 } 1729 1730 // In multi-hier mode, update the parents selection state 1731 // issue #82: only if not initializing, because the children may not exist yet 1732// if( !dtnode.data.isStatusNode && opts.selectMode==3 && !isInitializing ) 1733// dtnode._fixSelectionState(); 1734 1735 // In multi-hier mode, update the parents selection state 1736 if( dtnode.bSelected && opts.selectMode==3 ) { 1737 var p = this; 1738 while( p ) { 1739 if( !p.hasSubSel ){ 1740 p._setSubSel(true); 1741 } 1742 p = p.parent; 1743 } 1744 } 1745 // render this node and the new child 1746 if ( tree.bEnableUpdate ){ 1747 this.render(); 1748 } 1749 return dtnode; 1750 }, 1751 1752 addChild: function(obj, beforeNode) { 1753 /**
1754 * Add a node object as child. 1755 * 1756 * This should be the only place, where a DynaTreeNode is constructed! 1757 * (Except for the root node creation in the tree constructor) 1758 * 1759 * @param obj A JS object (may be recursive) or an array of those. 1760 * @param {DynaTreeNode} beforeNode (optional) sibling node. 1761 * 1762 * Data format: array of node objects, with optional 'children' attributes. 1763 * [ 1764 * { title: "t1", isFolder: true, ... } 1765 * { title: "t2", isFolder: true, ..., 1766 * children: [ 1767 * {title: "t2.1", ..}, 1768 * {..} 1769 * ] 1770 * } 1771 * ] 1772 * A simple object is also accepted instead of an array. 1773 * 1774 */ 1775// this.tree.logDebug("%s.addChild(%o, %o)", this, obj, beforeNode); 1776 if(typeof(obj) == "string"){ 1777 throw "Invalid data type for " + obj; 1778 }else if( !obj || obj.length === 0 ){ // Passed null or undefined or empty array 1779 return; 1780 }else if( obj instanceof DynaTreeNode ){ 1781 return this._addChildNode(obj, beforeNode); 1782 } 1783 1784 if( !obj.length ){ // Passed a single data object 1785 obj = [ obj ]; 1786 } 1787 var prevFlag = this.tree.enableUpdate(false); 1788 1789 var tnFirst = null; 1790 for (var i=0, l=obj.length; i<l; i++) { 1791 var data = obj[i]; 1792 var dtnode = this._addChildNode(new DynaTreeNode(this, this.tree, data), beforeNode); 1793 if( !tnFirst ){ 1794 tnFirst = dtnode; 1795 } 1796 // Add child nodes recursively 1797 if( data.children ){ 1798 dtnode.addChild(data.children, null); 1799 } 1800 } 1801 this.tree.enableUpdate(prevFlag); 1802 return tnFirst; 1803 }, 1804 1805 append: function(obj) { 1806 this.tree.logWarning("node.append() is deprecated (use node.addChild() instead)."); 1807 return this.addChild(obj, null); 1808 }, 1809 1810 appendAjax: function(ajaxOptions) { 1811 var self = this; 1812 this.removeChildren(false, true); 1813 this.setLazyNodeStatus(DTNodeStatus_Loading); 1814 // Debug feature: force a delay, to simulate slow loading... 1815 if(ajaxOptions.debugLazyDelay){ 1816 var ms = ajaxOptions.debugLazyDelay; 1817 ajaxOptions.debugLazyDelay = 0; 1818 this.tree.logInfo("appendAjax: waiting for debugLazyDelay " + ms); 1819 setTimeout(function(){self.appendAjax(ajaxOptions);}, ms); 1820 return; 1821 } 1822 // Ajax option inheritance: $.ajaxSetup < $.ui.dynatree.prototype.options.ajaxDefaults < tree.options.ajaxDefaults < ajaxOptions 1823 var orgSuccess = ajaxOptions.success, 1824 orgError = ajaxOptions.error, 1825 eventType = "nodeLoaded.dynatree." + this.tree.$tree.attr("id") + "." + this.data.key; 1826 var options = $.extend({}, this.tree.options.ajaxDefaults, ajaxOptions, { 1827 success: function(data, textStatus, jqXHR){ 1828 // <this> is the request options 1829// self.tree.logDebug("appendAjax().success"); 1830 var prevPhase = self.tree.phase; 1831 self.tree.phase = "init"; 1832 // postProcess is similar to the standard dataFilter hook, 1833 // but it is also called for JSONP 1834 if( options.postProcess ){ 1835 data = options.postProcess.call(this, data, this.dataType); 1836 } 1837 // Process ASPX WebMethod JSON object inside "d" property 1838 // http://code.google.com/p/dynatree/issues/detail?id=202 1839 else if (data && data.hasOwnProperty("d")) { 1840 data = (typeof data.d) == "string" ? $.parseJSON(data.d) : data.d; 1841 } 1842 if(!$.isArray(data) || data.length !== 0){ 1843 self.addChild(data, null); 1844 } 1845 self.tree.phase = "postInit"; 1846 if( orgSuccess ){ 1847 orgSuccess.call(options, self, data, textStatus); 1848 } 1849 self.tree.logDebug("trigger " + eventType); 1850 self.tree.$tree.trigger(eventType, [self, true]); 1851 self.tree.phase = prevPhase; 1852 // This should be the last command, so node._isLoading is true 1853 // while the callbacks run 1854 self.setLazyNodeStatus(DTNodeStatus_Ok); 1855 if($.isArray(data) && data.length === 0){ 1856 // Set to [] which is interpreted as 'no children' for lazy 1857 // nodes 1858 self.childList = []; 1859 self.render(); 1860 } 1861 }, 1862 error: function(jqXHR, textStatus, errorThrown){ 1863 // <this> is the request options 1864 self.tree.logWarning("appendAjax failed:", textStatus, ":\n", jqXHR, "\n", errorThrown); 1865 if( orgError ){ 1866 orgError.call(options, self, jqXHR, textStatus, errorThrown); 1867 } 1868 self.tree.$tree.trigger(eventType, [self, false]); 1869 self.setLazyNodeStatus(DTNodeStatus_Error, {info: textStatus, tooltip: "" + errorThrown}); 1870 } 1871 }); 1872 $.ajax(options); 1873 }, 1874 1875 move: function(targetNode, mode) { 1876 /**Move this node to targetNode. 1877 * mode 'child': append this node as last child of targetNode. 1878 * This is the default. To be compatble with the D'n'd 1879 * hitMode, we also accept 'over'. 1880 * mode 'before': add this node as sibling before targetNode. 1881 * mode 'after': add this node as sibling after targetNode. 1882 */ 1883 var pos; 1884 if(this === targetNode){ 1885 return; 1886 } 1887 if( !this.parent ){ 1888 throw "Cannot move system root"; 1889 } 1890 if(mode === undefined || mode == "over"){ 1891 mode = "child"; 1892 } 1893 var prevParent = this.parent; 1894 var targetParent = (mode === "child") ? targetNode : targetNode.parent; 1895 if( targetParent.isDescendantOf(this) ){ 1896 throw "Cannot move a node to it's own descendant"; 1897 } 1898 // Unlink this node from current parent 1899 if( this.parent.childList.length == 1 ) { 1900 this.parent.childList = this.parent.data.isLazy ? [] : null; 1901 this.parent.bExpanded = false; 1902 } else { 1903 pos = $.inArray(this, this.parent.childList); 1904 if( pos < 0 ){ 1905 throw "Internal error"; 1906 } 1907 this.parent.childList.splice(pos, 1); 1908 } 1909 // Remove from source DOM parent 1910 if(this.parent.ul){ 1911 this.parent.ul.removeChild(this.li); 1912 } 1913 1914 // Insert this node to target parent's child list 1915 this.parent = targetParent; 1916 if( targetParent.hasChildren() ) { 1917 switch(mode) { 1918 case "child": 1919 // Append to existing target children 1920 targetParent.childList.push(this); 1921 break; 1922 case "before": 1923 // Insert this node before target node 1924 pos = $.inArray(targetNode, targetParent.childList); 1925 if( pos < 0 ){ 1926 throw "Internal error"; 1927 } 1928 targetParent.childList.splice(pos, 0, this); 1929 break; 1930 case "after": 1931 // Insert this node after target node 1932 pos = $.inArray(targetNode, targetParent.childList); 1933 if( pos < 0 ){ 1934 throw "Internal error"; 1935 } 1936 targetParent.childList.splice(pos+1, 0, this); 1937 break; 1938 default: 1939 throw "Invalid mode " + mode; 1940 } 1941 } else { 1942 targetParent.childList = [ this ]; 1943 } 1944 // Parent has no <ul> tag yet: 1945 if( !targetParent.ul ) { 1946 // This is the parent's first child: create UL tag 1947 // (Hidden, because it will be 1948 targetParent.ul = document.createElement("ul"); 1949 targetParent.ul.style.display = "none"; 1950 targetParent.li.appendChild(targetParent.ul); 1951 } 1952 // Issue 319: Add to target DOM parent (only if node was already rendered(expanded)) 1953 if(this.li){
1954 targetParent.ul.appendChild(this.li); 1955 } 1956 1957 if( this.tree !== targetNode.tree ) { 1958 // Fix node.tree for all source nodes 1959 this.visit(function(node){ 1960 node.tree = targetNode.tree; 1961 }, null, true); 1962 throw "Not yet implemented."; 1963 } 1964 // TODO: fix selection state 1965 // TODO: fix active state 1966 if( !prevParent.isDescendantOf(targetParent)) { 1967 prevParent.render(); 1968 } 1969 if( !targetParent.isDescendantOf(prevParent) ) { 1970 targetParent.render(); 1971 } 1972// this.tree.redraw(); 1973/* 1974 var tree = this.tree; 1975 var opts = tree.options; 1976 var pers = tree.persistence; 1977 1978 1979 // Always expand, if it's below minExpandLevel 1980// tree.logDebug ("%s._addChildNode(%o), l=%o", this, dtnode, dtnode.getLevel()); 1981 if ( opts.minExpandLevel >= dtnode.getLevel() ) { 1982// tree.logDebug ("Force expand for %o", dtnode); 1983 this.bExpanded = true; 1984 } 1985 1986 // In multi-hier mode, update the parents selection state 1987 // issue #82: only if not initializing, because the children may not exist yet 1988// if( !dtnode.data.isStatusNode && opts.selectMode==3 && !isInitializing ) 1989// dtnode._fixSelectionState(); 1990 1991 // In multi-hier mode, update the parents selection state 1992 if( dtnode.bSelected && opts.selectMode==3 ) { 1993 var p = this; 1994 while( p ) { 1995 if( !p.hasSubSel ) 1996 p._setSubSel(true); 1997 p = p.parent; 1998 } 1999 } 2000 // render this node and the new child 2001 if ( tree.bEnableUpdate ) 2002 this.render(); 2003 2004 return dtnode; 2005 2006*/ 2007 }, 2008 2009 // --- end of class 2010 lastentry: undefined 2011}; 2012 2013/************************************************************************* 2014 * class DynaTreeStatus 2015 */ 2016 2017var DynaTreeStatus = Class.create(); 2018 2019 2020DynaTreeStatus._getTreePersistData = function(cookieId, cookieOpts) { 2021 // Static member: Return persistence information from cookies 2022 var ts = new DynaTreeStatus(cookieId, cookieOpts); 2023 ts.read(); 2024 return ts.toDict(); 2025}; 2026// Make available in global scope 2027getDynaTreePersistData = DynaTreeStatus._getTreePersistData; // TODO: deprecated 2028 2029 2030DynaTreeStatus.prototype = { 2031 // Constructor 2032 initialize: function(cookieId, cookieOpts) { 2033// this._log("DynaTreeStatus: initialize"); 2034 if( cookieId === undefined ){ 2035 cookieId = $.ui.dynatree.prototype.options.cookieId; 2036 } 2037 cookieOpts = $.extend({}, $.ui.dynatree.prototype.options.cookie, cookieOpts); 2038 2039 this.cookieId = cookieId; 2040 this.cookieOpts = cookieOpts; 2041 this.cookiesFound = undefined; 2042 this.activeKey = null; 2043 this.focusedKey = null; 2044 this.expandedKeyList = null; 2045 this.selectedKeyList = null; 2046 }, 2047 // member functions 2048 _log: function(msg) { 2049 // this.logDebug("_changeNodeList(%o): nodeList:%o, idx:%o", mode, nodeList, idx); 2050 Array.prototype.unshift.apply(arguments, ["debug"]); 2051 _log.apply(this, arguments); 2052 }, 2053 read: function() { 2054// this._log("DynaTreeStatus: read"); 2055 // Read or init cookies. 2056 this.cookiesFound = false; 2057 2058 var cookie = $.cookie(this.cookieId + "-active"); 2059 this.activeKey = cookie || ""; 2060 if( cookie ){ 2061 this.cookiesFound = true; 2062 } 2063 cookie = $.cookie(this.cookieId + "-focus"); 2064 this.focusedKey = cookie || ""; 2065 if( cookie ){ 2066 this.cookiesFound = true; 2067 } 2068 cookie = $.cookie(this.cookieId + "-expand"); 2069 this.expandedKeyList = cookie ? cookie.split(",") : []; 2070 if( cookie ){ 2071 this.cookiesFound = true; 2072 } 2073 cookie = $.cookie(this.cookieId + "-select"); 2074 this.selectedKeyList = cookie ? cookie.split(",") : []; 2075 if( cookie ){ 2076 this.cookiesFound = true; 2077 } 2078 }, 2079 write: function() { 2080// this._log("DynaTreeStatus: write"); 2081 $.cookie(this.cookieId + "-active", ( this.activeKey === null ) ? "" : this.activeKey, this.cookieOpts); 2082 $.cookie(this.cookieId + "-focus", ( this.focusedKey === null ) ? "" : this.focusedKey, this.cookieOpts); 2083 $.cookie(this.cookieId + "-expand", ( this.expandedKeyList === null ) ? "" : this.expandedKeyList.join(","), this.cookieOpts); 2084 $.cookie(this.cookieId + "-select", ( this.selectedKeyList === null ) ? "" : this.selectedKeyList.join(","), this.cookieOpts); 2085 }, 2086 addExpand: function(key) { 2087// this._log("addExpand(%o)", key); 2088 if( $.inArray(key, this.expandedKeyList) < 0 ) { 2089 this.expandedKeyList.push(key); 2090 $.cookie(this.cookieId + "-expand", this.expandedKeyList.join(","), this.cookieOpts); 2091 } 2092 }, 2093 clearExpand: function(key) { 2094// this._log("clearExpand(%o)", key); 2095 var idx = $.inArray(key, this.expandedKeyList); 2096 if( idx >= 0 ) { 2097 this.expandedKeyList.splice(idx, 1); 2098 $.cookie(this.cookieId + "-expand", this.expandedKeyList.join(","), this.cookieOpts); 2099 } 2100 }, 2101 addSelect: function(key) { 2102// this._log("addSelect(%o)", key); 2103 if( $.inArray(key, this.selectedKeyList) < 0 ) { 2104 this.selectedKeyList.push(key); 2105 $.cookie(this.cookieId + "-select", this.selectedKeyList.join(","), this.cookieOpts); 2106 } 2107 }, 2108 clearSelect: function(key) { 2109// this._log("clearSelect(%o)", key); 2110 var idx = $.inArray(key, this.selectedKeyList); 2111 if( idx >= 0 ) { 2112 this.selectedKeyList.splice(idx, 1); 2113 $.cookie(this.cookieId + "-select", this.selectedKeyList.join(","), this.cookieOpts); 2114 } 2115 }, 2116 isReloading: function() { 2117 return this.cookiesFound === true; 2118 }, 2119 toDict: function() { 2120 return { 2121 cookiesFound: this.cookiesFound, 2122 activeKey: this.activeKey, 2123 focusedKey: this.activeKey, 2124 expandedKeyList: this.expandedKeyList, 2125 selectedKeyList: this.selectedKeyList 2126 }; 2127 }, 2128 // --- end of class 2129 lastentry: undefined 2130}; 2131 2132 2133/************************************************************************* 2134 * class DynaTree 2135 */ 2136 2137var DynaTree = Class.create(); 2138 2139// --- Static members ---------------------------------------------------------- 2140 2141DynaTree.version = "@@Version"; 2142 2143//--- Class members ------------------------------------------------------------ 2144 2145DynaTree.prototype = { 2146 // Constructor 2147 initialize: function($widget) { 2148 // instance members 2149 this.phase = "init"; 2150 this.$widget = $widget; 2151 this.options = $widget.options; 2152 this.$tree = $widget.element; 2153 this.timer = null; 2154 // find container element 2155 this.divTree = this.$tree.get(0); 2156 2157 _initDragAndDrop(this); 2158 }, 2159 2160 // member functions 2161 2162 _load: function(callback) { 2163 var $widget = this.$widget; 2164 var opts = this.options, 2165 self = this; 2166 this.bEnableUpdate = true; 2167 this._nodeCount = 1; 2168 this.activeNode = null; 2169 this.focusNode = null; 2170 2171 // Some deprecation warnings to help with migration 2172 if( opts.rootVisible !== undefined ){ 2173 this.logWarning("Option 'rootVisible' is no longer supported."); 2174 } 2175 if( opts.minExpandLevel < 1 ) { 2176 this.logWarning("Option 'minExpandLevel' must be >= 1."); 2177 opts.minExpandLevel = 1; 2178 } 2179// _log("warn", "jQuery.support.boxModel " + jQuery.support.boxModel); 2180
2181 // If a 'options.classNames' dictionary was passed, still use defaults 2182 // for undefined classes: 2183 if( opts.classNames !== $.ui.dynatree.prototype.options.classNames ) { 2184 opts.classNames = $.extend({}, $.ui.dynatree.prototype.options.classNames, opts.classNames); 2185 } 2186 if( opts.ajaxDefaults !== $.ui.dynatree.prototype.options.ajaxDefaults ) { 2187 opts.ajaxDefaults = $.extend({}, $.ui.dynatree.prototype.options.ajaxDefaults, opts.ajaxDefaults); 2188 } 2189 if( opts.dnd !== $.ui.dynatree.prototype.options.dnd ) { 2190 opts.dnd = $.extend({}, $.ui.dynatree.prototype.options.dnd, opts.dnd); 2191 } 2192 // Guess skin path, if not specified 2193 if(!opts.imagePath) { 2194 $("script").each( function () { 2195 var _rexDtLibName = /.*dynatree[^\/]*\.js$/i; 2196 if( this.src.search(_rexDtLibName) >= 0 ) { 2197 if( this.src.indexOf("/")>=0 ){ // issue #47 2198 opts.imagePath = this.src.slice(0, this.src.lastIndexOf("/")) + "/skin/"; 2199 }else{ 2200 opts.imagePath = "skin/"; 2201 } 2202 self.logDebug("Guessing imagePath from '%s': '%s'", this.src, opts.imagePath); 2203 return false; // first match 2204 } 2205 }); 2206 } 2207 2208 this.persistence = new DynaTreeStatus(opts.cookieId, opts.cookie); 2209 if( opts.persist ) { 2210 if( !$.cookie ){ 2211 _log("warn", "Please include jquery.cookie.js to use persistence."); 2212 } 2213 this.persistence.read(); 2214 } 2215 this.logDebug("DynaTree.persistence: %o", this.persistence.toDict()); 2216 2217 // Cached tag strings 2218 this.cache = { 2219 tagEmpty: "<span class='" + opts.classNames.empty + "'></span>", 2220 tagVline: "<span class='" + opts.classNames.vline + "'></span>", 2221 tagExpander: "<span class='" + opts.classNames.expander + "'></span>", 2222 tagConnector: "<span class='" + opts.classNames.connector + "'></span>", 2223 tagNodeIcon: "<span class='" + opts.classNames.nodeIcon + "'></span>", 2224 tagCheckbox: "<span class='" + opts.classNames.checkbox + "'></span>", 2225 lastentry: undefined 2226 }; 2227 2228 // Clear container, in case it contained some 'waiting' or 'error' text 2229 // for clients that don't support JS. 2230 // We don't do this however, if we try to load from an embedded UL element. 2231 if( opts.children || (opts.initAjax && opts.initAjax.url) || opts.initId ){ 2232 $(this.divTree).empty(); 2233 } 2234 var $ulInitialize = this.$tree.find(">ul:first").hide(); 2235 2236 // Create the root element 2237 this.tnRoot = new DynaTreeNode(null, this, {}); 2238 this.tnRoot.bExpanded = true; 2239 this.tnRoot.render(); 2240 this.divTree.appendChild(this.tnRoot.ul); 2241 2242 var root = this.tnRoot, 2243 isReloading = ( opts.persist && this.persistence.isReloading() ), 2244 isLazy = false, 2245 prevFlag = this.enableUpdate(false); 2246 2247 this.logDebug("Dynatree._load(): read tree structure..."); 2248 2249 // Init tree structure 2250 if( opts.children ) { 2251 // Read structure from node array 2252 root.addChild(opts.children); 2253 2254 } else if( opts.initAjax && opts.initAjax.url ) { 2255 // Init tree from AJAX request 2256 isLazy = true; 2257 root.data.isLazy = true; 2258 this._reloadAjax(callback); 2259 2260 } else if( opts.initId ) { 2261 // Init tree from another UL element 2262 this._createFromTag(root, $("#"+opts.initId)); 2263 2264 } else { 2265 // Init tree from the first UL element inside the container <div> 2266// var $ul = this.$tree.find(">ul:first").hide(); 2267 this._createFromTag(root, $ulInitialize); 2268 $ulInitialize.remove(); 2269 } 2270 2271 this._checkConsistency(); 2272 // Fix part-sel flags 2273 if(!isLazy && opts.selectMode == 3){ 2274 root._updatePartSelectionState(); 2275 } 2276 // Render html markup 2277 this.logDebug("Dynatree._load(): render nodes..."); 2278 this.enableUpdate(prevFlag); 2279 2280 // bind event handlers 2281 this.logDebug("Dynatree._load(): bind events..."); 2282 this.$widget.bind(); 2283 2284 // --- Post-load processing 2285 this.logDebug("Dynatree._load(): postInit..."); 2286 this.phase = "postInit"; 2287 2288 // In persist mode, make sure that cookies are written, even if they are empty 2289 if( opts.persist ) { 2290 this.persistence.write(); 2291 } 2292 // Set focus, if possible (this will also fire an event and write a cookie) 2293 if( this.focusNode && this.focusNode.isVisible() ) { 2294 this.logDebug("Focus on init: %o", this.focusNode); 2295 this.focusNode.focus(); 2296 } 2297 if( !isLazy ) { 2298 if( opts.onPostInit ) { 2299 opts.onPostInit.call(this, isReloading, false); 2300 } 2301 if( callback ){ 2302 callback.call(this, "ok"); 2303 } 2304 } 2305 this.phase = "idle"; 2306 }, 2307 2308 _reloadAjax: function(callback) { 2309 // Reload 2310 var opts = this.options; 2311 if( ! opts.initAjax || ! opts.initAjax.url ){ 2312 throw "tree.reload() requires 'initAjax' mode."; 2313 } 2314 var pers = this.persistence; 2315 var ajaxOpts = $.extend({}, opts.initAjax); 2316 // Append cookie info to the request 2317// this.logDebug("reloadAjax: key=%o, an.key:%o", pers.activeKey, this.activeNode?this.activeNode.data.key:"?"); 2318 if( ajaxOpts.addActiveKey ){ 2319 ajaxOpts.data.activeKey = pers.activeKey; 2320 } 2321 if( ajaxOpts.addFocusedKey ){ 2322 ajaxOpts.data.focusedKey = pers.focusedKey; 2323 } 2324 if( ajaxOpts.addExpandedKeyList ){ 2325 ajaxOpts.data.expandedKeyList = pers.expandedKeyList.join(","); 2326 } 2327 if( ajaxOpts.addSelectedKeyList ){ 2328 ajaxOpts.data.selectedKeyList = pers.selectedKeyList.join(","); 2329 } 2330 // Set up onPostInit callback to be called when Ajax returns 2331 if( ajaxOpts.success ){ 2332 this.logWarning("initAjax: success callback is ignored; use onPostInit instead."); 2333 } 2334 if( ajaxOpts.error ){ 2335 this.logWarning("initAjax: error callback is ignored; use onPostInit instead."); 2336 } 2337 var isReloading = pers.isReloading(); 2338 ajaxOpts.success = function(dtnode, data, textStatus) { 2339 if(opts.selectMode == 3){ 2340 dtnode.tree.tnRoot._updatePartSelectionState(); 2341 } 2342 if(opts.onPostInit){ 2343 opts.onPostInit.call(dtnode.tree, isReloading, false); 2344 } 2345 if(callback){
2346 callback.call(dtnode.tree, "ok"); 2347 } 2348 }; 2349 ajaxOpts.error = function(dtnode, XMLHttpRequest, textStatus, errorThrown) { 2350 if(opts.onPostInit){ 2351 opts.onPostInit.call(dtnode.tree, isReloading, true, XMLHttpRequest, textStatus, errorThrown); 2352 } 2353 if(callback){ 2354 callback.call(dtnode.tree, "error", XMLHttpRequest, textStatus, errorThrown); 2355 } 2356 }; 2357// } 2358 this.logDebug("Dynatree._init(): send Ajax request..."); 2359 this.tnRoot.appendAjax(ajaxOpts); 2360 }, 2361 2362 toString: function() { 2363 return "Dynatree '" + this.$tree.attr("id") + "'"; 2364 }, 2365 2366 toDict: function(includeRoot) { 2367 var dict = this.tnRoot.toDict(true); 2368 return includeRoot ? dict : dict.children; 2369 }, 2370 2371 serializeArray: function(stopOnParents) { 2372 // Return a JavaScript array of objects, ready to be encoded as a JSON 2373 // string for selected nodes 2374 var nodeList = this.getSelectedNodes(stopOnParents), 2375 name = this.$tree.attr("name") || this.$tree.attr("id"), 2376 arr = []; 2377 for(var i=0, l=nodeList.length; i<l; i++){ 2378 arr.push({name: name, value: nodeList[i].data.key}); 2379 } 2380 return arr; 2381 }, 2382 2383 getPersistData: function() { 2384 return this.persistence.toDict(); 2385 }, 2386 2387 logDebug: function(msg) { 2388 if( this.options.debugLevel >= 2 ) { 2389 Array.prototype.unshift.apply(arguments, ["debug"]); 2390 _log.apply(this, arguments); 2391 } 2392 }, 2393 2394 logInfo: function(msg) { 2395 if( this.options.debugLevel >= 1 ) { 2396 Array.prototype.unshift.apply(arguments, ["info"]); 2397 _log.apply(this, arguments); 2398 } 2399 }, 2400 2401 logWarning: function(msg) { 2402 Array.prototype.unshift.apply(arguments, ["warn"]); 2403 _log.apply(this, arguments); 2404 }, 2405 2406 isInitializing: function() { 2407 return ( this.phase=="init" || this.phase=="postInit" ); 2408 }, 2409 isReloading: function() { 2410 return ( this.phase=="init" || this.phase=="postInit" ) && this.options.persist && this.persistence.cookiesFound; 2411 }, 2412 isUserEvent: function() { 2413 return ( this.phase=="userEvent" ); 2414 }, 2415 2416 redraw: function() { 2417// this.logDebug("dynatree.redraw()..."); 2418 this.tnRoot.render(false, false); 2419// this.logDebug("dynatree.redraw() done."); 2420 }, 2421 renderInvisibleNodes: function() { 2422 this.tnRoot.render(false, true); 2423 }, 2424 reload: function(callback) { 2425 this._load(callback); 2426 }, 2427 2428 getRoot: function() { 2429 return this.tnRoot; 2430 }, 2431 2432 enable: function() { 2433 this.$widget.enable(); 2434 }, 2435 2436 disable: function() { 2437 this.$widget.disable(); 2438 }, 2439 2440 getNodeByKey: function(key) { 2441 // Search the DOM by element ID (assuming this is faster than traversing all nodes). 2442 // $("#...") has problems, if the key contains '.', so we use getElementById() 2443 var el = document.getElementById(this.options.idPrefix + key); 2444 if( el ){ 2445 return el.dtnode ? el.dtnode : null; 2446 } 2447 // Not found in the DOM, but still may be in an unrendered part of tree 2448 var match = null; 2449 this.visit(function(node){ 2450// window.console.log("%s", node); 2451 if(node.data.key === key) { 2452 match = node; 2453 return false; 2454 } 2455 }, true); 2456 return match; 2457 }, 2458 2459 getActiveNode: function() { 2460 return this.activeNode; 2461 }, 2462 2463 reactivate: function(setFocus) { 2464 // Re-fire onQueryActivate and onActivate events. 2465 var node = this.activeNode; 2466// this.logDebug("reactivate %o", node); 2467 if( node ) { 2468 this.activeNode = null; // Force re-activating 2469 node.activate(); 2470 if( setFocus ){ 2471 node.focus(); 2472 } 2473 } 2474 }, 2475 2476 getSelectedNodes: function(stopOnParents) { 2477 var nodeList = []; 2478 this.tnRoot.visit(function(node){ 2479 if( node.bSelected ) { 2480 nodeList.push(node); 2481 if( stopOnParents === true ){ 2482 return "skip"; // stop processing this branch 2483 } 2484 } 2485 }); 2486 return nodeList; 2487 }, 2488 2489 activateKey: function(key) { 2490 var dtnode = (key === null) ? null : this.getNodeByKey(key); 2491 if( !dtnode ) { 2492 if( this.activeNode ){ 2493 this.activeNode.deactivate(); 2494 } 2495 this.activeNode = null; 2496 return null; 2497 } 2498 dtnode.focus(); 2499 dtnode.activate(); 2500 return dtnode; 2501 }, 2502 2503 loadKeyPath: function(keyPath, callback) { 2504 var segList = keyPath.split(this.options.keyPathSeparator); 2505 // Remove leading '/' 2506 if(segList[0] === ""){ 2507 segList.shift(); 2508 } 2509 // Remove leading system root key 2510 if(segList[0] == this.tnRoot.data.key){ 2511 this.logDebug("Removed leading root key."); 2512 segList.shift(); 2513 } 2514 keyPath = segList.join(this.options.keyPathSeparator); 2515 return this.tnRoot._loadKeyPath(keyPath, callback); 2516 }, 2517 2518 selectKey: function(key, select) { 2519 var dtnode = this.getNodeByKey(key); 2520 if( !dtnode ){ 2521 return null; 2522 } 2523 dtnode.select(select); 2524 return dtnode; 2525 }, 2526 2527 enableUpdate: function(bEnable) { 2528 if ( this.bEnableUpdate==bEnable ){ 2529 return bEnable; 2530 } 2531 this.bEnableUpdate = bEnable; 2532 if ( bEnable ){ 2533 this.redraw(); 2534 } 2535 return !bEnable; // return previous value 2536 }, 2537 2538 count: function() { 2539 return this.tnRoot.countChildren();
2540 }, 2541 2542 visit: function(fn, includeRoot) { 2543 return this.tnRoot.visit(fn, includeRoot); 2544 }, 2545 2546 _createFromTag: function(parentTreeNode, $ulParent) { 2547 // Convert a <UL>...</UL> list into children of the parent tree node. 2548 var self = this; 2549/* 2550TODO: better? 2551 this.$lis = $("li:has(a[href])", this.element); 2552 this.$tabs = this.$lis.map(function() { return $("a", this)[0]; }); 2553 */ 2554 $ulParent.find(">li").each(function() { 2555 var $li = $(this), 2556 $liSpan = $li.find(">span:first"), 2557 $liA = $li.find(">a:first"), 2558 title, 2559 href = null, 2560 target = null, 2561 tooltip; 2562 if( $liSpan.length ) { 2563 // If a <li><span> tag is specified, use it literally. 2564 title = $liSpan.html(); 2565 } else if( $liA.length ) { 2566 title = $liA.html(); 2567 href = $liA.attr("href"); 2568 target = $liA.attr("target"); 2569 tooltip = $liA.attr("title"); 2570 } else { 2571 // If only a <li> tag is specified, use the trimmed string up to 2572 // the next child <ul> tag. 2573 title = $li.html(); 2574 var iPos = title.search(/<ul/i); 2575 if( iPos >= 0 ){ 2576 title = $.trim(title.substring(0, iPos)); 2577 }else{ 2578 title = $.trim(title); 2579 } 2580// self.logDebug("%o", title); 2581 } 2582 // Parse node options from ID, title and class attributes 2583 var data = { 2584 title: title, 2585 tooltip: tooltip, 2586 isFolder: $li.hasClass("folder"), 2587 isLazy: $li.hasClass("lazy"), 2588 expand: $li.hasClass("expanded"), 2589 select: $li.hasClass("selected"), 2590 activate: $li.hasClass("active"), 2591 focus: $li.hasClass("focused"), 2592 noLink: $li.hasClass("noLink") 2593 }; 2594 if( href ){ 2595 data.href = href; 2596 data.target = target; 2597 } 2598 if( $li.attr("title") ){ 2599 data.tooltip = $li.attr("title"); // overrides <a title='...'> 2600 } 2601 if( $li.attr("id") ){ 2602 data.key = "" + $li.attr("id"); 2603 } 2604 // If a data attribute is present, evaluate as a JavaScript object 2605 if( $li.attr("data") ) { 2606 var dataAttr = $.trim($li.attr("data")); 2607 if( dataAttr ) { 2608 if( dataAttr.charAt(0) != "{" ){ 2609 dataAttr = "{" + dataAttr + "}"; 2610 } 2611 try { 2612 $.extend(data, eval("(" + dataAttr + ")")); 2613 } catch(e) { 2614 throw ("Error parsing node data: " + e + "\ndata:\n'" + dataAttr + "'"); 2615 } 2616 } 2617 } 2618 var childNode = parentTreeNode.addChild(data); 2619 // Recursive reading of child nodes, if LI tag contains an UL tag 2620 var $ul = $li.find(">ul:first"); 2621 if( $ul.length ) { 2622 self._createFromTag(childNode, $ul); // must use 'self', because 'this' is the each() context 2623 } 2624 }); 2625 }, 2626 2627 _checkConsistency: function() { 2628// this.logDebug("tree._checkConsistency() NOT IMPLEMENTED - %o", this); 2629 }, 2630 2631 _setDndStatus: function(sourceNode, targetNode, helper, hitMode, accept) { 2632 // hitMode: 'after', 'before', 'over', 'out', 'start', 'stop' 2633 var $source = sourceNode ? $(sourceNode.span) : null, 2634 $target = $(targetNode.span), 2635 posOpts, 2636 markerOffsetX = 0, 2637 markerAt = "center"; 2638 2639 if( !this.$dndMarker ) { 2640 this.$dndMarker = $("<div id='dynatree-drop-marker'></div>") 2641 .hide() 2642 .css({"z-index": 1000}) 2643 .prependTo($(this.divTree).parent()); 2644 2645// logMsg("Creating marker: %o", this.$dndMarker); 2646 } 2647/* 2648 if(hitMode === "start"){ 2649 } 2650 if(hitMode === "stop"){ 2651// sourceNode.removeClass("dynatree-drop-target"); 2652 } 2653*/ 2654 if(hitMode === "after" || hitMode === "before" || hitMode === "over"){ 2655// $source && $source.addClass("dynatree-drag-source"); 2656// $target.addClass("dynatree-drop-target"); 2657 2658 switch(hitMode){ 2659 case "before": 2660 this.$dndMarker.removeClass("dynatree-drop-after dynatree-drop-over"); 2661 this.$dndMarker.addClass("dynatree-drop-before"); 2662 markerAt = "top"; 2663 break; 2664 case "after": 2665 this.$dndMarker.removeClass("dynatree-drop-before dynatree-drop-over"); 2666 this.$dndMarker.addClass("dynatree-drop-after"); 2667 markerAt = "bottom"; 2668 break; 2669 default: 2670 this.$dndMarker.removeClass("dynatree-drop-after dynatree-drop-before"); 2671 this.$dndMarker.addClass("dynatree-drop-over"); 2672 $target.addClass("dynatree-drop-target"); 2673 markerOffsetX = 8; 2674 } 2675// logMsg("Creating marker: %o", this.$dndMarker); 2676// logMsg(" $target.offset=%o", $target); 2677// logMsg(" pos/$target.offset=%o", pos); 2678// logMsg(" $target.position=%o", $target.position()); 2679// logMsg(" $target.offsetParent=%o, ot:%o", $target.offsetParent(), $target.offsetParent().offset());
2680// logMsg(" $(this.divTree).offset=%o", $(this.divTree).offset()); 2681// logMsg(" $(this.divTree).parent=%o", $(this.divTree).parent()); 2682// var pos = $target.offset(); 2683// var parentPos = $target.offsetParent().offset(); 2684// var bodyPos = $target.offsetParent().offset(); 2685 2686 if( jquerySupports.positionMyOfs ){ 2687 posOpts = { 2688 my: "left" + offsetString(markerOffsetX) + " center", 2689 at: "left " + markerAt, 2690 of: $target 2691 }; 2692 } else { 2693 posOpts = { 2694 my: "left center", 2695 at: "left " + markerAt, 2696 of: $target, 2697 offset: "" + markerOffsetX + " 0" 2698 }; 2699 } 2700 this.$dndMarker 2701 .show() 2702 .position(posOpts); 2703 2704// helper.addClass("dynatree-drop-hover"); 2705 } else { 2706// $source && $source.removeClass("dynatree-drag-source"); 2707 $target.removeClass("dynatree-drop-target"); 2708 this.$dndMarker.hide(); 2709// helper.removeClass("dynatree-drop-hover"); 2710 } 2711 if(hitMode === "after"){ 2712 $target.addClass("dynatree-drop-after"); 2713 } else { 2714 $target.removeClass("dynatree-drop-after"); 2715 } 2716 if(hitMode === "before"){ 2717 $target.addClass("dynatree-drop-before"); 2718 } else { 2719 $target.removeClass("dynatree-drop-before"); 2720 } 2721 if(accept === true){ 2722 if($source){ 2723 $source.addClass("dynatree-drop-accept"); 2724 } 2725 $target.addClass("dynatree-drop-accept"); 2726 helper.addClass("dynatree-drop-accept"); 2727 }else{ 2728 if($source){ 2729 $source.removeClass("dynatree-drop-accept"); 2730 } 2731 $target.removeClass("dynatree-drop-accept"); 2732 helper.removeClass("dynatree-drop-accept"); 2733 } 2734 if(accept === false){ 2735 if($source){ 2736 $source.addClass("dynatree-drop-reject"); 2737 } 2738 $target.addClass("dynatree-drop-reject"); 2739 helper.addClass("dynatree-drop-reject"); 2740 }else{ 2741 if($source){ 2742 $source.removeClass("dynatree-drop-reject"); 2743 } 2744 $target.removeClass("dynatree-drop-reject"); 2745 helper.removeClass("dynatree-drop-reject"); 2746 } 2747 }, 2748 2749 _onDragEvent: function(eventName, node, otherNode, event, ui, draggable) { 2750 /** 2751 * Handles drag'n'drop functionality. 2752 * 2753 * A standard jQuery drag-and-drop process may generate these calls: 2754 * 2755 * draggable helper(): 2756 * _onDragEvent("helper", sourceNode, null, event, null, null); 2757 * start: 2758 * _onDragEvent("start", sourceNode, null, event, ui, draggable); 2759 * drag: 2760 * _onDragEvent("leave", prevTargetNode, sourceNode, event, ui, draggable); 2761 * _onDragEvent("over", targetNode, sourceNode, event, ui, draggable); 2762 * _onDragEvent("enter", targetNode, sourceNode, event, ui, draggable); 2763 * stop: 2764 * _onDragEvent("drop", targetNode, sourceNode, event, ui, draggable); 2765 * _onDragEvent("leave", targetNode, sourceNode, event, ui, draggable); 2766 * _onDragEvent("stop", sourceNode, null, event, ui, draggable); 2767 */ 2768 var hitMode, enterResponse, r, 2769 dnd = this.options.dnd, 2770 res = null, 2771 nodeTag = $(node.span); 2772 2773 switch (eventName) { 2774 case "helper": 2775 // Only event and node argument is available 2776 var $helper = $("<div class='dynatree-drag-helper'><span class='dynatree-drag-helper-img' /></div>") 2777 .append($(event.target).closest(".dynatree-title").clone()); 2778// .append($(event.target).closest('a').clone()); 2779 // issue 244: helper should be child of scrollParent 2780 $("ul.dynatree-container", node.tree.divTree).append($helper); 2781// $(node.tree.divTree).append($helper); 2782 // Attach node reference to helper object 2783 $helper.data("dtSourceNode", node); 2784 res = $helper; 2785 break; 2786 case "start": 2787 if(node.isStatusNode()) { 2788 res = false; 2789 } else if(dnd.onDragStart) { 2790 res = dnd.onDragStart(node); 2791 } 2792 if(res === false) { 2793 this.logDebug("tree.onDragStart() cancelled"); 2794 //draggable._clear(); 2795 // NOTE: the return value seems to be ignored (drag is not canceled, when false is returned) 2796 ui.helper.trigger("mouseup"); 2797 ui.helper.hide(); 2798 } else { 2799 nodeTag.addClass("dynatree-drag-source"); 2800 } 2801 break; 2802 case "enter": 2803 r = dnd.onDragEnter ? dnd.onDragEnter(node, otherNode, ui, draggable) : null; 2804 if(!r){ 2805 // convert null, undefined, false to false 2806 res = false; 2807 }else if ( $.isArray(r) ) { 2808 res = { 2809 over: ($.inArray("over", r) >= 0), 2810 before: ($.inArray("before", r) >= 0), 2811 after: ($.inArray("after", r) >= 0) 2812 }; 2813 }else{ 2814 res = { 2815 over: ((r === true) || (r === "over")), 2816 before: ((r === true) || (r === "before")), 2817 after: ((r === true) || (r === "after")) 2818 }; 2819 } 2820 ui.helper.data("enterResponse", res); 2821// this.logDebug("helper.enterResponse: %o", res); 2822 break; 2823 case "over": 2824 enterResponse = ui.helper.data("enterResponse"); 2825 hitMode = null; 2826 if(enterResponse === false){ 2827 // Don't call onDragOver if onEnter returned false. 2828 // issue 332 2829// break; 2830 } else if(typeof enterResponse === "string") { 2831 // Use hitMode from onEnter if provided. 2832 hitMode = enterResponse; 2833 } else { 2834 // Calculate hitMode from relative cursor position. 2835 var nodeOfs = nodeTag.offset(); 2836 var relPos = { x: event.pageX - nodeOfs.left, 2837 y: event.pageY - nodeOfs.top }; 2838 var relPos2 = { x: relPos.x / nodeTag.width(), 2839 y: relPos.y / nodeTag.height() }; 2840 2841 if( enterResponse.after && relPos2.y > 0.75 ){ 2842 hitMode = "after"; 2843 } else if(!enterResponse.over && enterResponse.after && relPos2.y > 0.5 ){ 2844 hitMode = "after"; 2845 } else if(enterResponse.before && relPos2.y <= 0.25) { 2846 hitMode = "before"; 2847 } else if(!enterResponse.over && enterResponse.before && relPos2.y <= 0.5) { 2848 hitMode = "before"; 2849 } else if(enterResponse.over) { 2850 hitMode = "over"; 2851 } 2852 // Prevent no-ops like 'before source node' 2853 // TODO: these are no-ops when moving nodes, but not in copy mode 2854 if( dnd.preventVoidMoves ){ 2855 if(node === otherNode){ 2856 hitMode = null; 2857 }else if(hitMode === "before" && otherNode && node === otherNode.getNextSibling()){ 2858 hitMode = null; 2859 }else if(hitMode === "after" && otherNode && node === otherNode.getPrevSibling()){ 2860 hitMode = null; 2861 }else if(hitMode === "over" && otherNode 2862 && otherNode.parent === node && otherNode.isLastSibling() ){ 2863 hitMode = null; 2864 } 2865 } 2866// this.logDebug("hitMode: %s - %s - %s", hitMode, (node.parent === otherNode), node.isLastSibling()); 2867 ui.helper.data("hitMode", hitMode); 2868 } 2869 // Auto-expand node (only when 'over' the node, not 'before', or 'after') 2870 if(hitMode === "over" 2871 && dnd.autoExpandMS && node.hasChildren() !== false && !node.bExpanded) { 2872 node.scheduleAction("expand", dnd.autoExpandMS); 2873 } 2874 if(hitMode && dnd.onDragOver){ 2875 res = dnd.onDragOver(node, otherNode, hitMode, ui, draggable); 2876 if(res === "over" || res === "before" || res === "after") { 2877 hitMode = res; 2878 } 2879 } 2880 // issue 332 2881// this._setDndStatus(otherNode, node, ui.helper, hitMode, res!==false); 2882 this._setDndStatus(otherNode, node, ui.helper, hitMode, res!==false && hitMode !== null); 2883 break; 2884 case "drop": 2885 // issue 286: don't trigger onDrop, if DnD status is 'reject' 2886 var isForbidden = ui.helper.hasClass("dynatree-drop-reject"); 2887 hitMode = ui.helper.data("hitMode"); 2888 if(hitMode && dnd.onDrop && !isForbidden){ 2889 dnd.onDrop(node, otherNode, hitMode, ui, draggable); 2890 } 2891 break; 2892 case "leave": 2893 // Cancel pending expand request 2894 node.scheduleAction("cancel"); 2895 ui.helper.data("enterResponse", null); 2896 ui.helper.data("hitMode", null); 2897 this._setDndStatus(otherNode, node, ui.helper, "out", undefined); 2898 if(dnd.onDragLeave){ 2899 dnd.onDragLeave(node, otherNode, ui, draggable); 2900 } 2901 break; 2902 case "stop": 2903 nodeTag.removeClass("dynatree-drag-source"); 2904 if(dnd.onDragStop){ 2905 dnd.onDragStop(node); 2906 } 2907 break; 2908 default: 2909 throw "Unsupported drag event: " + eventName; 2910 } 2911 return res; 2912 }, 2913 2914 cancelDrag: function() { 2915 var dd = $.ui.ddmanager.current; 2916 if(dd){ 2917 dd.cancel(); 2918 } 2919 }, 2920 2921 // --- end of class 2922 lastentry: undefined 2923}; 2924 2925/************************************************************************* 2926 * Widget $(..).dynatree 2927 */ 2928 2929$.widget("ui.dynatree", { 2930/* 2931 init: function() {
2932 // ui.core 1.6 renamed init() to _init(): this stub assures backward compatibility 2933 _log("warn", "ui.dynatree.init() was called; you should upgrade to jquery.ui.core.js v1.8 or higher."); 2934 return this._init(); 2935 }, 2936 */ 2937 _init: function() { 2938// if( parseFloat($.ui.version) < 1.8 ) { 2939 if(versionCompare($.ui.version, "1.8") < 0){ 2940 // jquery.ui.core 1.8 renamed _init() to _create(): this stub assures backward compatibility 2941 if(this.options.debugLevel >= 0){ 2942 _log("warn", "ui.dynatree._init() was called; you should upgrade to jquery.ui.core.js v1.8 or higher."); 2943 } 2944 return this._create(); 2945 } 2946 // jquery.ui.core 1.8 still uses _init() to perform "default functionality" 2947 if(this.options.debugLevel >= 2){ 2948 _log("debug", "ui.dynatree._init() was called; no current default functionality."); 2949 } 2950 }, 2951 2952 _create: function() { 2953 var opts = this.options; 2954 if(opts.debugLevel >= 1){ 2955 logMsg("Dynatree._create(): version='%s', debugLevel=%o.", $.ui.dynatree.version, this.options.debugLevel); 2956 } 2957 // The widget framework supplies this.element and this.options. 2958 this.options.event += ".dynatree"; // namespace event 2959 2960 var divTree = this.element.get(0); 2961/* // Clear container, in case it contained some 'waiting' or 'error' text 2962 // for clients that don't support JS 2963 if( opts.children || (opts.initAjax && opts.initAjax.url) || opts.initId ) 2964 $(divTree).empty(); 2965*/ 2966 // Create the DynaTree object 2967 this.tree = new DynaTree(this); 2968 this.tree._load(); 2969 this.tree.logDebug("Dynatree._init(): done."); 2970 }, 2971 2972 bind: function() { 2973 // Prevent duplicate binding 2974 this.unbind(); 2975 2976 var eventNames = "click.dynatree dblclick.dynatree"; 2977 if( this.options.keyboard ){ 2978 // Note: leading ' '! 2979 eventNames += " keypress.dynatree keydown.dynatree"; 2980 } 2981 this.element.bind(eventNames, function(event){ 2982 var dtnode = $.ui.dynatree.getNode(event.target); 2983 if( !dtnode ){ 2984 return true; // Allow bubbling of other events 2985 } 2986 var tree = dtnode.tree; 2987 var o = tree.options; 2988 tree.logDebug("event(%s): dtnode: %s", event.type, dtnode); 2989 var prevPhase = tree.phase; 2990 tree.phase = "userEvent"; 2991 try { 2992 switch(event.type) { 2993 case "click": 2994 return ( o.onClick && o.onClick.call(tree, dtnode, event)===false ) ? false : dtnode._onClick(event); 2995 case "dblclick": 2996 return ( o.onDblClick && o.onDblClick.call(tree, dtnode, event)===false ) ? false : dtnode._onDblClick(event); 2997 case "keydown": 2998 return ( o.onKeydown && o.onKeydown.call(tree, dtnode, event)===false ) ? false : dtnode._onKeydown(event); 2999 case "keypress": 3000 return ( o.onKeypress && o.onKeypress.call(tree, dtnode, event)===false ) ? false : dtnode._onKeypress(event); 3001 } 3002 } catch(e) { 3003 var _ = null; // issue 117 3004 tree.logWarning("bind(%o): dtnode: %o, error: %o", event, dtnode, e); 3005 } finally { 3006 tree.phase = prevPhase; 3007 } 3008 }); 3009 3010 // focus/blur don't bubble, i.e. are not delegated to parent <div> tags, 3011 // so we use the addEventListener capturing phase. 3012 // See http://www.howtocreate.co.uk/tutorials/javascript/domevents 3013 function __focusHandler(event) { 3014 // Handles blur and focus. 3015 // Fix event for IE: 3016 // doesn't pass JSLint: 3017// event = arguments[0] = $.event.fix( event || window.event ); 3018 // what jQuery does: 3019// var args = jQuery.makeArray( arguments ); 3020// event = args[0] = jQuery.event.fix( event || window.event ); 3021 event = $.event.fix( event || window.event ); 3022 var dtnode = $.ui.dynatree.getNode(event.target); 3023 return dtnode ? dtnode._onFocus(event) : false; 3024 } 3025 var div = this.tree.divTree; 3026 3027 if( div.addEventListener ) { 3028 div.addEventListener("focus", __focusHandler, true); 3029 div.addEventListener("blur", __focusHandler, true); 3030 } else { 3031 div.onfocusin = div.onfocusout = __focusHandler; 3032 } 3033 // EVENTS 3034 // disable click if event is configured to something else 3035// if (!(/^click/).test(o.event)) 3036// this.$tabs.bind("click.tabs", function() { return false; }); 3037 3038 }, 3039 3040 unbind: function() { 3041 this.element.unbind(".dynatree"); 3042 }, 3043 3044/* TODO: we could handle option changes during runtime here (maybe to re-render, ...) 3045 setData: function(key, value) { 3046 this.tree.logDebug("dynatree.setData('" + key + "', '" + value + "')"); 3047 }, 3048*/ 3049 enable: function() { 3050 this.bind(); 3051 // Call default disable(): remove -disabled from css: 3052 $.Widget.prototype.enable.apply(this, arguments); 3053 }, 3054 3055 disable: function() { 3056 this.unbind(); 3057 // Call default disable(): add -disabled to css: 3058 $.Widget.prototype.disable.apply(this, arguments); 3059 }, 3060 3061 // --- getter methods (i.e. NOT returning a reference to $) 3062 getTree: function() { 3063 return this.tree; 3064 }, 3065 3066 getRoot: function() { 3067 return this.tree.getRoot(); 3068 }, 3069 3070 getActiveNode: function() { 3071 return this.tree.getActiveNode(); 3072 }, 3073 3074 getSelectedNodes: function() { 3075 return this.tree.getSelectedNodes(); 3076 }, 3077 3078 // ------------------------------------------------------------------------ 3079 lastentry: undefined 3080}); 3081 3082 3083// The following methods return a value (thus breaking the jQuery call chain): 3084if(versionCompare($.ui.version, "1.8") < 0){ 3085 $.ui.dynatree.getter = "getTree getRoot getActiveNode getSelectedNodes"; 3086} 3087 3088/******************************************************************************* 3089 * Tools in ui.dynatree namespace 3090 */ 3091$.extend($.ui.dynatree, { 3092 /** @type {String} */ 3093 version: "1.2.5", 3094 /** @type {String} */ 3095 buildType: "release", 3096 /** Expose class object as $.ui.dynatree._DynaTreeClass */ 3097 _DynaTreeClass: DynaTree, 3098 /** Expose class object as $.ui.dynatree._DynaTreeNodeClass */ 3099 _DynaTreeNodeClass: DynaTreeNode, 3100 /** 3101 * Return a DynaTreeNode object for a given DOM element 3102 */ 3103 getNode: function(el) { 3104 if(el instanceof DynaTreeNode){ 3105 return el; // el already was a DynaTreeNode 3106 } 3107 if(el.selector !== undefined){ 3108 el = el[0]; // el was a jQuery object: use the DOM element 3109 } 3110 // TODO: for some reason $el.parents("[dtnode]") does not work (jQuery 1.6.1) 3111 // maybe, because dtnode is a property, not an attribute 3112 while( el ) { 3113 if(el.dtnode) { 3114 return el.dtnode; 3115 } 3116 el = el.parentNode; 3117 } 3118 return null; 3119 }, 3120 /**Return persistence information from cookies.*/ 3121 getPersistData: DynaTreeStatus._getTreePersistData 3122}); 3123 3124 3125 3126/******************************************************************************* 3127 * Plugin default options: 3128 */ 3129$.ui.dynatree.prototype.options = { 3130 title: "Dynatree", // Tree's name (only used for debug output) 3131 minExpandLevel: 1, // 1: root node is not collapsible 3132 imagePath: null, // Path to a folder containing icons. Defaults to 'skin/' subdirectory. 3133 children: null, // Init tree structure from this object array. 3134 initId: null, // Init tree structure from a <ul> element with this ID. 3135 initAjax: null, // Ajax options used to initialize the tree strucuture. 3136 autoFocus: true, // Set focus to first child, when expanding or lazy-loading.
3137 keyboard: true, // Support keyboard navigation. 3138 persist: false, // Persist expand-status to a cookie 3139 autoCollapse: false, // Automatically collapse all siblings, when a node is expanded. 3140 clickFolderMode: 3, // 1:activate, 2:expand, 3:activate and expand 3141 activeVisible: true, // Make sure, active nodes are visible (expanded). 3142 checkbox: false, // Show checkboxes. 3143 selectMode: 2, // 1:single, 2:multi, 3:multi-hier 3144 fx: null, // Animations, e.g. null or { height: "toggle", duration: 200 } 3145 noLink: false, // Use <span> instead of <a> tags for all nodes 3146 // Low level event handlers: onEvent(dtnode, event): return false, to stop default processing 3147 onClick: null, // null: generate focus, expand, activate, select events. 3148 onDblClick: null, // (No default actions.) 3149 onKeydown: null, // null: generate keyboard navigation (focus, expand, activate). 3150 onKeypress: null, // (No default actions.) 3151 onFocus: null, // null: set focus to node. 3152 onBlur: null, // null: remove focus from node. 3153 3154 // Pre-event handlers onQueryEvent(flag, dtnode): return false, to stop processing 3155 onQueryActivate: null, // Callback(flag, dtnode) before a node is (de)activated. 3156 onQuerySelect: null, // Callback(flag, dtnode) before a node is (de)selected. 3157 onQueryExpand: null, // Callback(flag, dtnode) before a node is expanded/collpsed. 3158 3159 // High level event handlers 3160 onPostInit: null, // Callback(isReloading, isError) when tree was (re)loaded. 3161 onActivate: null, // Callback(dtnode) when a node is activated. 3162 onDeactivate: null, // Callback(dtnode) when a node is deactivated. 3163 onSelect: null, // Callback(flag, dtnode) when a node is (de)selected. 3164 onExpand: null, // Callback(flag, dtnode) when a node is expanded/collapsed. 3165 onLazyRead: null, // Callback(dtnode) when a lazy node is expanded for the first time. 3166 onCustomRender: null, // Callback(dtnode) before a node is rendered. Return a HTML string to override. 3167 onCreate: null, // Callback(dtnode, nodeSpan) after a node was rendered for the first time. 3168 onRender: null, // Callback(dtnode, nodeSpan) after a node was rendered. 3169 // postProcess is similar to the standard dataFilter hook, 3170 // but it is also called for JSONP 3171 postProcess: null, // Callback(data, dataType) before an Ajax result is passed to dynatree 3172 3173 // Drag'n'drop support 3174 dnd: { 3175 // Make tree nodes draggable: 3176 onDragStart: null, // Callback(sourceNode), return true, to enable dnd 3177 onDragStop: null, // Callback(sourceNode) 3178// helper: null, 3179 revert: false, // true: slide helper back to source if drop is rejected 3180 // Make tree nodes accept draggables 3181 autoExpandMS: 1000, // Expand nodes after n milliseconds of hovering. 3182 preventVoidMoves: true, // Prevent dropping nodes 'before self', etc. 3183 onDragEnter: null, // Callback(targetNode, sourceNode, ui, draggable) 3184 onDragOver: null, // Callback(targetNode, sourceNode, hitMode) 3185 onDrop: null, // Callback(targetNode, sourceNode, hitMode, ui, draggable) 3186 onDragLeave: null // Callback(targetNode, sourceNode) 3187 }, 3188 ajaxDefaults: { // Used by initAjax option 3189 cache: false, // false: Append random '_' argument to the request url to prevent caching. 3190 timeout: 0, // >0: Make sure we get an ajax error for invalid URLs 3191 dataType: "json" // Expect json format and pass json object to callbacks. 3192 }, 3193 strings: { 3194 loading: "Loading…", 3195 loadError: "Load error!" 3196 }, 3197 generateIds: false, // Generate id attributes like <span id='dynatree-id-KEY'> 3198 idPrefix: "dynatree-id-", // Used to generate node id's like <span id="dynatree-id-<key>">. 3199 keyPathSeparator: "/", // Used by node.getKeyPath() and tree.loadKeyPath(). 3200// cookieId: "dynatree-cookie", // Choose a more unique name, to allow multiple trees. 3201 cookieId: "dynatree", // Choose a more unique name, to allow multiple trees. 3202 cookie: { 3203 expires: null //7, // Days or Date; null: session cookie 3204// path: "/", // Defaults to current page 3205// domain: "jquery.com", 3206// secure: true 3207 }, 3208 // Class names used, when rendering the HTML markup. 3209 // Note: 3210 // These settings only apply on initialisation.
3211 // If only single entries are passed for options.classNames, all other 3212 // values are still set to default. 3213 classNames: { 3214 container: "dynatree-container", 3215 node: "dynatree-node", 3216 folder: "dynatree-folder", 3217// document: "dynatree-document", 3218 3219 empty: "dynatree-empty", 3220 vline: "dynatree-vline", 3221 expander: "dynatree-expander", 3222 connector: "dynatree-connector", 3223 checkbox: "dynatree-checkbox", 3224 nodeIcon: "dynatree-icon", 3225 title: "dynatree-title", 3226 noConnector: "dynatree-no-connector", 3227 3228 nodeError: "dynatree-statusnode-error", 3229 nodeWait: "dynatree-statusnode-wait", 3230 hidden: "dynatree-hidden", 3231 combinedExpanderPrefix: "dynatree-exp-", 3232 combinedIconPrefix: "dynatree-ico-", 3233 nodeLoading: "dynatree-loading", 3234// disabled: "dynatree-disabled", 3235 hasChildren: "dynatree-has-children", 3236 active: "dynatree-active", 3237 selected: "dynatree-selected", 3238 expanded: "dynatree-expanded", 3239 lazy: "dynatree-lazy", 3240 focused: "dynatree-focused", 3241 partsel: "dynatree-partsel", 3242 lastsib: "dynatree-lastsib" 3243 }, 3244 debugLevel: 1, // 0:quiet, 1:normal, 2:debug 3245 3246 // ------------------------------------------------------------------------ 3247 lastentry: undefined 3248}; 3249// 3250if(versionCompare($.ui.version, "1.8") < 0){ 3251 $.ui.dynatree.defaults = $.ui.dynatree.prototype.options; 3252} 3253 3254/******************************************************************************* 3255 * Reserved data attributes for a tree node. 3256 */ 3257$.ui.dynatree.nodedatadefaults = { 3258 title: null, // (required) Displayed name of the node (html is allowed here) 3259 key: null, // May be used with activate(), select(), find(), ... 3260 isFolder: false, // Use a folder icon. Also the node is expandable but not selectable. 3261 isLazy: false, // Call onLazyRead(), when the node is expanded for the first time to allow for delayed creation of children. 3262 tooltip: null, // Show this popup text. 3263 href: null, // Added to the generated <a> tag. 3264 icon: null, // Use a custom image (filename relative to tree.options.imagePath). 'null' for default icon, 'false' for no icon. 3265 addClass: null, // Class name added to the node's span tag. 3266 noLink: false, // Use <span> instead of <a> tag for this node 3267 activate: false, // Initial active status. 3268 focus: false, // Initial focused status. 3269 expand: false, // Initial expanded status. 3270 select: false, // Initial selected status. 3271 hideCheckbox: false, // Suppress checkbox display for this node. 3272 unselectable: false, // Prevent selection. 3273// disabled: false, 3274 // The following attributes are only valid if passed to some functions: 3275 children: null, // Array of child nodes. 3276 // NOTE: we can also add custom attributes here. 3277 // This may then also be used in the onActivate(), onSelect() or onLazyTree() callbacks. 3278 // ------------------------------------------------------------------------ 3279 lastentry: undefined 3280}; 3281 3282/******************************************************************************* 3283 * Drag and drop support 3284 */ 3285function _initDragAndDrop(tree) { 3286 var dnd = tree.options.dnd || null; 3287 // Register 'connectToDynatree' option with ui.draggable 3288 if(dnd && (dnd.onDragStart || dnd.onDrop)) { 3289 _registerDnd(); 3290 } 3291 // Attach ui.draggable to this Dynatree instance 3292 if(dnd && dnd.onDragStart ) { 3293 tree.$tree.draggable({ 3294 addClasses: false, 3295 appendTo: "body", 3296 containment: false, 3297 delay: 0, 3298 distance: 4,
3299// revert: false, 3300 // slide back, when dropping over non-target 3301 revert: dnd.revert !== true ? false : function(dropped){ 3302 // This is called by ui-draggable._mouseStop() when a drag stops. 3303 // Return `true` to let the helper slide back. 3304 logMsg("draggable.revert(), dropped=", dropped); 3305 if(typeof dropped === "boolean"){ 3306 // dropped == true, when dropped over a simple, valid droppable target. 3307 // false, when dropped outside a drop target. 3308 return !dropped; 3309 } 3310 // Drop comes from another tree. Default behavior is to assume 3311 // a valid drop, since we are over a drop-target. 3312 // Therefore we have to make an extra check, if the target node 3313 // was rejected by a Dynatree callback. 3314 var helper = $.ui.ddmanager && $.ui.ddmanager.current && $.ui.ddmanager.current.helper; 3315 var isRejected = helper && helper.hasClass("dynatree-drop-reject"); 3316 return isRejected; 3317 }, 3318 scroll: true, // issue 244: enable scrolling (if ul.dynatree-container) 3319 scrollSpeed: 7, 3320 scrollSensitivity: 10, 3321 // Delegate draggable.start, drag, and stop events to our handler 3322 connectToDynatree: true, 3323 // Let source tree create the helper element 3324 helper: function(event) { 3325 var sourceNode = $.ui.dynatree.getNode(event.target); 3326 if(!sourceNode){ // issue 211 3327 return "<div></div>"; 3328 } 3329 return sourceNode.tree._onDragEvent("helper", sourceNode, null, event, null, null); 3330 }, 3331 start: function(event, ui) { 3332 // See issues 211, 268, 278 3333// var sourceNode = $.ui.dynatree.getNode(event.target); 3334 var sourceNode = ui.helper.data("dtSourceNode"); 3335 return !!sourceNode; // Abort dragging if no Node could be found 3336 } 3337 }); 3338 } 3339 // Attach ui.droppable to this Dynatree instance 3340 if(dnd && dnd.onDrop) { 3341 tree.$tree.droppable({ 3342 addClasses: false, 3343 tolerance: "pointer", 3344// tolerance: "intersect", 3345 greedy: false 3346 }); 3347 } 3348} 3349 3350//--- Extend ui.draggable event handling -------------------------------------- 3351var didRegisterDnd = false; 3352var _registerDnd = function() { 3353 if(didRegisterDnd){ 3354 return; 3355 } 3356 // Register proxy-functions for draggable.start/drag/stop 3357 $.ui.plugin.add("draggable", "connectToDynatree", { 3358 start: function(event, ui) { 3359 // issue 386 3360 var draggable = $(this).data("ui-draggable") || $(this).data("draggable"), 3361 sourceNode = ui.helper.data("dtSourceNode") || null; 3362// logMsg("draggable-connectToDynatree.start, %s", sourceNode); 3363// logMsg(" this: %o", this); 3364// logMsg(" event: %o", event); 3365// logMsg(" draggable: %o", draggable); 3366// logMsg(" ui: %o", ui); 3367 3368 if(sourceNode) { 3369 // Adjust helper offset, so cursor is slightly outside top/left corner 3370// draggable.offset.click.top -= event.target.offsetTop; 3371// draggable.offset.click.left -= event.target.offsetLeft; 3372 draggable.offset.click.top = -2; 3373 draggable.offset.click.left = + 16; 3374// logMsg(" draggable2: %o", draggable); 3375// logMsg(" draggable.offset.click FIXED: %s/%s", draggable.offset.click.left, draggable.offset.click.top); 3376 // Trigger onDragStart event 3377 // TODO: when called as connectTo..., the return value is ignored(?) 3378 return sourceNode.tree._onDragEvent("start", sourceNode, null, event, ui, draggable); 3379 } 3380 }, 3381 drag: function(event, ui) { 3382 // issue 386 3383 var draggable = $(this).data("ui-draggable") || $(this).data("draggable"), 3384 sourceNode = ui.helper.data("dtSourceNode") || null, 3385 prevTargetNode = ui.helper.data("dtTargetNode") || null, 3386 targetNode = $.ui.dynatree.getNode(event.target); 3387// logMsg("$.ui.dynatree.getNode(%o): %s", event.target, targetNode); 3388// logMsg("connectToDynatree.drag: helper: %o", ui.helper[0]); 3389 if(event.target && !targetNode){ 3390 // We got a drag event, but the targetNode could not be found 3391 // at the event location. This may happen, 3392 // 1. if the mouse jumped over the drag helper, 3393 // 2. or if non-dynatree element is dragged 3394 // We ignore it: 3395 var isHelper = $(event.target).closest("div.dynatree-drag-helper,#dynatree-drop-marker").length > 0;
3396 if(isHelper){ 3397// logMsg("Drag event over helper: ignored."); 3398 return; 3399 } 3400 } 3401// logMsg("draggable-connectToDynatree.drag: targetNode(from event): %s, dtTargetNode: %s", targetNode, ui.helper.data("dtTargetNode")); 3402 ui.helper.data("dtTargetNode", targetNode); 3403 // Leaving a tree node 3404 if(prevTargetNode && prevTargetNode !== targetNode ) { 3405 prevTargetNode.tree._onDragEvent("leave", prevTargetNode, sourceNode, event, ui, draggable); 3406 } 3407 if(targetNode){ 3408 if(!targetNode.tree.options.dnd.onDrop) { 3409 // not enabled as drop target 3410 } else if(targetNode === prevTargetNode) { 3411 // Moving over same node 3412 targetNode.tree._onDragEvent("over", targetNode, sourceNode, event, ui, draggable); 3413 }else{ 3414 // Entering this node first time 3415 targetNode.tree._onDragEvent("enter", targetNode, sourceNode, event, ui, draggable); 3416 } 3417 } 3418 // else go ahead with standard event handling 3419 }, 3420 stop: function(event, ui) { 3421 // issue 386 3422 var draggable = $(this).data("ui-draggable") || $(this).data("draggable"), 3423 sourceNode = ui.helper.data("dtSourceNode") || null, 3424 targetNode = ui.helper.data("dtTargetNode") || null, 3425// mouseDownEvent = draggable._mouseDownEvent, 3426 eventType = event.type, 3427 dropped = (eventType == "mouseup" && event.which == 1); 3428 logMsg("draggable-connectToDynatree.stop: targetNode(from event): %s, dtTargetNode: %s", targetNode, ui.helper.data("dtTargetNode")); 3429// logMsg("draggable-connectToDynatree.stop, %s", sourceNode); 3430// logMsg(" type: %o, downEvent: %o, upEvent: %o", eventType, mouseDownEvent, event); 3431// logMsg(" targetNode: %o", targetNode); 3432 if(!dropped){ 3433 logMsg("Drag was cancelled"); 3434 } 3435 if(targetNode) { 3436 if(dropped){ 3437 targetNode.tree._onDragEvent("drop", targetNode, sourceNode, event, ui, draggable); 3438 } 3439 targetNode.tree._onDragEvent("leave", targetNode, sourceNode, event, ui, draggable); 3440 } 3441 if(sourceNode){ 3442 sourceNode.tree._onDragEvent("stop", sourceNode, null, event, ui, draggable); 3443 } 3444 } 3445 }); 3446 didRegisterDnd = true; 3447}; 3448 3449// --------------------------------------------------------------------------- 3450}(jQuery));
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.