/****************************************************************************************
                  Script to save or recover the values of form inputs
                   v2.1 written by Mark Wilton-Jones, 11-12/11/2003
         updated 5/10/2005 to allow specific inputs to be included or excluded
                   updated 4/03/2007 to allow Web Forms 2.0 inputs
*****************************************************************************************

Please see http://www.howtocreate.co.uk/jslibs/ for details and a demo of this script
Please see http://www.howtocreate.co.uk/jslibs/termsOfUse.html for terms of use

The script can save the values of:
text inputs
textareas
password inputs (optional)
radio buttons
checkbox inputs
select inputs

To use this, insert the following into the head of your document:

<script src="PATH TO SCRIPT/saveFormValues.js" language="javascript1.2" type="text/javascript"></script>

I suggest using my cookie script to save the form inputs into a cookie, which can be
recovered using the same script (the amount of form information that can be stored
will be limited by the maximum size of document.cookie - 4KB encoded):

<script src="PATH TO SCRIPT/saveFormValues.js" language="javascript1.2" type="text/javascript"></script>
<script src="PATH TO SCRIPT/cookie.js" language="javascript1.2" type="text/javascript"></script>
...
setCookie( 'formInputs', getFormString( document.forms.myForm, true ), 604800 );
...
recoverInputs( document.forms.myForm, retrieveCookie( 'formInputs' ), true );
...

This header file provides two functions:
var input_values_string = getFormString( reference to the form, bool: include password fields[, optional compareType: compare type, optional string: token list] );
recoverInputs( reference to the form, input_values_string, bool: include password fields[, optional compareType: compare type, optional string: token list] );

Opera 5 and 6 (not 7) will produce a prompt asking for permission to access password
fields. If many of your visitors use this browser, I suggest that you do not save
password values.

The token list option allows you to specify a list of inputs that you want to either
include or exclude from the saved values. The token list is a comma separated list of
name, class, or id values, such as 'name,email,address1'. The compare type option lets
you choose if you want to compare against the name, class (multiple classes are
supported), or id, and if you want to include or exclude inputs that match a token.
Note that the inputs will still need to be within the correct form.
Avaliable compareType values are:
FS_INCLUDE_NAMES   - Include elements with the specified names
FS_EXCLUDE_NAMES   - Exclude elements with the specified names 
FS_INCLUDE_IDS     - Include elements with the specified IDs
FS_EXCLUDE_IDS     - Exclude elements with the specified IDs
FS_INCLUDE_CLASSES - Include elements with the specified classes
FS_EXCLUDE_CLASSES - Exclude elements with the specified classes

Example:
setCookie( 'formInputs', getFormString( document.forms.myForm, true, FS_EXCLUDE_NAMES, 'CreditCard,SortCode' ), 604800 );
...
recoverInputs( document.forms.myForm, retrieveCookie( 'formInputs' ), true, FS_EXCLUDE_NAMES, 'CreditCard,SortCode' );


It is best not to use this script with forms whose inputs (or select options) are
generated with JavaScript.

Please see http://www.howtocreate.co.uk/tutorials/jsexamples/saveForm.html for
examples of how to use this script, and important notes.
_______________________________________________________________________________________*/
/*******************************************************************************************
 * Object: Hashtable
 * Description: Implementation of hashtable
 * Author: Uzi Refaeli
 *******************************************************************************************/

//======================================= Properties ========================================
Hashtable.prototype.hash	 	= null;
Hashtable.prototype.keys		= null;
Hashtable.prototype.location	= null;

/**
 * Hashtable - Constructor
 * Create a new Hashtable object.
 */
function Hashtable(){
	this.hash = new Array();
	this.keys = new Array();

	this.location = 0;
}

/**
 * put
 * Add new key
 * param: key - String, key name
 * param: value - Object, the object to insert
 */
Hashtable.prototype.put = function (key, value){
	if (value == null)
		return;

	if (this.hash[key] == null)
		this.keys[this.keys.length] = key;

	this.hash[key] = value;
}

/**
 * get
 * Return an element
 * param: key - String, key name
 * Return: object - The requested object
 */
Hashtable.prototype.get = function (key){
		return this.hash[key];
}

/**
 * remove
 * Remove an element
 * param: key - String, key name
 */
