/******************************************************************************
 qForm JavaScript API

 Author: Dan G. Switzer, II
 Date:   December 10, 2000
 Build:  200

 Description:
 This library provides a API to forms on your page. This simplifies retrieval
 of field values by providing methods to retrieve the values from fields,
 without having to do complicate coding.

 To contribute money to further the development of the qForms API, see:
 http://www.pengoworks.com/qForms/donations/

 GNU License
 ---------------------------------------------------------------------------
 This library provides common methods for interacting with HTML forms
 Copyright (C) 2001  Dan G. Switzer, II

 This library is free software; you can redistribute it and/or
 modify it under the terms of the GNU Lesser General Public
 License as published by the Free Software Foundation; either
 version 2.1 of the License, or (at your option) any later version.

 This library 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
 Lesser General Public License for mser details.
 You should have received a copy of the GNU Lesser General Public
 License along with this library; if not, write to the Free Software
 Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
******************************************************************************/

/******************************************************************************
 Generic JS extensions
******************************************************************************/
function __xQ(){
	this.instances=0;
	this.objects = {};
}
xQAPI = new __xQ();

// queueing mechanism
function xQ(){
	// queued items
	this.items = [];
	// is queue async or sync
	this.async = false;
	// the timer id
	this.tId = null;
	// the time to recheck queue for items
	this.delay = 100;
	this.id = ++xQAPI.instances;
	xQAPI.objects[this.id] = this;
	this.pointer = "xQAPI.objects['" + this.id + "']";
}

xQ.prototype.add = function (s){
	this.items.unshift(new Function(s));
}

xQ.prototype.prepend = function (s){
	this.items.push(new Function(s));
}

xQ.prototype.exec = function (_i, _e, _k){
	var i = _param(_i, 0, "number"), e = _param(_e, this.items.length, "number"), k = _param(_k, false, "boolean"), r;
	i = this.items.length - i - 1;
	e = this.items.length - e - 1;

	if( this.async ){
		for( var j=i; j > e; j-- ) setTimeout(this.pointer + ".process(" + j + ", " + k + "," + (j == e+1) + ")", 1);
	}	else {
		for( var j=i; j > e; j-- ){
			r = this.items[j]();
			if( _t(r, "boolean") && !r ) break;
		}
		if( k ) this.clear(i, e);
		this.onProcessed();
	}

	if( _t(r, "boolean") ) return r;
	else return;
}

xQ.prototype.process = function (i, d, l){
	this.items[i]();
	if( d ) this.items.splice(i, i+1);
	if( !!l ) this.onProcessed();
}

xQ.prototype.flush = function (){
	this.exec(null, null, true);
}

xQ.prototype.onProcessed = function (){ return true; }

xQ.prototype.clear = function (_i, _e){
	if( this.items.length == 0 ) return false;
	var i = _param(_i, 0, "number"), e = _param(_e, this.items.length, "number");
	i = this.items.length - i - 1;
	e = this.items.length - e - 1;
	this.items.splice(i, e);
	return true;
}

xQ.prototype.start = function (){
	this.flush();
	this.tId = setTimeout(this.pointer + ".start()", this.delay);
}

xQ.prototype.end = function (){
	clearTimeout(this.tId);
}

/******************************************************************************
 qForm API Initialization
******************************************************************************/
// define _q object
function _q(){
	// build number
	this.version = "200";
	// does the browser support getElementById method
	this.w3c = (document.getElementById)
	// # of qForm instances
	this.instances = 0;
	// hold references to all the qForm objects
	this.objects = {};
	// path where the external components are found
	this.librarypath = "";
	// default modules to load when the wildcard ("*") is specified
	this.modules = ["field", "functions", "validation"];
	// queue items
	this.queue = {
		"qForm": {
			"oninit": []
		},
		"Field": {
			"oninit": []
		}
	};
	// name of the modules that have been loaded, libraries will not be loaded more then once
	this.packages = {};
	// list of validators that are availabe
	this.validators = {};
	// this contains a list of the original contents of a container,
	// when the setValue() method is used on a container, then the
	// containers object is checked to see if the key exists
	this.containers = {};
	// background color style to use when a form field validation
	// error has occurred
	this.errorColor = "red";
	// this specifies whether or not to use error color coding
	// (by default browser that support it use it)
	this.useErrorColorCoding = (document.all || this.w3c);
	// specifies whether all qForm objects should be validated
	// upon a form submission, or just the form being submitted. By default
	// only the form being submitted is validated.
	this.validateAll = false;
	// specifies whether or not a form can be submitted if validation
	// errors occurred. If set to false, the user gets an alert box, if set to true, the user receives a confirm box.
	this.allowSubmitOnError = false;
	// place holder for the number of custom validators that
	// have been initialized
	this.customValidators = 0;
	// specify whether the reset method should be run when the form object is initialized
	this.resetOnInit = false;
	// determine whether to show status bar messages
	this.showStatusMsgs = true;
	this.coreapi = {
		compatibility: true,
		field: true,
		functions: true,
		validation: {
			doInclude: ((!_t(window.__doLoadValidationAPI, "undefined")) ? window.__doLoadValidationAPI : true),
			included: false
		},
		language: {
			type: ((!_t(window.__language, "undefined")) ? window.__language : "en"),
			included: false
		}
	}
	this.lang = {};
	this.translation = {};
	return true;
}
qFormAPI = new _q();


