﻿/*
 * VARIABLES
 */
COLCLASS = "p2column";
COLLOADCLASS = "p2loadingcolumn";
HOLDCLASS = "p2colview";
COLSELECTEDHEADERCLASS = "p2selectedcolmsghc";
ARBTALLCOL = 2000; //first column is made this tall briefly to get the view size. since I have reservations about most html engines' ability to deal with large dimensions I kept this reasonably small
SCROLLFPS = 30; //frames per second for scolling animation
SCROLLSECS = 0.33; // duration of scrolling animation in seconds

// preload select images to get around IE bug where the images are not shown until mouseover
nodedeletePreload = new Image();
nodedeletePreload.src = "WebResource.axd?d=YvHdf4QZKyaMwWwdpkNgqZiRv85m3VhFijZkNtuA0yg7qb1CdxZ3rjRJLNkV0GG71Xs4sqjjR4l20372nT8BEoSGHk3jsp6Q0&t=634188598485089663";
addcancelPreload = new Image();
addcancelPreload.src = "WebResource.axd?d=YvHdf4QZKyaMwWwdpkNgqZiRv85m3VhFijZkNtuA0yg7qb1CdxZ3rjRJLNkV0GG71Xs4sqjjR4l20372nT8BEoSGHk3jsp6Q0&t=634188598485089663";
addsubmitPreload = new Image();
addsubmitPreload.src = "WebResource.axd?d=YvHdf4QZKyaMwWwdpkNgqZiRv85m3VhFijZkNtuA0yg7qb1CdxZ3rjRJLNkV0GG71Xs4sqjjR4kjaC6exvIbTCBkemogyPIr0&t=634188598485089663";
// This is for overwriting the original Microsoft version to fix a bug.
// http://forums.microsoft.com/MSDN/ShowPost.aspx?PostID=705049&SiteID=1

function WebForm_CallbackComplete_SyncFixed() {
  // SyncFix: the original version uses "i" as global thereby resulting in javascript errors when "i" is used elsewhere in consuming pages
  for (var i = 0; i < __pendingCallbacks.length; i++) {
   callbackObject = __pendingCallbacks[i];
  if (callbackObject && callbackObject.xmlRequest && (callbackObject.xmlRequest.readyState == 4)) {
   // the callback should be executed after releasing all resources 
   // associated with this request. 
   // Originally if the callback gets executed here and the callback 
   // routine makes another ASP.NET ajax request then the pending slots and
   // pending callbacks array gets messed up since the slot is not released
   // before the next ASP.NET request comes.
   // FIX: This statement has been moved below
   // WebForm_ExecuteCallback(callbackObject);
   if (!__pendingCallbacks[i].async) {
     __synchronousCallBackIndex = -1;
   }
   __pendingCallbacks[i] = null;

   var callbackFrameID = "__CALLBACKFRAME" + i;
   var xmlRequestFrame = document.getElementById(callbackFrameID);
   if (xmlRequestFrame) {
     xmlRequestFrame.parentNode.removeChild(xmlRequestFrame);
   }

   // SyncFix: the following statement has been moved down from above;
   WebForm_ExecuteCallback(callbackObject);
  }
 }
}

// a base set of OO-help from
// MooTools, My Object Oriented Javascript Tools. Copyright (c) 2006 Valerio Proietti, <http://mad4milk.net>, MIT Style License.
eval(function(p,a,c,k,e,d){e=function(c){return(c<a?'':e(parseInt(c/a)))+((c=c%a)>35?String.fromCharCode(c+29):c.toString(36))};if(!''.replace(/^/,String)){while(c--){d[e(c)]=k[c]||e(c)}k=[function(e){return d[e]}];e=function(){return'\\w+'};c=1};while(c--){if(k[c]){p=p.replace(new RegExp('\\b'+e(c)+'\\b','g'),k[c])}}return p}('6 1z={1F:\'1.11\'};4 $Y(9){2(9!=16)};4 $f(9){7(!$Y(9))2 s;7(9.1p)2\'19\';6 f=W 9;7(f==\'z\'&&9.1B){13(9.1H){t 1:2\'19\';t 3:2(/\\S/).1x(9.1v)?\'1y\':\'1t\'}}7(f==\'z\'||f==\'4\'){13(9.1h){t T:2\'1i\';t 1L:2\'1G\';t o:2\'1A\'}7(W 9.M==\'1C\'){7(9.1E)2\'1D\';7(9.1q)2\'5\'}}2 f};4 $O(){6 B={};r(6 i=0;i<5.M;i++){r(6 g I 5[i]){6 G=5[i][g];6 L=B[g];7(L&&$f(G)==\'z\'&&$f(L)==\'z\')B[g]=$O(L,G);Q B[g]=G}}2 B};6 $h=4(){6 8=5;7(!8[1])8=[b,8[0]];r(6 g I 8[1])8[0][g]=8[1][g];2 8[0]};6 $F=4(){r(6 i=0,l=5.M;i<l;i++){5[i].h=4(U){r(6 m I U){7(!b.k[m])b.k[m]=U[m];7(!b[m])b[m]=$F.1f(m)}}}};$F.1f=4(m){2 4(a){2 b.k[m].N(a,T.k.1K.1J(5,1))}};$F(1n,T,1I,1M);4 $1l(9){2!!(9||9===0)};4 $1j(9,1a){2 $Y(9)?9:1a};4 $1c(14,18){2 1b.1r(1b.1c()*(18-14+1)+14)};4 $1w(){2 w 1s().1u()};4 $1U(Z){29(Z);2a(Z);2 y};6 12=4(9){9=9||{};9.h=$h;2 9};6 28=w 12(d);6 27=w 12(n);n.1d=n.2c(\'1d\')[0];d.1e=!!(n.26);7(d.2b)d.2d=d[d.2j?\'2h\':\'1o\']=p;Q 7(n.2f&&!n.2e&&!2g.2i)d.H=d[d.1e?\'25\':\'23\']=p;Q 7(n.1T!=y)d.24=p;d.1S=d.H;1R.h=$h;7(W K==\'16\'){6 K=4(){};7(d.H)n.1O("1P");K.k=(d.H)?d["[[1N.k]]"]:{}}K.k.1p=4(){};7(d.1o)1g{n.1Q("1V",s,p)}15(e){};6 o=4(C){6 A=4(){2(5[0]!==y&&b.X&&$f(b.X)==\'4\')?b.X.N(b,5):b};$h(A,b);A.k=C;A.1h=o;2 A};o.1W=4(){};o.k={h:4(C){6 J=w b(y);r(6 g I C){6 1k=J[g];J[g]=o.1m(1k,C[g])}2 w o(J)},21:4(){r(6 i=0,l=5.M;i<l;i++)$h(b.k,5[i])}};o.1m=4(x,v){7(x&&x!=v){6 f=$f(v);7(f!=$f(x))2 v;13(f){t\'4\':6 10=4(){b.V=5.1q.V;2 v.N(b,5)};10.V=x;2 10;t\'z\':2 $O(x,v)}}2 v};1n.h({q:4(c){6 P=b;c=$O({\'a\':P,\'j\':s,\'5\':y,\'u\':s,\'D\':s,\'R\':s},c);7($1l(c.5)&&$f(c.5)!=\'1i\')c.5=[c.5];2 4(j){6 8;7(c.j){j=j||d.j;8=[(c.j===p)?j:w c.j(j)];7(c.5)8.h(c.5)}Q 8=c.5||5;6 E=4(){2 P.N($1j(c.a,P),8)};7(c.u)2 22(E,c.u);7(c.D)2 20(E,c.D);7(c.R)1g{2 E()}15(1Z){2 s};2 E()}},1X:4(8,a){2 b.q({\'5\':8,\'a\':a})},R:4(8,a){2 b.q({\'5\':8,\'a\':a,\'R\':p})()},a:4(a,8){2 b.q({\'a\':a,\'5\':8})},1Y:4(a,8){2 b.q({\'a\':a,\'j\':p,\'5\':8})},u:4(u,a,8){2 b.q({\'u\':u,\'a\':a,\'5\':8})()},D:4(17,a,8){2 b.q({\'D\':17,\'a\':a,\'5\':8})()}});',62,144,'||return||function|arguments|var|if|args|obj|bind|this|options|window||type|property|extend||event|prototype||prop|document|Class|true|create|for|false|case|delay|current|new|previous|null|object|klass|mix|properties|periodical|returns|native|ap|webkit|in|proto|HTMLElement|mp|length|apply|merge|fn|else|attempt||Array|props|parent|typeof|initialize|defined|timer|merged||Abstract|switch|min|catch|undefined|interval|max|element|picked|Math|random|head|xpath|generic|try|constructor|array|pick|pp|chk|Merge|Function|ie6|htmlElement|callee|floor|Date|whitespace|getTime|nodeValue|time|test|textnode|MooTools|class|nodeName|number|collection|item|version|regexp|nodeType|String|call|slice|RegExp|Number|DOMElement|createElement|iframe|execCommand|Object|khtml|getBoxObjectFor|clear|BackgroundImageCache|empty|pass|bindAsEventListener|err|setInterval|implement|setTimeout|webkit419|gecko|webkit420|evaluate|Document|Window|clearTimeout|clearInterval|ActiveXObject|getElementsByTagName|ie|all|childNodes|navigator|ie7|taintEnabled|XMLHttpRequest'.split('|'),0,{}))