Hashtable.prototype.remove = function (key){
	for (var i = 0; i < this.keys.length; i++){
		//did we found our key?
		if (key == this.keys[i]){
			//remove it from the hash
			this.hash[this.keys[i]] = null;
			//and throw away the key...
			this.keys.splice(i ,1);
			return;
		}
	}
}

/**
 * size
 * Return: Number of elements in the hashtable
 */
Hashtable.prototype.size = function (){
    return this.keys.length;
}

/**
 * populateItems
 * Deprecated
 */
Hashtable.prototype.populateItems = function (){}

/**
 * next
 * Return: true if theres more items
 */
Hashtable.prototype.next = function (){
	if (++this.location < this.keys.length)
		return true;
	else
		return false;
}

/**
 * moveFirst
 * Move to the first item.
 */
Hashtable.prototype.moveFirst = function (){
	try {
		this.location = -1;
	} catch(e) {/*//do nothing here :-)*/}
}

/**
 * moveLast
 * Move to the last item.
 */
Hashtable.prototype.moveLast = function (){
	try {
		this.location = this.keys.length - 1;
	} catch(e) {/*//do nothing here :-)*/}
}

/**
 * getKey
 * Return: The value of item in the hash
 */
Hashtable.prototype.getKey = function (){
	try {
		return this.keys[this.location];
	} catch(e) {
		return null;
	}
}

/**
 * getValue
 * Return: The value of item in the hash
 */
Hashtable.prototype.getValue = function (){
	try {
		return this.hash[this.keys[this.location]];
	} catch(e) {
		return null;
	}
}

/**
 * getKey
 * Return: The first key contains the given value, or null if not found
 */
Hashtable.prototype.getKeyOfValue = function (value){
	for (var i = 0; i < this.keys.length; i++)
		if (this.hash[this.keys[i]] == value)
			return this.keys[i]
	return null;
}


/**
 * toString
 * Returns a string representation of this Hashtable object in the form of a set of entries,
 * enclosed in braces and separated by the ASCII characters ", " (comma and space).
 * Each entry is rendered as the key, an equals sign =, and the associated element,
 * where the toString method is used to convert the key and element to strings.
 * Return: a string representation of this hashtable.
 */
Hashtable.prototype.toString = function (){

	try {
		var s = new Array(this.keys.length);
		s[s.length] = "{";

		for (var i = 0; i < this.keys.length; i++){
			s[s.length] = this.keys[i];
			s[s.length] = "=";
			var v = this.hash[this.keys[i]];
			if (v)
				s[s.length] = v.toString();
			else
				s[s.length] = "null";

			if (i != this.keys.length-1)
				s[s.length] = ", ";
		}
	} catch(e) {
		//do nothing here :-)
	}finally{
		s[s.length] = "}";
	}

	return s.join("");
}

/**
 * add
 * Concatanates hashtable to another hashtable.
 */
Hashtable.prototype.add = function(ht){
	try {
		ht.moveFirst();
		while(ht.next()){
			var key = ht.getKey();
			//put the new value in both cases (exists or not).
			this.hash[key] = ht.getValue();
			//but if it is a new key also increase the key set
			if (this.get(key) != null){
				this.keys[this.keys.length] = key;
			}
		}
	} catch(e) {
		//do nothing here :-)
	} finally {
		return this;
	}
};

var FS_INCLUDE_NAMES = 0, FS_EXCLUDE_NAMES = 1, FS_INCLUDE_IDS = 2, FS_EXCLUDE_IDS = 3, FS_INCLUDE_CLASSES = 4, FS_EXCLUDE_CLASSES = 5;