/****************************************************
 define event handlers:
 if function returns false, then event is cancelled
*****************************************************/
_q.prototype.onUnload = function (){ return true; }
_q.prototype.onThrowError = function (sMsg){ return true; }
_q.prototype.onReset = function (){ return true; }

// qFormAPI setLibraryPath(sPath) method
_q.prototype.setLibraryPath = function (p){
	if( p.substring(p.length-1) != '/' ) p += '/';
	this.librarypath = p;
	// load dependent libraries
	if( !this.coreapi.language.included ) this.coreapi.language.included = this.languagePack("general");
	if( Function.prototype.apply == null ) this.include("compatibility");
	return true;
}

// qFormAPI include(sSource, sPath) method
_q.prototype.include = function (s, p){
	if( !s ) return false;
	if( !p ) var p = this.librarypath + "qforms/";
	if( !!this.coreapi[s] ) p += "api/";

	if( s.substring(s.length-3) != ".js" ) s += ".js";
	var sPkg = s.substring(0,s.length-3);

	// if the package is already loaded, then kill method
	if( !!this.packages[sPkg] || (!this.coreapi.validation.doInclude && sPkg == "validation") ) return false;

	var aJS = ["<sc"+"ript language=\"JavaScript1.2\" src=\"", "\"><\/sc"+"ript>"];

	// if the validation library should be loaded and it's not, load it now
	if( this.coreapi.validation.doInclude && !this.coreapi.validation.included ){
		document.write(aJS[0] + p + "lang/" + this.coreapi.language.type + "/msg.core.validation.js" + aJS[1]);
		document.write(aJS[0] + p + "api/core.validation.js" + aJS[1]);
		this.coreapi.validation.included = true;
	}

	// if loading all packages, loop through the modules and call the include method for each one
	if( sPkg == "*" ) for( var i=0; i < this.modules.length; i++ ) this.include(this.modules[i], p);
	// write the script tag to the browser
	else document.write(aJS[0] + p + s + aJS[1]);

	this.packages[sPkg] = true;
	return true;
}

// qFormAPI languagePack(sPack) method
_q.prototype.languagePack = function (s){
	return this.include("lang/" + this.coreapi.language.type + "/msg." + s);
}

// qFormAPI throwError(sCode, [sArgument1], [...sArgument9]) method
_q.prototype.throwError = function (_c){
	var e = this.getMessage.apply(this, arguments);
	var r = this.onThrowError(e);
	return (r==false) ? r : this.displayError(e, "throwError");
}

// qFormAPI displayError
_q.prototype.displayError = function (e, _y, _b){
	var t = _param(arguments[1], "").split(":"), b = _param(_b, false, "boolean"), r;
	if( !b ){
		alert(e);
		r = false;
	} else r = confirm(e);
	// if the display error is coming from the "is" validation method, then
	// place focus on the field
	if( t[0] == "is" ) setTimeout(this.objects[t[1]][t[2]].pointer + ".focus();", 1);
	return r;
}

// qFormAPI getMessage(sCode, [sArgument1], [...sArgument9]) method
_q.prototype.getMessage = function (_c){
	var a = [], c = _param(_c, "").split(":");
	// convert the extra arguments to an array
	if( (arguments.length == 2) && _t(arguments[1], "array") ) a = arguments[1];
	else if( arguments.length > 1 ) for( var i=1; i < arguments.length; i++ ) a[a.length] = arguments[i];
	return (_t(this.lang[c[0]], "undefined") || _t(this.lang[c[0]][c[1]], "undefined")) ? "An error has occurred. (Error code: " + c[1] + " [" + c[0] + "])" : this.lang[c[0]][c[1]].replaceArgs(a);
}

