/*************************************************************************
	jquery.dynatree.js
	Dynamic tree view control, with support for lazy loading of branches.

	Copyright (c) 2008-2009  Martin Wendt (http://wwWendt.de)
	Licensed under the MIT License (MIT-License.txt)

	A current version and some documentation is available at
		http://dynatree.googlecode.com/

	Let me know, if you find bugs or improvements (martin at domain wwWendt.de).

	$Version: 0.4.1$
	$Revision: 206, 2009-03-23 17:33:58$

 	@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);
}


/*************************************************************************
 *	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) {
		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.hasSubSel = false;

		if( tree.initMode == "cookie" ) {
			// Init status from cookies
			if( tree.initActiveKey == this.data.key )
				tree.activeNode = this;
			if( tree.initFocusKey == this.data.key )
				tree.focusNode = this;
			this.bExpanded = ($.inArray(this.data.key, tree.initExpandedKeys) >= 0);
			this.bSelected = ($.inArray(this.data.key, tree.initSelectedKeys) >= 0);
		} else {
			// Init status from data (write to cookie after init phase)
			if( data.activate )
				tree.activeNode = this;
			if( data.focus )
				tree.focusNode = this;
			this.bExpanded = ( data.expand == true ); // Collapsed by default
			this.bSelected = ( data.select == true ); // Deselected by default
		}
		// Add this node to a list, so we can fire events after initialization phase
		if( this.bExpanded )
			tree.expandedNodes.push(this);
		if( this.bSelected )
			tree.selectedNodes.push(this);
	},

	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; i<this.childList.length; i++ )
				dict.children.push(this.childList[i].toDict(true, callback));
		} else {
			delete dict.children;
		}
		return dict;
	},

	_getInnerHtml: function() {
		var opts = this.tree.options;
		var cache = this.tree.cache;
		// parent connectors
		var rootParent = opts.rootVisible ? null : this.tree.tnRoot; 
		var bHideFirstExpander = (opts.rootVisible && opts.minExpandLevel>0) || 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;
		}

		// 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 += "<img src='" + opts.imagePath + this.data.icon + "' alt='' />";
   		} 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 +=  "<a href='#'" + tooltip + ">" + this.data.title + "</a>";
		return res;
	},

	render: function(bDeep, bHidden) {
		/**
		 * create <div><span>..</span> .. </div> tags for this node.
		 * 
		 * <div> // This div contains the node's span and list of child div's.
		 *   <span>S S S A</span> // Span contains graphic spans and title <a> tag 
		 *   <div>child1</div>
		 *   <div>child2</div>
		 * </div>
		 */