/*
 * CLASSES
 */

function ScrollManager(view) {
	/*
	 * Manages the scrolling & animation for an instance of ColumnView
	 */
	this.SANE_TIME = 2000; // if we've been animating for more than this (in milliseconds) kill the animation
	
	this.view = view;
	var dest = null; // the destination we're heading toward
	var lastx = null; // the last position. this stops us from getting 'caught' on limits
	
		// animation variables
	var perframe = null;
	var step = null;
	var startTime = 0;
	var timeout = null;
	
	/*
	 * METHODS of SCROLLMANAGER
	 */
		/*
		 * PRIVATE METHODS - haha
		 */
	this._calcAnimation = function() {
		// calculate animation settings
		// call once the view is setup (we need column sizes)
		perframe = Math.round(1000 / SCROLLFPS);
		step = Math.round((dest - view.view().scrollLeft) / (SCROLLFPS * SCROLLSECS));
	}
	this._scrollPulse = function() {
		var stopScroll = false; // kills the next iteration if true
		curDate = new Date();
		if (curDate.getTime() - startTime > this.SANE_TIME) {
			// in case something goes wrong this will kill the animation after a short time
			stopScroll = true;
		}
		if (this.isCloseEnough()) {
			stopScroll = true;
		} else {
			var newx = view.view().scrollLeft + step;
			if (lastx == newx) {
				// we've snagged an edge or something
				stopScroll = true;
			} else {
				lastx = newx;
				this.jumpTo(newx);
			}
		}

		if (stopScroll) {
			this.jumpTo(dest);
			this.stop();
		} else {
			this._setupPulse();
		}
	}
	this._setupPulse = function() {
		timeout = setTimeout(this._scrollPulse.bind(this), perframe); // thanks to OO and .bind we can call this anywhere // make sure private methods can work with this
	}
		/*
		 * PUBLIC METHODS
		 */
	this.isCloseEnough = function() {
		// if we're 'close enough' to the destination
		if (Math.abs(dest - this.view.view().scrollLeft) < Math.abs(step)) {
			return true;
		}
		return false;
	}
		// set the destination. we animate there
	this.headTo = function(x) {
		var curTime = new Date();
		startTime = curTime.getTime();
		dest = x;
		this._calcAnimation();
		this._setupPulse();
	}
	this.jumpTo = function(x) {
		// jump to a position immediately. no animation
		view.view().scrollLeft = x;
	}
	this.stop = function() {
		// kill the transition no matter where we are
		dest = this.view.view().scrollLeft;
		startTime = 0; // will fail all time-based sanity checks
		clearTimeout(timeout);
	}
	this.isMoving = function() {
		// are we in transition somewhere?
		return !this.isCloseEnough(); //
}
}

	/*
	 *  Deals with the actual fetching of data from the server
	 */
function DataHolder() {
	var root;
	var cache = {};
	var restored = {};
	// class for dealing with asyncronous returns
	this.getRootNode = function() {
		if (typeof(root) == 'undefined') {
			root = new ColumnNode("root");
			root.parent = null;
			root.setView(this.view);
			for(ii in this.view.selectedNodes) {
				root.hasSelectedDescendants = true;
				break;
            }
        }
		return root;
	}
	this.setView = function(view) {
		this.view = view; // expects an instance of ColumnView to send data back
	}
	this.getChildrenOf = function(node, callback) {
		if (cache[node.getPath()]) {
			callback(cache[node.getPath()]);
		} else {
			if (node.attributes.New) {
				if (restored[node.getPath()]) {
					for (var jj=0; jj < restored[node.getPath()].length; jj++) {
						var child = restored[node.getPath()][jj];
						child.parent = node;
						node.add(child);
						this.cacheNode(node);
					}
				}
				callback(node);
			} else {
			    this.sendCallback(node.getPath(), this.receiveCallback.bind(this), [node, callback]); // sendCallback is setup by the C#
			}
		}
	}
	this.receiveCallback = function(result, context) {
		// be careful to keep the format assumed here in sync with ColumnView.cs which provides it
		var node = context[0];
		if (result.replace(/[ \t\n\r]/, "") == "") {
			node.setEmpty();
		} else {
			// first catalogue the nodes we already have
			var oldChildren = node.children;
			node.children = new Array();
			if (restored[node.getPath()]) {
				for (var jj=0; jj < restored[node.getPath()].length; jj++) {
					oldChildren.push(restored[node.getPath()][jj]);
				}
			}
			lines = result.split("\n"); // keep in sync with ColumnView.cs
			for (var ii=0; ii < lines.length; ii++) {
				if (line == ";;END;;") {
					break; //ignore everything after this
				}
				var line = lines[ii];
				var parts = line.split("|");
				if (parts.length > 1) {
					// otherwise something is wrong, don't try creating a node
					var newnode = node.createChild(); // parts[0] is name
					newnode.parseData(line);
					for (var jj=0; jj < oldChildren.length; jj++) {
						if (newnode.getPath() == oldChildren[jj].getPath()) {
							// its the same node
							if (oldChildren[jj].startSelected) {
								newnode.startSelected = true;
							}
							break;
						}
					}
				}
			}
			//add new nodes
			for (var ii=0; ii < oldChildren.length; ii++) {
				var child = oldChildren[ii];
				if (child.attributes.New) {
					if (!child.parent)
						child.parent = node;
					this.getChildrenOf(child, function(){}); // flesh out the tree - new nodes will only ever have new node children
					this.cacheNode(node);
					node.add(child);
				}
			}
		}
		context[1](node);
	}
	this.cacheNode = function(node) {
		cache[node.getPath()] = node;
	}
	this.restore = function(dataStr) {
		var node = new ColumnNode();
		node.parseData(dataStr);
		if (node.attributes.ParentPath) {
			node.setView(this.view);
			if (!restored[node.attributes.ParentPath])
				restored[node.attributes.ParentPath] = new Array();
			restored[node.attributes.ParentPath].push(node);
			this.view.store(node);
			this.view.fireEvent('onadd', node);
		}
	}
}

	/*
	 * Each clickable item in each column maps to an instance of
	 * this class
	 */
