var global = this;	// Provides access to the global scripting namespace from within object methods.

//dx-forms: General -------------------------------------------------------------------------------------------------------------------------

// Page-wide behavior flags ----------------------------------------------------------------------------------------------------------------------------------

// Should mouse-clicks cause full-text selection for dx:forms elements, the way tabbing in does?
window.dxforms_selectOnMouseClick = false

//"Dirty" Form Handling ---------------------------------------------------------------------------------------------------------------------

function setDirty() {
	document.isDirty = true;
}

function setupIsDirtyHandling(frm) {
	for(var i = 0; i < frm.elements.length; i++) {
		var e = frm.elements[i];
		switch(e.type) {
			case 'text':
			case 'textarea':
			case 'checkbox':
			case 'radio':
			case 'select-one':
			case 'select-multiple':
				if(e.onchange && e.onchange != FormElement_onchange) {
					e.FormElement_previousOnChange = e.onchange;
				}
				e.onchange = FormElement_onchange;
				break;
		}	
	}
	frm.isDirty = Form_isDirty;
	frm.resetDirtyFlags = Form_resetDirtyFlags;
}

function FormElement_onchange() {
	setDirty();
	this.isDirty = true;
	if(this.FormElement_previousOnChange) 
		return(this.FormElement_previousOnChange()); 
}

function Form_isDirty() {
	if(this.dirty) return(true);
	for(var i = 0; i < this.elements.length; i++) {
		var e = this.elements[i];
		e.blur();
		switch(e.type) {
			case 'text':
			case 'textarea':
			case 'checkbox':
			case 'radio':
			case 'select-one':
			case 'select-multiple':
				if(e.isDirty) return(true);
				break;
			case 'file':
				if(e.value != '') return(true);
				break;
			case 'hidden':
				if(e.isDirty) return(true);
				break;
		}	
	}
	return(false);
}

function Form_resetDirtyFlags() {
	this.dirty = false;
	for(var i = 0; i < this.elements.length; i++) {
		var e = this.elements[i];
		switch(e.type) {
			case 'text':
			case 'textarea':
			case 'checkbox':
			case 'radio':
			case 'select-one':
			case 'select-multiple':
				e.isDirty = false; 
				break;
		}	
	}
}

//JS Classes -------------------------------------------------------------------------------------------------------------------------

function setupJSClasses(rootElement, force) {
	setupJSClassForElements(rootElement.getElementsByTagName("FORM"), force);
	setupJSClassForElements(rootElement.getElementsByTagName("INPUT"), force);
	setupJSClassForElements(rootElement.getElementsByTagName("TEXTAREA"), force);
	setupJSClassForElements(rootElement.getElementsByTagName("SPAN"), force);
	setupJSClassForElements(rootElement.getElementsByTagName("DIV"), force);
	setupJSClassForElements(rootElement.getElementsByTagName("SELECT"), force);
	setupJSClassForElements(rootElement.getElementsByTagName("TABLE"), force);
	setupJSClassForElements(rootElement.getElementsByTagName("IFRAME"), force);
}

function setupJSClassForElements(elements, force) {
	for(var c = 0; c < elements.length; c++) {
		var elm = elements[c];
		if(!elm._JSClasses_Processed || force) {
			elm._JSClasses_Processed = true;
			var jsClass = elm.getAttribute("js-class");
			if(jsClass) {
				this[jsClass].call(elm);
				if(elm.initialize) elm.initialize();
			}
		}
	}
}

function showObject(id, element) {
	var elm = (element ? element : getObjectById(id));
	
	if(document.layers)
		elm.visibility = "show";
	else
		elm.style.display = "inline";
}

function hideObject(id, element) {
	var elm = (element ? element : getObjectById(id));
	
	if(document.layers)
		elm.visibility = "hide";
	else
		elm.style.display = "none";
}


function getObjectById(id) {
	if(document.layers) {
		if(document.layers[id])
			return document.layers[id];
		else if(document.anchors[id])
			return document.anchors[id];
	}
	else if(document.all)
		return document.all[id];
	else if(document.getElementById)
		return document.getElementById(id);
 	else
		return false;
}


function isVisible(elm) {
	while(elm != null && elm.nodeName != "#document") {
		if(elm.style.visibility == "hidden" || elm.style.display == "none")
			return(false);
		elm = elm.parentNode;
	}
	return(true);
}

function isChild(elm, parentElm) {
	while(elm && elm.nodeName != "#document" && elm.parentNode) {
		if(elm.parentNode == parentElm)
			return(true);
		else
			elm = elm.parentNode;
	}
}

