﻿/**
 * @author Simon Schmidt
 */


/**
 * Following are the preset patterns that can be used to validate the form.
 * The input fields in the form need to have the same class name as the names 
 * of the patterns to be matched against them.
 */
var patterns = [	
	{	"name"			: "OBLIGATORY", 
		"pattern"		: ["\\S"],
		"errorMessage"	: {	"en"	:	"This field is obligatory",
							"de"	: 	"Dieses Feld ist ein Pflichtfeld"}},
	{	"name" 			: "DATE",
		"pattern"		: [	"^[0-9][0-9]?.[0-9][0-9]?.[0-9][0-9][0-9][0-9]$",
							"^[0-9][0-9]?-[0-9][0-9]?-[0-9][0-9][0-9][0-9]$",
							"^[0-9][0-9]?/[0-9][0-9]?/[0-9][0-9][0-9][0-9]$"],
		"errorMessage"	: {	"en"	:	"This field requires a date (e.g. mm/dd/yyy)",
							"de"	: 	"Dieses Feld erfordert eine Datumseingabe (z.b. dd.mm.jjjj)"}},
	{ 	"name"			: "EMAIL",
		"pattern"  		: [	"^(([A-Za-z0-9]+_+)|([A-Za-z0-9]+\\-+)|([A-Za-z0-9]+\\.+)|([A-Za-z0-9]+\\++))*[A-Za-z0-9]+@((\\w+\\-+)|(\\w+\\.))*\\w{1,63}\\.[a-zA-Z]{2,6}$"],
		"errorMessage"	: {	"en"	:	"This field requires a email address",
							"de"	: 	"Dieses Feld erwartet eine Emailadresse"}},
	{	"name"			: "NUMERIC",
		"pattern"		: [	"^\\d+$"],
		"errorMessage"	: {	"en"	:	"This field requires a numeric value",
							"de"	: 	"Dieses Feld erwartet eine numerische Eingabe"}},
	{	"name"			: "NUMERIC_EXTENDED",
		"pattern"		: [	"^\\d+[\\d\\-\\/]+\\d+$"],
		"errorMessage"	: {	"en"	:	"This field requires a numeric value",
							"de"	: 	"Dieses Feld erwartet eine numerische Eingabe"}},
	{	"name"			: "WORD",
		"pattern"		: [ "^\\w*$"],
		"errorMessage"	: {	"en"	:	"This field requires a word (without whitespaces)",
							"de"	: 	"Dieses Feld erwartet ein Wort (ohne Leerzeichen)"}},
	{	"name"			: "TEXT",
		"pattern"		: [ "^[A-ZÀ-Üß][A-ZÀ-Üß\\s]*$"],
		"errorMessage"	: {	"en"	:	"This field requires a text value",
							"de"	: 	"Dieses Feld erfordert eine text Eingabe"}},
	{	"name"			: "TEXT_EXTENDED", 
		"pattern"		: [	"^[A-ZÀ-Üß\\-_\\.:,;\\'][A-ZÀ-Üß\\-_\\.:,;\\'\\s]*$"],
		"errorMessage"	: {	"en"	:	"",
							"de"	: 	""}}
	];	

	
