/**
 * $Id$
 *
 * Javascript DOM Library v0.1
 *
 * Copyright Noggin <info@noggin.com.au> 2007
 *
 */

NG.AJAX = function (url, responseHandler) {
	this.responseHandlers = {};
	this.url = url;
	if (arguments.length > 1) this.setResponseHandler(responseHandler);
	this.method = 'GET';
	this.requestHeaders = {};
	this.async = true;
	this.lastState = null;
}

NG.AJAX.UNITIALIZED = 0;
NG.AJAX.LOADING = 1;
NG.AJAX.LOADED = 2;
NG.AJAX.INTERACTIVE = 3;
NG.AJAX.COMPLETE = 4;

NG.AJAX.prototype.setRequestHeader = function (key, value) { this.requestHeaders[key] = value; }

NG.AJAX.prototype.setResponseHandler = function (responseHandler, status, readyState) {
	if (arguments.length < 2) status = 200;
	if (arguments.length < 3) readyState = NG.AJAX.COMPLETE;
	if (!isDef(typeof this.responseHandlers[readyState])) this.responseHandlers[readyState] = {};
	this.responseHandlers[readyState][status] = responseHandler;
}

NG.AJAX.prototype.send = function (content) {
	if (arguments < 1) content = '';
	var req = null;
	if (window.XMLHttpRequest) {
		try { req = new XMLHttpRequest(); } catch (e) { req = null; }
	} else if (window.ActiveXObject) {
		try { req = new ActiveXObject('Msxml2.XMLHTTP'); }
		catch(e) { try { req = new ActiveXObject('Microsoft.XMLHTTP'); } catch(e) { req = null; } }
	}
	if (req) {
		this.req = req;
		var w = this;
		req.onreadystatechange = function () {
			if (req && isFunc(isDef)) {
				try { var r = req.readyState; } catch (e) { return; }
				try { var s = req.status; } catch (e) { return; }
				w.lastState = r;
				if (!isDef(typeof w.responseHandlers[r]) && isDef(typeof w.responseHandlers[''])) r = '';
				if (r == NG.AJAX.COMPLETE) NG.delClass(document.body, 'ngcursor-progress');
				if (isDef(typeof w.responseHandlers[r])) {
					if (isDef(typeof w.responseHandlers[r][s])) {
						w.responseHandlers[r][s](req);
					} else if (isDef(typeof w.responseHandlers[r][''])) {
						w.responseHandlers[r][''](req);
					}
				}
			}
		}
		req.open(this.method, this.url, this.async);
		for (var i in this.requestHeaders) {
			if (!isFunc(this.requestHeaders[i])) req.setRequestHeader(i, this.requestHeaders[i]);
		}
		NG.addClass(document.body, 'ngcursor-progress');
		var c = content;
		setTimeout(function () { req.send(c); }, 0);
	} else {
		throw('Could not create AJAX request');
	}
}

NG.AJAX.prototype.cancel = function () {
	if (this.req && this.lastState != NG.AJAX.COMPLETE) this.req.abort();
}

NG.AJAX.send = function (url, responseHandler, content) { new NG.AJAX(url, responseHandler).send(content); }

NG._zdisregard = 0;

NG.hideZdisregard = function () {
	if (NG._zdisregard == 0) {
		var e = document.getElementsByTagName('embed');
		for (var i = 0; i < e.length; i++) e[i].style.display = 'none';
		var e = document.getElementsByTagName('object');
		for (var i = 0; i < e.length; i++) e[i].style.visibility = 'hidden';
		if (NG.ua.isEng('MSIE', '6.0', '<=')) {
			var e = document.getElementsByTagName('select');
			for (var i = 0; i < e.length; i++) e[i].style.visibility = 'hidden';
		}
	}
	NG._zdisregard++;
}

NG.showZdisregard = function () {
	if (NG._zdisregard > 0) NG._zdisregard--;
	if (NG._zdisregard == 0) {
		var e = document.getElementsByTagName('embed');
		for (var i = 0; i < e.length; i++) e[i].style.display = 'block';
		var e = document.getElementsByTagName('object');
		for (var i = 0; i < e.length; i++) e[i].style.visibility = 'visible';
		if (NG.ua.isEng('MSIE', '6.0', '<=')) {
			var e = document.getElementsByTagName('select');
			for (var i = 0; i < e.length; i++) e[i].style.visibility = 'visible';
		}
	}
}

/***********************
 * Class manipulation
 */

NG.addClass = function (node, className) {
	var c = (node.className+'').split(/ +/);
	for (var j = 1; j < arguments.length; j++) if (c.search(arguments[j]) == -1) node.className += ' '+arguments[j];
}

NG.delClass = function (node, className) {
	var i,c = (node.className+'').split(/ +/);
	for (var j = 1; j < arguments.length; j++) {
		if (arguments[j].constructor == RegExp) {
			for (var i = 0; i < c.length; i++) if (className.test(c[i])) { c.splice(i,1); break; }
		} else {
			if ((i = c.search(arguments[j])) >= 0) c.splice(i,1);
		}
	}
	node.className = c.join(' ');
}

NG.replaceClass = function (node, oldClassName, newClassName) {
	var i,c = (node.className+'').split(/ +/);
	if (oldClassName.constructor == RegExp) {
		for (var i = 0; i < c.length; i++) {
			if (oldClassName.test(c[i])) c.splice(i,1);
		}
	} else if ((i = c.search(oldClassName)) >= 0) {
		c.splice(i,1);
	}
	if ((i = c.search(newClassName)) < 0) c.push(newClassName);
	node.className = c.join(' ');
}

NG.hasClass = function (node, className) {
	if (node.nodeType != 1) return false;
	var i,c = (node.className+'').split(/ +/);
	for (var j = 1; j < arguments.length; j++) {
		if (arguments[j].constructor == RegExp) {
			for (var i = 0; i < c.length; i++) if (className.test(c[i])) return true;
		} else {
			if ((i = c.search(arguments[j])) >= 0) return true;
		}
	}
	return false;
}

NG.addStyleRule = function (selector, rule, pos) {
	if (document.styleSheets.length > 0) {
		var s = document.styleSheets[0];
		if (arguments.length < 3) pos = 0;
		else if (pos < 0) pos = (isDef(typeof s.cssRules) ? s.cssRules : s.rules).length+pos+1;
		if (isDef(typeof s.insertRule)) s.insertRule(selector+' { '+rule+' }', pos);
		else if (isDef(typeof s.addRule)) {
			var ss = selector.split(/, */);
			for (var i = 0; i < ss.length; i++) s.addRule(ss[i], rule, pos);
		}
	}
}

NG.getComputedStyle = function (node) {
	if (window.getComputedStyle) return window.getComputedStyle(node, '');
	if (node.currentStyle) return node.currentStyle;
	return {};
}

NG.getComputedStylePx = function (node, field) {
	var m,v = NG.getComputedStyle(node)[field];
	if (m = /^([0-9]+(\.[0-9]+)?)px$/.exec(v)) return parseInt(m[1]);
	return 0;
}

NG.setNodeHeight = function (node, px) {
	if (isNaN(px)) return;
	var h = px-NG.getComputedStylePx(node, 'paddingTop')-NG.getComputedStylePx(node, 'paddingBottom');
	h -= NG.getComputedStylePx(node, 'borderTopWidth')+NG.getComputedStylePx(node, 'borderBottomWidth');
	if (h >= 0) node.style.height = h+'px';
}


/***********************
 * Drag & drop
 */
NG.drag = function (item) {
	NG.drag.cancel();
	NG.drag.tentativeItem = item;
	NG.addEventListener(document, 'mouseup', NG.drag.cancel);
	NG.addEventListener(document, 'mousemove', NG.drag.begin);
}

NG.drag.cursorMap = {
	'can-drop' : 'Drop here',
	'move-drop' : 'Drop here to move',
	'copy-drop' : 'Drop here to copy',
	'link-drop' : 'Drop here to link',
	'bad-drop' : 'Not permitted to drop here',
	'no-drop' : 'Cannot drop here'
}

NG.drag.hasRunOnce = false;
NG.drag.runOnce = function () {
	if (!NG.drag.hasRunOnce) {
		NG.drag.hasRunOnce = true;
		NG.addStyleRule('.ng-drop', 'position:absolute; padding:2px; padding-left:4px; padding-right:4px; z-Index:100; color:white;');
		NG.addStyleRule('.ng-can-drop, .ng-move-drop, .ng-copy-drop, .ng-link-drop', 'background-color:#129301;');
		NG.addStyleRule('.ng-bad-drop', 'background-color:#d00000; padding:2px; z-Index:100; color:white;');
		NG.addStyleRule('.ng-no-drop', 'background-color:#dcdcdc;');
	}
}

NG.drag.curItem = NG.drag.curZone = NG.drag.tentativeItem = null;
NG.drag.setCursor = function (cursor) {
	NG.drag.runOnce();
	if (arguments.length == 0) {
		if (NG.drag.setCursor.node && NG.drag.setCursor.node.parentNode) {
			NG.drag.setCursor.node.parentNode.removeChild(NG.drag.setCursor.node);
		}
		NG.removeEventListener(document, 'mousemove', NG.drag.updateCursor);
		return;
	}
	if (!NG.drag.setCursor.node) {
		NG.drag.setCursor.node = document.createElement('div');
		NG.drag.setCursor.node.appendChild(document.createTextNode(' '));
		NG.drag.setCursor.node.className = 'no-drop';
	}
	NG.drag.setCursor.node.firstChild.nodeValue = NG.drag.cursorMap[cursor];
	NG.drag.setCursor.node.className = 'ng-drop ng-'+cursor;
	if (!NG.drag.setCursor.node.offsetParent) {
		document.body.appendChild(NG.drag.setCursor.node);
		NG.addEventListener(document, 'mousemove', NG.drag.updateCursor);
	}
}

NG.drag.updateCursor = function (e) {
	if (!e) e = window.event;
	if (NG.drag.setCursor.node) {
		NG.drag.setCursor.node.style.top = (e.clientY+5)+'px';
		NG.drag.setCursor.node.style.left = (e.clientX+8)+'px';
	}
}

