/*
	DynaTable - A DHTML-based Table Data API
	Version 0.9a (14-Mar-04)
	Copyright (C) 2004 Daniel Nolan (dynatable@danere.com)
	
	For more information, visit the DynaTable Website:
	http://dynatable.sourceforge.net

	This program is free software; you can redistribute it and/or modify it 
	under the terms of the GNU General Public License as published by the 
	Free Software Foundation; either version 2 of the License, or (at your 
	option) any later version.
	
	This program is distributed in the hope that it will be useful, but 
	WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY 
	or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 
	for more details.
	
	You should have received a copy of the GNU General Public License along with 
	this program; if not, write to the Free Software Foundation, Inc., 59 Temple 
	Place, Suite 330, Boston, MA 02111- 1307 USA
*/

var dynatable_object_holder = null;
var dynatable_object_holders = new Array();

// START - DTBL CONSTANTS
var DTBL_ROWADD_AUTO = 0;
var DTBL_ROWADD_MANUAL = 1;
var DTBL_PREFIX = "dtbl_";
var DTBL_PREFIX_FIELD = "field";
var DTBL_PREFIX_BUTTON = "button";
var DTBL_PREFIX_SELECTOR = "selector";
var DTBL_PREFIX_CUSTOM = "custom";
var DTBL_PREFIX_MULTIROWBUTTON = "multiRowButton";
var DTBL_TABLESECTID_ITEMS = DTBL_PREFIX+"items";
var DTBL_TABLESECTID_TEMPLATE = DTBL_PREFIX+"template";
var DTBL_TABLESECTID_AUTOADD = DTBL_PREFIX+"autoAdd";
var DTBL_ITEMTYPES = new Array(DTBL_PREFIX_FIELD, DTBL_PREFIX_BUTTON, DTBL_PREFIX_SELECTOR, DTBL_PREFIX_CUSTOM);
var DTBL_ITEMTYPES_DESC = new Array("fields", "buttons", "selectors", "custom");
var DTBL_EDITABLEFORMINPUTS = new Array('INPUT','SELECT','TEXTAREA');
var DTBL_EVENT_CHANGE = "change";
var DTBL_EVENT_FOCUS = "focus";
var DTBL_EVENT_BLUR = "blur";
var DTBL_EVENT_SELECT = "select";
var DTBL_EVENTSTOBIND_FIELD = new Array(DTBL_EVENT_CHANGE, DTBL_EVENT_BLUR, DTBL_EVENT_FOCUS, DTBL_EVENT_SELECT);
var DTBL_EVENTSTOBIND_ROW = new Array('dblclick','click','keypress','keydown','keyup','mousedown','mouseup'); //,'mouseover','mouseout');
var DTBL_EVENTSTOBIND_ALL = DTBL_EVENTSTOBIND_FIELD.concat(DTBL_EVENTSTOBIND_ROW);
var DTBL_UNKNOWN = "unknown";
var DTBL_PARSETYPE_NONE = 0;
var DTBL_PARSETYPE_STRING = 1;
var DTBL_PARSETYPE_NUMBER = 2;
var DTBL_PARSETYPE_DATETIME = 3;
var DTBL_PARSETYPE_BOOLEAN = 4;
var DTBL_VALUEDELIM = ",";
var DTBL_WDDXRECORDSET = "wddxRecordsetInterface";
var DTBL_FIELDINFO_MODIFIED = DTBL_PREFIX + "date_modified";
var DTBL_FIELDINFO_CREATED = DTBL_PREFIX + "date_created";
var DTBL_FIELDINFO_ALL = new Array(DTBL_FIELDINFO_MODIFIED, DTBL_FIELDINFO_CREATED);
// END - DTBL CONSTANTS

var dtbl_isIE = (dtbl_msieversion() >= 4 && window.navigator.platform == 'Win32');
var dtbl_silentError = false;
var dtbl_throwException = true;
var dtbl_showStatus = false;

function dtbl_msieversion()
{
	var ua = window.navigator.userAgent;
	var msie = ua.indexOf ( "MSIE " );
	if ( msie > 0 ) return parseInt ( ua.substring ( msie+5, ua.indexOf ( ".", msie ) ) );
		else return 0;
}

// START - COMMON FUNCTIONS

var dtbl_getElemArrDeep = (dtbl_isIE) ? dtbl_p_getElemArrDeepIE : dtbl_getElemArrDeepPrefix;

function dtbl_handleErr(message, type, returnValue)
{
	if (type == null) type = "General";
	if (!dtbl_silentError) top.window.status = "DynaTable ERROR (" + type + "): " + message;
	if (dtbl_throwException) throw new class_dynatable_exception(type, message);
		else return returnValue;
}
function dtbl_setInnerText(oNode, sNewValue)
{
	var oTextNode = dtbl_findTextNode(oNode);
	if (oTextNode)	oTextNode.nodeValue = sNewValue;
		else oNode.appendChild(document.createTextNode(sNewValue));

}
function dtbl_getInnerText(oNode)
{
	var oTextNode = dtbl_findTextNode(oNode);
	return (oTextNode) ? oTextNode.nodeValue : "";
}	
function dtbl_getAttrStr(oNode, attrName)
{
	if (!attrName) attrName = "value";
	var vVal = oNode.getAttribute(attrName);
	return (vVal) ? vVal : "";
}
function dtbl_focusObj(oObj, bScrollIntoView)
{
	if (oObj.disabled || oObj.type == "hidden") return false;
	dynatable_object_holder = oObj;
	if (bScrollIntoView) oObj.scrollIntoView();
	if (oObj.focus) setTimeout("dynatable_object_holder.focus()", 0);
}
function dtbl_removeChildren(oObj)
{
	while (oObj.childNodes.length)
		oObj.removeChild(oObj.childNodes.item(0));
}
function dtbl_validObj(oObj)
{
	return (oObj != null) ? oObj : new Object();
}
function dtbl_loadIncludes(aIncl)
{
	for (var i=0; i<aIncl.length; i++)
		document.write('<script language="JavaScript" type="text/javascript" src="'+aIncl[i]+'"></script>');
}		
function dtbl_formatDateForDisplay(dateVal)
{
	if (dateVal == null || dateVal.length == 0) return "";
	if (dateVal.getDate == null) dateVal = new Date(dateVal);
	
	var sMins = "00" + dateVal.getMinutes().toString();
	var sTime = dateVal.getHours() + ":" + sMins.slice(sMins.length-2);
	
	return ((dateVal.getMonth()+1) + "/" + dateVal.getDate() + "/" + dateVal.getYear() + " " + sTime);
}
function dtbl_compareArrays(aMain, aComp)
{
	if (aComp.length == 0) return aMain;
	var aNotIn = new Array();
	
	for (var i=0; i<aMain.length; i++)
		if (!dtbl_isStrInArray(aMain[i], aComp)) aNotIn[aNotIn.length] = aMain[i];

	return aNotIn;
}
function dtbl_findTextNode(oNode)
{
	if (oNode.normalize) oNode.normalize();
	var oCh = oNode.childNodes;
	for (var i=0; i<oCh.length; i++)
	{
		if (oCh.item(i).nodeType == 7 || oCh.item(i).nodeType == 3) return oCh.item(i);
	}
}
function dtbl_selectThisValue(vVal, oSelect)
{
	var oOpt;
	if (oSelect.namedItem && (oOpt = oSelect.namedItem(oSelect.id+"_"+vVal))) 
	{
		oOpt.selected = true;
		return true;
	}
	else
	{
		for (var i=0; i<oSelect.options.length; i++)
		{
			if (oSelect.options[i].value == vVal)
			{
				oSelect.options[i].selected = true;
				return true;
			}
		}
	}
	return false;
}
function dtbl_unselectAll(oSelect)
{
	for (var i=0; i<oSelect.options.length; i++)
		oSelect.options[i].selected = false;
}
function dtbl_convListToArr(sList, sDelim)
{
	if (!sDelim) sDelim = ",";
	var arRet = new Array();
	
	if (sList.length == 0) return arRet;
	
	var iPosL = -1;
	var iPos = sList.indexOf(sDelim);
	if (iPos == -1) iPos = sList.length;
	while (true)
	{
		arRet[arRet.length] = sList.slice(iPosL+1, iPos);
		iPosL = iPos;
		if (iPos == sList.length) break;
		iPos = sList.indexOf(sDelim, iPos+1);
		if (iPos == -1) iPos = sList.length;
	}
	return arRet;
}
function dtbl_isStrInArray(sVal, arList)
{
	for (var i=0; i<arList.length; i++) 
		if (sVal == arList[i]) return true;
	return false;
};
function dtbl_getElemShallow(oNode, sID)
{
	var oChi;
	
	if (oNode == null) oNode = document;
	if (oNode.hasChildNodes)
	{
		for (var i=0; i<oNode.childNodes.length; i++)
		{
			oChi = oNode.childNodes[i];
			if (oChi.id == sID) return oChi;
		}
	}
	return null;
}
function dtbl_getElemArrDeepPrefix(oNode, sID, bByPrefix, attrName)
{
	if (bByPrefix == null) bByPrefix = false;
	if (attrName == null) attrName = "id";
	
	var oInp;
	var aItems = new Array();
	var aChildItems;
	var attrVal;
	if (oNode.hasChildNodes)
	{
		for (var i=0; i<oNode.childNodes.length; i++)
		{
			if ((oInp = oNode.childNodes[i]) != null && oInp.nodeType == 1)
			{
				attrVal = oInp.getAttribute(attrName);
				if (oInp.id.length && attrVal && (sID == null || ((!bByPrefix && attrVal == sID) || (bByPrefix && attrVal.slice(0, sID.length)==sID))))
				{
					aItems[aItems.length] = oInp;
				}
				else
				{
					aChildItems = dtbl_getElemArrDeepPrefix(oInp, sID, bByPrefix, attrName);
					if (aChildItems.length) aItems = aItems.concat(aChildItems);
				};
			}
		}
	}
	return aItems;
}
function dtbl_p_getElemArrDeepIE(oNode, sID)
{
	var oObj = oNode.all.item(sID);
	var aObj = new Array();
	if (oObj.parentNode) aObj[0] = oObj;
		else for (var i=0; i<oObj.length; i++)	aObj[i] = oObj[i];

	return aObj;
}
function dtbl_toggleElemDisplay(oObj, bState)
{
	var sState = (bState) ? "" : "none";
	if (sState != oObj.style.display) {
		oObj.style.display = sState;
		return true;
	} else {
		return false;
	};
}
function dtbl_getElementsByTagNames(oNode, arTags)
{
	var oColl = new class_dynatable_collection;
	for (var i=0; i<arTags.length; i++)
		oColl.addCollection(oNode.getElementsByTagName(arTags[i]), "id");

	return oColl;
}
function dtbl_bindFunctToEvent(oNode, eventType, funct)
{
	if (dtbl_isIE)	oNode.attachEvent("on"+eventType, funct);
		else oNode.addEventListener(eventType, funct, true);		
}
function dtbl_parentDynaTable(oNode)
{
	var oPNode = oNode.parentNode;
	if (!oPNode || !oPNode.parentNode) return null;
	if (oPNode.dynatable) return oPNode.dynatable;
		else return dtbl_parentDynaTable(oPNode);
}
function dtbl_genTag(tagName, tagID)
{
	var oTag = document.createElement(tagName);
	if (tagID) oTag.id = tagID;
	return oTag;
}
function dtbl_insertNodeAfter(oNewNode, oNodeRef)
{
	if (oNodeRef.nextSibling) return oNodeRef.parentNode.insertBefore(oNewNode, oNodeRef.nextSibling);
		else return oNodeRef.parentNode.appendChild(oNewNode);
}
function dtbl_isFunct(oFunct)
{
	return (typeof(oFunct) == 'function');
}
function dtbl_insertIntoArray(oItem, aArr, iPos)
{
	if (aArr == null) aArr = new Array();
	if (iPos == null) iPos = aArr.length;
	
	if (iPos > aArr.length) return dtbl_handleErr("New item position exceeds array length."); // THROWEXCEPTION
	
	if (dtbl_isFunct(aArr.splice))
	{
		aArr.splice(iPos, 0, oItem);
	}
	else
	{
		var iNewLen = aArr.length;
		for (var i=iNewLen; i > iPos; i--)
			aArr[i] = aArr[i-1];

		aArr[iPos] = oItem;
	}
	return aArr;
}
function dtbl_removeFromArray(aArr, iPos)
{
	if (aArr == null) return dtbl_handleErr("No array specified to remove item from."); // THROWEXCEPTION
	if (iPos == null) iPos = aArr.length;
	
	var retVal = aArr[iPos];
	
	if (dtbl_isFunct(aArr.splice))
	{
		aArr.splice(iPos, 1);
	}
	else
	{
		for (var i=iPos; i<aArr.length-1; i++)
			aArr[i] = aArr[i+1];

		aArr.pop();
	}
	
	return retVal;
}
function dtbl_getArrayIndexByValue(aArr, oItem)
{
	for (var i=0; i<aArr.length; i++)
		if (aArr[i]==oItem)
			return i;
}
function dtbl_getCollIndexByValue(clCollection, oItem)
{
	for (var i=0; i<clCollection.length; i++)
		if (clCollection.item(i)==oItem)
			return i;
}
function dtbl_choose()
{
	for (var i=0; i<arguments.length; i++)
		if (arguments[i] != null) return arguments[i];
}
function dtbl_parseXMLDocument(s_xml)
{
	if (window.DOMParser) return new DOMParser().parseFromString(s_xml, 'text/xml'); // Mozilla
	try // Internet Exploror
	{
		var xmlD = new ActiveXObject('Microsoft.XMLDOM');
		xmlD.loadXML(s_xml);
		return xmlD;
	}
	catch (e) {	alert("Could not create XMLDocument:" & e) }
}
function dtbl_getXML(xmlDocument)
{
	if (window.XMLSerializer) return (new XMLSerializer()).serializeToString(xmlDocument);
		else return xmlDocument.xml;
}

