/**
 * Kaizen Framework - JavaScript
 * 
 * @author Vasil Georgiev Dinkov - Kaizen Web-Productions (http://www.kaizen-web.com)
 * @version 1.0
 * @copyright Copyright(C), Kaizen Web-Productions, 2004-2008, All Rights Reserved.
 * @package KaizenFramework
 */

/**
 * Form Functions (validation, others)
 * 
 * @author Vasil Georgiev Dinkov - Kaizen Web-Productions (http://www.kaizen-web.com)
 * @version 1.0
 * @copyright Copyright(C), Kaizen Web-Productions, 2004-2008, All Rights Reserved.
 * @package KaizenFramework
 * @subpackage Form
 */

/**
 * Class for form validation
 *
 * @param string	formId		The id of the form
 * @param object	options		Any custom options to override the default options
 */
Kaizen.FormValidate = function(formId, options) {
	var ref = this;
	this.options = {
		onValidated: null,					// Function that will be executed when all fields in the form are validated,
									// just before submitting - 'this' in the function will refer to the form element.
									// The function should return true/false to allow form submission or not.
		useJSAlertOnErrors: false,				// Whether to use a JS alert or a Kaizen.Alert popup on errors
		alertBoxOptions: {},					// Custom options for Kaizen.Alert.Box if 'useJSAlertOnErrors' is 'false'
		validateAsUserTypesTimeout: 2000,			// Timeout (milliseconds) for validating a field as the user types (i.e. on a pause larger than this timeout the field is validated)
		errorClass: 'KaizenFormValidateError',			// CSS class for the error message SPAN
		focusedFieldClass: 'KaizenFormValidateFocusedField',	// CSS class applied to the parent container of a given field when it is focused
		validFieldClass: 'KaizenFormValidateValidField',	// CSS class applied to the parent container of a given field when it is valid
		invalidFieldClass: 'KaizenFormValidateInvalidField',	// CSS class applied to the parent container of a given field when it is invalid
		passwordMeterClass: 'KaizenFormValidatePasswordMeter',	// CSS class for the password meter holder SPAN
		passwordMeterStrengthBoxClass: 'KaizenFormValidatePasswordMeterStrengthBox',	// CSS class for the strength color indicator box
		passwordMeterStrengthLabelClass: 'KaizenFormValidatePasswordMeterStrengthLabel',// CSS class for the strength level label
		passwordMeterLevelXClass: 'KaizenFormValidatePasswordMeterLevel',		// Prefix for the CSS class for each 5 levels of the password meter graph - i.e. KaizenFormValidatePasswordMeterLevel[1-5]
		calcPasswordStrengthRequirement:			// Default 'requirement' argument for the calcPasswordStrength function
			{'characters_after':2, 'password_length':8, 'requirement_bonus':5},
		calcPasswordStrengthMultiplier:				// Default 'multiplier' argument for the calcPasswordStrength function
			{'alphabet':4.5, 'alphabet_failure':32.5, 'alphabet_reverse':4.5, 'alphabet_reverse_failure':32.5, 'alpha_lc':1.5, 'alpha_lc_after':2.5, 'alpha_uc':1.5, 'alpha_uc_after':2.5, 'integer':4, 'integer_after':4, 'keyboard':2, 'keyboard_failure':32.5, 'keyboard_reverse':2, 'keyboard_reverse_failure':32.5, 'middle_character':2.5, 'numbers':4.5, 'numbers_failure':32.5, 'numbers_reverse':4.5, 'numbers_reverse_failure':32.5, 'requirement_bonus':5.5, 'repeat':6, 'string_length':7.5, 'password_length_penalty':15, 'symbol':6, 'symbol_after':6},
		calcPasswordStrengthAlphabet:				// Default 'alphabet' argument for the calcPasswordStrength function
			['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z'],
		calcPasswordStrengthKeyboard:				// Default 'keyboard' argument for the calcPasswordStrength function
			['', ['q', 'w', 'e', 'r', 't', 'y', 'u', 'i', 'o', 'p'], ['a', 's', 'd', 'f', 'g', 'h', 'j', 'k', 'l'], ['z', 'x', 'c', 'v', 'b', 'n', 'm']],
		emailFieldsValidationRegExp: ['^[A-Z0-9._%+-]+@[A-Z0-9.-]+\\.[A-Z]{2,4}$', 'i'],	// An array containing arguments for 'new RegExp()'
		cardsHolderClass: 'KaizenFormValidateCardsHolder',		// CSS class for the credit cards holder SPAN
		cardClass: 'KaizenFormValidateCard',				// CSS class for all card SPANs
		cardInactiveClass: 'KaizenFormValidateCardInactive',		// CSS class for the inactive (grayed out) card SPANs
		AmericanExpressClass: 'KaizenFormValidateAmericanExpress',	// CSS class for the American Express card SPAN
		DinersClubClass: 'KaizenFormValidateDinersClub',		// CSS class for the Diners Club card SPAN
		DinersClubCarteBlancheClass: 'KaizenFormValidateDinersClubCarteBlanche',	// CSS class for the Diners Club Carte Blanche card SPAN
		DinersClubInternationalClass: 'KaizenFormValidateDinersClubInternational',	// CSS class for the Diners Club International card SPAN
		DiscoverCardClass: 'KaizenFormValidateDiscoverCard',		// CSS class for the Discover card SPAN
		MaestroClass: 'KaizenFormValidateMaestro',			// CSS class for the Maestro card SPAN
		MasterCardClass: 'KaizenFormValidateMasterCard',		// CSS class for the Master Card card SPAN
		VisaClass: 'KaizenFormValidateVisa',				// CSS class for the Visa card SPAN
		VisaElectronClass: 'KaizenFormValidateVisaElectron',		// CSS class for the Visa Electron card SPAN
		Language: {						// An object containing any words that may be used
			'pleaseCorrectErrors': 'Please correct the errors in the form!', // no HTML!
			'waitForRunningUploads': 'There are still running upload transfers.\nPlease wait until all files are completely uploaded and then submit the form.', // no HTML!
			'OK': 'OK',
			'passwordStrength': 'Password Strength',
			'passwordStrengthLevels': ['Very Weak', 'Weak', 'Good', 'Strong', 'Very Strong']
		}
	};
	if (options)
		Kaizen.extendObject(this.options, options);

	this.form = document.getElementById(formId);

	// stores fields' requirements
	this.fieldsReqs = {};
	// stores any Kaizen.Form.Upload objects in the current form
	this.uploadObjects = [];

	this.form.onsubmit = this.validateForm.bind(this);
	Kaizen.DOM.IEDOMleaks.push([this.form, 'onsubmit']);
	var cur, att, nodes = this.form.getElementsByTagName('input');
	for (var i = 0, l = nodes.length; i < l; i++) {
		cur = nodes[i];
		att = cur.getAttribute('type');
		if (att != 'button' && att != 'submit') {
			Kaizen.DOM.addEvent(cur, 'focus', function(){ref.fieldFocus(this)});
			Kaizen.DOM.addEvent(cur, 'blur', function(){ref.fieldBlur(this)});
		}
	}
	nodes = this.form.getElementsByTagName('textarea')
	for (i = 0, l = nodes.length; i < l; i++) {
		cur = nodes[i];
		Kaizen.DOM.addEvent(cur, 'focus', function(){ref.fieldFocus(this)});
		Kaizen.DOM.addEvent(cur, 'blur', function(){ref.fieldBlur(this)});
	}
	nodes = this.form.getElementsByTagName('select')
	for (i = 0, l = nodes.length; i < l; i++) {
		cur = nodes[i];
		Kaizen.DOM.addEvent(cur, 'focus', function(){ref.fieldFocus(this)});
		Kaizen.DOM.addEvent(cur, 'blur', function(){ref.fieldBlur(this)});
	}

	if (!this.options.useJSAlertOnErrors)
		this.alertBox = new Kaizen.Alert.Box(this.options.alertBoxOptions);

	// stores creditcards data
	this.cardsData = {
		AmericanExpress: {
			name: 'American Express',
			length: [15],
			prefix: /^(34|37)/
		},
		DinersClubCarteBlanche: {
			name: 'Diners Club Carte Blanche',
			length: [14],
			prefix: /^30[0-5]/
		},
		DinersClubInternational: {
			name: 'Diners Club International',
			length: [14],
			prefix: /^36/
		},
		DiscoverCard: {
			name: 'Discover Card',
			length: [16],
			prefix: /^(6011|622[2-8][0-9]|6221[3-9][0-9]|62212[6-9]|6229[0-1][0-9]|62292[0-5]|64[4-9]|65)/
		},
		Maestro: {
			name: 'Maestro',
			length: [12,13,14,15,16,17,18,19],
			prefix: /^(5018|5020|5038|6304|6759|6761)/
		},
		MasterCard: {
			name: 'MasterCard',
			length: [16],
			prefix: /^5[1-5]/
		},
		VisaElectron: {
			name: 'Visa Electron',
			length: [16],
			prefix: /^(417500|4917|4913|4508|4844)/
		},
		Visa: {
			name: 'Visa',
			length: [16],
			prefix: /^4/
		}
	}

}

/**
 * Add validation for field
 *
 * @param string	fieldId		The id of the field
 * @param object	reqs		The requirements for the field
 */
Kaizen.FormValidate.prototype.add = function(fieldId, reqs) {
	var ref = this;

	var field = document.getElementById(fieldId);

	if (!field) {
		return;
		}

	if (reqs.uploadObject)
		this.uploadObjects.push(reqs.uploadObject);
	else
		this.addError(fieldId, reqs);

	Kaizen.DOM.addEvent(field, 'focus', function(){ref.fieldFocus(this)});
	Kaizen.DOM.addEvent(field, 'blur', function(){ref.fieldBlur(this)});
}