// qFormAPI unload() method
_q.prototype.unload = function (){
	if( !this.onUnload() ) return false;
	var isFramed = false, o = this.objects;
	// loop through all the forms and reset the status of the form to idle
	for( var k in o ){
		o[k]._status = "idle";
		if( !!o[k]._frame ) isFramed = true;
	}
	// some psuedo garbage collection to destroy some of the pointers if in a framed environment
	if( isFramed ){
		// kill the objects if using frames
		this.objects = {};
		// kill the containers if using frames
		this.containers = {};
	}
	return true;
}

// qFormAPI queueExec
_q.prototype.queueExec = function (o, e){
	var t = String(o.constructor); t = t.substring(t.indexOf(" ")+1, t.indexOf("("));
	var q = qFormAPI.queue[t];
	if( !_t(q, "object") && !_t(q[e], "array") ) return false;
	for( var i=0; i < q[e].length; i++ ) q[e][i].apply(o);
	return true;
}

// qFormAPI reset(bHardReset) method
_q.prototype.reset = function (b){
	if( !this.onReset() ) return false;
	// loop through all the forms and reset the properties
	for( var k in this.objects ) this.objects[k].reset(b);
	return true;
}

// qFormAPI getFields() method
_q.prototype.getFields = function (){
	var o = this.objects, d = {};

	// loop through all the forms
	for( var k in o ){
		// check the form for errors
		var x = o[k].getFields();
		// add the value from this form to the structure
		for( var f in x ){
			if( !d[f] ) d[f] = x[f];
			else d[f] += "," + x[f];
		}
	}

	// return all the form data
	return d;
}

// qFormAPI setFields(oData, bResetDefault, bResetAll) method
_q.prototype.setFields = function (o, d, a){
	// loop through each form and populate the fields
	for( var k in this.objects ) this.objects[k].setFields.apply(this.objects[k], arguments);
}

// qFormAPI dump() method
_q.prototype.dump = function (){
	_dump(this.getFields());
}


/******************************************************************************
 qForm Object
******************************************************************************/
// define qForm(sName, sParentLayerPath, sFramePath) object
function qForm(n, p, f){
	if( !n ) return qFormAPI.throwError("general:100");
	// increase the instance counter
	qFormAPI.instances++;
	// make sure the unload event is called
	if( qFormAPI.instances ==  1 ) window.onunload = new Function(_fnToString(window.onunload, ";qFormAPI.unload();"));
	this._name = n;
	this._parent = (!!p) ? p : null;
	this._frame = (!!f) ? f : null;
	this._status = null;
	this._showError = false;
	this._q = new xQ();
	this._queue = {
		"totalErrors": 0,
		"errors": {},
		"validation": []
	};
	this._allowSubmitOnError = qFormAPI.allowSubmitOnError;
	this._locked = false;
	qFormAPI.objects[n] = this;
	// this is a string pointer to the qFormAPI object copy of this object
	this._pointer = "qFormAPI.objects['" + n + "']";
	this._init();
	return true;
}

/****************************************************
 define event handlers:
 if function returns false, then event is cancelled
*****************************************************/
qForm.prototype.onInit = function (){ return true; }
qForm.prototype.onSubmit = function (){ return true; }
qForm.prototype.onReset = function (){ return true; }
qForm.prototype.onAddField = function (sFieldName){ return true; }
qForm.prototype.onRemoveField = function (sFieldName){ return true; }

// qForm _init() method
qForm.prototype._init = function (){
	// create a pointer to the form
	this._form = "document." + this._name;
	// if this is NS4 and the form is in a layer
	if( this._parent && document.layers ) this._form = this._parent + "." + this._form;

	// if the form is in a frame, then add path to the frame
	if( this._frame ) this._form = this._frame + "." + this._form;
	// create a pointer to the form object
	this.obj = eval(this._form);

	// if no "name" attribute was specified, check for matching "id" attribute
	if( !this.obj && qFormAPI.w3c ){
		var _s = this._form.replace(new RegExp("document\." + this._name), "document.getElementById('" + this._name + "')")
		this.obj = eval(_s);
		if( this.obj ){
			this.obj.name = this._name;
			this._form = _s;
		}
	}

	// if the object doesn't exist, thrown an error
	if( !this.obj ) return qFormAPI.throwError("general:101", this._name);

	// set the onSubmit method equal to whatever the current onSubmit is
	// this function is then run whenever the submitCheck determines it's ok to submit the form
	this.onSubmit = new Function(_fnToString(this.obj.onsubmit, ""));

	// replace the form's onSubmit event and just run the submitCheck() method
	var x = this._pointer + ".submitCheck();";
	if( this._frame )	x = "top." + sOnSubmit;
	this.obj.onsubmit = new Function("return " + x);

	// loop through form elements
	this._fields = [];
	this._pointers = {};
	for( var j=0; j < this.obj.elements.length; j++ ) this.addField(this.obj.elements[j].name);
	this._status = "initialized";

	// reset the form
	if( qFormAPI.resetOnInit ) this.reset();

	// invoke the event handler
	this.onInit();

	// run registered oninit events
	qFormAPI.queueExec(this, "oninit");

	return true;
}

