﻿var eve = eve || {};

/**
 * Erstellt fuer das Tell A Friend-Formular eine entsprechende Validation.
 *
 * 2008-09-15 changed rel to class, because rel is not xhtml valid, an profile now must begin with "validate-*", * = email, required or an other profile
 *
 * @class	eve.formValidation
 * @constructor
 * @param	{object}	options						Optionshash:
 * @param	{string}	options.formId				ID des Form-Dom-Elements
 * @param	{object}	options.customMessages		Fehlermeldungen die statt der Defaults genutzt werden ...
 * @param	{boolean}	options.redLabels			Optional: If true, the Labels will be styled red if an error occur. Default is true.
 * @param	{boolean}	options.alertBox			Optional: If true, the Error-Messages will be displayed in an alert box, default is false
 */
eve.formValidation = function(options) {
	
	this.formId = options.formId || 'keineAhnungNixAngegeben';
	
	/**
	 * Formular-DOM-Element, welches validiert werden soll.
	 * @type	{element}
	 */
	this.formEl = jQuery('#' + this.formId).get(0) || null;
	
	/**
	 * If true, the Labels will be styled red if an error occur.
	 * @type	{boolean}
	 */
	this.redLabels = (typeof options.redLabels == 'boolean') ? options.redLabels : true;
	
	/**
	 * If true, the Error-Messages will be displayed in an alert box.
	 * @type	{boolean}
	 */
	this.alertBox = options.alertBox || false;
	
	/**
	 * Profile koennen auf einem Element angegeben werden, um die jeweilige Validations-
	 * Methode zu definieren. Wichtig ist, dass jedes Formular-Element, welches validiert
	 * werden soll auch ein Label-Element hat.
	 *
	 * %t = Titel des Eingabefeldes (Label)
	 * %i = Position eines Elements, zum Beispiel einer E-Mail-Liste
	 * %v = Wert eines Eingabefeldes
	 *
	 * @type	{object}
	 */
	this.profiles = this.serializeProfiles({
		
		'email' : {
			'invalid' : 'Die unter "%t" angegebene E-Mail-Adresse scheint ung&uuml;ltig zu sein.'
		},
		
		'emaillist' : {
			'overmax' : 'Es sind maximal %i E-Mail-Adressen f&uuml;r "%t" erlaubt!',
			'invalid' : 'Die unter "%t" angegebene E-Mail-Adresse "%v" scheint ung&uuml;ltig zu sein.'
		},
		
		'required' : {
			
			'missing' : {
				'text,password,textarea' : 'Bitte f&uuml;llen Sie das Feld "%t" aus!',
				'checkbox,radio,select' : 'Bitte w&auml;hlen Sie zu "%t" etwas aus!'
			}
		}		
	});
	
	/**
	 * Custom Error Messages
	 * @type	{object}
	 */
	this.customMessages = options.customMessages || {};
	
	/**
	 * Ermittelt aus dem Formular die zu validierenden Eingabefelder und uebergibt diese
	 * an diese Variable.
	 * @type	{object}
	 */
	this.formFields = [];
	
	/**
	 * Regular Expression fuer E-Mail-Validation ...
	 * @type	{regexp}
	 */
	this.mailRegExp = /^\w+([\.-]?\w+)*@\w+([\.-]?\w+)*\.(\w{2}|(com|net|org|edu|int|mil|gov|arpa|biz|aero|name|coop|info|pro|museum))$/;
	
	/**
	 * Container in dem die Fehlermeldungen angezeigt werden.
	 * @type	{element}
	 */
	this.errorContainer = document.createElement('span');
	
	/** Initialisieren der Form-Validation ... */
	this.serializeForm();
};