var errorTexts = [
	{	"name" 			: "RADIO", 
		"errorMessage" 	: [	{	"lang" :	"en",
								"text" : 	"Please select one of the options"}, 
							{	"lang" :	"de",
								"text" : 	"Bitte w&auml;hlen Sie eine der Optionen"}]},
	{	"name"			: "OBLIGATORY", 
		"errorMessage"	: [	{	"lang" :	"en",
								"text" :	"This field is obligatory"},
							{	"lang" : 	"de",
								"text" : 	"Dieses Feld ist ein Pflichtfeld"}]},
	{	"name" 			: "DATE",
		"errorMessage"	: [	{	"lang" :	"en",
								"text" :	"This field requires a date (e.g. mm/dd/yyy)"},
							{	"lang" : 	"de",
								"text" : 	"Dieses Feld erfordert eine Datumseingabe (z.b. dd.mm.jjjj)"}]},
	{ 	"name"			: "EMAIL",
		"errorMessage"	: [	{	"lang" :	"en",
								"text" :	"This field requires a email address"},
							{	"lang" : 	"de",
								"text" : 	"Dieses Feld erwartet eine Emailadresse"}]},
	{	"name"			: "NUMERIC",
		"errorMessage"	: [	{	"lang" :	"en",
								"text" :	"This field requires a numeric value"},
							{	"lang" : 	"de",
								"text" : 	"Dieses Feld erwartet eine numerische Eingabe"}]},
	{	"name"			: "NUMERIC_EXTENDED",
		"errorMessage"	: [	{	"lang" :	"en",
								"text" :	"This field requires a numeric value"},
							{	"lang" : 	"de",
								"text" : 	"Dieses Feld erwartet eine numerische Eingabe"}]},
	{	"name"			: "WORD",
		"errorMessage"	: [	{	"lang" :	"en",
								"text" :	"This field requires a word (without whitespaces)"},
							{	"lang" : 	"de",
								"text" : 	"Dieses Feld erwartet ein Wort (ohne Leerzeichen)"}]},
	{	"name"			: "TEXT",
		"errorMessage"	: [	{	"lang" :	"en",
								"text" :	"This field requires a text value"},
							{	"lang" : 	"de",
								"text" : 	"Dieses Feld erfordert eine text Eingabe"}]},
	{	"name"			: "TEXT_EXTENDED", 
		"errorMessage"	: [	{	"lang" :	"en",
								"text" :	""},
							{	"lang" : 	"de",
								"text" : 	""}]},
	{	"name"			: "MAX_LENGTH",
		"errorMessage"	: [ {	"lang" : 	"en",
								"text" :	"This field allowes a maximum of $x chracter input"},
							{	"lang" :	"de",
								"text" :	"Dieses Feld erlaubt eine maximale Eingabe von $x Zeichen"}]},
	{	"name"			: "MIN_LENGTH",
		"errorMessage"	: [ {	"lang" : 	"en",
								"text" :	"This field requires an input of at least $x chracters"},
							{	"lang" :	"de",
								"text" :	"Dieses Feld erfordert eine Eingabe von mindestens $x Zeichen"}]},
	{	"name"			: "LENGTH",
		"errorMessage"	: [ {	"lang" : 	"en",
								"text" :	"This field requires an input of exactly $x chracters"},
							{	"lang" :	"de",
								"text" :	"Dieses Feld erfordert eine Eingabe von genau $x Zeichen"}]}
	];
	
	
/**
 * @classDescription
 * @constructor
 * @param {function} fn
 */
function Container(fn)
{
    this._content = new Array();
    this._numElements = 0;
    
    this._iterateFn = fn;
} 
Container.prototype = {
    "count"  : function()
    {
        return this._numElements;
    },
    "updateNumElements" : function()
    {
        this._numElements = this._content.length;
    },
    "push" : function(elm)
    {
        this._content[this._numElements] = elm;
        this.updateNumElements();
    },
    "pop" : function()
    {
        var retval = null;
        
        if(this._numElements>0)
        {
            retval = this._content[this._numElements-1];
        
            this._content[this._numElements-1] = null;
        
            this._numElements -= 1;
        }
        
        return retval;
    },
    "clear" : function()
    {
        this._content = null;
        
        this._content = new Array();
        
        this._numElements = 0;
    },
    "getElement" : function(i)
    {
        var retval = null;
        
        if(i < this._numElements && i >= 0)
            retval = this._content[i];
            
        return retval;        
    },
    "setElement" : function(i, elm)
    {
        var retval = false;
        
        if(i <= this._numElements && i >= 0)
        {
            this._content[i] = elm;
            retval = true;
        }
        
        this.updateNumElements();
                        
        return retval;
    },
    "iterate" : function(fn)
    {
        if(fn!="" && jQuery.isFunction(fn))
        {
            for(var i = 0; i < this._numElements; i++)
            {
                fn(this._content[i]);
                
            }
        }
        
    }
}
/**
 * @classDescription
 * @constructor
 * @param {void}
 */
