/************************************************************************* jquery.dynatree.js Dynamic tree view control, with support for lazy loading of branches. Copyright (c) 2008-2010, 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: 0.5.4$ $Revision: 329, 2010-05-05 08:04:39$ @depends: jquery.js @depends: ui.core.js @depends: jquery.cookie.js *************************************************************************/ /************************************************************************* * 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); } } catch(e) { if( !window.console ) _canLog = false; // Permanently disable, when logging is not supported by the browser } } function logMsg(msg) { Array.prototype.unshift.apply(arguments, ["debug"]); _log.apply(this, arguments); } // Forward declaration var getDynaTreePersistData = undefined; /************************************************************************* * 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); } } } /************************************************************************* * 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 == undefined ) data.key = "_" + tree._nodeCount++; this.data = $.extend({}, $.ui.dynatree.nodedatadefaults, data); this.div = null; // not yet created this.span = null; // not yet created this.childList = null; // no subnodes yet // this.isRead = false; // Lazy content not yet read this.isLoading = false; // Lazy content is being loaded this.hasSubSel = false; }, toString: function() { return "dtnode<" + 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; i0) || opts.minExpandLevel>1; var bHideFirstConnector = opts.rootVisible || opts.minExpandLevel>0; var res = ""; var p = this.parent; while( p ) { // Suppress first connector column, if visible top level is always expanded if ( bHideFirstConnector && p==rootParent ) break; res = ( p.isLastSibling() ? cache.tagEmpty : cache.tagVline) + res; p = p.parent; } //@modify liguocai 2011-11-30 让顶层元素也有展开收缩的功能 // connector (expanded, expandable or simple) //if( bHideFirstExpander && this.parent==rootParent ) { // skip connector //} else if ( this.childList || this.data.isLazy ) { res += cache.tagExpander; } else { res += cache.tagConnector; } // Checkbox mode if( opts.checkbox && this.data.hideCheckbox!=true && !this.data.isStatusNode ) { res += cache.tagCheckbox; } // folder or doctype icon if ( this.data.icon ) { res += ""; } else if ( this.data.icon == false ) { // icon == false means 'no icon' } else { // icon == null means 'default icon' res += cache.tagNodeIcon; } // node name var tooltip = ( this.data && typeof this.data.tooltip == "string" ) ? " title='" + this.data.tooltip + "'" : ""; res += "" + this.data.title + ""; return res; }, _fixOrder: function() { /** * Make sure, that
order matches childList order. */ var cl = this.childList; if( !cl ) return; var childDiv = this.div.firstChild.nextSibling; for(var i=0; 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 was fired on TIHS node. var tcn = event && event.target ? event.target.className : ""; var 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.folder)>=0 || tcn.indexOf(cns.document)>=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; ia").focus(); } catch(e) { } }, _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.span, 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 ) // Pass element as 'this' (jQuery convention) opts.onActivate.call(this.span, this); } else { // Deactivate if( this.tree.activeNode === this ) { var opts = this.tree.options; if ( opts.onQueryActivate && opts.onQueryActivate.call(this.span, 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.span, this); } } }, activate: function() { // Select - but not focus - this node. // this.tree.logDebug("dtnode.activate(): %o", this); this._activate(true, true); }, 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 && this.tree.options.minExpandLevel>0 ) { 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); } }, _fixSelectionState: function() { // fix selection status, for multi-hier mode // this.tree.logDebug("_fixSelectionState(%o) - %o", this.bSelected, this); if( this.bSelected ) { // Select all children this.visit(function(dtnode){ dtnode.parent._setSubSel(true); dtnode._select(true, false, false); }); // Select parents, if all children are selected var p = this.parent; while( p ) { p._setSubSel(true); var allChildsSelected = true; for(var i=0; iDIV", this.div).animate(opts.fx, duration); $(filter, this.div).animate(opts.fx, duration); } else { $(filter, this.div).toggle(); // var $d = $(">DIV", this.div); // this.tree.logDebug("_expand: got div, start toggle - %o", this); // $d.toggle(); } //*/ // this.tree.logDebug("_expand: end div toggle - %o", this); if ( opts.onExpand ) opts.onExpand.call(this.span, bExpand, this); }, expand: function(flag) { if( !this.childList && !this.data.isLazy && flag ) return; // Prevent expanding empty nodes if( this.parent == null && this.tree.options.minExpandLevel>0 && !flag ) return; // Prevent collapsing the root this._expand(flag); }, toggleExpand: function() { this.expand(!this.bExpanded); }, collapseSiblings: function() { if( this.parent == null ) return; var ac = this.parent.childList; for (var i=0; i jumps to the top return false; }, 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; // 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) ) { 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: // var sib = this.prevSibling(); while( sib && sib.bExpanded && sib.childList ) sib = sib.childList[sib.childList.length-1]; if( !sib && this.parent && (this.tree.options.rootVisible || this.parent.parent) ) sib = this.parent; if( sib ) sib.focus(); break; case 40: // var sib; 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].nextSibling(); if( sib ) break; } } if( sib ) sib.focus(); break; default: handled = false; } // Return false, if handled, to prevent default processing return !handled; }, 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 ) // Pass element as 'this' (jQuery convention) opts.onBlur.call(this.span, 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 ) // Pass element as 'this' (jQuery convention) opts.onFocus.call(this.span, 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, data, includeSelf) { // Call fn(dtnode, data) for all child nodes. Stop iteration, if fn() returns false. var n = 0; if( includeSelf == true ) { if( fn(this, data) == false ) return 1; n++; } if ( this.childList ) for (var i=0; i must be another child of "; // --- Add dtnode as a child if ( this.childList==null ) { this.childList = []; } else if( ! beforeNode ) { // Fix 'lastsib' $(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); // alert(this.childList); } 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 ("%o._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(true, true); 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("%o.addChild(%o, %o)", this, obj, beforeNode); if( !obj || obj.length==0 ) // Passed null or undefined or empty array return; 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; i is the request options // self.tree.logDebug("appendAjax().success"); var prevPhase = self.tree.phase; self.tree.phase = "init"; // self.append(data); self.addChild(data, null); self.tree.phase = "postInit"; self.setLazyNodeStatus(DTNodeStatus_Ok); if( orgSuccess ) orgSuccess.call(options, self); self.tree.phase = prevPhase; }, error: function(XMLHttpRequest, textStatus, errorThrown){ // is the request options // self.tree.logWarning("appendAjax failed: %o:\n%o\n%o", textStatus, XMLHttpRequest, errorThrown); self.tree.logWarning("appendAjax failed:", textStatus, ":\n", XMLHttpRequest, "\n", errorThrown); self.setLazyNodeStatus(DTNodeStatus_Error, {info: textStatus, tooltip: ""+errorThrown}); if( orgError ) orgError.call(options, self, XMLHttpRequest, textStatus, errorThrown); }, beforeSend:function(XMLHttpRequest ){ var cToken = $.cookie(headtoken) || token;XMLHttpRequest.setRequestHeader(headtoken,cToken); } }); $.ajax(options); }, // --- end of class lastentry: undefined } /************************************************************************* * class DynaTreeStatus */ var DynaTreeStatus = Class.create(); DynaTreeStatus._getTreePersistData = function(cookieId, cookieOpts) { // Static member: Return persistence information from cookies var ts = new DynaTreeStatus(cookieId, cookieOpts); ts.read(); return ts.toDict(); } // Make available in global scope getDynaTreePersistData = DynaTreeStatus._getTreePersistData; DynaTreeStatus.prototype = { // Constructor initialize: function(cookieId, cookieOpts) { this._log("DynaTreeStatus: initialize"); if( cookieId === undefined ) cookieId = $.ui.dynatree.defaults.cookieId; cookieOpts = $.extend({}, $.ui.dynatree.defaults.cookie, cookieOpts); this.cookieId = cookieId; this.cookieOpts = cookieOpts; this.cookiesFound = undefined; this.activeKey = null; this.focusedKey = null; this.expandedKeyList = null; this.selectedKeyList = null; }, // member functions _log: function(msg) { // this.logDebug("_changeNodeList(%o): nodeList:%o, idx:%o", mode, nodeList, idx); Array.prototype.unshift.apply(arguments, ["debug"]); _log.apply(this, arguments); }, read: function() { this._log("DynaTreeStatus: read"); // Read or init cookies. this.cookiesFound = false; var cookie = $.cookie(this.cookieId + "-active"); this.activeKey = ( cookie == null ) ? "" : cookie; if( cookie != null ) this.cookiesFound = true; cookie = $.cookie(this.cookieId + "-focus"); this.focusedKey = ( cookie == null ) ? "" : cookie; if( cookie != null ) this.cookiesFound = true; cookie = $.cookie(this.cookieId + "-expand"); this.expandedKeyList = ( cookie == null ) ? [] : cookie.split(","); if( cookie != null ) this.cookiesFound = true; cookie = $.cookie(this.cookieId + "-select"); this.selectedKeyList = ( cookie == null ) ? [] : cookie.split(","); if( cookie != null ) this.cookiesFound = true; }, write: function() { this._log("DynaTreeStatus: write"); $.cookie(this.cookieId + "-active", ( this.activeKey == null ) ? "" : this.activeKey, this.cookieOpts); $.cookie(this.cookieId + "-focus", ( this.focusedKey == null ) ? "" : this.focusedKey, this.cookieOpts); $.cookie(this.cookieId + "-expand", ( this.expandedKeyList == null ) ? "" : this.expandedKeyList.join(","), this.cookieOpts); $.cookie(this.cookieId + "-select", ( this.selectedKeyList == null ) ? "" : this.selectedKeyList.join(","), this.cookieOpts); }, addExpand: function(key) { this._log("addExpand(%o)", key); if( $.inArray(key, this.expandedKeyList) < 0 ) { this.expandedKeyList.push(key); $.cookie(this.cookieId + "-expand", this.expandedKeyList.join(","), this.cookieOpts); } }, clearExpand: function(key) { this._log("clearExpand(%o)", key); var idx = $.inArray(key, this.expandedKeyList); if( idx >= 0 ) { this.expandedKeyList.splice(idx, 1); $.cookie(this.cookieId + "-expand", this.expandedKeyList.join(","), this.cookieOpts); } }, addSelect: function(key) { this._log("addSelect(%o)", key); if( $.inArray(key, this.selectedKeyList) < 0 ) { this.selectedKeyList.push(key); $.cookie(this.cookieId + "-select", this.selectedKeyList.join(","), this.cookieOpts); } }, clearSelect: function(key) { this._log("clearSelect(%o)", key); var idx = $.inArray(key, this.selectedKeyList); if( idx >= 0 ) { this.selectedKeyList.splice(idx, 1); $.cookie(this.cookieId + "-select", this.selectedKeyList.join(","), this.cookieOpts); } }, isReloading: function() { return this.cookiesFound == true; }, toDict: function() { return { cookiesFound: this.cookiesFound, activeKey: this.activeKey, focusedKey: this.activeKey, expandedKeyList: this.expandedKeyList, selectedKeyList: this.selectedKeyList }; }, // --- end of class lastentry: undefined }; /************************************************************************* * class DynaTree */ var DynaTree = Class.create(); // --- Static members ---------------------------------------------------------- DynaTree.version = "$Version: 0.5.4$"; /* DynaTree._initTree = function() { }; DynaTree._bind = function() { }; */ //--- Class members ------------------------------------------------------------ DynaTree.prototype = { // Constructor // initialize: function(divContainer, options) { initialize: function($widget) { // instance members this.phase = "init"; this.$widget = $widget; this.options = $widget.options; this.$tree = $widget.element; // find container element this.divTree = this.$tree.get(0); }, // member functions _load: function() { var $widget = this.$widget; var opts = this.options; this.bEnableUpdate = true; this._nodeCount = 1; this.activeNode = null; this.focusNode = null; // If a 'options.classNames' dictionary was passed, still use defaults // for undefined classes: if( opts.classNames !== $.ui.dynatree.defaults.classNames ) { opts.classNames = $.extend({}, $.ui.dynatree.defaults.classNames, opts.classNames); } // Guess skin path, if not specified if(!opts.imagePath) { $("script").each( function () { // Eclipse syntax parser breaks on this expression, so put it at the bottom: if( this.src.search(_rexDtLibName) >= 0 ) { if( this.src.indexOf("/")>=0 ) // issue #47 opts.imagePath = this.src.slice(0, this.src.lastIndexOf("/")) + "/skin/"; else opts.imagePath = "skin/"; // logMsg("Guessing imagePath from '%s': '%s'", this.src, opts.imagePath); return false; // first match } }); } this.persistence = new DynaTreeStatus(opts.cookieId, opts.cookie); if( opts.persist ) { if( !$.cookie ) _log("warn", "Please include jquery.cookie.js to use persistence."); this.persistence.read(); } this.logDebug("DynaTree.persistence: %o", this.persistence.toDict()); // Cached tag strings this.cache = { tagEmpty: "", tagVline: "", tagExpander: "", tagConnector: "", tagNodeIcon: "", tagCheckbox: "", lastentry: undefined }; // Clear container, in case it contained some 'waiting' or 'error' text // for clients that don't support JS. // We don't do this however, if we try to load from an embedded UL element. if( opts.children || (opts.initAjax && opts.initAjax.url) || opts.initId ) $(this.divTree).empty(); else if( this.divRoot ) $(this.divRoot).remove(); // create the root element this.tnRoot = new DynaTreeNode(null, this, {title: opts.title, key: "root"}); this.tnRoot.data.isFolder = true; this.tnRoot.render(false, false); this.divRoot = this.tnRoot.div; this.divRoot.className = opts.classNames.container; // add root to container // TODO: this should be delayed until all children have been created for performance reasons this.divTree.appendChild(this.divRoot); var root = this.tnRoot; var isReloading = ( opts.persist && this.persistence.isReloading() ); var isLazy = false; var prevFlag = this.enableUpdate(false); this.logDebug("Dynatree._load(): read tree structure..."); // Init tree structure if( opts.children ) { // Read structure from node array root.addChild(opts.children); } else if( opts.initAjax && opts.initAjax.url ) { // Init tree from AJAX request isLazy = true; root.data.isLazy = true; this._reloadAjax(); } else if( opts.initId ) { // Init tree from another UL element this._createFromTag(root, $("#"+opts.initId)); } else { // Init tree from the first UL element inside the container
var $ul = this.$tree.find(">ul").hide(); this._createFromTag(root, $ul); $ul.remove(); } this._checkConsistency(); // Render html markup this.logDebug("Dynatree._load(): render nodes..."); this.enableUpdate(prevFlag); // bind event handlers this.logDebug("Dynatree._load(): bind events..."); this.$widget.bind(); // --- Post-load processing this.logDebug("Dynatree._load(): postInit..."); this.phase = "postInit"; // In persist mode, make sure that cookies are written, even if they are empty if( opts.persist ) { this.persistence.write(); } // Set focus, if possible (this will also fire an event and write a cookie) if( this.focusNode && this.focusNode.isVisible() ) { this.logDebug("Focus on init: %o", this.focusNode); this.focusNode.focus(); } if( !isLazy && opts.onPostInit ) { opts.onPostInit.call(this, isReloading, false); } this.phase = "idle"; }, _reloadAjax: function() { // Reload var opts = this.options; if( ! opts.initAjax || ! opts.initAjax.url ) throw "tree.reload() requires 'initAjax' mode."; var pers = this.persistence; var ajaxOpts = $.extend({}, opts.initAjax); // Append cookie info to the request // this.logDebug("reloadAjax: key=%o, an.key:%o", pers.activeKey, this.activeNode?this.activeNode.data.key:"?"); if( ajaxOpts.addActiveKey ) ajaxOpts.data.activeKey = pers.activeKey; if( ajaxOpts.addFocusedKey ) ajaxOpts.data.focusedKey = pers.focusedKey; if( ajaxOpts.addExpandedKeyList ) ajaxOpts.data.expandedKeyList = pers.expandedKeyList.join(","); if( ajaxOpts.addSelectedKeyList ) ajaxOpts.data.selectedKeyList = pers.selectedKeyList.join(","); // Set up onPostInit callback to be called when Ajax returns if( opts.onPostInit ) { if( ajaxOpts.success ) this.logWarning("initAjax: success callback is ignored when onPostInit was specified."); if( ajaxOpts.error ) this.logWarning("initAjax: error callback is ignored when onPostInit was specified."); var isReloading = pers.isReloading(); ajaxOpts["success"] = function(dtnode) { opts.onPostInit.call(dtnode.tree, isReloading, false); }; ajaxOpts["error"] = function(dtnode) { opts.onPostInit.call(dtnode.tree, isReloading, true); }; } this.logDebug("Dynatree._init(): send Ajax request..."); this.tnRoot.appendAjax(ajaxOpts); }, toString: function() { return "DynaTree '" + this.options.title + "'"; }, toDict: function() { return this.tnRoot.toDict(true); }, getPersistData: function() { return this.persistence.toDict(); }, logDebug: function(msg) { if( this.options.debugLevel >= 2 ) { Array.prototype.unshift.apply(arguments, ["debug"]); _log.apply(this, arguments); } }, logInfo: function(msg) { if( this.options.debugLevel >= 1 ) { Array.prototype.unshift.apply(arguments, ["info"]); _log.apply(this, arguments); } }, logWarning: function(msg) { Array.prototype.unshift.apply(arguments, ["warn"]); _log.apply(this, arguments); }, isInitializing: function() { return ( this.phase=="init" || this.phase=="postInit" ); }, isReloading: function() { return ( this.phase=="init" || this.phase=="postInit" ) && this.options.persist && this.persistence.cookiesFound; }, isUserEvent: function() { return ( this.phase=="userEvent" ); }, redraw: function() { this.logDebug("dynatree.redraw()..."); this.tnRoot.render(true, true); this.logDebug("dynatree.redraw() done."); }, reloadAjax: function() { this.logWarning("tree.reloadAjax() is deprecated since v0.5.2 (use reload() instead)."); }, reload: function() { this._load(); }, getRoot: function() { return this.tnRoot; }, getNodeByKey: function(key) { // $("#...") has problems, if the key contains '.', so we use getElementById() // return $("#" + this.options.idPrefix + key).attr("dtnode"); var el = document.getElementById(this.options.idPrefix + key); return ( el && el.dtnode ) ? el.dtnode : null; }, getActiveNode: function() { return this.activeNode; }, reactivate: function(setFocus) { // Re-fire onQueryActivate and onActivate events. var node = this.activeNode; // this.logDebug("reactivate %o", node); if( node ) { this.activeNode = null; // Force re-activating node.activate(); if( setFocus ) node.focus(); } }, getSelectedNodes: function(stopOnParents) { var nodeList = []; this.tnRoot.visit(function(dtnode){ if( dtnode.bSelected ) { nodeList.push(dtnode); if( stopOnParents == true ) return false; // stop processing this branch } }); return nodeList; }, activateKey: function(key) { var dtnode = (key === null) ? null : this.getNodeByKey(key); if( !dtnode ) { if( this.activeNode ) this.activeNode.deactivate(); this.activeNode = null; return null; } dtnode.focus(); dtnode.activate(); return dtnode; }, selectKey: function(key, select) { var dtnode = this.getNodeByKey(key); if( !dtnode ) return null; dtnode.select(select); return dtnode; }, enableUpdate: function(bEnable) { if ( this.bEnableUpdate==bEnable ) return bEnable; this.bEnableUpdate = bEnable; if ( bEnable ) this.redraw(); return !bEnable; // return previous value }, visit: function(fn, data, includeRoot) { return this.tnRoot.visit(fn, data, includeRoot); }, _createFromTag: function(parentTreeNode, $ulParent) { // Convert a
    ...