eve.formValidation.prototype = {
	
	/**
	 * Wandelt eine Profilkonfiguration in ein valides und nutzbares Profil
	 * um und gibt dieses zurueck.
	 *
	 * @param	{object}	profileConfig	Profil-Konfigurations-Objekt.
	 * @return	Liefert die serialisierten neuen Profile zurueck.
	 * @type	{object}
	 */
	serializeProfiles : function(profileConfig) {
		
		profileConfig = profileConfig || {};
		
		var serializeProfile = {};
		
		/** PROFIL NAME */
		for (var profileName in profileConfig) {
			
			var tmpProfile = profileConfig[profileName];
			serializeProfile[profileName] = serializeProfile[profileName] || {};
			
			/** PROFILTYPEN */
			for (var profileType in tmpProfile) {
			
				var tmpProfileType = tmpProfile[profileType];
				serializeProfile[profileName][profileType] = serializeProfile[profileName][profileType] || {};
				
				if (typeof tmpProfileType == 'string') {
					
					serializeProfile[profileName][profileType]['*'] = tmpProfileType;
					
				} else if (typeof tmpProfileType == 'object') {
					
					/** ELEMENT TYPES */
					for (var tmpElementTypes in tmpProfileType) {
					
						var tmpElementProfile = tmpProfileType[tmpElementTypes];
						var tmpElementNames = tmpElementTypes.split(',');
						
						for (var i = 0; i < tmpElementNames.length; i++) serializeProfile[profileName][profileType][tmpElementNames[i]] = tmpElementProfile;
					}
				}
			}
		}
		
		return serializeProfile;
	},
	
	
	/**
	 * Liefert alle Form-Felder zurueck, die validiert werden sollen. Dazu wird
	 * im Rel-Attribute der Elemente nachgeschaut.
	 *
	 * @return	Liefert alle Form-Felder zurueck, die validiert werden sollen.
	 * @type	{object}
	 */
	serializeForm : function() {
		
		/** Merke die aktuelle Instanz und bereite weitere Werte vor */
		this.formEl.taffvClass = this;
		var availableFormFields = [];
		
		/** Hole alle Felder die ein Rel Attribute haben und innerhalb des Formulars liegen ... */
		var tmpFormId = this.formId;
		var tmpFields = jQuery('#' + tmpFormId + ' [class]'); /** Old rel */
		
		for (var i = 0; i < tmpFields.length; i++) {
			
			/** Shortcut */
			var tmpField = tmpFields[i];
			
			/** Aktuelle Instanz auf dem Element merken */
			tmpField.taffvClass = this;
			
			/** Profile fuer Validation ermitteln */
			var tmpRels = ((tmpField.className || tmpField.getAttribute('class')) || '').split(' ');
			var tmpProfiles = {};
			var tmpRegExp = new RegExp('validate-', 'g');
			
			for (var j = 0; j < tmpRels.length; j++) {
				
				/** Shortcut */
				var tmpProfileName = tmpRels[j];
				
				if (!tmpRegExp.test(tmpProfileName)) continue;
				
				tmpProfileName = tmpProfileName.replace(tmpRegExp, '');
				
				/** Nachschauen, ob das Profile noch Parameter hat, welcher durch einen : angehangen werden. */
				var tmpParams = tmpProfileName.split(':');
				var tmpName   = tmpParams.shift();
				
				tmpProfiles[tmpName] = (tmpParams.length > 0) ? tmpParams : true ;
			}
			
			/** Typ bestimmen */
			var tmpType = tmpField.nodeName.toLowerCase();
			if (tmpType == 'input') tmpType = (tmpField.getAttribute('type') || 'text').toLowerCase();
			
			/** Label ermitteln ... */
			var tmpFieldId = tmpField.getAttribute('id');
			var tmpLabel = jQuery('#' + tmpFormId + ' label[for=' + tmpFieldId + ']').get(0) || null;
			var tmpTitle = (tmpLabel != null) ? tmpLabel.firstChild.nodeValue || '' : '' ;
			var tmpName = tmpField.getAttribute('name') || 'unknown';
			
			this.formFields.push({
				
				el : tmpField,
				profiles : tmpProfiles,
				type : tmpType,
				label : tmpLabel,
				title : tmpTitle,
				name : tmpName
			});
		}
		
		/** Nur, wenn es auch was zu tun gibt, dann Validation starten ... */
		if (this.formFields.length > 0) {
			
			/** ErrorContainer einhaengen */
			jQuery(this.formEl).prepend(this.errorContainer);
			
			/** Submit Event erstellen */
			jQuery(this.formEl).submit(this.checkForm);
		}
	},
	
	/**
	 * Validiert das Formular, gibt true fuer valide und false fuer nicht valide
	 * zurueck, um das Absenden des Formulars jeweils zu verhindern oder auch nicht.
	 */
	checkForm : function() {
		
		/** Wenn this nicht die aktuelle Instanz ist, dann rufe die aktuelle Instanz auf! */
		if ((this.taffvClass || null) != null) return this.taffvClass.checkForm();
		
		/** Validstatus initialisieren */
		this.errors    = {};
		this.hasErrors = false;
		
		/** Alle Elemente pruefen ... */
		for (var i = 0; i < this.formFields.length; i++) this.checkElement(this.formFields[i]);
		
		this.showErrors();
		
		if (this.hasErrors) return false;
		
		return true;
	},
	
	/**
	 * Prueft alle auf dem Element angegebenen Profile ..., tritt ein Fehler auf wird
	 * dies in this.errors vermerkt.
	 *
	 * @param	{object}	elementToCheck		Objekt enthaelt das DOM-Element und die darauf zu pruefenden Profile.
	 */
	checkElement : function(elementToCheck) {
		
		for (var profileName in elementToCheck.profiles) {
			
			/** Pruefen ob das Profile existiert ... */
			if ((this[profileName + 'Validation'] || null) != null) this[profileName + 'Validation'](elementToCheck, elementToCheck.profiles[profileName]);
		}
	},
	
	/**
	 * Zeigt Fehlermeldungen an. Dabei ist es wichtig, dass zuerst nur required-Fehler angezeigt werden und
	 * dann der Rest, falls es pro Feld keine Requireds gibt ;)
	 */
	showErrors : function() {
		
		this.errorContainer.innerHTML = '';
		var tmpErrorCode = '';
		
		for (var fi = 0; fi < this.formFields.length; fi++) {
			
			var tmpField = this.formFields[fi];
			var tmpName  = tmpField.name;
			var tmpLabel = tmpField.label;
			var tmpError = this.errors[tmpName] || null;
			
			if (jQuery(tmpLabel).hasClass('error')) jQuery(tmpLabel).removeClass('error');
			
			if ((tmpError || null) == null) continue;
			
			var tmpRequired = tmpError.required || null;
			
			if (!jQuery(tmpLabel).hasClass('error') && this.redLabels === true) jQuery(tmpLabel).addClass('error');
			
			if (tmpRequired != null) {
				
				for (var errorType in tmpRequired) {
					
					var tmpErrorType = tmpRequired[errorType];
					for (var i = 0; i < tmpErrorType.length; i++) tmpErrorCode += '<li>' + tmpErrorType[i] + '</li>';
				}
				
			} else {
				
				for (var profileName in tmpError) {
					
					var tmpErrorType = tmpError[profileName] || {};
					for (var errorType in tmpErrorType) {
						
						var tmpProfileError = tmpErrorType[errorType] || [];
						for (var i = 0; i < tmpProfileError.length; i++) tmpErrorCode += '<li>' + tmpProfileError[i] + '</li>';
					}
				}
			}
		}
		
		if (tmpErrorCode != '') {
		
			if (this.alertBox) {
				
				var tmpMsg = tmpErrorCode.replace(new RegExp('<li>', 'g'), '').replace(new RegExp('</li>', 'g'), "\n").replace(new RegExp('&ouml;', 'g'), 'ö').replace(new RegExp('&auml;', 'g'), 'ä').replace(new RegExp('&uuml;', 'g'), 'ü');
				
				alert(tmpMsg);
				
			} else {
				
				this.errorContainer.innerHTML = '<div class="hcError"><h3>Es sind Fehler bei der Eingabe entstanden:</h3><ul>' + tmpErrorCode + '</ul></div>';
			}
		}
	},
	
	/**
	 * Setzt entsprechende Fehlermeldungen nach Profil-Type und Fehler-Typ
	 * fuer bestimmte Elemente.
	 *
	 * @param	{object}	options				Optionshash:
	 * @param	{element}	options.field		Internes Objekt mit .el - DOM-Element usw.
	 * @param	{string}	options.profileName	Name des Profiles, welches einen Error geschmissen hat.
	 * @param	{string}	options.errorType	Name des Error-Typen, z. B. invalid, missing, usw. ...
	 * @param	{object}	options.replaces	Objekt mit Plathaltern und zugehörigen Werten fuer die Fehlermeldungen.
	 */
	setError : function(options) {
		
		var tmpField       = options.field || false;
		var tmpProfileName = options.profileName || false;
		var tmpErrorType   = options.errorType || false;
		var tmpReplaces    = options.replaces || {};
		
		/** Fehlt was? */
		if (!tmpProfileName || !tmpErrorType || !tmpField) {
			
			if ((console || null) != null) console.debug('Fehler :: setError :: kein Eingabeelement, Profilname oder Errortyp', arguments);
		}
		
		var tmpFormEl     = tmpField.el || {};
		var tmpFormElType = tmpField.type;
		var tmpFieldName  = tmpField.name;
		
		
		/** Objekte vorbereiten */
		this.errors = this.errors || {};
		this.errors[tmpFieldName] = this.errors[tmpFieldName] || {};
		this.errors[tmpFieldName][tmpProfileName] = this.errors[tmpFieldName][tmpProfileName] || {};
		this.errors[tmpFieldName][tmpProfileName][tmpErrorType] = this.errors[tmpFieldName][tmpProfileName][tmpErrorType] || [];
		
		/** Meldung ermitteln */
		var tmpProfile = this.profiles[tmpProfileName][tmpErrorType] || {};
		var errorMsg = ((this.customMessages[tmpFieldName] || {})[tmpProfileName] || {})[tmpErrorType] || (tmpProfile[tmpFormElType] ||tmpProfile['*']);
		
		/** Meldung zusammenparsen ... */
		tmpReplaces.t = tmpField.title;
		
		for (var placeholder in tmpReplaces) errorMsg = errorMsg.replace(new RegExp('%' + placeholder, 'g'), tmpReplaces[placeholder]);
		
		/** Meldung merken ... */
		this.errors[tmpFieldName][tmpProfileName][tmpErrorType].push(errorMsg);
		this.hasErrors = true;
	},
	
	/**
	 * Validationsprofil: required
	 *
	 * Prueft ob das Eingabefeld, je nach Typ, gesetzt wurde.
	 *
	 * @param	{object}	field		Internes Objekt mit .el - DOM-Element usw.
	 */
	requiredValidation : function(field) {
		
		var tmpProfile = 'required';
		var el         = field.el;
		var elNodeName = el.nodeName.toLowerCase();
		var elType     = field.type;
		var tmpValue   = '';
		
		if (elNodeName == 'input' || elNodeName == 'textarea') {
			
			if (elType == 'text' || elType == 'password' || elNodeName == 'textarea') {
				
				tmpValue = (el.value || '');
				
			} else if (elType == 'radio') {
				
				/** Not supported yet! */
				return;
				
			} else if (elType == 'checkbox') {
				
				/** Not supported yet! */
				return;
			}
				
		} else {
				
			/** SELECT */
			
			/** Not supported yet! */
			return;
		}
		
		if (tmpValue == '') this.setError({
														   
			field       : field,
			profileName : tmpProfile,
			errorType   : 'missing'
		});
	},
	
	/**
	 * Validationsprofil: emaillist
	 *
	 * E-Maillisten werden durch ein ";" und/oder "; " getrennt und dann auf Anzahl
	 * und gueltigkeit hin geprueft.
	 *
	 * @param	{object}	field		Internes Objekt mit .el - DOM-Element usw.
	 * @param	{integer}	maxEntrys	Maximal erlaubte E-Mails in einer Liste, Default unendlich viele.
	 */
	emaillistValidation : function(field, maxEntrys) {
		
		var tmpProfile = 'emaillist';
		var el         = field.el;
		
		/** Default? */
		maxEntrys = maxEntrys || 0;
		
		/** Richtige Parametertyp? */
		if (typeof maxEntrys != 'integer') maxEntrys = parseInt(maxEntrys);
		
		/** Inhalte ermitteln ... */
		var tmpArray = (el.value || '').split('; ').join(';').split(';');
		var tmpEntrys = [];
		for (var i = 0; i < tmpArray.length; i++) {
			
			if (tmpArray[i] != '') tmpEntrys.push(tmpArray[i]);
		}
		
		/** MAXENTRYS? */
		if (maxEntrys > 0 && tmpEntrys.length > maxEntrys) {
			
			this.setError({
														   
				field       : field,
				profileName : tmpProfile,
				errorType   : 'overmax',
				replaces    : { v : tmpEntrys.length, i : maxEntrys }
			});
				
			return;
		}
		
		/** ALLE EMAILS GUELTIG? */
		for (var i = 0; i < tmpEntrys.length; i++) {
			
			var tmpValue = tmpEntrys[i] || '';
			
			if (tmpValue == '') continue;
			
			if (!this.mailRegExp.test(tmpValue)) this.setError({
														   
				field       : field,
				profileName : tmpProfile,
				errorType   : 'invalid',
				replaces    : { 'v' : tmpValue }
			});
		}
		
		el.value = tmpEntrys.join(';');
	},
	
	/**
	 * Validationsprofil: required
	 *
	 * @param	{object}	field		Internes Objekt mit .el - DOM-Element usw.
	 *
	 * Prueft ob die angegebene E-Mail gueltig ist.
	 */
	emailValidation : function(field) {
		
		var el         = field.el;
		var tmpProfile = 'email';
		
		/** Value ermitteln */
		var tmpValue = (el.value || '');
		
		if (tmpValue == '') return;
		
		if (!this.mailRegExp.test(tmpValue)) {
			
			this.setError({
														   
				field       : field,
				profileName : tmpProfile,
				errorType   : 'invalid',
				replaces    : { 'v' : tmpValue }
			});
		}
	}
};