function findParentByTagName(elm, parentTagName) {
	parentTagName = parentTagName.toUpperCase();
	while(elm && elm.parentNode && elm.parentNode.tagName) {
		if(elm.parentNode.tagName.toUpperCase() == parentTagName)
			return(elm.parentNode);
		else
			elm = elm.parentNode;
	}
}

function findChildElement(context, comparisonFunction) {
	var allChildren = context.getElementsByTagName("*");
	
	for(var counter = 0; counter < allChildren.length; counter++) {
		var child = allChildren[counter];
		if(comparisonFunction.call(child)) {
			return(child);
		}
	}
}

function getPreviousElement(elm) {
	previousElement = elm.previousSibling;
	while(previousElement && previousElement.nodeType != 1) {
		previousElement = elm.previousSibling;
	}
	return(previousElement);
}

function getNextElement(elm) {
	nextElement = elm.nextSibling;
	while(nextElement.nodeType != 1) {
		nextElement = elm.nextSibling;
	}
	return(nextElement);
}

function GetElementPosition(id, elm) {
	var position;
	
	if(browserName == "IE") {
		position = ie_GetElementPosition(id, elm);
	}
	else {
		position = ns_GetElementPosition(id, elm);
	}
	return(position);
}

function ie_GetElementPosition(id, elm, compensateForScrolling)  {
	var position = new Object;
	
	var elem = (elm ? elm : document.getElementById(id));
	if(elem) {
		position.x = getOffsetLeft(elem, compensateForScrolling);
		position.y = getOffsetTop(elem, compensateForScrolling);
		return(position);
	}
	else {
		return(null);
	}
}

function getOffsetLeft (el, compensateForScrolling) {
	var ol = el.offsetLeft;
	if (compensateForScrolling) ol -= el.scrollLeft;
	while (el.currentStyle.position != "absolute" && (el = el.offsetParent) != null) {
		ol += el.offsetLeft;
		if (compensateForScrolling) ol -= el.scrollLeft;
	}
	return ol;
}

function getOffsetTop (el, compensateForScrolling) {
	var ot = el.offsetTop;
	if (compensateForScrolling) ot -= el.scrollTop;
	while(el.currentStyle.position != "absolute" && (el = el.offsetParent) != null) {
		ot += el.offsetTop;
		if (compensateForScrolling) ot -= el.scrollTop;
	}
	return ot+document.body.scrollTop;
}


function ns_GetElementPosition(id, elm)  {
	var position = new Object;

	var elem = (elm ? elm : document.anchors[id]);
	position.x = elem.x;
	position.y = elem.y;

	return(position);
}

function getX(elm) {
	var iReturnValue = 0;
	while(elm != null) {
		iReturnValue += elm.offsetLeft;
		elm = elm.offsetParent;
		if(elm && elm.scrollLeft) {
			iReturnValue -= elm.scrollLeft;
		}
	}
	return iReturnValue;
}

function getY(elm) {
	var iReturnValue = 0;
	while(elm != null) {
		iReturnValue += elm.offsetTop;
		elm = elm.offsetParent;
		if(elm && elm.scrollTop) {
			iReturnValue -= elm.scrollTop;
		}
	}
	return iReturnValue;
}




// Spreadsheet-like functionality
// There is only ever one instance of the dxSpreadsheet class on a given page,
// (held in the 'Spreadsheet' global variable). It is *not* attached to any particular
// page element - while spreadsheet functionality is often used in conjunction with
// editable data-tables, neither requires or is required by the other.
// The dxSpreadsheet object simply looks for any valid elements (INPUT, DIV, SPAN) that 
// have a 'formula' attribute and 'recalc' function, and performs recalculations on them as necessary.
// NOTE: The 'recalc' function is automatically set up for dx: controls, in the dxControl()
// constructor below.
function dxSpreadsheet() {
	this.formulaContextStack = new Array(); // Call stack containing the currently executing formula.

	this.recalcMode = 0;	// 0 = Push (control edit causes recalc of dependent formula fields)
										// 1 = Pull (Formula fields are calculated recursively)
	
	this.formulaMode = 0;	// 0 = Calculate (Formulas are calculated and the results are displayed).
										// 1 = Get dependencies (Formulas are executed only to discover what fields should trigger recalculation.)
	
	this.recalcID = 0;		// Identifies the current full recalc iteration.  This is used to prevent fields from running their formulas more than once per calc.
	
	this.formulaControls = new Object;  // Collection of fields that actually have formulas.

	this.setupFormulaDependencies = dxSpreadsheet_setupFormulaDependencies;
	this.queueRecalc = dxSpreadsheet_queueRecalc;
	this.recalcAll = dxSpreadsheet_recalcAll;
}

