FormValidateInit = function ( args )
{
	if( typeof(args) == 'undefined' ) args = {};

	for( var i = 0; i < document.forms.length; ++i )
	{
		var elements = document.forms[i].getElementsByTagName('INPUT');
		for( var x = 0; x < elements.length; ++x )
		{
			if( elements[x].name == 'xml_validation' && elements[x].type == 'hidden' )
			{
				new FormValidate( document.forms[i], elements[x].value, args );
			}
		}
	}
};

FormValidate = function ( form, uri, args )
{
	if( typeof(form) == 'undefined' ) return this.systemError( 7, 'The form element passed to new FormValidate() was undefined' );
	if( typeof(args) == 'undefined' ) args = {};
	
	this.form = form;
	if( !this.form.id ) return this.systemError( 'Forms that are going to be validated, *must* have an id' );
	
	this.cookie2form();
	
	this.enableClientValidation();
	this.loadXML( uri, args.no_cache );
	this.loadForm( form );
};

FormValidate.prototype = {
	form2cookie						: function ()
	{
		var cookie_data = {};
		
		var tags = [ 'SELECT', 'INPUT', 'TEXTAREA' ];
		for( var i = 0; i < tags.length; ++i )
		{
			var elements = this.form.getElementsByTagName(tags[i]);
			for( var z = 0; z < elements.length; ++z ){
				var name  = elements[z].name;
				var value = elements[z].type == 'checkbox' || elements[z].type == 'radio' ? elements[z].checked : elements[z].value;
				if( !cookie_data[name] ) cookie_data[name] = [];
				cookie_data[name].push( escape(value) );
			}
		}
		for( key in cookie_data ){
			this.setCookie( this.form.id + '_' + key, cookie_data[key].join(';') );
		}
	},
	cookie2form						: function ()
	{
		var cookie_cache = {};
		var tags = [ 'SELECT', 'INPUT', 'TEXTAREA' ];
		for( var i = 0; i < tags.length; ++i )
		{
			var elements = this.form.getElementsByTagName(tags[i]);
			for( var z = 0; z < elements.length; ++z ){
				if( elements[z].type == 'submit' || elements[z].type == 'hidden' ) continue;
				var name  = elements[z].name;
				if( !cookie_cache[name] ) cookie_cache[name] = this.getCookie( this.form.id + '_' + name );
				this.setCookie( this.form.id + '_' + name, '', 'Thu, 01-Jan-70 00:00:01 GMT' );
				if( cookie_cache[name] )
				{
					var value = cookie_cache[name].shift();
					if( typeof(value) != 'undefined' )
					{
						if( elements[z].type == 'checkbox' || elements[z].type == 'radio' )
							elements[z].checked = value == 'true' ? true : false;
						else
							elements[z].value = value;
					}
				}
			}
		}
	},
	getCookie						: function(key)
	{	
		var cookies = document.cookie.split(';');
		for( var i = 0; i < cookies.length; ++i )
		{
			cookies[i] = cookies[i].replace(/^\s+/,'');
			
			if( cookies[i].indexOf( key + '=' ) == 0 )
			{
				var value = cookies[i].substring( key.length+1, cookies[i].length );
				var parts = unescape( value ).split(';');
				var ret   = [];
				for( var z = 0; z < parts.length; ++z ) ret.push( unescape(parts[z] ) );
				return ret;
			}
		}
	},	
	setCookie						: function(key, value)
	{
		var cookie = [key+'=' + escape(value), 'path=/', 'domain=' + window.location.hostname ];
		
		var expiry = new Date(); 1 * 60 * 60 * 1000
		expiry.setTime(expiry.getTime() + (3 * 60 * 1000) );
		
		cookie.push( expiry.toGMTString() );
		
		return document.cookie = cookie.join('; ');
	},
	addClass							: function ( el, className )
	{
		if( typeof(el) == 'string' ) el = document.getElementById( el );
		
		var classes = el.className ? el.className.split( ' ' ) : [];
		for( var z = 0; z < classes.length; ++z )
		{
			if( classes[z] == className ) return;
		}
		classes.push( className );
		el.className = classes.join( ' ' );
	},
	remClass							: function ( el, className )
	{
		if( typeof(el) == 'string' ) el = document.getElementById( el );
		if( !el.className ) return;
		
		var oldClasses = el.className.split( ' ' );
		var newClasses = [];
		
		for( var z = 0; z < oldClasses.length; ++z )
		{
			if( oldClasses[z] != className ) newClasses.push( oldClasses[z] );
		}
		el.className = newClasses.join( ' ' );
	},
	addErrorClass					: function ( data )
	{
		for( param in data )
		{
			var desc = document.getElementById( param + '_FormInputDesc' );
			if( typeof(desc) != 'undefined' && desc !=null ) this.addClass( desc, 'error' );
		}
	},
	remErrorClass					: function ( failures )
	{
		var params = this.getParams();
		for( var param_name in params )
		{
			if( failures[param_name] ) continue;
			var desc = document.getElementById( param_name + '_FormInputDesc' );
			if( desc!=null && typeof(desc) != 'undefined' ) this.remClass( desc, 'error' );
		}
	},
	onfail							: function ( data )
	{
		if( typeof(data) == 'function' ){
			this.onfail = data;
		} else {
			var messages = [];
			for( param in data ){
				if( typeof(data[param]) == 'string' ) messages.push( data[param] );
			}
			alert( messages.join("\n") );
		}
	},
	onsubmit							: function ()
	{
		if( this.clientValidation != true ) return true;
		
		this.failures = [];
	
		var failed = false;
		
		var params = this.getParams();
		for( var param_name in params )
		{
			var form_elements = this.formElements( param_name );
			if( form_elements.length == 0 ) form_elements.push( null );

			var param     = params[param_name];
			
			var condition = this.getConditionFromParam( param );
			if( condition != null )
			{
				try
				{
					var task = this.parseEvaluation( condition );
				
					for( var i = 0; i < form_elements.length; ++i )
					{
						var evaluated = this.evaluate( param, form_elements[i], task );
						if( !evaluated )
						{
							if( !this.failures[param_name] )
							{
								if( !task.group_condition )
								{
									this.failures[param_name] = 'Failed to validate '+param_name;
								}
							}
							failed = true;
							break;
						}
					}
				}
				catch(e)
				{
					this.systemError( "E1:"+e );
				}
			}
			var group_condition = this.getGroupConditionFromParam( param );
			if( group_condition != null )
			{
				try
				{
					var minTrue = group_condition.getAttribute( 'minTrue' ) || form_elements.length;
					var maxTrue = group_condition.getAttribute( 'maxTrue' ) || form_elements.length;
					var task    = this.parseEvaluation( group_condition );
					
					var trueCount = 0;
					for( var i = 0; i < form_elements.length; ++i )
					{
						var evaluated = this.evaluate( param, form_elements[i], task );
						if( evaluated ) ++trueCount;
						if( trueCount > maxTrue ) break;
					}
					if( trueCount < minTrue || trueCount > maxTrue )
					{
						failed = true;
						var error = group_condition.getAttribute('onfail') || 'Failed to validate '+param_name;
						if( !this.failures[param_name] ) this.failures[param_name] = error;
					}
				}
				catch(e)
				{
					this.systemError( "E2:"+e )
				}
			}
		}

		if( failed )
		{
			this.remErrorClass( this.failures );
			this.addErrorClass( this.failures );			
			this.onfail( this.failures );
			return false;
		}

		this.form2cookie();
		return true;
	},
	evaluate							: function ( param, form_element, task )
	{
		if( task.op )
		{
			if( this['op_'+task.op] )
			{
				var result;
				try {
					result = this['op_'+task.op]( param, form_element, task.options, task.arguments, task.text );
				} catch ( e ) {
					result = false;
				}
				if( typeof(result) == 'boolean' && !result && task.options )
				{
					if( !task.group_condition ) this.addFailure( param, task.options.onfail );
				}
				return result;
			}
			else
			{
				console.log('Unknown operation: '+task.op );
			}
		} else {
			return this.fatalError( 'Missing operation on param '+param.nodeName );
		}
	},
	addFailure						: function ( param, message )
	{
		if( this.failures[param.nodeName] ) return;
		if( typeof( message ) != 'string' ) return;
		this.failures[param.nodeName] = message;
	},
	op_and							: function ( param, form_element, options, arguments, text )
	{
		if( arguments.length < 1 ) return this.fatalError('At param('+param.nodeName+'), operation "and" takes at least one argument');
		
		for( var i = 0; i < arguments.length; ++i )
		{
			if( !this.evaluate( param, form_element, arguments[i] ) ) return false;
		}
		return true;
	},
	op_or								: function ( param, form_element, options, arguments, text )
	{
		if( arguments.length < 1 ) return this.fatalError('At param('+param.nodeName+'), operation "or" takes at least one argument');
		
		for( var i = 0; i < arguments.length; ++i )
		{
			if( this.evaluate( param, form_element, arguments[i] ) ) return true;
		}
		return false;
	},
	op_not							: function ( param, form_element, options, arguments, text )
	{
		if( arguments.length != 1 ) return this.fatalError('At param('+param.nodeName+'), operation "not" takes one argument');
		return ! this.evaluate( param, form_element, arguments[0] );
	},
	op_if 							: function ( param, form_element, options, arguments, text )
	{
		if( arguments.length != 3 ) return this.fatalError('At param('+param.nodeName+'), operation "if" takes three arguments');
		
		if( this.evaluate( param, form_element, arguments[0] ) )
			return this.evaluate( param, form_element, arguments[1] )
		else
			return this.evaluate( param, form_element, arguments[2] );
	},
	op_eq								: function ( param, form_element, options, arguments, text )
	{
		if( arguments.length == 1 ) arguments = this.unshift( {op:'this'}, arguments );
		if( arguments.length != 2 ) return this.fatalError('At param('+param.nodeName+'), operation "eq" takes two arguments');
		
		var eq_result1 = this.evaluate( param, form_element, arguments[0] );
		var eq_result2 = this.evaluate( param, form_element, arguments[1] );		
		
		return eq_result1 == eq_result2 ? true : false;
	},
	op_lt								: function ( param, form_element, options, arguments, text )
	{
		if( arguments.length == 1 ) arguments = this.unshift( {op:'this'}, arguments );
		if( arguments.length != 2 ) return this.fatalError('At param('+param.nodeName+'), operation "lt" takes two arguments');
		
		var lt_result1 = this.evaluate( param, form_element, arguments[0] );
		var lt_result2 = this.evaluate( param, form_element, arguments[1] );
		
		return lt_result1 < lt_result2 ? true : false;
	},
	op_gt								: function ( param, form_element, options, arguments, text )
	{
		if( arguments.length == 1 ) arguments = this.unshift( {op:'this'}, arguments );
		if( arguments.length != 2 ) return this.fatalError('At param('+param.nodeName+'), operation "gt" takes two arguments');
		
		var gt_result1 = this.evaluate( param, form_element, arguments[0] );
		var gt_result2 = this.evaluate( param, form_element, arguments[1] );
		return gt_result1 > gt_result2 ? true : false;
	},
	op_lc								: function ( param, form_element, options, arguments, text )
	{
		if( arguments.length == 0 ) arguments = this.unshift( {op:'this'}, arguments );
		if( arguments.length != 1 ) return this.fatalError('At param('+param.nodeName+'), operation "lc" takes one arguments');
		
		var lc_result1 = this.evaluate( param, form_element, arguments[0] );
		return lc_result1.toLowerCase();
	},
	op_blank							: function ( param, form_element, options, arguments, text )
	{
		if( arguments.length > 0 ) return this.fatalError('At param('+param.nodeName+'), operation "blank" takes no arguments');
		
		return '';
	},
	op_length						: function ( param, form_element, options, arguments, text )
	{
		if( arguments.length == 0 ) arguments = this.unshift( {op:'this'}, arguments );
		if( arguments.length != 1 ) return this.fatalError('At param('+param.nodeName+'), operation "length" takes one argument');
		
		var length_result1 = this.evaluate( param, form_element, arguments[0] );
		return length_result1.length;
	},
	op_md5							: function ( param, form_element, options, arguments, text )
	{
		if( arguments.length == 0 ) arguments = this.unshift( {op:'this'}, arguments );
		if( arguments.length != 1 ) return this.fatalError('At param('+param.nodeName+'), operation "md5" takes one argument');
		
		// This check can only be done server side, unless I slurp a large third party MD5 class into this library
		  return true;
	},
	op_captchacheck				: function ( param, form_element, options, arguments, text )
	{
		if( arguments.length == 1 ) arguments = this.unshift( {op:'this'}, arguments );
		if( arguments.length != 2 ) return this.fatalError('At param('+param.nodeName+'), operation "captchacheck" takes two arguments');
		
		// This check should only be done server side
		  return true;
	},
	op_qnacheck						: function ( param, form_element, options, arguments, text )
	{
		if( arguments.length == 1 ) arguments = this.unshift( {op:'this'}, arguments );
		if( arguments.length != 2 ) return this.fatalError('At param('+param.nodeName+'), operation "qnacheck" takes two arguments');
		
		// This check should only be done server side
		  return true;
	},
	op_match							: function ( param, form_element, options, arguments, text )
	{
		if( arguments.length == 1 ) arguments = this.unshift( {op:'this'}, arguments );
		if( arguments.length != 2 ) return this.fatalError('At param('+param.nodeName+'), operation "match" takes two arguments');
		
		var string = this.evaluate( param, form_element, arguments[0] );
		var regexp = this.evaluate( param, form_element, arguments[1] );

		return (string+'').match( regexp ) ? true : false;
	},
	op_pattern						: function ( param, form_element, options, arguments, text )
	{
		var regex;
		if( options.type )
		{
			if( options.type == 'email' )
				regex = new RegExp( "^[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*@(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\\.)+[a-z0-9]{2,}$", 'i' );
			else
				return this.fatalError('No named pattern called "'+options.type+'"');
		}
		else
		{
			if( options.quantifiers )
				regex = new RegExp( text, options.quantifiers );
			else
				regex = new RegExp( text );
		}	
		return regex;
	},
	op_date							: function ( param, form_element, options, arguments, text )
	{
		if( arguments.length != 1 ) return this.fatalError('At param('+param.nodeName+'), operation "date" takes one argument');

		var dateString = this.evaluate( param, form_element, arguments[0] );

		var date = this.buildDate( options.format, dateString );
		
		var year  = this.xbYear(date);
		var month = date.getMonth()+1; if( month < 10 ) month = '0'+month;
		var day   = date.getDate();    if( day   < 10 ) day   = '0'+day;
		
		var cmpDateString = year+''+month+''+day;
		return date == null ? false : cmpDateString;
	},
	op_field_value					: function ( param, form_element, options, arguments, text )
	{
		var element = this.formElement( text );
		var type    = element.type;
		
		if( type == 'checkbox' ) return element.checked ? true : false;
		if( type == 'radio' )
		{
			console.log('RADIO VALUE:'+element.value);
			if( element.checked ) return element.value;
			return null;
		}
		return element.value;
	},
	op_checked						: function ( param, form_element, options, arguments, text )
	{
		if( arguments.length == 0 ) arguments = this.unshift( {op:'this'}, arguments );
		if( arguments.length != 1 ) return this.fatalError('At param('+param.nodeName+'), operation "checked" takes one argument');
		
		var checked = this.evaluate( param, form_element, arguments[0] );
		if( checked == null ) return false;
		if( typeof(checked) == 'boolean' ){
			return checked;
		} else {
			return true;
		}
	},
	op_data							: function ( param, form_element, options, arguments, text )
	{
		return text;
	},
	op_boolean						: function ( param, form_element, options, arguments, text )
	{
		if( text == 'true'  ) return true;
		if( text == 'false' ) return false;
		return this.fatalError( '"' + text + '" is not a boolean');
	},
	op_true							: function ( param, form_element, options, arguments, text )
	{
		return true;
	},
	op_false							: function ( param, form_element, options, arguments, text )
	{
		return false;
	},
	op_this							: function ( param, form_element, options, arguments, text )
	{
		var param_type   = param.getAttribute( 'type' );
		var element_type = form_element.type.replace(/-.+/,'');
		
		if( param_type == element_type )
		{
			if( param_type == 'checkbox' ) return form_element.checked ? true : false;
			if( param_type == 'radio' ){
				if( form_element.checked ) return form_element.value;
				return null;
			}
			return form_element.value;
		}
		else
		{
			return this.fatalError( param.nodeName+' should be a '+param_type+' but it\'s a '+element_type );
		}
	},
	parseEvaluation				: function ( node )
	{
		// Define the basic task
			var task = {
				op        : node.nodeName == 'condition' || node.nodeName == 'group_condition' ? 'and' : node.nodeName,
				options   : {},
				arguments : [],
				text      : this.textFromNode( node )
			};
			
			if( node.nodeName == 'group_condition' ) task.group_condition = true;
		
		// The various attributes
		   if( node.attributes )
			{
				var attributes = node.attributes;
				for( var i = 0; i < attributes.length; ++i )
				{
					task.options[ attributes[i].nodeName ] = attributes[i].nodeValue;
				}
			}

		// The children
			if( node.childNodes )
			{
				var children = node.childNodes;
				for( var i = 0; i < children.length; ++i )
				{
					var child = children[i];
					if( child.nodeType == 1 ) task.arguments.push( this.parseEvaluation( child ) );
				}
			}
				
		return task;
	},
	getConditionFromParam		: function ( param )
	{
		var conditions = param.getElementsByTagName('condition');
		if( conditions.length == 1 ) return conditions[0];
		return null;		
	},
	getGroupConditionFromParam : function ( param )
	{
		var conditions = param.getElementsByTagName('group_condition');
		if( conditions.length == 1 ) return conditions[0];
		return null;
	},
	getParams						: function ()
	{
		var data = {};
		var params = this.xml.getElementsByTagName( 'params' );
		params = params[0];
		for( var i = 0; i < params.childNodes.length; ++i )
		{
			var node = params.childNodes[i];
			if( node.nodeType != 1 ) continue;
			data[node.nodeName] = node;
		}
		return data;
	},
	formElement						: function ( name )
	{
		if( this.form_elements[name] ) return this.form_elements[name][0];
		return this.fatalError( 'Missing form parameter - "'+name+'"' );
	},
	formElements					: function ( name )
	{
		if( this.form_elements[name] ) return this.form_elements[name];
		return this.fatalError( 'Missing form parameters - "'+name+'"')
	},
	loadForm							: function ( form )
	{
		var form_elements = {};
		var tags = [ 'SELECT', 'INPUT', 'TEXTAREA' ];
		for( var i = 0; i < tags.length; ++i )
		{
			var elements = this.form.getElementsByTagName(tags[i]);
			for( var z = 0; z < elements.length; ++z ){
				var name = elements[z]['name'];
				if( name == 'xml_validation' && elements[z].type == 'hidden' ){
				} else {
					if( !form_elements[name] ) form_elements[name] = [];
					form_elements[name].push( elements[z] );
				}
			}
		}
		this.form_elements = form_elements;
	},
	loadXML							: function ( uri, no_cache )
	{
		// If you want to avoid using cached versions of the xml, this will stick a random number on the end of the url
			if( no_cache )
			{
				uri += uri.match( /\?/ ) ? '&' : '?';
				uri += 'defeat_cache='+Math.random();
			}

		var ajax = FormValidate.ajaxObject();
		ajax.open( 'GET', uri, true );
		var self = this;

		ajax.onreadystatechange = function() {
			
			if( ajax.readyState != 4 ) return;
			if( (ajax.status+"").match(/^[45]/) ) return self.systemError('Bad status code for xml at '+uri+' ('+ajax.status+')'); 
			if( ajax.status != 200 ) return;
			
			try {
				// Get and parse the XML
					var xml;
					try {
						xml = ajax.responseXML.lastChild;
						if( xml == null ) return self.xmlError( 'XML Parsing error on '+uri );
					} catch ( e ) {
						return self.xmlError( e );
					}
					if( xml.nodeName == 'FormValidate' ){
						self.xml = xml;
						
						FormValidate.addEvent( self.form, 'submit', function ( event )
						{
							try {
								if( self.evaluating )
									return self.stopEvent( event );
								else
									self.evaluating = true;
								
								if( !self.onsubmit() ) self.stopEvent( event );
								self.evaluating = false;
							} catch ( e ) {
								return self.systemError( "E3:"+e );
							}
						});
						
					} else if ( xml.nodeName == 'parsererror' ){
						return self.xmlError( 'Basic parse failure on '+uri+":\n\n"+xml.textContent );
					} else {
						return self.xmlError( 'Basic parse failure on '+uri );
					}
			} catch ( e ){
				return self.systemError( "E4:"+e );
			}
		};	
		ajax.send( null );
	},
	unshift							: function ( item, array )
	{
		var new_array = [ item ];
		for( var i = 0; i < array.length; ++i ) new_array.push( array[i] );
		return new_array;
	},
	buildDate						: function ( format, string )
	{
		var year, month, day;
		if( typeof(format) != 'string' ) return this.fatalError( 'Missing date format' );

		if( format == 'special' )
		{
			var date;
			if( string == 'today'           ) date = new Date();
			if( string == 'yesterday'       ) date = new Date( new Date().getTime()-86400000 );
			if( string == 'tomorrow'        ) date = new Date( new Date().getTime()+86400000 );

			if( typeof(date) == 'undefined' ) return null;
			return new Date( this.xbYear(date), date.getMonth(), date.getDate() );
		}

//		if( format.match( /^(YYYYMMDD|YYYY-MM-DD|YYYY_MM_DD|YYYY\/MM\/DD)$/i ) )
		if( format.match( /^(YMD|DMY)$/i ) )
		{
			string = string.replace(/[^0-9]+/g,'');

			if( string.match( /^[0-9]{8}$/ ) )
			{
			    if (format == 'ymd') {
				
				year  = string.substr(0,4);
				month = ""+(string.substr(4,2)-1);
				day   = string.substr(6,2);

			    } else {

				year  = string.substr(4,4);
				month = ""+(string.substr(2,2)-1);
				day   = string.substr(0,2);
			    }
			}
			else
			{
				return null;
			}
		}
		else
		{
			return this.fatalError('Unrecognised date format: '+format );
		}

		var date = new Date( year, month, day );

		if( this.xbYear(date) != year  ) return null;
		if( date.getMonth()       != month ) return null;
		if( date.getDate()        != day   ) return null;
		return date;
	},
	xbYear							: function ( date )
	{
		var x = date.getYear();
		var y = x % 100;
		y += ( y < 38 ) ? 2000 : 1900;
		return y;
	},
	systemError						: function ( message )
	{
		alert( 'System Error: ' + message );
		return false;
	},
	fatalError						: function ( message )
	{
//		alert( 'Fatal: ' + message );
		throw( 'Fatal: ' + message );
	}, 
	xmlError							: function ( message )
	{
		return this.systemError( "XML Error: "+message );
	},
	textFromNode					: function ( node )
	{
		var content = node.textContent;
		if( typeof( content ) != 'string' )
		{
			if( node.childNodes.length == 1 )
				content = node.childNodes[0].nodeValue;
		}
		
		if( typeof( content ) == 'string' ) return content.replace( /^\s*(.*?)\s*$/,'$1','gs' );
		return '';
	},
	enableClientValidation		: function ()
	{
		this.clientValidation = true;
	},
	disableClientValidation		: function ()
	{
		this.clientValidation = false;
	},
	stopEvent						: function ( event )
	{
		if( !event ) event = window.event;
		
		event.cancelBubble = true; // IE 
		event.returnValue  = false;
		if( event.stopPropagation ) // Firefox
		{
			event.stopPropagation();
			event.preventDefault();
		}
	}
};