function getFormString( formRef, oAndPass, oTypes, oNames ) {
	if( oNames ) {
		oNames = new RegExp((( oTypes > 3 )?'\\b(':'^(')+oNames.replace(/([\\\/\[\]\(\)\.\+\*\{\}\?\^\$\|])/g,'\\$1').replace(/,/g,'|')+(( oTypes > 3 )?')\\b':')$'),'');
		var oExclude = oTypes % 2;
	}
	if (document.all) {
		var x = 0;
		for( var i = 0, oStr = '', y = false; i < formEleArr.length; i++ ) {
			if (formEleArr[i] == formRef.elements[x]) {
				if( formRef.elements[x].type ) {
					if( oNames ) {
						var theAttr = ( oTypes > 3 ) ? formRef.elements[x].className : ( ( oTypes > 1 ) ? formRef.elements[x].id : formRef.elements[x].name );
						if( ( oExclude && theAttr && theAttr.match(oNames) ) || ( !oExclude && !( theAttr && theAttr.match(oNames) ) ) ) { continue; }
					}
					var oE = formRef.elements[x]; var oT = oE.type.toLowerCase();
					if( oT == 'text' || oT == 'textarea' || ( oT == 'password' && oAndPass ) || oT == 'datetime' || oT == 'datetime-local' || oT == 'date' || oT == 'month' || oT == 'week' || oT == 'time' || oT == 'number' || oT == 'range' || oT == 'email' || oT == 'url' ) {
						oStr += ( y ? ',' : '' ) + oE.name + '=' + oE.value.replace(/%/g,'%p').replace(/,/g,'%c');
						y = true;
					} else if( oT == 'radio' || oT == 'checkbox' ) {
						oStr += ( y ? ',' : '' ) + oE.name + '|' + oE.value + '=' + ( oE.checked ? '1' : '' );
						y = true;
					} else if( oT == 'select-one' ) {
						oStr += ( y ? ',' : '' ) + oE.name + '=' + oE.selectedIndex;
						y = true;
					} else if( oT == 'select-multiple' ) {
						for( var oO = oE.options, i = 0; oO[i]; i++ ) {
							oStr += ( y ? ',' : '' ) + (i == 0 ?  (oE.name + '=') : '') + ( oO[i].selected ? '1' : '' );
							y = true;
						}
					}
				}
				x++;
			}
			else {
				oStr += (y ? ',' : '');
			}
		}
	}
	else {
		for( var x = 0, oStr = '', y = false; formRef.elements[x]; x++ ) {
			if( formRef.elements[x].type ) {
				if( oNames ) {
					var theAttr = ( oTypes > 3 ) ? formRef.elements[x].className : ( ( oTypes > 1 ) ? formRef.elements[x].id : formRef.elements[x].name );
					if( ( oExclude && theAttr && theAttr.match(oNames) ) || ( !oExclude && !( theAttr && theAttr.match(oNames) ) ) ) { continue; }
				}
				var oE = formRef.elements[x]; var oT = oE.type.toLowerCase();
				if( oT == 'text' || oT == 'textarea' || ( oT == 'password' && oAndPass ) || oT == 'datetime' || oT == 'datetime-local' || oT == 'date' || oT == 'month' || oT == 'week' || oT == 'time' || oT == 'number' || oT == 'range' || oT == 'email' || oT == 'url' ) {
					oStr += ( y ? ',' : '' ) + oE.name + '=' + oE.value.replace(/%/g,'%p').replace(/,/g,'%c');
					y = true;
				} else if( oT == 'radio' || oT == 'checkbox' ) {
					oStr += ( y ? ',' : '' ) + oE.name + '|' + oE.value + '=' + ( oE.checked ? '1' : '' );
					y = true;
				} else if( oT == 'select-one' ) {
					oStr += ( y ? ',' : '' ) + oE.name + '=' + oE.selectedIndex;
					y = true;
				} else if( oT == 'select-multiple' ) {
					for( var oO = oE.options, i = 0; oO[i]; i++ ) {
						oStr += ( y ? ',' : '' ) + oE.name + '@' + i + '=' + ( oO[i].selected ? '1' : '' );
						y = true;
					}
				}
			}
		}
	}
	return oStr;
}

var formEleArr = new Array();
var storeMap = null;