function inheritFrom(oSuperClass)
{
	var oSubClass = function(inh) { if (inh!=inheritFrom) initClass(this, oSubClass, arguments) };
	if (!oSuperClass) oSubClass.prototype.callOn = f_callOn;
	else
	{
		oSubClass.prototype = new oSuperClass(inheritFrom);
		oSubClass.superclass = oSuperClass.prototype;
	}
	oSubClass.prototype.constructor = oSubClass;
	return oSubClass;

	/* SUBCLASS PROTOTYPE FUNCTIONS */
	function f_callOn(oFunct) { execOn(this, oFunct, arguments, 1) };
	function execOn(oObj, oFunct, args, argSt)
	{
		if (!oFunct) return;
		oObj.tempFunct = oFunct;
		if (args.length == 0) return oObj.tempFunct();
			else if (args.length==1 && argSt==0) return oObj.tempFunct(args[0]);

		var prm = "";
		for (var i=argSt; i<args.length; i++)
			prm += ((prm.length) ? "," : "") + "args["+i+"]";
		
		return eval("oObj.tempFunct("+prm+")");
	}
	function initClass(oObj, oClass, args)
	{
		if (oObj.autoInherit && oClass.superclass)
			initClass(oObj, oClass.superclass.constructor, args);

		execOn(oObj, oClass.prototype.init, args, 0) 
	}
}

function dynatable_eventRedirector(evt)
{
	if (dtbl_isIE && !evt.target) evt.target = evt.srcElement;
	if (!evt.target || window.dynatableEventsOff) return;
	var oDG = evt.target.dynatable;
	if (dtbl_showStatus) top.window.status = "("+evt.type+") ";
	if (!oDG) oDG = dtbl_parentDynaTable(evt.target);
	if (oDG) return oDG.handleEvent(evt);
}
// END - COMMON FUNCTIONS

// START - DTBL BASE CLASS
with (class_dynatable_base = inheritFrom(null))
{
	prototype.type = "dynatableBaseObj";
	prototype.handleEvent = function(evt)
	{
		if (this.fireEvent) return this.fireEvent(evt.type, evt);
	}
	prototype.handle = function(eventName, funct, label)
	{
		return this.events.addHandler(eventName, funct, label);
	}
	prototype.handles = function(eventNames, funct, label)
	{
		var arEv = new Array();
		var arEvNm = dtbl_convListToArr(eventNames);
		for (var i=0; i<arEvNm.length; i++)
			arEv[arEv.length] = this.events.addHandler(arEvNm[i], funct, label);
		
		return arEv;
	}
	prototype.behave = function(oItemDef)
	{
		return this.behaviors.addBehavior(oItemDef);
	}
	prototype.init = function()
	{
		if (!this.events) this.events = new class_dynatable_event_collection;
		if (!this.behaviors) this.behaviors = new class_dynatable_behavior_collection(this);
	}
}
// END - DTBL BASE CLASS