// qForm addField(sFieldName) method
qForm.prototype.addField = function (n){
	if( !_t(n, "string") ) return false;
	var r = this.onAddField(n);
	if( !r ) return r;
	var o = this.obj[n];
	if( _t(o, "undefined") ) return false;
	// if the field is an array
	if( _t(o.type, "undefined") ) o = o[0];
	if( (!!o.type) && (_t(this[n], "undefined")) && (n.length > 0) ){
		this[n] = new Field(this.obj, n);
		this._pointers[n.toLowerCase()] = this[n];
		this._fields[this._fields.length] = n;
	}
	return true;
}

// qForm removeField(sFieldName) method
qForm.prototype.removeField = function (n){
	// this function requires a JS1.2 browser

	// currently, events attached to a form field are not
	// deleted. this means you'll need to manually remove
	// the field from the DOM, or errors will occur
	var r = this.onRemoveField(n);
	if( !r ) return r;
	if( _t(this[n], "undefined") ) return false;

	delete this[n];
	delete this._pointers[n.toLowerCase()];
	var f = this._fields;
	// find the field in the fields array and remove it
	for( var i=0; i < f.length; i++ ){
		if( f[i] == n ){
			f.splice(i,1);
			break;
		}
	}

	var q = this._queue.validation;
	// loop through validation queue, and remove references of
	for( var j=0; j < q.length; j++ ){
		if( q[j][0] == field ){
			q.splice(j,1);
			j--;
		}
	}

	return true;
}

// qForm submitCheck() method
qForm.prototype.submitCheck = function (){
	// make sure the form is not submitted again
	if( this._status == "submitting" || this._status == "validating" ) return false;
	this._status = "submitting";

	// validate the form if the validation lib is loaded
	var b = ( !qFormAPI.packages.validation ) ? true : qFormAPI.validate(this._name);

	// if no errors occurred, run the onSubmit() method
	if( b ){
		// run the custom onSubmit method
		var x = this.onSubmit();
		// if a boolean value was passed back, then update the result value
		if( _t(x, "boolean") ) b = x;
	}

	// if the form shouldn't be submitted, then reset the form's status
	if( !b ){
		// if any validation errors occur or the form is not to be submitted because the
		// onSubmit() event return false, then set the reset the form's status
		this._status = "idle";
	// run any processing that should be done before submitting the form
	} else {
		// make sure to select all "container" objects so the values are included when submitted
		_setContainerValues(this);
	}
	return b;
}

// qForm addEvent(sEvent, sCmd, bAppend) method
qForm.prototype.addEvent = function (e, c, a){
	if( arguments.length < 2 ) return qFormAPI.throwError("general:301.1");
	var b = _param(arguments[2], true, "boolean");
	_addEvent(this.obj, e, c, b);
	return true;
}

// qForm submit() method
qForm.prototype.submit = function (){
	var r = this.obj.submit();
	return (_t(r, "boolean")) ? r : true;
}

// qForm reset(bHardReset) method
qForm.prototype.reset = function (b){
	if( this._status == null ) return false;
	var f = this._fields;
	// loop through form elements
	for( var j=0; j < f.length; j++ ){
		// reset the value for this field
		this[f[j]].setValue(((!!b) ? null : this[f[j]].defaultValue), true, false);
		this[f[j]].applyCSS(false);
		// enforce any depencies of the current field
		if( this[f[j]]._queue.dependencies.length > 0 ) this[f[j]].enforceDependency();
	}
	this.onReset();
	return true;
}

// qForm disabled(bStatus) method
qForm.prototype.disabled = function (b){
	var bExists = _t(this.obj.disabled, "boolean");
	if( arguments.length == 0 ) var b = (!this.obj.disabled);
	// if the "disabled" var doesn't exist, then use the build in "locked" feature
	if( !bExists ) this._locked = b;
	// switch the status of the disabled property
	else this.obj.disabled = b;
	return true;
}