function ColumnNode(name) {
	this.parent = null; // the node parent, not the column it's in
	var clientId;
	var view = null;
	this.column = new Column(this);
	this.name = name;
	var has_data = false;
	this.children = new Array();
	this.hasSelectedDescendants = false;
	this.isGrowable = false;
	this.selectable = false;
	this.selected = false;
	this.attributes = {};
	var depth = null;
	var path = "";
	
	this.setView = function(newview) {
		view = newview;
		this.column.setView(newview);
	}
	this.view = function() {
		return view;
	}
	this.createChild = function(pos) {
		// shortcut for adding children (important for bandwidth)
		// position is optional
		this.setEmpty(true);
		var node = new ColumnNode(name);
		node.parent = this;
		node.setView(view);
		
		if (typeof(pos) == 'undefined') {
			this.children.push(node);
		} else {
			this.children.splice(pos, 0, node);
		}	
		return node;
	}
	this.addChild = function(child, pos) {
		this.setEmpty(true);
		if (typeof(pos) == 'undefined') {
			this.children.push(child);
		} else {
			this.children.splice(pos, 0, child);
		}
	}
	this.attrTrim = function trim(attr) {
		s = attr.replace(/^(\s)*/, '');
		s = s.replace(/(\s)*$/, '');
		return s;
	}
	this.parseData = function(datastr) {
		// parses data from the server-created string
		var parts = datastr.split("|");
		this.name = unescape(parts[0]); // parts[0] is name
		this.setPath(unescape(parts[1])); // parts[1] is path
		if (parts.length > 2) {
			// we have an isGrowable flag
			if (parts[2] == "True") {
				this.isGrowable = true;
			}
			if (parts[3] == "True") {
			// can be multiselected
			    this.selectable = true;
			}
			if (parts[4] == "True") {
				// is an empty leaf node, to prevent uneccessary callbacks
				has_data = true;
			}
			if (parts.length > 5) {
				// we have attributes
				attrParts = parts[5].split(":");
				for (var ii=0; ii < attrParts.length ; ii++) {
					keyval = attrParts[ii].split("=");
					this.attributes[unescape(keyval[0])] = unescape(this.attrTrim(keyval[1])); // 0=key, 1=value
				}
			}
		}
		if (this.view() && this.view().multiSelectEnabled) {
		    if (this.view().hierarchicalSelection) {
		        if ((this.view().selectedNodes[this.getPath()] || this.view().ancestorSelectedFor(this)) && !this.view().prunedNodes[this.getPath()] && !this.view().onPathToPruned(this)) {
		            this.selected = true;
		            this.hasSelectedDescendants = !this.isLeaf();
		        } else if (this.view().onPathToPruned(this) || this.view().onPathToSelected(this)) {
		            this.selected = false;
		            this.hasSelectedDescendants = true;
		        } else {
		            this.selected = false;
		            this.hasSelectedDescendants = false;
		        }
		    } else {
		        if (this.view().selectedNodes[this.getPath()]) {
		            this.selected = true;
		            this.hasSelectedDescendants = this.view().onPathToSelected(this);
		        } else if (this.view().onPathToSelected(this)) {
		            this.selected = false;
		            this.hasSelectedDescendants = true;
		        } else {
		            this.selected = false;
		            this.hasSelectedDescendants = false;
		        }
		    }		            
		}
	}
	this.encodeData = function() {
		var datastr = "";
		var parts = new Array();
		parts.push(escape(this.name));
		parts.push(escape(this.getPath()));
		parts.push(this.isGrowable ? "True" : "");
		parts.push(this.selectable ? "True" : "");
		parts.push(has_data ? "True" : "");
		var attrparts = new Array();
		for (var ii in this.attributes) {
			attrparts.push(escape(ii)+"="+escape(this.attributes[ii]));
		}
		if (!this.attributes.ParentPath)
			attrparts.push("ParentPath="+escape(this.parent.getPath()));
		parts.push(attrparts.join(":"));
		datastr = parts.join("|");
		return datastr;
	}
	this.hasData = function() {
		return has_data;
	}
	this.isLeaf = function() {
		return has_data && this.children.length == 0;
	}
	this.setEmpty = function(val) {
		if (typeof(val) != 'undefined') {
			has_data = val;
		} else {
			has_data = true;
		}
	}
	this.depth = function() {
		if (depth == null) {
			depth = 0;
			var vader = this.parent;
			while (vader != null) {
				depth++;
				vader = vader.parent;
				if (depth > 1000) {
					break; //sanity
				}
			}
		}
		return depth;
	}
	this.add = function(newnode) {
		// find the position, add the node, then return its new position
		var newpos = this.getPosition(newnode);
		this.addChild(newnode, newpos);
		return newpos;
	}
	this.getPosition = function(n) {
		// uses a lame sort to get the position in the parent node for insertion
		return this.search(n, 0, this.children.length-1, 0);
	}
	this.getClientId = function() {
		if (!clientId)
			clientId = this.view().clientIdFor(this);
		return clientId;
	}
	this.setClientId = function(newid) {
		clientId = newid;
	}
	this.setPath = function(newpath) {
		path = newpath;
	}
	this.getPath = function() {
		return path;
	}
	this.search = function(n, min, max, depth) {
		// I wrote this on cold medicine. sorry -n
		var par = this.children;
		if (par.length == 0) {
			return null;
		}
			if (depth > 100) {
			// probably broken recursion
			return null;
		}
		if (n.name.toLowerCase() < par[min].name.toLowerCase()) {
			return min;
		}
		if (n.name.toLowerCase() > par[max].name.toLowerCase()) {
			return max;
		}
		if (max - min == 1) {
			return max;
		}
		depth++;
		var med = parseInt((max - min) / 2);
		if (n.name.toLowerCase() < par[min+med].name.toLowerCase()) {
			return this.search(n, min, min+med, depth);
		} else {
			return this.search(n, min+med, max, depth);
		}
	}
	this.removeChild = function(node) {
		var ii;
		for (ii=0; ii < this.children.length; ii++) {
			if (node.getPath() == this.children[ii].getPath()) {
				break;
			}
		}
		this.children.splice(ii, 1);
	}	
	this.setSelected = function(isSelected) {
	    if(isSelected) {
	        this.view().addSelected(this);
	        this.selected = true;
	    } else {
	        this.view().removeSelected(this);
	        this.selected = false;
	        if(this.view().multiSelectEnabled && this.view().hierarchicalSelection) {
	            this.hasSelectedDescendants = false;
	        }
	    }
	    if(this.parent) {
	        this.parent.updateSelectedStatus();
	    }
	}	
	this.updateSelectedStatus = function() {
	    var ii;
	    var stateChanged = false;
	    var wasSelected = this.selected;
	    if (this.view().hierarchicalSelection && this.children.length > 1 && this.selectable) {
	        this.selected = true; 
	    }
	    var hadSelectedDescendants = this.hasSelectedDescendants;
	    this.hasSelectedDescendants = false; 
	    
		var lastStatus;
		for(ii=0;ii<this.children.length;ii++) {
			if(!this.children[ii].selected) {
				if (this.view().hierarchicalSelection) {
					this.selected = false;
				}
				if (this.children[ii].hasSelectedDescendants) {
					this.hasSelectedDescendants = true;
				}
			} else {
				this.hasSelectedDescendants = true;
			} 
			if(ii > 0 && (lastStatus != this.children[ii].selected)) {
				break;
			}
			lastStatus = this.children[ii].selected;      
		}
		if (this.view().hierarchicalSelection) {
			if(hadSelectedDescendants && !this.hasSelectedDescendants) {
				this.selected = false;
				stateChanged = this.view().removeSelected(this);
			} 
			if (!wasSelected && this.selected && this.selectable) {
				this.selected = true;
				stateChanged = this.view().addSelected(this);
			}
		} 
		if(this.parent) {
			stateChanged = (this.parent.updateSelectedStatus() || stateChanged);
		}
		else {
			stateChanged = false;
		}
	    return stateChanged;
	}
}

	/* Each column maps to an instance of this class.
	 */