/**
 * Add validation for field
 *
 * @param string	fieldId		The id of the field
 * @param object	reqs		The requirements for the field
 */
Kaizen.FormValidate.prototype.addError = function(fieldId, reqs) {
	var ref = this;

	// save requirements
	if (typeof this.fieldsReqs[fieldId] == 'undefined')
		this.fieldsReqs[fieldId] = [];
	this.fieldsReqs[fieldId].push(reqs);

	var field = document.getElementById(fieldId);

	// init requirements
	if (reqs.validateOn) {
		Kaizen.DOM.addEvent(field, reqs.validateOn, function(){ref.validateField(fieldId)});
		if (reqs.groupWith)
			for (var i = 0, l = reqs.groupWith.length; i < l; i++)
				Kaizen.DOM.addEvent(document.getElementById(reqs.groupWith[i]), reqs.validateOn, function(){ref.validateField(fieldId)});
	}
	if (reqs.onChange)
		Kaizen.DOM.addEvent(field, 'change', eval(reqs.onChange));
	if (reqs.validateAsUserTypes) {
		Kaizen.DOM.addEvent(field, 'keyup', function(){ref.setValidateAsUserTypesTimeout(this, fieldId)});
		if (reqs.validateOn)
			Kaizen.DOM.addEvent(field, reqs.validateOn, function(){ref.clearValidateAsUserTypesTimeout(this)});
	}
	if (reqs.addPasswordMeter || reqs.supportedCards) {
		// make sure we would insert it after any SPANs following the field (for example, the validation flag SPAN) but before any server-side generated errors
		var nextSibling = field.nextSibling;
		var errorClass = reqs.errorClass || this.options.errorClass;
		while (nextSibling && (nextSibling.nodeName.toUpperCase() == 'SPAN' && !Kaizen.DOM.hasClass(nextSibling, errorClass) || nextSibling.nodeName.toUpperCase() == '#TEXT'))
			nextSibling = nextSibling.nextSibling;

		if (reqs.addPasswordMeter) {
			// create password strength meter elements
			var meterHolder = new Kaizen.Element('span', {'text':this.options.Language.passwordStrength, 'class':this.options.passwordMeterClass});
			meterHolder.append(new Kaizen.Element('br', ''));
			var box = new Kaizen.Element('div', {'text':' ', 'class':this.options.passwordMeterStrengthBoxClass});
			meterHolder.append(box);
			var strengthLabel = new Kaizen.Element('span', this.options.passwordMeterStrengthLabelClass);
			strengthLabel.innerHTML = '&nbsp;';
			meterHolder.append(strengthLabel);

			field.parentNode.insertBefore(meterHolder, nextSibling);
			Kaizen.DOM.addEvent(field, 'keyup', function(){ref.passwordMeterUpdate(this, fieldId)});
		} else {
			var cardsHolder = new Kaizen.Element('span', this.options.cardsHolderClass);
			var cur, card;
			for (var i = 0, l = reqs.supportedCards.length; i < l; i++) {
				cur = reqs.supportedCards[i];
				card = new Kaizen.Element('span', {'class':this.options.cardClass + ' ' + this.options[cur + 'Class'], 'title':cur == 'DinersClub' ? 'Diners Club' : this.cardsData[cur].name});
				cardsHolder.append(card);
			}
			cardsHolder.append(new Kaizen.Element('span', {'style':'clear:both;display:block;height:0;overflow:hidden;visibility:hidden;'}));

			field.parentNode.insertBefore(cardsHolder, nextSibling);
			Kaizen.DOM.addEvent(field, 'keyup', function(){ref.creditcardsUpdate(this, fieldId)});
			// update cards if we have value by default in the field
			if (field.value != '')
				this.creditcardsUpdate(field, fieldId);
		}
	}
	if (reqs.matchField)
		this.relateFieldsValidation(fieldId, [reqs.matchField]);
	else if (reqs.matchFieldsSum)
		this.relateFieldsValidation(fieldId, reqs.matchFieldsSum);
}

/**
 * Remove validation for field
 *
 * @param string	fieldId		The id of the field
 */
Kaizen.FormValidate.prototype.remove = function(fieldId) {
	var ref = this;

	var reqs = this.fieldsReqs[fieldId];
	if (!reqs)
		return;

	var field = document.getElementById(fieldId);
	if (reqs[0].addPasswordMeter) {
		// remove password strength meter
		var passwordMeter = field.nextSibling;
		while (!Kaizen.DOM.hasClass(passwordMeter, this.options.passwordMeterClass))
			passwordMeter = passwordMeter.nextSibling;
		field.parentNode.removeChild(passwordMeter);
	} else if (reqs[0].supportedCards || (reqs[1] && reqs[1].supportedCards)) {
		// remove credit cards holder
		var cardsHolder = field.nextSibling;
		while (!Kaizen.DOM.hasClass(cardsHolder, this.options.cardsHolderClass))
			cardsHolder = cardsHolder.nextSibling;
		field.parentNode.removeChild(cardsHolder);
	}
	delete this.fieldsReqs[fieldId];
}

/**
 * Disable validation for field temporary (do not remove its validation requirements altogether)
 *
 * @param string	fieldIdOrElm		The id of the field or the field element
 */
Kaizen.FormValidate.prototype.disable = function(fieldIdOrElm) {
	var elm;
	if (typeof fieldIdOrElm == 'string')
		elm = document.getElementById(fieldIdOrElm);
	else
		elm = fieldIdOrElm;
	if (typeof elm._noValidate != 'undefined')
		elm._noValidate++;
	else
		elm._noValidate = 1;
}

/**
 * Enable validation for field (if it has been disabled)
 *
 * @param string	fieldIdOrElm		The id of the field or the field element
 */
Kaizen.FormValidate.prototype.enable = function(fieldIdOrElm) {
	var elm;
	if (typeof fieldIdOrElm == 'string')
		elm = document.getElementById(fieldIdOrElm);
	else
		elm = fieldIdOrElm;
	if (typeof elm._noValidate != 'undefined')
		elm._noValidate--;
}

Kaizen.FormValidate.prototype.validateForm = function() {
	var isValid = true;
	for (var i in this.fieldsReqs) {
		if (!this.validateField(i, true))
			isValid = false;
	}
	if (!isValid) {
		this.showAlert(this.options.Language.pleaseCorrectErrors);
	} else if (this.uploadObjects.length > 0) {
		// if we have any uploads in the current form, check them for running transfers
		var allFinished = true;
		for (var i = 0, l = this.uploadObjects.length; i < l; i++) {
			if (this.uploadObjects[i].isInProgress()) {
				allFinished = false;
				break;
			}
		}
		if (!allFinished) {
			isValid = false;
			this.showAlert(this.options.Language.waitForRunningUploads);
		}
	}
	if (isValid && this.options.onValidated)
		isValid = this.options.onValidated.apply(this.form);

	return isValid;
}