NG.drag.setCursor.node = null;
NG.drag.begin = function (e) {
	if (NG.drag.tentativeItem) {
		NG.drag.curItem = NG.drag.tentativeItem;
		NG.drag.setCursor('no-drop');
		NG.drag.curItem.ondragstart(e);
	}
	if (document.selection) document.selection.empty();
	NG.removeEventListener(document, 'mousemove', NG.drag.begin);
}
NG.drag.enterZone = function (zone, e) {
	if (!NG.drag.curItem) return;
	if (!e) e = window.event;
	if (zone.canDrop(e)) {
		if (NG.drag.curZone && zone != NG.drag.curZone) NG.drag.curZone.out(e);;
		NG.drag.setCursor('can-drop');
		NG.drag.curZone = zone;
		NG.drag.overCurZone = function (e) { zone.over(e); }
		NG.addEventListener(document, 'keydown', NG.drag.overCurZone);
		NG.addEventListener(document, 'keyup', NG.drag.overCurZone);
		zone.over(e);
	} else {
		NG.drag.setCursor('bad-drop');
	}
	NG.stopPropagation(e);
}
NG.drag.leaveZone = function (zone, e) {
	if (!NG.drag.curItem) return;
	if (NG.drag.curZone == zone) {
		if (!e) e = window.event;
		NG.removeEventListener(document, 'keydown', NG.drag.overCurZone);
		NG.removeEventListener(document, 'keyup', NG.drag.overCurZone);
		NG.drag.overCurZone = function () {}
		zone.out(e);
		NG.drag.curZone = null;
		NG.drag.setCursor('no-drop');
	}
}
NG.drag.dropInZone = function (zone, e) {
	if (!e) e = window.event;
	if (NG.drag.curItem && zone.canDrop(e)) {
		NG.drag.curZone = zone;
		NG.drag.curZone.drop(e);
		NG.drag.curItem.ondrop(e);
	}
	NG.drag.cancel(e);
	NG.stopPropagation(e);
}
NG.drag.cancel = function (e) {
	if (!e) e = window.event;
	if (NG.drag.curZone) NG.drag.curZone.out(e);
	if (NG.drag.curItem) NG.drag.curItem.ondragend(e);
	NG.removeEventListener(document, 'mouseup', NG.drag.cancel);
	NG.removeEventListener(document, 'mousemove', NG.drag.begin);
	NG.removeEventListener(document, 'keydown', NG.drag.overCurZone);
	NG.removeEventListener(document, 'keyup', NG.drag.overCurZone);
	NG.drag.setCursor();
	NG.drag.curItem = NG.drag.curZone = NG.drag.tentativeItem = null;
}
NG.drag.item = function (type) {
	this.type = type;
	this.ondragstart = function (e) {};
	this.ondragend = function (e) {};
	this.ondrop = function (e) {};
}
NG.drag.zone = function () {}
NG.drag.zone.prototype.canDrop = function (e) { return false; }
NG.drag.zone.prototype.over = function (e) { }
NG.drag.zone.prototype.out = function (e) { }
NG.drag.zone.prototype.drop = function (e) { }
NG.drag.zone.prototype.attach = function (node) {
	var z = this;
	NG.addEventListener(node, 'mouseover', function (e) { NG.drag.enterZone(z,e); });
	NG.addEventListener(node, 'mouseout', function (e) { NG.drag.leaveZone(z,e); });
	NG.addEventListener(node, 'mouseup', function (e) { NG.drag.dropInZone(z,e); });
}


/*********************************************
 * NG Tree
 */
NG.tree = function () {
	this.collections = {};
	this.newCollections = this.instructions = this.canDropList = [];
	this.node = this.focusedItem = this.editingItem = null;
	this.layers = [ NG.tree.layers.toggle, NG.tree.layers.label ];
	this.hooks = {};
	this.isEditable = this.isDraggable = this.canAppearMultipleTimesInParent = false;
	this.canCreate = this.isCopyable = this.isMoveable = this.isLinkable = this.canMultiFocus = false;
	this.linkOnNotCopyable = false;
	this.deepCopy = true;
	this.dragName = 'NG.tree.item';
	this.dataurl = null;
	this.saveurl = null;
	this.ajaxq = [];
	this.ajaxqp = false;
}

NG.tree.prototype.qajax = function (o) {
	if (this.saveurl === null) return;
	this.ajaxq.push(o);
	this.flush_ajaxq();
}

NG.tree.prototype.flush_ajaxq = function () {
	if (!this.ajaxqp && this.ajaxq.length > 0) {
		this.ajaxqp = true;
		var f = ['pk', 'apk', 'rpk', 'cpk'];
		var o = ['pos', 'label'];
		var a = this.ajaxq[0];
		var url = new NGUrl(isDef(typeof a.url) ? a.url : this.saveurl);
		for (var i = 0; i < f.length; i++) if (isDef(typeof a[f[i]])) url.addArgument(f[i], ifnull(a[f[i]].pk, '\\N'));
		for (var i = 0; i < o.length; i++) if (isDef(typeof a[o[i]])) url.addArgument(o[i], a[o[i]]);
		var ajax = new NG.AJAX(url.toString());
		var w = this;
		ajax.setResponseHandler(function (req) { w.flush_ajaxq_success(req); }, 200);
		ajax.setResponseHandler(function (req) { w.flush_ajaxq_failure(req); }, '');
		ajax.send();
	}
}

NG.tree.prototype.flush_ajaxq_success = function (req) {
	var a = this.ajaxq.shift();
	if (isDef(typeof a.response)) a.response(req);
	this.ajaxqp = false;
	this.flush_ajaxq();
}

NG.tree.prototype.flush_ajaxq_failure = function (req) {
	this.ajaxq.shift();
	this.ajaxqp = false;
	this.reload();
	var s = [];
	if (req && req.responseXML) {
		var e = req.responseXML.getElementsByTagName('error');
		for (var i = 0; i < e.length; i++) if (e[i].firstChild.nodeType == 3) s.push(e[i].firstChild.nodeValue);
	}
	if (s.length == 0) s.push('We were unable to update the server with your changes due to an unknown problem');
	alert('- '+s.join('\n- '));
}

NG.tree.prototype.addCollection = function (pk, label, options) {
	var c = new NG.tree.collection(pk, label, options);
	if (c.pk !== null) this.collections[pk] = c;
	else this.newCollections[(c.newOffset = this.newCollections.length)] = c;
	var w = this;
	c.getTree = function () { return w; }
	return c;
}

NG.tree.prototype.getRootItem = function () {
	if (!this.rootItem) {
		var w = this;
		var c = new NG.tree.collection(null,null)
		c.isComplete = false;
		c.getTree = function () { return w; }
		this.rootItem = new NG.tree.item(c);
		this.rootItem.getLayers = function () { return w.layers; }
		this.rootItem.refresh = function () { w.refresh(); }
		this.rootItem.expand = this.rootItem.collapse = this.rootItem.focus = this.rootItem.blur = function () {}
		this.rootItem.isVisible = function () { return true; }
		this.rootItem.isExpanded = true;
	}
	return this.rootItem;
}

NG.tree.prototype.getNode = function () {
	if (!this.node) {
		this.node = document.createElement('ul');
		for (var i = 0; i < this.getRootItem().children.length; i++) { this.node.appendChild(this.getRootItem().children[i].getNode()); }
		this.node.onselectstart = function () { return false; }
	}
	return this.node;
}

NG.tree.prototype.addHook = function (hook, func) {
	if (!isDef(typeof this.hooks[hook])) this.hooks[hook] = [];
	this.hooks[hook].push(func);
}

NG.tree.prototype.applyHooks = function (obj, hook, args) {
	if (isDef(typeof this.hooks[hook])) {
		for (var i = 0; i < this.hooks[hook].length; i++) this.hooks[hook][i].apply(obj, args);
	}
}

NG.tree.prototype.refresh = function () {
	if (!this.node) return;
	var n = this.node.parentNode;
	n.removeChild(this.node);
	this.node = null;
	this.attach(n);
}

NG.tree.prototype.focusItem = function (item) {
	if (this.focusedItem === item) return;
	if (this.editingItem) this.editingItem.stopEdit();
	this.editingItem = null;
	if (this.focusedItem) this.focusedItem.blur();
	if (item === this.getRootItem()) {
		if (item.children.length == 0) return;
		item = item.children[0];
	}
	item.focus();
	this.focusedItem = item;
	item.applyHooks('focus');
	NG.ContextMenu.closeAll();
}

NG.tree.prototype.editItem = function (item) {
	if (this.editingItem) this.editingItem.stopEdit();
	this.editingItem = item;
	if (item) item.startEdit();
}

NG.tree.prototype.i = function (pk, label, options) { this.addCollection(pk, label, options); }

NG.tree.prototype.l = function (pk) {
	var p = this.getRootItem().getCollection();
	if (pk !== null && isDef(typeof this.collections[pk])) p = this.collections[pk];
	for (var i = 1; i < arguments.length; i++) {
		if (isDef(typeof this.collections[arguments[i]])) p.linkChild(this.collections[arguments[i]]);
	}
}

NG.tree.prototype.addNew = function () {
	if (this.focusedItem && this.focusedItem.isExpanded) return this.focusedItem.addNew();
	if (this.focusedItem) return this.focusedItem.getParent().addNew(this.focusedItem.parentOffset+1);
	return this.getRootItem().addNew();
}

NG.tree.prototype.add = function (pk, label, options) { return this.getRootItem().add(pk, label, options); }
NG.tree.prototype.attach = function (node) { node.appendChild(this.getNode()); }

NG.tree.prototype.reattach = function () {
	if (NG.ua.isEng('MSIE')) {
		var n = this.getNode();
		//var st = this.node.lastChild.lastChild.scrollTop;
		n.parentNode.appendChild(n.parentNode.removeChild(n));
		//this.node.lastChild.lastChild.scrollTop = st;
	}
}

NG.tree.prototype.reload = function () { this.getRootItem().reload(); }
NG.tree.prototype.setAutoReload = function (secs) { this.getRootItem().setAutoReload(secs); }

/*********************************************
 * NG Tree item collection
 */
NG.tree.collection = function (pk, label, options) {
	this.pk = pk;
	this.newOffset = null;
	this.label = label;
	this.items = [];
	this.state = 0;
	this.dataurl = null;
	this.autoReload = null;
	this.autoReloadSecs = null;
	this.options = (arguments.length > 2 && isObj(options) ? options : {});
	this.isComplete = (!isDef(typeof this.options.complete) || this.options.complete ? true : false);
}

NG.tree.collection.prototype.getTree = function () { return null; }

NG.tree.collection.prototype.getDataUrl = function () {
	this.applyHooks('dataurl');
	var d = new NGUrl(this.dataurl === null ? this.getTree().dataurl : this.dataurl);
	if (this.pk !== null) d.addArgument('pk', this.pk, true);
	return d.toString();
}

NG.tree.collection.prototype.hasAncestor = function (collection) {
	if (this === collection) return true;
	for (var i = 0; i < this.items.length; i++) {
		if (this.items[i].getParent() && this.items[i].getParent().getCollection().hasAncestor(collection)) return true;
	}
	return false;
}

NG.tree.collection.prototype.copyChild = function (collection, pos, prefix) {
	if (this.hasAncestor(collection)) return false;
	if (arguments.length < 3) var prefix = 'Copy of ';
	return this._copyChild(collection, pos, prefix);
}

NG.tree.collection.prototype._copyChild = function (collection, pos, prefix) {
	if (this.items.length == 0 || collection.items.length == 0) return true;
	var c = this.getTree().addCollection(null, prefix+collection.label);
	if (this.linkChild(c, pos)) {
		if (this.getTree().deepCopy) {
			for (var i = 0; i < collection.items[0].children.length; i++) {
				c.copyChild(collection.items[0].children[i].getCollection(), i, '');
			}
		} else {
			for (var i = 0; i < collection.items[0].children.length; i++) {
				c.linkChild(collection.items[0].children[i].getCollection(), i);
			}
		}
		return true;
	}
	return false;
}