function dxSpreadsheet_setupFormulaDependencies(rootElement) {
	this.formulaMode = 1;
	/* first, set up all the formulas for the new elements */
	
	var validFormulaElements = Array("INPUT", "DIV", "SPAN");
	
	for(var i = 0; i < validFormulaElements.length; i++) {
		var elements = rootElement.getElementsByTagName(validFormulaElements[i]);
		for(var c = 0; c < elements.length; c++) {
			var elm = elements[c];
			if(elm.formula && elm.recalc) {
				elm.recalc();
			}
		}
	}
	/* then, see how the new elements interact with existing formulas */
	this.formulaMode = 0;
}

function dxSpreadsheet_queueRecalc(needsRecalcOnly) {
	if(this.recalcTimer) {
		clearTimeout(this.recalcTimer);
	}
	this.recalcTimer = setTimeout("Spreadsheet.recalcAll(" + (needsRecalcOnly ? "true" : "") + ");", 1);     //if(Spreadsheet.onRecalcTimer) clearTimeout(Spreadsheet.onRecalcTimer); Spreadsheet.onRecalcTimer = setTimeout('Spreadsheet.onrecalc()', 100);
}

function dxSpreadsheet_recalcAll(needsRecalcOnly) {
	if(needsRecalcOnly) this.recalcMode = 1;
	this.recalcID ++;
	
	for(var formulaID in this.formulaControls) {
		var elm = this.formulaControls[formulaID];
		
		if(elm) {
			try {
				if(elm.sourceIndex <= 0) {
					elm.formula = null;
					elm.formulaFunction = null;
					elm.dependents = null;
					this.formulaControls[formulaID] = null;
				}
			}
			catch(e) {
				this.formulaControls[formulaID] = null;
				elm = null;
			}
			if(elm && elm.formula && elm.recalc) {
				if(!needsRecalcOnly || elm.needsRecalc) {
					elm.needsRecalc = false;
					elm.recalc();
				}
			}
		}
	}
	if(needsRecalcOnly) this.recalcMode = 0;
}



// Note: by design, there is only every one Spreadsheet object on a page,
// handling *all* formulae for that page. See comments above dxSpreadsheet().
var Spreadsheet;
function initSpreadsheet(standalone) {
	if(standalone) {
		Spreadsheet = new dxSpreadsheet();
	}
	else {
		Spreadsheet = window.top.Spreadsheet;
		if(!Spreadsheet) {
			Spreadsheet = window.top.Spreadsheet = new dxSpreadsheet();
		}
	}

	if(Spreadsheet.initTimer)
		clearTimeout(Spreadsheet.initTimer);

	Spreadsheet.initTimer = setTimeout("\
			Spreadsheet.setupFormulaDependencies(document); \
			Spreadsheet.recalcAll(); \
			Spreadsheet.initTimer = null; \
		", 1);
}

// Returns the sum of the values in a control or array of controls.
function SUM() {
	var total = 0;
	
	for(var c = 0; c < arguments.length; c++) {
		var elm = arguments[c];
		if(elm) {
			if(elm.getValue) {  // elm has a getValue method, so it's a single element and not an array.
				var val = parseFloat(elm.getValue());
				if(!isNaN(val)) {
					total += val;
				}
			}
			else {	// elm is an array.
				var length = elm.length;
				if(length) {
					var total = 0;
					for(var c = 0; c < length; c++) {
						var val;
						if(elm[c]) {
							if(elm[c].getValue)
								val = parseFloat(elm[c].getValue());
							else if(elm[c].value)
								val = parseFloat(elm[c].value);
							else
								val = parseFloat(elm[c]);
							if(!isNaN(val)) {
								total += val;
							}
						}
					}
				}
			}
		}
	}
	return(parseFloat(total));
}


// Returns the sum of the values for controls that meet the specified condition.
function SUMIF(aValues, sCondition) {
	var total = 0;
	
	var elm = aValues;
	if(elm) {
		if(elm.getValue) {  // elm has a getValue method, so it's a single element and not an array.
			if(testCondition(elm, sCondition)) {
				var val = parseFloat(elm.getValue());
				if(!isNaN(val)) {
					total += val;
				}
			}
		}
		else {	// elm is an array.
			var length = elm.length;
			if(length) {
				var total = 0;
				for(var c = 0; c < length; c++) {
					if(testCondition(elm[c], sCondition)) {
						var val = parseFloat(elm[c].getValue());
						if(!isNaN(val)) {
							total += val;
						}
					}
				}
			}
		}
	}
	return(parseFloat(total));
}