Kaizen.FormValidate.prototype.validateField = function(fieldId, calledOnSubmit) {
	var reqs = this.fieldsReqs[fieldId];
	if (!reqs)
		return;

	var field = document.getElementById(fieldId);
	if (field._noValidate)
		return true;

	// remove all error messages for this field
	var errorClass = reqs[0].errorClass || this.options.errorClass;
	var errorMessage;
	var lastOfGroup;
	if (reqs[0].type == 'checkbox' && reqs[0].groupWith)
		lastOfGroup = document.getElementById(reqs[0].groupWith[reqs[0].groupWith.length - 1]);
	var nextSibling = (lastOfGroup || field).nextSibling;
	while (nextSibling) {
		if (Kaizen.DOM.hasClass(nextSibling, errorClass)) {
			errorMessage = nextSibling;
			nextSibling = nextSibling.nextSibling;
			errorMessage.parentNode.removeChild(errorMessage);
		} else {
			nextSibling = nextSibling.nextSibling;
		}
	}

	var isValid = true;
	var value = field.value;
	var cur, isValidCur, criticalError, successMessageHolder;
	for (var i = 0, l = reqs.length; i < l; i++) {
		// copy the requirements object and work with the copy as we may need to change some things (like tags in the error messages) and we don't want to affect the original stored requirements
		cur = Kaizen.extendObject({}, reqs[i]);
		isValidCur = true;

		// fix global (passed with the 'add()' method) 'decimalsNumber' option for the field
		if (!cur.decimalsNumber && reqs[0].decimalsNumber)
			cur.decimalsNumber = reqs[0].decimalsNumber;

		if (cur.required && (value == '' && reqs[0].type != 'checkbox' || reqs[0].type == 'checkbox' && !field.checked)) {
			criticalError = true;
			isValidCur = false;
			cur.error = cur.required;
		}
		if (isValidCur && (value != '' || reqs[0].type == 'checkbox') && reqs[0].type) {
			switch (reqs[0].type) {
				case 'string':
					if (cur.regExp)
						isValidCur = this.stringValidationRegExp(value, new RegExp(cur.regExp[0], cur.regExp[1]));
					if (isValidCur && cur.validChars)
						isValidCur = this.stringValidationValidChars(value, cur.validChars);
					if (isValidCur && cur.minLength)
						isValidCur = this.stringValidationMinLength(value, cur.minLength);
					if (isValidCur && cur.maxLength)
						isValidCur = this.stringValidationMaxLength(value, cur.maxLength);
					break;
				case 'int':
					var valueNum = this.numberValidationParseFloat(value);
					isValidCur = this.stringValidationValidChars(valueNum, '01234567689');
					if (isValidCur && cur.regExp)
						isValidCur = this.stringValidationRegExp(value, new RegExp(cur.regExp[0], cur.regExp[1]));
					if (isValidCur && cur.minValue)
						isValidCur = this.numberValidationMinValue(valueNum, cur.minValue);
					if (isValidCur && cur.maxValue)
						isValidCur = this.numberValidationMaxValue(valueNum, cur.maxValue);
					break;
				case 'float':
					var valueNum = this.numberValidationParseFloat(value);
					isValidCur = this.stringValidationValidChars(valueNum, '.01234567689');
					if (isValidCur && cur.regExp)
						isValidCur = this.stringValidationRegExp(value, new RegExp(cur.regExp[0], cur.regExp[1]));
					if (isValidCur && cur.minValue)
						isValidCur = this.numberValidationMinValue(valueNum, cur.minValue);
					if (isValidCur && cur.maxValue)
						isValidCur = this.numberValidationMaxValue(valueNum, cur.maxValue);
					break;
				case 'password':
					if (cur.minRating)
						isValidCur = this.passwordValidationMinRating(value, cur.minRating);
					break;
				case 'email':
					isValidCur = this.stringValidationRegExp(value, new RegExp(this.options.emailFieldsValidationRegExp[0], this.options.emailFieldsValidationRegExp[1]));
					break;
				case 'checkbox':
					if (reqs[0].groupWith && (cur.minSelected || cur.maxSelected)) {
						var numSel = field.checked ? 1 : 0;
						for (var j = 0, m = reqs[0].groupWith.length; j < m; j++)
							if (document.getElementById(reqs[0].groupWith[j]).checked)
								numSel++;
						if (cur.minSelected)
							isValidCur = numSel >= cur.minSelected;
						if (isValidCur && cur.maxSelected)
							isValidCur = numSel <= cur.maxSelected;
					}
					break;
				case 'creditcard':
					if (cur.supportedCards) {
						var checkCard = this.creditcardValidation(value, cur);
						if (checkCard.matchesUnsupported) {
							isValidCur = false;
							cur.error = (reqs[0].unsupportedCardError || reqs[1].error).replace('[creditcard]', checkCard.name);
						} else if (!checkCard.name || !checkCard.lengthMatches) {
							isValidCur = false;
							cur.error = reqs[0].error;
						}
					}
					break;
				default:;
			}
		}
		if (isValidCur) {
			if (cur.matchField) {
				isValidCur = value == document.getElementById(cur.matchField).value;
			} else if (cur.matchFieldsSum) {
				var sum = 0;
				for (var j = 0, m = cur.matchFieldsSum.length; j < m; j++)
					sum += parseFloat(this.numberValidationParseFloat(document.getElementById(cur.matchFieldsSum[j]).value));
				var decimalRounding = Math.pow(10, (cur.decimalsNumber ? cur.decimalsNumber : 2));
				sum = Math.round(sum * decimalRounding) / decimalRounding;
				isValidCur = parseFloat(this.numberValidationParseFloat(value)) == sum;
				if (!isValidCur && cur.error)
					cur.error = cur.error.replace('[amount]', '\'' + value + '\'').replace('[req_amount]', '\'' + (isNaN(sum) ? '0' : sum) + '\'');
			}
		}

		// clear the AJAX validation success message if it has been displayed before
		if (cur.validateURL && cur.validateURLsuccessMessageHolder) {
			successMessageHolder = document.getElementById(cur.validateURLsuccessMessageHolder);
			if (successMessageHolder)
				successMessageHolder.innerHTML = '';
		}

		// passed JS validation, proceed with AJAX validation if needed
		if (isValidCur && cur.validateURL) {
			// create a new synchron request to check the field
			var request = new Kaizen.AJAX.Request(cur.validateURL, function(){}, false);
			request.load(fieldId + '=' + value);
			var responseText = request.AJAXObject.XMLHttp.responseText;
			if (responseText.isJSON())
				eval('var obj = ' + responseText);
			else
				var obj = {success:false, message: 'Kaizen.FormValidate Error: Invalid response (not valid JSON) from the AJAX validation server-side script for the "' + fieldId + '" form field. Please contact the site administrator.', callback:''};
			request.AJAXObject.inUse = false;

			if (obj.success) {
				if (obj.message && cur.validateURLsuccessMessageHolder) {
					successMessageHolder = document.getElementById(cur.validateURLsuccessMessageHolder);
					if (successMessageHolder)
						successMessageHolder.innerHTML = obj.message;
				}
			} else {
				isValidCur = false;
				cur.error = obj.message;
			}
			if (obj.callback)
				eval(obj.callback + '.apply(field)');
		}

		// show error message if validation has failed
		if (!isValidCur && cur.error) {
			// fire 'onError' if set for this field
			if (reqs[0].onError)
				eval(reqs[0].onError + '.apply(field)');

			// replace tags in error message if there are any used
			cur.error = cur.error.replace('[min_value]', cur.minValue)
						.replace('[max_value]', cur.maxValue)
						.replace('[min_length]', cur.minLength)
						.replace('[max_length]', cur.maxLength)
						.replace('[min_selected]', cur.minSelected)
						.replace('[max_selected]', cur.maxSelected);

			errorMessage = new Kaizen.Element('span', {'text':cur.error, 'class':errorClass});

			// make sure we would insert it after any SPANs following the field (for example, the validation flag SPAN) or any previous errors
			nextSibling = (lastOfGroup || field).nextSibling;
			while (nextSibling && (nextSibling.nodeName.toUpperCase() == 'SPAN' || nextSibling.nodeName.toUpperCase() == '#TEXT' || reqs[0].type == 'checkbox' && nextSibling.nodeName.toUpperCase() == 'LABEL'))
				nextSibling = nextSibling.nextSibling;
			(lastOfGroup || field).parentNode.insertBefore(errorMessage, nextSibling);
		}

		if (!isValidCur)
			isValid = false;

		if (criticalError)
			break;
	}

	// if we have a group of fields (checkboxes), setting the validation flag to the parent node of each of the fields of the group
	if (lastOfGroup)
		for (i = 0, l = reqs[0].groupWith.length; i < l; i++)
			this.setValidationClass(document.getElementById(reqs[0].groupWith[i]).parentNode, isValid);

	// if we are validating an optional checkbox (and not onsubmit), we are going to remove the valid class when the checkbox is unchecked (BUG #6)
	if (!calledOnSubmit && reqs[0].type == 'checkbox' && !reqs[0].required && !reqs[0].error && !reqs[0].groupWith && !reqs[1] && !field.checked)
		Kaizen.DOM.removeClass(field.parentNode, this.options.validFieldClass);
	else
		this.setValidationClass(field.parentNode, isValid);

	return isValid;
}

Kaizen.FormValidate.prototype.setValidateAsUserTypesTimeout = function(field, fieldId) {
	var ref = this;
	this.clearValidateAsUserTypesTimeout(field);
	field._validateAsUserTypesTimeout = setTimeout(function(){ref.validateField(fieldId)}, this.options.validateAsUserTypesTimeout);
}

Kaizen.FormValidate.prototype.clearValidateAsUserTypesTimeout = function(field) {
	if (field._validateAsUserTypesTimeout) {
		clearTimeout(field._validateAsUserTypesTimeout);
		field._validateAsUserTypesTimeout = 0;
	}
}

Kaizen.FormValidate.prototype.setValidationClass = function(fieldParent, isValid) {
	Kaizen.DOM.removeClass(fieldParent, isValid ? this.options.invalidFieldClass : this.options.validFieldClass);
	Kaizen.DOM.addClass(fieldParent, isValid ? this.options.validFieldClass : this.options.invalidFieldClass);
}

Kaizen.FormValidate.prototype.relateFieldsValidation = function(fieldId, relatedFieldsIds) {
	var ref = this;
	var id, cur, curReqs;
	for (var i = 0, l = relatedFieldsIds.length; i < l; i++) {
		id = relatedFieldsIds[i];
		curReqs = this.fieldsReqs[id];
		if (curReqs[0].required && !this.fieldsReqs[fieldId][0].required)
			this.fieldsReqs[fieldId][0].required = curReqs[0].required;
		cur = document.getElementById(id);
		Kaizen.DOM.addEvent(cur, this.fieldsReqs[id] && curReqs[0].validateOn || 'change', function(){
			if (document.getElementById(fieldId).value != '')
				ref.validateField(fieldId);
		});
	}
}

Kaizen.FormValidate.prototype.stringValidationRegExp = function(str, regExp) {
	return regExp.test(str);
}

Kaizen.FormValidate.prototype.stringValidationValidChars = function(str, validChars) {
	var tmpChar;
	for (var i = 0, l = str.length; i < l; i++) {
		tmpChar = str.substring(i, i + 1) + '';
		if (validChars.indexOf(tmpChar) == -1)
			return false;
	}
	return true;
}
	
Kaizen.FormValidate.prototype.stringValidationMinLength = function(str, length) {
	return str.length >= length;
}
	
Kaizen.FormValidate.prototype.stringValidationMaxLength = function(str, length) {
	return str.length <= length;
}

Kaizen.FormValidate.prototype.numberValidationMinValue = function(str, minValue) {
	return parseFloat(str) >= minValue;
}

Kaizen.FormValidate.prototype.numberValidationMaxValue = function(str, maxValue) {
	return parseFloat(str) <= maxValue;
}

Kaizen.FormValidate.prototype.numberValidationParseFloat = function(str) {
	str = str.replace(/\s/g, '');
	return str;
}