function Column(node) {
	var colid; //used to store the div
	var coladdid = null; // store the add div id, if there is one
	var view;
	this.col = function() {
		return document.getElementById(colid);
	}
	this.setView = function(newview) {
		view = newview;
	}
	this.coladd = function() {
		return document.getElementById(coladdid);
	}
	this.view = function() {
		return view;
	}
	this.clear = function() {
		// clears the contents of the column for redrawing
		var coldiv = document.getElementById(colid);
		for (var ii=0; ii < coldiv.childNodes.length; ii++) {
			var child = coldiv.childNodes[ii];
			coldiv.removeChild(child);
		}
		coldiv.className = COLCLASS;
	}
	this.drawInto = function(coldiv) {
		colid = coldiv.id;
		if (node.isGrowable) {
			// "add missing" panel
			this.showAddPanelFor(coldiv);
		}
		
		if(node.selectable){
		    var selpanel = document.createElement('div');
			    selpanel = coldiv.appendChild(selpanel);
			        coldiv.className = COLCLASS + " p2selectedcol";
			if(!this.view().multiSelectEnabled){
			    selpanel.innerHTML = 'You Have Selected<br/><span class="p2selname">'+node.name+"</span>";
			}
			else {
				selpanel.innerHTML = node.name + ' has';
				if (!node.selected){
					selpanel.innerHTML += ' not';
				}
				selpanel.innerHTML += ' been selected, there are no further areas within ' + node.name + ' available for selection.';
			}
	    }
			
		if (node.children.length > 0) {
		    if(selpanel){
		        coldiv.removeChild(selpanel);
		    }
		    //selpanel.innerHTML = selpanel.innerHTML + 'or choose from below';
			for (var ii=0; ii < node.children.length; ii++) {
				//var newnode = node.children[ii];	
				if (this.view().multiSelectEnabled) {
                    if (this.view().hierarchicalSelection) {
                        if (node.selected) {
				            node.children[ii].selected = true; //force node.children[ii] to selected if parent is selected
                        } else if (!node.hasSelectedDescendants){
    				        node.children[ii].selected = false; //node.children[ii] cannot be select if parent has not selected children
    				        node.children[ii].hasSelectedDescendants = false;//node.children[ii] cannot have selected children if parent does not
    				    }
    				} else if (node.hasSelectedDescendants) {
    				    node.children[ii].selected = node.children[ii].selected;//node.children[ii] maintains previous selected status
				        node.children[ii].hasSelectedDescendants = node.children[ii].hasSelectedDescendants;//node.children[ii] maintains previous selected child status
				    }
				} else {
				    //this are irrelevant outside of multiselect mode, setting just to be sure
				    node.children[ii].selected = false;
				    node.children[ii].hasSelectedDescendants = false;
				}
				
				var newlink = this.makeColItem(node.children[ii]);
				coldiv.appendChild(newlink);
				if (node.children[ii].startSelected) {
					newlink.className += " p2colitemsel";
					node.children[ii].startSelected = false;
					this.lastColItem = newlink;
					coldiv.scrollTop = newlink.offsetTop;
				}
			}
			coldiv.className = COLCLASS;
		} else {
		    if(selpanel){
		        selpanel.className = "p2selectedcolmsg";
		    }else
		    {
				var selpanel = document.createElement('div');
			    selpanel = coldiv.appendChild(selpanel);
			    coldiv.className = COLCLASS + " p2selectedcol";
			    selpanel.className = "p2selectedcolmsg";
				selpanel.innerHTML = 'There are no further areas within ' + node.name + '.'
		    }
		}
	}
	this.makeColItem = function(itemnode, allowDelete) {
		var div = document.createElement('div');
		div.className = "p2colitem";
		if(itemnode.isLeaf()) {
		    div.className += " leaf";
		}
		div.node = itemnode;
		div.onclick = this.onColItemClick.bind(this);
		div.setAttribute('unselectable', 'on'); // for IE. Mozilla gets this from CSS
		if (this.view().multiSelectEnabled){
			var selDiv = document.createElement('div');
			selDiv.className = "p2colCheckbox";
			if(itemnode.selectable) {
				if(itemnode.selected){
					selDiv.className += " p2colCheckboxOn";
				} else if (itemnode.hasSelectedDescendants) {
					selDiv.className += " p2colCheckboxPartial";
				}
			} else if (itemnode.hasSelectedDescendants) {
				selDiv.className += " p2colCheckboxDisabledPartial";
			} else {
				selDiv.className += " p2colCheckboxDisabledOff";
			}
			selDiv.onclick = this.onColItemClick.bind(this);
			div.appendChild(selDiv);
		}
		if (itemnode.attributes.New && allowDelete) {
			div.innerHTML += '<div class="p2colitemnewbadge"></div>'+itemnode.name+"&nbsp;";
			var delImg = nodedeletePreload.cloneNode(false);
				delImg.alt = "Delete "+itemnode.name;
				delImg.align = "absmiddle";
				delImg.onclick = this.onDelClick.bind(this);
			div.appendChild(delImg);			
		} else {
			div.innerHTML += itemnode.name;
		}
		
		itemnode.setClientId(this.view().clientIdFor(itemnode));
		div.id = this.view().clientIdFor(itemnode);
		return div;
	}
	this.onDelClick = function(e) {
		if (!e) {
			e = window.event;
		}
		var el = (e.target) ? e.target : e.srcElement;
		var delnode = el.parentNode.node;
		var childMsg = "";
		if (delnode.children.length > 0)
			childMsg = " and all its children";
		if (confirm('Delete '+delnode.name+childMsg+'?')) {
		    var stateChanged = false; 
		    if(this.view().multiSelectEnabled) {
		        //have to orphan the node while we remove it from selected to avoid 
		        //some state logic based on parent checking in removeSelected
		        var parentOfDeleted = delnode.parent;
		        delnode.parent = null;
		        stateChanged = this.view().removeSelected(delnode);
		        delnode.parent = parentOfDeleted;
		        //remove selected doesn't do this in non-hierarchical selection, so force it
		        if(!this.view().hierarchicalSelection) {
		            stateChanged = (this.view().clearChildStateFor(delnode) || stateChanged);	
		        }	       
		    }
		    this.col().removeChild(el.parentNode);
			delnode.parent.removeChild(delnode);
		    if(this.view().multiSelectEnabled) {
		        stateChanged = (parentOfDeleted.updateSelectedStatus() || stateChanged);
		        this.redrawNode(parentOfDeleted);
		        if (stateChanged) {
			        this.view().fireEvent('selStateChanged',this.view().renderSelectionState());
			    }
		    }
			this.view().fireEvent('ondel', delnode);
			this.view().unstore(delnode);
			this.view().showColumnFor(delnode.parent);
			this.view().fireEvent('onselect', delnode.parent);
			
		}
	}
	this.onColItemClick = function(e) {
		if (!e) {
			e = window.event;
		}
		var targetElement = (e.target) ? e.target : e.srcElement;
		var el;
		
		if (this.view().multiSelectEnabled && targetElement.className.indexOf('Checkbox') != -1){
		    el = targetElement.parentNode;
		    if (el.node.selectable) {
				if(el.node.selected){
					el.node.setSelected(false);
					this.view().fireEvent('selStateChanged',this.view().renderSelectionState());
				} else {
					el.node.setSelected(true);
					this.view().fireEvent('selStateChanged',this.view().renderSelectionState());
				}
				this.redrawNode(el.node);
			}
		}
		else{
		    el = targetElement;
		}
		    
        if (el.node) {
			if (this.lastColItem) {
				this.lastColItem.className = this.lastColItem.className.replace(/ p2colitemsel/, "");
			}
			el.className += " p2colitemsel";
			if (el.className.indexOf("p2colitemnew") >= 0) {
				el.className = el.className.replace(/p2colitemnew/, "p2colitemnewsel");
			}
			this.lastColItem = el;
			this.view().setLastRenderedNode(el.node);
			this.view().showColumnFor(el.node);
			this.view().fireEvent('onselect', el.node);
		}
	}
	this.redrawNode = function(node) {
	    var div = document.getElementById(node.getClientId());
	    if (div) {
			if (this.view().multiSelectEnabled) { //div.node.selectable) {
				var ii;
				for(ii=0;ii<div.childNodes.length;ii++) {
					if(div.childNodes[ii].tagName == 'DIV' && div.childNodes[ii].className.indexOf("Checkbox") >= 0) {
						break;
					}
				}
				var selDiv = div.childNodes[ii];
				selDiv.className = "p2colCheckbox";
				if (node.selectable) {
					if(node.selected) {
						selDiv.className += " p2colCheckboxOn";
					} else if (node.hasSelectedDescendants) {
						selDiv.className += " p2colCheckboxPartial";
					}
				} else if (node.hasSelectedDescendants) {
					selDiv.className += " p2colCheckboxDisabledPartial";
				} else {
					selDiv.className += " p2colCheckboxDisabledOff";
				}
				this.redrawNode(node.parent);
			}
	    }
	}
	this.showAddPanelFor = function(col) {
		addpanel = document.createElement('div');
		this.view().view().appendChild(addpanel);
		addpanel.className = "p2additem";
		addpanel.id = col.id+"add";
		coladdid = addpanel.id;
		this.showAddTextFor(addpanel);
		addpanel.style.width = col.offsetWidth+"px";
		addpanel.style.left = col.offsetLeft+"px";
		addpanel.style.top = (this.view().colHeight - addpanel.offsetHeight)+"px";
		col.style.height = (this.view().colHeight-addpanel.offsetHeight)+"px";
	}
	this.showAddTextFor = function(addpanel) {
		addtext = document.createElement('a');
		addpanel.appendChild(addtext);
		addtext.href = "javascript:void(0);";
		addtext.setAttribute("unselectable", "on");
		addtext.innerHTML = "Add Missing";
		addtext.className = "p2addtext";
		addtext.onclick = this.addTextClick.bind(this);
		addtext.style.width = "100%";
	}
	this.addTextClick = function(e) {
		if (!e) {
			e = window.event;
		}
		var el = (e.target) ? e.target : e.srcElement;
		var parentEl = el.parentNode;
		if(this.view().fireEvent('customadd', node).fired){
			//do nothing add handled by external code
		} else {				
			el.style.display = "none";
			var addinput = document.createElement('input');
			parentEl.appendChild(addinput);
			addinput.className = "p2addinput";
			addinput.style.width = (parentEl.offsetWidth - 47)+"px"; // 41 for the submit & cancel buttons
			addinput.style.height = (parentEl.offsetHeight - 6)+"px"; // 6 for an edge
			addinput.onkeypress = this.addInputKeypress.bind(this);
			
			
			// submit button
			addsubmitPreload.align = "absmiddle";
			parentEl.appendChild(addsubmitPreload);
			addsubmitPreload.className = "p2addsubmit";
			addsubmitPreload.onclick = this.handleAdd.bind(this);
			
			// cancel button
			addcancelPreload.align = "absmiddle";
			parentEl.appendChild(addcancelPreload);
			addcancelPreload.className = "p2addcancel";
			addcancelPreload.onclick = this.cancelAdd.bind(this);
			
			// now we can type
			addinput.focus();
			
			// register this column with the CV
			this.col().style.backgroundColor = "#f6fdf8";
			//this.view().addingFor(this);
		}
		this.view().addingFor(this);
	}
	this.cancelAdd = function(e) {
		this.col().style.backgroundColor = "";
		this.view().addingFor(false);
		
		var el = this.coladd();
		var numchil = el.childNodes.length;
		for(ii = 0; ii < numchil; ii++) {
			el.removeChild(el.childNodes[0]);
		}
		this.showAddTextFor(el);
	}
	this.handleAdd = function(e) {
		if (!e) {
			e = window.event;
		}
		var el = (e.target) ? e.target : e.srcElement;
		var newname = '';
		siblings = el.parentNode.childNodes;
		for (var ii = 0; ii < siblings.length; ii++) {
			var sibling = siblings[ii];
			try {
				if (sibling.tagName.toLowerCase() == 'input') {
					newname = sibling.value;
				}
			} catch (e) { /* swallow - yum */ }
		}
		var coldiv = document.getElementById(colid);
		if (coldiv.className.match(/p2selectedcol/)) {
			this.clear();
		}
		this.addWithName(newname);
	}
	this.addWithName = function(newname, newid, allowDelete) {
		//if allowDelete parameter not supplied as boolean literal false, assume true
		allowDelete = (allowDelete === false) ? false : true;
		var result = this.view().fireEvent('addvalidate', newname);
		if (result.fired) {
			if (!result.output) {
				return;
			}
		} else {
			if (newname == "") {
				return;
			}
		}
		this.col().style.backgroundColor = "";
		var newnode = new ColumnNode(newname);
		newnode.setView(this.view());
		newnode.parent = node;
		newnode.setEmpty();
		newnode.isGrowable = true; // if it can be added assume it can have additions
		newnode.attributes.New = true;
		if(newid){
			newnode.setPath(newnode.parent.getPath() + this.view().pathSeparator() + newid);
		} else {
			newnode.setPath(newnode.parent.getPath() + this.view().pathSeparator() + 'cvNew '+this.view().getNewCount());
		}		
		var selectable = false;
		if(node.children.length > 0) {
			for (var ii = 0; ii < node.children.length; ii++) {
				if(node.children[ii].selectable){
					selectable = true;
					break;
				}
			}
		} else {
			selectable = node.selectable;
		}
		newnode.selectable = selectable;
		if(newnode.selectable) {
			newnode.selected = this.view().hierarchicalSelection && (newnode.parent.selected || this.view().onPathToPruned(newnode.parent));
			if(newnode.selected) {
				newnode.parent.hasSelectedDescendants = true;
				this.view().fireEvent('selStateChanged',this.view().renderSelectionState());
			}
		}
        var newdiv = this.makeColItem(newnode, allowDelete);
		var newpos = node.add(newnode);
		var re = new RegExp(COLSELECTEDHEADERCLASS);
		if(this.col().childNodes[0] && this.col().childNodes[0].className.match(re)) {
			//account for selected message
			newpos = newpos + 1;
		}
		this.view().cacheNode(node);
		if (newpos == null) {
			this.col().appendChild(newdiv);
		} else {
			this.col().insertBefore(newdiv, this.col().childNodes[newpos]);
		}
		this.cancelAdd();
		this.view().store(newnode);
		this.view().fireEvent('onadd', newnode);
	}
	this.addInputKeypress = function(e) {
		if (!e) {
			e = window.event;
		}
		if (e.keyCode == 13) { //13 is enter/return
			this.handleAdd(e);
			if (e.preventDefault) {
				e.preventDefault(); // stop the form from submitting w3c
			}
			e.returnValue = false; // stop submission in ie
		}
		return true;
	}
	this.remove = function() {
		if (coladdid != null) {
			this.col().parentNode.removeChild(this.coladd());
		}
}
}

	/* An abstraction for the column view control that
	 * can deal with adding columns, etc.
	 * - takes the id of the container div as an argument
	 * - second argument is the object that will return data for columns as requested
	 *
	 * Works like this: events from clicking go to the column, it can check if it has
	 * what it needs, and clean up the UI as needed, then asks for the data.
	 */