// Returns the count of controls that meed the specified condition.
function COUNTIF(aValues, sCondition) {
	var total = 0;
	
	var elm = aValues;
	if(elm) {
		if(elm.getValue) {  // elm has a getValue method, so it's a single element and not an array.
			if(testCondition(elm, sCondition)) {
				total ++;
			}
		}
		else {	// elm is an array.
			var length = elm.length;
			if(length) {
				var total = 0;
				for(var c = 0; c < length; c++) {
					if(testCondition(elm[c], sCondition)) {
						total ++;
					}
				}
			}
		}
	}
	return(total);
}

// Returns the count of the controls.
function COUNT() {
	var total = 0;
	
	for(var c = 0; c < arguments.length; c++) {
		var elm = arguments[c];
		if(elm) {
			if(elm.getValue) {  // elm has a getValue method, so it's a single element and not an array.
				total++;
			}
			else {	// elm is an array.
				total += elm.length;
			}
		}
	}
	return(total);
}

function EXISTS(elements, sCondition) {
	if(elements.tagName) {
		return(testCondition(elements, sCondition));
	}
	else {
		for(var c = 0; c < elements.length; c++) {
			if(testCondition(elements[c], sCondition)) 
				return(true);
		}
	}
	return(false);
}

function testCondition(element, sCondition) {
	with(element) {
		return(eval(sCondition));
	}
}

// UI CONTROL ACCESS ----------------------------------------------------------------------------------------------------------------------------------


// INPUT[@type='text'] ----------------------------------------------------------------------------------------------------------------------------------
function TextInput () {
	this.setControlValue = TextInput_setControlValue;
	this.getControlValue = TextInput_getControlValue;
	this.generateFormInput = TextInput_generateFormInput;
	this.setControlStatic = TextInput_setControlStatic;
}

function TextInput_setControlValue(value) {
	this.value = value;
}
function TextInput_getControlValue() {
	return(this.value);
}

function TextInput_generateFormInput(dest, prefix) {
	var inp = document.createElement("input");
	inp.setAttribute("type", "hidden");
	inp.setAttribute("name", (prefix ? prefix : "") + (this.name ? this.name : this.id));
	inp.value = this.getValue();
	dest.insertAdjacentElement("BeforeEnd", inp);
}

// Tested only on IE. Currently used in Admin only.
function TextInput_setControlStatic(flag, staticClass) {
	if (flag) {
		this.readOnly = true;
		this.StaticControl = this;
		if (!this.nonStaticClassName) this.nonStaticClassName = this.className;
		if (staticClass) this.className = staticClass;
	} else {
		this.readOnly = false;
		this.StaticControl = null;
		if (this.nonStaticClassName) {
			this.className = this.nonStaticClassName;
			this.nonStaticClassName = null;
		}
	}
};

// DROP DOWN SELECT ----------------------------------------------------------------------------------------------------------------------------------
function DropDownSelect () {
	this.setControlValue = DropDownSelect_setControlValue;
	this.getControlValue = DropDownSelect_getControlValue;
	this.getText =DropDownSelect_getText;
	this.DropDownSelect_base_onclick = this.onclick;
	this.onclick = DropDownSelect_onclick;
	this.generateFormInput = DropDownSelect_generateFormInput;
	
	// Tested only on IE. Currently used in Admin only.
	this.setControlStatic = setControlStatic_swap;
}

function DropDownSelect_setControlValue(value, desc) {
	// Search values.
	for(var counter = 0; counter < this.options.length; counter ++) {
		if(this.options[counter].value == value) {
			this.selectedIndex = counter;
			return;
		}
	}
	
	// Search text descriptions.
	for(var counter = 0; counter < this.options.length; counter ++) {
		if(this.options[counter].text == value) {
			this.selectedIndex = counter;
			return;
		}
	}
	
	// Not found. Create new option.
	if (desc == null) desc = '';
	var opt = document.createElement('option');
	opt.setAttribute('value', value);
	opt.text = desc;
	this.insertAdjacentElement('afterBegin', opt);
	this.selectedIndex = 0;
}

function DropDownSelect_getControlValue() {
	if (this.selectedIndex < 0) {
		return '';
	} else {
		return(this.options[this.selectedIndex].value);
	}
}