// START - MAIN DTBL CLASS
with (class_dynatable = inheritFrom(class_dynatable_base))
{
	prototype.type = "dynatableObj";
	
	// TABLE object
	prototype.domTableNode = null; // HTMLTable
	// BODY object
	prototype.domTemplateSectNode = null; // HTMLTableSection
	// ROW object, from BODY object (ID: dynatable_items)
	prototype.domTemplateNode = null; // HTMLTableSection
	// BODY object (ID: dynatable_template)
	prototype.domNode = null; // HTMLTableSectiony
//	prototype.rows.domNode = null; // ??
	prototype.customNode = null; // Variable HTMLNode
	
	prototype.id = null; // String
	prototype.title = null; // String
	prototype.rowAddUserMethod = DTBL_ROWADD_MANUAL; // Constant integer
	prototype.activated = false; // Boolean
	prototype.propagateToParent = false; // Boolean

	prototype.recordset = null; // DynaRecordset
	
	prototype.rowDef = null; // DynaRowDef

	prototype.rowItemSect = null; // DynaSectInterface
	prototype.parentDynaTable = null; // DynaTable
		
	prototype.fields = null; // DynaInterfaceColl
	prototype.buttons = null; // DynaInterfaceColl
	prototype.selectors = null; // DynaInterfaceColl
	prototype.custom = null; // DynaInterfaceColl

	prototype.foot = null; // DynaSectInterface
	prototype.head = null; // DynaSectInterface
	
	prototype.maxRows = 0; // Integer

	prototype.definition = null; // Function object
	
	prototype.init = function(htmlTable, jsfDefinition, htmlTblSect_Template, htmlTblSect_Items)
	{
		this.callOn(superclass.init);

		this.rows = new Object;
		this.rows.item = function (rowIndex)
		{
			var oRow = this.domNode.rows.item(rowIndex);
			return (oRow) ? oRow.dynatable : null;
		}

		// TABLE object
		this.domTableNode = htmlTable;
		// BODY object
		this.domTemplateSectNode = (htmlTblSect_Template) ? htmlTblSect_Template : this.getTableTemplateSection(this.domTableNode);
		// ROW object, from BODY object (ID: dynatable_items)
		this.domTemplateNode = this.domTemplateSectNode.rows[0];
		// BODY object (ID: dynatable_template)
		this.domNode = (htmlTblSect_Items) ? htmlTblSect_Items : this.getTableItemsSection(this.domTableNode, this.domTemplateSectNode);
		this.rows.domNode = this.domNode;
		
		this.id = this.domTableNode.id;
		this.title = dtbl_getAttrStr(this.domTableNode, 'title');
		
		dtbl_toggleElemDisplay(this.domTemplateSectNode, false);
		
		this.rowDef = new class_dynatable_rowInterface(this);
	
		this.rowItemSect = new class_dynatable_itemSectInterface(this.domNode, this.rowDef);
		
		var c = new class_dynatable_categorized_collection(DTBL_ITEMTYPES, this.domTemplateNode);
		this.fields = new class_dynatable_interface_collection(c.byName[DTBL_PREFIX_FIELD], DTBL_PREFIX_FIELD);
		this.buttons = new class_dynatable_interface_collection(c.byName[DTBL_PREFIX_BUTTON], DTBL_PREFIX_BUTTON);
		this.selectors = new class_dynatable_interface_collection(c.byName[DTBL_PREFIX_SELECTOR], DTBL_PREFIX_SELECTOR);
		this.custom = new class_dynatable_interface_collection(c.byName[DTBL_PREFIX_CUSTOM], DTBL_PREFIX_CUSTOM);
	
		this.foot = this.getExtraSectionInterface(this.domTableNode.tFoot);
		this.head = this.getExtraSectionInterface(this.domTableNode.tHead);
		
		if (!this.domTableNode.dynatable && !this.domTableNode.bindedDynaTableEvents) // Possibly IE specific
		{
			this.bindEvents(this.domTableNode);
			this.domTableNode.bindedDynaTableEvents = true;
		}
		if (!this.domTableNode.dynatable && dtbl_isIE && !this.domTemplateNode.bindedDynaTableEvents)
		{
			this.bindNodeEventsIE(this.domTemplateNode);
			this.domTemplateNode.bindedDynaTableEvents = true;
		}
		
		this.behave(behavior_dynatable_checkForInputOptions);
		this.behave(behavior_dynatable_rowKeyCollector);
		if (dtbl_isIE) this.rowDef.behave(behavior_dynatable_inputValueFix);
		this.rowItemSect.behave(behavior_dynatable_maxRows);
		this.rowItemSect.behave(behavior_dynatable_moveButtonStateWatch);
		this.rowItemSect.behave(behavior_dynatable_watchRows);
	
		this.definition = jsfDefinition;
		if (this.definition) this.definition();
		this.activate();
	}
	prototype.addRow = function (insertBefore, autoActivate)
	{
		if (!this.activated) this.activate();
		return this.rowItemSect.addRow(insertBefore, autoActivate);
	}
	prototype.removeRow = function (iIndex)
	{
		this.rows.item(iIndex).remove();
	}
	prototype.rows = null;
//	this.rows.item = p_rows;

	prototype.rowCount = function ()
	{
		return this.domNode.rows.length;
	}
	prototype.fireEvent = function (eventName, evt, noPropag)
	{
		if (dtbl_showStatus) window.status += " / table: "+this.id;
		return this.events.fire(eventName, this, (this.propagateToParent && !noPropag) ? this.parentDynaTable : null, evt);
	}
	prototype.removeAll = function ()
	{
		var i_rowCt = this.rowCount();
		for (var i=0; i<i_rowCt; i++)
			this.removeRow(0);
	}
	prototype.bindEvents = function (oNode)
	{
		var arEvts = DTBL_EVENTSTOBIND_ALL;
		for (var i=0; i<arEvts.length; i++)
		{
			dtbl_bindFunctToEvent(oNode, arEvts[i], dynatable_eventRedirector);
		}
	}
	prototype.bindNodeEventsIE = function (oRow)
	{
		var oInp;
		var oInps = dtbl_getElementsByTagNames(oRow, DTBL_EDITABLEFORMINPUTS);
		for (var q=0; q<oInps.length; q++)
		{
			oInp = oInps.item(q);
			for (var i=0; i<DTBL_EVENTSTOBIND_FIELD.length; i++)
			{
				dtbl_bindFunctToEvent(oInp, DTBL_EVENTSTOBIND_FIELD[i], dynatable_eventRedirector);
			}
		}
	}
	prototype.bindExistingRowNodes = function ()
	{
		for (var i=0; i<this.domNode.rows.length; i++) 
			this.bindRowNode(this.domNode.rows.item(i), null, this, true);
	}
	prototype.activate = function ()
	{
		if (this.activated) return false;
		if (this.fireEvent("beforeactivate")==false) return false;

		if (this.rowAddUserMethod == DTBL_ROWADD_AUTO) this.behave(behavior_dynatable_autoRowAdder);
		this.activated = true;
		
		this.fireEvent("activate");
		return true;
	}
	prototype.getTableTemplateSection = function (oTable)
	{
		var oSect = dtbl_getElemShallow(oTable, DTBL_TABLESECTID_TEMPLATE);
		if (!oSect)
		{
			oSect = oTable.tBodies[0];
			if (!oSect || !oSect.rows[0])
				return dtbl_handleErr("No template section found (a single row needs to be placed within <TBODY></TBODY> tags)."); // THROWEXCEPTION

		}
		return oSect;
	}
	prototype.getTableItemsSection = function (oTable, oTempSect)
	{
		var oSect = dtbl_getElemShallow(oTable, DTBL_TABLESECTID_ITEMS);
		return (oSect) ? oSect : oTable.insertBefore(dtbl_genTag("TBODY", DTBL_TABLESECTID_ITEMS), oTempSect);
	}
	prototype.moveRowSect = function (oRow, oSect)
	{
		if (!oSect) oSect = this.rowItemSect;
		var oSectOld = oRow.sect;
		var evt = new Object();
		evt[oRow.type] = oRow;
		if (oSectOld.fireEvent("beforerowdelete", evt) == false) return false;
		if (oSect.fireEvent("beforerowinsert", evt) == false) return false;
		if (oRow.fireEvent("beforerowmove", null, true) == false) return false;
		if (oRow.fireEvent("beforerownodemove", null, true) == false) return false;
		oSect.domNode.appendChild(oRow.domNode);
		oRow.sect = oSect;
		oRow.fireEvent("rownodemove", null, true);
		oRow.fireEvent("rowmove", null, true);
		oSect.fireEvent("rowinsert", evt);
		oSectOld.fireEvent("rowdelete", evt);
	}
	prototype.getExtraSectionInterface = function (oSect)
	{
		if (!oSect) return null;
		var oInt = new class_dynatable_sectInterface(oSect, this, DTBL_ITEMTYPES, DTBL_ITEMTYPES_DESC);

		for (var sItem in oInt.buttons.byName) oInt.buttons.byName[sItem].behave(behavior_dynatable_multiRowButton);
		for (var sItem in oInt.fields.byName) oInt.fields.byName[sItem].behave(behavior_dynatable_sortRows);

		return oInt;
	}
}
// END - MAIN DTBL CLASS