//		this.tree.logDebug("%o.render()", this);
		// --- 
		if( ! this.div ) {
			this.span = document.createElement("span");
			this.span.dtnode = this;
			if( this.data.key )
				this.span.id = this.tree.options.idPrefix + this.data.key;

			this.div  = document.createElement("div");
			this.div.appendChild(this.span);
			if ( this.parent )
				this.parent.div.appendChild(this.div);

			if( this.parent==null && !this.tree.options.rootVisible )
				this.span.style.display = "none";
		}
		// set node connector images, links and text
		this.span.innerHTML = this._getInnerHtml();

		// hide this node, if parent is collapsed
		this.div.style.display = ( this.parent==null || this.parent.bExpanded ? "" : "none");

		// Set classes for current status
		var opts = this.tree.options;
		var cn = opts.classNames;
		var isLastSib = this.isLastSibling();
		var cnList = [];
		cnList.push( ( this.data.isFolder ) ? cn.folder : cn.document );
		if( this.bExpanded )
			cnList.push(cn.expanded);
		if( this.data.isLazy && !this.isRead )
			cnList.push(cn.lazy);
		if( isLastSib )
			cnList.push(cn.lastsib);
		if( this.bSelected )
			cnList.push(cn.selected);
		if( this.hasSubSel )
			cnList.push(cn.partsel);
		if( this.tree.activeNode === this )
			cnList.push(cn.active);
		if( this.data.addClass )
			cnList.push(this.data.addClass);
		// IE6 doesn't correctly evaluate multiples class names,
		// so we create combined class names that can be used in the CSS
		cnList.push(cn.combinedExpanderPrefix
				+ (this.bExpanded ? "e" : "c")
				+ (this.data.isLazy && !this.isRead ? "d" : "")
				+ (isLastSib ? "l" : "")
				);
		cnList.push(cn.combinedIconPrefix
				+ (this.bExpanded ? "e" : "c")
				+ (this.data.isFolder ? "f" : "")
				);
		this.span.className = cnList.join(" ");

		if( bDeep && this.childList && (bHidden || this.bExpanded) ) {
			for(var i=0; i<this.childList.length; i++) {
				this.childList[i].render(bDeep, bHidden)
			}
		}
	},

	hasChildren: function() {
		return this.childList != null;
	},

	isLastSibling: function() {
		var p = this.parent;
		if ( !p ) return true;
		return p.childList[p.childList.length-1] === this;
	},

	prevSibling: function() {
		if( !this.parent ) return null;
		var ac = this.parent.childList;
		for(var i=1; i<ac.length; i++) // start with 1, so prev(first) = null
			if( ac[i] === this )
				return ac[i-1];
		return null;
	},

	nextSibling: function() {
		if( !this.parent ) return null;
		var ac = this.parent.childList;
		for(var i=0; i<ac.length-1; i++) // up to length-2, so next(last) = null
			if( ac[i] === this )
				return ac[i+1];
		return null;
	},

	_setStatusNode: function(data) {
		// Create, modify or remove the status child node (pass 'null', to remove it).
		var firstChild = ( this.childList ? this.childList[0] : null );
		if( !data ) {
			if ( firstChild ) {
				this.div.removeChild(firstChild.div);
				if( this.childList.length == 1 )
					this.childList = null;
				else
					this.childList.shift();
			}
		} else if ( firstChild ) {
			data.isStatusNode = true;
			firstChild.data = data;
			firstChild.render(false, false);
		} else {
			data.isStatusNode = true;
//			firstChild = this._addChildNode(new DynaTreeNode(this, this.tree, data));
			firstChild = this._addNode(data);
		}
	},

	setLazyNodeStatus: function(lts) {
		switch( lts ) {
			case DTNodeStatus_Ok:
				this._setStatusNode(null);
				this.isRead = true;
				this.render(false, false);
				if( this.tree.options.autoFocus ) {
					if( this === this.tree.tnRoot && !this.tree.options.rootVisible && this.childList ) {
						// special case: using ajaxInit
						this.childList[0].focus();
					} else {
						this.focus();
					}
				}
				break;
			case DTNodeStatus_Loading:
				this._setStatusNode({
					title: this.tree.options.strings.loading,
//					icon: "ltWait.gif"
					addClass: this.tree.options.classNames.nodeWait
				});
				break;
			case DTNodeStatus_Error:
				this._setStatusNode({
					title: this.tree.options.strings.loadError,
//					icon: "ltError.gif"
					addClass: this.tree.options.classNames.nodeError
				});
				break;
			default:
				throw "Bad LazyNodeStatus: '" + lts + "'.";
		}
	},

	_parentList: function(includeRoot, includeSelf) {
		var l = new Array();
		var dtn = includeSelf ? this : this.parent;
		while( dtn ) {
			if( includeRoot || dtn.parent )
				l.unshift(dtn);
			dtn = dtn.parent;
		};
		return l;
	},

	getLevel: function() {
		var level = 0;
		var dtn = this.parent;
		while( dtn ) {
			level++;
			dtn = dtn.parent;
		};
		return level;
	},

	isVisible: function() {
		// Return true, if all parents are expanded.
		var parents = this._parentList(true, false);
		for(var i=0; i<parents.length; i++)
			if( ! parents[i].bExpanded ) return false;
		return true;
	},

	makeVisible: function() {
		// Make sure, all parents are expanded
		var parents = this._parentList(true, false);
		for(var i=0; i<parents.length; i++)
			parents[i]._expand(true);
	},

	focus: function() {
		// TODO: check, if we already have focus
//		this.tree.logDebug("dtnode.focus(): %o", this);
		this.makeVisible();
		try {
			$(this.span).find(">a").focus();
		} catch(e) { }
	},

	isActive: function() {
		return (this.tree.activeNode === this);
	},
	
	activate: function() {
		// Select - but not focus - this node.
//		this.tree.logDebug("dtnode.activate(): %o", this);
		var opts = this.tree.options;
		if( this.data.isStatusNode )
			return;
		if ( opts.onQueryActivate && opts.onQueryActivate.call(this.span, true, this) == false )
			return; // Callback returned false
		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.span).addClass(opts.classNames.active);
		if ( opts.onActivate ) // Pass element as 'this' (jQuery convention)
			opts.onActivate.call(this.span, this);
	},

	deactivate: function() {
//		this.tree.logDebug("dtnode.deactivate(): %o", this);
		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 )
				$.cookie(opts.cookieId+"-active", "", opts.cookie);
			this.tree.activeNode = null;
			if ( opts.onDeactivate )
				opts.onDeactivate.call(this.span, 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; i<p.childList.length;  i++) {
					var n = p.childList[i]; 
					if( !n.bSelected && !n.data.isStatusNode ) {
						allChildsSelected = false;
						break;
					}
				}
				if( allChildsSelected )
					p._select(true, false, false);
				p = p.parent;
			}
		} else {
			// Deselect all children
			this._setSubSel(false);
			this.visit(function(dtnode){
				dtnode._setSubSel(false);
				dtnode._select(false, false, false);
			});
			// Deselect parents, and recalc hasSubSel
			var p = this.parent;
			while( p ) {
				p._select(false, false, false);
				var isPartSel = false;
				for(var i=0; i<p.childList.length;  i++) {
					if( p.childList[i].bSelected || p.childList[i].hasSubSel ) {
						isPartSel = true;
						break;
					}
				}
				p._setSubSel(isPartSel);
				p = p.parent;
			}
		}
	},
	
	_select: function(sel, fireEvents, deep) {
		// Select - but not focus - this node.
//		this.tree.logDebug("dtnode._select(%o) - %o", sel, this);
		var opts = this.tree.options;
		if( this.data.isStatusNode )
			return;
		// 
		if( this.bSelected == sel ) {
//			this.tree.logDebug("dtnode._select(%o) IGNORED - %o", sel, this);
			return;
		}
		// Allow event listener to abort selection
		if ( fireEvents && opts.onQuerySelect && opts.onQuerySelect.call(this.span, sel, this) == false )
			return; // Callback returned false
		
		// Force single-selection
		if( opts.selectMode==1 && this.tree.selectedNodes.length && sel ) 
			this.tree.selectedNodes[0]._select(false, false, false);

		this.bSelected = sel;
        this.tree._changeNodeList("select", this, sel);
			
		if( sel ) {
			$(this.span).addClass(opts.classNames.selected);

			if( deep && opts.selectMode==3 )
				this._fixSelectionState();

			if ( fireEvents && opts.onSelect )
				opts.onSelect.call(this.span, true, this);

		} else {
			$(this.span).removeClass(opts.classNames.selected);

	    	if( deep && opts.selectMode==3 )
				this._fixSelectionState();

	    	if ( fireEvents && opts.onSelect )
				opts.onSelect.call(this.span, false, this);
		}
	},

	isSelected: function() {
		return this.bSelected;
	},
	
	select: function(sel) {
		// Select - but not focus - this node.
//		this.tree.logDebug("dtnode.select(%o) - %o", sel, this);
		return this._select(sel!=false, true, true);
	},

	toggleSelect: function() {
//		this.tree.logDebug("dtnode.toggleSelect() - %o", this);
		return this.select(!this.bSelected);
	},

	_expand: function(bExpand) {
//		this.tree.logDebug("dtnode._expand(%o) - %o", bExpand, this);
		if( this.bExpanded == bExpand ) {
//			this.tree.logDebug("dtnode._expand(%o) IGNORED - %o", bExpand, this);
			return;
		}
		var opts = this.tree.options;
		if( !bExpand && this.getLevel()<opts.minExpandLevel ) {
			this.tree.logDebug("dtnode._expand(%o) forced expand - %o", bExpand, this);
			return;
		}
		if ( opts.onQueryExpand && opts.onQueryExpand.call(this.span, bExpand, this) == false )
			return; // Callback returned false
		this.bExpanded = bExpand;
		// Persist expand state
    	this.tree._changeNodeList("expand", this, bExpand);
/*
        if( bExpand ) {
			$(this.span).addClass(opts.classNames.expanded);
        } else {
			$(this.span).removeClass(opts.classNames.expanded);
        }
*/
        this.render(false);

        // Auto-collapse mode: collapse all siblings
		if( this.bExpanded && this.parent && opts.autoCollapse ) {
			var parents = this._parentList(false, true);
			for(var i=0; i<parents.length; i++)
				parents[i].collapseSiblings();
		}
		// If current focus is now hidden, focus the first visible parent.
		// TODO: doesn't make sense here(?) we should check if the currently focused node (not <this>) is visible.
		// At the moment, _expand gets only called, after focus was set to <this>.
//		if( ! this.bExpanded && ! this.isVisible() ) {
//			this.tree.logDebug("Focus became invisible: setting to this.");
//			this.focus();
//		}
		// If currently active node is now hidden, deactivate it
		if( opts.activeVisible && this.tree.activeNode && ! this.tree.activeNode.isVisible() ) {
			this.tree.activeNode.deactivate();
		}
		// Expanding a lazy node: set 'loading...' and call callback
		if( bExpand && this.data.isLazy && !this.isRead ) {
			try {
				this.tree.logDebug("_expand: start lazy - %o", this);
				this.setLazyNodeStatus(DTNodeStatus_Loading);
				if( true == opts.onLazyRead.call(this.span, this) ) {
					// If function returns 'true', we assume that the loading is done:
					this.setLazyNodeStatus(DTNodeStatus_Ok);
					// Otherwise (i.e. if the loading was started as an asynchronous process)
					// the onLazyRead(dtnode) handler is expected to call dtnode.setLazyNodeStatus(DTNodeStatus_Ok/_Error) when done.
					this.tree.logDebug("_expand: lazy succeeded - %o", this);
				}
			} catch(e) {
				this.setLazyNodeStatus(DTNodeStatus_Error);
			}
			return;
		}
//		this.tree.logDebug("_expand: start div toggle - %o", this);

		if( opts.fx ) {
			var duration = opts.fx.duration || 200;
			$(">DIV", this.div).animate(opts.fx, duration);
		} else {
//			$(">DIV", 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);
/*		
//		this.tree.logDebug("toggleExpand("+this.data.title+")...");
		if( !this.childList && !this.data.isLazy )
			return;
		if( this.parent == null && this.tree.options.minExpandLevel>0 && this.bExpanded)
			return; // Prevent collapsing the root
		this._expand( ! this.bExpanded);
//		this.tree.logDebug("toggleExpand("+this.data.title+") done.");
*/
	},

	collapseSiblings: function() {
		if( this.parent == null )
			return;
		var ac = this.parent.childList;
		for (var i=0; i<ac.length; i++) {
			if ( ac[i] !== this && ac[i].bExpanded )
				ac[i]._expand(false);
		}
	},

	onClick: function(event) {
//		this.tree.logDebug("dtnode.onClick(" + event.type + "): dtnode:" + this + ", button:" + event.button + ", which: " + event.which);

		if( $(event.target).hasClass(this.tree.options.classNames.expander) ) {
			// Clicking the expander icon always expands/collapses
			this.toggleExpand();
		} else if( $(event.target).hasClass(this.tree.options.classNames.checkbox) ) {
			// Clicking the checkbox always (de)selects
			this.toggleSelect();
		} else {
			this._userActivate();
			// Chrome and Safari don't focus the a-tag on click
//			this.tree.logDebug("a tag: ", this.span.getElementsByTagName("a")[0]);
			this.span.getElementsByTagName("a")[0].focus();
//			alert("hasFocus=" + this.span.getElementsByTagName("a")[0].focused);
		}
		// Make sure that clicks stop, otherwise <a href='#'> 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: // <enter>
				// <enter> on a focused <a> tag seems to generate a click-event. 
				// this._userActivate();
				// break;
			case 32: // <space>
				this._userActivate();
				break;
			case 8: // <backspace>
				if( this.parent )
					this.parent.focus();
				break;
			case 37: // <left>
				if( this.bExpanded ) {
					this.toggleExpand();
					this.focus();
				} else if( this.parent && (this.tree.options.rootVisible || this.parent.parent) ) {
					this.parent.focus();
				}
				break;
			case 39: // <right>
				if( !this.bExpanded && (this.childList || this.data.isLazy) ) {
					this.toggleExpand();
					this.focus();
				} else if( this.childList ) {
					this.childList[0].focus();
				}
				break;
			case 38: // <up>
				var sib = this.prevSibling();
				while( sib && sib.bExpanded )
					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: // <down>
				var sib;
				if( this.bExpanded ) {
					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", null, 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;
	},

	_postInit: function() {
		// Called, when childs have been loaded.
		if ( opts.onPostInit ) // Pass element as 'this' (jQuery convention)
			opts.onPostInit.call(this.span, this);
	},
		
	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<this.childList.length; i++)
				n += this.childList[i].visit(fn, data, true);
		return n;
	},

	remove: function() {
        // Remove this node
//		this.tree.logDebug ("%o.remove()", this);
        if ( this === this.tree.root )
            return false;
        return this.parent.removeChild(this);
	},

	removeChild: function(tn) {
		// Remove tn from list of direct children.
		var ac = this.childList;
		if( ac.length == 1 ) {
			if( tn !== ac[0] )
				throw "removeChild: invalid child";
			return this.removeChildren();
		}
        if ( tn === this.tree.activeNode )
        	tn.deactivate();
        if ( tn.bSelected )
        	this.tree._changeNodeList("select", tn, false);
        if ( tn.bExpanded )
        	this.tree._changeNodeList("expand", tn, false);
		tn.removeChildren(true);
		this.div.removeChild(tn.div);
		for(var i=0; i<ac.length; i++) {
			if( ac[i] === tn ) {
				this.childList.splice(i, 1);
				delete tn;
				break;
			}
		}
	},

	removeChildren: function(recursive) {
        // Remove all child nodes (more efficiently than recursive remove())
//		this.tree.logDebug ("%o.removeChildren(%o)", this, recursive);
		var tree = this.tree;
        var ac = this.childList;
        if( ac ) {
        	for(var i=0; i<ac.length; i++) {
				var tn=ac[i];
//        		this.tree.logDebug ("del %o", tn);
                if ( tn === tree.activeNode )
                	tn.deactivate();
                if ( tn.bSelected )
                	this.tree._changeNodeList("select", tn, false);
                if ( tn.bExpanded )
                	this.tree._changeNodeList("expand", tn, false);
                tn.removeChildren(true);
				this.div.removeChild(tn.div);
                delete tn;
        	}
        	this.childList = null;
			if( ! recursive ) {
				this._expand(false);
				this.isRead = false;
				this.render(false, false);
			}
        }
	},

	_addChildNode: function (dtnode) {
//		this.tree.logDebug ("%o._addChildNode(%o)", this, dtnode);
		var tree = this.tree;
		var opts = tree.options;
		if ( this.childList==null ) {
			this.childList = new Array();
		} else {
			// Fix 'lastsib'
			$(this.childList[this.childList.length-1].span).removeClass(opts.classNames.lastsib);
		}

		this.childList.push (dtnode);
		dtnode.parent = this; // TODO: only need to assert this

		// Expand the parent, if it's below minExpandLevel, or marked as expanded
//		tree.logDebug ("%o._addChildNode(%o), l=%o", this, dtnode, dtnode.getLevel());
		if ( dtnode.data.expand || opts.minExpandLevel >= dtnode.getLevel() )
			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 )
		if( !dtnode.data.isStatusNode && opts.selectMode==3 && !tree.isInitializing() )
			dtnode._fixSelectionState();

		if ( tree.bEnableUpdate )
			this.render(true, true);

		return dtnode;
	},

	_addNode: function(data) {
		return this._addChildNode(new DynaTreeNode(this, this.tree, data));
	},

	append: function(obj) {
		/*
		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.append(%o)", this, obj);
		if( !obj || obj.length==0 ) // Passed null or undefined or empty array
			return;
		if( !obj.length ) // Passed a single node
			obj = [ obj ];
//			return this._addNode(obj);

		var prevFlag = this.tree.enableUpdate(false);

		var tnFirst = null;
		for (var i=0; i<obj.length; i++) {
			var data = obj[i];
			var dtnode = this._addNode(data);
			if( !tnFirst ) tnFirst = dtnode;
			if( data.children )
				dtnode.append(data.children);
		}
		this.tree.enableUpdate(prevFlag);
		return tnFirst;
	},

	appendAjax: function(ajaxOptions) {
		this.setLazyNodeStatus(DTNodeStatus_Loading);
		// Ajax option inheritance: $.ajaxSetup < $.ui.dynatree.defaults.ajaxDefaults < tree.options.ajaxDefaults < ajaxOptions
		var self = this;
		var ajaxOptions = $.extend({}, this.tree.options.ajaxDefaults, ajaxOptions, {
       		success: function(data, textStatus){
				self.append(data);
				self.setLazyNodeStatus(DTNodeStatus_Ok);
       			},
       		error: function(XMLHttpRequest, textStatus, errorThrown){
				self.setLazyNodeStatus(DTNodeStatus_Error);
       			}
		});
       	$.ajax(ajaxOptions);
	},
	// --- end of class
	lastentry: undefined
}

/*************************************************************************
 * class DynaTree
 */

var DynaTree = Class.create();

// static members
DynaTree.version = "$Version: 0.4.1$"; 

DynaTree.prototype = {
	// Constructor
	initialize: function(divContainer, options) {
		// instance members
		this.options = options;

		this.bEnableUpdate = true;
		this._nodeCount = 0;

		// 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.
		this.initMode = "data";

		this.activeNode = null;
		this.selectedNodes = new Array();
		this.expandedNodes = new Array();

		if( this.options.persist ) {
			// Requires jquery.cookie.js:
			this.initActiveKey = $.cookie(this.options.cookieId + "-active");
			if( cookie || this.initActiveKey != null )
				this.initMode = "cookie";

			this.initFocusKey = $.cookie(this.options.cookieId + "-focus");

			var cookie = $.cookie(this.options.cookieId + "-expand");
            if( cookie != null )
                this.initMode = "cookie";
			this.initExpandedKeys = cookie ? cookie.split(",") : [];

			cookie = $.cookie(this.options.cookieId + "-select");
			this.initSelectedKeys = cookie ? cookie.split(",") : [];
		}
		this.logDebug("initMode: %o, active: %o, expanded: %o, selected: %o", this.initMode, this.initActiveKey, this.initExpandedKeys, this.initSelectedKeys);

		// Cached tag strings
		this.cache = {
			tagEmpty: "<span class='" + options.classNames.empty + "'></span>",
			tagVline: "<span class='" + options.classNames.vline + "'></span>",
			tagExpander: "<span class='" + options.classNames.expander + "'></span>",
			tagConnector: "<span class='" + options.classNames.connector + "'></span>",
			tagNodeIcon: "<span class='" + options.classNames.nodeIcon + "'></span>",
			tagCheckbox: "<span class='" + options.classNames.checkbox + "'></span>",
			lastentry: undefined
		};

		// find container element
		this.divTree = divContainer;
		// create the root element
		this.tnRoot = new DynaTreeNode(null, this, {title: this.options.title, key: "root"});
		this.tnRoot.data.isFolder = true;
		this.tnRoot.render(false, false);
		this.divRoot = this.tnRoot.div;
		this.divRoot.className = this.options.classNames.container;
		// add root to container
		this.divTree.appendChild(this.divRoot);
	},

	// member functions

	toString: function() {
		return "DynaTree '" + this.options.title + "'";
	},

	toDict: function() {
		return this.tnRoot.toDict(true);
	},
	
	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.initMode=="data" || this.initMode=="cookie" || this.initMode=="postInit" );
	},
	
	_changeNodeList: function(mode, node, bAdd) {
		// Add or remove key from a key list and optionally write cookie.
		if( !node )
			return false;
		var cookieName = this.options.cookieId + "-" + mode;
		var nodeList = ( mode=="expand" ) ? this.expandedNodes : this.selectedNodes;  
		var idx = $.inArray(node, nodeList);
//		this.logDebug("_changeNodeList(%o): nodeList:%o, idx:%o", mode, nodeList, idx);
		if( bAdd ) {
			if( idx >=0 )
				return false;
			nodeList.push(node);
		} else {
			if( idx < 0 )
				return false;
			nodeList.splice(idx, 1);
		}
//		this.logDebug("  -->: nodeList:%o", nodeList);
		if( this.options.persist ) {
			var keyList = $.map(nodeList, function(e,i){return e.data.key});
//			this.logDebug("_changeNodeList: write cookie <%s> = '%s'", cookieName, keyList.join("', '"));
			$.cookie(cookieName, keyList.join(","), this.options.cookie);
		} else {
//			this.logDebug("_changeNodeListCookie: %o", nodeList);
		}
	},
	
	redraw: function() {
		this.logDebug("dynatree.redraw()...");
		this.tnRoot.render(true, true);
		this.logDebug("dynatree.redraw() done.");
	},

	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;
	},

	getSelectedNodes: function(stopOnParents) {
		if( stopOnParents == true ) {
			var nodeList = [];
			this.tnRoot.visit(function(dtnode){
				if( dtnode.bSelected ) {
					nodeList.push(dtnode);
					return false; // stop processing this branch
				}
			});
			return nodeList;
		} else {
			return this.selectedNodes;
		}
	},

	activateKey: function(key) {
		var dtnode = this.getNodeByKey(key);
		if( !dtnode ) {
			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 <UL>...</UL> 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 <li><span> tag is specified, use it literally.
				title = $liSpan.html();
			} else {
				// If only a <li> tag is specified, use the trimmed string up to the next child <ul> tag.
				title = $li.html();
				var iPos = title.search(/<ul/i);
				if( iPos>=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._addNode(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
			}
		});
	},
	// --- 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