Kaizen.FormValidate.prototype.passwordValidationMinRating = function(password, minRating) {
	return this.calcPasswordStrength(password, this.options.calcPasswordStrengthRequirement, this.options.calcPasswordStrengthMultiplier, this.options.calcPasswordStrengthAlphabet, this.options.calcPasswordStrengthKeyboard) >= minRating;
}

Kaizen.FormValidate.prototype.creditcardValidation = function(str, reqs) {
	var supported = reqs.supportedCards;
	var num = str.replace(/[\s-]/g, '');
	var name, id, lengthMatches, matchesUnsupported, data;
	if (this.stringValidationValidChars(num, '01234567689')) { // make sure only numbers are entered
		for (var i in this.cardsData) {
			data = this.cardsData[i];
			if (data.prefix.test(num)) {
				name = data.name;
				id = i;
				lengthMatches = data.length.indexOf(num.length) > -1;
				if (supported.indexOf(id) == -1 && (id != 'VisaElectron' || supported.indexOf('Visa') == -1) && (id != 'DinersClubCarteBlanche' && id != 'DinersClubInternational' || supported.indexOf('DinersClub') == -1))
					matchesUnsupported = true;
				break;
			}
		}
	}
	return {name:name, id:id, lengthMatches:lengthMatches, matchesUnsupported:matchesUnsupported};
}

Kaizen.FormValidate.prototype.fieldFocus = function(field) {
	Kaizen.DOM.addClass(field.parentNode, this.options.focusedFieldClass);
}

Kaizen.FormValidate.prototype.fieldBlur = function(field) {
	Kaizen.DOM.removeClass(field.parentNode, this.options.focusedFieldClass);
}

Kaizen.FormValidate.prototype.showAlert = function(message) {
	if (this.options.useJSAlertOnErrors)
		alert(message);
	else
		this.alertBox.show(message.replace(/\n/g, '<br />'), this.options.Language.OK);
}

Kaizen.FormValidate.prototype.passwordMeterUpdate = function(field, fieldId) {
	if (!this.fieldsReqs[fieldId])
		return;

	var password = field.value;
	var passwordMeter = field.nextSibling;
	while (!Kaizen.DOM.hasClass(passwordMeter, this.options.passwordMeterClass))
		passwordMeter = passwordMeter.nextSibling;
	var strength = 0;
	var level = 0;
	if (password.length > 0) {
		strength = this.calcPasswordStrength(password, this.options.calcPasswordStrengthRequirement, this.options.calcPasswordStrengthMultiplier, this.options.calcPasswordStrengthAlphabet, this.options.calcPasswordStrengthKeyboard);
		if (strength < 20)
			level = 1;
		else if (strength < 45)
			level = 2;
		else if (strength < 65)
			level = 3;
		else if (strength < 85)
			level = 4;
		else
			level = 5;
	}

	passwordMeter.lastChild.innerHTML = password.length == 0 ? '&nbsp;' : this.options.Language.passwordStrengthLevels[level - 1];// + ' ' + strength; 
	var strenghBox = passwordMeter.lastChild.previousSibling;
	for (var i = 1, l = 5; i <= l; i++) {
		if (i == level)
			Kaizen.DOM.addClass(strenghBox, this.options.passwordMeterLevelXClass + i);
		else
			Kaizen.DOM.removeClass(strenghBox, this.options.passwordMeterLevelXClass + i);
	}
}

Kaizen.FormValidate.prototype.creditcardsUpdate = function(field, fieldId) {
	var reqs = this.fieldsReqs[fieldId];
	if (!reqs)
		return;

	var supported = reqs[0].supportedCards || reqs[1].supportedCards;
	var activeId;
	var checkCard = this.creditcardValidation(field.value, reqs[0].supportedCards ? reqs[0] : reqs[1]);
	if (checkCard.name) {
		if (!checkCard.matchesUnsupported) {
			if (checkCard.id == 'VisaElectron' && supported.indexOf('VisaElectron') == -1)
				checkCard.id = 'Visa';
			if (checkCard.id == 'DinersClubCarteBlanche' && supported.indexOf('DinersClubCarteBlanche') == -1 || checkCard.id == 'DinersClubInternational' && supported.indexOf('DinersClubInternational') == -1)
				checkCard.id = 'DinersClub';
			activeId = checkCard.id;
		}
	}
	var cardsHolder = field.nextSibling;
	while (!Kaizen.DOM.hasClass(cardsHolder, this.options.cardsHolderClass))
		cardsHolder = cardsHolder.nextSibling;
	var cur, card;
	for (var i = 0, l = supported.length; i < l; i ++) {
		cur = supported[i];
		card = cardsHolder.childNodes[i];
		if (activeId && cur != activeId)
			Kaizen.DOM.addClass(card, this.options.cardInactiveClass);
		else
			Kaizen.DOM.removeClass(card, this.options.cardInactiveClass);
	}
}