function ValidationError()
{	
	/**
	 * @type: string
	 */
	this._errorMessageText = "";
	
	/**
	 * @type: int
	 */
	this._errorState = 0;
	
	/**
	 * @type: string
	 */
	this._noErrorInfo = "";
	
	/**
	 * @type: string
	 */
	this._errorInfo = "";
	
	this._errorFields = new Array();
}
ValidationError.prototype = {
	/**
	 * @method logError
	 * @param {string} errorText
	 * @return {void}
	 */
	"logError" : function(fieldName, patternName, lang, strLen)
	{
		if(lang==undefined || lang=="")
			lang = "en";
			
		var message = "";	
		for(var t = 0; t < errorTexts.length; t++)
		{
			if(errorTexts[t].name==patternName)
			{
				for(var l = 0; l < errorTexts[t].errorMessage.length; l++)
				{
					if(errorTexts[t].errorMessage[l].lang == lang)
					{
						message = errorTexts[t].errorMessage[l].text;
						
						if((patternName=="MAX_LENGTH"||patternName=="MIN_LENGTH"||patternName=="LENGTH")&&strLen!=undefined&&strLen>-1)
						{
							message = message.replace(/\$x/g,strLen);
						}						
						l = errorTexts[t].errorMessage.length;
					}
				}
				t = errorTexts.length;
			}
		}
		this._errorMessageText += fieldName + ": " + message + "\n";
	},
	/**
	 * @method getErrorMessageText
	 * @param {void}
	 * @return {string} (String containing the error message)
	 */
	"getErrorMessageText" : function()
	{
		return this._errorMessageText;
	},
	/**
	 * @method reset
	 * @param {void}
	 * @return {void}
	 */
	"reset" : function(){
		this._errorState = 0;
		this._errorMessageText = "";
		this._errorFields = new Array();
	},
	/**
	 * @method addErrorField
	 * @param {XHTML_Object} $field
	 * @return {void}
	 */
	"addErrorField" : function($field){
			this._errorFields[this._errorFields.length] = $field;
	},
	/**
	 * @method markUpErrors
	 * @param {void}
	 * @return {void}	
	 */
	"getErrorFields" : function(){
		return this._errorFields;
	}
}

/**
 * @classDescription
 * @constructor
 * @param {ValidationError_Object} veo
 */
function FormValidator(veo)
{
	this._validationErrorObject = (veo!=undefined)?veo:null;
	
	this._pattern = new Array();
}
FormValidator.prototype = {
	/**
	 * @method getErrorMessageText
	 * @param {void}
	 * @return {string} (String containing the error message)
	 */
	"getErrorMessageText" : function()
	{
		this._validationErrorObject.getErrorMessage();
	},
	/**
	 * @method validate
	 * @param {string} val
	 * @param {string} patternName
	 * @return {bool} (true if form is valid, false if form is invalid)
	 */
	"validate" : function(val, patternName){
		var retval = new Object();
		
		retval.success = false;
		retval.pattern = new Object();
		
		//find pattern
		for(var p = 0; p < patterns.length; p++)
		{	
			if(patterns[p].name == patternName)
			{
				for(var i = 0; i < patterns[p].pattern.length; i++)
				{
					var regexpPattern = new RegExp(patterns[p].pattern[i], "gi");
					if (val.match(regexpPattern)) {
						retval.success = true;
						i = patterns[p].pattern.length;
					}
					else
					{
						/*console.log("\"" + val + "\" no match for: " + regexpPattern);*/
					}
					retval.pattern = patterns[p];
				}
				p = patterns.length;
			}
		}
		return retval;
	},
	/**
	 * @method addPattern
	 * @param {string} patternName
	 * @param {string} pattern
	 * @return {void} 
	 */
	"addPattern" : function(patternName, pattern){
		
	},
	/**
	 * @method registerValidationErrorObject
	 * @param {ValidationError_Object} veo
	 * @return {void} 
	 */
	"registerValidationErrorObject" : function(veo){
		this._validationErrorObject = veo;
	},
	/**
	 * @method getNumErrors
	 * @param {void}
	 * @return {int} (number of errors) 
	 */
	"getNumErrors" : function(){
		return this._ve.getNumErrors();
	}
}

/**
 * @classDescription manages HTML Form validation and sending
 * @constructor
 * @param {XHTML_Object} $form
 * @param {JSON_Object} options
 * 		@param {string} classPrefix
 * 		@param {bool} saveFormForReset
 */