NG.tree.collection.prototype.linkChild = function (collection, pos) {
	this.isComplete = true;
	if (this.items.length == 0) return true;
	if (arguments.length < 2) var pos = this.items[0].children.length;
	if (this.hasAncestor(collection)) return false;
	for (var i = 0; i < this.items.length; i++) this.items[i].attachCollection(collection, pos);
	return true;
}

NG.tree.collection.prototype.removeChild = function (collection) {
	if (this.items.length == 0) return;
	for (var i = 0; i < this.items.length; i++) this.items[i].detachCollection(collection);
}

NG.tree.collection.prototype.startDrag = function () {
	for (var i = 0; i < this.items.length; i++) {
		NG.addClass(this.items[i].getNode(), 'drag');
		if (this.items[i].isExpanded) {
			for (var j = 0; j < this.items[i].children.length; j++) this.items[i].children[j].getCollection().startDrag();
		}
	}
}

NG.tree.collection.prototype.stopDrag = function () {
	for (var i = 0; i < this.items.length; i++) {
		NG.delClass(this.items[i].getNode(), 'drag');
		if (this.items[i].isExpanded) {
			for (var j = 0; j < this.items[i].children.length; j++) this.items[i].children[j].getCollection().stopDrag();
		}
	}
}
NG.tree.collection.prototype.refresh = function () {
	for (var i = 0; i < this.items.length; i++) this.items[i].refresh();
}

NG.tree.collection.prototype.select = function (state) {
	if (arguments.length == 0) state = (this.state+1)%2;
	for (var i = 0; i < this.items.length; i++) {
		this.items[i].select(state);
	}
}

NG.tree.collection.prototype.setLabel = function (label) {
	if (label == this.label || label.length == 0) return;
	this.label = label;
	this.refresh();
	this.getTree().qajax({'pk' : this, 'label' : label});
	this.applyHooks('changed');
}

NG.tree.collection.prototype.applyHooks = function (hook, args) {
	if (arguments.length < 2) var args = [];
	this.getTree().applyHooks(this, hook, args);
}

/*********************************************
 * NG Tree item
 */
NG.tree.item = function (collection) {
	this.children = [];
	this.isExpanded = (isDef(typeof collection.options.exp) ? collection.options.exp : false);
	var c = collection;
	this.getCollection = function () { return c; }
	this.getParent = function () { return null }
	this.node = this.parentOffset = this.last = this.editNode = this.loadNode = null;
	if (c.items.length > 0) {
		for (var i = 0; i < c.items[0].children.length; i++) this.attachCollection(c.items[0].children[i].getCollection(),i);
	}
	if (c) c.items.push(this);
	this._isfound = false;
	this.focusTeam = [];
}

NG.tree.item.prototype.getValues = function () {
	var values = [this.getValue()];
	for (var i = 0; i < this.focusTeam.length; i++) {
		values.push({ 'value' : this.focusTeam[i].pk, 'label' : this.focusTeam[i].label });
	}
	return values;
}

NG.tree.item.prototype.getValue = function () {
	var c = this.getCollection();
	return { 'value' : c.pk, 'label' : c.label };
}

NG.tree.item.prototype.copyChild = function (item, pos) {
	if (arguments.length < 2 || pos > this.children.length) var pos = this.children.length;
	if (this.getCollection().copyChild(item.getCollection(), pos)) return this.last;
	return false;
}

NG.tree.item.prototype.linkChild = function (item, pos) {
	if (arguments.length < 2 || pos > this.children.length) var pos = this.children.length;
	if (this.getCollection().linkChild(item.getCollection(), pos)) return this.last;
	return false;
}
NG.tree.item.prototype.moveChild = function (item, pos) {
	if (arguments.length < 2 || pos > this.children.length) var pos = this.children.length;
	if (this.getCollection().linkChild(item.getCollection(), pos)) {
		item.getParent().detachCollection(item.getCollection(), this.last);
		return this.last;
	}
	return false;
}

NG.tree.item.prototype.removeChild = function (item) {
	this.getCollection().removeChild(item.getCollection());
}

NG.tree.item.prototype.getLayers = function () { return (this.layers ? this.layers : this.getParent().getLayers()); }
NG.tree.item.prototype.getTree = function () { return this.getCollection().getTree(); }
NG.tree.item.prototype.applyHooks = function (hook, args) {
	if (arguments.length < 2) var args = [];
	this.getTree().applyHooks(this, hook, args);
}

NG.tree.item.prototype.attachCollection = function (collection, pos) {
	for (var j = pos; j < this.children.length; j++) this.children[j].parentOffset++;
	var w = this;
	this.last = new NG.tree.item(collection);
	this.last.getParent = function () { return w; }
	this.children.splice(pos, 0, this.last);
	this.last.parentOffset = pos;
	if (!this.canAppearMultipleTimesInParent) this.detachCollection(collection, this.last);
	this.refresh();
	return this.last;
}

NG.tree.item.prototype.detachCollection = function (collection, excludeItem) {
	for (var i = collection.items.length-1; i >= 0; i--) {
		if (collection.items[i].getParent() == this && collection.items[i] !== excludeItem) {
			this.children.splice(collection.items[i].parentOffset,1);
			for (var j = collection.items[i].parentOffset; j < this.children.length; j++) this.children[j].parentOffset--;
			collection.items.splice(i,1);
		}
	}
	this.refresh();
}

NG.tree.item.prototype.setAutoReload = function (secs) {
	if (this.getCollection().autoReload) clearInterval(this.getCollection().autoReload);
	if (arguments.length > 0 && secs > 0) {
		var w = this;
		var f = function () { if (!w.getTree().ajaxqp && w.isVisible()) w.reload(); };
		if (!this.getCollection().isComplete) setTimeout(f, 0);
		this.getCollection().autoReload = setInterval(f, secs*1000);
	}
}

NG.tree.item.prototype.reload = function () {
	var d = this.getCollection().getDataUrl();
	if (d === null) return;
	var w = this;
	var a = new NG.AJAX(d);
	var f = function (req) {
		var t = req.responseXML.getElementsByTagName('tree')[0];
		w.hideLoading();
		if (t) w.load(t);
		if (w.getCollection().isComplete) w.expand();
		if (w.getCollection().autoReloadSecs > 0) {
			w.setAutoReload(w.getCollection().autoReloadSecs);
			w.getCollection().autoReloadSecs = null;
		}
	}
	a.setResponseHandler(f, 200);
	a.setResponseHandler(function () { w.hideLoading(); }, null);
	this.hideLoading();
	if (!this.getCollection().isComplete) {
		var p = (this.getTree().getRootItem() == this ? this.getTree().getNode().parentNode : this.getNode());
		p.appendChild(this.loadNode = document.createElement('div'));
		p.lastChild.className = 'ngcms-tree-loading';
		p.lastChild.appendChild(document.createTextNode('Loading...'));
	}
	a.send();
}

NG.tree.item.prototype.hideLoading = function () {
	if (this.loadNode && this.loadNode.parentNode) this.loadNode.parentNode.removeChild(this.loadNode);
	this.loadNode = null;
}

NG.tree.item.prototype.load = function (xml) {
	var c = this.getCollection();
	for (var j = 0; j < this.children.length; j++) this.children[j]._isfound = false;
	for (var pk,label,o=0,t,f,n,opt,i = 0; i < xml.childNodes.length; i++) {
		n = xml.childNodes[i];
		if (n.nodeType == 1 && n.nodeName == 'item') {
			f = false;
			pk = n.getAttribute('pk');
			label = n.getAttribute('label');
			opt=[];
			if (n.getAttribute('theme')) opt['theme'] = n.getAttribute('theme');
			for (var j = 0; f == false && j < this.children.length; j++) {
				if (this.children[j].getCollection().pk == pk) {
					if (label != this.children[j].getCollection().label) {
						this.children[j].getCollection().label = label;
						this.children[j].getCollection().refresh();
					}
					if (this.children[j].parentOffset != o) this.linkChild(this.children[j],o);
					f = true;
				}
			}
			if (!f) {
				if (!isDef(typeof this.getTree().collections[pk])) this.getTree().addCollection(pk, label,opt);
				this.getCollection().linkChild(this.getTree().collections[pk], o);
				if (this.getTree().focusedItem == this) this.expand();
			}

			if (n.getAttribute('focus')) this.getTree().focusItem(this.children[o]);
			this.children[o].load(n);
			this.children[o]._isfound = true;
			if (n.getAttribute('exp')) this.children[o].expand();
			o++;
		}
	}
	for (var j = 0; j < this.children.length; j++) {
		if (!this.children[j]._isfound) this.getCollection().removeChild(this.children[j].getCollection());
	}
	c.isComplete = (xml.getAttribute('complete') == 'no' ? false : true);
	if (isStr(xml.getAttribute('src'))) c.dataurl = xml.getAttribute('src');
	if (xml.getAttribute('interval') > 0) c.autoReloadSecs = xml.getAttribute('interval');
}

NG.tree.item.prototype.add = function (pk, label, options) {
	var t = this.getTree();
	if (!isDef(typeof t.collections[pk])) this.getTree().addCollection(pk, label, options);
	this.getCollection().linkChild(t.collections[pk], this.children.length);
	return this.last;
}

NG.tree.item.prototype.addNew = function (pos) {
	if (!this.getTree().canCreate) return;
	if (arguments.length == 0) var pos = this.children.length;
	var c = this.getTree().addCollection(null, 'New item');
	this.getCollection().linkChild(c, pos);
	var f = function (req) {
		try {
			var pk = req.responseXML.getElementsByTagName('pkalloc')[0].getAttribute('pk');
		} catch(e) {
			return;
		}
		c.pk = pk;
		c.getTree().collections[pk] = c;
	}
	this.getTree().qajax({'apk' : this.getCollection(), 'pos' : pos, 'response' : f});
	this.last.applyHooks('changed');
	this.getTree().focusItem(this.last);
	var l = c.label;
	c.label = '';
	this.last.startEdit();
	c.label = l;
}

NG.tree.item.prototype.getNode = function () {
	if (!this.node) {
		this.node = document.createElement('li');
		var c = this.getCollection();
		var n,l = this.getLayers();
		this.node.appendChild(n = document.createElement('div'));
		for (var i = 0; i < l.length; i++) {
			n.appendChild(document.createElement('div'));
			n = n.lastChild;
			NG.addClass(n, 'layer'+i);
			l[i].apply(this,[n]);
		}
		if (this.children.length > 0 || !c.IsComplete) {
			if (this.isExpanded) { this.isExpanded = false; this.expand(); }
			else if (this.children.length > 0) { NG.addClass(this.node.firstChild, 'collapsed'); }
		}
		NG.addClass(this.node.firstChild, 'state'+c.state);
		if (isStr(c.options.theme)) NG.addClass(this.node.firstChild, 'tree-theme-'+c.options.theme);
		var w = this;
		var d = new NG.tree.dropZone();
		d.getItem = function () { return w; }
		d.attach(this.node.firstChild);
		this.applyHooks('node');
	}
	return this.node;
}