Kaizen.FormValidate.prototype.calcPasswordStrength = function(password, requirement, multiplier, alphabet, keyboard) {
	var strength = {'alphabet':0, 'alphabet_failure':0, 'alphabet_reverse':0, 'alphabet_reverse_failure':0, 'alpha_lc':0, 'alpha_lc_after':0, 'alpha_uc':0, 'alpha_uc_after':0, 'integer':0, 'integer_after':0, 'keyboard':0, 'keyboard_failure':0, 'keyboard_reverse':0, 'keyboard_reverse_failure':0, 'middle_character':0, 'numbers':0, 'numbers_failure':0, 'numbers_reverse':0, 'numbers_reverse_failure':0, 'repeat':0, 'requirements':0, 'symbol':0, 'symbol_after':0};

	var repeat_processed = [];
	var password_length = password.length;
	
	if (password_length < 8) {
		return false;
		}
	
	var password_min = Math.ceil(Math.ceil(password_length / 2) / 2); //i.e. on a length of 8 this value is 2
	var password_max = password_length - password_min + 1; //i.e. on a length of 8 this value is 7

	var seq_alphabet = 0;
	var seq_alphabet_reverse = 0;
	var seq_keyboard = 0;
	var seq_keyboard_reverse = 0;
	var seq_integer = 0;
	var seq_integer_reverse = 0;
	
	var has_letter = false;	//if the password has at least one letter
	var has_number = false;	//if the password has at least one number

	//Loop over the entire password string and then one more time
	for (var nr = 0; true; ++nr) {
		var character_found = false;
		var seq_alphabet_found = false;
		var seq_alphabet_reverse_found = false;
		var seq_keyboard_found = false;
		var seq_keyboard_reverse_found = false;
		var seq_integer_found = false;	
		var seq_integer_reverse_found = false;	

		//Only run the code inside the if sentence if the length of the string has not been reached!
		var character = password.charAt(nr);	
		if (character) {
			var prev_character = password.charAt(nr - 1) || '';
			
			//if there is a number in the password
			if((parseFloat(character) == parseInt(character)) && !isNaN(character)){
				has_number = true;
				}

			//Check if is is a lower case letter
			if (/[a-z]/.test(character)) {
				//Check if the previous character was a lower case letter
				if (nr > 0 && /[a-z]/.test(prev_character)) ++strength['alpha_lc_after'];

				++strength['alpha_lc'];
			}
			//Check if it is a capital letter
			else if (/[A-Z]/.test(character)) {
				//Check if the previous character was a capital letter
				if (nr > 0 && /[A-Z]/.test(prev_character)) ++strength['alpha_uc_after'];

				++strength['alpha_uc'];
			}
			//Check if it is a number
			else if (/[0-9]/.test(character)) {
				//Check if the previous character was a number
				if (nr > 0 && /[0-9]/.test(prev_character)) ++strength['integer_after'];

				//Check if this is the "middle" half of characters, if length is 8 then the six middle one is valid.
				if (nr > password_min && nr < password_max) ++strength['middle_character'];

				++strength['integer'];
			}
			//Check if it is a symbol
			else if (/[^a-zA-Z0-9]/.test(character)) {
				//Check if the previous character was a symbol
				if (nr > 0 && /[^a-zA-Z0-9]/.test(prev_character)) ++strength['symbol_after'];

				//Check if this is the "middle" half of characters, if length is 8 then the six middle one is valid.
				if (nr > password_min && nr < password_max) ++strength['middle_character'];

				++strength['symbol'];
			}

			//Set the character and the prev character to lowercase for the final tests.
			character = character.toLowerCase();	
			prev_character = prev_character.toLowerCase();

			//Lets loop over the password, checking if this character/integer/symbol is used more than once
			//Note, modified by Sven, only run the for loop if the character has not been checked before
			if (!repeat_processed[character]) {
				for (var i = 0; password.charAt(i); ++i) {
					//was this before: if (nr != i && !repeat_processed[character] && character === password.charAt(i).toLowerCase()) ++strength['repeat']; //It is case insensitive
					if (nr != i && character === password.charAt(i).toLowerCase()) ++strength['repeat']; //It is case insensitive
				}
			}

			//Check if the user typed in characters that are after each other (alphabet)
			if (nr > 0 && alphabet.indexOf(character) > -1) {
				has_letter = true;
				character_found = true;
				var position = alphabet.indexOf(character);

				//Normal direction
				if (typeof alphabet[--position] != 'undefined' && alphabet[position] == prev_character) {
					seq_alphabet_found = true;
					
					++seq_alphabet;
				}

				//Reverse direction
				if (typeof alphabet[(position += 2)] != 'undefined' && alphabet[position] == prev_character) {
					seq_alphabet_reverse_found = true;

					++seq_alphabet_reverse;
				}	
			}

			//Check if the user typed in characters that are after each other	(keyboard)
			if (character_found === true) { //update here
				//Find out what row on the keyboard it belong to
				var key;
				if (keyboard[1].indexOf(character) > -1) key = 1;
				else if (keyboard[2].indexOf(character) > -1) key = 2;
				else key = 3;

				var position = keyboard[key].indexOf(character);

				//Normal direction
				if (typeof keyboard[key][--position] != 'undefined' && keyboard[key][position] == prev_character) {
					seq_keyboard_found = true;
					
					++seq_keyboard;
				}
			
				//Reverse direction	
				if (typeof keyboard[key][(position += 2)] != 'undefined' && keyboard[key][position] == prev_character) {
					seq_keyboard_reverse_found = true;
					
					++seq_keyboard_reverse;
				}
			}

			//Check if the user typed in numbers that are after each other
			if (/[0-9]/.test(prev_character) && /[0-9]/.test(character)) {

				//Normal direction
				if (character > 0 && character <= 9 && (parseInt(character) - 1) == prev_character) {
					seq_integer_found = true;
					
					++seq_integer;
				}

				//Reverse direction		
				if (character >= 0 && character < 9 && (parseInt(character) + 1) == prev_character) {
					seq_integer_reverse_found = true;
					
					++seq_integer_reverse;
					}
				}

			//Set the character as processed in the repeat array
			repeat_processed[character] = true;
			}


		/**
		 * The checks below are run after the string has finished as well.
		 * I.e. they are run a final time to check if any there was any sequences at the end of the string
		 */

		//Check how many characters that were after each other, only run when its failed to find more characters (alphabet)
		if (seq_alphabet_found === false && seq_alphabet > 0) {
			//If there is more than the allowed characters after each other add penalty
			if (seq_alphabet >= requirement['characters_after']) ++strength['alphabet_failure'];

			strength['alphabet'] += seq_alphabet;

			seq_alphabet = 0; //reset the counter
		}

		//Check how many characters that were after each other in reverse order, only run when its failed to find more characters (alphabet)
		if (seq_alphabet_reverse_found === false && seq_alphabet_reverse > 0) {
			//If there is more than the allowed characters after each other add penalty
			if (seq_alphabet_reverse >= requirement['characters_after']) ++strength['alphabet_reverse_failure'];

			strength['alphabet_reverse'] += seq_alphabet_reverse;

			seq_alphabet_reverse = 0; //reset the counter
		}

		//Check how many characters that were after each other, only run when its failed to find more characters (keyboard)			
		if (seq_keyboard_found === false && seq_keyboard > 0) {
			//If there is more than the allowed characters after each other add penalty
			if (seq_keyboard >= requirement['characters_after']) ++strength['keyboard_failure'];

			strength['keyboard'] += seq_keyboard;

			seq_keyboard = 0; //reset the counter
		}

		//Check how many characters that were after each other in reverse order, only run when its failed to find more characters (keyboard)
		if (seq_keyboard_reverse_found === false && seq_keyboard_reverse > 0) {
			//If there is more than the allowed characters after each other add penalty
			if (seq_keyboard_reverse >= requirement['characters_after']) ++strength['keyboard_reverse_failure'];

			strength['keyboard_reverse'] += seq_keyboard_reverse;

			seq_keyboard_reverse = 0; //reset the counter
		}

		//Check how many characters that were after each other, only run when its failed to find more characters (numbers)		
		if (seq_integer_found === false && seq_integer > 0) {
			//If there is more than the allowed numbers after each other add penalty
			if (seq_integer >= requirement['characters_after']) ++strength['numbers_failure'];

			strength['numbers'] += seq_integer;

			seq_integer = 0; //reset the counter
		}

		//Check how many characters that were after each other in reverse order, only run when its failed to find more characters (numbers)
		if (seq_integer_reverse_found === false && seq_integer_reverse > 0) {
			//If there is more than the allowed characters after each other add penalty
			if (seq_integer_reverse >= requirement['characters_after']) ++strength['numbers_reverse_failure'];

			strength['numbers_reverse'] += seq_integer_reverse;

			seq_integer_reverse = 0; //reset the counter
		}


		//Terminate the for loop after it has run through it one time too much	
		if (!password.charAt(nr)) break;	
	}
	
	if (has_letter == false || has_number == false) {
		return false;
		}



	/**
	 * Calculate the password strength score
	 */
	
	//Check if the password length requirement is met
	if (password_length >= requirement['password_length']) ++strength['requirements']; //Used to see if the user has used enough different characters to get a bonus

	//Give the initial score from the password length
	var score = password_length * multiplier['string_length']; // (n * multiplier)

	//Give points if there is lowercase characters together with other characters
	if (strength['alpha_lc'] > 0 && strength['alpha_lc'] < password_length) {
		++strength['requirements'];

		score += (password_length - strength['alpha_lc']) * multiplier['alpha_lc']; // ((password length - n) * multiplier)
	}

	//Give points if there is capital characters together with other characters	
	if (strength['alpha_uc'] > 0 && strength['alpha_uc'] < password_length) {
		++strength['requirements'];

		score += (password_length - strength['alpha_uc']) * multiplier['alpha_uc']; // ((password length - n) * multiplier)
	}

	//Give points if there is number characters together with other characters
	if (strength['integer'] > 0 && strength['integer'] < password_length) {
		++strength['requirements'];

		score += strength['integer'] * multiplier['integer']; // (n * multiplier)
	}
		
	//Give points if there is symbol characters together with other characters
	if (strength['symbol'] > 0 && strength['symbol'] < password_length) {
		++strength['requirements'];

		score += strength['symbol'] * multiplier['symbol']; // (n * multiplier)
	}

	//Give points if the "middle" characters are numbers or symbols
	if (strength['middle_character'] > 0) {
		score += strength['middle_character'] * multiplier['middle_character']; // (n * multiplier)
	}

	//Give points	if the password meet the requirements
	if (strength['requirements'] >= requirement['requirement_bonus']) {
		score += strength['requirements'] * multiplier['requirement_bonus']; // (n * multiplier)
	}	


	//Remove points if the password is too short
	if (password_length < requirement['password_length']) {
		score -= multiplier['password_length_penalty']; // (- multiplier)
	}

	//Remove points if there is only letters
	if (/^[a-zA-Z]*$/.test(password)) {
		score -= password_length; // (- length)
	}

	//Remove points if there is only numbers
	if (/^[0-9]*$/.test(password)) {
		score -= password_length; // (- length)
	}

	//Remove points if the same characters exist more than once
	if (strength['repeat'] > 0) {
		score -= strength['repeat'] * multiplier['repeat']; // (n * multiplier)
	}	

	//Remove points if there was lowercase letters after each other
	if (strength['alpha_lc_after'] > 0) {
		score -= strength['alpha_lc_after'] * multiplier['alpha_lc_after']; // (n * multiplier)
	}

	//Remove points if there was capital letters after each other	
	if (strength['alpha_uc_after'] > 0) {
		score -= strength['alpha_uc_after'] * multiplier['alpha_uc_after']; // (n * multipler)
	}

	//Remove points if there was numbers after each other
	if (strength['integer_after'] > 0) {
		score -= strength['integer_after'] * multiplier['integer_after']; // (n * multipler)
	}

	//Remove points if there was symbols after each other
	if (strength['symbol_after'] > 0) {
		score -= strength['symbol_after'] * multiplier['symbol_after']; // (n * multipler)
	}

	//Remove points if there was characters	after each other (alphabet)
	if (strength['alphabet'] > 0) {
		score -= strength['alphabet'] * multiplier['alphabet']; // (n * multipler)
	}

	//Remove points if there was more characters after each other than allowed (alphabet)
	if (strength['alphabet_failure'] > 0) {
		score -= strength['alphabet_failure'] * multiplier['alphabet_failure']; // (n * multipler)
	}		

	//Remove points if there was characters after each other in reverse order (alphabet)
	if (strength['alphabet_reverse'] > 0) {
		score -= strength['alphabet_reverse'] * multiplier['alphabet_reverse']; // (n * multipler)
	}

	//Remove points if there was more characters after each other than allowed (alphabet)
	if (strength['alphabet_reverse_failure'] > 0) {
		score -= strength['alphabet_reverse_failure'] * multiplier['alphabet_reverse_failure']; // (n * multipler)
	}

	//Remove points if there was characters	after each other (keyboard)
	if (strength['keyboard'] > 0) {
		score -= strength['keyboard'] * multiplier['keyboard']; // (n * multipler)
	}

	//Remove points if there was more characters after each other than allowed (keyboard)
	if (strength['keyboard_failure'] > 0) {
		score -= strength['keyboard_failure'] * multiplier['keyboard_failure']; // (n * multipler)
	}

	//Remove points if there was characters after each other in reverse order (keyboard)
	if (strength['keyboard_reverse'] > 0) {
		score -= strength['keyboard_reverse'] * multiplier['keyboard_reverse']; // (n * multipler)
	}

	//Remove points if there was more characters after each other than allowed (keyboard)
	if (strength['keyboard_reverse_failure'] > 0) {
		score -= strength['keyboard_reverse_failure'] * multiplier['keyboard_reverse_failure']; // (n * multipler)
	}

	//Remove points if there was characters	after each other (numbers)
	if (strength['numbers'] > 0) {
		score -= strength['numbers'] * multiplier['numbers']; // (n * multipler)
	}

	//Remove points if there was more characters after each other than allowed (numbers)
	if (strength['numbers_failure'] > 0) {
		score -= strength['numbers_failure'] * multiplier['numbers_failure']; // (n * multipler)
	}

	//Remove points if there was characters after each other in reverse order (numbers)
	if (strength['numbers_reverse'] > 0) {
		score -= strength['numbers_reverse'] * multiplier['numbers_reverse']; // (n * multipler)
	}		

	//Remove points if there was more characters after each other than allowed (numbers)
	if (strength['numbers_reverse_failure'] > 0) {
		score -= strength['numbers_reverse_failure'] * multiplier['numbers_reverse_failure']; // (n * multipler)
	}			


	/**
	 * Lets do the final checks on the score
	 */

	if (score < 0) score = 0;
	else if (score > 100) score = 100;

	return Math.floor(score);
}

