/************************************************************************* jquery.dynatree.js Dynamic tree view control, with support for lazy loading of branches. Copyright (c) 2006-2013, Martin Wendt (http://wwWendt.de) Dual licensed under the MIT or GPL Version 2 licenses. http://code.google.com/p/dynatree/wiki/LicenseInfo A current version and some documentation is available at http://dynatree.googlecode.com/ $Version: 1.2.4$ $Revision: 644, 2013-02-12 21:39:36$ @depends: jquery.js @depends: jquery.ui.core.js @depends: jquery.cookie.js *************************************************************************/ /* jsHint options*/ // Note: We currently allow eval() to parse the 'data' attribtes, when initializing from HTML. // TODO: pass jsHint with the options given in grunt.js only. // The following should not be required: /*global alert */ /*jshint nomen:false, smarttabs:true, eqeqeq:false, evil:true, regexp:false */ /************************************************************************* * Debug functions */ var _canLog = true; function _log(mode, msg) { /** * Usage: logMsg("%o was toggled", this); */ if( !_canLog ){ return; } // Remove first argument var args = Array.prototype.slice.apply(arguments, [1]); // Prepend timestamp var dt = new Date(); var tag = dt.getHours()+":"+dt.getMinutes()+":"+dt.getSeconds()+"."+dt.getMilliseconds(); args[0] = tag + " - " + args[0]; try { switch( mode ) { case "info": window.console.info.apply(window.console, args); break; case "warn": window.console.warn.apply(window.console, args); break; default: window.console.log.apply(window.console, args); break; } } catch(e) { if( !window.console ){ _canLog = false; // Permanently disable, when logging is not supported by the browser }else if(e.number === -2146827850){ // fix for IE8, where window.console.log() exists, but does not support .apply() window.console.log(args.join(", ")); } } } /* Check browser version, since $.browser was removed in jQuery 1.9 */ function _checkBrowser(){ var matched, browser; function uaMatch( ua ) { ua = ua.toLowerCase(); var match = /(chrome)[ \/]([\w.]+)/.exec( ua ) || /(webkit)[ \/]([\w.]+)/.exec( ua ) || /(opera)(?:.*version|)[ \/]([\w.]+)/.exec( ua ) || /(msie) ([\w.]+)/.exec( ua ) || ua.indexOf("compatible") < 0 && /(mozilla)(?:.*? rv:([\w.]+)|)/.exec( ua ) || []; return { browser: match[ 1 ] || "", version: match[ 2 ] || "0" }; } matched = uaMatch( navigator.userAgent ); browser = {}; if ( matched.browser ) { browser[ matched.browser ] = true; browser.version = matched.version; } if ( browser.chrome ) { browser.webkit = true; } else if ( browser.webkit ) { browser.safari = true; } return browser; } var BROWSER = jQuery.browser || _checkBrowser(); function logMsg(msg) { Array.prototype.unshift.apply(arguments, ["debug"]); _log.apply(this, arguments); } // Forward declaration var getDynaTreePersistData = null; /************************************************************************* * Constants */ var DTNodeStatus_Error = -1; var DTNodeStatus_Loading = 1; var DTNodeStatus_Ok = 0; // Start of local namespace (function($) { /************************************************************************* * Common tool functions. */ var Class = { create: function() { return function() { this.initialize.apply(this, arguments); }; } }; // Tool function to get dtnode from the event target: function getDtNodeFromElement(el) { alert("getDtNodeFromElement is deprecated"); return $.ui.dynatree.getNode(el); /* var iMax = 5; while( el && iMax-- ) { if(el.dtnode) { return el.dtnode; } el = el.parentNode; } return null; */ } function noop() { } /** Compare two dotted version strings (like '10.2.3'). * @returns {Integer} 0: v1 == v2, -1: v1 < v2, 1: v1 > v2 */ function versionCompare(v1, v2) { var v1parts = ("" + v1).split("."), v2parts = ("" + v2).split("."), minLength = Math.min(v1parts.length, v2parts.length), p1, p2, i; // Compare tuple pair-by-pair. for(i = 0; i < minLength; i++) { // Convert to integer if possible, because "8" > "10". p1 = parseInt(v1parts[i], 10); p2 = parseInt(v2parts[i], 10); if (isNaN(p1)){ p1 = v1parts[i]; } if (isNaN(p2)){ p2 = v2parts[i]; } if (p1 == p2) { continue; }else if (p1 > p2) { return 1; }else if (p1 < p2) { return -1; } // one operand is NaN return NaN; } // The longer tuple is always considered 'greater' if (v1parts.length === v2parts.length) { return 0; } return (v1parts.length < v2parts.length) ? -1 : 1; } /************************************************************************* * Class DynaTreeNode */ var DynaTreeNode = Class.create(); DynaTreeNode.prototype = { initialize: function(parent, tree, data) { /** * @constructor */ this.parent = parent; this.tree = tree; if ( typeof data === "string" ){ data = { title: data }; } if( !data.key ){ data.key = "_" + tree._nodeCount++; }else{ data.key = "" + data.key; // issue 371 } this.data = $.extend({}, $.ui.dynatree.nodedatadefaults, data); this.li = null; // not yet created this.span = null; // not yet created this.ul = null; // not yet created this.childList = null; // no subnodes yet this._isLoading = false; // Lazy content is being loaded this.hasSubSel = false; this.bExpanded = false; this.bSelected = false; }, toString: function() { return "DynaTreeNode<" + this.data.key + ">: '" + this.data.title + "'"; }, toDict: function(recursive, callback) { var dict = $.extend({}, this.data); dict.activate = ( this.tree.activeNode === this ); dict.focus = ( this.tree.focusNode === this ); dict.expand = this.bExpanded; dict.select = this.bSelected; if( callback ){ callback(dict); } if( recursive && this.childList ) { dict.children = []; for(var i=0, l=this.childList.length; i 1){ res += cache.tagConnector; } // .. else (i.e. for root level) skip expander/connector altogether } else if( this.hasChildren() !== false ) { res += cache.tagExpander; } else { res += cache.tagConnector; } // Checkbox mode if( opts.checkbox && data.hideCheckbox !== true && !data.isStatusNode ) { res += cache.tagCheckbox; } // folder or doctype icon if ( data.icon ) { if (data.icon.charAt(0) === "/"){ imageSrc = data.icon; }else{ imageSrc = opts.imagePath + data.icon; } res += ""; } else if ( data.icon === false ) { // icon == false means 'no icon' // noop(); // keep JSLint happy } else if ( data.iconClass ) { res += ""; } else { // icon == null means 'default icon' res += cache.tagNodeIcon; } // node title var nodeTitle = ""; if ( opts.onCustomRender ){ nodeTitle = opts.onCustomRender.call(tree, this) || ""; } if(!nodeTitle){ var tooltip = data.tooltip ? ' title="' + data.tooltip.replace(/\"/g, '"') + '"' : '', href = data.href || "#"; if( opts.noLink || data.noLink ) { nodeTitle = '' + data.title + ''; // this.tree.logDebug("nodeTitle: " + nodeTitle); } else { nodeTitle = '' + data.title + ''; } } res += nodeTitle; return res; }, _fixOrder: function() { /** * Make sure, that
  • order matches childList order. */ var cl = this.childList; if( !cl || !this.ul ){ return; } var childLI = this.ul.firstChild; for(var i=0, l=cl.length-1; i this.li = this.span = null; this.ul = document.createElement("ul"); if( opts.minExpandLevel > 1 ){ this.ul.className = cn.container + " " + cn.noConnector; }else{ this.ul.className = cn.container; } } else if( parent ) { // Create
  • if( ! this.li ) { firstTime = true; this.li = document.createElement("li"); this.li.dtnode = this; if( data.key && opts.generateIds ){ this.li.id = opts.idPrefix + data.key; } this.span = document.createElement("span"); this.span.className = cn.title; this.li.appendChild(this.span); if( !parent.ul ) { // This is the parent's first child: create UL tag // (Hidden, because it will be parent.ul = document.createElement("ul"); parent.ul.style.display = "none"; parent.li.appendChild(parent.ul); // if( opts.minExpandLevel > this.getLevel() ){ // parent.ul.className = cn.noConnector; // } } // set node connector images, links and text // this.span.innerHTML = this._getInnerHtml(); parent.ul.appendChild(this.li); } // set node connector images, links and text this.span.innerHTML = this._getInnerHtml(); // Set classes for current status var cnList = []; cnList.push(cn.node); if( data.isFolder ){ cnList.push(cn.folder); } if( this.bExpanded ){ cnList.push(cn.expanded); } if( this.hasChildren() !== false ){ cnList.push(cn.hasChildren); } if( data.isLazy && this.childList === null ){ cnList.push(cn.lazy); } if( isLastSib ){ cnList.push(cn.lastsib); } if( this.bSelected ){ cnList.push(cn.selected); } if( this.hasSubSel ){ cnList.push(cn.partsel); } if( tree.activeNode === this ){ cnList.push(cn.active); } if( data.addClass ){ cnList.push(data.addClass); } // IE6 doesn't correctly evaluate multiple class names, // so we create combined class names that can be used in the CSS cnList.push(cn.combinedExpanderPrefix + (this.bExpanded ? "e" : "c") + (data.isLazy && this.childList === null ? "d" : "") + (isLastSib ? "l" : "")); cnList.push(cn.combinedIconPrefix + (this.bExpanded ? "e" : "c") + (data.isFolder ? "f" : "")); this.span.className = cnList.join(" "); // TODO: we should not set this in the tag also, if we set it here: this.li.className = isLastSib ? cn.lastsib : ""; // Allow tweaking, binding, after node was created for the first time if(firstTime && opts.onCreate){ opts.onCreate.call(tree, this, this.span); } // Hide children, if node is collapsed // this.ul.style.display = ( this.bExpanded || !parent ) ? "" : "none"; // Allow tweaking after node state was rendered if(opts.onRender){ opts.onRender.call(tree, this, this.span); } } // Visit child nodes if( (this.bExpanded || includeInvisible === true) && this.childList ) { for(var i=0, l=this.childList.length; i b.data.title ? 1 : -1; var x = a.data.title.toLowerCase(), y = b.data.title.toLowerCase(); return x === y ? 0 : x > y ? 1 : -1; }; cl.sort(cmp); if( deep ){ for(var i=0, l=cl.length; i 0) { // special case: using ajaxInit this.childList[0].focus(); } else { this.focus(); } } break; case DTNodeStatus_Loading: this._isLoading = true; $(this.span).addClass(this.tree.options.classNames.nodeLoading); // The root is hidden, so we set a temporary status child if(!this.parent){ this._setStatusNode({ title: this.tree.options.strings.loading + info, tooltip: tooltip, addClass: this.tree.options.classNames.nodeWait }); } break; case DTNodeStatus_Error: this._isLoading = false; // $(this.span).addClass(this.tree.options.classNames.nodeError); this._setStatusNode({ title: this.tree.options.strings.loadError + info, tooltip: tooltip, addClass: this.tree.options.classNames.nodeError }); break; default: throw "Bad LazyNodeStatus: '" + lts + "'."; } }, _parentList: function(includeRoot, includeSelf) { var l = []; var dtn = includeSelf ? this : this.parent; while( dtn ) { if( includeRoot || dtn.parent ){ l.unshift(dtn); } dtn = dtn.parent; } return l; }, getLevel: function() { /** * Return node depth. 0: System root node, 1: visible top-level node. */ var level = 0; var dtn = this.parent; while( dtn ) { level++; dtn = dtn.parent; } return level; }, _getTypeForOuterNodeEvent: function(event) { /** Return the inner node span (title, checkbox or expander) if * event.target points to the outer span. * This function should fix issue #93: * FF2 ignores empty spans, when generating events (returning the parent instead). */ var cns = this.tree.options.classNames; var target = event.target; // Only process clicks on an outer node span (probably due to a FF2 event handling bug) if( target.className.indexOf(cns.node) < 0 ) { return null; } // Event coordinates, relative to outer node span: var eventX = event.pageX - target.offsetLeft; var eventY = event.pageY - target.offsetTop; for(var i=0, l=target.childNodes.length; i= x && eventX <= (x+nx) && eventY >= y && eventY <= (y+ny) ) { // alert("HIT "+ cn.className); if( cn.className==cns.title ){ return "title"; }else if( cn.className==cns.expander ){ return "expander"; }else if( cn.className==cns.checkbox ){ return "checkbox"; }else if( cn.className==cns.nodeIcon ){ return "icon"; } } } return "prefix"; }, getEventTargetType: function(event) { // Return the part of a node, that a click event occured on. // Note: there is no check, if the event was fired on THIS node. var tcn = event && event.target ? event.target.className : "", cns = this.tree.options.classNames; if( tcn === cns.title ){ return "title"; }else if( tcn === cns.expander ){ return "expander"; }else if( tcn === cns.checkbox ){ return "checkbox"; }else if( tcn === cns.nodeIcon ){ return "icon"; }else if( tcn === cns.empty || tcn === cns.vline || tcn === cns.connector ){ return "prefix"; }else if( tcn.indexOf(cns.node) >= 0 ){ // FIX issue #93 return this._getTypeForOuterNodeEvent(event); } return null; }, isVisible: function() { // Return true, if all parents are expanded. var parents = this._parentList(true, false); for(var i=0, l=parents.length; ia").focus(); } catch(e) { } }, isFocused: function() { return (this.tree.tnFocused === this); }, _activate: function(flag, fireEvents) { // (De)Activate - but not focus - this node. this.tree.logDebug("dtnode._activate(%o, fireEvents=%o) - %o", flag, fireEvents, this); var opts = this.tree.options; if( this.data.isStatusNode ){ return; } if ( fireEvents && opts.onQueryActivate && opts.onQueryActivate.call(this.tree, flag, this) === false ){ return; // Callback returned false } if( flag ) { // Activate if( this.tree.activeNode ) { if( this.tree.activeNode === this ){ return; } this.tree.activeNode.deactivate(); } if( opts.activeVisible ){ this.makeVisible(); } this.tree.activeNode = this; if( opts.persist ){ $.cookie(opts.cookieId+"-active", this.data.key, opts.cookie); } this.tree.persistence.activeKey = this.data.key; $(this.span).addClass(opts.classNames.active); if ( fireEvents && opts.onActivate ){ opts.onActivate.call(this.tree, this); } } else { // Deactivate if( this.tree.activeNode === this ) { if ( opts.onQueryActivate && opts.onQueryActivate.call(this.tree, false, this) === false ){ return; // Callback returned false } $(this.span).removeClass(opts.classNames.active); if( opts.persist ) { // Note: we don't pass null, but ''. So the cookie is not deleted. // If we pass null, we also have to pass a COPY of opts, because $cookie will override opts.expires (issue 84) $.cookie(opts.cookieId+"-active", "", opts.cookie); } this.tree.persistence.activeKey = null; this.tree.activeNode = null; if ( fireEvents && opts.onDeactivate ){ opts.onDeactivate.call(this.tree, this); } } } }, activate: function() { // Select - but not focus - this node. // this.tree.logDebug("dtnode.activate(): %o", this); this._activate(true, true); }, activateSilently: function() { this._activate(true, false); }, deactivate: function() { // this.tree.logDebug("dtnode.deactivate(): %o", this); this._activate(false, true); }, isActive: function() { return (this.tree.activeNode === this); }, _userActivate: function() { // Handle user click / [space] / [enter], according to clickFolderMode. var activate = true; var expand = false; if ( this.data.isFolder ) { switch( this.tree.options.clickFolderMode ) { case 2: activate = false; expand = true; break; case 3: activate = expand = true; break; } } if( this.parent === null ) { expand = false; } if( expand ) { this.toggleExpand(); this.focus(); } if( activate ) { this.activate(); } }, _setSubSel: function(hasSubSel) { if( hasSubSel ) { this.hasSubSel = true; $(this.span).addClass(this.tree.options.classNames.partsel); } else { this.hasSubSel = false; $(this.span).removeClass(this.tree.options.classNames.partsel); } }, /** * Fix selection and partsel status, of parent nodes, according to current status of * end nodes. */ _updatePartSelectionState: function() { // alert("_updatePartSelectionState " + this); // this.tree.logDebug("_updatePartSelectionState() - %o", this); var sel; // Return `true` or `false` for end nodes and remove part-sel flag if( ! this.hasChildren() ){ sel = (this.bSelected && !this.data.unselectable && !this.data.isStatusNode); this._setSubSel(false); return sel; } // Return `true`, `false`, or `undefined` for parent nodes var i, l, cl = this.childList, allSelected = true, allDeselected = true; for(i=0, l=cl.length; i jumps to the top event.preventDefault(); }, _onDblClick: function(event) { // this.tree.logDebug("dtnode.onDblClick(" + event.type + "): dtnode:" + this + ", button:" + event.button + ", which: " + event.which); }, _onKeydown: function(event) { // this.tree.logDebug("dtnode.onKeydown(" + event.type + "): dtnode:" + this + ", charCode:" + event.charCode + ", keyCode: " + event.keyCode + ", which: " + event.which); var handled = true, sib; // alert("keyDown" + event.which); switch( event.which ) { // charCodes: // case 43: // '+' case 107: // '+' case 187: // '+' @ Chrome, Safari if( !this.bExpanded ){ this.toggleExpand(); } break; // case 45: // '-' case 109: // '-' case 189: // '+' @ Chrome, Safari if( this.bExpanded ){ this.toggleExpand(); } break; //~ case 42: // '*' //~ break; //~ case 47: // '/' //~ break; // case 13: // // on a focused tag seems to generate a click-event. // this._userActivate(); // break; case 32: // this._userActivate(); break; case 8: // if( this.parent ){ this.parent.focus(); } break; case 37: // if( this.bExpanded ) { this.toggleExpand(); this.focus(); // } else if( this.parent && (this.tree.options.rootVisible || this.parent.parent) ) { } else if( this.parent && this.parent.parent ) { this.parent.focus(); } break; case 39: // if( !this.bExpanded && (this.childList || this.data.isLazy) ) { this.toggleExpand(); this.focus(); } else if( this.childList ) { this.childList[0].focus(); } break; case 38: // sib = this.getPrevSibling(); while( sib && sib.bExpanded && sib.childList ){ sib = sib.childList[sib.childList.length-1]; } // if( !sib && this.parent && (this.tree.options.rootVisible || this.parent.parent) ) if( !sib && this.parent && this.parent.parent ){ sib = this.parent; } if( sib ){ sib.focus(); } break; case 40: // if( this.bExpanded && this.childList ) { sib = this.childList[0]; } else { var parents = this._parentList(false, true); for(var i=parents.length-1; i>=0; i--) { sib = parents[i].getNextSibling(); if( sib ){ break; } } } if( sib ){ sib.focus(); } break; default: handled = false; } // Return false, if handled, to prevent default processing // return !handled; if(handled){ event.preventDefault(); } }, _onKeypress: function(event) { // onKeypress is only hooked to allow user callbacks. // We don't process it, because IE and Safari don't fire keypress for cursor keys. // this.tree.logDebug("dtnode.onKeypress(" + event.type + "): dtnode:" + this + ", charCode:" + event.charCode + ", keyCode: " + event.keyCode + ", which: " + event.which); }, _onFocus: function(event) { // Handles blur and focus events. // this.tree.logDebug("dtnode._onFocus(%o): %o", event, this); var opts = this.tree.options; if ( event.type == "blur" || event.type == "focusout" ) { if ( opts.onBlur ){ opts.onBlur.call(this.tree, this); } if( this.tree.tnFocused ){ $(this.tree.tnFocused.span).removeClass(opts.classNames.focused); } this.tree.tnFocused = null; if( opts.persist ){ $.cookie(opts.cookieId+"-focus", "", opts.cookie); } } else if ( event.type=="focus" || event.type=="focusin") { // Fix: sometimes the blur event is not generated if( this.tree.tnFocused && this.tree.tnFocused !== this ) { this.tree.logDebug("dtnode.onFocus: out of sync: curFocus: %o", this.tree.tnFocused); $(this.tree.tnFocused.span).removeClass(opts.classNames.focused); } this.tree.tnFocused = this; if ( opts.onFocus ){ opts.onFocus.call(this.tree, this); } $(this.tree.tnFocused.span).addClass(opts.classNames.focused); if( opts.persist ){ $.cookie(opts.cookieId+"-focus", this.data.key, opts.cookie); } } // TODO: return anything? // return false; }, visit: function(fn, includeSelf) { // Call fn(node) for all child nodes. Stop iteration, if fn() returns false. var res = true; if( includeSelf === true ) { res = fn(this); if( res === false || res == "skip" ){ return res; } } if(this.childList){ for(var i=0, l=this.childList.length; i reloading %s...", this, keyPath, child); var self = this; // Note: this line gives a JSLint warning (Don't make functions within a loop) /*jshint loopfunc:true */ child.reloadChildren(function(node, isOk){ // After loading, look for direct child with that key if(isOk){ tree.logDebug("%s._loadKeyPath(%s) -> reloaded %s.", node, keyPath, node); callback.call(tree, child, "loaded"); node._loadKeyPath(segList.join(tree.options.keyPathSeparator), callback); }else{ tree.logWarning("%s._loadKeyPath(%s) -> reloadChildren() failed.", self, keyPath); callback.call(tree, child, "error"); } }); // we can ignore it, since it will only be exectuted once, the the loop is ended // See also http://stackoverflow.com/questions/3037598/how-to-get-around-the-jslint-error-dont-make-functions-within-a-loop } else { callback.call(tree, child, "loaded"); // Look for direct child with that key child._loadKeyPath(segList.join(tree.options.keyPathSeparator), callback); } return; } } } // Could not find key // Callback params: child: undefined, the segment, isEndNode (segList.length === 0) callback.call(tree, undefined, "notfound", seg, segList.length === 0); tree.logWarning("Node not found: " + seg); return; }, resetLazy: function() { // Discard lazy content. if( this.parent === null ){ throw "Use tree.reload() instead"; }else if( ! this.data.isLazy ){ throw "node.resetLazy() requires lazy nodes."; } this.expand(false); this.removeChildren(); }, _addChildNode: function(dtnode, beforeNode) { /** * Internal function to add one single DynatreeNode as a child. * */ var tree = this.tree, opts = tree.options, pers = tree.persistence; // tree.logDebug("%s._addChildNode(%o)", this, dtnode); // --- Update and fix dtnode attributes if necessary dtnode.parent = this; // if( beforeNode && (beforeNode.parent !== this || beforeNode === dtnode ) ) // throw " must be another child of "; // --- Add dtnode as a child if ( this.childList === null ) { this.childList = []; } else if( ! beforeNode ) { // Fix 'lastsib' if(this.childList.length > 0) { $(this.childList[this.childList.length-1].span).removeClass(opts.classNames.lastsib); } } if( beforeNode ) { var iBefore = $.inArray(beforeNode, this.childList); if( iBefore < 0 ){ throw " must be a child of "; } this.childList.splice(iBefore, 0, dtnode); } else { // Append node this.childList.push(dtnode); } // --- Handle persistence // Initial status is read from cookies, if persistence is active and // cookies are already present. // Otherwise the status is read from the data attributes and then persisted. var isInitializing = tree.isInitializing(); if( opts.persist && pers.cookiesFound && isInitializing ) { // Init status from cookies // tree.logDebug("init from cookie, pa=%o, dk=%o", pers.activeKey, dtnode.data.key); if( pers.activeKey === dtnode.data.key ){ tree.activeNode = dtnode; } if( pers.focusedKey === dtnode.data.key ){ tree.focusNode = dtnode; } dtnode.bExpanded = ($.inArray(dtnode.data.key, pers.expandedKeyList) >= 0); dtnode.bSelected = ($.inArray(dtnode.data.key, pers.selectedKeyList) >= 0); // tree.logDebug(" key=%o, bSelected=%o", dtnode.data.key, dtnode.bSelected); } else { // Init status from data (Note: we write the cookies after the init phase) // tree.logDebug("init from data"); if( dtnode.data.activate ) { tree.activeNode = dtnode; if( opts.persist ){ pers.activeKey = dtnode.data.key; } } if( dtnode.data.focus ) { tree.focusNode = dtnode; if( opts.persist ){ pers.focusedKey = dtnode.data.key; } } dtnode.bExpanded = ( dtnode.data.expand === true ); // Collapsed by default if( dtnode.bExpanded && opts.persist ){ pers.addExpand(dtnode.data.key); } dtnode.bSelected = ( dtnode.data.select === true ); // Deselected by default /* Doesn't work, cause pers.selectedKeyList may be null if( dtnode.bSelected && opts.selectMode==1 && pers.selectedKeyList && pers.selectedKeyList.length>0 ) { tree.logWarning("Ignored multi-selection in single-mode for %o", dtnode); dtnode.bSelected = false; // Fixing bad input data (multi selection for mode:1) } */ if( dtnode.bSelected && opts.persist ){ pers.addSelect(dtnode.data.key); } } // Always expand, if it's below minExpandLevel // tree.logDebug ("%s._addChildNode(%o), l=%o", this, dtnode, dtnode.getLevel()); if ( opts.minExpandLevel >= dtnode.getLevel() ) { // tree.logDebug ("Force expand for %o", dtnode); this.bExpanded = true; } // In multi-hier mode, update the parents selection state // issue #82: only if not initializing, because the children may not exist yet // if( !dtnode.data.isStatusNode && opts.selectMode==3 && !isInitializing ) // dtnode._fixSelectionState(); // In multi-hier mode, update the parents selection state if( dtnode.bSelected && opts.selectMode==3 ) { var p = this; while( p ) { if( !p.hasSubSel ){ p._setSubSel(true); } p = p.parent; } } // render this node and the new child if ( tree.bEnableUpdate ){ this.render(); } return dtnode; }, addChild: function(obj, beforeNode) { /** * Add a node object as child. * * This should be the only place, where a DynaTreeNode is constructed! * (Except for the root node creation in the tree constructor) * * @param obj A JS object (may be recursive) or an array of those. * @param {DynaTreeNode} beforeNode (optional) sibling node. * * Data format: array of node objects, with optional 'children' attributes. * [ * { title: "t1", isFolder: true, ... } * { title: "t2", isFolder: true, ..., * children: [ * {title: "t2.1", ..}, * {..} * ] * } * ] * A simple object is also accepted instead of an array. * */ // this.tree.logDebug("%s.addChild(%o, %o)", this, obj, beforeNode); if(typeof(obj) == "string"){ throw "Invalid data type for " + obj; }else if( !obj || obj.length === 0 ){ // Passed null or undefined or empty array return; }else if( obj instanceof DynaTreeNode ){ return this._addChildNode(obj, beforeNode); } if( !obj.length ){ // Passed a single data object obj = [ obj ]; } var prevFlag = this.tree.enableUpdate(false); var tnFirst = null; for (var i=0, l=obj.length; i is the request options // self.tree.logDebug("appendAjax().success"); var prevPhase = self.tree.phase; self.tree.phase = "init"; // postProcess is similar to the standard dataFilter hook, // but it is also called for JSONP if( options.postProcess ){ data = options.postProcess.call(this, data, this.dataType); } // Process ASPX WebMethod JSON object inside "d" property // http://code.google.com/p/dynatree/issues/detail?id=202 else if (data && data.hasOwnProperty("d")) { data = (typeof data.d) == "string" ? $.parseJSON(data.d) : data.d; } if(!$.isArray(data) || data.length !== 0){ self.addChild(data, null); } self.tree.phase = "postInit"; if( orgSuccess ){ orgSuccess.call(options, self, data, textStatus); } self.tree.logDebug("trigger " + eventType); self.tree.$tree.trigger(eventType, [self, true]); self.tree.phase = prevPhase; // This should be the last command, so node._isLoading is true // while the callbacks run self.setLazyNodeStatus(DTNodeStatus_Ok); if($.isArray(data) && data.length === 0){ // Set to [] which is interpreted as 'no children' for lazy // nodes self.childList = []; self.render(); } }, error: function(jqXHR, textStatus, errorThrown){ // is the request options self.tree.logWarning("appendAjax failed:", textStatus, ":\n", jqXHR, "\n", errorThrown); if( orgError ){ orgError.call(options, self, jqXHR, textStatus, errorThrown); } self.tree.$tree.trigger(eventType, [self, false]); self.setLazyNodeStatus(DTNodeStatus_Error, {info: textStatus, tooltip: "" + errorThrown}); } }); $.ajax(options); }, move: function(targetNode, mode) { /**Move this node to targetNode. * mode 'child': append this node as last child of targetNode. * This is the default. To be compatble with the D'n'd * hitMode, we also accept 'over'. * mode 'before': add this node as sibling before targetNode. * mode 'after': add this node as sibling after targetNode. */ var pos; if(this === targetNode){ return; } if( !this.parent ){ throw "Cannot move system root"; } if(mode === undefined || mode == "over"){ mode = "child"; } var prevParent = this.parent; var targetParent = (mode === "child") ? targetNode : targetNode.parent; if( targetParent.isDescendantOf(this) ){ throw "Cannot move a node to it's own descendant"; } // Unlink this node from current parent if( this.parent.childList.length == 1 ) { this.parent.childList = this.parent.data.isLazy ? [] : null; this.parent.bExpanded = false; } else { pos = $.inArray(this, this.parent.childList); if( pos < 0 ){ throw "Internal error"; } this.parent.childList.splice(pos, 1); } // Remove from source DOM parent if(this.parent.ul){ this.parent.ul.removeChild(this.li); } // Insert this node to target parent's child list this.parent = targetParent; if( targetParent.hasChildren() ) { switch(mode) { case "child": // Append to existing target children targetParent.childList.push(this); break; case "before": // Insert this node before target node pos = $.inArray(targetNode, targetParent.childList); if( pos < 0 ){ throw "Internal error"; } targetParent.childList.splice(pos, 0, this); break; case "after": // Insert this node after target node pos = $.inArray(targetNode, targetParent.childList); if( pos < 0 ){ throw "Internal error"; } targetParent.childList.splice(pos+1, 0, this); break; default: throw "Invalid mode " + mode; } } else { targetParent.childList = [ this ]; } // Parent has no