function ColumnView(divid, dataholder) {
	//this pseudo constructor is called in the last line of this class
	var waiting = new Array(null, null); /// columns & nodes we're waiting for data. in case the user changes columns before we have data and it comes back we shouldn't draw it.
	this.numcol = 3; // 3 is just the default. set with setColumnCount()
	this.multiSelectEnabled = false; // false is the default. set with setMultiSelectEnabled()
	this.hierarchicalSelection = false; // false is the default. set with setMultiSelectEnabled()
	this.viewOverlap = 15; // just the default. set with setViewOverlap()
	this.events = {};
	var multiSelect = false;
	var hiddenID = "";
	var addingCol;
	var stored = new Array();
	this.selectedNodes = {};
	this.prunedNodes = {};
	this.pathParts;
	var lastRenderedNode;
	var pathSeparator = "-"; //default
	
	this.init = function() {
		this.scroller = new ScrollManager(this);
		dataholder.setView(this);
		this.colHeight = false; // will get set once we draw our first column
		this.colWidth = false; // ditto
		this.root = dataholder.getRootNode();
		//this.fetchChildrenFor(this.root);
		//this.curcol = 0;
	}
	this.view = function() {
		/* So why a method instead of keeping a reference?
		 * To stop a circular reference involving the DOM from 
		 * leaking memory in IE.
		 */
		return document.getElementById(divid);
	}
	this.getID = function(path) {
		return path.substring(path.lastIndexOf(pathSeparator)+1, path.length);
	}
	this.setLastRenderedNode = function(node) {
		this.lastRenderedNode = node;
	}
	this.setColumnCount = function(num) {
		this.numcol = num;
	}
	this.setViewOverlap = function(value) {
		// is always in pixels
		this.viewOverlap = parseInt(value);
	}
	this.waitingFor = function(node) {
		ii = this.colNumFor(node);
		waiting[ii] = node.name;
	}
	this.isWaitingFor = function(node) {
		ii = this.colNumFor(node);
		if (typeof(waiting[ii]) != 'undefined'
				&& waiting[ii] == node.name) {
			delete waiting[ii];
			return true;
		}
		return false;
	}
	this.fetchChildrenFor = function(node, shouldntScroll) {
		this.drawColumnFor(node, shouldntScroll);
		//force nodes to check for restored client only nodes, as they
		if (node.hasData() && !this.hasStoredChildNodes(node)) {
		    if(!(this.multiSelectEnabled && node.isLeaf() && !node.isGrowable)) {
			    this.drawChildrenFor(node);
			}
		} else {
			this.waitingFor(node);
			dataholder.getChildrenOf(node, this.childrenAddedTo.bind(this));
		}
	}
	this.childrenAddedTo = function(node) {
		if (node == null) {
			return; // should be an associative array
		} else if (node.children == null) {
			return;
		}
		if (this.isWaitingFor(node)) {
			this.drawChildrenFor(node);
		}
	}
	this.colNumFor = function(node) {
			/// Get the column number
		var colnum = 0;
		var curnode = node;
		
		while (curnode.parent != null) { // root's parent is null
			curnode = curnode.parent;
			colnum++;
		}
		
		return colnum;
	}
	this._minScrollHolderWidth = function() {
		return this.view().scrollLeft + this.viewWidth;
		//this.scrollHolder.style.width = (this.colWidth * (ii+1))+"px";
	}
	
	this.goToPath = function(path) {
		
		while (path.substring(0,1) == ' ') {
			path = path.substring(1, path.length);
		}
		
		this.pathParts = path.split(pathSeparator);
		
		while (this.lastRenderedNode.parent != null) {
			
			var id = this.getID(this.lastRenderedNode.getPath());
			var found = false;		
			
			for (var ii = 0; ii < this.pathParts.length; ii++) {
				if(this.pathParts[ii] == id) {
					found = true;
					break;
				}
			}
				
			if(found) {
				this.fetchChildrenFor(this.lastRenderedNode, false);
				return;
			}
				
			this.lastRenderedNode = this.lastRenderedNode.parent;
		}
		
		this.fetchChildrenFor(this.root, false);					
	}
	
	this.drawColumnFor = function(node, shouldntScroll) {
		var colnum = this.colNumFor(node);
			/// Delete the now redundant columns
		var firstdel = true; //treat the first deletion special to stop view jumping around
		if (colnum >= 0) {
			for (var ii=this.curcol; ii >= colnum; ii--) { //count down in case the browser redraws midway, the columns will be disappearing logically
				var delcolid = this.getIdForColNum(ii);
				var delcol = document.getElementById(delcolid);
				if (firstdel) {
					var newwidth = this._minScrollHolderWidth(); // if we make the holder smaller then the view will jump
					this.scrollHolder.style.width = newwidth+"px";
					firstdel = false;
				}
				var delcoladd = document.getElementById(delcolid+"add");
				if (delcoladd) {
					this.view().removeChild(delcoladd);
				}
				if (delcol) {
				    this.view().removeChild(delcol);
				    delete(delcol);
				}
			}
		}
		//no column to show in this case, as the "selected" message is meaningless
		if(!(this.multiSelectEnabled && node.isLeaf()) || node.isGrowable) {
			    /// Create the column
		    var newcol = document.createElement('div');
		    newcol = this.view().appendChild(newcol);
		    newcol.className = COLCLASS;
		    if (!node.isLeaf()) {
		        newcol.className += " "+COLLOADCLASS;
		    }
		    newcol.id = this.getIdForColNum(colnum);
		    newcol.style.zIndex = 500;
    		
			    ///	Automatic column sizing goodness
		    if (!this.colHeight || !this.colWidth) {
				    // Column height
			    newcol.style.height = ARBTALLCOL+"px"; // arbitrary 'big' number. Hopefully not fullscreen on a 30 inch monitor
			    var boxedge = newcol.offsetHeight - ARBTALLCOL; // keep track of box model differences in case the columns have borders, margin, etc
			    this.view().scrollTop = 9999; //arbitrary other big number. should clip
			    this.colHeight = newcol.offsetHeight - this.view().scrollTop - boxedge;
			    this.view().scrollTop = 0;
    		
				    // View inner width
			    this.viewWidth = this.view().offsetWidth - 2;
    			
				    // Column width
				    // 100 is a throwaway number here to get the columns boxedge
			    newcol.style.width = "100px";
			    var boxedge = newcol.offsetWidth - 100; //damn box models
			    this.colWidth = Math.floor((this.viewWidth) / this.numcol) - boxedge;
    		
				    // now create the scroll holder (used when column are deleted but we still need extra space for the animation)
			    scrollHolder = document.createElement('div');
			    this.scrollHolder = this.view().appendChild(scrollHolder);
			    this.scrollHolder.style.position = "absolute";
			    this.scrollHolder.style.backgroundColor = "#eee";
			    this.scrollHolder.style.left = "0px";
			    this.scrollHolder.style.width = this.colWidth+"px";
			    this.scrollHolder.style.height = '5px';
			    this.scrollHolder.zindex = 5;
		    }
		    newcol.style.height = this.colHeight+"px";
		    newcol.style.width = this.colWidth+"px";
		    newcol.style.left = newcol.offsetWidth * colnum + "px";
    		
		    this.curcol = colnum;
		    if (!shouldntScroll) {
			    this.scrollTo(newcol.offsetLeft - newcol.offsetWidth - this.viewOverlap);
		    }
        } 
	}
	this.drawChildrenFor = function(node) {
		if(this.pathParts != null){
			this.markIfOnPath(node, false);
		}
		
		var colnum = this.colNumFor(node);
		//Remove selected message from previous column, if present.
		var prevColdiv = document.getElementById(this.getIdForColNum(colnum-1));
		if(prevColdiv)
		{
		    if(prevColdiv.childNodes[0] && !prevColdiv.childNodes[0].node)
		        prevColdiv.removeChild(prevColdiv.childNodes[0]);
		}
		var coldiv = document.getElementById(this.getIdForColNum(colnum))
		
		node.column.drawInto(coldiv);
		
		if(this.pathParts != null){
			this.navigateToPath(node, false);
		}
	}
	this.navigateToPath = function(node, shouldntScroll) {
		
		var nextNode = null;
		
		if (this.pathParts.length > 0) {
			if(node.parent != null) {
				var id = this.getID(node.getPath());
				
				while (this.pathParts[0] != id) {
						this.pathParts.shift();
				}
				this.pathParts.shift();
			}
			if (node.children != null) {
				for (var ii = 0; ii < node.children.length; ii++){
					if (this.pathParts[0] == this.getID(node.children[ii].getPath())) {
						nextNode = node.children[ii];
						break;
					}
				}
			}
		}
		
		if (nextNode != null){
			node = nextNode;
			this.fetchChildrenFor(node, shouldntScroll);						
		}
	}
	this.markIfOnPath = function(node, shouldntScroll) {
		
		if (node.children != null) {
			for (var jj = 0; jj < node.children.length; jj++){
				var found = false;
				var id = this.getID(node.children[jj].getPath());
				
				for (var ii = 0; ii < this.pathParts.length; ii++){
				
					if (this.pathParts[ii] == id) {
						node.children[jj].startSelected = true;
						found = true;
						break;
					}
				}
				if (found) {
					break;
				}
			}
		}
	}
	this.scrollTo = function(left) {
		// BOUNDS CHECKING
		if (left > (this.curcol-1) * this.colWidth)
			left = (this.curcol-1) * this.colWidth;
		if (left < 0) 
			left = 0;
		if (left != this.view().scrollLeft) {
			this.scroller.headTo(left);
		}
	}
	this.jumpTo = function(left) {
		// bounds checking isn't important for jumping - no animation to mess up
		this.scroller.jumpTo(left);
	}
	this.showColumnFor = function(node, shouldntScroll) {
	    this.fetchChildrenFor(node, shouldntScroll);
	}
	this.getIdForColNum = function(colnum) {
		return this.view().id+"_col"+colnum;
	}
	this.clientIdFor = function(node) {
		var nodePath = node.getPath().replace(/\s/g, '-');
		return this.view().id+"_node"+nodePath;
	}
	/* EVENTS
	 * specified by an id of a global function
	 */
	this.setEventId = function(eventKey, funcId) {
		this.events[eventKey] = funcId
	}
	this.fireEvent = function(eventKey, arg) {
		var result = {};
		result['fired'] = false;
		if (this.events[eventKey]) {
			if (typeof(window[this.events[eventKey]]) == 'function') {
				result['fired'] = true;
				result['output'] = window[this.events[eventKey]](arg);
			}
		}
		return result;
	}
	this.setHiddenFieldId = function(id) {
		hiddenID = id;
		this.hiddenfield().value = ""; // reset for reload
	}
	this.hiddenfield = function() {
		return document.getElementById(hiddenID);
	}
	this.setPathSeparator = function(ps) {
		pathSeparator = ps;
	}
	this.pathSeparator = function() {
		return pathSeparator;
	}
	this.store = function(node) {
		// store nodes in case of failed post-back
		stored.push(node);
		this.writeStoredInput();
	}
	this.unstore = function(node) {
		var ii;
		for (ii=0; ii < stored.length; ii++) {
			if (node.getPath() == stored[ii].getPath())
				break;
		}
		stored.splice(ii, 1);
		this.writeStoredInput();
	}
	this.writeStoredInput = function() {
		this.hiddenfield().value = "";
		for (var ii=0; ii < stored.length; ii++) {
			this.hiddenfield().value += stored[ii].encodeData()+"\n";
		}
	}
	this.hasStoredChildNodes = function(node) {
		var nodePath = node.getPath();
		for (var ii=0; ii < stored.length; ii++) {
			var parentPath = stored[ii].attributes.ParentPath;
			if(!parentPath && stored[ii].parent) {
				parentPath = stored[ii].parent.getPath();
			}
			if(parentPath == node.getPath()) {
				return true;
			}
		}
		return false;
	}
	this.cacheNode = function(node) {
		dataholder.cacheNode(node);
	}
	this.getNewCount = function() {
		var now = new Date();
		return now.getTime();
	}
	this.addingFor = function(col) {
		if (col && addingCol)
			addingCol.cancelAdd();
		addingCol = col;
	}
	this.addNodeFromExternal = function(name, id, allowDelete) {
		addingCol.addWithName(name, id, allowDelete);
	}
	/*
	 * Multiselection code
	 */
	this.setMultiSelectEnabled = function(enabled, hierarchical) {
		this.multiSelectEnabled = enabled;
		if (enabled){
		    this.hierarchicalSelection = hierarchical;
		}
	}
	this.parseSelectedNodeData = function(datastr) {
	    var newnode = new ColumnNode();
	    newnode.setView(this);
	    newnode.parseData(datastr);
	    this.selectedNodes[newnode.getPath()] = newnode;
	}
	this.onPathToSelected = function(node) {
        var re = new RegExp("\^" + node.getPath() + pathSeparator);
        var ii;
        for(ii in this.selectedNodes) {
            if(ii.match(re)) {
                return true;
            }
        }
        return false;
	}	
	this.parsePrunedNodeData = function(datastr) {
	    var newnode = new ColumnNode();
	    newnode.setView(this);
	    newnode.parseData(datastr);
	    this.prunedNodes[newnode.getPath()] = newnode;
	}
	this.onPathToPruned = function(node) {
        var re = new RegExp("\^" + node.getPath() + pathSeparator);
        var ii;
        for(ii in this.prunedNodes) {
            if(ii.match(re)) {
                return true;
            }
        }
        return false;
	}	
	this.addSelected = function(node) {
	    var stateChanged = false;
        if(this.prunedNodes[node.getPath()]) {
		    delete this.prunedNodes[node.getPath()];
		    stateChanged = true;
		} else {
		    this.selectedNodes[node.getPath()] = node; 
		    stateChanged = true;
		}		
		if(this.hierarchicalSelection) {
		    stateChanged = (this.clearChildStateFor(node) || stateChanged);
	    }
	    return stateChanged;
	}
	this.removeSelected = function(node) {
	    var stateChanged = false;
        if(this.selectedNodes[node.getPath()]) {
		    delete this.selectedNodes[node.getPath()];
		    stateChanged = true;
		} else if (this.hierarchicalSelection && this.ancestorSelectedFor(node)){//(this.hierarchicalSelection && this.ancestorSelectedFor(node) && !this.ancestorPrunedFor(node)) {
		    this.prunedNodes[node.getPath()] = node; 
		    stateChanged = true;
		}		
		if(this.hierarchicalSelection) {
		    stateChanged = (this.clearChildStateFor(node) || stateChanged);
	    }
	    return stateChanged;
	}
	this.isPruned = function(node) {
	    if(this.prunedNodes[node.getPath()]) {
	        return true;
	    } else {
	        return false;
	    }
	}
	this.isSelected = function(node) {
	    if(this.selectedNodes[node.getPath()]) {
	        return true;
	    } else {
	        return false;
	    }
	}
	this.clearChildStateFor = function(node) {
        var re = new RegExp("\^" + node.getPath() + pathSeparator);
        var ii;
        var stateChanged = false;
        for(ii in this.prunedNodes) {
            if(ii.match(re)) {
                stateChanged = true;
                delete this.prunedNodes[ii];
            }
        }
        for(ii in this.selectedNodes) {
            if(ii.match(re)) { 
                stateChanged = true;
                delete this.selectedNodes[ii];
            }
        } 
        return stateChanged;   
	}
	this.ancestorSelectedFor = function(node) {
        if (this.selectedNodes[node.getPath()]) {
            return true;
        } else if(!node.parent) {
            return false;
        } else {
            return this.ancestorSelectedFor(node.parent);
        }
    }
    this.ancestorPrunedFor = function(node) {
        if (this.prunedNodes[node.getPath()]) {
            return true;
        } else if(!node.parent) {
            return false;
        } else {
            return this.ancestorPrunedFor(node.parent);
        }
    }
	this.renderSelectionState = function() {
	    var state = new Array();
	    var selected = new Array();
	    var pruned = new Array();
	    var x = 0;
	    var ii,jj;
		for (ii in this.selectedNodes) {
			selected[x] = this.selectedNodes[ii];
			x++;
		}
		state[0] = selected;
		if(this.hierarchicalSelection) {
	        x = 0;
		    for (jj in this.prunedNodes) {
			    pruned[x] = this.prunedNodes[jj];
			    x++;
		    }
	        
	        state[1] = pruned;
		} 
		return state;
	}
}