PageRenderTime 98ms CodeModel.GetById 14ms app.highlight 69ms RepoModel.GetById 1ms app.codeStats 0ms

/static/scripts/jquery.dynatree.js

https://bitbucket.org/cistrome/cistrome-harvard/
JavaScript | 2034 lines | 1446 code | 204 blank | 384 comment | 428 complexity | cebbdac6e84b5d05b26e537eff9a82d1 MD5 | raw file

Large files files are truncated, but you can click here to view the full file

   1/*************************************************************************
   2	jquery.dynatree.js
   3	Dynamic tree view control, with support for lazy loading of branches.
   4
   5	Copyright (c) 2008-2010,  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: 0.5.4$
  13	$Revision: 329, 2010-05-05 08:04:39$
  14
  15 	@depends: jquery.js
  16 	@depends: ui.core.js
  17    @depends: jquery.cookie.js
  18*************************************************************************/
  19
  20
  21/*************************************************************************
  22 *	Debug functions
  23 */
  24
  25var _canLog = true;
  26
  27function _log(mode, msg) {
  28	/**
  29	 * Usage: logMsg("%o was toggled", this);
  30	 */
  31	if( !_canLog ) 
  32		return;
  33	// Remove first argument
  34	var args = Array.prototype.slice.apply(arguments, [1]); 
  35	// Prepend timestamp
  36	var dt = new Date();
  37	var tag = dt.getHours()+":"+dt.getMinutes()+":"+dt.getSeconds()+"."+dt.getMilliseconds();
  38	args[0] = tag + " - " + args[0];
  39
  40	try {
  41		switch( mode ) {
  42		case "info":
  43			window.console.info.apply(window.console, args);
  44			break;
  45		case "warn":
  46			window.console.warn.apply(window.console, args);
  47			break;
  48		default:
  49			window.console.log.apply(window.console, args);
  50		}
  51	} catch(e) {
  52		if( !window.console )
  53			_canLog = false; // Permanently disable, when logging is not supported by the browser 
  54	}
  55}
  56
  57function logMsg(msg) {
  58	Array.prototype.unshift.apply(arguments, ["debug"]);
  59	_log.apply(this, arguments);
  60}
  61
  62
  63// Forward declaration
  64var getDynaTreePersistData = undefined;
  65
  66
  67
  68/*************************************************************************
  69 *	Constants
  70 */
  71var DTNodeStatus_Error   = -1;
  72var DTNodeStatus_Loading = 1;
  73var DTNodeStatus_Ok      = 0;
  74
  75
  76// Start of local namespace
  77;(function($) {
  78
  79/*************************************************************************
  80 *	Common tool functions.
  81 */
  82
  83var Class = {
  84	create: function() {
  85		return function() {
  86			this.initialize.apply(this, arguments);
  87		}
  88	}
  89}
  90
  91/*************************************************************************
  92 *	Class DynaTreeNode
  93 */
  94var DynaTreeNode = Class.create();
  95
  96DynaTreeNode.prototype = {
  97	initialize: function(parent, tree, data) {
  98		/**
  99		 * @constructor 
 100		 */
 101		this.parent = parent; 
 102		this.tree = tree;
 103		if ( typeof data == "string" ) 
 104			data = { title: data };
 105		if( data.key == undefined )
 106			data.key = "_" + tree._nodeCount++;
 107		this.data = $.extend({}, $.ui.dynatree.nodedatadefaults, data);
 108		this.div = null; // not yet created
 109		this.span = null; // not yet created
 110		this.childList = null; // no subnodes yet
 111//		this.isRead = false; // Lazy content not yet read
 112		this.isLoading = false; // Lazy content is being loaded
 113		this.hasSubSel = false;
 114	},
 115
 116	toString: function() {
 117		return "dtnode<" + this.data.key + ">: '" + this.data.title + "'";
 118	},
 119
 120	toDict: function(recursive, callback) {
 121		var dict = $.extend({}, this.data);
 122		dict.activate = ( this.tree.activeNode === this );
 123		dict.focus = ( this.tree.focusNode === this );
 124		dict.expand = this.bExpanded;
 125		dict.select = this.bSelected;
 126		if( callback )
 127			callback(dict);
 128		if( recursive && this.childList ) {
 129			dict.children = [];
 130			for(var i=0; i<this.childList.length; i++ )
 131				dict.children.push(this.childList[i].toDict(true, callback));
 132		} else {
 133			delete dict.children;
 134		}
 135		return dict;
 136	},
 137
 138	_getInnerHtml: function() {
 139		var opts = this.tree.options;
 140		var cache = this.tree.cache;
 141		// parent connectors
 142		var rootParent = opts.rootVisible ? null : this.tree.tnRoot; 
 143		var bHideFirstExpander = (opts.rootVisible && opts.minExpandLevel>0) || opts.minExpandLevel>1;
 144		var bHideFirstConnector = opts.rootVisible || opts.minExpandLevel>0;
 145
 146		var res = "";
 147		var p = this.parent;
 148		while( p ) {
 149			// Suppress first connector column, if visible top level is always expanded
 150			if ( bHideFirstConnector && p==rootParent  )
 151				break;
 152			res = ( p.isLastSibling() ? cache.tagEmpty : cache.tagVline) + res;
 153			p = p.parent;
 154		}
 155
 156		// connector (expanded, expandable or simple)
 157		if( bHideFirstExpander && this.parent==rootParent ) { 
 158			// skip connector
 159		} else if ( this.childList || this.data.isLazy ) {
 160   			res += cache.tagExpander;
 161		} else {
 162   			res += cache.tagConnector;
 163		}
 164		
 165		// Checkbox mode
 166		if( opts.checkbox && this.data.hideCheckbox!=true && !this.data.isStatusNode ) {
 167   			res += cache.tagCheckbox;
 168		}
 169		
 170		// folder or doctype icon
 171   		if ( this.data.icon ) {
 172    		res += "<img src='" + opts.imagePath + this.data.icon + "' alt='' />";
 173   		} else if ( this.data.icon == false ) {
 174        	// icon == false means 'no icon'
 175		} else {
 176        	// icon == null means 'default icon'
 177   			res += cache.tagNodeIcon;
 178		}
 179
 180		// node name
 181		var tooltip = ( this.data && typeof this.data.tooltip == "string" ) ? " title='" + this.data.tooltip + "'" : "";
 182		res +=  "<a href='#' class='" + opts.classNames.title + "'" + tooltip + ">" + this.data.title + "</a>";
 183		return res;
 184	},
 185
 186	_fixOrder: function() {
 187		/**
 188		 * Make sure, that <div> order matches childList order.
 189		 */
 190		var cl = this.childList; 
 191		if( !cl )
 192			return;
 193		var childDiv = this.div.firstChild.nextSibling;
 194		for(var i=0; i<cl.length-1; i++) {
 195			var childNode1 = cl[i]; 
 196			var childNode2 = childDiv.firstChild.dtnode;
 197			if( childNode1 !== childNode2 ) {
 198				this.tree.logDebug("_fixOrder: mismatch at index " + i + ": " + childNode1 + " != " + childNode2);
 199				this.div.insertBefore(childNode1.div, childNode2.div);
 200			} else {
 201				childDiv = childDiv.nextSibling;
 202			}
 203		}
 204	},
 205	
 206	render: function(bDeep, bHidden) {
 207		/**
 208		 * Create HTML markup for this node.
 209		 * 
 210		 * <div> // This div contains the node's span and list of child div's.
 211		 *   <span id='key'>S S S A</span> // Span contains graphic spans and title <a> tag 
 212		 *   <div>child1</div>
 213		 *   <div>child2</div>
 214		 * </div>
 215		 */
 216//		this.tree.logDebug("%o.render()", this);
 217		var opts = this.tree.options;
 218		var cn = opts.classNames;
 219		var isLastSib = this.isLastSibling();
 220		// --- 
 221		if( ! this.div ) {
 222			this.span = document.createElement("span");
 223			this.span.dtnode = this;
 224			if( this.data.key )
 225				this.span.id = this.tree.options.idPrefix + this.data.key;
 226			this.div  = document.createElement("div");
 227			this.div.appendChild(this.span);
 228			
 229			if ( this.parent ) {
 230				this.parent.div.appendChild(this.div);
 231			}
 232
 233			if( this.parent==null && !this.tree.options.rootVisible )
 234				this.span.style.display = "none";
 235		}
 236		// set node connector images, links and text
 237		this.span.innerHTML = this._getInnerHtml();
 238
 239		// hide this node, if parent is collapsed
 240		this.div.style.display = ( this.parent==null || this.parent.bExpanded ? "" : "none");
 241
 242		// Set classes for current status
 243		var cnList = [];
 244		cnList.push( ( this.data.isFolder ) ? cn.folder : cn.document );
 245		if( this.bExpanded )
 246			cnList.push(cn.expanded);
 247		if( this.childList != null )
 248			cnList.push(cn.hasChildren);
 249		if( this.data.isLazy && this.childList==null )
 250			cnList.push(cn.lazy);
 251		if( isLastSib )
 252			cnList.push(cn.lastsib);
 253		if( this.bSelected )
 254			cnList.push(cn.selected);
 255		if( this.hasSubSel )
 256			cnList.push(cn.partsel);
 257		if( this.tree.activeNode === this )
 258			cnList.push(cn.active);
 259		if( this.data.addClass )
 260			cnList.push(this.data.addClass);
 261		// IE6 doesn't correctly evaluate multiple class names,
 262		// so we create combined class names that can be used in the CSS
 263		cnList.push(cn.combinedExpanderPrefix
 264				+ (this.bExpanded ? "e" : "c")
 265				+ (this.data.isLazy && this.childList==null ? "d" : "")
 266				+ (isLastSib ? "l" : "")
 267				);
 268		cnList.push(cn.combinedIconPrefix
 269				+ (this.bExpanded ? "e" : "c")
 270				+ (this.data.isFolder ? "f" : "")
 271				);
 272		this.span.className = cnList.join(" ");
 273
 274		if( bDeep && this.childList && (bHidden || this.bExpanded) ) {
 275			for(var i=0; i<this.childList.length; i++) {
 276				this.childList[i].render(bDeep, bHidden)
 277			}
 278			this._fixOrder();
 279		}
 280	},
 281
 282	hasChildren: function() {
 283		return this.childList != null;
 284	},
 285
 286	isLastSibling: function() {
 287		var p = this.parent;
 288		if ( !p ) return true;
 289		return p.childList[p.childList.length-1] === this;
 290	},
 291
 292	prevSibling: function() {
 293		if( !this.parent ) return null;
 294		var ac = this.parent.childList;
 295		for(var i=1; i<ac.length; i++) // start with 1, so prev(first) = null
 296			if( ac[i] === this )
 297				return ac[i-1];
 298		return null;
 299	},
 300
 301	nextSibling: function() {
 302		if( !this.parent ) return null;
 303		var ac = this.parent.childList;
 304		for(var i=0; i<ac.length-1; i++) // up to length-2, so next(last) = null
 305			if( ac[i] === this )
 306				return ac[i+1];
 307		return null;
 308	},
 309
 310	_setStatusNode: function(data) {
 311		// Create, modify or remove the status child node (pass 'null', to remove it).
 312		var firstChild = ( this.childList ? this.childList[0] : null );
 313		if( !data ) {
 314			if ( firstChild ) {
 315				this.div.removeChild(firstChild.div);
 316				if( this.childList.length == 1 )
 317					this.childList = null;
 318				else
 319					this.childList.shift();
 320			}
 321		} else if ( firstChild ) {
 322			data.isStatusNode = true;
 323			firstChild.data = data;
 324			firstChild.render(false, false);
 325		} else {
 326			data.isStatusNode = true;
 327			firstChild = this.addChild(data);
 328		}
 329	},
 330
 331	setLazyNodeStatus: function(lts, opts) {
 332		var tooltip = (opts && opts.tooltip) ? opts.tooltip : null;
 333		var info = (opts && opts.info) ? " (" + opts.info + ")" : "";
 334		switch( lts ) {
 335			case DTNodeStatus_Ok:
 336				this._setStatusNode(null);
 337//				this.isRead = true;
 338				this.isLoading = false;
 339				this.render(false, false);
 340				if( this.tree.options.autoFocus ) {
 341					if( this === this.tree.tnRoot && !this.tree.options.rootVisible && this.childList ) {
 342						// special case: using ajaxInit
 343						this.childList[0].focus();
 344					} else {
 345						this.focus();
 346					}
 347				}
 348				break;
 349			case DTNodeStatus_Loading:
 350				this.isLoading = true;
 351				this._setStatusNode({
 352					title: this.tree.options.strings.loading + info,
 353					tooltip: tooltip,
 354					addClass: this.tree.options.classNames.nodeWait
 355				});
 356				break;
 357			case DTNodeStatus_Error:
 358				this.isLoading = false;
 359				this._setStatusNode({
 360					title: this.tree.options.strings.loadError + info,
 361					tooltip: tooltip,
 362					addClass: this.tree.options.classNames.nodeError
 363				});
 364				break;
 365			default:
 366				throw "Bad LazyNodeStatus: '" + lts + "'.";
 367		}
 368	},
 369
 370	_parentList: function(includeRoot, includeSelf) {
 371		var l = [];
 372		var dtn = includeSelf ? this : this.parent;
 373		while( dtn ) {
 374			if( includeRoot || dtn.parent )
 375				l.unshift(dtn);
 376			dtn = dtn.parent;
 377		};
 378		return l;
 379	},
 380
 381	getLevel: function() {
 382		var level = 0;
 383		var dtn = this.parent;
 384		while( dtn ) {
 385			level++;
 386			dtn = dtn.parent;
 387		};
 388		return level;
 389	},
 390
 391	_getTypeForOuterNodeEvent: function(event) {
 392		/** Return the inner node span (title, checkbox or expander) if 
 393		 *  event.target points to the outer span.
 394		 *  This function should fix issue #93: 
 395		 *  FF2 ignores empty spans, when generating events (returning the parent instead).
 396		 */ 
 397		var cns = this.tree.options.classNames;
 398	    var target = event.target;
 399	    // Only process clicks on an outer node span (probably due to a FF2 event handling bug)
 400	    if( target.className.indexOf(cns.folder)<0 
 401	    	&& target.className.indexOf(cns.document)<0 ) {
 402	        return null
 403	    }
 404	    // Event coordinates, relative to outer node span:
 405	    var eventX = event.pageX - target.offsetLeft;
 406	    var eventY = event.pageY - target.offsetTop;
 407	    
 408	    for(var i=0; i<target.childNodes.length; i++) {
 409	        var cn = target.childNodes[i];
 410	        var x = cn.offsetLeft - target.offsetLeft;
 411	        var y = cn.offsetTop - target.offsetTop;
 412	        var nx = cn.clientWidth, ny = cn.clientHeight;
 413//	        alert (cn.className + ": " + x + ", " + y + ", s:" + nx + ", " + ny);
 414	        if( eventX>=x && eventX<=(x+nx) && eventY>=y && eventY<=(y+ny) ) {
 415//	            alert("HIT "+ cn.className);
 416	            if( cn.className==cns.title ) 
 417	            	return "title";
 418	            else if( cn.className==cns.expander ) 
 419	            	return "expander";
 420	            else if( cn.className==cns.checkbox ) 
 421	            	return "checkbox";
 422	            else if( cn.className==cns.nodeIcon ) 
 423	            	return "icon";
 424	        }
 425	    }
 426	    return "prefix";
 427	},
 428
 429	getEventTargetType: function(event) {
 430		// Return the part of a node, that a click event occured on.
 431		// Note: there is no check, if the was fired on TIHS node. 
 432		var tcn = event && event.target ? event.target.className : "";
 433		var cns = this.tree.options.classNames;
 434
 435		if( tcn == cns.title )
 436			return "title";
 437		else if( tcn==cns.expander )
 438			return "expander";
 439		else if( tcn==cns.checkbox )
 440			return "checkbox";
 441		else if( tcn==cns.nodeIcon )
 442			return "icon";
 443		else if( tcn==cns.empty || tcn==cns.vline || tcn==cns.connector )
 444			return "prefix";
 445		else if( tcn.indexOf(cns.folder)>=0 || tcn.indexOf(cns.document)>=0 )
 446			// FIX issue #93
 447			return this._getTypeForOuterNodeEvent(event);
 448		return null;
 449	},
 450
 451	isVisible: function() {
 452		// Return true, if all parents are expanded.
 453		var parents = this._parentList(true, false);
 454		for(var i=0; i<parents.length; i++)
 455			if( ! parents[i].bExpanded ) return false;
 456		return true;
 457	},
 458
 459	makeVisible: function() {
 460		// Make sure, all parents are expanded
 461		var parents = this._parentList(true, false);
 462		for(var i=0; i<parents.length; i++)
 463			parents[i]._expand(true);
 464	},
 465
 466	focus: function() {
 467		// TODO: check, if we already have focus
 468//		this.tree.logDebug("dtnode.focus(): %o", this);
 469		this.makeVisible();
 470		try {
 471			$(this.span).find(">a").focus();
 472		} catch(e) { }
 473	},
 474
 475	_activate: function(flag, fireEvents) {
 476		// (De)Activate - but not focus - this node.
 477		this.tree.logDebug("dtnode._activate(%o, fireEvents=%o) - %o", flag, fireEvents, this);
 478		var opts = this.tree.options;
 479		if( this.data.isStatusNode )
 480			return;
 481		if ( fireEvents && opts.onQueryActivate && opts.onQueryActivate.call(this.span, flag, this) == false )
 482			return; // Callback returned false
 483		
 484		if( flag ) {
 485			// Activate
 486			if( this.tree.activeNode ) {
 487				if( this.tree.activeNode === this )
 488					return;
 489				this.tree.activeNode.deactivate();
 490			}
 491			if( opts.activeVisible )
 492				this.makeVisible();
 493			this.tree.activeNode = this;
 494	        if( opts.persist )
 495				$.cookie(opts.cookieId+"-active", this.data.key, opts.cookie);
 496	        this.tree.persistence.activeKey = this.data.key;
 497			$(this.span).addClass(opts.classNames.active);
 498			if ( fireEvents && opts.onActivate ) // Pass element as 'this' (jQuery convention)
 499				opts.onActivate.call(this.span, this);
 500		} else {
 501			// Deactivate
 502			if( this.tree.activeNode === this ) {
 503				var opts = this.tree.options;
 504				if ( opts.onQueryActivate && opts.onQueryActivate.call(this.span, false, this) == false )
 505					return; // Callback returned false
 506				$(this.span).removeClass(opts.classNames.active);
 507		        if( opts.persist ) {
 508		        	// Note: we don't pass null, but ''. So the cookie is not deleted.
 509		        	// If we pass null, we also have to pass a COPY of opts, because $cookie will override opts.expires (issue 84)
 510					$.cookie(opts.cookieId+"-active", "", opts.cookie);
 511		        }
 512		        this.tree.persistence.activeKey = null;
 513				this.tree.activeNode = null;
 514				if ( fireEvents && opts.onDeactivate )
 515					opts.onDeactivate.call(this.span, this);
 516			}
 517		}
 518	},
 519
 520	activate: function() {
 521		// Select - but not focus - this node.
 522//		this.tree.logDebug("dtnode.activate(): %o", this);
 523		this._activate(true, true);
 524	},
 525
 526	deactivate: function() {
 527//		this.tree.logDebug("dtnode.deactivate(): %o", this);
 528		this._activate(false, true);
 529	},
 530
 531	isActive: function() {
 532		return (this.tree.activeNode === this);
 533	},
 534	
 535	_userActivate: function() {
 536		// Handle user click / [space] / [enter], according to clickFolderMode.
 537		var activate = true;
 538		var expand = false;
 539		if ( this.data.isFolder ) {
 540			switch( this.tree.options.clickFolderMode ) {
 541			case 2:
 542				activate = false;
 543				expand = true;
 544				break;
 545			case 3:
 546				activate = expand = true;
 547				break;
 548			}
 549		}
 550		if( this.parent == null && this.tree.options.minExpandLevel>0 ) {
 551			expand = false;
 552		}
 553		if( expand ) {
 554			this.toggleExpand();
 555			this.focus();
 556		} 
 557		if( activate ) {
 558			this.activate();
 559		}
 560	},
 561
 562	_setSubSel: function(hasSubSel) {
 563		if( hasSubSel ) {
 564			this.hasSubSel = true;
 565			$(this.span).addClass(this.tree.options.classNames.partsel);
 566		} else {
 567			this.hasSubSel = false;
 568			$(this.span).removeClass(this.tree.options.classNames.partsel);
 569		}
 570	},
 571
 572	_fixSelectionState: function() {
 573		// fix selection status, for multi-hier mode 
 574//		this.tree.logDebug("_fixSelectionState(%o) - %o", this.bSelected, this);
 575		if( this.bSelected ) {
 576			// Select all children
 577			this.visit(function(dtnode){
 578				dtnode.parent._setSubSel(true);
 579				dtnode._select(true, false, false);
 580			});
 581			// Select parents, if all children are selected
 582			var p = this.parent;
 583			while( p ) {
 584				p._setSubSel(true);
 585				var allChildsSelected = true;
 586				for(var i=0; i<p.childList.length;  i++) {
 587					var n = p.childList[i]; 
 588					if( !n.bSelected && !n.data.isStatusNode ) {
 589						allChildsSelected = false;
 590						break;
 591					}
 592				}
 593				if( allChildsSelected )
 594					p._select(true, false, false);
 595				p = p.parent;
 596			}
 597		} else {
 598			// Deselect all children
 599			this._setSubSel(false);
 600			this.visit(function(dtnode){
 601				dtnode._setSubSel(false);
 602				dtnode._select(false, false, false);
 603			});
 604			// Deselect parents, and recalc hasSubSel
 605			var p = this.parent;
 606			while( p ) {
 607				p._select(false, false, false);
 608				var isPartSel = false;
 609				for(var i=0; i<p.childList.length;  i++) {
 610					if( p.childList[i].bSelected || p.childList[i].hasSubSel ) {
 611						isPartSel = true;
 612						break;
 613					}
 614				}
 615				p._setSubSel(isPartSel);
 616				p = p.parent;
 617			}
 618		}
 619	},
 620	
 621	_select: function(sel, fireEvents, deep) {
 622		// Select - but not focus - this node.
 623//		this.tree.logDebug("dtnode._select(%o) - %o", sel, this);
 624		var opts = this.tree.options;
 625		if( this.data.isStatusNode )
 626			return;
 627		// 
 628		if( this.bSelected == sel ) {
 629//			this.tree.logDebug("dtnode._select(%o) IGNORED - %o", sel, this);
 630			return;
 631		}
 632		// Allow event listener to abort selection
 633		if ( fireEvents && opts.onQuerySelect && opts.onQuerySelect.call(this.span, sel, this) == false )
 634			return; // Callback returned false
 635		
 636		// Force single-selection
 637		if( opts.selectMode==1 && sel ) { 
 638			this.tree.visit(function(dtnode){
 639				if( dtnode.bSelected ) {
 640					// Deselect; assuming that in selectMode:1 there's max. one other selected node
 641					dtnode._select(false, false, false);
 642					return false;
 643				}
 644			});
 645		}
 646
 647		this.bSelected = sel;
 648//        this.tree._changeNodeList("select", this, sel);
 649  
 650		if( sel ) {
 651			if( opts.persist )
 652				this.tree.persistence.addSelect(this.data.key);
 653
 654			$(this.span).addClass(opts.classNames.selected);
 655
 656			if( deep && opts.selectMode==3 )
 657				this._fixSelectionState();
 658
 659			if ( fireEvents && opts.onSelect )
 660				opts.onSelect.call(this.span, true, this);
 661
 662		} else {
 663			if( opts.persist )
 664				this.tree.persistence.clearSelect(this.data.key);
 665			
 666			$(this.span).removeClass(opts.classNames.selected);
 667
 668	    	if( deep && opts.selectMode==3 )
 669				this._fixSelectionState();
 670
 671	    	if ( fireEvents && opts.onSelect )
 672				opts.onSelect.call(this.span, false, this);
 673		}
 674	},
 675
 676	select: function(sel) {
 677		// Select - but not focus - this node.
 678//		this.tree.logDebug("dtnode.select(%o) - %o", sel, this);
 679		if( this.data.unselectable )
 680			return this.bSelected;
 681		return this._select(sel!=false, true, true);
 682	},
 683
 684	toggleSelect: function() {
 685//		this.tree.logDebug("dtnode.toggleSelect() - %o", this);
 686		return this.select(!this.bSelected);
 687	},
 688
 689	isSelected: function() {
 690		return this.bSelected;
 691	},
 692	
 693	_loadContent: function() {
 694		try {
 695			var opts = this.tree.options;
 696			this.tree.logDebug("_loadContent: start - %o", this);
 697			this.setLazyNodeStatus(DTNodeStatus_Loading);
 698			if( true == opts.onLazyRead.call(this.span, this) ) {
 699				// If function returns 'true', we assume that the loading is done:
 700				this.setLazyNodeStatus(DTNodeStatus_Ok);
 701				// Otherwise (i.e. if the loading was started as an asynchronous process)
 702				// the onLazyRead(dtnode) handler is expected to call dtnode.setLazyNodeStatus(DTNodeStatus_Ok/_Error) when done.
 703				this.tree.logDebug("_loadContent: succeeded - %o", this);
 704			}
 705		} catch(e) {
 706//			alert(e);
 707			this.setLazyNodeStatus(DTNodeStatus_Error);
 708			this.tree.logWarning("_loadContent: failed - %o", e);
 709		}
 710	},
 711	
 712	_expand: function(bExpand) {
 713//		this.tree.logDebug("dtnode._expand(%o) - %o", bExpand, this);
 714		if( this.bExpanded == bExpand ) {
 715//			this.tree.logDebug("dtnode._expand(%o) IGNORED - %o", bExpand, this);
 716			return;
 717		}
 718		var opts = this.tree.options;
 719		if( !bExpand && this.getLevel()<opts.minExpandLevel ) {
 720			this.tree.logDebug("dtnode._expand(%o) forced expand - %o", bExpand, this);
 721			return;
 722		}
 723		if ( opts.onQueryExpand && opts.onQueryExpand.call(this.span, bExpand, this) == false )
 724			return; // Callback returned false
 725		this.bExpanded = bExpand;
 726
 727		// Persist expand state
 728		if( opts.persist ) {
 729	        if( bExpand )
 730				this.tree.persistence.addExpand(this.data.key);
 731			else 
 732				this.tree.persistence.clearExpand(this.data.key);
 733		}
 734
 735		this.render(false);
 736
 737        // Auto-collapse mode: collapse all siblings
 738		if( this.bExpanded && this.parent && opts.autoCollapse ) {
 739			var parents = this._parentList(false, true);
 740			for(var i=0; i<parents.length; i++)
 741				parents[i].collapseSiblings();
 742		}
 743
 744		// If the currently active node is now hidden, deactivate it
 745		if( opts.activeVisible && this.tree.activeNode && ! this.tree.activeNode.isVisible() ) {
 746			this.tree.activeNode.deactivate();
 747		}
 748		// Expanding a lazy node: set 'loading...' and call callback
 749		if( bExpand && this.data.isLazy && this.childList==null && !this.isLoading ) {
 750			this._loadContent();
 751			return;
 752		}
 753//		this.tree.logDebug("_expand: start div toggle - %o", this);
 754
 755		var fxDuration = opts.fx ? (opts.fx.duration || 200) : 0;
 756		if( this.childList ) {
 757			for(var i=0; i<this.childList.length; i++ ) {
 758				var $child = $(this.childList[i].div);
 759				if( fxDuration ) {
 760					// This is a toggle, so only do it, if not already rendered (in)visible (issue 98)
 761					if( bExpand != $child.is(':visible') )
 762						$child.animate(opts.fx, fxDuration);
 763				} else {
 764					if( bExpand )
 765						$child.show();
 766					else
 767						$child.hide(); // TODO: this seems to be slow, when called the first time for an element
 768				}
 769			}
 770		}
 771		
 772/* issue 109: using selector filter is really SLOW.
 773		// issue 98: only toggle, if render hasn't set visibility already:
 774		var filter = ">DIV" + (bExpand ? ":hidden" : ":visible");
 775		
 776		if( opts.fx ) {
 777			var duration = opts.fx.duration || 200;
 778//			$(">DIV", this.div).animate(opts.fx, duration);
 779			$(filter, this.div).animate(opts.fx, duration);
 780		} else {
 781			$(filter, this.div).toggle();
 782//			var $d = $(">DIV", this.div);
 783//			this.tree.logDebug("_expand: got div, start toggle - %o", this);
 784//			$d.toggle();
 785		}
 786//*/
 787//		this.tree.logDebug("_expand: end div toggle - %o", this);
 788
 789		if ( opts.onExpand )
 790			opts.onExpand.call(this.span, bExpand, this);
 791	},
 792
 793	expand: function(flag) {
 794		if( !this.childList && !this.data.isLazy && flag )
 795			return; // Prevent expanding empty nodes
 796		if( this.parent == null && this.tree.options.minExpandLevel>0 && !flag )
 797			return; // Prevent collapsing the root
 798		this._expand(flag);
 799	},
 800
 801	toggleExpand: function() {
 802		this.expand(!this.bExpanded);
 803	},
 804
 805	collapseSiblings: function() {
 806		if( this.parent == null )
 807			return;
 808		var ac = this.parent.childList;
 809		for (var i=0; i<ac.length; i++) {
 810			if ( ac[i] !== this && ac[i].bExpanded )
 811				ac[i]._expand(false);
 812		}
 813	},
 814
 815	onClick: function(event) {
 816//		this.tree.logDebug("dtnode.onClick(" + event.type + "): dtnode:" + this + ", button:" + event.button + ", which: " + event.which);
 817		var targetType = this.getEventTargetType(event);
 818		if( targetType == "expander" ) {
 819			// Clicking the expander icon always expands/collapses
 820			this.toggleExpand();
 821			this.focus(); // issue 95
 822		} else if( targetType == "checkbox" ) {
 823			// Clicking the checkbox always (de)selects
 824			this.toggleSelect();
 825			this.focus(); // issue 95
 826		} else {
 827			this._userActivate();
 828			// Chrome and Safari don't focus the a-tag on click
 829			this.span.getElementsByTagName("a")[0].focus();
 830		}
 831		// Make sure that clicks stop, otherwise <a href='#'> jumps to the top
 832		return false;
 833	},
 834
 835	onDblClick: function(event) {
 836//		this.tree.logDebug("dtnode.onDblClick(" + event.type + "): dtnode:" + this + ", button:" + event.button + ", which: " + event.which);
 837	},
 838
 839	onKeydown: function(event) {
 840//		this.tree.logDebug("dtnode.onKeydown(" + event.type + "): dtnode:" + this + ", charCode:" + event.charCode + ", keyCode: " + event.keyCode + ", which: " + event.which);
 841		var handled = true;
 842//		alert("keyDown" + event.which);
 843
 844		switch( event.which ) {
 845			// charCodes:
 846//			case 43: // '+'
 847			case 107: // '+'
 848			case 187: // '+' @ Chrome, Safari
 849				if( !this.bExpanded ) this.toggleExpand();
 850				break;
 851//			case 45: // '-'
 852			case 109: // '-'
 853			case 189: // '+' @ Chrome, Safari
 854				if( this.bExpanded ) this.toggleExpand();
 855				break;
 856			//~ case 42: // '*'
 857				//~ break;
 858			//~ case 47: // '/'
 859				//~ break;
 860			// case 13: // <enter>
 861				// <enter> on a focused <a> tag seems to generate a click-event. 
 862				// this._userActivate();
 863				// break;
 864			case 32: // <space>
 865				this._userActivate();
 866				break;
 867			case 8: // <backspace>
 868				if( this.parent )
 869					this.parent.focus();
 870				break;
 871			case 37: // <left>
 872				if( this.bExpanded ) {
 873					this.toggleExpand();
 874					this.focus();
 875				} else if( this.parent && (this.tree.options.rootVisible || this.parent.parent) ) {
 876					this.parent.focus();
 877				}
 878				break;
 879			case 39: // <right>
 880				if( !this.bExpanded && (this.childList || this.data.isLazy) ) {
 881					this.toggleExpand();
 882					this.focus();
 883				} else if( this.childList ) {
 884					this.childList[0].focus();
 885				}
 886				break;
 887			case 38: // <up>
 888				var sib = this.prevSibling();
 889				while( sib && sib.bExpanded && sib.childList )
 890					sib = sib.childList[sib.childList.length-1];
 891				if( !sib && this.parent && (this.tree.options.rootVisible || this.parent.parent) )
 892					sib = this.parent;
 893				if( sib ) sib.focus();
 894				break;
 895			case 40: // <down>
 896				var sib;
 897				if( this.bExpanded && this.childList ) {
 898					sib = this.childList[0];
 899				} else {
 900					var parents = this._parentList(false, true);
 901					for(var i=parents.length-1; i>=0; i--) {
 902						sib = parents[i].nextSibling();
 903						if( sib ) break;
 904					}
 905				}
 906				if( sib ) sib.focus();
 907				break;
 908			default:
 909				handled = false;
 910		}
 911		// Return false, if handled, to prevent default processing
 912		return !handled; 
 913	},
 914
 915	onKeypress: function(event) {
 916		// onKeypress is only hooked to allow user callbacks.
 917		// We don't process it, because IE and Safari don't fire keypress for cursor keys.
 918//		this.tree.logDebug("dtnode.onKeypress(" + event.type + "): dtnode:" + this + ", charCode:" + event.charCode + ", keyCode: " + event.keyCode + ", which: " + event.which);
 919	},
 920	
 921	onFocus: function(event) {
 922		// Handles blur and focus events.
 923//		this.tree.logDebug("dtnode.onFocus(%o): %o", event, this);
 924		var opts = this.tree.options;
 925		if ( event.type=="blur" || event.type=="focusout" ) {
 926			if ( opts.onBlur ) // Pass element as 'this' (jQuery convention)
 927				opts.onBlur.call(this.span, this);
 928			if( this.tree.tnFocused )
 929				$(this.tree.tnFocused.span).removeClass(opts.classNames.focused);
 930			this.tree.tnFocused = null;
 931	        if( opts.persist ) 
 932				$.cookie(opts.cookieId+"-focus", "", opts.cookie);
 933		} else if ( event.type=="focus" || event.type=="focusin") {
 934			// Fix: sometimes the blur event is not generated
 935			if( this.tree.tnFocused && this.tree.tnFocused !== this ) {
 936				this.tree.logDebug("dtnode.onFocus: out of sync: curFocus: %o", this.tree.tnFocused);
 937				$(this.tree.tnFocused.span).removeClass(opts.classNames.focused);
 938			}
 939			this.tree.tnFocused = this;
 940			if ( opts.onFocus ) // Pass element as 'this' (jQuery convention)
 941				opts.onFocus.call(this.span, this);
 942			$(this.tree.tnFocused.span).addClass(opts.classNames.focused);
 943	        if( opts.persist )
 944				$.cookie(opts.cookieId+"-focus", this.data.key, opts.cookie);
 945		}
 946		// TODO: return anything?
 947//		return false;
 948	},
 949
 950	visit: function(fn, data, includeSelf) {
 951		// Call fn(dtnode, data) for all child nodes. Stop iteration, if fn() returns false.
 952		var n = 0;
 953		if( includeSelf == true ) {
 954			if( fn(this, data) == false )
 955				return 1; 
 956			n++; 
 957		}
 958		if ( this.childList )
 959			for (var i=0; i<this.childList.length; i++)
 960				n += this.childList[i].visit(fn, data, true);
 961		return n;
 962	},
 963
 964	remove: function() {
 965        // Remove this node
 966//		this.tree.logDebug ("%o.remove()", this);
 967        if ( this === this.tree.root )
 968            return false;
 969        return this.parent.removeChild(this);
 970	},
 971
 972	removeChild: function(tn) {
 973		// Remove tn from list of direct children.
 974		var ac = this.childList;
 975		if( ac.length == 1 ) {
 976			if( tn !== ac[0] )
 977				throw "removeChild: invalid child";
 978			return this.removeChildren();
 979		}
 980        if( tn === this.tree.activeNode )
 981        	tn.deactivate();
 982        if( this.tree.options.persist ) {
 983	        if( tn.bSelected )
 984	            this.tree.persistence.clearSelect(tn.data.key);
 985	        if ( tn.bExpanded )
 986	            this.tree.persistence.clearExpand(tn.data.key);
 987        }
 988		tn.removeChildren(true);
 989		this.div.removeChild(tn.div);
 990		for(var i=0; i<ac.length; i++) {
 991			if( ac[i] === tn ) {
 992				this.childList.splice(i, 1);
 993				delete tn;
 994				break;
 995			}
 996		}
 997	},
 998
 999	removeChildren: function(isRecursiveCall, retainPersistence) {
1000        // Remove all child nodes (more efficiently than recursive remove())
1001//		this.tree.logDebug ("%o.removeChildren(%o)", this, isRecursiveCall);
1002		var tree = this.tree;
1003        var ac = this.childList;
1004        if( ac ) {
1005        	for(var i=0; i<ac.length; i++) {
1006				var tn=ac[i];
1007//        		this.tree.logDebug ("del %o", tn);
1008                if ( tn === tree.activeNode && !retainPersistence )
1009                	tn.deactivate();
1010                if( this.tree.options.persist && !retainPersistence ) {
1011	                if( tn.bSelected )
1012	                    this.tree.persistence.clearSelect(tn.data.key);
1013	                if ( tn.bExpanded )
1014	                    this.tree.persistence.clearExpand(tn.data.key);
1015                }
1016                tn.removeChildren(true, retainPersistence);
1017				this.div.removeChild(tn.div);
1018                delete tn;
1019        	}
1020        	this.childList = null;
1021        }
1022		if( ! isRecursiveCall ) {
1023//			this._expand(false);
1024//			this.isRead = false;
1025			this.isLoading = false;
1026			this.render(false, false);
1027		}
1028	},
1029
1030	reload: function(force) {
1031		// Discard lazy content (and reload, if node was expanded). 
1032		if( this.parent == null )
1033			return this.tree.reload();
1034		
1035		if( ! this.data.isLazy )
1036			throw "node.reload() requires lazy nodes.";
1037		if( this.bExpanded ) {
1038			this.expand(false);
1039			this.removeChildren();
1040			this.expand(true);
1041		} else {
1042			this.removeChildren();
1043			if( force )
1044				this._loadContent();
1045		}
1046	},
1047
1048	_addChildNode: function(dtnode, beforeNode) {
1049		/** 
1050		 * Internal function to add one single DynatreeNode as a child.
1051		 * 
1052		 */
1053		var tree = this.tree;
1054		var opts = tree.options;
1055		var pers = tree.persistence;
1056		
1057//		tree.logDebug("%o._addChildNode(%o)", this, dtnode);
1058		
1059		// --- Update and fix dtnode attributes if necessary 
1060		dtnode.parent = this;
1061//		if( beforeNode && (beforeNode.parent !== this || beforeNode === dtnode ) )
1062//			throw "<beforeNode> must be another child of <this>";
1063
1064		// --- Add dtnode as a child
1065		if ( this.childList==null ) {
1066			this.childList = [];
1067		} else if( ! beforeNode ) {
1068			// Fix 'lastsib'
1069			$(this.childList[this.childList.length-1].span).removeClass(opts.classNames.lastsib);
1070		}
1071		if( beforeNode ) {
1072			var iBefore = $.inArray(beforeNode, this.childList);
1073			if( iBefore < 0 )
1074				throw "<beforeNode> must be a child of <this>";
1075			this.childList.splice(iBefore, 0, dtnode);
1076//			alert(this.childList);
1077		} else {
1078			// Append node
1079			this.childList.push(dtnode);
1080		}
1081
1082		// --- Handle persistence 
1083		// Initial status is read from cookies, if persistence is active and 
1084		// cookies are already present.
1085		// Otherwise the status is read from the data attributes and then persisted.
1086		var isInitializing = tree.isInitializing();
1087		if( opts.persist && pers.cookiesFound && isInitializing ) {
1088			// Init status from cookies
1089//			tree.logDebug("init from cookie, pa=%o, dk=%o", pers.activeKey, dtnode.data.key);
1090			if( pers.activeKey == dtnode.data.key )
1091				tree.activeNode = dtnode;
1092			if( pers.focusedKey == dtnode.data.key )
1093				tree.focusNode = dtnode;
1094			dtnode.bExpanded = ($.inArray(dtnode.data.key, pers.expandedKeyList) >= 0);
1095			dtnode.bSelected = ($.inArray(dtnode.data.key, pers.selectedKeyList) >= 0);
1096//			tree.logDebug("    key=%o, bSelected=%o", dtnode.data.key, dtnode.bSelected);
1097		} else {
1098			// Init status from data (Note: we write the cookies after the init phase)
1099//			tree.logDebug("init from data");
1100			if( dtnode.data.activate ) {
1101				tree.activeNode = dtnode;
1102				if( opts.persist )
1103					pers.activeKey = dtnode.data.key;
1104			}
1105			if( dtnode.data.focus ) {
1106				tree.focusNode = dtnode;
1107				if( opts.persist )
1108					pers.focusedKey = dtnode.data.key;
1109			}
1110			dtnode.bExpanded = ( dtnode.data.expand == true ); // Collapsed by default
1111			if( dtnode.bExpanded && opts.persist )
1112				pers.addExpand(dtnode.data.key);
1113			dtnode.bSelected = ( dtnode.data.select == true ); // Deselected by default
1114/*			
1115			Doesn't work, cause pers.selectedKeyList may be null
1116			if( dtnode.bSelected && opts.selectMode==1 
1117				&& pers.selectedKeyList && pers.selectedKeyList.length>0 ) {
1118				tree.logWarning("Ignored multi-selection in single-mode for %o", dtnode);
1119				dtnode.bSelected = false; // Fixing bad input data (multi selection for mode:1)
1120			}
1121*/
1122			if( dtnode.bSelected && opts.persist )
1123				pers.addSelect(dtnode.data.key);
1124		}
1125
1126		// Always expand, if it's below minExpandLevel
1127//		tree.logDebug ("%o._addChildNode(%o), l=%o", this, dtnode, dtnode.getLevel());
1128		if ( opts.minExpandLevel >= dtnode.getLevel() ) {
1129//			tree.logDebug ("Force expand for %o", dtnode);
1130			this.bExpanded = true;
1131		}
1132
1133		// In multi-hier mode, update the parents selection state
1134		// issue #82: only if not initializing, because the children may not exist yet
1135//		if( !dtnode.data.isStatusNode && opts.selectMode==3 && !isInitializing )
1136//			dtnode._fixSelectionState();
1137
1138		// In multi-hier mode, update the parents selection state
1139		if( dtnode.bSelected && opts.selectMode==3 ) {
1140			var p = this;
1141			while( p ) {
1142				if( !p.hasSubSel )
1143					p._setSubSel(true);
1144				p = p.parent;
1145			}
1146		}
1147		// render this node and the new child
1148		if ( tree.bEnableUpdate )
1149			this.render(true, true);
1150
1151		return dtnode;
1152	},
1153
1154	addChild: function(obj, beforeNode) {
1155		/**
1156		 * Add a node object as child.
1157		 * 
1158		 * This should be the only place, where a DynaTreeNode is constructed!
1159		 * (Except for the root node creation in the tree constructor)
1160		 * 
1161		 * @param obj A JS object (may be recursive) or an array of those.
1162		 * @param {DynaTreeNode} beforeNode (optional) sibling node.
1163		 *     
1164		 * Data format: array of node objects, with optional 'children' attributes.
1165		 * [
1166		 *	{ title: "t1", isFolder: true, ... }
1167		 *	{ title: "t2", isFolder: true, ...,
1168		 *		children: [
1169		 *			{title: "t2.1", ..},
1170		 *			{..}
1171		 *			]
1172		 *	}
1173		 * ]
1174		 * A simple object is also accepted instead of an array.
1175		 * 
1176		 */
1177//		this.tree.logDebug("%o.addChild(%o, %o)", this, obj, beforeNode);
1178		if( !obj || obj.length==0 ) // Passed null or undefined or empty array
1179			return;
1180		if( obj instanceof DynaTreeNode )
1181			return this._addChildNode(obj, beforeNode);
1182		if( !obj.length ) // Passed a single data object
1183			obj = [ obj ];
1184
1185		var prevFlag = this.tree.enableUpdate(false);
1186
1187		var tnFirst = null;
1188		for (var i=0; i<obj.length; i++) {
1189			var data = obj[i];
1190			var dtnode = this._addChildNode(new DynaTreeNode(this, this.tree, data), beforeNode);
1191			if( !tnFirst ) tnFirst = dtnode;
1192			// Add child nodes recursively
1193			if( data.children )
1194				dtnode.addChild(data.children, null);
1195		}
1196		this.tree.enableUpdate(prevFlag);
1197		return tnFirst;
1198	},
1199
1200	append: function(obj) {
1201		this.tree.logWarning("node.append() is deprecated (use node.addChild() instead).");
1202		return this.addChild(obj, null);
1203	},
1204
1205	appendAjax: function(ajaxOptions) {
1206		this.removeChildren(false, true);
1207		this.setLazyNodeStatus(DTNodeStatus_Loading);
1208		// Ajax option inheritance: $.ajaxSetup < $.ui.dynatree.defaults.ajaxDefaults < tree.options.ajaxDefaults < ajaxOptions
1209		var self = this;
1210		var orgSuccess = ajaxOptions.success;
1211		var orgError = ajaxOptions.error;
1212		var options = $.extend({}, this.tree.options.ajaxDefaults, ajaxOptions, {
1213/*
1214       		complete: function(req, textStatus){
1215	 		    alert("ajax complete");
1216   			},
1217   			timeout: 5000, // 5 sec
1218*/   			
1219       		success: function(data, textStatus){
1220     		    // <this> is the request options
1221//				self.tree.logDebug("appendAjax().success");
1222				var prevPhase = self.tree.phase;
1223				self.tree.phase = "init";
1224//				self.append(data);
1225				self.addChild(data, null);
1226				self.tree.phase = "postInit";
1227				self.setLazyNodeStatus(DTNodeStatus_Ok);
1228				if( orgSuccess )
1229					orgSuccess.call(options, self);
1230				self.tree.phase = prevPhase;
1231       			},
1232       		error: function(XMLHttpRequest, textStatus, errorThrown){
1233       		    // <this> is the request options  
1234// 				self.tree.logWarning("appendAjax failed: %o:\n%o\n%o", textStatus, XMLHttpRequest, errorThrown);
1235   				self.tree.logWarning("appendAjax failed:", textStatus, ":\n", XMLHttpRequest, "\n", errorThrown);
1236				self.setLazyNodeStatus(DTNodeStatus_Error, {info: textStatus, tooltip: ""+errorThrown});
1237				if( orgError )
1238					orgError.call(options, self, XMLHttpRequest, textStatus, errorThrown);
1239       			}
1240		});
1241       	$.ajax(options);
1242	},
1243	// --- end of class
1244	lastentry: undefined
1245}
1246
1247/*************************************************************************
1248 * class DynaTreeStatus
1249 */
1250
1251var DynaTreeStatus = Class.create();
1252
1253
1254DynaTreeStatus._getTreePersistData = function(cookieId, cookieOpts) {
1255	// Static member: Return persistence information from cookies
1256	var ts = new DynaTreeStatus(cookieId, cookieOpts);
1257	ts.read();
1258	return ts.toDict();
1259}
1260// Make available in global scope
1261getDynaTreePersistData = DynaTreeStatus._getTreePersistData;
1262
1263
1264DynaTreeStatus.prototype = {
1265	// Constructor
1266	initialize: function(cookieId, cookieOpts) {
1267		this._log("DynaTreeStatus: initialize");
1268		if( cookieId === undefined )
1269			cookieId = $.ui.dynatree.defaults.cookieId;
1270		cookieOpts = $.extend({}, $.ui.dynatree.defaults.cookie, cookieOpts);
1271		
1272		this.cookieId = cookieId; 
1273		this.cookieOpts = cookieOpts;
1274		this.cookiesFound = undefined;
1275		this.activeKey = null;
1276		this.focusedKey = null;
1277		this.expandedKeyList = null;
1278		this.selectedKeyList = null;
1279	},
1280	// member functions
1281	_log: function(msg) {
1282		//	this.logDebug("_changeNodeList(%o): nodeList:%o, idx:%o", mode, nodeList, idx);
1283		Array.prototype.unshift.apply(arguments, ["debug"]);
1284		_log.apply(this, arguments);
1285	},
1286	read: function() {
1287		this._log("DynaTreeStatus: read");
1288		// Read or init cookies. 
1289		this.cookiesFound = false;
1290			
1291		var cookie = $.cookie(this.cookieId + "-active");
1292		this.activeKey = ( cookie == null ) ? "" : cookie;
1293		if( cookie != null ) this.cookiesFound = true;
1294
1295		cookie = $.cookie(this.cookieId + "-focus");
1296		this.focusedKey = ( cookie == null ) ? "" : cookie;
1297		if( cookie != null ) this.cookiesFound = true;
1298
1299		cookie = $.cookie(this.cookieId + "-expand");
1300		this.expandedKeyList = ( cookie == null ) ? [] : cookie.split(","); 
1301		if( cookie != null ) this.cookiesFound = true;
1302
1303		cookie = $.cookie(this.cookieId + "-select");
1304		this.selectedKeyList = ( cookie == null ) ? [] : cookie.split(","); 
1305		if( cookie != null ) this.cookiesFound = true;
1306	},
1307	write: function() {
1308		this._log("DynaTreeStatus: write");
1309		$.cookie(this.cookieId + "-active", ( this.activeKey == null ) ? "" : this.activeKey, this.cookieOpts);
1310		$.cookie(this.cookieId + "-focus", ( this.focusedKey == null ) ? "" : this.focusedKey, this.cookieOpts);
1311		$.cookie(this.cookieId + "-expand", ( this.expandedKeyList == null ) ? "" : this.expandedKeyList.join(","), this.cookieOpts);
1312		$.cookie(this.cookieId + "-select", ( this.selectedKeyList == null ) ? "" : this.selectedKeyList.join(","), this.cookieOpts);
1313	},
1314	addExpand: function(key) {
1315		this._log("addExpand(%o)", key);
1316		if( $.inArray(key, this.expandedKeyList) < 0 ) {
1317			this.expandedKeyList.push(key);
1318			$.cookie(this.cookieId + "-expand", this.expandedKeyList.join(","), this.cookieOpts);
1319		}
1320	},
1321	clearExpand: function(key) {
1322		this._log("clearExpand(%o)", key);
1323		var idx = $.inArray(key, this.expandedKeyList); 
1324		if( idx >= 0 ) {
1325			this.expandedKeyList.splice(idx, 1);
1326			$.cookie(this.cookieId + "-expand", this.expandedKeyList.join(","), this.cookieOpts);
1327		}
1328	},
1329	addSelect: function(key) {
1330		this._log("addSelect(%o)", key);
1331		if( $.inArray(key, this.selectedKeyList) < 0 ) {
1332			this.selectedKeyList.push(key);
1333			$.cookie(this.cookieId + "-select", this.selectedKeyList.join(","), this.cookieOpts);
1334		}
1335	},
1336	clearSelect: function(key) {
1337		this._log("clearSelect(%o)", key);
1338		var idx = $.inArray(key, this.selectedKeyList); 
1339		if( idx >= 0 ) {
1340			this.selectedKeyList.splice(idx, 1);
1341			$.cookie(this.cookieId + "-select", this.selectedKeyList.join(","), this.cookieOpts);
1342		}
1343	},
1344	isReloading: function() {
1345		return this.cookiesFound == true;
1346	},
1347	toDict: function() {
1348		return {
1349			cookiesFound: this.cookiesFound,
1350			activeKey: this.activeKey,
1351			focusedKey: this.activeKey,
1352			expandedKeyList: this.expandedKeyList,
1353			selectedKeyList: this.selectedKeyList
1354		};
1355	},
1356	// --- end of class
1357	lastentry: undefined
1358};
1359
1360
1361/*************************************************************************
1362 * class DynaTree
1363 */
1364
1365var DynaTree = Class.create();
1366
1367// --- Static members ----------------------------------------------------------
1368
1369DynaTree.version = "$Version: 0.5.4$"; 
1370/*
1371DynaTree._initTree = function() {
1372};
1373
1374DynaTree._bind = function() {
1375};
1376*/
1377//--- Class members ------------------------------------------------------------
1378
1379DynaTree.prototype = {
1380	// Constructor
1381//	initialize: function(divContainer, options) {
1382	initialize: function($widget) {
1383		// instance members
1384		this.phase = "init";
1385		this.$widget = $widget;
1386		this.options = $widget.options;
1387		this.$tree = $widget.element;
1388		// find container element
1389		this.divTree = this.$tree.get(0);
1390	},
1391
1392	// member functions
1393
1394	_load: function() {
1395		var $widget = this.$widget;
1396		var opts = this.options;
1397		this.bEnableUpdate = true;
1398		this._nodeCount = 1;
1399		this.activeNode = null;
1400		this.focusNode = null;
1401
1402		// If a 'options.classNames' dictionary was passed, still use defaults 
1403    	// for undefined classes:
1404    	if( opts.classNames !== $.ui.dynatree.defaults.classNames ) {
1405    		opts.classNames = $.extend({}, $.ui.dynatree.defaults.classNames, opts.classNames);
1406    	}
1407    	// Guess skin path, if not specified
1408    	if(!opts.imagePath) {
1409    		$("script").each( function () {
1410    			// Eclipse syntax parser breaks on this expression, so put it at the bottom:
1411    			if( this.src.search(_rexDtLibName) >= 0 ) {
1412                    if( this.src.indexOf("/")>=0 ) // issue #47
1413    				    opts.imagePath = this.src.slice(0, this.src.lastIndexOf("/")) + "/skin/";
1414                    else
1415    				    opts.imagePath = "skin/";
1416//    				logMsg("Guessing imagePath from '%s': '%s'", this.src, opts.imagePath);
1417    				return false; // first match
1418    			}
1419    		});
1420    	}
1421    	
1422		this.persistence = new DynaTreeStatus(opts.cookieId, opts.cookie);
1423		if( opts.persist ) {
1424			if( !$.cookie )
1425				_log("warn", "Please include jquery.cookie.js to use persistence.");
1426			this.persistence.read();
1427		}
1428		this.logDebug("DynaTree.persistence: %o", this.persistence.toDict());
1429
1430		// Cached tag strings
1431		this.cache = {
1432			tagEmpty: "<span class='" + opts.classNames.empty + "'></span>",
1433			tagVline: "<span class='" + opts.classNames.vline + "'></span>",
1434			tagExpander: "<span class='" + opts.classNames.expander + "'></span>",
1435			tagConnector: "<span class='" + opts.classNames.connector + "'></span>",
1436			tagNodeIcon: "<span class='" + opts.classNames.nodeIcon + "'></span>",
1437			tagCheckbox: "<span class='" + opts.classNames.checkbox + "'></span>",
1438			lastentry: undefined
1439		};
1440
1441    	// Clear container, in case it contained some 'waiting' or 'error' text 
1442    	// for clients that don't support JS.
1443		// We don't do this however, if we try to load from an embedded UL element.
1444    	if( opts.children || (opts.initAjax && opts.initAjax.url) || opts.initId )
1445    		$(this.divTree).empty();
1446    	else if( this.divRoot )
1447    		$(this.divRoot).remove();
1448
1449		// create the root element
1450		this.tnRoot = new DynaTreeNode(null, this, {title: opts.title, key: "root"});
1451		this.tnRoot.data.isFolder = true;
1452		this.tnRoot.render(false, false);
1453		this.divRoot = this.tnRoot.div;
1454		this.divRoot.className = opts.classNames.container;
1455		// add root to container
1456		// TODO: this should be delayed until all children have been created for performance reasons
1457		this.divTree.appendChild(this.divRoot);
1458
1459    	var root = this.tnRoot;
1460    	var isReloading = ( opts.persist && this.persistence.isReloading() );
1461    	var isLazy = false;
1462    	var prevFlag = this.enableUpdate(false);  
1463
1464    	this.logDebug("Dynatree._load(): read tree structure...");
1465
1466    	// Init tree structure
1467    	if( opts.children ) {
1468    		// Read structure from node array
1469    		root.addChild(opts.children);
1470
1471    	} else if( opts.initAjax && opts.initAjax.url ) {
1472    		// Init tree from AJAX request
1473    		isLazy = true;
1474    		root.data.isLazy = true;
1475    		this._reloadAjax();
1476
1477    	} else if( opts.initId ) {
1478    		// Init tree from another UL element
1479    		this._createFromTag(root, $("#"+opts.initId));
1480
1481    	} else {
1482    		// Init tree from the first UL element inside the container <div>
1483    		var $ul = this.$tree.find(">ul").hide();
1484    		this._createFromTag(root, $ul);
1485    		$ul.remove();
1486    	}
1487    	
1488    	this._checkConsistency();
1489    	// Render html markup
1490    	this.logDebug("Dynatree._load(): render nodes...");
1491    	this.enableUpdate(prevFlag);
1492    	
1493    	// bind event handlers
1494    	this.logDebug("Dynatree._load(): bind events...");
1495    	this.$widget.bind();
1496
1497        // --- Post-load processing
1498    	this.logDebug("Dynatree._load(): postInit...");
1499    	this.phase = "postInit";
1500    	
1501    	// In persist mode, make sure that cookies are written, even if they are empty
1502        if( opts.persist ) { 
1503			this.persistence.write();
1504        }
1505        
1506    	// Set focus, if possible (this will also fire an event and write a cookie)
1507    	if( this.focusNode && this.focusNode.isVisible() ) {
1508    		this.logDebug("Focus on init: %o", this.focusNode);
1509    		this.focusNode.focus();
1510    	}
1511
1512    	if( !isLazy && opts.onPostInit ) {
1513    		opts.onPostInit.call(this, isReloading, false);
1514    	}
1515
1516    	this.phase = "idle";
1517	},
1518	
1519	_reloadAjax: function() {
1520		// Reload 
1521		var opts = this.options;
1522		if( ! opts.initAjax || ! opts.initAjax.url )
1523			throw "tree.reload() requires 'initAjax' mode.";
1524		var pers = this.persistence;
1525		var ajaxOpts = $.extend({}, opts.initAjax);
1526		// Append cookie info to the request
1527//		this.logDebug("reloadAjax: key=%o, an.key:%o", pers.activeKey, this.activeNode?this.activeNode.data.key:"?");
1528		if( ajaxOpts.addActiveKey )
1529			ajaxOpts.data.activeKey = pers.activeKey; 
1530		if( ajaxOpts.addFocusedKey )
1531			ajaxOpts.data.focusedKey = pers.focusedKey; 
1532		if( ajaxOpts.addExpandedKeyList )
1533			ajaxOpts.data.expandedKeyList = pers.expandedKeyList.join(","); 
1534		if( ajaxOpts.addSelectedKeyList )
1535			ajaxOpts.data.selectedKeyList = pers.selectedKeyList.join(","); 
1536
1537		// Set up onPostInit callback to be called when Ajax returns
1538		if( opts.onPostInit ) {
1539			if( ajaxOpts.success )
1540				this.logWarning("initAjax: success callback is ignored when onPostInit was specified.");
1541			if( ajaxOpts.error )
1542				this.logWarning("initAjax: error callback is ignored when onPostInit was specified.");
1543			var isReloading = pers.isReloading();
1544			ajaxOpts["success"] = function(dtnode) { opts.onPostInit.call(dtnode.tree, isReloading, false); }; 
1545			ajaxOpts["error"] = function(dtnode) { opts.onPostInit.call(dtnode.tree, isReloading, true); }; 
1546		}
1547    	this.logDebug("Dynatree._init(): send Ajax request...");
1548    	this.tnRoot.appendAjax(ajaxOpts);
1549	},
1550
1551	toString: function() {
1552		return "DynaTree '" + this.options.title + "'";
1553	},
1554
1555	toDict: function() {
1556		return this.tnRoot.toDict(true);
1557	},
1558	
1559	getPersistData: function() {
1560		return this.persistence.toDict();
1561	},
1562	
1563	logDebug: function(msg) {
1564		if( this.options.debugLevel >= 2 ) {
1565			Array.prototype.unshift.apply(arguments, ["debug"]);
1566			_log.apply(this, arguments);
1567		}
1568	},
1569
1570	logInfo: function(msg) {
1571		if( this.options.debugLevel >= 1 ) {
1572			Array.prototype.unshift.apply(arguments, ["info"]);
1573			_log.apply(this, arguments);
1574		}
1575	},
1576
1577	logWarning: function(msg) {
1578		Array.prototype.unshift.apply(arguments, ["warn"]);
1579		_log.apply(this, arguments);
1580	},
1581
1582	isInitializing: function() {
1583		return ( this.phase=="init" || this.phase=="postInit" );
1584	},
1585	isReloading: function() {
1586		return ( this.phase=="init" || this.phase=="postInit" ) && this.options.persist && this.persistence.cookiesFound;
1587	},
1588	isUserEvent: function() {
1589		return ( this.phase=="userEvent" );
1590	},
1591
1592	redraw: function() {
1593		this.logDebug("dynatree.redraw()...");
1594		this.tnRoot.render(true, true);
1595		this.logDebug("dynatree.redraw() done.");
1596	},
1597
1598	reloadAjax: function() {
1599		this.logWarning("tree.reloadAjax() is deprecated since v0.5.2 (use reload() instead).");
1600	},
1601	
1602	reload: function() {
1603		this._load();
1604	},
1605	
1606	getRoot: function() {
1607		ret

Large files files are truncated, but you can click here to view the full file