// START - TABLE: SECTION, ROW, and FIELD INTERFACE CLASSES
with (class_dynatable_interface_item = inheritFrom(class_dynatable_base))
{
	prototype.type = "dynatableItemDef";

	prototype.name = null; // String
	prototype.prefix = null; // String
	prototype.domNodes = null; // Array
	prototype.domNode = null; // Variable HTMLNode
	prototype.hasNode = null; // Boolean
	prototype.multiple = false; // Boolean
	prototype.delimiter = DTBL_VALUEDELIM; // String
	prototype.controlType = DTBL_UNKNOWN; // Integer constant
	prototype.dataParseType = DTBL_PARSETYPE_NONE; // Integer constant
	prototype.dynamicControlType = false; // Boolean
	prototype.isCodeBased = false; // Boolean
	prototype.valueAttributeName = null; // String
	
	prototype.getControlValue = null; // Function Object
	prototype.setControlValue = null; // Function Object
	prototype.setControlValueFunct = function ()
	{
		var setFunct;
		var getFunct;
		var contType;
		switch (this.domNode.tagName)
		{
			case 'INPUT':
				contType = this.domNode.type.toLowerCase();
				switch (this.domNode.type.toLowerCase())
				{
					case "text":
					case "password":
					case "hidden":
						setFunct = setValue_text;
						getFunct = getValue_text;
						break;
					case "checkbox" :
						setFunct = setValue_checkbox;
						getFunct = getValue_checkbox;
						this.isCodeBased = true;
						break;
					case "radio" :
						setFunct = setValue_radio;
						getFunct = getValue_radio;
						this.isCodeBased = true;
						break;
					default :
						contType = "unsupported";
				}
				break;
			case 'TEXTAREA':
				contType = "textarea";
				setFunct = setValue_text;
				getFunct = getValue_text;
				break;
			case 'SELECT':
				contType = "select";
				setFunct = setValue_select;
				getFunct = getValue_select;
				this.isCodeBased = true;
				if (this.domNode.multiple) this.multiple = true;
				for (var i=0; i<this.domNode.options.length; i++) 
					this.domNode.options[i].id = this.domNode.id+"_"+dtbl_getAttrStr(this.domNode.options[i]);

				break;
			default:
				contType = "readonly";
				setFunct = setValue_default;
				getFunct = getValue_default;
		}
		this.setControlValue = setFunct;
		this.getControlValue = getFunct;
		this.controlType = contType;
	};
	prototype.focusControl = function (bScrollIntoView)
	{
		dtbl_focusObj(this.domNode, bScrollIntoView);
	};

	prototype.setValue = function (vVal, forceChange)
	{
		var vNewVal;
		
		if (this.dispFormatFunct)
		{
			var vFormattedVal = this.dispFormatFunct(vVal);
			if (vFormattedVal != null) vNewVal = vFormattedVal;
		}
		else
		{
			vNewVal = (vVal != null) ? String(vVal) : "";
		}

		if (this.row && this.row.sect && this.row.sect.grid.activated)
		{
			if (!forceChange && this.getControlValue() == vNewVal) return;
			this.setControlValue(vNewVal);
			this.fireEvent("rowitemchange");
		}
		else
		{
			this.setControlValue(vNewVal);
		}
	};
	prototype.getValue = function ()
	{
		var vVal = this.getControlValue();
		if (this.realFormatFunct)
		{
			var vFormattedVal = this.realFormatFunct(vVal);
			if (vFormattedVal != null) return vFormattedVal;
		}
		if (this.dataParseType == DTBL_PARSETYPE_NONE) return vVal;
		
		switch (this.dataParseType)
		{
			case DTBL_PARSETYPE_STRING: return String(vVal);
			case DTBL_PARSETYPE_NUMBER: return (!isNaN(vVal)) ? Number(vVal) : null;
			case DTBL_PARSETYPE_DATETIME: return (vVal.length) ? new Date(vVal) : null;
			case DTBL_PARSETYPE_BOOLEAN: return (vVal) ? true : false;
		}
	};
	prototype.getDisplayText = function ()
	{
		return this.getControlValue(true);
	};
	
	prototype.dispFormatFunct = null;
	prototype.realFormatFunct = null;
	prototype.sortCompFunct = null;
	prototype.fireEvent = function (eventName, evt, noPropag)
	{
		if (dtbl_showStatus) window.status += this.prefix+": "+this.name;
		return this.events.fire(eventName, this, (!noPropag) ? this.row : null, evt);
	}

	prototype.init = function (sName, aNodeArray, sPrefix)
	{
		this.callOn(superclass.init);
		
		this.name = sName;
		this.prefix = sPrefix;
		this.domNodes = aNodeArray;
		this.domNode = this.domNodes[0];
		this.hasNode = (aNodeArray != null);
		if (this.hasNode) for (var i=0; i<this.domNodes.length; i++) this.domNodes[i].dynatable = this;
		
		this.setControlValueFunct();
	}
	prototype.setDisplayed = function (bState)
	{
		for (var i=0; i<this.domNodes.length; i++)
			this.domNodes[i].style.display = (bState) ? "" : "none";
	}
	prototype.setVisibility = function (bState)
	{
		for (var i=0; i<this.domNodes.length; i++)
			this.domNodes[i].style.visibility = (bState) ? "visible" : "hidden";
	}
	prototype.setEnabled = function (bState)
	{
		for (var i=0; i<this.domNodes.length; i++)
			this.domNodes[i].disabled = (!(bState));
	}
}

// START SET/GET VALUE FUNCTIONS
function getValue_default()
{
	return dtbl_getInnerText(this.domNode);
}

function setValue_default(vVal)
{
	var oTextNode = dtbl_findTextNode(this.domNode);
	if (oTextNode)	oTextNode.nodeValue = vVal;
		else this.domNode.appendChild(document.createTextNode(vVal));
	if (this.valueAttributeName) this.domNode.setAttribute(this.valueAttributeName.toLowerCase(), vVal);
}

function getValue_text()
{
	return this.domNode.value;
}

function setValue_text(vVal)
{
	this.domNode.value = vVal;
}

function getValue_checkbox(bDispText)
{
	if (this.domNodes.length == 1)
	{
		var sVal = dtbl_getAttrStr(this.domNode);
		if (!sVal) sVal = "";
		if (this.domNode.checked) return (sVal.length) ? sVal : true;
			else return this.domNode.getAttribute("altvalue");
	}

	var aChked = new Array();
	var n;
	for (var i=0; i<this.domNodes.length; i++)
		if ((n=this.domNodes[i]).checked) aChked[aChked.length] = (!bDispText) ? dtbl_getAttrStr(n) : dtbl_getInnerText(n.parentNode);

	return aChked.join(this.delimiter);
}

function setValue_checkbox(vVal)
{
	if (this.domNodes.length == 1)
	{
		this.domNode.checked = (this.domNode.value.length != 0) ? (vVal == this.domNode.value) : ((vVal) ? true : false);
	}
	else
	{
		var aVal = dtbl_convListToArr(vVal, this.delimiter);
		for (var i=0; i<this.domNodes.length; i++)
			this.domNodes[i].checked = dtbl_isStrInArray(dtbl_getAttrStr(this.domNodes[i]), aVal);

	}
}

function getValue_select(bDispText)
{
	var aSelected = new Array();
	for (var optionsIdx = 0; optionsIdx < this.domNode.options.length; optionsIdx++)
	{
		var thisOption = this.domNode.options[optionsIdx]
		if (thisOption.selected) aSelected[aSelected.length] = (bDispText ? thisOption.text : thisOption.value);
	}
	return aSelected.join(this.delimiter);
}

function setValue_select(vVal)
{
	if (!this.multiple)
	{
		dtbl_selectThisValue(vVal, this.domNode);
	}
	else
	{
		var aVal = dtbl_convListToArr(vVal, this.delimiter);
		if (this.domNode.controlModified) dtbl_unselectAll(this.domNode);
		for (var i=0; i<aVal.length; i++) dtbl_selectThisValue(aVal[i], this.domNode);
		this.domNode.controlModified = true;
	}
}

function getValue_radio(bDispText)
{
	for (var i=0; i<this.domNodes.length; i++)
	{
		if (this.domNodes[i].checked) {
			return (!bDispText) ? dtbl_getAttrStr(this.domNodes[i]) : dtbl_getInnerText(this.domNodes[i].parentNode);
		}
	}
}

function setValue_radio(vVal)
{
	if (this.domNodes.length == 1)
	{
		this.domNode.checked = (this.domNode.value.length != 0) ? (vVal == this.domNode.value) : ((vVal) ? true : false);
	}
	else
	{
		for (var i=0; i<this.domNodes.length; i++)
			this.domNodes[i].checked = (dtbl_getAttrStr(this.domNodes[i]) == vVal);
	}
}
// END SET/GET VALUE FUNCTIONS

with (class_dynatable_rowInterface = inheritFrom(class_dynatable_base))
{
	prototype.init = function(dynatable)
	{
		this.callOn(superclass.init);
		this.grid = dynatable;
		this.domNode = this.grid.domTemplateNode;
		this.behave(behavior_dynatable_updateCatcher);
	}

	prototype.sect = null;
	prototype.grid = null;
	prototype.domGridNode = null;
	prototype.domNode = null;
	prototype.activated = null;
	prototype.insertBeforeIndex = null;
	
	prototype.fields = null;
	prototype.buttons = null;
	prototype.selectors = null;
	prototype.custom = null;

	prototype.show = function ()
	{ 
		if (!this.activated) this.activate();
		return dtbl_toggleElemDisplay(this.domNode, true);
	};
	prototype.hide = function ()
	{ 
		return dtbl_toggleElemDisplay(this.domNode, false); 
	};
	prototype.activate = function ()
	{
		if (this.activated) return false;
		var evt = new Object;
		if (this.fireEvent("beforerowinsert", evt)==false) return false;
		
		var rowCount = this.sect.domNode.rows.length;
		if (this.insertBeforeIndex == null || this.insertBeforeIndex >= rowCount || rowCount == 0) {
			this.sect.domNode.appendChild(this.domNode);
		} else {
			this.sect.domNode.insertBefore(this.domNode, this.rows.item(this.insertBeforeIndex).domNode);
		}
		this.activated = true;
		this.fireEvent("rowinsert", evt);

		return true;
	}
	prototype.getIndex = function ()
	{
		return this.domNode.sectionRowIndex;
	}
	prototype.populate = function (aFieldVals, aFields)
	{
		var evt = new Object();
		evt.dynatablePopFields = aFields;
		evt.dynatablePopVals = aFieldVals;
		if (this.fireEvent("beforerowpopulate", evt)==false) return false;
		if (aFields==null) aFields = this.grid.recordset.getFieldNames();
		var oField;
		
		for (var i=0; i<aFields.length; i++)
		{
			oField = this.fields.byName[aFields[i]];
			if (oField) oField.setValue(aFieldVals[i]);
		}
		this.fireEvent("rowpopulate", evt);
	}
	prototype.remove = function ()
	{
		var evt = new Object;
		evt.rowIndex = this.getIndex();

		if (this.fireEvent("beforerowdelete")==false) return false;
		this.sect.domNode.deleteRow(evt.rowIndex);
		this.fireEvent("rowdelete", evt);
	}
	prototype.fireEvent = function (eventName, evt, noPropag)
	{
		if (dtbl_showStatus) window.status += " / row: " + this.getIndex() + " / sect: " + this.sect.domNode.id;
		return this.events.fire(eventName, this, (!noPropag) ? this.sect : null, evt);
	};
	prototype.move = function (newPos)
	{
		var iRows = this.sect.rowCount();
		if (newPos == this.getIndex()) return;
		if (newPos < 0 || newPos > iRows) return false;
		if (this.fireEvent("beforerowmove")==false) return false;

		var oParent = this.domNode.parentNode;
		if (this.moveRow(this, (newPos!=0) ? newPos : 1) == false) return false;

		if (newPos == 0)
		{
			if (this.moveRow(this.sect.rows.item(0), 2) == false) return false;
		}
		
		this.fireEvent("rowmove");
			
		return true;
	};
	prototype.moveOffset = function (offsetPos)
	{
		return this.move(this.getIndex()+((offsetPos<0)?offsetPos:offsetPos+1));
	};
	prototype.moveAfter = function (oRef)
	{
		return this.move(oRef.getIndex()+1);
	};
	prototype.duplicate = function ()
	{
		if (this.fireEvent("beforerowduplicate")==false) return false;
		var oRow = this.sect.addRow(null, null, this.domNode.cloneNode(true));
		for (fieldName in oRow.fields.byName)
		{
			oRow.fields.byName[fieldName].setValue(this.fields.byName[fieldName].getValue(), true);
		}
		oRow.fireEvent("rowduplicate");
		return oRow;
	};
	prototype.focus = function (sColl)
	{
		var col = this[(sColl) ? sColl : "fields"];
		for (var i=0; i<col.length; i++)
			if (col.item(i).controlType != "readonly")
				return col.item(i).focusControl();
	};
	prototype.moveRow = function (oRow, newPos)
	{
		var oParent = oRow.domNode.parentNode;
		if (oRow.fireEvent("beforerownodemove")==false) return false;
		oParent.replaceChild(oRow.domNode, oParent.insertRow(newPos));
		oRow.fireEvent("rownodemove");
	}	
}