NG.tree.item.prototype.refresh = function () {
	if (!this.node || !this.node.parentNode) return;
	var n = this.getNode().parentNode;
	var x = this.getNode().nextSibling;
	n.removeChild(this.getNode());
	this.node = this.editNode = null;
	if (x) n.insertBefore(this.getNode(), x);
	else n.appendChild(this.getNode());
	if (this.getTree().focusedItem === this) this.focus();
}

NG.tree.item.prototype.expand = function () {
	if (this.isExpanded || (this.children.length == 0 && this.getCollection().isComplete == true)) return;
	if (!this.getCollection().isComplete) return this.reload();
	this.getNode().appendChild(document.createElement('ul'));
	for (var i = 0; i < this.children.length; i++) { this.getNode().lastChild.appendChild(this.children[i].getNode()); }
	NG.replaceClass(this.getNode().firstChild, 'collapsed', 'expanded');
	this.getTree().reattach();
	this.isExpanded = true;
	this.applyHooks('expand');
}

NG.tree.item.prototype.collapse = function () {
	if (!this.isExpanded || this.children.length == 0) return;
	this.getNode().removeChild(this.getNode().lastChild);
	NG.replaceClass(this.getNode().firstChild, 'expanded', 'collapsed');
	this.getTree().reattach();
	this.isExpanded = false;
	this.applyHooks('collapse');
}

NG.tree.item.prototype.toggle = function () { if (this.isExpanded) this.collapse(); else this.expand(); }
NG.tree.item.prototype.makeVisible = function () { this.getParent().makeVisible(); this.getParent().expand(); }
NG.tree.item.prototype.isVisible = function () { return this.getParent().isExpanded && this.getParent().isVisible(); }

NG.tree.item.prototype.focus = function (ancestor) {
	NG.addClass(this.getNode().firstChild, (ancestor ? 'focused-ancestor' : 'focused'));
	if (!ancestor) {
		var c = this.getCollection();
		for (var i = 0; i < c.items.length; i++) {
			if (c.items[i] !== this) NG.addClass(c.items[i].getNode().firstChild, 'focused-instance');
		}
	}
	this.getParent().focus(true);
	this.getParent().expand();
}

NG.tree.item.prototype.onFocusTeam = function (item) {
	var c = item.getCollection();
	for (var i = 0; i < this.focusTeam.length; i++) if (c.pk == this.focusTeam[i].pk) return i;
	return false;
}

NG.tree.item.prototype.teamFocus = function () {
	var f = this.getTree().focusedItem;
	if (f) {
		if (f.onFocusTeam(this) !== false) {
			this.teamBlur();
			return false;
		} else {
			var c = this.getCollection();
			NG.addClass(this.getNode().firstChild, 'teamfocused');
			for (var i = 0; i < c.items.length; i++) {
				if (c.items[i] !== this) NG.addClass(c.items[i].getNode().firstChild, 'teamfocused-instance');
			}
			f.focusTeam.push(c);
		}
	} else {
		this.getTree().focusItem(this);
	}
	return true;
}

NG.tree.item.prototype.teamBlur = function () {
	var o,f = this.getTree().focusedItem;
	if (f && (o = f.onFocusTeam(this)) !== false) {
		var c = this.getCollection();
		for (var i = 0; i < c.items.length; i++) {
			NG.delClass(c.items[i].getNode().firstChild, 'teamfocused', 'teamfocused-instance');
		}
		f.focusTeam.splice(o,1);
	}
}

NG.tree.item.prototype.blur = function () {
	NG.delClass(this.getNode().firstChild, 'focused', 'focused-ancestor');
	var c = this.getCollection();
	for (var i = 0; i < c.items.length; i++) {
		if (c.items[i] !== this) NG.delClass(c.items[i].getNode().firstChild, 'focused-instance');
		for (var j = 0; j < c.items[i].focusTeam.length; j++) {
			for (var k = 0; k < c.items[i].focusTeam[j].items.length; k++) {
				NG.delClass(c.items[i].focusTeam[j].items[k].getNode().firstChild, 'teamfocused', 'teamfocused-instance');
			}
		}
		c.items[i].focusTeam = [];
	}
	this.getParent().blur();
	this.applyHooks('blur');
}

NG.tree.item.prototype.select = function (state) {
	this.getCollection().state = state;
	var p = this.getParent();
	var ss = (p ? this.getSiblingState() : state);
	if (ss == state && p) {
		this.getParent().select(state);
	} else {
		this.setState(state);
		if (ss == 0.5) {
			while (p) {
				NG.replaceClass(p.getNode().firstChild, /state[0-9]+/, 'state1-ancestor');
				p.getCollection().state = 0;
				p = p.getParent();
			}
		} else {
			while (p) {
				p.getCollection().state = ss;
				NG.replaceClass(p.getNode().firstChild, /state[0-9]+/, 'state'+ss);
				p = p.getParent();
			}
		}
	}
}

NG.tree.item.prototype.setState = function (state) {
	this.getCollection().state = state;
	NG.replaceClass(this.getNode().firstChild, /state[0-9]+/, 'state'+state);
	for (var j = 0; j < this.children.length; j++) {
		this.children[j].setState(state);
	}
}

NG.tree.item.prototype.getSiblingState = function () {
	var s = null;
	var p = this.getParent();
	for (var i = 0; i < p.children.length; i++) {
		if (s === null) s = p.children[i].getCollection().state;
		else if (s != p.children[i].getCollection().state) return 0.5;
	}
	return s;
}
/*
NG.tree.item.prototype.virtualSelect = function (state) {
	var c = 0;
	for (var i = 0; c == i && i < this.children.length; i++) {
		if (NG.hasClass(this.getNode().firstChild, 'state'+state)) c++;
		else if (NG.hasClass(this.getNode().firstChild, 'state'+state+'-ancestor')) c += 0.5;
	}
	if (c == this.children.length) var s = 'state'+state
	else if (c > 0) var s = 'state'+state+'-ancestor';
	else var s = 'state'+state;
	NG.replaceClass(this.getNode().firstChild, /state[0-9]+/, s);
	this.getParent().virtualSelect(state);
}*/

NG.tree.item.prototype.getNextSibling = function () {
	if (this.parentOffset < this.getParent().children.length-1) return this.getParent().children[this.parentOffset+1];
	return null;
}

NG.tree.item.prototype.getPreviousSibling = function () {
	if (this.parentOffset > 0) return this.getParent().children[this.parentOffset-1];
	return null;
}

NG.tree.item.prototype.startEdit = function () {
	if (this.editNode) return;
	var w = this;
	var n = this.getNode();
	var c = this.getCollection();
	while (n.firstChild && n.firstChild.nodeType == 1) n = n.firstChild;
	var s = document.createElement('span');
	s.innerHTML = '<input type="text" class="edit" />';
	this.editNode = s.firstChild;
	this.editNode.value = c.label;
	NG.addEventListener(this.editNode, 'blur', function (e) { w.stopEdit(); NG.stopPropagation(e); });
	NG.addEventListener(this.editNode, 'keypress', function (e) {
		if (!e) e = window.event;
		if (e.keyCode == 13) w.stopEdit();
		else if (e.keyCode == 27) w.cancelEdit();
	});
	this.getNode().appendChild(this.editNode);
	NG.addClass(this.getNode().firstChild, 'editlabel');
	this.editNode.focus();
}

NG.tree.item.prototype.stopEdit = function () {
	if (this.editNode) {
		this.getCollection().setLabel(this.editNode.value);
		this.cancelEdit();
	}
}

NG.tree.item.prototype.cancelEdit = function () {
	if (this.editNode) {
		this.refresh();
		this.editNode = null;
	}
	if (this.getTree().editingItem === this) this.getTree().editingItem = null;
}

NG.tree.layers = {
	'label' : function (node) {
		var w = this;
		var t = this.getTree();
		var c = this.getCollection();
		NG.addEventListener(node, 'click', function (e) { w.expand(); NG.stopPropagation(e); });
		NG.addEventListener(node.parentNode, 'mousedown', function (e) {
			var w2 = null;
			if (t.canMultiFocus && e.ctrlKey && t.focusedItem) {
				if (w.teamFocus()) w2 = t.focusedItem;
			} else if (t.canMultiFocus && t.focusedItem && t.focusedItem !== w && t.focusedItem.onFocusTeam(w)) {
				w2 = t.focusedItem;
			} else {
				t.focusItem(w);
				w2 = w;
			}
			if (w2 && t.isDraggable && t.editingItem !== w2) {
				var i = new NG.drag.item(t.dragName);
				i.item = w2;
				i.ondragstart = function () {
					w2.getCollection().startDrag();
					for (var j = 0; j < w2.focusTeam.length; j++) w2.focusTeam[j].startDrag();
				}
				i.ondragend = function () {
					w2.getCollection().stopDrag();
					for (var j = 0; j < w2.focusTeam.length; j++) w2.focusTeam[j].stopDrag();
				}
				NG.drag(i);
			}
			NG.preventDefault(e);
			NG.stopPropagation(e);
		});
		node.appendChild(document.createTextNode(c.label));
		if (t.isEditable) {
			NG.addEventListener(node.parentNode, 'dblclick', function (e) { t.editItem(w); NG.stopPropagation(e); });
			var contextMenu = []
			if (isDef(typeof t.hooks['edit'])) contextMenu.push({'label' : 'Edit', 'func' : function () { w.applyHooks('edit'); }});
			if (isDef(typeof t.hooks['delete'])) contextMenu.push({'label' : 'Delete', 'func' : function () { w.applyHooks('delete'); }});
			if (contextMenu.length > 0) NG.ContextMenu.attach(node.parentNode, contextMenu);
		}
	},
	'icon' : function (node) {
		var w = this;
		NG.addEventListener(node, 'click', function (e) { w.expand(); NG.stopPropagation(e); });
	},
	'toggle' : function (node) {
		var w = this;
		NG.addEventListener(node, 'click', function (e) { w.toggle(); NG.stopPropagation(e); });
	},
	'select' : function (node) {
		var w = this.getCollection();
		NG.addEventListener(node, 'click', function (e) { w.select(); NG.stopPropagation(e); });
	}
};