function FormManager($form, options)
{
	/**
	 * _form: XHTML Object
	 * 
	 */
	this._form = ($form!=undefined)?$form:null;
	
	/**
	 * _ajaxFormTarget: String 
	 * 	file to which the form should be sent
	 */
	this._ajaxFormTarget = "";
	
	/**
	 * _ajaxResponseHandeler: function
	 * 	function that should be
	 */ 
	this._ajaxResponseHandeler = null;

	/**
	 * _autoValidation: bool
	 * 	defines if the Form should be validated automatically when submitted:
	 * 	true: 	validated
	 * 	false: 	not validated
	 */	
	this._autoValidation = true;
	
	
	this._customMarkUpObligatory = (options!=undefined&&options.customMarkUpObligatory!=undefined)?options.customMarkUpObligatory:"";
	
	this._customMarkUpError = (options!=undefined&&options.customMarkUpError!=undefined)?options.customMarkUpError:"";
	
	
	this._condPrefix 	= (options!=undefined&&options.condPrefix!=undefined)?options.condPrefix:"cond_";
	
	this._lang = (options!=undefined&&options.lang!=undefined)?options.lang:"de";
	
	this._saveFormForReset		= (options!=undefined&&options.saveFormForReset!=undefined)?options.saveFormForReset:"true";
	
	this._saveableClassName 	= (options!=undefined&&options.saveableClassName!=undefined)?options.saveableClassName:"saveable";
	
	this._condPrefix 			= "cond_";
	this._groupPrefix			= "group_";
	
	this._numberClassName 		= "val_numeric";
	this._dateClassName 		= "val_date";
	this._emailClassName 		= "val_email";
	this._textClassName 		= "val_text";
	this._obligatoryClassName 	= "val_obligatory";
	
	this._obligatoryMarkupClassName = "obligatoryMarkup";
	this._noErrorMarkupClass 	= "good";
	this._errorMarkupClass 		= "bad";
	
	this._classPrefix 			= (options!=undefined&&options.classPrefix!=undefined)?options.classPrefix:"val_";
	
    this._displayArea           = (options!=undefined&&options.displayArea!=undefined)?options.displayArea:"";
	$(this._displayArea).hide();
//alternative	
    this._errorTextHeadline     = (options!=undefined&&options.errorTextHeadline!=undefined)?options.errorTextHeadline:"errors:";
	
	this._vec = new Container();
	this._fv = new FormValidator();
	this._fv.registerValidationErrorObject(this._ve);
	
    this._imageFolderPath   = (options!=undefined&&options.imageFolderPath!=undefined)?options.imageFolderPath:"img/";
    
    this._dateClassName = (options!=undefined&&options.dateClassName!=undefined)?options.dateClassName:"date";
    
	this._resetOnClear = options.resetOnClear||false;
	this._formOriginalState = new Array();
	
	this._datePickers = new Array();
	
	
	//TODO: save values of all fields into an JSON (needed for clear())
    var self = this;
	
    $form.find("input").add('select').add('textbox').each(function(){
		if(($(this)[0].tagName).toLowerCase() == "input" && ($(this).attr('type') == "checkbox" || $(this).attr('type') == "radio"))
			self._formOriginalState[self._formOriginalState.length] = { "obj" : $(this), "checked" : $(this).is(':checked'), "type" : $(this).attr('type')};
		else
			self._formOriginalState[self._formOriginalState.length] = { "obj" : $(this), "val" : $(this).val(), "type" : "text" };
        
		if(self.selectDistinctClassByPrefix($(this).attr('class'), self._condPrefix)!=false){
			$(this).bind('change', {"self" : self}, self.conditionChanged);
		}
		
        if($(this).hasClass("val_date")){
			self.makeDatePicker($(this));
        }
		
		if($(this).hasClass("val_numeric")&&(!$(this).hasClass('no_picker'))){
			self.makeNumericUpDown($(this));
        }
		
		
    })
		
}
FormManager.prototype = {
	/**
	* @method makeDatePicker
	* @param {Object} $elem
	* @return {void}
	* @description
	*/
	"makeNumericUpDown" : function($elem){
		var self = this;
		
		$elem.wrap('<div class="ui-wrapper"></div>');
		$elem.parent('.ui-wrapper').css({  "position"  : "relative", "display" : "inline"});
		
		var numericUpDown = $('<span class="numericUpDown">' + 
								'<a class="up">&nbsp;</a>' + 
								'<a class="down">&nbsp;</a>' + 	
							'</span>');
		
		$elem.after(numericUpDown);
		
								
		numericUpDown.css({  	"position"  : "absolute",
								"left"      : $elem.outerWidth() - 10 + "px",
								"top"       : $elem.position().top + ($elem.outerHeight() - numericUpDown.outerHeight())/2 + "px",
								"padding"   : "0"});
								
		$triggers = new Object();
		$triggers.up = numericUpDown.find('.up');
		$triggers.down = numericUpDown.find('.down');
		new NumericUpDown($triggers, $elem);
	},
	/**
	* @method makeDatePicker
	* @param {Object} $elem
	* @return {void}
	* @description
	*/
	"makeDatePicker" : function($elem){
		var self = this;
		
		$elem.wrap('<div class="ui-wrapper"></div>');
		$elem.parent('.ui-wrapper').css({  "position"  : "relative", "display" : "inline"});
					
		var dateUpDown = $('<span class="numericUpDown">' + 
								'<a class="up">&nbsp;</a>' + 
								'<a class="down">&nbsp;</a>' + 	
							'</span>');
					
		var datePickerOpener = $('<a class="datePickerOpener"><img src="' + self._imageFolderPath + 'Kalender.jpg" /></a>');
		
		$elem.after(dateUpDown);
		$elem.after(datePickerOpener);
		
		dateUpDown.css({  	"position"  : "absolute",
								"left"      : $elem.outerWidth() - 32 + "px",
								"top"       : $elem.position().top + ($elem.outerHeight() - dateUpDown.outerHeight())/2 + "px",
								"padding"   : "0"});
					
		datePickerOpener.css({  "position"  : "absolute",
								"left"      : $elem.outerWidth() - 20 + "px",
								"top"       : $elem.position().top + ($elem.outerHeight() - datePickerOpener.outerHeight())/2 + "px",
								"padding"   : "0"});
						
		$triggers = new Object();
		$triggers.up = dateUpDown.find('.up');
		$triggers.down = dateUpDown.find('.down');
		$triggers.datePickerOpener = datePickerOpener;
						
		self._datePickers[self._datePickers.length] = new DatePicker($triggers, $elem, self._fv, self._dateClassName);
	},
	/**
	* @method selectDistinctClassByPrefix
	* @param {String} classes
	* @param {String} prefix
	* @return {String}
	* @description returns a distinct class name out of a string of space-seperated class names,
	* specified by an prefix
	*/
	"selectDistinctClassByPrefix" : function(classes, prefix){
		var theClass = false;
		var prefixRegExp = new RegExp('\\s?'+prefix+'[^\\s]+\\s?','gi');
		var matches = classes.match(prefixRegExp);
		if(matches!=null && matches.length>0){
				theClass = matches;
		}
		return theClass;
	}, 
	/**
	 * @method conditionChanged
	 * @param {void}
	 * @return {void}
	 * @description called when a conditioned object changes state (e.g. radiobox)
	 */
	"conditionChanged" : function(event){
		var self = event.data.self;
		
		var tagname = $(this)[0].tagName;
		var name = $(this).attr('name');
		self._form.find(tagname+"[name='"+name+"']").not(tagname+"[name='"+name+"']:checked").each(function(){
			var helper = self.selectDistinctClassByPrefix($(this).attr('class'), self._condPrefix);
			for(var i = 0; i < helper.length; i++)
			{
				var condClass = helper[i].split(self._condPrefix)[1];
				var validationClass = self._classPrefix + condClass.split('-')[0];
				var group = '.'+condClass.split('-')[1];
				self._form.find(group).removeClass(validationClass);
			}
		})
		var helper = String(self.selectDistinctClassByPrefix($(this).attr('class'), self._condPrefix));
		var condClass = helper.split(self._condPrefix)[1];
		var validationClass = self._classPrefix + condClass.split('-')[0];
		var group = '.'+condClass.split('-')[1];
		
		self._form.find(group).addClass(validationClass);
	},
	/**
	 * @method validate
	 * @param {void}
	 * @return {bool} (true: form data is valid, false:	form data is invalid)
	 * @description validates the form
	 */
	"validate" : function(){
		var self = this;
		var retval = false;
		var numFields = this._form.find("."+this._saveableClassName).length;
		var pos = 0;
		this._vec.clear();
		
		//special treatment for radio boxes
		this._form.find("."+this._saveableClassName).filter('[type="radio"]').each(function(){
			//check if obligatory radioboxes are set
			if($(this).hasClass(self._obligatoryClassName))
			{
				var radioName = $(this).attr('name');
				var checked = false
				self._form.find('input[name="'+radioName+'"]').each(function(){
					if($(this).is(':checked'))
						checked = true;
				})			
				if(!checked)
				{
					var ve = new ValidationError();
					var field_name = self._form.find('label[for="'+radioName+'"]').text();
					ve.logError(field_name, "RADIO", self._lang);
					ve.addErrorField($(this));
					
					var errorExists = false;
					self._vec.iterate(function(elm){
						if(elm.getErrorMessageText()==ve.getErrorMessageText())
						{
							errorExists = true;
						}
					})
					if(!errorExists)
						self._vec.push(ve);
					return true;
				}
			}
		});
		
		this._form.find("."+this._saveableClassName).not('[type="radio"]').each(function(){
			var valClasses = new Array();
			var classes = $(this).attr("class").split(" ");
			for(var c = 0; c < classes.length; c++)
			{
				var valPrefixRegExp = new RegExp("^"+self._classPrefix+".", "gi");
				if(classes[c].match(valPrefixRegExp))
				{
					var valPrefixRegExp2 = new RegExp("^"+self._classPrefix, "i");
					var patternName = classes[c].replace(valPrefixRegExp2, "");
					valClasses[valClasses.length] = patternName;
					var result = self._fv.validate($(this).val(), patternName.toUpperCase());
					if(!result.success && $(this).hasClass(self._obligatoryClassName))
					{
						//TODO: Alternative field_name if label does not exist
						var ve = new ValidationError();
						var field_name = self._form.find('label[for="'+$(this).attr('name')+'"]').text();
						ve.logError(field_name, patternName.toUpperCase(), self._lang);
						ve.addErrorField($(this));
						
						self._vec.push(ve);
						return true;
					}
				}
			}
			if($(this).attr("maxlength")!=-1&&$(this).val().length > $(this).attr("maxlength")*1)
			{
				var ve = new ValidationError();
				var field_name = self._form.find('label[for="'+$(this).attr('name')+'"]').text();
				ve.logError(field_name, "MAX_LENGTH", self._lang, $(this).attr("maxlength"));
				ve.addErrorField($(this));
				self._vec.push(ve);
				return true;
			}
			
			if($(this).attr("minlength")!=-1&&$(this).val().length < $(this).attr("minlength")*1)
			{
				var ve = new ValidationError();
				var field_name = self._form.find('label[for="'+$(this).attr('name')+'"]').text();
				ve.logError(field_name, "MIN_LENGTH", self._lang, $(this).attr("minlength"));
				ve.addErrorField($(this));
				self._vec.push(ve);
				return true;
			}
			
			if($(this).attr("length")!=-1&&$(this).val().length < $(this).attr("length")*1)
			{
				var ve = new ValidationError();
				var field_name = self._form.find('label[for="'+$(this).attr('name')+'"]').text();
				ve.logError(field_name, "LENGTH", self._lang, $(this).attr("length"));
				ve.addErrorField($(this));
				self._vec.push(ve);
				return true;
			}
			pos++;
		})
		if(pos == numFields)
			retval = true;
			
		self.markUpErrors();	
			
		return retval;
	},
	/**
	 * @method clear
	 * @param {void}
	 * @return {void}
	 * @description	clears all fields in the form
	 */
	"clear" : function(){
		if(this._resetOnClear)
		{
			for(var i = 0; i < this._formOriginalState.length; i++)
			{
				var obj = this._formOriginalState[i].obj;
				if(this._formOriginalState[i].type == "radio" || 
						this._formOriginalState[i].type=="checkbox")
				{
					if(this._formOriginalState[i].checked==true)
						obj.attr('checked', 'checked');
					else
						obj.removeAttr('checked');
				}else{
					obj.val(this._formOriginalState[i].val);
				}
			}
		}
		else
		{
			this._form.find('input')
				.not("[type='button'],[type='submit'],[type='reset']")
				.not("[type='image'],[type='hidden'],[type='file']").val("");
			this._form.find('textarea').val("");
			this._form.find('select:selected').removeAttr("selected");
			this._form.find(":checked").removeAttr("checked");
		}
	},
	/**
	 * @method sendFormAjax
	 * @param {bool} _validate
	 * @return {void}
	 */
	
	"sendFormAjax" : function(doValidation, options){
		if(doValidation!=false)
			doValidation = true;
		
		if(doValidation)
			this.validate();
		
		//send form
		var action = (options!=undefined&&options.action!=undefined)?options.action:this._form.attr('action');
		var method = (options!=undefined&&options.method!=undefined)?options.method:this._form.attr('method');
		var customSuccessHandler = (options!=undefined&&options.customSuccessHandler!=undefined)?options.customSuccessHandler:"";
		var customErrorHandler = (options!=undefined&&options.customErrorHandler!=undefined)?options.customErrorHandler:"";
		
		var data = this.serializeForm();
		
		$.ajax({
			"url" : action,
			"method" : method,
			"data" : data,
			"success" : function(msg){
				if(customSuccessHandler!=""&&jQuery.isFunction(customSuccessHandler))
					customSuccessHandler(msg)
			},
			"error" : function(msg){
				if(customErrorHandler!=""&&jQuery.isFunction(customErrorHandler()))
					customErrorHandler(msg)
			}
		})
	},
	/**
	 * @method markUpObligatory
	 * @param {void}
	 * @return {void}
	 */
	"markUpObligatory" : function(){
		if(this._customMarkUpObligatory == "" || !jQuery.isFunction(this._customMarkUpObligatory()))
			this._form.find("."+this._obligatoryClassName).addClass(this._obligatoryMarkupClassName);
		else
			this._customMarkUpObligatory();
	},
	/**
	 * @method getErrorMessageText()
	 * @param {void}
	 * @return {string} (String containing the Error Text)
	 */
	"getErrorMessageText" : function(){
		var text = "";
        this._vec.iterate(function(elem)
            {
                text += elem.getErrorMessageText();
            }
        );
        return text;
	},
	/**
	 * @method markUpErrors
	 * @param {void}
	 * @return {void}	
	 */
	"markUpErrors" : function(){
		var self = this;
		if(self._customMarkUpError == "" || !jQuery.isFunction(this._customMarkUpError()))
		{
			if (self._noErrorMarkupClass != "") {
				$("." + self._noErrorMarkupClass).each(function(){
					$(this).removeClass(self._noErrorMarkupClass);
				})
			}

			if (self._errorMarkupClass != "") {
				$("." + self._errorMarkupClass).each(function(){
					$(this).removeClass(self._errorMarkupClass);
				});
				
				this._vec.iterate(function(elm){
					
					var $errorFields = elm.getErrorFields();
								
					for(var e = 0;e < $errorFields.length; e++){
						$errorFields[e].next('.validator').addClass(self._errorMarkupClass);
					}
				});
			}
		}
		else
			self._customMarkupError();
	},
	/**
	 * @method displayErrorMessage
	 * @param {void}
	 * @return {void}
	 */
	"displayErrorMessage" : function(){
        var text = "<b>" + this._vec.count() + " " + this._errorTextHeadline + "</b><ul>";
        this._vec.iterate(function(elem)
            {
                text += "<li>"+elem.getErrorMessageText()+"</li>";
            }
        );
        text += "</ul>";        
		$(this._displayArea).html(text);
		$(this._displayArea).slideDown('slow');
	},
	/** 
	 * @private
	 * @method serializeForm()
	 * @param {void}
	 * @return {string} (String containingserialized form)
	 */
	"serializeForm" : function(){
		return this._form.find("."+this._saveableClassName).serialize();
	}
}