with (class_dynatable_sectInterface = inheritFrom(class_dynatable_base))
{
	prototype.type = "dynatableSect";
	prototype.domNode = null; // HTMLTableSection
	prototype.grid = null; // DynaTable
	
	prototype.init = function (oNode, oGrid, aDefItems, aItemDesc)
	{
		this.callOn(superclass.init);

		this.domNode = oNode;
		this.grid = oGrid;
		if (aDefItems)
		{
			var oGridNodes = new class_dynatable_categorized_collection(aDefItems, oNode);
			var oColl;
			var oNodeColl;
			var oRowItem;
			var sItem;
			for (var i=0; i<aDefItems.length; i++)
			{
				oColl = new class_dynatable_collection;
				oNodeColl = oGridNodes.byName[aDefItems[i]];
				for (sItem in oNodeColl.byName)
				{
					class_dynatable_rowItem.prototype = new class_dynatable_interface_item(sItem, oNodeColl.byName[sItem], aDefItems[i]);
					oRowItem = new class_dynatable_rowItem(this, true);
					oColl.add(oRowItem, sItem);
				}
				this[aItemDesc[i]] = oColl;
			}
		}
		oNode.dynatable = this;
	}
	
	prototype.fireEvent = function(eventName, evt, noPropag)
	{
		return this.events.fire(eventName, this, (!noPropag && this == this.grid.rowItemSect) ? this.grid : null, evt);
	}
	
}

with (class_dynatable_itemSectInterface = inheritFrom(class_dynatable_sectInterface))
{
	prototype.addRow = function (insertBefore, autoActivate, rowNode)
	{
		if (autoActivate == null) autoActivate = true;
		var oNewNode = (rowNode) ? rowNode : this.templateRowNode.cloneNode(true);
		var oNewRow = this.bindRowNode(oNewNode, insertBefore);
		if (autoActivate) oNewRow.activate();

		return oNewRow;
	}
	prototype.bindRowNode = function (oNode, insertBefore, alreadyAdded)
	{
		class_dynatable_row.prototype = this.rowDef;
		class_dynatable_row.prototype.constructor = class_dynatable_row;
		
		var oNewRow = new class_dynatable_row(oNode, insertBefore, this, alreadyAdded, this.rowDef);
		oNode.dynatable = oNewRow;
		return oNewRow;
	}
	prototype.domNode = null;
	prototype.grid = null;
	prototype.rows = null;
	prototype.rowCount = function ()
	{
		return (this.domNode.rows.length);
	}
	prototype.rowDef = null;
	prototype.templateRowNode = null;
	prototype.empty = function ()
	{
		while (this.domNode.childNodes.length) 
			this.domNode.removeChild(this.domNode.childNodes[0]);
	}
	
	prototype.init = function(oNode, oRowInterface)
	{
		this.callOn(superclass.init, oNode, oRowInterface.grid);

		this.rows = new Object;
		this.rows.domNode = this.domNode;
		this.rows.item = function (rowIndex)
		{
			var oRow = this.domNode.rows.item(rowIndex);
			return (oRow) ? oRow.dynatable : null;
		}
		this.rowDef = oRowInterface;
		this.templateRowNode = this.grid.domTemplateNode;		
	}

	prototype.type = "dynatableItemSect";
}

function class_dynatable_rowItem(oRow, bUseDefNode)
{
	this.type = "dynatableItem";

	this.parentItem = oRow;
	this.row = oRow;
	if (!bUseDefNode)
	{
		this.domNodes = dtbl_getElemArrDeep(this.row.domNode, this.name); // this.prefix+
		this.domNode = this.domNodes[0];
	}
	
	for (var i=0; i<this.domNodes.length; i++)	this.domNodes[i].dynatable = this;

	if (this.dynamicControlType) this.setControlValueFunct();
	
	this.fireEvent("instantiate");
}
// END - TABLE: SECTION, ROW, and FIELD INTERFACE CLASSES

// START - COLLECTION CLASSES
with (class_dynatable_collection = inheritFrom(null))
{
	prototype.byName = null; // Array
	prototype.byNum = null; // Array
	prototype.length = 0; // Integer
	prototype.itemType = null; // Variable DynaTable Object

	prototype.add = function (oItem, vItemIndex, insertBefore)
	{
		if (oItem == null) oItem = new this.itemType;
		if (insertBefore==null) insertBefore = this.length;
		var oColItem = new class_dynatable_collection_item(oItem, vItemIndex);

		dtbl_insertIntoArray(oColItem, this.byNum, insertBefore);

		if (vItemIndex != null) this.byName[vItemIndex] = oItem;
		this.length = this.byNum.length;
		return oItem;
	}
	prototype.item = function (vItem)
	{
		switch (typeof(vItem))
		{
			case "number" : if (vItem < this.length) return this.byNum[vItem].item; 
									else return dtbl_handleErr("Specified item position exceeds collection length."); // THROWEXCEPTION
			case "string" : return this.byName[vItem];
		}
	}
	prototype.remove = function (vItem, isNameIndex)
	{
		var oColItem;
		var iIndex;
		if (isNameIndex || typeof(vItem)=="string")
		{
			if (!this.byName[vItem]) return dtbl_handleErr("Cannot remove item; specified named index does NOT exist within collection."); // THROWEXCEPTION
			
			for (var i=0; i<this.byNum.length; i++)
			{
				if (this.byNum[i].name == vItem)
				{
					oColItem = this.byNum[i];
					iIndex = i;
					break;
				}
			}
			if (oColItem == null) return dtbl_handleErr("Cannot remove item; an unknown problem occurred within the collection index."); // THROWEXCEPTION
		}
		else if (typeof(vItem)=="number")
		{
			if (vItem < this.length) 
			{
				oColItem = this.byNum[vItem];
				iIndex = vItem;
			}
			else
			{
				return dtbl_handleErr("Cannot remove item; specified numerical index exceeds collection length."); // THROWEXCEPTION
			}
		}
		if (oColItem.name != null && oColItem.item == this.byName[oColItem.name]) delete this.byName[oColItem.name];
		
		dtbl_removeFromArray(this.byNum, iIndex);

		this.length = this.byNum.length;
	}
	prototype.addCollection = function (oColl, indexProperty)
	{
		if (!indexProperty)
		{
			for (var i=0; i<oColl.length; i++)
				this.add(oColl.item(i));
		}
		else
		{
			var oItem;
			for (var i=0; i<oColl.length; i++)
			{
				oItem = oColl.item(i);
				this.add(oItem, oItem[indexProperty]);
			}
		}
	}
	prototype.namedItem = function (vItem)
	{
		return this.byName[vItem];
	}
	prototype.getNameArray = function ()
	{
		var aNames = new Array();
		for (var sItem in this.byName) aNames[aNames.length] = sItem;
		return aNames;
	}
	prototype.getItemIndex = function (vName)
	{
		for (var i=0; i<this.byNum.length; i++)
			if (this.byNum[i].name == vName) return i;
			
		return dtbl_handleErr("Cannot get numerical index; specified name does NOT exist within the collection."); // THROWEXCEPTION
	}
	prototype.removeByValue = function (vVal)
	{
		for (var i=0; i<this.byNum.length; i++)
			if (this.byNum[i].item == vVal) return this.remove(i);
	}
	prototype.init = function (itemType)
	{
		this.byName = new Array();
		this.byNum = new Array();
		this.itemType = (itemType != null) ? itemType : Object;
	}
}

function class_dynatable_collection_item(oItem, vIndex)
{
	this.item = oItem;
	this.name = vIndex;
}