function DropDownSelect_getText() {
	//Call getValue so that the spreasheet stuff takes place.
	var val = this.getValue();
	return(this.options[this.selectedIndex].text);
}

function DropDownSelect_onclick() {
	this.updateDependents();
	if(this.DropDownSelect_base_onclick) this.DropDownSelect_base_onclick();
}

function DropDownSelect_generateFormInput(dest, prefix) {
	var inp = document.createElement("input");
	inp.setAttribute("type", "hidden");
	inp.setAttribute("name", (prefix ? prefix : "") + (this.name ? this.name : this.id));
	inp.value = this.getValue();
	dest.insertAdjacentElement("BeforeEnd", inp);
}


// Generic HTML Element ----------------------------------------------------------------------------------------------------------------------------------
function GenericElement() {
	this.setControlValue = GenericElement_setControlValue;
	
	this.getControlValue = GenericElement_getControlValue;

	this.initialize = GenericElement_initialize;
	
	if(this.contentEditable == "true") {
		this.GenericElement_base_onkeydown = this.onkeydown;
		this.onkeydown = GenericElement_onkeydown;
		
		this.GenericElement_base_onmousedown = this.onmousedown;
		this.onmousedown = GenericElement_onmousedown;
	
		this.GenericElement_base_onfocus = this.onfocus;
		this.onfocus = GenericElement_onfocus;
		this.GenericElement_base_onblur = this.onblur;
		this.onblur = GenericElement_onblur;
	}
	
	//This method allows non-form elements to be submitted as inputs.
	//It must be called explicitly before a form is submitted.
	this.generateFormInput = GenericElement_generateFormInput;
	
	// Tested only on IE. Currently used in Admin only.
	this.setControlStatic = setControlStatic_swap;
}

function GenericElement_setControlValue(value) {
	var elm = document.createElement("div");
	elm.insertAdjacentText("beforeEnd", value);
	this.innerHTML = elm.innerHTML;
	
	var table = findParentByTagName(this, "TABLE");
	if(table) {
		var layout = table.style.tableLayout;
		if(layout != "fixed") {
			table.style.layout = "fixed";
			table.style.layout = layout;
		}		
	}
}

function GenericElement_getControlValue(){
	if(this.innerText)
		return(this.innerText);
	else if(this.childNodes.length > 0)
		return(this.childNodes[0].nodeValue);
	else 
		return("");
}

function GenericElement_initialize() {
	if(this.getAttribute("value")) {
		if(this.getControlValue() == "") {
			this.setValue(this.getAttribute("value"));
		}
	}
}
	
function GenericElement_onkeydown() {
	if(event.keyCode == 13) {
		return(false);
	}
}

function GenericElement_onmousedown() {
	this.mouseFocus = true;
	if(this.GenericElement_base_onmousedown) this.GenericElement_base_onmousedown();
}

function GenericElement_onfocus() {
	this.currentValue = this.innerHTML;
	// If we navigated to the control via a method which should select all contents (defaults to non-mouse, but could be either), do so
	if((!this.mouseFocus || window.dxforms_selectOnMouseClick)) {
		// The document.selection.type check avoids obscure errors when clicking back from another window into a 
		// different frame than the one containing the control with the focus but no selection; see comments in dx-forms-admin.js:dxSmartSearchDiv_onfocus().
		var sel = document.selection.type != 'None' ? document.selection.createRange() : document.body.createTextRange(); 
		sel.moveToElementText(this); 
		sel.select(); 
	}
	if(this.GenericElement_base_onfocus) this.GenericElement_base_onfocus();
}

function GenericElement_onblur() {
	this.mouseFocus = false;
	if(this.innerHTML != this.currentValue) {
		setDirty();
		if(this.onchange) this.onchange();
	}
	if(this.GenericElement_base_onblur) this.GenericElement_base_onblur();
}
	
//This method allows non-form elements to be submitted as inputs.
//It must be called explicitly before a form is submitted.
function GenericElement_generateFormInput(dest, prefix) {
	var inp = document.createElement("input");
	inp.setAttribute("type", "hidden");
	inp.setAttribute("name", (prefix ? prefix : "") + (this.name ? this.name : this.id));
	inp.value = this.getValue();
	dest.insertAdjacentElement("BeforeEnd", inp);
}