NG.tree.dropZone = function () {}
NG.tree.dropZone.prototype = new NG.drag.zone();
NG.tree.dropZone.prototype.direction = 'e';
NG.tree.dropZone.prototype.isNativeDrop = function () {
	return (NG.drag.curItem.type == this.getItem().getTree().dragName && NG.drag.curItem.item && NG.drag.curItem.item.getTree && NG.drag.curItem.item.getTree() == this.getItem().getTree());
}
NG.tree.dropZone.prototype.canDrop = function (e) {
	if (this.isNativeDrop()) {
		var t = this.getItem().getTree();
		if (NG.drag.curItem.item.focusTeam.length > 1) return false;
		if (e.ctrlKey && e.shiftKey && !t.isLinkable) return false;
		else if (e.ctrlKey && !e.shiftKey && !t.isCopyable && (!t.linkOnNotCopyable || !t.isLinkable)) return false;
		else if (!e.ctrlKey && !e.shiftKey && !t.isMoveable) return false;
		else if (this.getItem().getCollection().hasAncestor(NG.drag.curItem.item.getCollection())) return false;
		return true;
	} else {
		return (this.getItem().getTree().canDropList.search(NG.drag.curItem.type) > -1);
	}
}
NG.tree.dropZone.prototype.over = function (e) {
	this.out(e);
	var nd = this.isNativeDrop();
	if (nd) {
		var n = this.getItem().getNode();
		var y = NG.getPagePos(n).y;
		var h = n.offsetHeight/4;
		if (e.clientY < y+h && this.getItem().getPreviousSibling() != NG.drag.curItem.item) this.direction = 'n';
		else if (e.clientY > y+(3*h) && this.getItem().getNextSibling() != NG.drag.curItem.item) this.direction = 's';
		else {
			this.direction = 'e';
			if (!this.getItem().getCollection().isComplete) this.getItem().reload();
			if (this.getItem().children.length > 0 && !this.getItem().isExpanded) {
				var i = this.getItem();
				setTimeout(function () {if (/(^| )draghover\-e( |$)/.test(i.getNode().firstChild.className)) i.expand(); }, 1000);
			}
		}
	}
	var t = this.getItem().getTree();
	if (e.ctrlKey && e.shiftKey && (!nd || t.isLinkable)) NG.drag.setCursor('link-drop');
	else if (e.ctrlKey && !e.shiftKey && (!nd || t.isCopyable || (t.linkOnNotCopyable && t.isLinkable))) NG.drag.setCursor('copy-drop');
	else if (!e.ctrlKey && !e.shiftKey && (!nd || t.isMoveable)) NG.drag.setCursor('move-drop');
	else NG.drag.setCursor('bad-drop');
	NG.addClass(this.getItem().getNode().firstChild, 'draghover-'+this.direction);
}
NG.tree.dropZone.prototype.out = function (e) {
	NG.delClass(this.getItem().getNode().firstChild, /draghover\-[nse]/);
}
NG.tree.dropZone.prototype.drop = function (e) {
	if (this.isNativeDrop()) {
		switch (this.direction) {
			case 'n': var p = this.getItem().getParent(); var o = this.getItem().parentOffset; break;
			case 's': var p = this.getItem().getParent(); var o = this.getItem().parentOffset+1; break;
			case 'e': var p = this.getItem(); var o = this.getItem().children.length; break;
			default: return;
		}
		var t = p.getTree();
		if (e.ctrlKey && t.isLinkable && (e.shiftKey || (t.linkOnNotCopyable && !t.isCopyable))) {
			var ni = p.linkChild(NG.drag.curItem.item, o);
			t.qajax({'pk' : NG.drag.curItem.item.getCollection(), 'apk' : p.getCollection(), 'pos' : o});
		} else if (e.ctrlKey && !e.shiftKey && t.isCopyable) {
			var ni = p.copyChild(NG.drag.curItem.item, o);
			var f = function (req) { t.reload(); }
			t.qajax({'pk' : NG.drag.curItem.item.getCollection(), 'cpk' : p.getCollection(), 'pos' : o, 'response' : f});
		} else if (!e.ctrlKey && !e.shiftKey && t.isMoveable) {
			var rpk = NG.drag.curItem.item.getParent().getCollection();
			var ni = p.moveChild(NG.drag.curItem.item, o);
			t.qajax({'pk' : NG.drag.curItem.item.getCollection(), 'rpk' : rpk, 'apk' : p.getCollection(), 'pos' : o});
		} else {
			return;
		}
		p.getCollection().refresh();
		if (ni) ni.getTree().focusItem(ni);
	} else {
		this.getItem().getCollection().applyHooks('external'+(e.ctrlKey?'copy':'move'), [this.getItem(),NG.drag.curItem]);
	}
}
NG.tree.dropZone.prototype.attach = function (node) {
	var z = this;
	NG.addEventListener(node, 'mouseover', function (e) { if (NG.drag.curZone == z) z.over(e); });
	NG.drag.zone.prototype.attach.apply(this, [node]);
}



/***********************
 * Menu
 */
NG.menu = function () {
	this.children = [];
	this.last = null;
	this.node = null;
	this.focusedItem = null;
	this.expandedItem = null;
	this.layers = [ NG.menu.layers.select, NG.menu.layers.label ];
	this.isRoot = false;
}
NG.menu.prototype.add = function (pk, label, isCurrent) { return this.addItem(new NG.menu.item(pk, label, isCurrent)); }
NG.menu.prototype.attach = function (node) {
	this.isRoot = true;
	node.appendChild(this.getNode());
}

NG.menu.prototype.addItem = function (item) {
	var w = this;
	item.getTree = function () { return w; }
	item.parentOffset = this.children.length;
	this.children.push(item);
	this.last = this.children[this.children.length-1];
	return this.last;
}

NG.menu.prototype.focusItem = function (item) {
	if (this.focusedItem) this.focusedItem.blur();
	item.focus();
	this.focusedItem = item;
}

NG.menu.prototype.collapseItem = function () {
	if (this.expandedItem) this.expandedItem.collapse();
}
NG.menu.prototype.collapseAll = function () {
	for (var i = 0; i < this.children.length; i++) {
		if (this.children[i].isExpanded) this.children[i].collapse();
	}
	if (this.focusedItem) this.focusedItem.blur();
	if (this._ca) {
		NG.removeEventListener(document, 'mouseover', this._ca);
		this._ca = false;
	}
}

NG.menu.prototype.getNode = function () {
	if (!this.node) {
		this.node = document.createElement('div');
		for (var i = 0; i < this.children.length; i++) {
			this.node.appendChild(this.children[i].getNode());
		}
		this.node.onselectstart = function () { return false; }
		NG.addEventListener(this.node, 'mouseover', NG.stopPropagation);
	}
	return this.node;
}

NG.menu.prototype.refresh = function () {
	var n = this.getNode().parentNode;
	n.removeChild(this.node);
	this.node = null;
	this.attach(n);
}

NG.menu.prototype.getProp = function (prop) { return this[prop]; }

NG.menu.layers = {
	'label' : function (node) {
		var w = this;
		NG.addEventListener(node, 'mouseover', function (e) {
			NG.ContextMenu.closeAll();
			if (w.getTree()) w.getTree().focusItem(w);
			w.expand(e);
			NG.stopPropagation(e);
		});
		if (this.label) {
			if (isStr(this.label)) {
				node.appendChild(document.createTextNode(this.label));
			} else {
				node.appendChild(this.label);
			}
		}
	},
	'select' : function (node) {
		var w = this;
		NG.addEventListener(node, 'click', function (e) { w.select(); NG.stopPropagation(e); });
	}
};

NG.menu.item = function (pk, label, isCurrent) {
	this.pk = pk;
	this.label = label;
	this.isCurrent = isCurrent;
	this.children = [];
	this.node = null;
	this.isExpanded = false;
	this.state = 0;
	this.parentOffset = null;
}

NG.menu.isCurrent = function (url) {
	var loc = new NGUrl(window.location);
	var url = new NGUrl(url);
	if (url.path == loc.path) return true;
	else return false;
}

NG.menu.item.prototype.getNode = function () {
	if (!this.node) {
		this.node = document.createElement('div');
		var n,l = this.getProp('layers');
		n = this.node;
		for (var i = 0; i < l.length; i++) {
			n.appendChild(document.createElement('div'));
			n = n.lastChild;
			NG.addClass(n, 'layer'+i);
			if (this.isCurrent == true) {
				NG.addClass(n, 'selected');
			}
			l[i].apply(this,[n]);
		}
		if (this.children.length > 0) {
			if (this.getParent()) {
				this.node.appendChild(ar = document.createElement('div'));
				NG.addClass(ar, 'arrow');
				ar.appendChild(document.createTextNode('►'));
			}
			if (this.isExpanded) {
				this.isExpanded = false;
				this.expand();
			} else {
				NG.addClass(this.node.firstChild, 'collapsed');
			}
		}
		NG.addEventListener(this.node, 'mouseover', NG.stopPropagation);
	}
	return this.node;
}

NG.menu.item.prototype.getParent = function () { return null; }
NG.menu.item.prototype.getTree = function () { return null; }

NG.menu.item.prototype.getNextSibling = function () {
	var p = this.getParent();
	if (!p) p = this.getTree();
	if (p && this.parentOffset < p.children.length-1) return p.children[this.parentOffset+1];
	return null;
}

NG.menu.item.prototype.getPreviousSibling = function () {
	var p = this.getParent();
	if (!p) p = this.getTree();
	if (p && this.parentOffset > 0) return p.children[this.parentOffset-1];
	return null;
}

NG.menu.item.prototype.refresh = function () {
	var n = this.getNode().parentNode;
	var x = this.getNode().nextSibling;
	n.removeChild(this.getNode());
	this.node = null;
	if (x) n.insertBefore(this.getNode(), x);
	else n.appendChild(this.getNode());
}

NG.menu.item.prototype.expand = function (e) {
	var t = this.getTree();
	if (!t._ca) {
		t._ca = function (e) { t.collapseAll(e); }
		NG.addEventListener(document, 'mouseover', t._ca);
	}
	if (e && t.expandedItem && t.expandedItem != this && (!this.getParent() || (t.expandedItem && this.getParent() == t.expandedItem.getParent()))) {
		var p = t.expandedItem.getParent();
		t.collapseItem();
		if (p) t.expandedItem = p;
	}
	if (this.isExpanded || this.children.length == 0) return;
	NG.hideZdisregard();
	var div = document.createElement('div');
	var n = this.getNode();
	NG.addClass(div, 'children');
	n.appendChild(div);
	for (var i = 0; i < this.children.length; i++) {
		n.lastChild.appendChild(this.children[i].getNode());
	}
	NG.replaceClass(n.firstChild, 'collapsed', 'expanded');
	this.isExpanded = true;
	if (t) t.expandedItem = this;
}

NG.menu.item.prototype.collapse = function () {
	if (!this.isExpanded || this.children.length == 0) return;
	for (var i = 0; i < this.children.length; i++) {
		if (this.children[i].isExpanded) this.children[i].collapse();
	}
	var t = this.getTree();
	var n = this.getNode();
	n.removeChild(n.lastChild);
	NG.replaceClass(n.firstChild, 'expanded', 'collapsed');
	this.isExpanded = false;
	if (t) t.expandedItem = null;
	NG.showZdisregard();
}