//        logMsg("ui.dynatree.init() was called, you should upgrade to ui.core.js v1.6 or higher.");
        return this._init();
    },

	_init: function() {
		logMsg("Dynatree._init(): version='%s', debugLevel=%o.", DynaTree.version, this.options.debugLevel);

		// The widget framework supplies this.element and this.options.
		this.options.event += ".dynatree"; // namespace event

		// Create DynaTree
		var $this = this.element;
		var opts = this.options;

		// Guess skin path, if not specified
		if(!opts.imagePath) {
			$("script").each( function () {
				if( this.src.search(/.*dynatree[^/]*\.js$/i) >= 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
				}
			});
		}
		// Attach the tree object to parent element
		var divContainer = $this.get(0);

		// Clear container, in case it contained some 'waiting' or 'error' 
		// for clients that don't support JS
		if( opts.children || (opts.initAjax && opts.initAjax.url) || opts.initId )
			$(divContainer).empty();

		this.tree = new DynaTree(divContainer, opts);
		var root = this.tree.getRoot();

		var prevFlag = this.tree.enableUpdate(false); // Speedup by 13s -> 1,5 s 
		this.tree.logDebug("Start init tree structure...");
		// Init tree structure
		if( opts.children ) {
			// Read structure from node array
			root.append(opts.children);

		} else if( opts.initAjax && opts.initAjax.url ) {
			// Init tree from AJAX request
			root.appendAjax(opts.initAjax);

		} else if( opts.initId ) {
			// Init tree from another UL element
			this.tree._createFromTag(root, $("#"+opts.initId));

		} else {
			// Init tree from the first UL element inside the container <div>
			var $ul = $this.find(">ul").hide();
			this.tree._createFromTag(root, $ul);
			$ul.remove();
		}
		this.tree.enableUpdate(prevFlag);
		this.tree.logDebug("Init tree structure... done.");

		// bind event handlers
		this.bind();

        // Fire expand/select/focus/activate events for all nodes that were initialized
		this.tree.initMode = "postInit";
		
/*	TODO: re-fire expand events is not required:
          Nodes are already rendered by _addChidNode and .expandedNodes[] is valid. 
    
//		logMsg("_init: expandedNodes: %o, re-fireing events", this.tree.expandedNodes);
//		var nodeList = this.tree.expandedNodes.slice();
//		this.tree.expandedNodes = [];
		var nodeList = this.tree.expandedNodes;
		for(var i=0; i<nodeList.length; i++ ) {
			var dtnode = nodeList[i];
			logMsg("Expand on init: %o", dtnode);
//			dtnode.bExpanded = false; // make sure this is not ignored
			dtnode._expand(true);
		}
//		logMsg("_init: expandedNodes: %o, after ", this.tree.expandedNodes);
*/

		// Re-fire select events, so we have the checks according to selectMode
		// and also the user may react on the events
		nodeList = this.tree.selectedNodes.slice();
		this.tree.selectedNodes = [];
		for(var i=0; i<nodeList.length; i++ ) {
			var dtnode = nodeList[i];
			this.tree.logDebug("Re-select on init: %o", dtnode);
			dtnode.bSelected = false; // make sure this is not ignored
			dtnode.select(true);
		}

		// Focus, that was initialized as 'active'
		if( this.tree.focusNode ) {
			this.tree.logDebug("Focus on init: %o", this.tree.focusNode);
			this.tree.focusNode.focus();
		}
		// Activate node, that was initialized as 'active'
		if( this.tree.activeNode ) {
			var dtnode = this.tree.activeNode;
			this.tree.activeNode = null; // make sure this is not ignored
			this.tree.logDebug("Activate on init: %o", dtnode);
			dtnode._userActivate();
		}
		this.tree.initMode = "running";
	},

	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 = 4;
			do {
				if( el.dtnode ) return el.dtnode;
				el = el.parentNode;
			} while( iMax-- );
			return null;
		}

		$this.bind("click.dynatree dblclick.dynatree keypress.dynatree keydown.dynatree", function(event){
			var dtnode = __getNodeFromElement(event.target);
			
			if( !dtnode )
				return false;
//			dtnode.tree.logDebug("bind(" + event.type + "): dtnode:" + this + ", charCode:" + event.charCode + ", keyCode: " + event.keyCode + ", which: " + event.which);
			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);
			};
		});
		
		// focus/blur don't bubble, i.e. are not delegated to parent <div> 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");
	},
	
	enable: function() {
		this.bind();
		// Enable and remove -disabled from css: 
		this.setData("disabled", false);
	},
	
	disable: function() {
		this.unbind();
		// Disable and add -disabled to css: 
		this.setData("disabled", true);
	},
	
	// --- 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 <ul> 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: handle focus.
	onBlur: null, // null: handle unfocus.

	// 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
	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.
//	onCollapse: null, // Callback(dtnode) when a node is 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&#8230;",
		loadError: "Load error!"
	},
	idPrefix: "ui-dynatree-id-", // Used to generate node id's like <span id="ui-dynatree-id-<key>">.
    cookieId: "ui-dynatree-cookie", // 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
	},
    
	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",
		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: null, // Suppress checkbox for this node.
//	unselectable: false, // Prevent selection.
//  disabled: null,	
	// 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);