// Tested only on IE. Currently used in Admin only.
function setControlStatic_swap(flag, staticClass) {
	if (flag) {
		var ctlStatic = document.getElementById('_static_' + this.id);
		if (!ctlStatic) {
			// Create static DIV to hide the dropdown.
			ctlStatic = document.createElement("div");
			if (staticClass) ctlStatic.className = staticClass;
			ctlStatic.id = '_static_' + this.id;
			this.insertAdjacentElement('afterEnd', ctlStatic);
		}
		
		// Hide dropdown.
		ctlStatic.style.display = 'inline';
		this.style.display = 'none';
		this.StaticControl = ctlStatic;
	} else {
		this.style.display = 'inline';
		if (this.StaticControl) {
			this.StaticControl.style.display = 'none';
			this.StaticControl = null;
		}
	}
}

// BASE CLASS FOR DX:CONTROLS ----------------------------------------------------------------------------------------------------------------------------------

function dxControl() {
	//Setup the DHTML control interface.
	switch(this.tagName.toUpperCase()) {
		case "INPUT":
			TextInput.call(this);
			break;
			
		case "SELECT":
			DropDownSelect.call(this);
			break;
			
		default:
			GenericElement.call(this);	
	}
	
	if(this.onchange) {
		if(typeof(this.onchange) == "string") {
			this.onchange = new Function(this.onchange);			
		}
	}

	this.setValue = dxControl_setValue;
	this.getValue = dxControl_getValue;
	this.formatValue = dxControl_formatValue;
	this.unFormatValue = dxControl_unFormatValue;
	this.setVisible = dxControl_setVisible;
	
	this.dxControl_base_onfocus = this.onfocus;
	this.onfocus = dxControl_onfocus;
	
	this.dxControl_base_onblur = this.onblur;
	this.onblur = dxControl_onblur;
	
	/* Spreadsheet Functionality */
	this.dependents = new Object;
	if(this.formula) {
		this.recalc = dxControl_recalc;
	}
	this.updateDependents = dxControl_updateDependents;
	this.setStatic = dxControl_setStatic;
	this.setEnabled = dxControl_setEnabled;
	this.isStatic = dxControl_isStatic;
	this.toXml = dxControl_toXml;
	this.preserveRawValue = this.getAttribute("preserve-raw-value");
}

function dxControl_setValue(value, forceUpdateDependents) {
	if(this.preserveRawValue) {
		before = this.rawValue;
		after = value;
		this.rawValue = value;
		if(this.hiddenRawValueInput) {
			var inp = this.hiddenRawValueInput();
			if(inp) inp.value = value;
		}
		this.setControlValue(this.formatValue(value));
	}
	else {
		var before = this.getControlValue();
		this.setControlValue(this.formatValue(value));
		var after = this.getControlValue();
	}
	if(before != after || forceUpdateDependents) {
		this.updateDependents();
	}
	if(before != after) {
		if(this.onchange) this.onchange();
	}
}

function dxControl_getValue() {
	if(Spreadsheet) {
		//Add the context as a dependent of this field (since it requested the value of this field).
		var context = Spreadsheet.formulaContextStack[Spreadsheet.formulaContextStack.length -1];
		if(context) {
			if(!context.formulaID) 
				context.formulaID = getUniqueID();
			if(!this.dependents[context.formulaID]) 
				this.dependents[context.formulaID] = context;
		}
	}
	
	if(this.formula && Spreadsheet.recalcMode == 1) {
			if(this.recalcID != Spreadsheet.recalcID) {
				this.recalcID = Spreadsheet.recalcID;
				this.recalc(); 
			}
	}

	var val = (this.preserveRawValue ? (this.rawValue == undefined ? "" : this.rawValue) : this.unFormatValue(this.getControlValue()));
	if(val == "" && this.getAttribute("save-blank-as")) {
		val = this.getAttribute("save-blank-as");
	}
	return(val);
}
function dxControl_formatValue(value) { return(value); }
function dxControl_unFormatValue(value) { return(value); }

function dxControl_setVisible(boolFlag) { this.style.visibility = (boolFlag ? 'visible' : 'hidden') };

function dxControl_onfocus() {
	this.previousValue = this.getControlValue();
	if(this.dxControl_base_onfocus) this.dxControl_base_onfocus();
}

function dxControl_onblur() {
	// For now, disable formatting and dependency checks for multi-select boxes.
	if (this.tagName.toUpperCase() != 'SELECT' || !this.multiple) {
		var ctlValue = this.getControlValue();
		if(ctlValue != this.previousValue) {
			var value = this.unFormatValue(ctlValue);
			this.setValue(value);
		}
	}
	if(this.dxControl_base_onblur) this.dxControl_base_onblur();
}