function DatePicker($trigger, $target, validator, datePatternName)
{
	var dp = this;
	dp._target = $target;
	dp._trigger = $trigger.datePickerOpener;
	
	dp._upTrigger = $trigger.up;
	dp._downTrigger = $trigger.down;
	
	dp._daysPerMonth = new Array();
	dp._daysPerMonth["days"] = new Array(31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31);
	dp._daysPerMonth["names"] = new Array(	"Januar", 	"Februar", 	"M&auml;rz", 	"April", 	"Mai", 		"Juni", 
											"Juli", 	"August", 	"September", 	"Oktoer", 	"November", "Dezember");
	dp._trigger.click(function(){
		if(dp._picker!=null)
		{
			dp._picker.fadeOut('fast');
			dp._picker=null;
		}else{
			var date = dp._target.val();
			var result = validator.validate(date, datePatternName.toUpperCase());
			
			var selectedDate = null;
			if(date!="" && result.success)
			{
				day = date.split('.')[0];
				month = date.split('.')[1];
				year = date.split('.')[2];
				
				selectedDate = new Date(year, month-1, day);
			}
			else
			{
				selectedDate = new Date();
			}
			
			var html = dp.getPickerForDate(selectedDate);
						
			dp.buildPicker(html);
		}
	})
	
	dp._upTrigger.mousedown(function(){
		var val = dp._target.val();
		var date = new Date(val.split('.')[2], val.split('.')[1], val.split('.')[0] * 1 + 1);
		
		var day = ((date.getDate()<10) ? "0" + date.getDate() : date.getDate());
		var month = ((date.getMonth()<10) ? "0" + date.getMonth() : date.getMonth());
		month = (month=="00")?"12":month;
		dp._target.val(   day + "." + month + "." +   date.getFullYear());
		
	});
	
	dp._downTrigger.mousedown(function(){
		var val = dp._target.val();
		var date = new Date(val.split('.')[2], val.split('.')[1], val.split('.')[0] * 1 - 1);
		
		var day = ((date.getDate()<10) ? "0" + date.getDate() : date.getDate());
		var month = ((date.getMonth()<10) ? "0" + date.getMonth() : date.getMonth());
		month = (month=="00")?"12":month;
		dp._target.val(   day + "." + month + "." +   date.getFullYear());
		
	});
	
}