NG.menu.item.prototype.toggle = function () {
	if (this.isExpanded) this.collapse();
	else this.expand();
}

NG.menu.item.prototype.focus = function (ancestor) {
	NG.addClass(this.getNode().firstChild, (ancestor ? 'focused-ancestor' : 'focused'));
	var p = this.getParent();
	if (p) {
		p.focus(true);
		p.expand();
	}
}

NG.menu.item.prototype.blur = function () {
	NG.delClass(this.getNode().firstChild, /^focused(\-ancestor)?$/);
	if (this.getParent()) this.getParent().blur();
}

NG.menu.item.prototype.select = function () { }

NG.menu.item.prototype.addItem = function (item) {
	var w = this;
	item.getParent = function () { return w; }
	item.getTree = function () { return w.getTree(); }
	item.parentOffset = this.children.length;
	this.children.push(item);
	this.last = this.children[this.children.length-1];
	if (item.isCurrent) this.setCurrent();
	return this.last;
}

NG.menu.item.prototype.setCurrent = function () {
	this.isCurrent = true;
	var p = this.getParent();
	if (p) p.setCurrent();
}

NG.menu.item.prototype.getProp = function (prop) {
	if (isDef(typeof this[prop])) return this[prop];
	if (this.getParent()) return this.getParent().getProp(prop);
	return this.getTree().getProp(prop);
}

NG.menu.item.prototype.add = NG.menu.prototype.add;


/***********************
 * Context menus
 */
NG.ContextMenu = function (spec) {
	this.node = null;
	this.focusedItem = null;
	this.items = [];
	this.targetNode = null;
	var w = this;
	if (spec) this.loadSpec(spec);

	this.attachFunc = function (event) {
		if (!isObj(event) && isObj(window.event)) var event = window.event;
		NG.ContextMenu.closeAll();
		w.show(event.clientX+document.documentElement.scrollLeft, event.clientY+document.documentElement.scrollTop);
		w.targetNode = this;
		NG.stopPropagation(event);
		NG.preventDefault(event);
		return false;
	}
}

NG.ContextMenu.hasRunOnce = false;
NG.ContextMenu.runOnce = function () {
	if (!NG.ContextMenu.hasRunOnce) {
		NG.ContextMenu.hasRunOnce = true;
		NG.addStyleRule('.context-menu', 'position:absolute; background-color:#fefefe; border:1px outset; padding:2px; z-Index:100;');
		NG.addStyleRule('.context-menu-item', 'padding:1px; padding-left:19px; padding-right:19px; height:16px; line-height:16px; overflow:none; -moz-user-select:none; -khtml-user-select:none; cursor:default; background-repeat:no-repeat; position:relative; white-space:nowrap;');
		NG.addStyleRule('.context-menu-item-focused', 'background-color:#cccccc;');
		NG.addStyleRule('.context-menu-item-disabled', 'color:#c0c0c0;');
		NG.addStyleRule('.context-menu-item span', 'right:2px; font-size:7px; top:1px; position:absolute; line-height:16px;');
		NG.addStyleRule('.context-menu-item div', 'top:0px; left:0px; font-size:9px; position:absolute; line-height:16px; width:16px; text-align:center;');
		NG.addStyleRule('.context-menu-item-disabled img', 'display:none;');
		NG.addStyleRule('.context-menu-separator', 'border-top:1px solid #c0c0c0; line-height:0px; font-size:0px; height:0px; margin-top:3px; margin-bottom:3px;');
		NG.addEventListener(document, 'mousedown', NG.ContextMenu.closeAll);
	}
}

NG.ContextMenu.curMenu = null;

NG.ContextMenu.prototype.loadSpec = function (spec) {
	var reshow = false;
	if (this.node && this.node.parentNode) {
		var x = this.node.offsetLeft;
		var y = this.node.offsetTop;
		this.hide();
		reshow = true;
	}
	this.node = null;
	this.items = [];
	var w = this;
	for (var i = 0; i < spec.length; i++) {
		this.items[i] = new NG.ContextMenu.item(spec[i]);
		this.items[i].offset = i;
		this.items[i].getMenu = function () { return w; };
	}
	if (reshow) this.show(x,y);
}

NG.ContextMenu.prototype.getRootMenu = function () {
	return this;
}

NG.ContextMenu.prototype.getParentItem = function () { return null; }

NG.ContextMenu.prototype.getNode = function () {
	if (!this.node) {
		this.node = document.createElement('div');
		if (NG.ua.isEng('MSIE', '6.0')) this.node.style.width = '0%';
		this.node.className = 'context-menu';
		for (var i = 0; i < this.items.length; i++) {
			this.node.appendChild(this.items[i].getNode());
		}
	}
	return this.node;
}

NG.ContextMenu.prototype.focusItem = function (item, event) {
	if (this.focusedItem) this.focusedItem.blur(event);
	if (item.isDisabled) {
		this.focusedItem = null;
	} else {
		this.focusedItem = item;
		this.focusedItem.focus(event);
	}
}

NG.ContextMenu.prototype.focusNextItem = function (event) {
	var s = (this.focusedItem ? this.focusedItem.offset+1 : 0);
	for (var i = s; i < this.items.length; i++) {
		if (!this.items[i].isDisabled && !this.items[i].isSeparator) return this.focusItem(this.items[i], event);
	}
}

NG.ContextMenu.prototype.focusPrevItem = function (event) {
	var s = (this.focusedItem ? this.focusedItem.offset-1 : this.items.length-1);
	for (var i = s; i >= 0; i--) {
		if (!this.items[i].isDisabled && !this.items[i].isSeparator) return this.focusItem(this.items[i], event);
	}
}

NG.ContextMenu.prototype.show = function (x,y,fX,fY) {
	if (arguments.length < 3) fX = x;
	if (arguments.length < 4) fY = y;
	var r = this.getRootMenu();
	if (NG.ContextMenu.curMenu != r) {
		NG.ContextMenu.closeAll();
		NG.ContextMenu.curMenu = r;
	}
	NG.ContextMenu.documentPosition(this.getNode(), x, y, fX, fY);
	if (NG.ua.isEng('MSIE')) {
		this.getNode().style.width = this.getNode().offsetWidth+'px';
		document.body.appendChild(this.getNode());
	}
	NG.hideZdisregard();
}

NG.ContextMenu.documentPosition = function (node, x, y, fX, fY) {
	if (arguments.length < 4) fX = x;
	if (arguments.length < 5) fY = y;
	var s = NG.getComputedStyle(node);
	var p = ['offsetHeight', 'offsetWidth', 'clientHeight', 'clientWidth', 'scrollTop', 'scrollLeft'];
	var d = {};
	for (var i = 0; i < p.length; i++) d[p[i]] = parseInt(document.documentElement[p[i]]);
	node.style.left = x+'px';
	node.style.top = y+'px';
	document.body.appendChild(node);
	if (fX-node.offsetWidth > d.scrollLeft && x+node.offsetWidth > d.clientWidth+d.scrollLeft) node.style.left = (fX-node.offsetWidth)+'px';
	if (fY-node.offsetHeight > d.scrollTop && y+node.offsetHeight > d.clientHeight+d.scrollTop) node.style.top = (fY-node.offsetHeight)+'px';
}
NG.ContextMenu.prototype.hide = function () {
	if (this.focusedItem) {
		this.focusedItem.blur();
		this.focusedItem = null;
	}
	if (NG.ContextMenu.curMenu == this) NG.ContextMenu.curMenu = null;
	if (this.getNode().parentNode) this.getNode().parentNode.removeChild(this.getNode());
	//NG.removeEventListener(document, 'mouseup', function () { w.hide(); });
	NG.showZdisregard();
}

NG.ContextMenu.addContextListener = function (node, func) {
	if (NG.ua.isEng('OPERA')) {
		NG.addEventListener(node, 'click', func);
	} else if (NG.ua.isApp('Konqueror')) {
		NG.addEventListener(node, 'click', func);
/*	} else if (NG.ua.isEng('GECKO') && dom.event.contextmenu.enabled
		NG.addEventListener(node, 'click', f);*/
	} else {
		NG.addEventListener(node, 'contextmenu', func);
	}
}

NG.ContextMenu.removeContextListener = function (node, func) {
	NG.removeEventListener(node, 'click', func);
	NG.removeEventListener(node, 'contextmenu', func);
}

NG.ContextMenu.prototype.attach = function (node) {
	NG.ContextMenu.runOnce();
	NG.ContextMenu.addContextListener(node, this.attachFunc);
}

NG.ContextMenu.prototype.detach = function (node) {
	NG.ContextMenu.removeContextListener(node, this.attachFunc);
}

NG.ContextMenu.attach = function (node, spec) {
	var c = new NG.ContextMenu(spec);
	c.attach(node);
	return c;
}

NG.ContextMenu.item = function (spec) {
	this.node = null;
	this.label = spec.label;
	this.confirm = spec.confirm;
	this.isCancel = (spec.cancel ? true : false);
	this.isDisabled = (spec.disabled ? true : false);
	this.isSeparator = (spec.separator ? true : false);
	this.isFlagged = (spec.flag ? true : false);
	this.func = spec.func;
	if (spec.url) this.url = spec.url;
	this.iconSrc = spec.iconsrc;
	if (spec.children) this.childMenu = new NG.ContextMenu(spec.children);
	else if (spec.childmenu) this.childMenu = spec.childmenu;
	else this.childMenu = null;
	if (this.childMenu) {
		var i = this;
		this.childMenu.getParentItem = function () { return i; };
		this.childMenu.getRootMenu = function () { return i.getMenu().getRootMenu(); };
	}
	this.value = spec.value;
	this.offset = null;
}

NG.ContextMenu.item.prototype.getMenu = function () {
	return null;
}

NG.ContextMenu.item.prototype.getNode = function () {
	if (!this.node) {
		this.node = document.createElement('div');
		if (this.isSeparator) {
			this.node.className = 'context-menu-separator';
		} else {
			this.node.className = 'context-menu-item';
			if (this.isDisabled) this.node.className += ' context-menu-item-disabled';
			if (this.iconSrc) this.node.style.backgroundImage = 'url('+this.iconSrc+')';
			this.node.appendChild(document.createTextNode(this.label));
			if (this.childMenu) {
				this.node.appendChild(document.createElement('span'));
				this.node.lastChild.appendChild(document.createTextNode('►'));
			}
			if (this.isFlagged) { this.isFlagged = false; this.flag(true); }
			var w = this;
			NG.addEventListener(this.node, 'mousedown', function (event) {  NG.stopPropagation(event); });
			NG.addEventListener(this.node, 'mouseup', function (event) { w.select(event); return NG.preventDefault(event); });
			NG.addEventListener(this.node, 'mouseover', function (event) { w.getMenu().focusItem(w, event); });
			NG.ContextMenu.addContextListener(this.node, function (event) { return NG.preventDefault(event); });
		}
	}
	return this.node;
}