FormValidate.ajaxObject = function ()
{
	try {return new XMLHttpRequest();}
	catch (error) {}
	try {return new ActiveXObject("Msxml2.XMLHTTP");}
	catch (error) {}
	try {return new ActiveXObject("Microsoft.XMLHTTP");}
	catch (error) {}
	throw new Error("Could not create HTTP request object.");
}

FormValidate.addEvent = function ( el, event, handler )
{
	if( el.addEventListener )
	{	
		el.addEventListener( event, handler, false );
	}
	else if( el.attachEvent )
	{
		el.attachEvent( 'on' + event, handler );
	}
	else
	{
		var oldHandler = el['on'+event];
		el['on'+event] = function(e) {
			if( oldHandler ) oldHandler();
			handler( event );
		}
	}
};

FormValidate.addCaptcha = function ( hidden_field, script_uri )
{
	if( typeof( hidden_field ) == 'string' ) hidden_field = document.getElementById( hidden_field );
	
	var img = document.createElement( 'img' );
	img.alt = 'Loading captcha image...';
	hidden_field.parentNode.insertBefore( img, hidden_field );

	var loadCaptcha = function ()
	{
		var ajax = FormValidate.ajaxObject();
		ajax.open( 'GET', script_uri, true );
		ajax.onreadystatechange = function() {
			if( ajax.readyState != 4 ) return;
			if( (ajax.status+"").match(/^[45]/) ) return alert('Bad status when requesting '+script_uri+' ('+ajax.status+')'); 
			if( ajax.status != 200 ) return;
			var results = ajax.responseText.split("\n");
			if( results.length == 2 )
			{
				hidden_field.value = results[0];
				img.src = results[1];
			}
		};
		ajax.send( null );
	};
	
	FormValidate.addEvent( img, 'click', loadCaptcha );
	img.title="Click to show a new captcha";
	
	loadCaptcha();
}