DatePicker.prototype = {
	"buildPicker" : function(html)
	{	
		
		this._picker = $(html).hide();
		this._target.after(this._picker);
		this._picker.css({	"left" : this._trigger.position().left + this._trigger.outerWidth() - this._picker.outerWidth()+"px",
							"top"  : this._trigger.position().top + this._trigger.outerHeight() + 2 + "px"});
		this._picker.fadeIn('fast');
		
		this.initPicker();
	},
	"getPickerForDate" : function(selectedDate){
		var day = selectedDate.getDate();
		var month = selectedDate.getMonth()+1;
		var year = selectedDate.getFullYear();
		
		var today = new Date();
		
		var days = this._daysPerMonth["days"][month-1];
		//check if is gap year
		if(month == 2 && ((year%4==0 || year%400==0) && year%100!=0))
		{
			days = 29;
		}
		
		var days_html = "";
		var t = 1;
		var currentDate = null;
		var weekday = -1;
		for(var d = 1; d <= days; d++)
		{
			currentDate = new Date(year, month-1, d);
			weekday = currentDate.getDay();
			
			while(d==1 && t != weekday)
			{
				days_html += '<b class="spacer">&nbsp;</b>';
				t=(t+1==7)?0:t+1;
				
			}
			var additionalClasses="";
			if(d == day){
				additionalClasses+=' current';
			}
			if(currentDate.getDay() == 0){
				additionalClasses+=' sunday';
			}
			if(currentDate == today){
				additionalClasses+=' today';
			}
			days_html += '<a class="day'  + additionalClasses + '">'+d+'</a>';
		}
		
		var html = '<div class="datePicker">' +
						'<a class="prev">&lt;</a>' + 
						'<div class="head">' + 
							'<span class="month" short="' + month + '">' + this._daysPerMonth["names"][month-1] + '</span> ' + 
							'<span class="year">' + year + '</span>' + 
						'</div>'+
						'<a class="next">&gt;</a>' +
						'<div class="days">' + 
							'<b class="weekday">Mo</b>' + 
							'<b class="weekday">Di</b>' + 
							'<b class="weekday">Mi</b>' +
							'<b class="weekday">Do</b>' +
							'<b class="weekday">Fr</b>' + 
							'<b class="weekday">Sa</b>' + 
							'<b class="weekday">So</b>' + days_html + '</div>' +
					'</div>';
					
					
		return html;
	},
	"initPicker" : function(){
		var self = this;
		self._picker.find('a.day').click(function(){
			var day = $(this).text();
			var month = self._picker.find('span.month').attr('short');
			var year = self._picker.find('span.year').text();
			
			self._target.val( self.buildDate(day, month, year) );
			
			self.destroy();
		})
		
		self._picker.find('a.next').click(function(){
			var day = self._picker.find('a.day.current').text();
			var month = (self._picker.find('span.month').attr('short')*1)+1;
			var year = (self._picker.find('span.year').text())*1;
			self.destroy();
			if(month>12)
			{
				month=1;
				year+=1
			}
			else if(month<1)
			{
				month=12;
				year-=1;
			}
			
			var date = new Date(year, month-1, day);
			
			var html = self.getPickerForDate(date);
			self.buildPicker(html);
			
		})
		
		self._picker.find('a.prev').click(function(){
			var day = self._picker.find('a.day.current').text();
			var month = (self._picker.find('span.month').attr('short')*1)-1;
			var year = (self._picker.find('span.year').text())*1;
			self.destroy();
			if(month>12)
			{
				month=1;
				year+=1
			}
			else if(month<1)
			{
				month=12;
				year-=1;
			}
			
			var date = new Date(year, month-1, day);
			
			var html = self.getPickerForDate(date);
			self.buildPicker(html);
			
		})
	},
	"buildDate" : function(day, month, year){
		day = (day < 10)?"0"+day:day;
		month = (month<10)?"0"+month:month;
		var date = day + "." + month + "." + year;
		
		return date;
	},
	"destroy" : function(){
		this._picker.fadeOut("fast");
		this._picker = null;
	}

}

function NumericUpDown($triggers, $target)
{
	nud = this;
	nud._target = $target;
	nud._triggers = $triggers;
	
	nud._mousePressed = false;
	
	nud._triggers.up.mousedown(function(){
		nud._mousePressed = true;
		nud.up();
	})
	
	nud._triggers.down.mousedown(function(){
		nud._mousePressed = true;
		nud.down();
	})
	
	nud._triggers.up.mouseup(function(){
		nud._mousePressed = false;		
	})
	
	nud._triggers.down.mouseup(function(){
		nud._mousePressed = false;
	})
}
NumericUpDown.prototype = {
	"up" : function()
	{
		if(nud._mousePressed == true)
		{
			nud._target.val(nud._target.val()*1+1);
			window.setTimeout(nud.up, "250");
		}
	},
	"down" : function()
	{
		if(nud._mousePressed == true)
		{
			nud._target.val(nud._target.val()*1-1);
			window.setTimeout(nud.down, "250");
		}
	}
}