with (class_dynatable_rowItem_collection = inheritFrom(class_dynatable_collection))
{
	prototype.init = function (oRow, oDefColl, oRowItemDef)
	{
		if (oRowItemDef == null) oRowItemDef = class_dynatable_rowItem;
		this.callOn(superclass.init, oRowItemDef);

		var oDef;
		for (var sName in oDefColl.byName)
		{
			if ((oDef = oDefColl.byName[sName]).hasNode) 
			{
				oRowItemDef.prototype = oDef;
				this.add(new (oRowItemDef.constructor = oRowItemDef)(oRow), sName);
			}
		}
	}
}
function class_dynatable_event_funct(funct, behavior, eventName)
{
	this.funct = funct;
	this.behavior = behavior;
	this.eventName = eventName;
}

with (class_dynatable_event_collection = inheritFrom(class_dynatable_collection))
{
	prototype.addHandler = function (eventName, funct, label, behavior)
	{
		if (!this.byName[eventName]) this.add(null, eventName);
		var oFunctItem = new class_dynatable_event_funct(funct, behavior, eventName);
		var vInsertPos = (behavior==null) ? 0 : null;
		
		return this.byName[eventName].add(oFunctItem, label, vInsertPos);

	}
	
	prototype.fire = function (eventName, context, parentItem, evt)
	{
		var bResult;
		
		if (evt == null) evt = new Object();
		if (context.type) evt[context.type] = context;
		evt.dynatableEvent = eventName; // if (!evt.dynatableEvent) 
		if (!evt.dynatableOrigEvent) evt.dynatableOrigEvent = eventName;
		
		if (this.byName[eventName])
		{
			var oFuncts = this.byName[eventName];
			var oFunctItem;
			for (var i=0; i<oFuncts.length; i++)
			{
				oFunctItem = oFuncts.byNum[i].item;
				if (oFunctItem.disabled) continue;
				if (oFunctItem.behavior)
				{
					oFunctItem.behavior.tempFunct = oFunctItem.funct;
					bResult = oFunctItem.behavior.tempFunct(context, evt);
				}
				else
				{
					bResult = oFunctItem.funct(context, evt);
				}
				
				if (oFunctItem.removeAfterRun) 
				{
					oFuncts.removeByValue(oFunctItem);
					i--;
				}

				if (bResult==false)
				{
					if (evt.preventDefault) evt.preventDefault();
					return false;
				}
			}
		}

		if (parentItem && bResult != true) return parentItem.fireEvent(eventName, evt);
		return bResult;
	}
	prototype.removeHandler = function (oFunctItem)
	{
		this.byName[oFunctItem.eventName].removeByValue(oFunctItem);
	}
	prototype.init = function()
	{
		this.callOn(superclass.init, class_dynatable_collection);
		// Do nothing	
	}
}

with (class_dynatable_categorized_collection = inheritFrom(class_dynatable_collection))
{
	prototype.addItemsById = function (aNodes)
	{
		var oNode;
		var sID;
		var oCat;
		var aNodeArray;
		var sCat;
		for (var i=0; i<aNodes.length; i++)
		{
			oNode = aNodes[i];
			for (sCat in this.byName)
			{
				sID = oNode.id;
				if (oNode.getAttribute("dtblType") == sCat)
				{
					oCat = this.byName[sCat];
//					sID = sID.slice(sCat.length);
					if (oCat.byName[sID]) aNodeArray = oCat.byName[sID];
						else aNodeArray = new Array();

					aNodeArray[aNodeArray.length] = oNode;
					oCat.add(aNodeArray, sID);
					break;
				}
			}
		}
	}
	prototype.init = function(aCategories, oNode)
	{
		this.callOn(superclass.init, class_dynatable_collection);
		
		for (var i=0; i<aCategories.length; i++)
			this.add(new class_dynatable_collection(Array), aCategories[i]);
	
		this.addItemsById(dtbl_getElemArrDeepPrefix(oNode, null, false, "dtblType"));
		
	}
}

with (class_dynatable_interface_collection = inheritFrom(class_dynatable_collection))
{
	prototype.init = function (oNodeColl, sPrefix, oItemDef)
	{
		if (oItemDef == null) oItemDef = class_dynatable_interface_item;
		this.callOn(superclass.init, oItemDef);
	
	//	var oNodeColl = oNodeCat.byName[sPrefix];
		for (var sItem in oNodeColl.byName)
		{
			this.add(new oItemDef(sItem, oNodeColl.byName[sItem], sPrefix), sItem);
		}
	}
}

with (class_dynatable_behavior_collection = inheritFrom(class_dynatable_collection))
{
	prototype.itemDef = null;
	
	prototype.addBehavior = function (oBehaviorDef)
	{
//		var oExistingDef;
		var oNew = new oBehaviorDef(this.itemDef);
//		if ((oExistingDef = this.byName[oNew.name]) != null) return oExistingDef;
		return this.add(oNew, oNew.name);
	}
	prototype.init = function (oItemDef)
	{
		this.callOn(superclass.init, class_dynatable_collection);
		this.itemDef = oItemDef;
	}
}
// END - COLLECTION CLASSES

// START - ROW INSTANCE CLASS
function class_dynatable_row(oNode, insertBeforeIndex, sect, alreadyActivated)
{
	this.type = "dynatableRow";
	this.sect = sect;
	this.grid = this.sect.grid;
	this.domGridNode = this.sect.domNode;
	this.domNode = oNode;
	if (!oNode.all) this.domNode.all = this.domNode.getElementsByTagName("*");
	this.activated = (alreadyActivated) ? true : false;
	this.insertBeforeIndex = insertBeforeIndex;
	
	this.fields = new class_dynatable_rowItem_collection(this, this.grid.fields);
	this.buttons = new class_dynatable_rowItem_collection(this, this.grid.buttons);
	this.selectors = new class_dynatable_rowItem_collection(this, this.grid.selectors);
	this.custom = new class_dynatable_rowItem_collection(this, this.grid.custom);
	
}
	
// END - ROW INSTANCE CLASS

// START - DTBL BEHAVIOR CLASSES
with (behavior_dynatable = inheritFrom(null))
{
	prototype.autoInherit = true;
	prototype.name = ""; // String
	prototype.context = null; // Variable Dyna Object
	prototype.reg = function (sEventName, oFunct, oContext, sInstance)
	{
		if (oContext == null) oContext = this.context;
		return oContext.events.addHandler(sEventName, oFunct, sInstance, this);
	}
	prototype.regs = function (sEventNames, oFunct, oContext, sInstance)
	{
		var a_nm = sEventNames.split(",");
		var a_evt = new Array();
		for (var i=0; i<a_nm.length; i++)
			a_evt[a_evt.length] = this.reg(a_nm[i], oFunct, oContext, sInstance);
			
		return a_evt;	
	}
	prototype.regExists = function (sEventInstanceName, oContext)
	{
		if (oContext == null) oContext = this.context;
		return (oContext.events.byName[sEventInstanceName] != null);
	}
	prototype.init = function (oContextDef, vName)
	{
		this.context = oContextDef;
		if (vName) this.name = vName;
	}
}

function class_dynatable_exception(type, message)
{
	this.type = type;
	this.message = message;
}

with (behavior_dynatable_autoRowAdder = inheritFrom(behavior_dynatable))
{
	prototype.name = "autoRowAdder";
	prototype.domNode = null; // HTMLTableSection
	prototype.rowAutoSect = null; // DynaSect

	prototype.init = function(oGrid, oAutoAddNode)
	{
		// BODY object (ID: dynatable_autoAdd)
		this.domNode = (oAutoAddNode) ? oAutoAddNode : this.getTableAutoAddSection(oGrid.domTableNode, oGrid.domNode);
		this.rowAutoSect = new class_dynatable_itemSectInterface(this.domNode, oGrid.rowDef);
		
		this.reg("activate", this.addRowIfReqd);
		this.reg("rowdelete", this.addRowIfReqd, oGrid.rowItemSect);
		this.reg("beforeupdate", this.activateRow, this.rowAutoSect);
		this.reg("beforerowinsert", this.checkIfAutoRowAllowed, this.rowAutoSect);
		this.reg("rowinsert", this.toggleNonInputs, this.rowAutoSect);
		this.reg("rowdelete", this.addRowIfReqd, this.rowAutoSect);
		this.reg("keydown", this.cancelTab, this.rowAutoSect);
		this.reg("recordsetload", this.addRowIfReqd);
		this.reg("recordsetload", this.removeRowIfNotReqd);
		
		this.domNode.insertRow(0).style.display = "none";
	}

	prototype.removeRowIfNotReqd = function (oGrid, evt)
	{
		if (this.rowAutoSect.rowCount() > 1 && this.context.rowItemSect.fireEvent("beforerowinsert") == false) 
			this.rowAutoSect.rows.item(1).remove();
	}	
	prototype.cancelTab = function (oSect, evt)
	{
		if (evt.keyCode == 9 && evt.dynatableItem)
		{
			var f = evt.dynatableRow.fields;
			var iNextIdx = f.getItemIndex(evt.dynatableItem.name)+1;
			if (iNextIdx<f.length) 
			{
				evt.dynatableItem.domNode.blur();
				f.byNum[iNextIdx].item.focusControl();
				return false;
			}
		}
	}
	prototype.checkIfAutoRowAllowed = function (oSect)
	{
		if (this.context.rowItemSect.fireEvent("beforerowinsert") == false) return false;
	}
	prototype.addRowIfReqd = function (oGrid)
	{
		if (this.rowAutoSect.rowCount()==1) this.rowAutoSect.addRow();
	}
	prototype.toggleNonInputs = function (oSect, evt, bEnable)
	{
		var r = oSect.rows.item(1);
		for (var i=0; i<r.selectors.length; i++) r.selectors.byNum[i].item.setEnabled(bEnable);
		for (var i=0; i<r.buttons.length; i++) r.buttons.byNum[i].item.setEnabled(bEnable);
	}
	prototype.activateRow = function (oSect, evt)
	{
		this.toggleNonInputs(oSect, evt, true);
		var oRow = oSect.rows.item(1);
		this.context.moveRowSect(oRow);

		if (evt.dynatableItem) 
		{
			var f = oRow.fields;
			var iNextIdx = f.getItemIndex(evt.dynatableItem.name)+1;
			f.byNum[(iNextIdx<f.length) ? iNextIdx : 0].item.focusControl();
		}
	}
	prototype.getTableAutoAddSection = function (oTable, oItemSect)
	{
		var oSect = dtbl_getElemShallow(oTable, DTBL_TABLESECTID_AUTOADD);
		return (oSect) ? oSect : dtbl_insertNodeAfter(dtbl_genTag("TBODY", DTBL_TABLESECTID_AUTOADD), oItemSect);
	}
}