NG.ContextMenu.item.prototype.select = function (event) {
	if (this.isDisabled || this.isSeparator || this.childMenu) return;
	if (this.confirm && !confirm(this.confirm)) {
	} else if (this.isCancel) {
	} else if (this.url) {
		window.location = this.url;
	} else if (this.func && isArr(this.func)) {
		this.func[0][this.func[1]]();
	} else if (this.func && isFunc(this.func)) {
		this.func(this, event);
	}
	if (NG.ua.isEng('MSIE')) setTimeout('NG.ContextMenu.closeAll();', 0);
	else NG.ContextMenu.closeAll();
}

NG.ContextMenu.item.prototype.focus = function (event) {
	this.getNode().className = this.getNode().className.replace(/(^| )context-menu-item-focused( |$)/, '')+' context-menu-item-focused';
	if (this.childMenu) {
		var x = this.getNode().offsetLeft+this.getMenu().getNode().offsetLeft;
		var y = this.getNode().offsetTop+this.getMenu().getNode().offsetTop;
		this.childMenu.targetNode = this.getMenu().targetNode;
		this.childMenu.show(x+this.getNode().offsetWidth,y,x,y+this.getNode().offsetHeight);
	}
}

NG.ContextMenu.item.prototype.blur = function (event) {
	this.getNode().className = this.getNode().className.replace(/(^| )context-menu-item-focused( |$)/, '');
	if (this.childMenu) this.childMenu.hide();
}

NG.ContextMenu.item.prototype.flag = function (val) {
	if (arguments.length == 0) val = !this.isFlagged;
	if (val && !this.isFlagged) {
		this.getNode().appendChild(document.createElement('div'));
		this.getNode().lastChild.appendChild(document.createTextNode('✔'));
	} else if (!val && this.isFlagged) {
		this.getNode().removeChild(this.getNode().lastChild);
	}
	this.isFlagged = val;
}

NG.ContextMenu.item.prototype.flagone = function () {
  var m = this.getMenu();
  for (var i = 0; i < m.items.length; i++) { m.items[i].flag(m.items[i] == this); }
}

NG.ContextMenu.closeAll = function (event) {
	if (NG.ContextMenu.curMenu) NG.ContextMenu.curMenu.hide();
	//NG.removeEventListener(document, 'mousedown', NG.ContextMenu.closeAll);
}


/***********************
 * DOM Shuffle
 */

NG.DOMShuffle = function (node) {
	for (var i = 0; i < node.rows[0].cells.length; i++) {
		var c = node.rows[0].cells[i].firstChild;
		while (c) { if (c.nodeType == 1) NG.DOMShuffle.install(c); c = c.nextSibling; }
	}
}

NG.DOMShuffle.curNode = null;
NG.DOMShuffle.markerNode = null;
NG.DOMShuffle.width = NG.DOMShuffle.height = NG.DOMShuffle.x = NG.DOMShuffle.y = null;

NG.DOMShuffle.install = function (node) {
	var n = node;
	NG.addEventListener(n, 'mousedown', function (e) { NG.DOMShuffle.begin(n, e); });
}

NG.DOMShuffle.begin = function (node, e) {
	if (NG.DOMShuffle.curNode) NG.DOMShuffle.end(e);
	NG.DOMShuffle.curNode = node;
	NG.addEventListener(document, 'mousemove', NG.DOMShuffle.move);
	NG.addEventListener(document, 'mouseup', NG.DOMShuffle.end);
	if (document.selection) document.selection.empty();
	NG.preventDefault(e);
}

NG.DOMShuffle.move = function (e) {
	if (!NG.DOMShuffle.curNode) return NG.DOMShuffle.end(e);
	if (!e) e = window.event;
	if (!NG.DOMShuffle.markerNode) {
		var p = NG.getPagePos(NG.DOMShuffle.curNode);
		NG.DOMShuffle.x = p.x-e.clientX;
		NG.DOMShuffle.y = p.y-e.clientY;

		if (NG.DOMShuffle.curNode.nodeName.toLowerCase() == 'tr') {
			NG.DOMShuffle.markerNode = document.createElement('tr');
			for (var i = 0; i < NG.DOMShuffle.curNode.cells.length; i++) {
				NG.DOMShuffle.markerNode.appendChild(document.createElement('td'));
				NG.DOMShuffle.markerNode.lastChild.style.height = (NG.DOMShuffle.curNode.cells[i].offsetHeight-2)+'px';
			}
			NG.DOMShuffle.curNode.parentNode.insertBefore(NG.DOMShuffle.markerNode, NG.DOMShuffle.curNode);
			var t = document.createElement('table');
			t.style.width = NG.DOMShuffle.curNode.offsetWidth+'px';
			t.style.position = 'absolute';
			t.appendChild(document.createElement('tbody'));
			t.lastChild.appendChild(NG.DOMShuffle.curNode);
			NG.DOMShuffle.curNode = t;
			document.body.appendChild(t);
		} else {
			NG.DOMShuffle.markerNode = document.createElement('div');
			NG.DOMShuffle.markerNode.style.height = (NG.DOMShuffle.curNode.offsetHeight-2)+'px';
			NG.DOMShuffle.markerNode.style.width = (NG.DOMShuffle.curNode.offsetWidth-2)+'px';
			var cs = NG.getComputedStyle(NG.DOMShuffle.curNode);
			NG.DOMShuffle.curNode.style.width = (NG.DOMShuffle.curNode.offsetWidth-parseInt(cs.paddingLeft)-parseInt(cs.paddingRight)-parseInt(cs.borderLeftWidth)-parseInt(cs.borderRightWidth))+'px';
			NG.DOMShuffle.curNode.parentNode.insertBefore(NG.DOMShuffle.markerNode, NG.DOMShuffle.curNode);
			NG.DOMShuffle.curNode.style.position = 'absolute';
			document.body.appendChild(NG.DOMShuffle.curNode);
		}
		NG.DOMShuffle.markerNode.className = 'shuffle-marker';
		NG.DOMShuffle.curNode.className += ' shuffle-drag';
	}
	NG.DOMShuffle.curNode.style.left = (e.clientX+NG.DOMShuffle.x)+'px';
	NG.DOMShuffle.curNode.style.top = (e.clientY+NG.DOMShuffle.y)+'px';
	var m = NG.getPagePos(NG.DOMShuffle.markerNode);
	var hw = NG.DOMShuffle.markerNode.offsetWidth/2;
	while (NG.DOMShuffle.curNode.offsetLeft+hw < m.x && NG.DOMShuffle.markerNode.parentNode.previousSibling) {
		NG.DOMShuffle.markerNode.parentNode.previousSibling.appendChild(NG.DOMShuffle.markerNode);
		m = NG.getPagePos(NG.DOMShuffle.markerNode);
	}
	while (NG.DOMShuffle.curNode.offsetLeft+hw > m.x+NG.DOMShuffle.markerNode.offsetWidth && NG.DOMShuffle.markerNode.parentNode.nextSibling) {
		NG.DOMShuffle.markerNode.parentNode.nextSibling.appendChild(NG.DOMShuffle.markerNode);
		m = NG.getPagePos(NG.DOMShuffle.markerNode);
	}
	var hh = NG.DOMShuffle.markerNode.offsetHeight/2;
	while (NG.DOMShuffle.curNode.offsetTop+hh < m.y && NG.DOMShuffle.markerNode.previousSibling) {
		NG.DOMShuffle.markerNode.parentNode.insertBefore(NG.DOMShuffle.markerNode, NG.DOMShuffle.markerNode.previousSibling);
		m = NG.getPagePos(NG.DOMShuffle.markerNode);
	}
	while (NG.DOMShuffle.curNode.offsetTop+hh > m.y+NG.DOMShuffle.markerNode.offsetHeight && NG.DOMShuffle.markerNode.nextSibling) {
		if (NG.DOMShuffle.markerNode.nextSibling.nextSibling) {
			NG.DOMShuffle.markerNode.parentNode.insertBefore(NG.DOMShuffle.markerNode, NG.DOMShuffle.markerNode.nextSibling.nextSibling);
		} else {
			NG.DOMShuffle.markerNode.parentNode.appendChild(NG.DOMShuffle.markerNode);
		}
		m = NG.getPagePos(NG.DOMShuffle.markerNode);
	}
}

NG.DOMShuffle.end = function (e) {
	NG.removeEventListener(document, 'mousemove', NG.DOMShuffle.move);
	NG.removeEventListener(document, 'mouseup', NG.DOMShuffle.end);
	if (NG.DOMShuffle.curNode && NG.DOMShuffle.markerNode) {
		if (
			(NG.DOMShuffle.markerNode.parentNode.nodeName.toLowerCase() == 'tbody'
			 || NG.DOMShuffle.markerNode.parentNode.nodeName.toLowerCase() == 'table')
			&& (NG.DOMShuffle.curNode.nodeName.toLowerCase() == 'tbody' || NG.DOMShuffle.curNode.nodeName.toLowerCase() == 'table')
		) {
			NG.DOMShuffle.curNode.parentNode.removeChild(NG.DOMShuffle.curNode);
			NG.DOMShuffle.markerNode.parentNode.insertBefore(NG.DOMShuffle.curNode.getElementsByTagName('tr')[0], NG.DOMShuffle.markerNode);
		} else {
			NG.DOMShuffle.curNode.style.left = NG.DOMShuffle.curNode.style.top = NG.DOMShuffle.curNode.style.width = 'auto';
			NG.DOMShuffle.markerNode.parentNode.insertBefore(NG.DOMShuffle.curNode, NG.DOMShuffle.markerNode);
			NG.DOMShuffle.curNode.style.position = 'relative';
		}
		NG.DOMShuffle.markerNode.parentNode.removeChild(NG.DOMShuffle.markerNode);
		NG.DOMShuffle.curNode.className = NG.DOMShuffle.curNode.className.replace(/(^| )shuffle-drag( |$)/, '');
	}
	NG.DOMShuffle.markerNode = NG.DOMShuffle.curNode = null;
}


/***********************
 * Textbox lookup
 */

NG.TextLookup = function (node, url, queryArg) {
	this.init(node, url, queryArg);
}