Kaizen.Form = {

	/**
	 * Clones form fields and returns them (without inserting them in DOM)
	 *
	 * @param string/element	fieldIdOrElm		The id of the field OR the field element that should be cloned
	 * @param boolean		cloneWholeParent	Should we clone the whole parent of the field (and all other fields inside it)?
	 *
	 * When 'cloneWholeParent' is 'false', the function returns an array - i.e. return [newField, newLabel];
	 * When 'cloneWholeParent' is 'true', the function returns the cloned parent - i.e. return newPar;
	 */
	cloneFields: function(fieldIdOrElm, cloneWholeParent) {
		var elm, fieldId;
		if (typeof fieldIdOrElm == 'string') {
			elm = document.getElementById(fieldIdOrElm);
			fieldId = fieldIdOrElm;
		} else {
			elm = fieldIdOrElm;
			fieldId = elm.getAttribute('id');
		}

		var re = /[0-9]*$/;
		var par = elm.parentNode, elms, cur, att, newAtt, newIndex, i, l;

		// clone whole parrent with all fields inside it
		if (cloneWholeParent) {
			var newPar = par.cloneNode(true);
			elms = newPar.getElementsByTagName('input');
			for (i = 0, l = elms.length; i < l; i++) {
				cur = elms[i];
				att = cur.getAttribute('id');
				newIndex = this.getNewIdIndex(att);
				newAtt = att.replace(re, newIndex);
				cur.setAttribute('id', newAtt);
				cur.value = '';
				cur._originalId = att;
			}
			elms = newPar.getElementsByTagName('textarea');
			for (i = 0, l = elms.length; i < l; i++) {
				cur = elms[i];
				att = cur.getAttribute('id');
				newIndex = this.getNewIdIndex(att);
				newAtt = att.replace(re, newIndex);
				cur.setAttribute('id', newAtt);
				cur.value = '';
				cur._originalId = att;
			}
			elms = newPar.getElementsByTagName('select');
			for (i = 0, l = elms.length; i < l; i++) {
				cur = elms[i];
				att = cur.getAttribute('id');
				newIndex = this.getNewIdIndex(att);
				newAtt = att.replace(re, newIndex);
				cur.setAttribute('id', newAtt);
				cur._originalId = att;
			}
			elms = newPar.getElementsByTagName('label');
			for (i = 0, l = elms.length; i < l; i++) {
				cur = elms[i];
				att = cur.htmlFor;
				newIndex = this.getNewIdIndex(att, true);
				newAtt = att.replace(re, newIndex);
				cur.htmlFor = newAtt;
			}
			return newPar;
		}

		var newField, newLabel;

		// clone field
		newField = elm.cloneNode(true);
		newIndex = this.getNewIdIndex(fieldId);
		newAtt = fieldId.replace(re, newIndex);
		newField.setAttribute('id', newAtt);
		newField.value = '';
		newField._originalId = fieldId;

		// clone label if available
		elms = par.getElementsByTagName('label');
		for (i = 0, l = elms.length; i < l; i++) {
			cur = elms[i];
			if (cur.htmlFor == fieldId) {
				newLabel = cur.cloneNode(true);
				newLabel.htmlFor = newAtt;
				break;
			}
		}
		return [newField, newLabel];
	},

	// Returns a proper new ID index - for internal use in Kaizen.Form.cloneFields
	getNewIdIndex: function(id, forLabel) {
		if (!window.KaizenClonedFieldsIds)
			KaizenClonedFieldsIds = {};
		var re = /[0-9]*$/;
		var idBase = id.replace(re, '');
		var index = parseInt(id.match(re)[0]) + 1;
		if (forLabel)
			return KaizenClonedFieldsIds[id];
		while (document.getElementById(idBase + index) || KaizenClonedFieldsIds[idBase + index])
			index++;
		KaizenClonedFieldsIds[id] = index;
		KaizenClonedFieldsIds[idBase + index] = 1;
		return index;
	},

	/**
	 * Hides elements (e.g. form fields) and inserts a show/hide checkbox before them in the DOM
	 *
	 * @param string/element	idOrElm			The id of the element OR the element that should be hidden
	 * @param string		showHideText		Text for the checkbox label
	 * @param boolean		toggleWholeParent	Should we hide the whole parent of the element?
	 */
	toggleFieldsDisplay: function(idOrElm, showHideText, toggleWholeParent) {
		var elm;
		if (typeof idOrElm == 'string')
			elm = document.getElementById(idOrElm);
		else
			elm = idOrElm;
		if (toggleWholeParent)
			elm = elm.parentNode;
		var chBox = new Kaizen.Element('input', {'type':'checkbox', 'id':'tglFieldsDisplay' + (++this.toggleFieldsDisplayIdIndex), 'onclick':function(){Kaizen.Form.toggleFieldsDisplayShowHide(elm)}});
		var label = new Kaizen.Element('label', {'text':showHideText, 'for':'tglFieldsDisplay' + this.toggleFieldsDisplayIdIndex});
		elm.parentNode.insertBefore(chBox, elm);
		elm.parentNode.insertBefore(label, elm);
		this.toggleFieldsDisplayShowHide(elm);
	},

	toggleFieldsDisplayIdIndex: 0,

	toggleFieldsDisplayShowHide: function(elm) {
		var show = elm.style.display == 'none';
		elm.style.display = show ? 'block' : 'none';
		if (elm.nodeName.toUpperCase() == 'INPUT' || elm.nodeName.toUpperCase() == 'TEXTAREA') {
			show ? Kaizen.FormValidate.prototype.enable(elm) : Kaizen.FormValidate.prototype.disable(elm);
		} else {
			var elms = elm.getElementsByTagName('input');
			for (var i = 0, l = elms.length; i < l; i++)
				show ? Kaizen.FormValidate.prototype.enable(elms[i]) : Kaizen.FormValidate.prototype.disable(elms[i]);
			elms = elm.getElementsByTagName('textarea');
			for (var i = 0, l = elms.length; i < l; i++)
				show ? Kaizen.FormValidate.prototype.enable(elms[i]) : Kaizen.FormValidate.prototype.disable(elms[i]);
		}
	},

	/**
	 * Class for creating an upload popup
	 *
	 * @param string		templateURL		URL to the HTML upload template file
	 */
	Upload: function(templateURL, options) {
		var ref = this;

		this.options = {
			useJSAlertOnErrors: false,					// Whether to use a JS alert or a Kaizen.Alert popup on errors
			alertBoxOptions: {},						// Custom options for Kaizen.Alert.Box if 'useJSAlertOnErrors' is 'false'
			popupOptions: {'popupClass':'uploadPopup', 'useOverlay':true},	// Custom options for Kaizen.Popup.Box if needed
			progressBarOptions: {'useOverlay':false},			// Custom options for Kaizen.Progress.Box if needed
			uploadedFiles: '',						// Pass any previously uploaded files - syntax: 'filename.zip:hash,filename2.zip:hash'
			uploadURL: '',							// URL to the upload perl script
			deleteFileURL: '',						// URL to the script that deletes uploaded files
			statusPollURL: '',						// URL to the status returning script
			statusPollDelay: 2000,						// Delay between requests to polling status script (milliseconds)
			statusPollDelayWhenClosed: 4000,				// Delay when/while the popup is closed
			statusPollMaxResponseWait: 3000,				// Max wait for response before trying with a new request
			statusPollMaxGetHashAtempts: 3,					// Max attempts to get current file hash before aborting
			statusPollMaxReturnedMatchingTimestamp: 3,			// Max allowed requests that return the same 'time' before aborting
			maxPendingFiles: 3,						// Max pending files for upload allowed
			allowedFileExtensions: '',					// Allow only these file extensions - syntax: '.rar,.zip,.tar.gz'
			field_pragma: '',						// Field for upload forms
			field_pragma_token: '',						// Field for upload forms
			field_token: '',						// Field for upload forms
			transportName: 'auto_generate',					// If you need custom name for the IFRAME that is used as a transport for the upload - this must be unique for each Kaizen.Form.Upload object so if you need custom name, make sure you don't use the same for more than one upload object
			Language: {							// An object containing any words that may be used
				'pleaseWait': 'Please wait. Loading...',
				'uploading': 'Uploading...',
				'cancel': 'Cancel',
				'delete': 'Delete',
				'min': 'min', // minutes
				'sec': 'sec', // seconds
				'messageMaxPendingLimit': '<span style="font-size:1.4em;"><span style="color:#f00;">The maximum allowed pending files limit has been reached!</span><br />Please wait until some of the files is uploaded before adding a new one...</span>', // HTML allowed
				'errorInvalidFileExt': 'Error:\n\nInvalid file extension!\nOnly the following file extensions are allowed:\n', // no HTML!
				'errorFilesizeTooLarge': 'Error:\n\nToo large file!\nThe maximum allowed filesize is: [allowedsize]', // no HTML!
				'OK': 'OK'
			}
		};
		if (options)
			Kaizen.extendObject(this.options, options);

		this.index = 1;
		this.inProgress = 0; // assigned the active index when uploading
		this.isClosed = true;
		this.forms = [];
		this.pendingFiles = [];
		this.templateURL = templateURL;
		this.popup = new Kaizen.Popup.Box(this.options.popupOptions);
		this.popupContent = null;
		this.progressBar = new Kaizen.Progress.Box(this.options.progressBarOptions);
		if (!this.options.useJSAlertOnErrors)
			this.alertBox = new Kaizen.Alert.Box(this.options.alertBoxOptions);
		this.AJAXloadTpl = new Kaizen.AJAX.Request(templateURL, function(){}, false);
		this.AJAXdeleteFile = new Kaizen.AJAX.Request(this.options.deleteFileURL, function(){});
		this.AJAXstatusPoll = new Kaizen.AJAX.Request(this.options.statusPollURL, function(status, responseText) {
			ref.processStatusPollResponse(status, responseText);
		});
		this.statusPollDelayTimeout = 0;
		this.statusPollResponseTimeout = 0;
		this.currentHash = '';
		this.currentRequestCount = 0;
		this.currentTimestamp = {'time':0, 'count':0}; // for detecting possible hangs while uploading
		this.statusPollReqVars = 'token=' + this.options.field_token + '&pragma_token=' + this.options.field_pragma_token;

		// make sure we have unique IFRAME name for each upload object
		if (this.options.transportName == 'auto_generate')
			this.options.transportName = 'KaizenFormUploadTransport' + new Date().getTime();
		// init transport
		this.transport = new Kaizen.Element('iframe', {'src':'about:blank', 'style':'display:none;', 'id':this.options.transportName, 'name':this.options.transportName});
		Kaizen.DOM.addEvent(this.transport, 'load', function(){ref.onLoad(this)});
		document.body.appendChild(this.transport);
		if (self.frames[this.options.transportName].name != this.options.transportName) // bug fix for IE
			self.frames[this.options.transportName].name = this.options.transportName;

		// init upload forms holder (after a file is selected the form in moved to this DIV from the upload popup)
		this.formsHolder = new Kaizen.Element('div', {'style':'position:absolute;left:0;top:0;visibility:hidden;'});
		document.body.appendChild(this.formsHolder);
	}

};