with (behavior_dynatable_multiRowButton = inheritFrom(behavior_dynatable))
{
	prototype.name = "multiRowButton";
	prototype.init = function(oButtonDef)
	{
		oButtonDef.selectorName = "";
		
		this.reg("click", this.fireForEveryRow);
	}
	
	prototype.fireForEveryRow = function (oButton, evt)
	{
		var bUseSelector = (oButton.selectorName && oButton.selectorName.length);
		var aRowsToFire = new Array();
		var g = oButton.row.grid;
		
		var iRows = g.rowCount();
		var oRow;
		for (var i=0; i<iRows; i++)
		{
			oRow = g.rows.item(i);
			if (!bUseSelector || oRow.selectors.byName[oButton.selectorName].domNode.checked)	
				aRowsToFire[aRowsToFire.length] = oRow;
		}

		for (var i=0; i<aRowsToFire.length; i++) 
			if (oButton.events.fire("allrowclick", aRowsToFire[i], oButton.row, evt) == false) return false;
	}
}

with (behavior_dynatable_maxRows = inheritFrom(behavior_dynatable))
{
	prototype.name = "maxRowEnforcer";
	prototype.init = function(oSect)
	{
		this.reg("beforerowinsert", this.checkRowsExceeded);
		this.reg("beforerowinsert", this.checkRowsMet);
	}

	prototype.checkRowsExceeded = function (oSect)
	{
		if (oSect.grid.maxRows == 0) return;
		var iNewCt = oSect.rowCount()+1;
		if (iNewCt > oSect.grid.maxRows)
		{
			var bResult = oSect.fireEvent("maxrowsexceeded");
			return (bResult==null) ? false : bResult;
		}
	}
	prototype.checkRowsMet = function (oSect)
	{
		if (oSect.grid.maxRows == 0) return;
		var iNewCt = oSect.rowCount()+1;
		if (iNewCt == oSect.grid.maxRows) return oSect.fireEvent("maxrowsmet");
	}
}

with (behavior_dynatable_watchRows = inheritFrom(behavior_dynatable))
{
	prototype.name = "rowWatcher";
	prototype.empty = true;
	prototype.init = function(oSect)
	{
		this.reg("activate", this.regEvents, oSect.grid);
		this.reg("activate", function() { this.checkRows(oSect) }, oSect.grid);
		this.reg("recordsetload", function() { this.checkRows(oSect) }, oSect.grid);
	}

	prototype.regEvents = function(oGrid)
	{
		this.reg("rowinsert", this.checkRows);
		this.reg("rowdelete", this.checkRows);
	}
	
	prototype.checkRows = function(oSect)
	{
		if (oSect.rowCount() == 0)
		{
			this.empty = true;
			oSect.fireEvent("rowstatechangenone");
		}
		else
		{
			if (this.empty) 
			{
				oSect.fireEvent("rowstatechangesome");
				this.empty = false;
			}
		}
	}
}

with (behavior_dynatable_checkForInputOptions = inheritFrom(behavior_dynatable))
{
	prototype.init = function(oGrid)
	{
		this.arColls = new Array(oGrid.fields, oGrid.selectors);
		for (var i=0; i<this.arColls.length; i++)
		{
			var c = this.arColls[i];
			for (var sName in c.byName)
			{
				var i = c.byName[sName];
				switch (i.controlType)
				{
					case "radio" :
						i.behave(behavior_dynatable_inputOptionGroup);
						break;
					case "checkbox" :
						i.behave(behavior_dynatable_inputCheckbox);
						break;
				}
			}
		}
	}

	prototype.arColls = null; // Array
	prototype.name = "checkForInputOptions";
	
}

with (behavior_dynatable_inputOptionGroup = inheritFrom(behavior_dynatable))
{
	prototype.init = function(oFieldDef)
	{
		this.reg("mousedown", this.captureVal);
		this.reg("click", this.setOptionState);
		this.reg("instantiate", this.setName);
	}

	prototype.name = "inputOptionGroup";
	prototype.currVal = null;
	
	prototype.setName = function(oField, evt)
	{
		if (oField.domNode.name) return;
		
		var iRandomNo = Math.floor(10000*Math.random());			

		for (var i=0; i<oField.domNodes.length; i++)
			oField.domNodes[i].name = oField.name + "_" + iRandomNo;

	}
	
	prototype.captureVal = function(oField, evt)
	{
		this.currVal = oField.getControlValue();
	}
	prototype.setOptionState = function (oField, evt)
	{
		var oNode;
		for (var i=0; i<oField.domNodes.length; i++)
		{
			oNode = oField.domNodes[i];
			oNode.checked = (evt.target == oNode && dtbl_getAttrStr(oNode) != this.currVal);
		}
		oField.fireEvent("rowitemchange", evt);
	}
}

with (behavior_dynatable_inputCheckbox = inheritFrom(behavior_dynatable))
{
	prototype.name = "inputCheckbox";
	prototype.currState = null;
	prototype.init = function(oFieldDef)
	{
		this.reg("mousedown", this.captureState);
		this.reg("click", this.setOptionState);
	}
	
	prototype.captureState = function (oField, evt)
	{
		this.currState = evt.target.checked;
	}
	prototype.setOptionState = function (oField, evt)
	{
		if (evt.target.checked == this.currState) oField.domNode.checked = !oField.domNode.checked;
		oField.fireEvent("rowitemchange", evt);
	}
}

with (behavior_dynatable_moveButtonStateWatch = inheritFrom(behavior_dynatable))
{
	prototype.name = "moveButtonStateWatch";
	prototype.init = function(oSect)
	{
		this.reg("activate", this.regHandlers, oSect.grid);
	}
	prototype.regHandlers = function(oGrid)
	{
		this.notifyMoveButtonState(this.context);
		this.regs("rowinsert,rowmove,rowdelete", this.notifyMoveButtonState);
	}
	prototype.notifyMoveButtonState = function(oSect)
	{
		var iRowCount = oSect.rowCount();
		var bUp;
		var bDown;
		var oRow;
		for (var i=0; i<iRowCount; i++)
		{
			oRow = oSect.rows.item(i); 

			if (oRow.moveButtonState_up != (bUp = (i > 0))) 
			{
				oRow.moveButtonState_up = bUp;
				oRow.fireEvent("statechangeup");
			}
			
			if (oRow.moveButtonState_down != (bDown = (i < iRowCount-1)))
			{
				oRow.moveButtonState_down = bDown;
				oRow.fireEvent("statechangedown");
			}
		}
	}
}

with (behavior_dynatable_moveRow = inheritFrom(behavior_dynatable))
{
	prototype.name = "rowMover";
	prototype.btnName = ""; // String
	prototype.btnType = ""; // String
	prototype.moveOffset = 0; // Integer
		
	prototype.init = function (oButtonDef)
	{
		this.name += "_" + this.btnType;
		this.btnName = oButtonDef.name;
		oButtonDef.setVisibility(false);
		this.reg("instantiate", this.regHandlers);
		this.reg("click", this.moveRow);
	}
	prototype.regHandlers = function (oButton)
	{
		var sEvent = "statechange"+this.btnType;
		var sInstance = sEvent+"_"+this.name;
		if (!this.regExists(sInstance)) this.reg(sEvent, this.setState, oButton.row, sInstance);
	}
	prototype.moveRow = function (oButton, evt)
	{
		if (evt.target.blur) evt.target.blur();
		oButton.row.moveOffset(this.moveOffset);
	}
	prototype.setState = function (oRow)
	{
		oRow.buttons.byName[this.btnName].setVisibility(oRow["moveButtonState_"+this.btnType]);
	}
}
with (behavior_dynatable_moveRowDown = inheritFrom(behavior_dynatable_moveRow))
{
	prototype.btnType = "down";
	prototype.moveOffset = 1;
	prototype.init = function(oButtonDef)
	{
		this.name += "_" + this.btnType;
	}
}
with (behavior_dynatable_moveRowUp = inheritFrom(behavior_dynatable_moveRow))
{
	prototype.btnType = "up";
	prototype.moveOffset = -1;
	prototype.init = function(oButtonDef)
	{
		this.name += "_" + this.btnType;
	}
}