// qForm getFields() method
qForm.prototype.getFields = function (){
	if( this._status == null ) return false;
	var d = {};
	// loop through form elements
	for( var j=0; j < this._fields.length; j++ ) d[this._fields[j]] = this[this._fields[j]].getValue();
	return d;
}

// qForm setFields(oData, bResetDefault, bResetAll) method
qForm.prototype.setFields = function (o, _d, _a){
	if( this._status == null ) return false;
	// if you need to reset the default values of the fields
	var d = _param(_d, false, "boolean"), a = _param(_a, true, "boolean");

	// reset the form
	if( a ) this.reset();
	// loop through form elements
	for( var k in o ){
		var x = this._pointers[k.toLowerCase()];
		if( x ){
			x.setValue(o[k], true, false);
			if(d) x.defaultValue = o[k];
		}
	}
	return true;
}

// qForm hasChanged() method
qForm.prototype.hasChanged = function (){
	if( this._status == null ) return false;
	var f = this._fields;
	// loop through form elements and if a field has changed, return true
	for( var j=0; j < f.length; j++ ) if( this[f[j]].getValue() != this[f[j]].defaultValue ) return true;
	return false;
}

// qForm changedFields() method
qForm.prototype.changedFields = function (){
	if( this._status == null ) return false;
	var d = {}, f = this._fields;

	// loop through form elements
	for( var j=0; j < f.length; j++ )
		// if the field has changed, grab the field and value and store in a structure
		if( this[f[j]].getValue() != this[f[j]].defaultValue ) d[f[j]] = this[f[j]].getValue();
	return d;
}

// qForm dump() method
qForm.prototype.dump = function (){
	_dump(this.getFields());
}

/******************************************************************************
 Field Object
******************************************************************************/
// define Field(oForm, sFieldname) object
function Field(o, f){
	this._queue = {
		"dependencies": [],
		"validation": []
	};
	this.qForm = qFormAPI.objects[o.name];
	this.name = f;
	this.path = this.qForm._form + "['" + f + "']";
	this.pointer = this.qForm._pointer + "['" + f + "']";
	this.obj = o[f];
	this.locked = false;
	this.description = f.toLowerCase();
	this.required = false;
	this.validate = false;
	this.container = false;
	this.$maxLength = {};
	this.type = (!this.obj.type && !!this.obj[0]) ? this.obj[0].type : this.obj.type;
	this.validatorAttached = false;

	var v = this.getValue();
	this.defaultValue = v;
	this.lastValue = v;

	// initialize the field object
	this.init();

	return true;
}

/****************************************************
 define event handlers:
 if function returns false, then event is cancelled
*****************************************************/
Field.prototype.onInitialize = function (){ return true; }
Field.prototype.onFocus = function (){ return true; }
Field.prototype.onDisabledFocus = function (){ return true; }
Field.prototype.onReset = function (){ return true; }
Field.prototype.onGetValue = function (sValue){ return true; }
Field.prototype.onSetValue = function (sValue){ return true; }
Field.prototype.onCSSChange = function (bOn){ return true; }

// Field init() method
Field.prototype.init = function (){
	var e;
	if( this.isCSSEnabled() ) this.backgroundColor = this.obj.style.backgroundColor.toLowerCase();
	if( document.layers && this.isArray() ) e = "onclick";
	else e = "onfocus";

	// check to see if form can recieve focus
//	this._queue.onfocus.prepend("return " + this.pointer + ".allowFocus();");
//	this.addEvent(e, "return " + this.pointer + "._queue.onfocus.exec();");

	this.addEvent(e, "return " + this.pointer + ".allowFocus();");
	this.onInitialize();
}

Field.prototype.applyCSS = function (_b){
	if( !this.isCSSEnabled() ) return false;
	var o = this.obj.style, b = _param(_b, true, "boolean");

	if( b ) o.backgroundColor = qFormAPI.errorColor.toLowerCase();
	else if( !b )	o.backgroundColor = this.backgroundColor;
	this.onCSSChange(b);
	return true;
}

Field.prototype.isCSSEnabled = function (){ return (qFormAPI.useErrorColorCoding && this.obj.style) }

// Field isArray() method
Field.prototype.isArray = function (){
	return ("checkbox,radio".listItemExists(this.type) && !!this.obj[0])
}