function recoverInputs( formRef, oStr, oAndPass, oTypes, oNames ) {
	formEleArr.length = 0;
	for( var x = 0, y = 0; formRef.elements[x]; x++ ) {
		formEleArr[formEleArr.length] = formRef.elements[x];
	}
	if( oStr ) {
		//alert(oStr);
		storeMap = new Hashtable();
		oStr = oStr.split( ',' );
		for( var i = 0; i < oStr.length; i++ ) {
			if (oStr[i]) {
				keyVal = oStr[i].split( '=' );
				storeMap.put(keyVal[0], keyVal[1]);
			}
		}
		if( oNames ) {
			oNames = new RegExp((( oTypes > 3 )?'\\b(':'^(')+oNames.replace(/([\\\/\[\]\(\)\.\+\*\{\}\?\^\$\|])/g,'\\$1').replace(/,/g,'|')+(( oTypes > 3 )?')\\b':')$'),'');
			var oExclude = oTypes % 2;
		}
		for( var x = 0; formRef.elements[x]; x++ ) {
			if( formRef.elements[x].type ) {
				if( oNames ) {
					var theAttr = ( oTypes > 3 ) ? formRef.elements[x].className : ( ( oTypes > 1 ) ? formRef.elements[x].id : formRef.elements[x].name );
					if( ( oExclude && theAttr && theAttr.match(oNames) ) || ( !oExclude && ( !theAttr || !theAttr.match(oNames) ) ) ) { continue; }
				}
				var oE = formRef.elements[x]; var oT = oE.type.toLowerCase();
				if( oT == 'text' || oT == 'textarea' || ( oT == 'password' && oAndPass ) || oT == 'datetime' || oT == 'datetime-local' || oT == 'date' || oT == 'month' || oT == 'week' || oT == 'time' || oT == 'number' || oT == 'range' || oT == 'email' || oT == 'url' ) {					
					var storedStr = storeMap.get(oE.name);
					if (storedStr) {
						oE.value = storedStr.replace(/%c/g,',').replace(/%p/g,'%');
					}
				} else if( oT == 'radio' || oT == 'checkbox' ) {
					var storedStr = storeMap.get(oE.name+'|'+oE.value);
					oE.checked = storedStr ? true : false;
					//fireClickEvent(oE);
					//fireChangeEvent(oE);
				} else if( oT == 'select-one' ) {
					var storedStr = storeMap.get(oE.name);
					if (storedStr) {
						oE.selectedIndex = parseInt( storedStr );
					}
					fireChangeEvent(oE);
				} else if( oT == 'select-multiple' ) {
					for( var oO = oE.options, i = 0; oO[i]; i++ ) {
						var storedStr = storeMap.get(oE.name+'@'+i);
						oO[i].selected = storedStr ? true : false;
					}
				}
			}
		}
	}
	if (document.all) {
		setTimeout(function(){invokeRecoverInputs(formRef)}, 50);
	}
}

function fireChangeEvent(control) 
{
	if( document.createEvent ) {
	  var evObj = document.createEvent('HTMLEvents');
	  evObj.initEvent( 'change', true, false );
	  control.dispatchEvent(evObj);
	} else if( document.createEventObject ) {
	  control.fireEvent('onchange');
	}
}

function fireClickEvent(control) 
{
	if( document.createEvent ) {
	  var evObj = document.createEvent('HTMLEvents');
	  evObj.initEvent( 'click', true, false );
	  control.dispatchEvent(evObj);
	} else if( document.createEventObject ) {
	  control.fireEvent('onclick');
	}
}

function invokeRecoverInputs( formRef ) {
	if( storeMap ) {
		for( var x = 0; formRef.elements[x]; x++ ) {
			if( formRef.elements[x].type ) {
				var oE = formRef.elements[x]; var oT = oE.type.toLowerCase();
				if( oT == 'text' || oT == 'textarea' || ( oT == 'password' && oAndPass ) || oT == 'datetime' || oT == 'datetime-local' || oT == 'date' || oT == 'month' || oT == 'week' || oT == 'time' || oT == 'number' || oT == 'range' || oT == 'email' || oT == 'url' ) {
					var storedStr = storeMap.get(oE.name);
					if (storedStr) {
						oE.value = storedStr.replace(/%c/g,',').replace(/%p/g,'%');
					}
				} else if( oT == 'radio' || oT == 'checkbox' ) {					
					var storedStr = storeMap.get(oE.name+'|'+oE.value);
					oE.checked = storedStr ? true : false;
				} else if( oT == 'select-one' ) {	
					var storedStr = storeMap.get(oE.name);
					if (storedStr) {
						oE.selectedIndex = parseInt( storedStr );
					}
				} else if( oT == 'select-multiple' ) {
					for( var oO = oE.options, i = 0; oO[i]; i++ ) {
						var storedStr = storeMap.get(oE.name+'@'+i);
						oO[i].selected = storedStr ? true : false;
					}
				}
			}
		}
	}
}