function dxControl_recalc() {
	if(Spreadsheet) {
		if(!this.formulaFunction) {
			if(typeof(global[this.formula]) == "function") 
				this.formulaFunction = global[this.formula];
			else 
				this.formulaFunction = new Function("return(" + this.formula + ");");
		}
		
		if(!this.formulaID) 
			this.formulaID = this.uniqueID;
		if(!Spreadsheet.formulaControls[this.formulaID])
			Spreadsheet.formulaControls[this.formulaID] = this;
		
		Spreadsheet.formulaContextStack.push(this);

		var value;
		value = this.formulaFunction();

		if(Spreadsheet.formulaMode == 0) {
			this.setValue(value);
		}

		Spreadsheet.formulaContextStack.pop();
	}
}

function dxControl_updateDependents() {
	if(Spreadsheet) {
		if(Spreadsheet.recalcMode == 0) {
			if(this.dependents) {
				for(var id in this.dependents) {
					var fld = this.dependents[id];
					if(fld && (fld.sourceIndex <= 0 || !fld.formula)) {
						this.dependents[id] = null;
						fld = null;
					}
					if(fld) {
						fld.needsRecalc = true;
						Spreadsheet.queueRecalc();
					}
					fld = null;
				}
			}
		}
	}
}

function dxControl_setEnabled(enabled) {
	this.disabled = !enabled;  //duh!
}

function dxControl_isStatic() {
	return(this.static == "1" || this.static == "yes" || this.static == "true");
}

function dxControl_setStatic(flag, staticVal) {
	this.static = (flag ? "1" : "0");
	this.setControlStatic(flag, 'dx-static');
	
	if (flag) {
		// Already set control to static mode, now set value to static value.
		if(staticVal != null) {
			if (this.nonStaticValue == null) this.nonStaticValue = this.getValue();
		}
		if (this != this.StaticControl) {
			var staticDisplayVal;
			if(staticVal != null) {
				staticDisplayVal = staticVal;
			}
			else {
				staticDisplayVal = this.getValue();
			}
			if (this.StaticControl.setValue) {
				this.StaticControl.setValue(staticDisplayVal);
			} else {
				this.StaticControl.innerText = staticDisplayVal;
			}
		}
	} else {
		// Restore non-static value.
		if (this.nonStaticValue != null) {
			this.setValue(this.nonStaticValue);
			this.nonStaticValue = null;
		}
	}
};


function dxControl_toXml() {
	var name = (this.name ? this.name : this.id);
	var tagName = (name ? name : this.tagName);
	var value = this.getValue();
	
	return(XMLEncodeElement(tagName, value.toString(), true));
}


// DX:FORM ----------------------------------------------------------------------------------------------------------------------------------
// Adds extra methods to a standard HTML Form for converting its data to XML.
// ------------------------------------------------------------------------------------------------------------------------------------------
function dxForm() {
	// Call this method to obtain an XML representation of the form's contents.
	this.toXml = dxForm_toXml;
	this.getFiles = dxForm_getFiles;
	this._reset = this.reset;
	this.reset = dxForm_reset;
	this._onreset = this.onreset;
	this.onreset = dxForm_onreset;
	setupIsDirtyHandling(this);
}

// Returns an array of dxFile controls.
function dxForm_getFiles() {
	var files = [];
	var inputs = this.getElementsByTagName("input");
	for(var i = 0; i < inputs.length; i++) {
		var input = inputs[i];
		if(input.getAttribute("js-class") == "dxFile" && input.value != "") {
			files.push(input);
		}
	}
	return files;
}

// Returns form data as XML.
function dxForm_toXml() {
	// Clear the sources, in case stuff has been left over from a previous run.
	dxForm_Sources = new Object();
	
	var aXML = new Array();
	// Create the form element's opening tag, supplying the name attribute.
	aXML.push("<Form " + XMLEncodeAttribute("name", this.name) + (document.isDirty ? XMLEncodeAttribute("dirty", "yes") : "") + ">");
	
	// Recursively search for elements that can be converted to XML.
	aXML.push(dxForm_recurseToXML(this));
	
	// Build the XML for the sources.
	for(var key in dxForm_Sources) {
		var source = dxForm_Sources[key];
		aXML.push("<" + XMLEncodeName(source.name) + XMLEncodeAttribute("id", source.id) + ">");
		aXML.push(source.join(""));
		aXML.push("</" + XMLEncodeName(source.name) + ">");
	}
	
	// Create the form element's closing tag.
	aXML.push("</Form>");
	
	// Wipe out the sources.  We don't need them anymore.
	dxForm_Sources = new Object();

	// Merge the array into a single string.
	return(aXML.join(""));
}

