﻿// $Id: forms.js,v 1.5 2007/11/12 15:47:21 kralja Exp $
// requires: yui.connect, yui.dom, dates.js (for complex date validation)
// optional: yui.anim, anim.js

Forms = {
	tests: {
		required: function(v) {return (v!=null && v.length>0)},
		'valid-number': function(v) {return (!isNaN(v))},
		'valid-email': function(v) {return (/^[\w-]+(\.[\w-]+)*(\+[\w-]+(\.[\w-]+))*@[\w-]+(\.\w+)+$/.test(v))},
		'valid-date': function(v) {return (/\d{4}-\d{2}-\d{2}$/.test(v))},
		'valid-time': function(v) {return (/\d{2}:\d{2}[\.:]\d{2}$/.test(v))}

	},
	
	messages: {
		// tests messages
		required: 'This field is required!',
		'valid-email': 'Please input valid e-mail!',
		'valid-number': 'Please input valid number!',
		'valid-date': 'Please input valid date (YYYY-MM-DD)!',
		'valid-time': 'Please input valid time (HH:MM:SS or MM:SS.ss)!',
		
		// misc messages
		'invalidDate': 'Date is not valid!',
		'submitFailed': 'Submit Failed! Please try again, or contact system administrator.'
	},

	form: null,
	fields: null,
	isValid: null,


	// generic form validation
	validate: function(obj, submitFn) {
		var errs;
		this.form = null; this.fields = null; this.isValid = true;


		// single element
		if (this.isFormElement(obj)) {
			this.fields = obj;
			errs = $D.getElementsByClassName('error','span', this.fields.parentNode)[0];

			if (errs!=null)
				this.fields.parentNode.removeChild(errs);
		}
		// form
		else if (obj.nodeName.toLowerCase()=='form') {
			this.form = obj;
			this.fields = $D.getElementsBy(this.isFormElement, '', obj);
			errs = $D.getElementsByClassName('error','span', obj);

			for (a in errs)
				errs[a].parentNode.removeChild(errs[a]);
			for (a in this.fields) {
				if ($D.hasClass(this.fields[a], 'invalid'))
					$D.removeClass(this.fields[a], 'invalid');
			}
		}
		// nothing important
		else
			return false;

		// check fields for tests and invalidate if failed
		for (i in this.fields) {
			for (test in this.tests) {
				if (($D.hasClass(this.fields[i], 'required') || this.fields[i].value!='') && $D.hasClass(this.fields[i], test) && !this.tests[test](this.fields[i].value))
					this.invalidate(this.fields[i], test);
			}
		}

		// submit if valid
		if (this.isValid && this.form!=null) {
			if (submitFn) {
				submitFn();
			}
			else
				this.form.submit();
		}
	},

	// invalidate field(s)
	invalidate: function (field, test) {
		this.isValid = false;

		if (!$D.hasClass(field, 'invalid'))
			$D.addClass(field, 'invalid');

		var parent = field.parentNode;
		var err = $D.getElementsByClassName('error','span', parent)[0];

		// first error on field - create new error container
		if (err==null) {
			err = document.createElement('span');
			err.className = 'error';
			err.innerHTML = this.messages[test];

			$D.generateId(err, 'err');
			$D.setStyle (err , 'opacity', 0)

			parent.appendChild(err);

			if (window.Anim)
				Anim.fadeIn(document.getElementById(err.id))
			else
				$D.setStyle (document.getElementById(err.id) , 'opacity', 1);
		}
		// not the first error - just append the message
		else if (err.innerHTML.indexOf(this.messages[test])<0)
			err.innerHTML += "<br/>"+this.messages[test];
	},

	isFormElement: function(el) {
		if ((el.nodeName.toLowerCase() == 'input' && el.type.toLowerCase()!='button' && el.type.toLowerCase()!='submit')
		 	|| el.nodeName.toLowerCase() == 'select' || el.nodeName.toLowerCase() == 'textarea')
	 		return true;
	 	else
	 		return false;
	},
	
	// async form submit
	submit: function(form) {
		if (form!=null)
			this.form = form;
			
		var uri = this.form.action.replace(/(http:\/\/[\w\.]{2,}[:\d]{0,})/g, "$1/async");

		// callback when request done
		var requestCallback = {
			success: Forms.submitSuccess,
			failure: Forms.submitFailure,
			scope: Forms
		};

		$C.setForm(this.form);
		var submitForm = $C.asyncRequest('POST', uri, requestCallback);
		if (window.Anim) {
			Anim.loadInProgress = true;
			Anim.slideContentOnLoad(submitForm, this.form.id, {x:500, speed:1});
		}
	},

	submitSuccess: function(o) {
		if (window.Anim)
			Anim.loadedContent = o.responseText;
		else {
			this.form.innerHTML = o.responseText;
		}
	},

	submitFailure: function(o) {
		if (window.Anim)
			Anim.loadedContent = this.messages.submitFailed;
		else
			this.form.innerHTML = this.messages.submitFailed;
	},
	
	
	// custom validation of date from/to controls
	validateDateOnSearchForm: function(form, controlFrom, controlTo, submitFn) {
		var fields, d1, m1, y1, d2, m2, y2;
		var errMsg = '';
		this.isValid = true;
		this.form = form;
		
		if (this.form==null)
			return false;

		// reset errors
		var errs = $D.getElementsByClassName('error','span', this.form);
		for (a in errs)
			errs[a].parentNode.removeChild(errs[a]);

		// get dates
		var containerFrom = document.getElementById(controlFrom);
		var dateFrom = this.getDateFromControl(this.getControl(controlFrom));
		d1 = dateFrom.day;
		m1 = dateFrom.month;
		y1 = dateFrom.year;
		
		var containerTo = document.getElementById(controlTo);
		var dateTo = this.getDateFromControl(this.getControl(controlTo));
		d2 = dateTo.day;
		m2 = dateTo.month;
		y2 = dateTo.year;
		
		// check starting date
		if (Dates.YMDdate2int(y1,m1,d1)<(Dates.date2int(Dates.currentDate())+1) 
		|| (d1==31 && (m1==2 || m1==4 || m1==6 || m1==9 || m1==11)) || (d1==30 && m1==2) 
		|| (m1==2 && d1==29 && !Dates.isYearOver(y1))) {
			this.invalidateDate(containerFrom);
			this.isValid = false;
		}

		// check ending date
		if (Dates.YMDdate2int(y1,m1,d1)>=Dates.YMDdate2int(y2,m2,d2)
		|| (d2==31 && (m2==2 || m2==4 || m2==6 || m2==9 || m2==11)) 
		|| (d2==30 && m2==2) || (m2==2 && d2==29 && !Dates.isYearOver(y2))) {
			this.invalidateDate(containerTo);
			this.isValid = false;
		}

		// submit if valid
		if (this.isValid && this.form!=null) {
			if (submitFn)
				submitFn();
			else
				this.form.submit();
		}
	},
	
	invalidateDate: function(container) {
		var err = $D.getElementsByClassName('error','span', container)[0];

		// first error on field - create new error container
		if (err==null) {
			err = document.createElement('span');
			err.className = 'error';
			err.innerHTML = this.messages.invalidDate;

			$D.generateId(err, 'err');
			$D.setStyle (err , 'opacity', 0)

			container.appendChild(err);

			if (window.Anim)
				Anim.fadeIn(document.getElementById(err.id))
			else
				$D.setStyle (document.getElementById(err.id) , 'opacity', 1);
		}
		// not the first error - just append the message
		else if (err.innerHTML.indexOf(this.messages.invalidDate)<0)
			err.innerHTML += "<br/>"+this.messages.invalidDate;
	},
	
	setCurrentDates: function(form, controlFrom, controlTo, start, offset) {
		if (!form)
			return false;
		if (!offset || offset==null)
			var offset = 7;

		if (!start || start==null)
			start = Dates.int2date(Dates.date2int(Dates.currentDate())+1);
		var end = Dates.int2date(Dates.date2int(start)+offset);

		this.setDateInControl(this.getControl(controlFrom), start);
		this.setDateInControl(this.getControl(controlTo), end);
	},
	
	// support methods, copied from calendar.js
	getControl: function(id) {
		var container = document.getElementById(id);
		var fields = $D.getElementsByAttribute({control: id}, '*', container);
		
		return fields;
	}, 
	
	getDateFromControl: function(obj) {
		var date;
		
		if (obj.length == 3) {
			var da = Array();
			for (a in obj)
				da[obj[a].getAttribute('field')] = obj[a].value;
			date = new Dates.date(da['year'], da['month'], da['day']);	
		}
		
		return date;
	},
	
	setDateInControl: function(obj, date) {
		if (obj.length == 3) {
			var da = Array();
			for (a in obj)
				da[obj[a].getAttribute('field')] = obj[a];
			
			da['year'].value = date.year;
			da['month'].value = date.month;
			da['day'].value = date.day;
		}
	}
}