// Field allowFocus() method
Field.prototype.allowFocus = function (){
	// remove css styles
	this.applyCSS(false);

	// store the current value in the lastValue property
	this.lastValue = this.getValue();
	// should the focus be allowed
	var b = this.checkIfLocked();

	// if the field is locked, and we have a select box, we need to reset the value of the field
	// and call the onblur method to remove focus
	if( (this.getType() == "select") && !b ) this.resetLast();

	// if the field isn't locked, run the onFocus event
	if( b ){
		var r = this.onFocus();
		// if the onFocus event return a boolean, return this value
		if( _t(r, "boolean") ) b = r;
	}
	// return the result of the checkIfLocked() method
	return b;
}

// Field addEvent(sEvent, sCmd, bAppend) method
Field.prototype.addEvent = function (e, c, a){
	if( arguments.length < 2 ) return qFormAPI.throwError("general:301.1");
	var b = _param(arguments[2], true, "boolean");

	// if the field is a multi-array element, then apply the event to all items in the array
	if( this.isArray() ){
		for( var i=0; i < this.obj.length; i++ ) _addEvent(this.obj[i], e, c, b);
	} else {
		_addEvent(this.obj, e, c, b);
	}
	return true;
}

// Field disabled(bStatus) method
Field.prototype.disabled = function (b){
	var oField = this.isArray() ? this.obj[0] : this.obj;
	var bExists = _t(oField.disabled, "boolean");
	if( arguments.length == 0 ) var b = (!oField.disabled);
	// if the "disabled" var doesn't exist, then use the build in "locked" feature
	if( !bExists ) this.locked = b;
	// switch the status of the disabled property
	else {
		if( this.isArray() && (this.getType() != "select") ) for( var i=0; this.obj.length; i++ ) this.obj[i].disabled = b;
		else this.obj.disabled = b;
	}
	return true;
}

// Field checkIfLocked(bShowMessage) method
Field.prototype.checkIfLocked = function (s){
	// if the value isn't equal to the key, then don't relocate the user
	if( this.isLocked() ){
		this.blur();
		qFormAPI.throwError("general:201");
 		this.onDisabledFocus();
 		return false;
	}
	return true;
}

// Field isLocked() method
Field.prototype.isLocked = function (){
	// if the entire form is locked, then this field is locked, otherwise use the field's
	// locked status
	return (this.qForm._locked) ? true : this.locked;
}

// Field isDisabled() method
Field.prototype.isDisabled = function (){
	// if the disabled object exists, then get its status or the form's disabled status
	if( _t(this.obj.disabled, "boolean") ) return (this.qForm.obj.disabled) ? true : this.obj.disabled;
	// otherwise, return false (saying it's not disabled)
	else return false;
}

// Field setMaxLength() method
Field.prototype.setMaxLength = function (l, _f){
	this.$maxLength = {
		"length": _param(arguments[0], null, "number"),
		"focusTo": _param(_f, null, "string")
	};
	this.addEvent("onkeyup", "return " + this.pointer + ".checkKeyPress(event);");
}

// Field checkKeyPress() method
Field.prototype.checkKeyPress = function (_e){
	var l = this.$maxLength.length, v = this.getValue(), k = (window.Event) ? _e.which : window.event.keyCode, o = _t(this.$lastEventValue, "string") ? this.$lastEventValue : "";
	this.$lastEventValue = v;
	if( (typeof l == "number") && (o.length != v.length) ){
		var f = this.$maxLength.focusTo;
		if( v.length > l ) this.obj.value = v = v.substring(0,l);
		if( v.length == l ){
			if( typeof f == "string" ){
				this.qForm[f].focus();
				if(this.qForm[f].getValue().length > 0) this.qForm[f].select();
			} else this.blur();
		}
	}
}

// Field focus() method
Field.prototype.focus = function (){ if( !!this.obj.focus ) this.obj.focus(); }
// Field blur() method
Field.prototype.blur = function (){ if( !!this.obj.blur ) this.obj.blur(); }
// Field select() method
Field.prototype.select = function (){ if( !!this.obj.select ) this.obj.select(); }
// Field reset() method
Field.prototype.reset = function (){ this.setValue(this.defaultValue, true, false); }

// Field getType() method
Field.prototype.getType = function (){
	return (this.type.substring(0,6) == "select") ? "select" : this.type;
}