// The dxForm_Sources variable is a global hash table that is used during the recursive
// construction of page XML.  Elements that have a "source" attribute (and an optional 
// sourceID attribute) will be grouped by the specified source rather than appearing in 
// straight document order, when rendering the XML.
var dxForm_Sources = new Object();
function dxForm_getSource(name, id) {
	var key = name + (id ? "_" + id : "");
	var source = dxForm_Sources[key];
	if(!source) {
		source = new Array();
		source.id = id;
		source.name = name;
		dxForm_Sources[key] = source;
	}
	return(source);
}

// This function recursively searches for elements that can be converted to XML.
// It is also used by the dxElement and dxFrame controls.
function dxForm_recurseToXML(parent) {
	var aXML = new Array();
	if (parent) {
		var children = parent.childNodes;
		// For each child node...
		for(var i = 0; i < children.length; i++) {
			var elm = children[i];
			
			//If it's an element...
			if(elm.nodeType == 1) {
				// If it has its own toXml method...
				if(elm.toXml) {
					// If the element has a source...
					if(elm.source) {
						var source = dxForm_getSource(elm.source, elm.sourceID);
						source.push(elm.toXml());
					}
					else {
						aXML.push(elm.toXml());
					}
				}
				else {
					// Otherwise, recurse and look for other elements.
					aXML.push(dxForm_recurseToXML(elm));
				}
			}
		}
	}
	// Join the array into a single string.
	return(aXML.join(""));
}

function dxForm_reset() {
	this._reset();
	this.onreset();
}

function dxForm_onreset() {
	var elements = this.getElementsByTagName("DIV");
	for(var i = 0; i < elements.length; i++) {
		var elm = elements[i];
		if(elm.setValue) {
			elm.setValue("");
		}
	}
	if(this._onreset) this._onreset();
}


// DX:ELEMENT CONTROL -------------------------------------------------------------------------------------------------------------------------------
// A dx:element control wraps form data inside an explicit XML element when the form is converted to XML (via the toXml method).
// --------------------------------------------------------------------------------------------------------------------------------------------------
function dxElement() {
	this.toXml = dxElement_toXml;
}

function dxElement_toXml() {
	var aXML = [];
	var elementName = this["element-name"];
	elementName = (elementName ? elementName : this.name);
	elementName = (elementName ? elementName : this.id);
	elementName = (elementName ? elementName : this.tagName.toLowerCase());
	// Create the element's open tag, using the name specified by the element-name attribute, or the tagName if not specified.
	aXML.push("<" + elementName + ">");
	
	// Recursively search for child elements that can be converted to XML.
	aXML.push(dxForm_recurseToXML(this));
	
	// Create the element's closing tag.
	aXML.push("</" + elementName + ">");
	
	// Merge the array into a single string.
	return(aXML.join(""));
}




// UTILITY FUNCTIONS ----------------------------------------------------------------------------------------------------------------------------------

var uniqueIDCounter = 0;

// Returns an ID that can be used to uniquely identify an item on a page.  Not guaranteed to be globally unique.
function getUniqueID() {
	uniqueIDCounter ++;
	return("dx_" + (new Date).valueOf().toString(32) + uniqueIDCounter.toString(32));
}

// Returns any control reference as a control array.
// Including if there are no elements, or only one.
function getControlArray(elements) {
	var aControls = new Array();
	if(elements) {
		if(!elements.tagName) {
			return(elements);
		}
		else {
			aControls.push(elements);
		}
	}
	return(aControls);
}

// CalcOverride control ---------------------------------------------------------------------------------------------------------------------------------
function toggleCalcOverrideDisplay(objCalcOverrideCheckbox, strOnChange) {
	// toggle the static and editable versions of the control
	var checkboxName = objCalcOverrideCheckbox.getAttribute("name");
	var calcOverrideName = checkboxName.substr(3); // name of the calcoverride textbox
	document.getElementById(calcOverrideName).style.display = (document.getElementById(checkboxName).checked) ? "block" : "none";
	if (!document.getElementById(checkboxName).checked) document.getElementById(calcOverrideName).setValue('');
	document.getElementById(calcOverrideName + '_static').style.display = (document.getElementById(checkboxName).checked) ? "none"  : "block";
	
	// if name of the onchange event is supplied, run onchange here
	if (strOnChange) {
		if (trim(strOnChange) != "") {
			eval(strOnChange);
		}
	}
	
}