with (behavior_dynatable_sortRows = inheritFrom(behavior_dynatable))
{
	prototype.name = "rowSorter";
	prototype.sortOrder = "ASC";
	prototype.init = function(oFieldDef)
	{
		this.reg("click", this.sortRows);
	}

	prototype.sortRows = function(oField)
	{
		if (oField.fireEvent("beforecolsort")==false) return false;

		var oGrid = oField.parentItem.grid;
		var oDef = oGrid.fields.byName[oField.name];
		if (!oDef) return dtbl_handleErr("Cannot sort; '" + oField.name + "' column header field does NOT have a matching item within the template section."); // THROWEXCEPTION
		var sortFunct = (oField.sortCompFunct) ? oField.sortCompFunct : this.getSortFunct(oDef.dataParseType);
				
		var s = oGrid.rowItemSect;
		var iRCt = s.rowCount();
		var aRows = new Array(iRCt);
		var aFieldVals = new Array(iRCt);
		for (var i=0; i<iRCt; i++)	
		{
			aRows[i] = s.rows.item(i);
			aFieldVals[i] = new class_dynatable_rowSortValueItem(i, aRows[i].fields.byName[oField.name].getValue());
		}
		aFieldVals.sort(sortFunct);

		if (this.sortOrder != "ASC") aFieldVals.reverse();
		
		for (var i=0; i<iRCt; i++)	aRows[aFieldVals[i].index].move(i);

		oField.fireEvent("colsort");

		function class_dynatable_rowSortValueItem(iIndex, vValue)
		{
			this.index = iIndex;
			this.value = vValue;
		}
	}
	prototype.getSortFunct = function(typeName)
	{
		switch (typeName)
		{
			case DTBL_PARSETYPE_NUMBER : return sortByNumber;
			case DTBL_PARSETYPE_DATETIME : return sortByDate;
			case DTBL_PARSETYPE_BOOLEAN : return sortByBoolean;
			case DTBL_PARSETYPE_STRING : 
			default :
				return sortByDefault;
		}	
		function sortByDefault(a, b)
		{
			if (a.value == b.value) return 0;
			return (String(a.value).toLowerCase() > String(b.value).toLowerCase()) ? 1 : -1;
		}
		function sortByBoolean(a, b)
		{
			if (a.value == b.value) return 0;
			return (a.value > b.value) ? 1 : -1;
		}
		function sortByNumber(a, b)
		{
			if (isNaN(a.value) || isNaN(b.value)) return 0;
			return a.value - b.value;
		}
		function sortByDate(a, b)
		{
			var dA = new Date(a.value);
			var dB = new Date(b.value);
			if (dA == dB) return 0;
			return (dA > dB) ? 1 : -1;
		}
	}
}
with (behavior_dynatable_sortRowsDesc = inheritFrom(behavior_dynatable_sortRows))
{
	prototype.name = "rowSorterDescending";
	
	prototype.sortOrder = "DESC";
}

with (behavior_dynatable_inputValueFix = inheritFrom(behavior_dynatable))
{
	prototype.init = function(oRowDef)
	{
		this.reg("activate", this.regHandlers, oRowDef.grid);
	}

	prototype.name = "radioCheckboxFieldFix";
	
	prototype.aVals = null; // Array

	prototype.regHandlers = function (oGrid)
	{
		this.reg("beforerownodemove", this.captureVals);
		this.reg("rownodemove", this.setVals);
	}
	prototype.captureVals = function (oRow)
	{
		this.aVals = new Array();
		var sItem;
		var oField;
		var f = oRow.fields;
		for (sItem in f.byName)
		{
			oField = f.byName[sItem];
			this.aVals[sItem] = oField.getControlValue();
		}
	}
	prototype.setVals = function (oRow)
	{
		for (var sItem in this.aVals)
		{
			oRow.fields.byName[sItem].setControlValue(this.aVals[sItem]);
		}
	}
}

with (behavior_dynatable_removeRow = inheritFrom(behavior_dynatable))
{
	prototype.init = function(oButton)
	{
		this.reg("click", function(oButton) { if (!oButton.domNode.disabled) oButton.row.remove() });
	}
}

with (behavior_dynatable_addRow = inheritFrom(behavior_dynatable))
{
	prototype.init = function(oButton)
	{
		this.reg("click", function(oButton) { if (!oButton.domNode.disabled) oButton.row.grid.addRow() });
	}
}


with (behavior_dynatable_recordset = inheritFrom(behavior_dynatable))
{
	prototype.oldRowIndex = null; // Integer
	prototype.grid = null; // DynaTable
	prototype.sect = null; // DynaSect
	prototype.init = function(oGrid)
	{
		this.reg("activate", this.regHandlers);
		
		this.grid = oGrid;
		this.sect = this.grid.rowItemSect;
	}
	prototype.regHandlers = function (oGrid)
	{
		this.reg("beforerowmove", this.captureRow, this.grid.rowDef);
		this.reg("beforerowdelete", this.captureRow, this.grid.rowDef);
	}
	prototype.captureRow = function (oRow)
	{
		this.oldRowIndex = oRow.getIndex();
	}
}

with (behavior_dynatable_updateCatcher = inheritFrom(behavior_dynatable))
{
	prototype.autoInherit = true;
	prototype.name = "rowItemUpdateCatcher";
	
	prototype.init = function(oRow)
	{
		this.reg("rowitemchange", this.notifyChange);
		this.reg("select", this.checkForChange);
		this.reg("change", this.checkForChange);
		this.reg("blur", this.checkForChange);
		this.reg("focus", this.setOldVal);
		this.reg("instantiate", this.setInitVal);
	}

	prototype.bSkipValueSet = false;
	
	prototype.setInitVal = function (oRow, evt)
	{
		if (!evt.dynatableItem || evt.dynatableItem.prefix != DTBL_PREFIX_FIELD) return;
		evt.dynatableItem.oldValue = null;
	}
	
	prototype.setOldVal = function (oRow, evt)
	{
		if (!evt.dynatableItem || evt.dynatableItem.prefix != DTBL_PREFIX_FIELD) return;
		if (this.bSkipValueSet)
		{
			this.bSkipValueSet = false;
			return;
		}
		evt.dynatableItem.oldValue = evt.dynatableItem.getControlValue();
	}
	prototype.checkForChange = function (oRow, evt)
	{
		var c = evt.dynatableItem;
		if (c.oldValue == null || !c || c.prefix != DTBL_PREFIX_FIELD || !oRow.grid.activated) return;
		var vVal = c.getControlValue();
		if (c.oldValue != vVal) this.notifyChange(oRow, evt);
	}
	prototype.notifyChange = function notifyChange(oRow, evt)
	{
		var c = evt.dynatableItem;
		evt.dynatableValue = c.getValue();
		if (c.fireEvent("beforeupdate", evt)==false) 
		{
			c.fireEvent("cancelupdate", evt);
			return;
		}

		c.fireEvent("update", evt);
		c.oldValue = evt.dynatableValue; // null
	}
}

with (behavior_dynatable_rowKeyCollector = inheritFrom(behavior_dynatable))
{
	prototype.name = "rowKeys";
	prototype.keys = null; // DynaCollection

	prototype.init = function(oGrid)
	{
		this.reg("activate", this.regEvents);
		this.reg("rowpopulate", this.indexRow, oGrid.rowItemSect);
		
		this.keys = new class_dynatable_collection;
		oGrid.keyFieldName = "";
		oGrid.keyValNew = 0;
		oGrid.useKey = null;
		oGrid.keys = this.keys;
	}
	
	prototype.regEvents = function (oGrid)
	{
		oGrid.useKey = (oGrid.keyFieldName.length != 0);
		if (!oGrid.useKey) return;
		
		this.reg("rowinsert", this.addKey, oGrid.rowItemSect);
		this.reg("rowdelete", this.deleteKey, oGrid.rowItemSect);
		this.reg("update", this.updateKey, oGrid.fields.byName[oGrid.keyFieldName]);
	}
	prototype.indexRow = function (oSect, evt)
	{
		this.keys.add(evt.dynatableRow, this.getKeyValue(evt.dynatableRow));
	}
	prototype.addKey = function (oSect, evt)
	{
		var oRow = evt.dynatableRow;
		var keyVal = this.getKeyValue(oRow);
		if (keyVal != this.context.keyValNew) this.keys.add(oRow, keyVal);
	}
	prototype.deleteKey = function (oSect, evt)
	{
		var oRow = evt.dynatableRow;
		var keyVal = this.getKeyValue(oRow);
		if (this.keys.byName[keyVal]) this.keys.remove(keyVal, true);
	}
	prototype.updateKey = function (oField, evt)
	{
		for (var sKey in this.keys.byName)
		{
			if (this.keys.byName[sKey] == oField.row)
			{
				this.keys.remove(sKey, true);
				break;
			}
		}
		this.keys.add(oField.row, evt.dynatableValue);
	}
	prototype.getKeyValue = function (oRow)
	{
		if (!this.context.keyFieldName.length) return;

		return oRow.fields.byName[this.context.keyFieldName].getValue();
	}
}
with (behavior_dynatable_template = inheritFrom(behavior_dynatable))
{
	// NAME THE BEHAVIOR
	prototype.name = ""; // String
	
	prototype.init = function(oContext)
	{
		// REGISTER YOUR EVENTS
		this.reg("click", this.myFunct);
	}
	
	// YOUR MATCHING FUNCTIONS
	prototype.myFunct = function(oContext, evt)
	{

	}
}