Kaizen.Form.Upload.prototype.show = function() {
	var ref = this;

	this.progressBar.show(null, this.options.Language.pleaseWait);

	if (!this.popupContent) {
		this.AJAXloadTpl.load('', 'GET');
		var tempNode = new Kaizen.Element('div', {'style':'position:absolute;top:0;left:0;visibility:hidden;'});
		tempNode.innerHTML = this.AJAXloadTpl.AJAXObject.XMLHttp.responseText.replace(/[\r]*[\n]*/g, '').match(/<div id="upload-container">.*/)[0].replace(/<\/body>.*/, '');
		this.AJAXloadTpl.AJAXObject.inUse = false;
		document.body.appendChild(tempNode);
		this.popupContent = tempNode.firstChild;

		// cache some variables for better performance
		this.formHolder = document.getElementById('upload-file-input');
		this.pendingHolder = Kaizen.DOM.getElementsByClassName('file-wrapper', 'div', document.getElementById('upload-pending'))[0];
		this.uploadedHolder = Kaizen.DOM.getElementsByClassName('file-wrapper', 'div', document.getElementById('upload-finished'))[0];
		this.progressHolder = document.getElementById('upload-progress');
		this.progressBarProgressed = document.getElementById('upload-bar-progressed');

		// init elements in the popupContent element
		var closeLnk1 = document.getElementById('left-upload-footer').firstChild;
		closeLnk1.onclick = function(){ref.hide();return false};
		Kaizen.DOM.IEDOMleaks.push([closeLnk1, 'onclick']);

		var closeLnk2 = document.getElementById('right-upload-header').firstChild;
		closeLnk2.onclick = closeLnk1.onclick;
		Kaizen.DOM.IEDOMleaks.push([closeLnk2, 'onclick']);

		document.getElementById('upload-progress').style.display = 'none';

		var form = this.formHolder.getElementsByTagName('form')[0];
		var inputs = form.getElementsByTagName('input');
		inputs[0].value = this.options.field_pragma;
		inputs[1].value = this.options.field_pragma_token;
		inputs[2].value = this.options.field_token;
		Kaizen.DOM.addEvent(inputs[3], 'change', function(){ref.inputOnChange(this)});

		this.forms[1] = form;
		this.forms[1].action = this.options.uploadURL;
		this.forms[1].target = this.options.transportName;

		// init any previously uploaded files
		if (this.options.uploadedFiles) {
			var filesArr = this.options.uploadedFiles.split(',');
			var cur;
			for (var i = 0, l = filesArr.length; i < l; i++) {
				cur = filesArr[i].split(':');
				this.addUploaded(cur[0], cur[1]);
			}
		}		
	}

	this.progressBar.hide();
	this.popup.show(this.popupContent);
	this.isClosed = false;

	if (typeof tempNode != 'undefined')
		document.body.removeChild(tempNode);
}

Kaizen.Form.Upload.prototype.hide = function() {
	this.popup.hide();
	this.isClosed = true;
}

Kaizen.Form.Upload.prototype.inputOnChange = function(input) {
	this.addPending();

	// check for valid file extension
	if (this.options.allowedFileExtensions) {
		var filename = input.value.toLowerCase();
		var allowedExtArr = this.options.allowedFileExtensions.split(',');
		var cur, allowed;
		for (var i = 0, l = allowedExtArr.length; i < l; i++) {
			cur = allowedExtArr[i];
			if (filename.substring(filename.length - cur.length) == cur) {
				allowed = true;
				break;
			}
		}
		if (!allowed) {
			this.deletePending(this.pendingFiles.length - 1);
			var message = this.options.Language.errorInvalidFileExt + this.options.allowedFileExtensions.replace(/\,/g, ' , ');
			this.showAlert(message);
		}
	}

	this.startUpload();
	this.index++;
}