list into children of the parent tree node. var self = this; /* TODO: better? this.$lis = $("li:has(a[href])", this.element); this.$tabs = this.$lis.map(function() { return $("a", this)[0]; }); */ $ulParent.find(">li").each(function() { var $li = $(this); var $liSpan = $li.find(">span:first"); var title; if( $liSpan.length ) { // If a
  • tag is specified, use it literally. title = $liSpan.html(); } else { // If only a
  • tag is specified, use the trimmed string up to the next child
      tag. title = $li.html(); var iPos = title.search(/
        =0 ) title = $.trim(title.substring(0, iPos)); else title = $.trim(title); // self.logDebug("%o", title); } // Parse node options from ID, title and class attributes var data = { title: title, isFolder: $li.hasClass("folder"), isLazy: $li.hasClass("lazy"), expand: $li.hasClass("expanded"), select: $li.hasClass("selected"), activate: $li.hasClass("active"), focus: $li.hasClass("focused") }; if( $li.attr("title") ) data.tooltip = $li.attr("title"); if( $li.attr("id") ) data.key = $li.attr("id"); // If a data attribute is present, evaluate as a JavaScript object if( $li.attr("data") ) { var dataAttr = $.trim($li.attr("data")); if( dataAttr ) { if( dataAttr.charAt(0) != "{" ) dataAttr = "{" + dataAttr + "}" try { $.extend(data, eval("(" + dataAttr + ")")); } catch(e) { throw ("Error parsing node data: " + e + "\ndata:\n'" + dataAttr + "'"); } } } childNode = parentTreeNode.addChild(data); // Recursive reading of child nodes, if LI tag contains an UL tag var $ul = $li.find(">ul:first"); if( $ul.length ) { self._createFromTag(childNode, $ul); // must use 'self', because 'this' is the each() context } }); }, _checkConsistency: function() { // this.logDebug("tree._checkConsistency() NOT IMPLEMENTED - %o", this); }, // --- end of class lastentry: undefined }; /************************************************************************* * widget $(..).dynatree */ $.widget("ui.dynatree", { init: function() { // ui.core 1.6 renamed init() to _init(): this stub assures backward compatibility _log("warn", "ui.dynatree.init() was called; you should upgrade to ui.core.js v1.6 or higher."); return this._init(); }, _init: function() { if( parseFloat($.ui.version) < 1.8 ) { // jquery.ui.core 1.8 renamed _init() to _create(): this stub assures backward compatibility _log("info", "ui.dynatree._init() was called; consider upgrading to jquery.ui.core.js v1.8 or higher."); return this._create(); } // jquery.ui.core 1.8 still uses _init() to perform "default functionality" _log("debug", "ui.dynatree._init() was called; no current default functionality."); }, _create: function() { if( parseFloat($.ui.version) >= 1.8 ) { this.options = $.extend(true, {}, $[this.namespace][this.widgetName].defaults, this.options); } logMsg("Dynatree._create(): version='%s', debugLevel=%o.", DynaTree.version, this.options.debugLevel); var opts = this.options; // The widget framework supplies this.element and this.options. this.options.event += ".dynatree"; // namespace event var divTree = this.element.get(0); /* // Clear container, in case it contained some 'waiting' or 'error' text // for clients that don't support JS if( opts.children || (opts.initAjax && opts.initAjax.url) || opts.initId ) $(divTree).empty(); */ // Create the DynaTree object this.tree = new DynaTree(this); this.tree._load(); this.tree.logDebug("Dynatree._create(): done."); }, bind: function() { var $this = this.element; var o = this.options; // Prevent duplicate binding this.unbind(); // Tool function to get dtnode from the event target: function __getNodeFromElement(el) { var iMax = 5; while( el && iMax-- ) { if( el.dtnode ) return el.dtnode; el = el.parentNode; }; return null; } var eventNames = "click.dynatree dblclick.dynatree"; if( o.keyboard ) // Note: leading ' '! eventNames += " keypress.dynatree keydown.dynatree"; $this.bind(eventNames, function(event){ var dtnode = __getNodeFromElement(event.target); if( !dtnode ) return true; // Allow bubbling of other events var prevPhase = dtnode.tree.phase; dtnode.tree.phase = "userEvent"; try { dtnode.tree.logDebug("bind(%o): dtnode: %o", event, dtnode); switch(event.type) { case "click": return ( o.onClick && o.onClick(dtnode, event)===false ) ? false : dtnode.onClick(event); case "dblclick": return ( o.onDblClick && o.onDblClick(dtnode, event)===false ) ? false : dtnode.onDblClick(event); case "keydown": return ( o.onKeydown && o.onKeydown(dtnode, event)===false ) ? false : dtnode.onKeydown(event); case "keypress": return ( o.onKeypress && o.onKeypress(dtnode, event)===false ) ? false : dtnode.onKeypress(event); }; } catch(e) { var _ = null; // issue 117 // dtnode.tree.logError("bind(%o): dtnode: %o", event, dtnode); } finally { dtnode.tree.phase = prevPhase; } }); // focus/blur don't bubble, i.e. are not delegated to parent
        tags, // so we use the addEventListener capturing phase. // See http://www.howtocreate.co.uk/tutorials/javascript/domevents function __focusHandler(event) { // Handles blur and focus. // Fix event for IE: event = arguments[0] = $.event.fix( event || window.event ); var dtnode = __getNodeFromElement(event.target); return dtnode ? dtnode.onFocus(event) : false; } var div = this.tree.divTree; if( div.addEventListener ) { div.addEventListener("focus", __focusHandler, true); div.addEventListener("blur", __focusHandler, true); } else { div.onfocusin = div.onfocusout = __focusHandler; } // EVENTS // disable click if event is configured to something else // if (!(/^click/).test(o.event)) // this.$tabs.bind("click.tabs", function() { return false; }); }, unbind: function() { this.element.unbind(".dynatree"); }, /* TODO: we could handle option changes during runtime here (maybe to re-render, ...) setData: function(key, value) { this.tree.logDebug("dynatree.setData('" + key + "', '" + value + "')"); }, */ enable: function() { this.bind(); // Call default disable(): remove -disabled from css: $.widget.prototype.enable.apply(this, arguments); }, disable: function() { this.unbind(); // Call default disable(): add -disabled to css: $.widget.prototype.disable.apply(this, arguments); }, // --- getter methods (i.e. NOT returning a reference to $) getTree: function() { return this.tree; }, getRoot: function() { return this.tree.getRoot(); }, getActiveNode: function() { return this.tree.getActiveNode(); }, getSelectedNodes: function() { return this.tree.getSelectedNodes(); }, // ------------------------------------------------------------------------ lastentry: undefined }); // The following methods return a value (thus breaking the jQuery call chain): $.ui.dynatree.getter = "getTree getRoot getActiveNode getSelectedNodes"; // Plugin default options: $.ui.dynatree.defaults = { title: "Dynatree root", // Name of the root node. rootVisible: false, // Set to true, to make the root node visible. minExpandLevel: 1, // 1: root node is not collapsible imagePath: null, // Path to a folder containing icons. Defaults to 'skin/' subdirectory. children: null, // Init tree structure from this object array. initId: null, // Init tree structure from a
          element with this ID. initAjax: null, // Ajax options used to initialize the tree strucuture. autoFocus: true, // Set focus to first child, when expanding or lazy-loading. keyboard: true, // Support keyboard navigation. persist: false, // Persist expand-status to a cookie autoCollapse: false, // Automatically collapse all siblings, when a node is expanded. clickFolderMode: 3, // 1:activate, 2:expand, 3:activate and expand activeVisible: true, // Make sure, active nodes are visible (expanded). checkbox: false, // Show checkboxes. selectMode: 2, // 1:single, 2:multi, 3:multi-hier fx: null, // Animations, e.g. null or { height: "toggle", duration: 200 } // Low level event handlers: onEvent(dtnode, event): return false, to stop default processing onClick: null, // null: generate focus, expand, activate, select events. onDblClick: null, // (No default actions.) onKeydown: null, // null: generate keyboard navigation (focus, expand, activate). onKeypress: null, // (No default actions.) onFocus: null, // null: set focus to node. onBlur: null, // null: remove focus from node. // Pre-event handlers onQueryEvent(flag, dtnode): return false, to stop processing onQueryActivate: null, // Callback(flag, dtnode) before a node is (de)activated. onQuerySelect: null, // Callback(flag, dtnode) before a node is (de)selected. onQueryExpand: null, // Callback(flag, dtnode) before a node is expanded/collpsed. // High level event handlers onPostInit: null, // Callback(isReloading, isError) when tree was (re)loaded. onActivate: null, // Callback(dtnode) when a node is activated. onDeactivate: null, // Callback(dtnode) when a node is deactivated. onSelect: null, // Callback(flag, dtnode) when a node is (de)selected. onExpand: null, // Callback(dtnode) when a node is expanded/collapsed. onLazyRead: null, // Callback(dtnode) when a lazy node is expanded for the first time. ajaxDefaults: { // Used by initAjax option cache: false, // false: Append random '_' argument to the request url to prevent caching. dataType: "json" // Expect json format and pass json object to callbacks. }, strings: { loading: "Loading…", loadError: "Load error!" }, idPrefix: "ui-dynatree-id-", // Used to generate node id's like . // cookieId: "ui-dynatree-cookie", // Choose a more unique name, to allow multiple trees. cookieId: "dynatree", // Choose a more unique name, to allow multiple trees. cookie: { expires: null //7, // Days or Date; null: session cookie // path: "/", // Defaults to current page // domain: "jquery.com", // secure: true }, // Class names used, when rendering the HTML markup. // Note: if only single entries are passed for options.classNames, all other // values are still set to default. classNames: { container: "ui-dynatree-container", folder: "ui-dynatree-folder", document: "ui-dynatree-document", empty: "ui-dynatree-empty", vline: "ui-dynatree-vline", expander: "ui-dynatree-expander", connector: "ui-dynatree-connector", checkbox: "ui-dynatree-checkbox", nodeIcon: "ui-dynatree-icon", title: "ui-dynatree-title", nodeError: "ui-dynatree-statusnode-error", nodeWait: "ui-dynatree-statusnode-wait", hidden: "ui-dynatree-hidden", combinedExpanderPrefix: "ui-dynatree-exp-", combinedIconPrefix: "ui-dynatree-ico-", // disabled: "ui-dynatree-disabled", hasChildren: "ui-dynatree-has-children", active: "ui-dynatree-active", selected: "ui-dynatree-selected", expanded: "ui-dynatree-expanded", lazy: "ui-dynatree-lazy", focused: "ui-dynatree-focused", partsel: "ui-dynatree-partsel", lastsib: "ui-dynatree-lastsib" }, debugLevel: 1, // ------------------------------------------------------------------------ lastentry: undefined }; /** * Reserved data attributes for a tree node. */ $.ui.dynatree.nodedatadefaults = { title: null, // (required) Displayed name of the node (html is allowed here) key: null, // May be used with activate(), select(), find(), ... isFolder: false, // Use a folder icon. Also the node is expandable but not selectable. isLazy: false, // Call onLazyRead(), when the node is expanded for the first time to allow for delayed creation of children. tooltip: null, // Show this popup text. icon: null, // Use a custom image (filename relative to tree.options.imagePath). 'null' for default icon, 'false' for no icon. addClass: null, // Class name added to the node's span tag. activate: false, // Initial active status. focus: false, // Initial focused status. expand: false, // Initial expanded status. select: false, // Initial selected status. hideCheckbox: false, // Suppress checkbox display for this node. unselectable: false, // Prevent selection. // disabled: false, // The following attributes are only valid if passed to some functions: children: null, // Array of child nodes. // NOTE: we can also add custom attributes here. // This may then also be used in the onActivate(), onSelect() or onLazyTree() callbacks. // ------------------------------------------------------------------------ lastentry: undefined }; // --------------------------------------------------------------------------- })(jQuery); // Eclipse syntax parser breaks on this expression, so we put it at the bottom. var _rexDtLibName = /.*dynatree[^/]*\.js$/i;