// Field getValue() method
Field.prototype.getValue = function (){
	var v = [], o = this.obj, t = this.getType();

	switch( t ){
		case "checkbox": case "radio":
			// if more then one checkbox
			if( !!o[0] ){
				// loop through all checkbox elements, and if a checkbox is checked, grab the value
				for( var i=0; i < o.length; i++ ) if( o[i].checked ) v[v.length] = o[i].value;
				// otherwise, store the value of the field (if checkmarked) into the list
			} else if( o.checked ){
				v[v.length] = o.value;
			}
		break;

		case "select":
			if( this.type == "select-one" && !this.container ){
				v[v.length] = (o.selectedIndex == -1) ? "" : o[o.selectedIndex].value;
			} else {
				// loop through all element in the array for this field
				for( var i=0; i < o.length; i++ ){
					// if the element is selected, get the selected values (unless it's a dummy container)
					if( (o[i].selected || this.container) && (!this.dummyContainer) ){
						// append the selected value, if the value property doesn't exist, use the text
						v[v.length] = o[i].value;
					}
				}
			}
		break;

		default:
			v[v.length] = o.value;
		break;
	}

	v = v.join(",");

	// pass the value to the onGetValue event
	this.onGetValue(v);

	return v;
}

// Field setValue(sValue, bReset, doEvents) method
Field.prototype.setValue = function (v, r, e){
	var bReset = _param(r, true, "boolean"), o = this.obj, doEvents = _param(e, true, "boolean"), t = this.getType(), x;
	this.lastValue = this.getValue();

	switch( t ){
		case "checkbox": case "radio":
			// if more then one checkbox (Opera seems to report weird things w/checkbox arrays
			if( !!o[0] && !o.value ){
				// loop through all checkbox elements, and if a checkbox is checked, grab the value
				for( var i=0; i < o.length; i++ ){
					if( v.listItemExists(o[i].value) ) o[i].checked = true;
					else if( bReset ) o[i].checked = false;
				}
			// otherwise, store the value of the field (if checkmarked) into the list
			} else if( o.value == v ){
				o.checked = true;
			} else if( bReset ){
				o.checked = false;
			}
		break;

		case "select":
			var bSelectOne = (this.type == "select-one");
			var bKeepLooking = true; // if select-one type, then only select the first value found
			// if the select box is not a container
			if( !this.container ){
				// loop through all element in the array for this field
				for( var i=0; i < o.length; i++ ){
					x = o[i].value;
					bSelectItem = v.listItemExists(x);
					if( bSelectItem && bKeepLooking ){
						o[i].selected = true;
						if( bSelectOne && bKeepLooking ) bKeepLooking = false;
					} else if( bReset || !bKeepLooking ) o[i].selected = false;
				}
				// if a select-one box and nothing selected, then try to select the default value
				if( bSelectOne && bKeepLooking ){
					if( this.defaultValue == v ) if( this.obj.length > 0 ) this.obj[0].selected = true;
					else this.setValue(this.defaultValue);
				}
			// if the select box is a container, then search through the container's original contents
			} else {
				x = {};
				for( var i=0; i < this.boundContainers.length; i++ ){
					var c = qFormAPI.containers[this.qForm._name + ":" + this.boundContainers[i]];
					// check to see if the container exists, if it does check for the value
					if( !!c ){
						// loop through all the container objects
						for( var k in c ){
							// if the key is in the container, then make sure to add the value (but don't
							// duplicate the value
							if( v.listItemExists(k) && !this.lastValue.listItemExists(k) ) x[k] = c[k];
						}
					}
				}
				// populate the container values
				this.populate(x, bReset)
			}
		break;

		default:
			o.value = (_t(v, "string")) ? v : "";
		break;
	}

	// run the trigger events
	if( doEvents ){
		this.triggerEvent("onblur");
		// run the onchange event if the value has changed
		if( this.lastValue != v ) this.triggerEvent("onchange");
	}
	// run the onSetValue method
	this.onSetValue(v);

	return true;
}

// Field triggerEvent(sEvent) method
Field.prototype.triggerEvent = function (e){
	var o = this.obj, oEvent = o[e];
	if( this.isArray() ){
		for( var i=0; i < o.length; i++ ){
			oEvent = o[i][event];
			if( _t(oEvent, "function") ) oEvent();
		}
	} else if( _t(oEvent, "function") ){
		oEvent();
	}
	return true;
}

/******************************************************************************
 Required Functions
******************************************************************************/
// dump a associative array to an alert box
_dump = function (o){ var x = ""; for( var k in o ) x += k + " = " + o[k] + "\n"; return alert(x); }
// check to see if the "typeof" value of an object is equal to a string
function _t(o, v){
	var t = (typeof o).toLowerCase(), v = String(v).toLowerCase();
	if( t == "object" ){
		if( instanceOf(o, Array) ) t = "array";
		else if( instanceOf(o, Date) ) t = "date";
		else if( instanceOf(o, qForm) ) t = "qform";
		else if( instanceOf(o, Field) ) t = "field";
	}
	return (t == v);
}