Kaizen.Form.Upload.prototype.addPending = function() {
	var ref = this;

	// hide previous form
	var prevForm = this.forms[this.index];
	this.formsHolder.appendChild(prevForm);

	// create the new form
	// IE is stupid again and, unfortunately, we must use innerHTML for the form creation :(
	var tempNode = new Kaizen.Element('div');
	tempNode.innerHTML = '<form method="POST" enctype="multipart/form-data" action="' + this.options.uploadURL + '" target="' + this.options.transportName + '"><p><input type="hidden" name="pragma" value="' + this.options.field_pragma + '" /><input type="hidden" name="pragma_token" value="' + this.options.field_pragma_token + '" /><input type="hidden" name="token" value="' + this.options.field_token + '" /><input type="file" name="file" /></p></form>';
	var newForm = tempNode.firstChild;
	Kaizen.DOM.addEvent(newForm.firstChild.lastChild, 'change', function(){ref.inputOnChange(this)});
	this.formHolder.appendChild(newForm);
	this.forms[this.index + 1] = newForm;

	var files = Kaizen.DOM.getElementsByClassName('file', 'div', this.pendingHolder);

	// show max pending limit message if limit reached
	if (files.length + 1 == this.options.maxPendingFiles) {
		if (!this.messageMaxPendingLimit) {
			this.messageMaxPendingLimit = new Kaizen.Element('p');
			this.messageMaxPendingLimit.innerHTML = this.options.Language.messageMaxPendingLimit;
			this.formHolder.appendChild(this.messageMaxPendingLimit);
		}
		this.forms[this.forms.length - 1].style.display = 'none';
		this.messageMaxPendingLimit.style.display = 'block';
	}

	// add the file to the pending list
	var fileName = prevForm.getElementsByTagName('input')[3].value;
	if (/\\/.test(fileName))
		fileName = fileName.substring(fileName.lastIndexOf('\\') + 1);
	else if (/\//.test(fileName))
		fileName = fileName.substring(fileName.lastIndexOf('/') + 1);
	var newFile = new Kaizen.Element('div', 'file');
	var span1 = new Kaizen.Element('span', {'text':files.length + 1 + '. ' + fileName, 'class':'name'});
	var span2 = new Kaizen.Element('span', 'delete');
	var link1 = new Kaizen.Element('a', {'text':this.options.Language['delete'], 'href':'#', 'onclick':function(){
		ref.deletePending(this._fileIndex);
		return false;
	}});
	link1._fileIndex = this.index;
	span2.append(link1);
	var span3 = new Kaizen.Element('span', 'progress');
	newFile.append(span1, span2, span3);
	this.pendingHolder.appendChild(newFile);
	this.pendingFiles[this.index] = newFile;
}

Kaizen.Form.Upload.prototype.deletePending = function(index, success) {
	// if we are uploading this file at the moment
	if (index == this.inProgress)
		this.cancelUpload(success);

	// remove form and file
	var form = this.forms[index];
	form.parentNode.removeChild(form);
	this.forms[index] = null;
	var file = this.pendingFiles[index];
	file.parentNode.removeChild(file);
	this.pendingFiles[index] = null;

	this.updateFileListIndexes(this.pendingHolder);

	// hide max pending limit message if shown
	if (this.messageMaxPendingLimit && this.messageMaxPendingLimit.style.display != 'none') {
		this.messageMaxPendingLimit.style.display = 'none';
		this.forms[this.forms.length - 1].style.display = 'block';
	}

	// start next file
	this.startUpload();
}

Kaizen.Form.Upload.prototype.addUploaded = function(fileName, hash) {
	var ref = this;

	var files = Kaizen.DOM.getElementsByClassName('file', 'div', this.uploadedHolder);
	var newFile = new Kaizen.Element('div', 'file');
	var span1 = new Kaizen.Element('span', {'text':files.length + 1 + '. ' + fileName, 'class':'name'});
	var span2 = new Kaizen.Element('span', 'delete');
	var link1 = new Kaizen.Element('a', {'text':this.options.Language['delete'], 'href':'#', 'onclick':function(){
		ref.deleteUploaded(this);
		return false;
	}});
	link1._hash = hash;
	span2.append(link1);
	newFile.append(span1, span2);
	this.uploadedHolder.appendChild(newFile);
}

Kaizen.Form.Upload.prototype.deleteUploaded = function(link) {
	this.sendDeleteCommand(link._hash);

	var file = link.parentNode.parentNode;
	file.parentNode.removeChild(file);

	this.updateFileListIndexes(this.uploadedHolder);
}

Kaizen.Form.Upload.prototype.updateFileListIndexes = function(holder) {
	var files = Kaizen.DOM.getElementsByClassName('file', 'div', holder);
	var cur;
	for (var i = 0, l = files.length; i < l; i++) {
		cur = files[i].getElementsByTagName('span')[0];
		cur.innerHTML = cur.innerHTML.replace(/^[0-9]*\./, i + 1 + '.');
	}
}

Kaizen.Form.Upload.prototype.sendDeleteCommand = function(hash) {
	this.AJAXdeleteFile.load(this.statusPollReqVars + '&hash=' + hash);
}

Kaizen.Form.Upload.prototype.showStatus = function() {
	this.progressBarProgressed.style.width = '0';
	var spans = this.progressHolder.getElementsByTagName('span');
	spans[0].innerHTML = this.pendingFiles[this.inProgress].firstChild.firstChild.data.replace(/^[0-9]*\. /, ''); // name
	spans[1].innerHTML = '0%'; // percent
	spans[2].innerHTML = '-'; // left
	spans[3].innerHTML = '-'; // speed
	this.progressHolder.style.display = 'block';
	this.startStatusPoll();
}

Kaizen.Form.Upload.prototype.hideStatus = function() {
	this.stopStatusPoll();
	this.progressHolder.style.display = 'none';
}

Kaizen.Form.Upload.prototype.updateStatus = function(speed, percent, left) {
	this.progressBarProgressed.style.width = percent + '%';
	var spans = this.progressHolder.getElementsByTagName('span');
	spans[1].innerHTML = percent + '%'; // percent
	spans[2].innerHTML = (left >= 1 ? parseInt(left) + ' ' + this.options.Language.min + ' ' : '') + (left % 1 ? Math.floor(parseFloat(left.toString().replace(/^[0-9]*\./,'0.')) / 0.0166) : 0) + ' ' + this.options.Language.sec; // left
	spans[3].innerHTML = Kaizen.formatSize(speed); // speed
}

Kaizen.Form.Upload.prototype.startStatusPoll = function() {
	var ref = this;

	this.resetStatusPoll();
	this.AJAXstatusPoll.load(this.statusPollReqVars + (this.currentHash ? '&hash=' + this.currentHash : ''));
	this.statusPollResponseTimeout = setTimeout(function(){ref.startStatusPoll()}, this.options.statusPollMaxResponseWait);
}

Kaizen.Form.Upload.prototype.stopStatusPoll = function() {
	this.resetStatusPoll();
	this.currentHash = '';
	this.currentTimestamp = {'time':0, 'count':0};
	this.currentRequestCount = 0;
}

Kaizen.Form.Upload.prototype.resetStatusPoll = function() {
	if (this.AJAXstatusPoll.AJAXObject && this.AJAXstatusPoll.AJAXObject.inProgress)
		this.AJAXstatusPoll.abort();
	if (this.statusPollDelayTimeout) {
		clearTimeout(this.statusPollDelayTimeout);
		this.statusPollDelayTimeout = 0;
	}
	if (this.statusPollResponseTimeout) {
		clearTimeout(this.statusPollResponseTimeout);
		this.statusPollResponseTimeout = 0;
	}
}

Kaizen.Form.Upload.prototype.processStatusPollResponse = function(status, responseText) {
	var ref = this;

	this.currentRequestCount++;
	//window.status = 'status = ' + status + ' | responseText = ' + responseText;

	var nodelay;
	clearTimeout(this.statusPollResponseTimeout);
	this.statusPollResponseTimeout = 0;

	if (status == 200) {
		if (!this.currentHash) {
			// after the first request for hash, we don't need a delay (no matter whether we get the hash with the reply or not)
			if (this.currentRequestCount == 1)
				nodelay = true;
			if (responseText && responseText.isJSON()) {
				eval('var obj = ' + responseText);
				if (obj.hash) {
					this.currentHash = obj.hash;
					nodelay = true;
				}
			}
			if (this.currentRequestCount > this.options.statusPollMaxGetHashAtempts) {
				// with a short timeout to allow the AJAX code to clean up after the callback
				setTimeout(function(){
					ref.uploadFailure();
				}, 20);
				return;
			}
		} else {
			if (responseText && responseText.isJSON()) {
				eval('var obj = ' + responseText);
				if (obj.speed) {
					if (this.currentTimestamp.time == obj.time)
						this.currentTimestamp.count++;
					else
						this.currentTimestamp = {'time':obj.time, 'count':0};
					// detect possible upload hang and abort if detected
					if (this.currentTimestamp.count > this.options.statusPollMaxReturnedMatchingTimestamp) {
						setTimeout(function(){
							ref.uploadFailure();
						}, 20);
						return;
					}
					this.updateStatus(obj.speed, obj.percent, obj.left);
				} else if (obj.error) {
					setTimeout(function(){
						if (obj.error == 'finished')
							ref.uploadSuccess(ref.currentHash);
						else
							ref.uploadFailure();
					}, 20);
					return;
				}
			}
		}
	}
	this.statusPollDelayTimeout = setTimeout(function(){ref.startStatusPoll()}, nodelay ? 1 : this.isClosed ? this.options.statusPollDelayWhenClosed : this.options.statusPollDelay);
}

Kaizen.Form.Upload.prototype.showAlert = function(message) {
	if (this.options.useJSAlertOnErrors)
		alert(message);
	else
		this.alertBox.show(message.replace(/\n/g, '<br />'), this.options.Language.OK);
}

Kaizen.Form.Upload.prototype.startUpload = function() {
	if (this.inProgress)
		return;
	var files = Kaizen.DOM.getElementsByClassName('file', 'div', this.pendingHolder);
	if (!files.length)
		return;

	// update elements
	var file = files[0];
	var progress = file.getElementsByTagName('span')[2];
	progress.innerHTML = this.options.Language.uploading;
	var link = file.getElementsByTagName('a')[0];
	link.innerHTML = this.options.Language.cancel;

	// start it
	var index = link._fileIndex;
	this.inProgress = index;
	this.forms[index].submit();

	this.showStatus();
}

Kaizen.Form.Upload.prototype.cancelUpload = function(success) {
	if (!this.inProgress)
		return;
	// do not use this.transport.src = 'about:blank' to avoid stupid bug in IE
	self.frames[this.options.transportName].location = 'about:blank';
	this.inProgress = 0;
	if (!success && this.currentHash) {
		var ref = this;
		var hash = this.currentHash;
		// after we have aborted the upload give the perl script a couple of seconds to release the file so that we can successfully delete it
		setTimeout(function(){ref.sendDeleteCommand(hash)}, 2000);
	}
	this.hideStatus();
}

Kaizen.Form.Upload.prototype.uploadSuccess = function(hash) {
	this.addUploaded(this.pendingFiles[this.inProgress].firstChild.firstChild.data.replace(/^[0-9]*\. /, ''), hash);
	this.deletePending(this.inProgress, true);
}

Kaizen.Form.Upload.prototype.uploadFailure = function() {
	this.deletePending(this.inProgress);
}

Kaizen.Form.Upload.prototype.onLoad = function() {
	var doc = this.transport.contentDocument || this.transport.contentWindow.document || this.transport.document;
	var responseText = doc.body.innerHTML;
	//alert(responseText + '==' + responseText.isJSON())

	// about:blank, 404, 500 etc...
	if (!responseText || !responseText.isJSON())
		return;

	eval('var obj = ' + responseText);

	// file failed or uploaded, go to next
	if (obj.error) {
		switch (obj.error) {
			case 'finished':
				this.uploadSuccess(obj.hash);
				return;
			case 'extension':
				var message = this.options.Language.errorInvalidFileExt + obj.valid.replace(/\,/g, ' , ');
				this.showAlert(message);
				break;
			case 'setting':
				if (obj.allowedsize) {
					var message = this.options.Language.errorFilesizeTooLarge.replace(/\[allowedsize\]/, Kaizen.formatSize(obj.allowedsize));
					this.showAlert(message);
				}
				break;
			default:
				;
		}
	}
	this.uploadFailure();
}

/* some API functions */

// Returns uploaded files in an array like this: [{'name':'filename1', 'hash':'hash1'}, {'name':'filename2', 'hash':'hash2'}...]
Kaizen.Form.Upload.prototype.getUploaded = function() {
	var uploadedArr = [];
	var files = Kaizen.DOM.getElementsByClassName('file', 'div', this.uploadedHolder);
	var cur, name, hash;
	for (var i = 0, l = files.length; i < l; i++) {
		cur = files[i].getElementsByTagName('span');
		name = cur[0].innerHTML.replace(/^[0-9]*\. /, '');
		hash = cur[1].firstChild._hash;
		uploadedArr.push({'name':name, 'hash':hash});
	}
	return uploadedArr;
}

// Checks whether there are any running uploads - returns true/false
Kaizen.Form.Upload.prototype.isInProgress = function() {
	return this.inProgress || this.pendingHolder && this.pendingHolder.getElementsByTagName('div').length > 0;
}