NG.TextLookup.prototype.init = function (node, url, queryArg) {
	this.uSecDelay = 1000;
	this.minLength = 3;
	this.minWidth = 100;
	this.displayHeight = 100;

	NG.TextLookup.runOnce();
	this.url = new NGUrl(url);
	this.queryArg = (queryArg ? queryArg : 'q');
	this.textNode = node;
	this.eH = this.listNode = this.searchAJAX = this.lastLookup = this.focusedItem = null;
	this.ignoreNextBlur = this.inProgress = this.isValid = false;
	this.originalLabel = this.textNode.value;
	this.originalId = this.textNode.getAttribute('ngid');
	var w = this;
	this.textNode._ngTextLookup = function () { return w; };
	NG.addClass(this.textNode, 'ng-textlookup');
	if (this.originalLabel.length > 0 && isId(this.originalId)) {
		NG.addClass(this.textNode, 'ng-textlookup-valid');
		this.isValid = true;
	}
	NG.addEventListener(this.textNode, 'focus', function (e) { if (w.lastLookup == w.textNode.value) w.show(); });
	NG.addEventListener(this.textNode, 'blur', function (e) { w.blur(e); });
	NG.addEventListener(this.textNode, 'keyup', function (e) { w.keydown(e); });
	NG.addEventListener(this.textNode, 'paste', function () { w.startSearch(); });
	NG.addEventListener(document, 'mousedown', function (e) {
		if (e.target == w.textNode || e.srcElement == w.textNode) return;
		if (w.listNode && (e.target == w.listNode || e.srcElement == w.listNode)) {
			w.ignoreNextBlur = true;
			return;
		}
		w.hide();
	});
}
NG.TextLookup.hasRunOnce = false;
NG.TextLookup.runOnce = function () {
	if (!NG.TextLookup.hasRunOnce) {
		//   .ng-textlookup-process { background-image:url(processing.gif); }
		//   .ng-textlookup-valid { background-image:url(tree-folder-yellow.png); }
		//   .ng-textlookup { background-image:url(expanded.png); }
		NG.addStyleRule('.ng-textlookup-list', 'position:absolute; height:100px; border:1px solid #c0c0c0; background-color:white; overflow-y:scroll;');
		NG.addStyleRule('.ng-textlookup-list div', 'width:100%; overflow:hidden;');
		NG.addStyleRule('.ng-textlookup-list div div', 'padding:3px; padding-left:5px; line-height:110%; white-space:nowrap; cursor:pointer;');
		NG.addStyleRule('.ng-textlookup-valid', 'color:black;');
		NG.addStyleRule('.ng-textlookup', 'background-repeat:no-repeat; background-position:right center; color:red;');
		NG.addStyleRule('.ng-textlookup-empty', 'font-style:italic; color:#c0c0c0; text-align:center; height:200%;');
		NG.addStyleRule('.ng-textlookup-focus', 'background-color:#45a6e3; color:white;');
		NG.TextLookup.hasRunOnce = true;
	}
}

NG.TextLookup.install = function (node, url, queryArg) {
	if (node._ngTextLookup) return node._ngTextLookup();
	return new NG.TextLookup(node, url, queryArg);
}

NG.TextLookup.prototype.keydown = function (e) {
	switch (e.keyCode) {
		case 40: // Down arrow key
			if (this.focusedItem && this.focusedItem.nextSibling) {
				this.focusItem(this.focusedItem.nextSibling);          // Focus next item
				this.scrollTofocused();
			} else if (!this.focusedItem && this.listNode && this.listNode.firstChild
						&& this.listNode.firstChild.firstChild && this.listNode.firstChild.firstChild.nodeType == 1) {
				this.focusItem(this.listNode.firstChild.firstChild);   // Focus first item
				this.scrollTofocused();
			}
			break;
		case 38: // Up arrow key
			if (this.focusedItem && this.focusedItem.previousSibling) {
				this.focusItem(this.focusedItem.previousSibling);      // Focus previous item
				this.scrollTofocused();
			} else if (!this.focusedItem && this.listNode && this.listNode.firstChild
						&& this.listNode.firstChild.lastChild && this.listNode.firstChild.lastChild.nodeType == 1) {
				this.focusItem(this.listNode.firstChild.lastChild);   // Focus first item
				this.scrollTofocused();
			}
			break;
		case 13: // Enter key
			if (this.focusedItem) this.selectItem(this.focusedItem);   // Select currently focused item
			else if (this.isValid) this.hide();
			this.onfinish();
			break;
		case 27: // Escape key
			this.cancel();
			this.onfinish();
			break;
		default:
			// TODO: Don't start seaching immediately. Instead set a timeout of 300-400ms to call startSeach. 
			// If the user types within the timeout period, reset the timer. startSearch needs to clear timer as well
			this.startSearch();
	}
}

NG.TextLookup.prototype.blur = function (e) {
	if (this.ignoreNextBlur) {
		this.ignoreNextBlur = false;
		this.textNode.focus();
	} else {
		this.hide();
		this.cancel();
		this.onfinish();
	}
}

NG.TextLookup.prototype.startSearch = function () {
	var f = false;
	if (this.textNode.value == this.originalLabel) {
		this.select(this.originalId, this.originalLabel);
		this.hide();
		return;
	} else if (this.listNode && this.listNode.firstChild && this.listNode.firstChild.nodeType == 1) {
		var n = this.listNode.firstChild;
		for (var i = 0; !f && i < n.childNodes.length; i++) {
			if (n.childNodes[i].nodeType == 1 && n.childNodes[i].getAttribute('nglabel') == this.textNode.value) {
				this.selectItem(n.childNodes[i]);
				f = true;
			}
		}
	}
	if (!f) this.unselectItem();
	if (this.inProgress || this.eH) return; // Don't search if one already in progress or scheduled
	if (this.textNode.value.length < this.minLength) {
		this.hide();     // If not enough text, don't search and make sure list is hidden
	} else if (this.searchAJAX || this.textNode.value.length >= this.minLength) {
		this.search();   // Search not first search or there is enough text if it is the first search
	}
}

NG.TextLookup.prototype.search = function () {
	if (this.lastLookup == this.textNode.value) {
		if (this.eH) clearTimeout(this.eH);
		this.eH = null;
		this.show();
		return;
	}
	var w = this;
	if (!this.searchAJAX) {
		this.searchAJAX = new NG.AJAX();
		this.searchAJAX.setResponseHandler(function (req) { w.results(req); }, 200);
		this.searchAJAX.setResponseHandler(function (req) { w.logout(req); }, 401);
		this.searchAJAX.setResponseHandler(function (req) { w.cancel(); }, '');
	} else {
		this.searchAJAX.cancel();
	}
	NG.addClass(this.textNode, 'ng-textlookup-process');
	this.lastLookup = this.textNode.value;
	this.inProgress = true;
	this.url.addArgument(this.queryArg, this.lastLookup, true);
	this.searchAJAX.url = this.url.toString();
	this.searchAJAX.send();
}

NG.TextLookup.prototype.scrollTofocused = function () {
	var w = this;
	if (!w.focusedItem || !w.listNode) return;
	if (w.focusedItem.offsetTop+w.focusedItem.offsetHeight > w.listNode.scrollTop+w.listNode.offsetHeight
			|| w.focusedItem.offsetTop < w.listNode.scrollTop) {
		w.focusedItem.scrollIntoView();
	}
}

NG.TextLookup.prototype.focusItem = function (node) {
	this.blurItem();
	this.focusedItem = node;
	NG.addClass(this.focusedItem, 'ng-textlookup-focus');
}

NG.TextLookup.prototype.blurItem = function () {
	if (this.focusedItem) NG.delClass(this.focusedItem, 'ng-textlookup-focus');
	this.focusedItem = null;
}

NG.TextLookup.prototype.selectItem = function (node) {
	this.select(node.getAttribute('ngid'), node.firstChild.nodeValue, node.getAttribute('nglabel'));
}

NG.TextLookup.prototype.select = function (id, label, nglabel) {
	this.textNode.value = label;
	NG.addClass(this.textNode, 'ng-textlookup-valid');
	this.textNode.setAttribute('ngid', id);
	this.textNode.setAttribute('nglabel', nglabel);
	this.isValid = true;
	this.cancel();
}

NG.TextLookup.prototype.unselectItem = function () {
	this.isValid = false;
	NG.delClass(this.textNode, 'ng-textlookup-valid');
	this.textNode.removeAttribute('ngid');
	this.textNode.removeAttribute('nglabel');
}

NG.TextLookup.prototype.logout = function (req) { }

NG.TextLookup.prototype.cancel = function (req) {
	if (this.searchAJAX) this.searchAJAX.cancel();
	if (this.eH) clearTimeout(this.eH);
	this.eH = null;
	this.inProgress = false;
	NG.delClass(this.textNode, 'ng-textlookup-process');
	this.hide();
}

NG.TextLookup.prototype.results = function (req) {
	this.inProgress = false;
	NG.delClass(this.textNode, 'ng-textlookup-process');
	var w = this;
	if (req && req.responseXML) {
		var c = document.createElement('div');
		var t = req.responseXML.getElementsByTagName('item');
		this.blurItem();
		var f = null;
		for (var i = 0; i < t.length; i++) {
			c.appendChild(document.createElement('div'));
			c.lastChild.appendChild(document.createTextNode(t[i].getAttribute('label')));
			c.lastChild.setAttribute('title', t[i].getAttribute('label'));
			c.lastChild.setAttribute('alt', t[i].getAttribute('label'));
			c.lastChild.setAttribute('ngid', t[i].getAttribute('id'));
			c.lastChild.setAttribute('nglabel', ifnull(t[i].getAttribute('nglabel'), t[i].getAttribute('label')));
			NG.addEventListener(c.lastChild, 'mouseover', function () { w.focusItem(this); });
			NG.addEventListener(c.lastChild, 'mousedown', function () { w.selectItem(this); w.onfinish(); });
			if (t[i].getAttribute('label') == this.textNode.value) {
				this.selectItem(c.lastChild);
			}
		}
		if (t.length == 0) {
			c.className = 'ng-textlookup-empty';
			c.appendChild(document.createTextNode('Empty'));
		} else if (t.length == 1) {
			this.focusItem(c.lastChild);
		}
		this.show(c);
	}
	this.eH = setTimeout(function () { w.search(); }, this.uSecDelay); // Schedule another search
}

NG.TextLookup.prototype.show = function (contentsNode) {
	if (!this.textNode.parentNode) return this.hide();
	var tCS = NG.getComputedStyle(this.textNode);
	if (!this.listNode) {
		this.listNode = document.createElement('div');
		this.listNode.className = 'ng-textlookup-list';
		this.listNode.style.height = this.displayHeight + 'px';
		this.listNode.style.fontFamily = tCS.fontFamily;
		this.listNode.style.fontSize = tCS.fontSize;
	}
	if (contentsNode) {
		while (this.listNode.firstChild) this.listNode.removeChild(this.listNode.firstChild);
		this.listNode.appendChild(contentsNode);
	}
	var p = NG.getPagePos(this.textNode);
	this.listNode.style.width = Math.max(this.minWidth, this.textNode.offsetWidth-NG.getComputedStylePx(this.listNode, 'borderLeftWidth')-NG.getComputedStylePx(this.listNode, 'borderRightWidth'))+'px';
	
	p.y += window.document.documentElement.scrollTop;
	p.x += window.document.documentElement.scrollLeft;

	NG.ContextMenu.documentPosition(this.listNode, p.x, p.y+this.textNode.offsetHeight, p.x, p.y);
}

NG.TextLookup.prototype.hide = function () {
	if (this.listNode && this.listNode.parentNode) this.listNode.parentNode.removeChild(this.listNode);
}

NG.TextLookup.prototype.onfinish = function () {}