function instanceOf(o, c) {
	while( o != null ){
		if( o.constructor == c) return true;
		o = o.__proto__;
	}
	return false;
}


// define the _param(value, default, type) function
function _param(v, d, t){
	var v=arguments[0], d=arguments[1], t=arguments[2];
	// if no default value is present, use an empty string
	if( _t(d, "undefined") ) d = "";
	// if no type value is present, use "string"
	if( _t(t, "undefined") ) t = "string";
	// if datatype should be a number and it's a string, convert it to a number
	if( t == "number" && _t(v, "string") ) var v = parseFloat(arguments[0]);
	// get the value to return, if the v param is not equal to the type, use default value
	return (!_t(v, "undefined") && _t(v, t.toLowerCase())) ? v : d;
}

// define the addEvent(oElement, sEvent, sCmd, bAppend) function
function _addEvent(o, _e, c, _b){
	if( arguments.length < 3 ) return qFormAPI.throwError("general:301");
	var e = _e.toLowerCase(), b = _param(_b, true, "boolean"), x = (o[e]) ? o[e].toString() : "";

	// strip out the body of the function
	x = x.substring(x.indexOf("{")+1, x.lastIndexOf("}"));
	x = (b) ? (x + c) : (c + x);
	x += "\n";
	o[e] = (!!window.Event) ? new Function("event", x) : new Function(x);
	return true;
}

// define the _fnToString(oFunction, sCmd, bAppend) function
function _fnToString(fn, c, _b){
	if( arguments.length < 1 ) return qFormAPI.throwError("general:300");
	var b = _param(_b, true, "boolean"), sFn = (!fn) ? "" : fn.toString();
	// strip out the body of the function
	sFn = sFn.substring(sFn.indexOf("{")+1, sFn.lastIndexOf("}"));
	if( c ) sFn = (b) ? (sFn + c + "\n") : (c + sFn + "\n");
	return sFn;
}

// defined the _setContainerValues(obj) function
function _setContainerValues(obj){
	// loop through form elements
	var o = obj._fields;
	for( var i=0; i < o.length; i++ )
		if( obj[o[i]].container && obj[o[i]].getType() == "select" )
			for( var x=0; x < obj[o[i]].obj.length; x++ )
				obj[o[i]].obj[x].selected = (!obj[o[i]].dummyContainer);
}

/******************************************************************************
 Add-ons to String object
******************************************************************************/
// add replaceArgs() string prototype
String.prototype.replaceArgs = function (_a){
	var a = [], s = this, x = "";
	// if a single argument was passed that was an array, use that to replace vars with
	if( arguments.length == 1 && _t(a, "array") ) a = _a;
	// otherwise, use the arguments as the array
	else for( var i=0; i < arguments.length; i++ ) a[a.length] = arguments[i];
	// do the string replacements
	for( var i=0; i < a.length; i++){
		x = a[i].toString();
		if( x.indexOf("[\"") == 0 ) x = x.substring(2, x.length-2); // fix bug in Mozilla v1.0
		s = s.replace(new RegExp("\\{"+i+"\\}", "gi"), (!qFormAPI.translation[x]) ? x : qFormAPI.translation[x]);
	}
	return s;
}
// remove all beginning whitespace
String.prototype.ltrim = function (){ return (this.length==0)?"":this.replace(new RegExp("^\\s+", "g"), ""); }
// remove all ending whitespace
String.prototype.rtrim = function (){ return (this.length==0)?"":this.replace(new RegExp("\\s+$", "g"), ""); }
// remove whitespace on both sides of string
String.prototype.trim = function (){ return this.ltrim().rtrim(); }
// convert a list to an array (strips whitespacing before and after delimiter)
String.prototype.toArray = function (_d){ var d = _param(_d, ","); return this.trim().split(new RegExp("\\s*" + d + "\\s*", "i")); }
// remove all spaces from a string
String.prototype.removeSpaces = function (){ return (this.length==0)?"":this.replace(new RegExp("\\s+", "g"), ""); }
// see if an item exists in a list
String.prototype.listItemExists = function (_v, _d){
	var d = _param(_d, ",", "string");
	var l = d + this + d, v = d + _v + d;
	return (l.indexOf(v) != -1);
}

String.prototype.toInt = function (_b){
	var b = _param(_b, 10, "number");
	return parseInt(this, b);
}

