/*
* This Javascript file provides the Javascript functions necessary for the forms built with the CSFormFactory library to work correctly.
* It does not need to be loaded individually, it will automatically be loaded when the CSFormFactory class is called.
*/

function formfactory (id) {
	this.$root = $_(id, this, 'root');
	this.$root.__formFactoryInstance = this;
	this.errors = new Hash();
	this.id = id;
	this.ready = true;
	this.ajax = true;
	this.return_type = 'json';
	this.flashUploadConfigs = [];
	this.flashUploaders = {};
	this.htmlUploadConfigs = [];
	this.lang = null;
	this.submit_button = null; // Holds the name of the submit button used to submit the form. Passed through the AJAX call as the "submit_button" parameter so we can figure out which submit was used on submit server side.
	
	this.initialize = function()
	{
		this.lang = new CSLang('formfactory_js');
		this.initializeAction();
		this.initializeSubmit();
		this.initializeValidation();
		this.initializePasswordStrength();
		this.initializeRemainingChars();
		this.initializeToolTips();
		this.initialize_location_selects();
		this.initialize_flash_uploads();
		this.initialize_date_fields_simple_calendar();
		this.initializeTSDateTimeFields();
	};
	
	
	this.initializeTSDateTimeFields = function()
	{
		this.$root.select('input[cs-type="ts-date"]').each(function(elem)
		{
			var tf = $(elem.readAttribute('id').replace(/_date$/, '_time'));
			var calendar = CSCalendar.factory(elem);
			if ( tf )
			{
				var f = function(cal)
				{
					cal.getSelectedDate(true).fromLocaleTimeString(tf.value, tf.readAttribute('time-format'));
				};
				f(calendar);
				tf.observe('change', f.bind(null, calendar));
			}
			var ef = (function(event)
			{
				//this.validateElement.bind(this, elem, true, undefined, true).defer();
				event.element = function()
				{
					return elem;
				};
				this.validateOnChange(event);
			}).bind(this);
			calendar.observe('change', ef);
		}, this);
	};
	

	this.addCallback = function(run_time, functionPath)
	{
		var cb = {callback: functionPath, run_time: run_time},
			cbs = $A((this.$root.readAttribute('callback') || '[]').evalJSON());
		cbs.push(cb);
		this.$root.writeAttribute('callback', Object.toJSON(cbs));
	};
	
	
	this.initializeRemainingChars = function()
	{
		this.$root.select(['input[render-remaining-chars-to]', 'textarea[render-remaining-chars-to]']).each(function(element)
		{
			var func = function()
			{
				var max = parseInt(this.readAttribute('maxlength'), 10);
				var r = String(this.getValue()).length;
				var t = $(this.readAttribute('render-remaining-chars-to'));
				if ( !max || !t )
				{
					return;
				}
				r = max - r;
				t.innerHTML = r;
			}.bind(element);
			$A(['change', 'keyup', 'keypressed', 'paste']).each(function(name)
			{
				element.observe(name, func);
			});
			func();
		}, this);
	};
	
	this.initializePasswordStrength = function()
	{
		$$('#'+this.id+' input[password_strength=Y]').each(
			function( element ){
				var element_id = element.id;
				var indicator = $('password_strength_' + element_id );
				
				if( element && indicator )
				{
					var pw_strength = new Password_strength( indicator, element );
				}
			}
		);
	};
	
	this.initialize_date_fields_simple_calendar = function(){
		var imgCalendar_Click = function(e, targetInputId, renderCalendarWidgetTo){
			CalendarUtil.showCalendar(targetInputId, e, renderCalendarWidgetTo);
		};
		
		var imgDateTime_Click = function(e, targetInputId, renderCalendarWidgetTo){
			DateTimeUtil.showCalendar(targetInputId, e, renderCalendarWidgetTo);
		};

		if($$('#'+this.id+' input.formfactory_date_field')){ // progressively enhances the html to have a datepicker.
			$$('#'+this.id+' input.formfactory_date_field').each(function(handler_data, index) {
				var renderCalendarWidgetTo = handler_data.getAttribute('formfactory-render-calendar-widget-to');
				var calendar_trigger = Builder.node("img", {id: handler_data.id+"_trigger",className: "scal_trigger_class", src: "/images/date_picker.png"});
				handler_data.insert({after: calendar_trigger});
				calendar_trigger.observe("click", imgCalendar_Click.bindAsEventListener(this, handler_data.id, renderCalendarWidgetTo));
			});
		}
		
		if($$('#'+this.id+' input.formfactory_date_time_field')){ // progressively enhances the html to have a datepicker.
			$$('#'+this.id+' input.formfactory_date_time_field').each(function(handler_data, index) {
				var renderCalendarWidgetTo = handler_data.getAttribute('formfactory-render-calendar-widget-to');
				var calendar_trigger = Builder.node("img", {id: handler_data.id+"_trigger",className: "scal_trigger_class", src: "/scal_calendar.jpg"});
				handler_data.insert({after: calendar_trigger});
				calendar_trigger.observe("click", imgDateTime_Click.bindAsEventListener(this, handler_data.id, renderCalendarWidgetTo));
			});
		}
		

	};

	this.initializeAction = function () {
		if( this.$root.readAttribute('ajax_action') ){
			this.$root.writeAttribute('action',this.$root.readAttribute('ajax_action'));
		}
	};
	
	/**
	* Sets up each form created by the formfactory to call the formfactory.formSubmit function when it is submitted.
	*/
	this.initializeSubmit = function () {
		// Initialize "click" handlers for each submit button on the form.
		this.$root.select('input[type=submit]').each(function (el) {
			el.observe('click', this.submitButtonHandler.bind(this));
		}, this);

		this.$root.stopObserving('submit'); // Make sure we only have ONE submit handler on the form submission.
		this.$root.observe('submit', this.formSubmitHandler.bind(this));
	};
	
	this.formSubmitHandler = function (event) {
		if (this.ajax) { // Only stop normal submission if the form isset to submit via ajax.
			Event.stop(event); // Stop the form from submitting normally.
		}
		
		// First, set the focus on the submit button. This should finish validation
		// on any fields we're still in, and lets the "submit on enter" functionality of
		// some browsers work as expected:
		var submit_button = this.$root.select('input[type="submit"],button[type="submit"]').first();
		if ( submit_button && submit_button.focus )
		{
			// saving the scroll position and restore it after focusing since the focus is scrolling
			// the page
			var pos = {x: window.scrollX, y: window.scrollY};
			submit_button.focus();
			window.scrollTo(pos.x, pos.y);
		}
		
		this.formSubmit.bind(this).delay(0.25, event.target, this); // Call form submit with a quarter second delay so that the validation (triggered above) can finish running. Bug fix for IE 8.
	};
	
	this.submitButtonHandler = function (event) {
		this.submit_button = event.target.name; // Sets the form factory's submit_button to the event target's "name" property when a submit button is clicked.
	};
	/*
	 * Reads the attribute "callback" from the form and executes each of them that
	 * are matching with "runtime".
	 *
	 * Example of how this attribute is rendered:
	 * <form callback='[{"callback":"afterCouchRequestSubmit","run_time":"after"},{"callback":"Prototype.emptyFunction","run_time":"afterClientSideValidation"}]' ...... />
	 *
	 *
	 * @context is expected to be the current formfactory instance.
	 * @param target the formfactory instance
	 * @param argsForCallback the arguments that should be passed to the callback. must be a JSON string. i.e. {arg1: value1, arg2: value2}
	 * @param runtime - which part of the flow are we executing? ex. after submit, before submit, ...
	 * @param afterEachCallBackFailure_callback - what to do immediatly after each callback failed. its a callback of a callback. hmmm...
	 * @return the number of callbacks matching the specified runtime.
	 */
	this._invokeCallbacks = function(target, argsForCallback, runtime, afterEachCallBackFailure_callback)
	{
		var callbacksFoundCounter = 0;
		if (target.readAttribute('callback') != '[]') {
			// Call the designated callback functions.
			var handlers = target.readAttribute('callback').evalJSON();
			$A(handlers).each(function(handler_data)
			{
				if (handler_data.run_time == runtime) { // Only run the function if it is tagged with the runtime that we're interested
					callbacksFoundCounter++;
					var handler = this._resolveHandler(handler_data.callback);
					if ( !handler.apply(null, argsForCallback) )
					{
						(afterEachCallBackFailure_callback || Prototype.emptyFunction)();
					}
				}
			}, this);
		}

		return callbacksFoundCounter;
	};


	/**
	 * Resolve the handler describe as a js name (object.propertie.someMethod) or
	 * return simply the parameter in case it's already a function
	 * @private
	 * @param {Object} handlerPath The handler, as a string or directly the function
	 * @return Returns the function to call directly, which is correctly binded on
	 * the correct object
	 * @type Function
	 */
	this._resolveHandler = function(handlerPath)
	{
		if ( Object.isFunction(handlerPath) )
		{
			return handlerPath;
		}
		return Function.fromPath(handlerPath);
	};

	
	this.formSubmit = function (target, instance, submitted) {
		if (!submitted) submitted = 'manual';
		
		// First clean up the errors array:
		var cleaned_errors = instance.clean_errors();
		
		// Check to see if there are any errors found.
		if (cleaned_errors.size() > 0) {
			// Display the errors:
			this.displayValidationErrors(cleaned_errors);
	
			var callbacksFound = instance._invokeCallbacks.bind(instance, target, [target, cleaned_errors], 'afterClientSideValidation', function(){
				submit_after_handlers = false;
			})();
			
			// if we didnt specify any callback, then do the default behaviour.
			if( callbacksFound < 1 ){
				this.displayError(this.lang.translate('errors_on_form'));
			}
		} else {
			// First clear out any helpfull tool tips:
			instance.clearToolTips();
			
			var submit_after_handlers = true;
			
			instance._invokeCallbacks.bind(instance, target, [this, target], 'before', function(){
				submit_after_handlers = false;
			})();
			
			if (submit_after_handlers && instance.ajax && instance.ready) {
				// Set ready = false so we don't try to double submit.
				instance.ready = false;
				instance._invokeCallbacks.bind(instance, target, [this], 'beforeAjaxSubmit')();

				// Submit the form.
				var csr = csr_v2_formfactory(instance.$root, {
					type: instance.return_type,
					parameters: {'submitted': submitted, 'submit_button': this.submit_button},
					onComplete: function(transport){
						instance.ready = true;
						instance._invokeCallbacks.bind(instance, target, [this, transport], 'afterAjaxSubmit')();
					}.bind(this),
					on0: instance.displayError.bind(instance, this.lang.translate('unable_to_reach_server')),
					onFailure: function(transport){
						instance.displayError.bind(instance, this.lang.translate('something_is_broken'))();
						instance._invokeCallbacks.bind(instance, target, [this, transport], 'afterAjaxFailure')();
					}.bind(this),
					onSuccess: instance.displayResult.bind(instance)
				});
				
				instance._invokeCallbacks.bind(instance, target, [this, csr], 'ajaxStarted')();
			}  
			
			instance.displayToolTips();
		}
	};
	
	/**
	* Handles displaying a message or errors when the form is submitted.
	*/
	this.displayResult = function (data, errors, item) {
		var fadeElementFn = function(el){
			el.fade();
		};

		// finds an element ending with input_name.
		// because formfactory automatically appends "date_" & "time_" to date/time fields respectively we need to lookup for these cases.
		var lookupInput = function(input_name){
			var matches = $A();
			var match = undefined;
			if (this.$root.select('[name="date_time_'+input_name+'"]').length > 0)
			{
				matches = this.$root.select('[name="date_time_'+input_name+'"]');
			}
			else if (this.$root.select('[name="date_'+input_name+'"]').length > 0)
			{
				matches = this.$root.select('[name="date_'+input_name+'"]');
			}
			else if (this.$root.select('[name="time_'+input_name+'"]').length > 0)
			{
				matches = this.$root.select('[name="time_'+input_name+'"]');
			}
			else if (this.$root.select('[name="'+input_name+'_date"]').length > 0 &&
					this.$root.select('[name="'+input_name+'_time"]').length > 0 )
			{
				matches = this.$root.select('[name="'+input_name+'_date"]');
			}
			else if (this.$root.select('[name="'+input_name+'"]').length > 0)
			{
				matches = this.$root.select('[name="'+input_name+'"]');
			}

			// Return only if the element is visible
			matches.each(function(input) {
				var new_id;
				// This if block detects and compensates for our location autocompletes, which acutaly store their data in a hidden field and remove the "name" attribute from the user visible field.
				if ( input.id.indexOf('__data__field') > 0)
				{
					new_id = input.id.replace('__data__field', '');
					new_id = this.id+'_'+new_id;
					input = $(new_id);
				}
				if ( input.id == 'recaptcha_response_field' ) // Handles recaptchas.
				{
					new_id = this.id+'_'+input.id;
					match = {id: null};
					match.id = new_id;
				} else if ( real_visible(input) )
				{
					match = input;
				}
			}, this);

			

			return match;
		};

		var globalMessage = $A();
		if (errors) {
			var scroll_to = null;
			var scroll_to_offset = null;
			
			for (var i in errors)
			{
				// First let's get the ID we're looking for:
				var search = lookupInput.bind(this)(i);
				if (search != undefined)
				{
					var search_id = search.id;
					
					if ($('form_error_'+search_id)) {
						$('form_error_'+search_id).update (errors[i]);
						$('form_error_'+search_id).show();
						this.errors.set(search_id, errors[i]); // Update number of known errors

						// Determine if this is the highest element in the document with an error.
						var this_offset = $('form_error_'+search_id).cumulativeOffset();
						if (!scroll_to_offset || this_offset.top < scroll_to_offset)
						{
							scroll_to_offset = this_offset.top;
							scroll_to = $('form_error_'+search_id);
						}
					}
				}
				else if ( Object.isString(errors[i]) )
				{
					globalMessage.push(errors[i]);
					continue;
				}
				
			}
			
			// Get some information about the document's scroll position and height.
			var doc_offsets = document.viewport.getScrollOffsets();
			var doc_height = document.viewport.getDimensions().height;
			
			// Scroll to element scroll_to only if it is currently off the screen.
			if ( (doc_offsets.top > scroll_to_offset || doc_offsets.top + doc_height < scroll_to_offset) && scroll_to )
			{
				scroll_to.scrollTo();
			}
			
			if (data.message && $('message')) {
				this.displayError(data.message);
			} 
		} else if (data.message && $('message')) {
			this.displayError(data.message);
		} else if ($('message')) {
			this.displayError('Data Saved');
		}

		// make errors false if no errors, 
		// otherwise its hard to compare because of the invasive prototype way of adding extra things to the objects
		// therefore them never being empty, if you know a cleaner way of false when no errors (feel free)
		if(errors && Object.toJSON(errors) == '{}'){
			errors = false;
		}

		// make extra json data false if no extra data
		var extra_json_data = item.extra_json_data;
		if( !extra_json_data ){
			extra_json_data = {};
		}
		
		// showing global message
		if ( globalMessage.length > 0 )
		{
			this.displayError(globalMessage.join('<br/>'));
		}

		// Run any "after" callbacks.
		this._invokeCallbacks(this.$root, [this.$root, data, errors, extra_json_data], 'after');
	};
	
	/**
	* Called when there is a problem submitting the form.
	*
	* @this the formfactory instance
	*/
	this.displayError = function (errorMessage) {
		
		var form_message = $('message_'+this.id);
		
		if( !form_message ){
			// the div to show the form messages isn't there
			return undefined;
		}
		
		form_message.update(errorMessage);
		form_message.appear();
		form_message.fade.bind(form_message, {duration: 1}).delay(8);


		return true;
	};
	
	/**
	 * Called when an external script wants to evaluate all fields and display any errors.
	 */
	this.displayAllErrors = function() {
		this.$root.select('input', 'textarea').each( function (el ) {
			el.simulate('blur');
		});
		
		this.$root.select('select').each( function (el ) {
			el.simulate('change');
		});
	};
	
	/******************* Tool Tip Functions ******************/
	/*
	* Sets up all textarea tooltips.
	*/
	this.initializeToolTips = function ($root) {
		this.$root.select('input[title!=""], textarea[title!=""]').each(function (el) {
			el.observe('focus', this.toolTipFocus.bind(el));
			el.observe('blur', this.toolTipBlur.bind(el));
			
			if ( !el.value ) {
				el.value = el.title;
				el.addClassName('placeholder');
			}
		}, this);
	};
	
	this.toolTipFocus = function (event)
	{
		this.removeClassName('placeholder');
		if ( this.value == this.title )
		{
			this.value = '';
		}
	};
	
	this.toolTipBlur = function (event) {
		if ( this.title && !this.value ) {
				this.value = this.title;
		}
	};
	
	this.displayToolTips = function () {
		this.$root.select('input[title!=""], textarea[title!=""]').each(function (el) {
			if ( !el.value ) {
				el.value = el.title;
			}
		}, this);
	};
	
	this.clearToolTips = function () {
		this.$root.select('input[title!=""], textarea[title!=""]').each(function (el) {
			if (el.value == el.title) {
				el.value = '';
			}
		}, this);
	};
	
	/******************* Validation Functions *****************/
	
	/**
	* Sets up all input, select and textarea inputs to validate on change. Also runs initial error checks on
	* each field so that it can immediately display errors on submit.
	*/
	this.initializeValidation = function ($root) {
		var selected = $$('#'+this.id+' input', '#'+this.id+' textarea');
		selected.each(function (el) {
			if ( el.readAttribute( 'auto-complete-options' ) ) // This will be true if working with an autocomplete element.
			{
				el.observe( 'autocomplete_location:after_blur', this.validateOnChange.bind(this)); // Trigger on a special event fired AFTER the autocomplete's blur method is called. This fixes some bugs where tabbing into and out of a location autocomplete on a form resulted in errors.
			}
			else if ( !el.match('input[cs-type="ts-date"]') )
			{
				el.observe('blur', this.validateOnChange.bind(this));
			}
			this.validateElement(el, false, true); // Runs an initial validation check on this element, building an array of fields with errors givent the default data.
		}, this);
		
		selected = $$('#'+this.id+' select');
		selected.each(function (el)
		{
			if ( !el.match('select[cs-type="ts-time"]') )
			{
				el.observe('change', this.validateOnChange.bind(this));
			}
			this.validateElement(el, false, true); // Runs an initial validation check on this element, building an array of fields with errors givent the default data.
		}, this);

		// take all types and save if they got on focus
		selected = $$('#'+this.id+' input', '#'+this.id+' textarea', '#'+this.id+' select');
		selected.each(function (el) {
			el.observe('focus', function(event) {
				event.element().writeAttribute('got_focus','yes');
			});
		}, this);
	};
	
	/**
	* Called when a form element changes. Validates the form element.
	*/
	this.validateOnChange = function (event) {
		this.validateElement(event.element());

		// Re-check previous fields that are 'on focus''
		var selected = $$('#'+this.id+' [got_focus]');
		selected.each(function (el) {
			this.validateElement(el);
		}, this);
	};
	
	/**
	* Runs actual validation functions on the given element and updates the internal errors array.
	* @param element. The prototype extended version of the element to check.
	* @param boolean display_errors. Boolean representation of whether to display errors or not. Defaults to true.
	*/
	this.validateElement = function (element, display_errors, required_only, noAnimation)
	{
		var element_id, result;
		if (display_errors !== false)
		{
			display_errors = true;
		}
		
		var expected_date_format, error = '';
		
		if ( element.readAttribute('validation_rules') && element.visible() ) {
			
			var rules = eval("("+element.readAttribute('validation_rules')+")"); // Convert the objects validation attribute into object.

			for (var i in rules) {
				if (required_only && i !== 'required')
					continue;
					
				switch (i) {
					case 'required': {
						if (!this.required(element)) {
							error = this.lang.translate('field_cannot_be_blank');
						}
					}break;

					case 'length': {
						if (!this.length(element, rules[i])) {
							error = this.lang.translate('must_be_shorter_than', {'length': rules[i]});
						}
					}break;

					case 'minlength': {
						if (!this.minlength(element, rules[i])) {
							error = this.lang.translate('must_be_greater_than', {'length': rules[i]});
						}
					}break;

					case 'date': {
						if (!this.date_check(element)) {
							expected_date_format = common_localized_properties.getProperty('date_format', 'dd/mm/yyyy');
							error = this.lang.translate('date_format', {'date_format': expected_date_format});
						}
					}break;

					case 'time': {
						if (!this.time_check(element)) {
							error = this.lang.translate('time_format', {'time_format': '12:00 PM'});
						}
					}break;

					case 'date_time': {
						if (!this.date_time_check(element)) {
							expected_date_format = common_localized_properties.getProperty('date_format', 'dd/mm/yyyy');
							error = this.lang.translate('date_time_format', {'date_time_format': expected_date_format + ' HH:MM AM'});
						}
					}break;
					
					case 'matches': {
						for (var match in i) {
							if (!this.matches_check(element, match)) {
								error = this.lang.translate('must_match_field', {'match': match});
							}
						}
					}break;
					
					case 'chars': {
						for (var chars in i) {
							if (!this.chars_check(element, chars)) {
								error = this.lang.translate('must_contain_characters', {'chars': chars});
							}
						}
					}break;
					
					case 'email': {
						if (!this.email_check(element)) {
							error = this.lang.translate('valid_email');
						}
					}break;
					
					case 'url': {
						if (!this.url_check(element)) {
							error = this.lang.translate('valid_url');
						}
					}break;
					
					case 'ip': {
						if (!this.ip_check(element)) {
							error = this.lang.translate('valid_ip');
						}
					}break;
					
					case 'phone': {
						if (!this.phone_check(element, rules.i)) {
							error = this.lang.translate('valid_phone_number');
						}
					}break;
					
					case 'alpha_numeric': {
						if (!this.alpha_numeric_check(element)) {
							error = this.lang.translate('only_letters_numbers');
						}
					}break;
					
					case 'alpha_dash': {
						if (!this.alpha_dash_check(element)) {
							error = this.lang.translate('only_letters_numbers_underscores_dashes');
						}
					}break;
					
					case 'digit': {
						if (!this.digit_check(element)) {
							error = this.lang.translate('digits_only');
						}
					}break;
					
					case 'numeric': {
						if (!this.numeric_check(element)) {
							error = this.lang.translate('numeric_value');
						}
					}break;
					
					case 'standard_text': {
						if (!this.standard_text_check(element)) {
							error = this.lang.translate('standard_text');
						}
					}break;
					
					case 'decimal': {
						if (!this.decimal_check(element)) {
							error = this.lang.translate('decimal_number');
						}
					}break;
					
					case 'custom': {
						var callback_class = null;
						var callback = rules[i];
						if (callback && callback.indexOf('.')) {
							callback_class = callback.substr(0, callback.indexOf('.'));
							callback = callback.substr(callback.indexOf('.')+1);
						}
						
						// Call the callback function:
						if(callback)
						{
							result = false;
							if (callback_class && window[callback_class] && window[callback_class][callback]) {
								result = window[callback_class][callback](element);
							} else if (window[callback]) {
								result = window[callback](element);
							}
							
							// Now, determine what to do with the result. If the callback function did not return TRUE,
							// asume there was an error. In this case, the callback should return the (translated) error.
							if (result !== true)
							{
								if (typeof(result) == 'string')
								{
									error = result;
								}
							}
						}
					}break;
					
					default: break;
				}
				if ( error )
				{
					break;
				}
			}
		}
		
		if (element.id && element.id == 'recaptcha_response_field')
		{
			element_id = this.id+'_'+element.id;
		}
		else if (element.id) {
			element_id = element.id;
		} else {
			element_id = element.name;
		}
		
		if (error) {
			this.errors.set(element_id, error); // Update number of known errors
			if ( display_errors && $('form_error_'+element_id) )
			{
				$('form_error_'+element_id).innerHTML = error;
				if ( noAnimation )
				{
					$('form_error_'+element_id).show();
				}
				else
				{
					$('form_error_'+element_id).appear( {duration: 0.3} );
				}
			}
		} else {
			// Unset the "errors" array for this element:
			this.errors.unset(element_id);
			if ( $('form_error_'+element_id) )
			{
				if ( noAnimation )
				{
					$('form_error_'+element_id).hide();
				}
				else
				{
					$('form_error_'+element_id).fade( {duration: 0.3} );
				}
			}
		}
	};
	
	/**
	* Loops through the internal errors array and displays each error:
	*/
	this.displayValidationErrors = function (errors)
	{
		if (!errors)
		{
			errors = this.errors;
		}
		errors.each(function(error) {
			if ($(error.key) != undefined && $('form_error_'+error.key) != undefined && !$(error.key).disabled)
			{
				$('form_error_'+error.key).innerHTML = error.value;
				$('form_error_'+error.key).show();
			}
		}, this);
	};
	
	/**
	* Checks if "element" meets the criteria for a required field.
	*/
	this.required = function (element) {
		if (!element.value) {
			return false;
		}
		
		return true;
	};
	
	/**
	* Checks if "element" value is less than or equal to max in length.
	*/
	this.length = function (element, max) {
		if (element.value.length > max) {
			return false;
		}
		
		return true;
	};
	this.minlength = function (element, min) {
		if (element.value.length < min) {
			return false;
		}

		return true;
	};
	/**
	 * Transforms a date from date_format to 'YYYY-MM-DD' format, so it can work
	 * with the date_check function.
	 * 
	 * @param <string> date_format the format being used by date
	 * @param <string> date the date
	 * @return <string> the date converted to the other format.
	 */
	this._normalize_date = function(date_format, date){
		if(date.empty() || typeof(date) != 'string' || typeof(date_format) != 'string'){
			return date;
		}

		// Identifies the indexes for the day, month, year position for a particular date format.
		// i.e. for 'dd/mm/yyyy' the day starts at position 0. for 'mm/dd/yyyy' the day starts at position 3.  
		var date_formats_to_indexes_map = new Hash();
		date_formats_to_indexes_map.set('mm/dd/yyyy', {day_start: 3, month_start: 0, year_start: 6, separator: '/'});
		date_formats_to_indexes_map.set('dd/mm/yyyy', {day_start: 0, month_start: 3, year_start: 6, separator: '/'});
		date_formats_to_indexes_map.set('dd.mm.yyyy', {day_start: 0, month_start: 3, year_start: 6, separator: '.'});
		date_formats_to_indexes_map.set('dd-mm-yyyy', {day_start: 0, month_start: 3, year_start: 6, separator: '-'});
		var separator_positions = {first: 2, second: 5};

		var mapper = date_formats_to_indexes_map.get(date_format);
		// to YYYY-MM-DD
		var normalized = date.substr(mapper.year_start, 4) +
			date.substr(separator_positions.first, 1) +
			date.substr(mapper.month_start, 2) + date.substr(separator_positions.second, 1) +
			date.substr(mapper.day_start, 2);
		normalized = normalized.gsub(mapper.separator, '-');

		return normalized;
	};
	
	// Original JavaScript code by Chirp Internet: www.chirp.com.au 
	// Please acknowledge use of this code by including this header.
	this.date_check = function (field) {
		var re, regs, normalized_date = this._normalize_date(common_localized_properties.getProperty('date_format', 'dd/mm/yyyy'), field.value);

		var minYear = 1902; 
		var maxYear = (new Date()).getFullYear()+50;

		// regular expression to match required date format 
		re = /^(\d{4})-(\d{1,2})-(\d{1,2})/; 

		if ( normalized_date )
		{
			if ( (regs = normalized_date.match(re)) )
			{ 
				if(regs[3] < 1 || regs[3] > 31) { 
					return false;
				} else if(regs[2] < 1 || regs[2] > 12) { 
					return false;
				} else if(regs[1] < minYear || regs[1] > maxYear) { 
					return false;
				} 
			} else { 
				return false;
			} 
		}
		
		return true;
	};
	
	// Original JavaScript code by Chirp Internet: www.chirp.com.au 
	// Please acknowledge use of this code by including this header.
	this.time_check = function (field) { 
		// regular expression to match required time format 
		var regs, re = /(\d{1,2}):(\d{2})(:00)? ([apAP][mM])?/; 
		
		if ( field.value )
		{ 
			if ( (regs = field.value.match(re)) )
			{ 
				if(regs[4]) { // 12-hour time format with am/pm 
					if(regs[1] < 1 || regs[1] > 12) { 
						return false;
					} 
				} else { // 24-hour time format 
					if(regs[1] > 23) { 
						return false;
					}
				}
				if(regs[2] > 59) { 
					return false;
				} 
			} else { 
				return false;
			}
		}
		return true; 
	};
	
	this.date_time_check = function (field) {
		return this.time_check(field) && this.date_check(field);
	};
	
	this.matches_check = function (field, match_field) {
		return field.value == $(match_field).value;
	};
	
	this.chars_check = function (field, chars) {
		re = new RegExp ('^['+chars+']*$');
		return field.value.match(re);
	};
	
	
	this.email_check = function (field) {
		re = /^([a-zA-Z0-9_\.\-\+])+\@(([a-zA-Z0-9\-])+\.)+([a-zA-Z0-9]{2,4})+$/i;
		return field.value.match(re);
	};
	
	this.url_check = function (field) {
		re = /^(ftp|http|https):\/\/(\w+:{0,1}\w*@)?(\S+)(:[0-9]+)?(\/|\/([\w#!:.?+=&%@!\-\/]))?$/i;
		return field.value.match(re);
	};
	
	this.ip_check = function (field) {
		var regs, re = /^(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})$/;
		if ( (regs = field.value.match(re)) )
		{
			if ( regs[1] == '127' || regs[1] == '255' || regs[1] === '0' ) {
				return false;
			}
			return true;
		}
		return false;
	};
	
	this.phone_check = function (field, rules) {
		var key;
		if (!rules) {
			rules = array(7,10,11);
		}
		
		var phone_num = field.value.replace(/\D+/, '');
		
		for (key in rules) {
			if (rules[key] == phone_num.length())
			{
				return true;
			}
		} 
		return false;
	};
	
	this.alpha_numeric_check = function(field) {
		return field.value.match(/^[A-Za-z0-9]+$/);
	};
	
	this.alpha_dash_check = function (field) {
		return field.value.match(/^[-a-zA-Z0-9_]+$/);
	};
	
	this.digit_check = function (field) {
		return field.value.match(/^[0-9]+$/);
	};
	
	this.numeric_check = function(field) {
		return field.value.match(/^[-]?[0-9]+[\.]?[0-9]*$/);
	};
	
	this.standard_text_check = function(field) {
		return field.value.match(/^[-a-zA-Z0-9 \._]+$/);
	};
	
	this.decimal_check = function (field) {
		return field.value.match(/^[0-9]+[\.]?[0-9]+$/);
	};
	
	this.clean_errors = function() {
		temp_errors = this.errors;
		temp_errors.each(function(error) {
			if ( !$(error.key) || !real_visible($(error.key)) )
			{
				temp_errors.unset(error.key);
			}
		}, this);
		
		return temp_errors;
	};
	
	this.enable = function() {
		this.ready = true;
		this.$root.select('button[type="submit"], input[type="submit"]').each(function(el)
		{
			el.disabled = false;
		}, this);
	};
	
	this.disable = function() {
		this.ready = false;
		this.$root.select('button[type="submit"], input[type="submit"]').each(function(el)
		{
			el.disabled = true;
		}, this);
	};
	
	/**************************** Select Location Functions *********************/
	this.initialize_location_selects = function () {
		var selected = $$('#'+this.id+' input.city_input', '#'+this.id+' input.country_input', '#'+this.id+' input.state_input');
		selected.each(function (el) {
			el.remove();
		}, this);
		selected = $$('#'+this.id+' select.city_select');
		selected.each(function (el) {
			el.show();
		}, this);
		selected = $$('#'+this.id+' select.region_select', '#'+this.id+' select.country_select', '#'+this.id+' select.state_select');
		selected.each(function (el) {
			el.show();
			el.observe('change', this.locationSelectChange.bind(this));
		}, this);
	};
	
	this.locationSelectChange = function (event) {
		details = event.target.id.split('_select_');
		group_id = details[0];
		select_type = details[1];
		
		csr ('/formfactory/load_location',
			{
				csr_options: {
					csr_type: 'CSR.IterativeHashedJSON',
					type: 'json',
					dataonly: false,
					no_hash: true
				},
				prototype_options: {
					parameters: {
						'select_type': select_type,
						'load_id': event.target.options[event.target.selectedIndex].value,
						'group_id': group_id
					},
					onSuccess: this.locationUpdateBoxes.bind(this)
				}
			}	
		);
	};
	
	this.locationUpdateBoxes = function (data) {
		var options = '';
		
		for (var id in data.locations) {
			if (id == 'each') {
				options = '<option value="">no data found</option>';
				break;
			}
			options += '<option value="'+id+'">'+data.locations[id]+'</option>';
		}
		
		if (data.select_type == 'region') {
			$(data.group_id+'_select_country').innerHTML = options;
			$(data.group_id+'_select_state').innerHTML = '<option>Select a Country</option>';
			$(data.group_id+'_select_city').innerHTML = '<option>Select a State</option>';
		} else if (data.select_type == 'country') {
			$(data.group_id+'_select_state').innerHTML = options;
			$(data.group_id+'_select_city').innerHTML = '<option>Select a State</option>';
		} else if (data.select_type == 'state') {
			$(data.group_id+'_select_city').innerHTML = options;
		}
	};
	
	/**************************** SWFUpload Functions *********************/
	
	this.initialize_flash_uploads = function() {
		for (var i = 0; i < this.flashUploadConfigs.length; i++) {
			$(this.flashUploadConfigs[i].custom_settings.element_id+'_html_upload').remove(); // Remove old HTML one.
			
			// Set up event handlers:
			this.flashUploadConfigs[i].file_queue_error_handler = this.file_queue_error;
			this.flashUploadConfigs[i].file_queued_handler = this.file_queued;
			this.flashUploadConfigs[i].upload_progress_handler = this.upload_progress;
			this.flashUploadConfigs[i].upload_error_handler = this.upload_error;
			this.flashUploadConfigs[i].upload_success_handler = this.upload_success;
			this.flashUploadConfigs[i].upload_complete_handler = this.upload_complete;
			
			this.flashUploadConfigs[i].minimum_flash_version = "9.0.28";
			
			this.flashUploadConfigs[i].swfupload_load_failed_handler = this.flash_upload_failed;
			
			this.flashUploadConfigs[i].file_queue_limit = 1;
			
			//this.flashUploadConfigs[i].requeue_on_error = true;
			
			this.flashUploaders[this.flashUploadConfigs[i].custom_settings.element_id] = new SWFUpload(this.flashUploadConfigs[i]);
		
			//Set up "onclick" handlers:
			$(this.flashUploadConfigs[i].custom_settings.element_id+'_start_upload').observe('click', this.start_file_upload.bind(this));
			$(this.flashUploadConfigs[i].custom_settings.element_id+'_stop_upload').observe('click', this.stop_file_upload.bind(this));
			$(this.flashUploadConfigs[i].custom_settings.element_id+'_upload_control').show();
		}
	};

	this.flash_upload_failed = function () {
		
		var content, iframe = document.createElement("iframe");
		iframe.setAttribute("id",this.customSettings.element_id+"_iframe");
		iframe.setAttribute("name",this.customSettings.element_id+"_iframe");
		iframe.setAttribute("width","0");
		iframe.setAttribute("height","0");
		iframe.setAttribute("border","0");
		iframe.setAttribute("style","width: 235px; height: 61px; border: none;");
		
		$(this.settings.button_placeholder_id).update (iframe);
		window.frames[this.customSettings.element_id+'_iframe'].name=this.customSettings.element_id+"_iframe";
		
		var add_form = function() {
			iframe = $(this.customSettings.element_id+'_iframe');
			
			var doc = null;
			if(iframe.contentDocument) { // Firefox, Opera
				doc = iframe.contentDocument;
			} else if(iframe.contentWindow) { // Internet Explorer
				doc = iframe.contentWindow.document;
				var ie = true;
			} else if(iframe.document) { // Others?
				doc = iframe.document;
			}
			
			content = '<form id="'+this.customSettings.element_id+'_uploadform">' +
				'<input type="file" name="'+this.customSettings.element_id+'" id="'+this.customSettings.element_id+'"' +
				'onchange="ajaxUpload(document.getElementById(\''+this.customSettings.element_id+'_uploadform\'), \''+this.settings.upload_url+'\', \''+this.customSettings.element_id+'_message\', \'File Uploading Please Wait...\', \'Uploading Error\');" />' +
				'<div id="'+this.customSettings.element_id+'_message"></div>' +
				'<script type="text/javascript" src="/js/ajaxupload.js"></script>' +
				'</form>';

			doc.open();
			doc.write(content);
			if (!ie) { // Only close the document for all other browsers, not for IE
				doc.close();
			}
		};
		window.setTimeout(add_form.bind(this), 100);
		
		
		$(this.customSettings.element_id+'_stop_upload').remove();// Remove the cancel button, not needed.
	};
	
	this.file_queue_error = function (file, error_code, message) {
		try {
			var error_name = "";
			if (error_code === SWFUpload.QUEUE_ERROR['QUEUE_LIMIT_EXCEEDED']) {
				error_name = "You have attempted to queue too many files.";
			}

			if (error_name !== "") {
				alert(error_name);
				return;
			}

			alert(message);
			
		} catch (ex) {
			this.debug(ex);
		}
	};
	
	this.file_queued = function (file) {
		var element_id = this.customSettings.element_id;
		$(element_id+'_upload_queue').insert('<li id="'+file.id+'_queue">'+file.name+'</li>');
		//var progress = new FileProgress(file,  this.customSettings.element_id+'_upload_progress_container');
	};
	
	this.start_file_upload = function(event) {
		Event.stop(event);
		element_id = event.target.id.replace('_start_upload', '');
		
		this.disable();
		
		try {
			this.flashUploaders[element_id].startUpload();
		}
		catch (ex) {
			// try to run the upload the other way, with HMLT / JS.
			ajaxUpload(
				this.$root,
				this.htmlUploadConfigs[element_id],
				element_id+'_upload_progress_container',
				'File Uploading Please Wait...',
				'Uploading Error');
		}
	};
	
	this.stop_file_upload = function(event) {
		Event.stop(event);
		element_id = event.target.id.replace('_stop_upload', '');
		this.flashUploaders[element_id].stopUpload();
	};
	
	this.upload_progress = function(file, bytes_loaded, bytes_total) {
		
		var element_id = this.customSettings.element_id;
		$$('#'+element_id+'_upload_queue #'+file.id+'_queue').each(function(el){
			el.remove();
		});
		
		try {
			var percent = Math.ceil((bytes_loaded / file.size) * 100);

			var progress = new FileProgress(file,  element_id+'_upload_progress_container');
			progress.setProgress(percent);
			if (percent === 100) {
				progress.setStatus("Finished Uploading");
				progress.toggleCancel(false, this);
			} else {
				progress.setStatus("Uploading...");
				progress.toggleCancel(true, this);
			}
		} catch (ex) {
			this.debug(ex);
		}
	};
	
	this.upload_success = function(file, server_data) {
		
		$(this.customSettings.element_id+'_start_upload').disabled = true;
		
		try {
			try {
				var server_data_json = eval("("+server_data+")");
			} catch (ex) {
			}
			var progress = new FileProgress(file,  this.customSettings.element_id+'_upload_progress_container');
			if (server_data_json) {
				for (x in server_data_json) {
					if (server_data_json[x].data.record_id) {
						var control_container = $(this.customSettings.element_id+'_upload_control');
						control_container.insert({'bottom': '<input type="hidden" name="'+this.customSettings.element_name+'[]" value="'+server_data_json[x].data.record_id+'" />'});
						if (server_data_json[x].data.message)
						{
							progress.setStatus(server_data_json[x].data.message);
						}
						else
						{
							progress.setStatus("Finished Uploading");
						}
					} else if (typeof (server_data_json[x].data) == 'string'){
						progress.setStatus(server_data_json[x].data);
					}
				}
			} else {
				progress.setStatus(server_data);
			}
		} catch (ex) {
			this.debug(ex);
		}
	};
	
	this.upload_complete = function(file) {
		this.customSettings.form.enable();
		
		try {
			/*  I want the next upload to continue automatically so I'll call startUpload here */
			if (this.getStats().files_queued > 0) {
				this.startUpload();
			} else {
				var progress = new FileProgress(file,  this.customSettings.element_id+'_upload_progress_container');
				progress.setComplete();
				//progress.setStatus("All uploads completed.");
				progress.toggleCancel(false);
			}
		} catch (ex) {
			this.debug(ex);
		}
	};
	
	this.upload_error = function(file, error_code, message) {
		var progress, friendly_message;
		try {
			switch (error_code)
			{
				case SWFUpload.UPLOAD_ERROR.FILE_CANCELLED:
					try {
						progress = new FileProgress(file,  this.customSettings.upload_target);
						progress.setCancelled();
						progress.setStatus("Cancelled");
						progress.toggleCancel(false);
					}
					catch (ex1) {
						this.debug(ex1);
					}
					break;
				case SWFUpload.UPLOAD_ERROR.UPLOAD_STOPPED:
					try {
						progress = new FileProgress(file,  this.customSettings.upload_target);
						progress.setCancelled();
						progress.setStatus("Stopped");
						progress.toggleCancel(true);
					}
					catch (ex2) {
						this.debug(ex2);
					}
					break;
				case SWFUpload.UPLOAD_ERROR.UPLOAD_LIMIT_EXCEEDED:
					break;
				default:
					switch (message) {
						case '504': {
							friendly_message = 'Upload timed out.';
						}break;

						default: {
							friendly_message = message;
						}break;
					}
					progress = new FileProgress(file,  this.customSettings.element_id+'_upload_progress_container');
					progress.setComplete();
					progress.setStatus(friendly_message);
					progress.toggleCancel(false);
					break;
			}

		} catch (ex3) {
			this.debug(ex3);
		}
	};
}


/**
 * Retreive the form factory instance looking at an element
 * 
 * @param {Object} form Either the form element or its ID
 * @return The instance of the formfactory or null if no form factory attached to this element
 * @type {formfactory}
 */
formfactory.getInstanceFromElement = function(form, instanciate)
{
	var el = $(form);
	if ( !el || !el.__formFactoryInstance )
	{
		if ( instanciate )
		{
			return new formfactory(form);
		}
		return null;
	}
	return el.__formFactoryInstance;
};


/******************* Functions for the FormFactory create_select_with_detail_forms method ******************/

var select_with_detail_forms = {

	insert_detail_forms: function (id, key_field, base_field, base_val) {
		var $select = $('select_with_detail_'+id);
		var key_val = $select[$select.selectedIndex].value;
		
		if ($(id+'_item_'+key_val)) {
			alert ('Sorry, you can\'t add the same thing twice!');
			return false;
		}
		
		// Generate the html template for the form we will be inserting.
		var tmp_template = $('select_with_detail_schema_'+id).innerHTML; // Load form HTML from template.
		
		// Insert hidden forms with base data.
		var hidden = '<input type="hidden" name="'+key_field+'" value="'+key_val+'" />'+"\n";
		if (base_field) {
			hidden += '<input type="hidden" name="'+base_field+'" value="'+base_val+'" />'+"\n";
		}
		tmp_template = tmp_template.substring(0,tmp_template.lastIndexOf('</form>'))+hidden+tmp_template.substring(tmp_template.lastIndexOf('</form>'));
		
		tmp_template = tmp_template.replace(/scrpt/g, 'script');
		
		script = 'factory_template_form_id.formSubmit($(\'template_form_id\'), factory_template_form_id);';
		temp_template = tmp_template.substring(0,tmp_template.lastIndexOf('</script>'))+script+tmp_template.substring(tmp_template.lastIndexOf('</script>'));
		
		var template = temp_template.gsub(/template_form_id/, id+'_select_with_detail_form_'+key_val);

		var output = '<li id="'+id+'_item_'+key_val+'">';
		output += $select[$select.selectedIndex].text+' <a href="#" onclick="select_with_detail_forms.toggle_detail_forms(\''+key_val+'\', \''+id+'\'); return false;">(show / hide detail)</a>';
		output += '<div id="'+id+'_item_form_'+key_val+'">';
		output += template;
		output += '</div>';
		output += '<img src="/images/delete.gif" onclick="select_with_detail_forms.delete_detail_forms(\''+key_val+'\', \''+id+'\'); return false;" />';
		output += '</li>';
		
		$('items_'+id).insert(output);
		return undefined;
	},
	
	toggle_detail_forms: function (item_id, id) {
		$(id+'_item_form_'+item_id).toggle();
		// Save stuff here.
	},
	
	delete_detail_forms: function (item_id, id) {		
		csr_v2_formfactory($(id+'_select_with_detail_form_'+item_id), {
			type: 'json',
			method: 'delete'
		});
		
		$(id+'_item_'+item_id).remove();
	}
};

function submit_form (form_id) {
	// Check to see if we're passed an object. If so, get it's id.
	if (typeof(form_id) == 'object')
		form_id = form_id.id;
		
	// Try to submit the form factory form:
	if (window['factory_'+form_id]) {
		window['factory_'+form_id]['formSubmit']($(form_id), window['factory_'+form_id], 'script');
		return true;
	}
	
	return false; // we failed.
}


/******************* Functions for the FormFactory habtm_detail Form Elements ******************/

var habtm_detail = {

	insert_detail_elements: function (id, field_name, fkey_field) {
		var $select = $(id+'_selector');
		var fkey_val = $select[$select.selectedIndex].value;
		
		if ($(id+'_habtm_item_'+fkey_val)) {
			alert ('Sorry, you can\'t add the same thing twice!');
			return false;
		}
		
		// Generate the html template for the form elemens we will be inserting.
		var template = $(id+'_sub_elements_template').innerHTML; // Load element HTML from template.
		template = template.replace(/fkey/g, fkey_val);
		template = template.replace(/disabled="1"/g, '');
		
		// Insert hidden field with base data.
		var hidden = '<input type="hidden" name="'+field_name+'['+fkey_val+']['+fkey_field+']" value="'+fkey_val+'" />'+"\n";

		template = hidden + template;
				
		var output = '<li id="'+id+'_habtm_item_'+fkey_val+'">';
		output += $select[$select.selectedIndex].text+' <a href="#" onclick="habtm_detail.toggle_detail_elements(\''+fkey_val+'\', \''+id+'\'); return false;">(show / hide detail)</a>';
		output += '<img src="/images/delete.gif" onclick="habtm_detail.delete_detail_elements(\''+fkey_val+'\', \''+id+'\'); return false;" />';
		output += '<div id="'+id+'_item_elements_'+fkey_val+'">';
		output += template;
		output += '</div>';
		output += '</li>';
		
		$('habtm_items_'+id).insert(output);
		
		reinitialize_form ($select.form.id);
		return undefined;
	},
	
	toggle_detail_elements: function (item_id, id) {
		$(id+'_item_elements_'+item_id).toggle();
		// Save stuff here.
	},
	
	delete_detail_elements: function (item_id, id) {		
		$(id+'_habtm_item_'+item_id).remove();
	}
};


function reinitialize_form (form_id) {
	// Check to see if we're passed an object. If so, get it's id.
	if (typeof(form_id) == 'object')
		form_id = form_id.id;
		
	// Try to submit the form factory form:
	if (window['factory_'+form_id]) {
		window['factory_'+form_id]['initialize']( true );
		return true;
	}
	
	return false; // we failed.
}




/* ******************************************
 *	FileProgress Object
 *	Control object for displaying file info 
 *  during a SWF upload
 * ****************************************** */

function FileProgress(file, targetID) {
	this.fileProgressID = "divFileProgress_"+file.name;

	this.fileProgressWrapper = document.getElementById(this.fileProgressID);
	if (!this.fileProgressWrapper) {
		this.fileProgressWrapper = document.createElement("div");
		this.fileProgressWrapper.className = "progressWrapper";
		this.fileProgressWrapper.id = this.fileProgressID;

		this.fileProgressElement = document.createElement("div");
		this.fileProgressElement.className = "progressContainer";

		var progressCancel = document.createElement("a");
		progressCancel.className = "progressCancel";
		progressCancel.href = "#";
		progressCancel.style.visibility = "hidden";
		progressCancel.appendChild(document.createTextNode(" "));

		var progressText = document.createElement("div");
		progressText.className = "progressName";
		progressText.appendChild(document.createTextNode(file.name));

		var progressBar = document.createElement("div");
		progressBar.className = "progressBarInProgress";

		var progressStatus = document.createElement("div");
		progressStatus.className = "progressBarStatus";
		progressStatus.innerHTML = "&nbsp;";

		this.fileProgressElement.appendChild(progressCancel);
		this.fileProgressElement.appendChild(progressText);
		this.fileProgressElement.appendChild(progressStatus);
		this.fileProgressElement.appendChild(progressBar);

		this.fileProgressWrapper.appendChild(this.fileProgressElement);

		document.getElementById(targetID).appendChild(this.fileProgressWrapper);
		Effect.Appear(this.fileProgressWrapper, 0);

	} else {
		this.fileProgressElement = this.fileProgressWrapper.firstChild;
		this.fileProgressElement.childNodes[1].firstChild.nodeValue = file.name;
	}

	this.height = this.fileProgressWrapper.offsetHeight;

}
FileProgress.prototype.setProgress = function (percentage) {
	this.fileProgressElement.className = "progressContainer green";
	this.fileProgressElement.childNodes[3].className = "progressBarInProgress";
	this.fileProgressElement.childNodes[3].style.width = percentage + "%";
};
FileProgress.prototype.setComplete = function () {
	this.fileProgressElement.className = "progressContainer blue";
	this.fileProgressElement.childNodes[3].className = "progressBarComplete";
	this.fileProgressElement.childNodes[3].style.width = "";

};
FileProgress.prototype.setError = function () {
	this.fileProgressElement.className = "progressContainer red";
	this.fileProgressElement.childNodes[3].className = "progressBarError";
	this.fileProgressElement.childNodes[3].style.width = "";

};
FileProgress.prototype.setCancelled = function () {
	this.fileProgressElement.className = "progressContainer";
	this.fileProgressElement.childNodes[3].className = "progressBarError";
	this.fileProgressElement.childNodes[3].style.width = "";

};
FileProgress.prototype.setStatus = function (status) {
	this.fileProgressElement.childNodes[2].innerHTML = status;
};

FileProgress.prototype.toggleCancel = function (show, swfuploadInstance) {
	this.fileProgressElement.childNodes[0].style.visibility = show ? "visible" : "hidden";
	if (swfuploadInstance) {
		var fileID = this.fileProgressID;
		this.fileProgressElement.childNodes[0].onclick = function () {
			swfuploadInstance.cancelUpload(fileID);
			return false;
		};
	}
};

