/**
 * modified by Jersy Zhang
 * 
 * License:
 *	MIT-style license.

 * Copyright:
 * 
 * Copyright (c) http://zy8643954.javaeye.com/
 *
 * Version 1.0.0
 * 
 * 
 */

/*
Script: Clientcide.js
	The Clientcide namespace.

License: 
	http://www.clientcide.com/wiki/cnet-libraries#license
*/

var ValidationUtils = {
		format : function(str,args) {
			args = args || [];
			ValidationUtils.assert(args.constructor == Array,"ValidationUtils.format() arguement 'args' must is Array");
			var result = str
			for (var i = 0; i < args.length; i++){
				result = result.replace(/%s/, args[i]);	
			}
			return result;
		},
		assert : function(condition,message) {
			var errorMessage = message || ("assert failed error,condition="+condition);
			if (!condition) {
				alert(errorMessage);
				throw new Error(errorMessage);
			}else {
				return condition;
			}
		},
		isDate : function(v,dateFormat) {
			var MONTH = "MM";
		   	var DAY = "dd";
		   	var YEAR = "yyyy";
			var regex = '^'+dateFormat.replace(YEAR,'\\d{4}').replace(MONTH,'\\d{2}').replace(DAY,'\\d{2}')+'$';
			if(!new RegExp(regex).test(v)) return false;
	
			var year = v.substr(dateFormat.indexOf(YEAR),4);
			var month = v.substr(dateFormat.indexOf(MONTH),2);
			var day = v.substr(dateFormat.indexOf(DAY),2);
			
			var d = new Date(ValidationUtils.format('%s/%s/%s',[year,month,day]));
			return ( parseInt(month, 10) == (1+d.getMonth()) ) && 
						(parseInt(day, 10) == d.getDate()) && 
						(parseInt(year, 10) == d.getFullYear() );		
		},
		getRangeValue : function(postfix) {
			var results = [];
			var args =  postfix.split('-');
			for(var i = 0; i < args.length; i++) {
				if(args[i] == '') {
					if(i+1 < args.length) args[i+1] = '-'+args[i+1];
				}else{
					results.push(args[i]);
				}
			}
			return results;
		}
}

/*
Script: Date.js
	Extends the Date native object to include methods useful in managing dates.

License:
	http://www.clientcide.com/wiki/cnet-libraries#license
	modified by Jersy Zhang
*/

new Native({name: 'Date', initialize: Date, protect: true});
['now','parse','UTC'].each(function(method){
	Native.genericize(Date, method, true);
});
Date.$Methods = new Hash();
["Date", "Day", "FullYear", "Hours", "Milliseconds", "Minutes", "Month", "Seconds", "Time", "TimezoneOffset", 
	"Week", "Timezone", "GMTOffset", "DayOfYear", "LastMonth", "UTCDate", "UTCDay", "UTCFullYear",
	"AMPM", "UTCHours", "UTCMilliseconds", "UTCMinutes", "UTCMonth", "UTCSeconds"].each(function(method) {
	Date.$Methods.set(method.toLowerCase(), method);
});
$each({
	ms: "Milliseconds",
	year: "FullYear",
	min: "Minutes",
	mo: "Month",
	sec: "Seconds",
	hr: "Hours"
}, function(value, key){
	Date.$Methods.set(key, value);
});


Date.implement({
	set: function(key, value) {		
		key = key.toLowerCase();
		var m = Date.$Methods;
		if (m.has(key)) this['set'+m.get(key)](value);
		
		return this;
	},
	get: function(key) {
		key = key.toLowerCase();
		var m = Date.$Methods;
		if (m.has(key)) return this['get'+m.get(key)]();
		return null;
	},
	clone: function() {
		return new Date(this.get('time'));
	},
	increment: function(interval, times) {
		return this.multiply(interval, times);
	},
	decrement: function(interval, times) {
		return this.multiply(interval, times, false);
	},
	multiply: function(interval, times, increment){
		interval = interval || 'day';
		times = $pick(times, 1);
		increment = $pick(increment, true);
		var multiplier = increment?1:-1;
		var month = this.format("%m").toInt()-1;
		var year = this.format("%Y").toInt();
		var time = this.get('time');
		var offset = 0;
		switch (interval) {
				case 'year':
					times.times(function(val) {
						if (Date.isLeapYear(year+val) && month > 1 && multiplier > 0) val++;
						if (Date.isLeapYear(year+val) && month <= 1 && multiplier < 0) val--;
						offset += Date.$units.year(year+val);
					});
					break;
				case 'month':
					times.times(function(val){
						if (multiplier < 0) val++;
						var mo = month+(val*multiplier);
						var year = year;
						if (mo < 0) {
							year--;
							mo = 12+mo;
						}
						if (mo > 11 || mo < 0) {
							year += (mo/12).toInt()*multiplier;
							mo = mo%12;
						}
						offset += Date.$units.month(mo, year);
					});
					break;
				case 'day':
					return this.set('date', this.get('date')+(multiplier*times));
				default:
					offset = Date.$units[interval]()*times;
					break;
		}
		this.set('time', time+(offset*multiplier));
		return this;
	},
	isLeapYear: function() {
		return Date.isLeapYear(this.get('year'));
	},
	clearTime: function() {
		['hr', 'min', 'sec', 'ms'].each(function(t){
			this.set(t, 0);
		}, this);
		return this;
	},
	diff: function(d, resolution) {
		resolution = resolution || 'day';
		if($type(d) == 'string') d = Date.parse(d);
		switch (resolution) {
			case 'year':
				return d.format("%Y").toInt() - this.format("%Y").toInt();
				break;
			case 'month':
				var months = (d.format("%Y").toInt() - this.format("%Y").toInt())*12;
				return months + d.format("%m").toInt() - this.format("%m").toInt();
				break;
			default:
				var diff = d.get('time') - this.get('time');
				if (diff < 0 && Date.$units[resolution]() > (-1*(diff))) return 0;
				else if (diff >= 0 && diff < Date.$units[resolution]()) return 0;
				return ((d.get('time') - this.get('time')) / Date.$units[resolution]()).round();
		}
	},
	getWeek: function() {
		var day = (new Date(this.get('year'), 0, 1)).get('date');
		return Math.round((this.get('dayofyear') + (day > 3 ? day - 4 : day + 3)) / 7);
	},
	getTimezone: function() {
		return this.toString()
			.replace(/^.*? ([A-Z]{3}).[0-9]{4}.*$/, '$1')
			.replace(/^.*?\(([A-Z])[a-z]+ ([A-Z])[a-z]+ ([A-Z])[a-z]+\)$/, '$1$2$3');
	},
	getGMTOffset: function() {
		var off = this.get('timezoneOffset');
		return ((off > 0) ? '-' : '+')
			+ Math.floor(Math.abs(off) / 60).zeroise(2)
			+ (off % 60).zeroise(2);
	},
	parse: function(str) {
		this.set('time', Date.parse(str));
		return this;
	},
	format: function(f) {
		f = f || "%x %X";
		
		if (!this.valueOf()) return 'invalid date';
		//replace short-hand with actual format
		if (Date.$formats[f.toLowerCase()]) f = Date.$formats[f.toLowerCase()];
		
		var d = this;
		return f.replace(/\%([aAbBcdHIjmMpSUWwxXyYTZ])/g,
			function($1, $2) {
				switch ($2) {
					case 'a': return Date.$days[d.get('day')].substr(0, 3);
					case 'A': return Date.$days[d.get('day')];
					case 'b': return Date.$months[d.get('month')].substr(0, 3);
					case 'B': return Date.$months[d.get('month')];
					case 'c': return d.toString();
					case 'd': return d.get('date').zeroise(2);
					case 'H': return d.get('hr').zeroise(2);
					case 'I': return ((d.get('hr') % 12) || 12);
					case 'j': return d.get('dayofyear').zeroise(3);
					case 'm': return (d.get('mo') + 1).zeroise(2);
					case 'M': return d.get('min').zeroise(2);
					case 'p': return d.get('hr') < 12 ? 'AM' : 'PM';
					case 'S': return d.get('seconds').zeroise(2);
					case 'U': return d.get('week').zeroise(2);
					case 'W': throw new Error('%W is not supported yet');
					case 'w': return d.get('day');
					case 'x': 
						var c = Date.$cultures[Date.$culture];
						//return d.format("%{0}{3}%{1}{3}%{2}".substitute(c.map(function(s){return s.substr(0,1)}))); //grr!												
						return d.format('%' + c[0].substr(0,1) + c[3] + '%' + c[1].substr(0,1) + c[3] + '%' + c[2].substr(0,1));
					case 'X': return d.format('%I:%M%p');
					case 'y': return d.get('year').toString().substr(2);
					case 'Y': return d.get('year');
					case 'T': return d.get('GMTOffset');
					case 'Z': return d.get('Timezone');
					case '%': return '%';
				}
				return $2;
			}
		);
	},
	setAMPM: function(ampm){
		ampm = ampm.toUpperCase();
		if (this.format("%H").toInt() > 11 && ampm == "AM") 
			return this.decrement('hour', 12);
		else if (this.format("%H").toInt() < 12 && ampm == "PM")
			return this.increment('hour', 12);
		return this;
	}
});

Date.prototype.compare = Date.prototype.diff;
Date.prototype.strftime = Date.prototype.format;

Date.$nativeParse = Date.parse;

$extend(Date, {
	$months: ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'],
	$days: ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'],
	$daysInMonth: function(monthIndex, year) {
		if (Date.isLeapYear(year.toInt()) && monthIndex === 1) return 29;
		return [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31][monthIndex];
	},
	$epoch: -1,
	$era: -2,
	$units: {
		ms: function(){return 1},
		second: function(){return 1000},
		minute: function(){return 60000},
		hour: function(){return 3600000},
		day: function(){return 86400000},
		week: function(){return 608400000},
		month: function(monthIndex, year) {
			var d = new Date();
			return Date.$daysInMonth($pick(monthIndex,d.format("%m").toInt()), $pick(year,d.format("%Y").toInt())) * 86400000;
		},
		year: function(year){
			year = year || new Date().format("%Y").toInt();
			return Date.isLeapYear(year.toInt())?31622400000:31536000000;
		}
	},
	$formats: {
		db: '%Y-%m-%d %H:%M:%S',
		compact: '%Y%m%dT%H%M%S',
		iso8601: '%Y-%m-%dT%H:%M:%S%T',
		rfc822: '%a, %d %b %Y %H:%M:%S %Z',
		'short': '%d %b %H:%M',
		'long': '%B %d, %Y %H:%M'
	},
	
	isLeapYear: function(yr) {
		return new Date(yr,1,29).getDate()==29;
	},

	parseUTC: function(value){
		var localDate = new Date(value);
		var utcSeconds = Date.UTC(localDate.get('year'), localDate.get('mo'),
		localDate.get('date'), localDate.get('hr'), localDate.get('min'), localDate.get('sec'));
		return new Date(utcSeconds);
	},
	
	parse: function(from) {
		var type = $type(from);
		if (type == 'number') return new Date(from);
		if (type != 'string') return from;
		if (!from.length) return null;
		for (var i = 0, j = Date.$parsePatterns.length; i < j; i++) {
			var r = Date.$parsePatterns[i].re.exec(from);
			if (r) {
				try {
					return Date.$parsePatterns[i].handler(r);
				} catch(e) {
					dbug.log('date parse error: ', e);
					return null;
				}
			}
		}
		return new Date(Date.$nativeParse(from));
	},

	parseMonth: function(month, num) {
		var ret = -1;
		switch ($type(month)) {
			case 'object':
				ret = Date.$months[month.get('mo')];
				break;
			case 'number':
				ret = Date.$months[month - 1] || false;
				if (!ret) throw new Error('Invalid month index value must be between 1 and 12:' + index);
				break;
			case 'string':
				var match = Date.$months.filter(function(name) {
					return this.test(name);
				}, new RegExp('^' + month, 'i'));
				if (!match.length) throw new Error('Invalid month string');
				if (match.length > 1) throw new Error('Ambiguous month');
				ret = match[0];
		}
		return (num) ? Date.$months.indexOf(ret) : ret;
	},

	parseDay: function(day, num) {
		var ret = -1;
		switch ($type(day)) {
			case 'number':
				ret = Date.$days[day - 1] || false;
				if (!ret) throw new Error('Invalid day index value must be between 1 and 7');
				break;
			case 'string':
				var match = Date.$days.filter(function(name) {
					return this.test(name);
				}, new RegExp('^' + day, 'i'));
				if (!match.length) throw new Error('Invalid day string');
				if (match.length > 1) throw new Error('Ambiguous day');
				ret = match[0];
		}
		return (num) ? Date.$days.indexOf(ret) : ret;
	},
	
	fixY2K: function(d){
		if (!isNaN(d)) {
			var newDate = new Date(d);
			if (newDate.get('year') < 2000 && d.toString().indexOf(newDate.get('year')) < 0) {
				newDate.increment('year', 100);
			}
			return newDate;
		} else return d;
	},

	$cultures: {
		'zh-cn': ['Year', 'month', 'date', '-'],
		'GB': ['date', 'month', 'year', '/']
	},

	$culture: 'zh-cn',
	
	$language: 'zhCn',
	
	$cIndex: function(unit){
		return Date.$cultures[Date.$culture].indexOf(unit)+1;
	},

	$parsePatterns: [
		{			
			//"08.12.31", "08-12-31", "08/12/31", "2008.12.31", "2008-12-31", "2008/12/31"
			re: /^(\d{2,4})[\.\-\/](\d{1,2})[\.\-\/](\d{1,2})$/,
			handler: function(bits){
				var d = new Date();
				var culture = Date.$cultures[Date.$culture];
							
				d.set('year', bits[Date.$cIndex('Year')]);
				d.set('month', bits[Date.$cIndex('month')]-1);
				d.set('date', bits[Date.$cIndex('date')]);
				
				return Date.fixY2K(d);
			}
		},
		//"12.31.08", "12-31-08", "12/31/08", "12.31.2008", "12-31-2008", "12/31/2008"
		//above plus "10:45pm" ex: 12.31.08 10:45pm
		{
		  	//re: /^(\d{1,2})[\.\-\/](\d{1,2})[\.\-\/](\d{2,4})\s(\d{1,2}):(\d{1,2})(\w{2})$/,
			re: /^(\d{2,4})[\.\-\/](\d{1,2})[\.\-\/](\d{1,2})\s(\d{1,2}):(\d{1,2})(\w{2})$/,
			handler: function(bits){
				var d = new Date();
				d.set('year', bits[Date.$cIndex('Year')]);
				d.set('month', bits[Date.$cIndex('month')] - 1);
				d.set('date', bits[Date.$cIndex('date')]);
				d.set('hr', bits[4]);
				d.set('min', bits[5]);
				d.set('ampm', bits[6]);
				return Date.fixY2K(d);
			}
		},
		{
			//"12.31.08 11:59:59", "12-31-08 11:59:59", "12/31/08 11:59:59", "12.31.2008 11:59:59", "12-31-2008 11:59:59", "12/31/2008 11:59:59"
		   //re: /^(\d{1,2})[\.\-\/](\d{1,2})[\.\-\/](\d{2,4})\s(\d{1,2}):(\d{1,2}):(\d{1,2})/,
			re: /^(\d{2,4})[\.\-\/](\d{1,2})[\.\-\/](\d{1,2})\s(\d{1,2}):(\d{1,2}):(\d{1,2})/,
			handler: function(bits) {
				var d = new Date();
				var culture = Date.$cultures[Date.$culture];
				d.set('year', bits[Date.$cIndex('Year')]);
				d.set('month', bits[Date.$cIndex('month')] - 1);
				d.set('date', bits[Date.$cIndex('date')]);
				d.set('hours', bits[4]);
				d.set('minutes', bits[5]);
				d.set('seconds', bits[6]);
				return Date.fixY2K(d);
			}
		}
	]
});

Number.implement({
	zeroise: function(length) {
		return String(this).zeroise(length);
	}
});

String.implement({
	repeat: function(times) {
		var ret = [];
		for (var i = 0; i < times; i++) ret.push(this);
		return ret.join('');
	},
	zeroise: function(length) {
		return '0'.repeat(length - this.length) + this;
	}

});


/*
Script: Element.Forms.js
	Extends the Element native object to include methods useful in managing inputs.

License:
	http://www.clientcide.com/wiki/cnet-libraries#license
*/
Element.implement({
	tidy: function(){
		this.set('value', this.get('value').tidy());
	},
	getTextInRange: function(start, end) {
		return this.get('value').substring(start, end);
	},
	getSelectedText: function() {
		if(Browser.Engine.trident) return document.selection.createRange().text;
		return this.get('value').substring(this.getSelectionStart(), this.getSelectionEnd());
	},
	getIERanges: function(){
		this.focus();
		var range = document.selection.createRange();
		var re = this.createTextRange();
		var dupe = re.duplicate();
		re.moveToBookmark(range.getBookmark());
		dupe.setEndPoint('EndToStart', re);
		return { start: dupe.text.length, end: dupe.text.length + range.text.length, length: range.text.length, text: range.text };
	},
	getSelectionStart: function() {
		if(Browser.Engine.trident) return this.getIERanges().start;
		return this.selectionStart;
	},
	getSelectionEnd: function() {
		if(Browser.Engine.trident) return this.getIERanges().end;
		return this.selectionEnd;
	},
	getSelectedRange: function() {
		return {
			start: this.getSelectionStart(),
			end: this.getSelectionEnd()
		}
	},
	setCaretPosition: function(pos) {
		if(pos == 'end') pos = this.get('value').length;
		this.selectRange(pos, pos);
		return this;
	},
	getCaretPosition: function() {
		return this.getSelectedRange().start;
	},
	selectRange: function(start, end) {
		this.focus();
		if(Browser.Engine.trident) {
			var range = this.createTextRange();
			range.collapse(true);
			range.moveStart('character', start);
			range.moveEnd('character', end - start);
			range.select();
			return this;
		}
		this.setSelectionRange(start, end);
		return this;
	},
	insertAtCursor: function(value, select) {
		var start = this.getSelectionStart();
		var end = this.getSelectionEnd();
		this.set('value', this.get('value').substring(0, start) + value + this.get('value').substring(end, this.get('value').length));
 		if($pick(select, true)) this.selectRange(start, start + value.length);
		else this.setCaretPosition(start + value.length);
		return this;
	},
	insertAroundCursor: function(options, select) {
		options = $extend({
			before: '',
			defaultMiddle: 'SOMETHING HERE',
			after: ''
		}, options);
		value = this.getSelectedText() || options.defaultMiddle;
		var start = this.getSelectionStart();
		var end = this.getSelectionEnd();
		if(start == end) {
			var text = this.get('value');
			this.set('value', text.substring(0, start) + options.before + value + options.after + text.substring(end, text.length));
			this.selectRange(start + options.before.length, end + options.before.length + value.length);
			text = null;
		} else {
			text = this.get('value').substring(start, end);
			this.set('value', this.get('value').substring(0, start) + options.before + text + options.after + this.get('value').substring(end, this.get('value').length));
			var selStart = start + options.before.length;
			if($pick(select, true)) this.selectRange(selStart, selStart + text.length);
			else this.setCaretPosition(selStart + text.length);
		}	
		return this;
	}
});


Element.Properties.inputValue = {
 
    get: function(){
			 switch(this.get('tag')) {
			 	case 'select':
					vals = this.getSelected().map(function(op){ 
						var v = $pick(op.get('value'),op.get('text')); 
						return (v=="")?op.get('text'):v;
					});
					return this.get('multiple')?vals:vals[0];
				case 'input':
					switch(this.get('type')) {
						case 'checkbox':
							return this.get('checked')?this.get('value'):false;
						case 'radio':
							var checked;
							if (this.get('checked')) return this.get('value');
							$(this.getParent('form')||document.body).getElements('input').each(function(input){
								if (input.get('name') == this.get('name') && input.get('checked')) checked = input.get('value');
							}, this);
							return checked||null;
					}
			 	case 'input': case 'textarea':
					return this.get('value');
				default:
					return this.get('inputValue');
			 }
    },
 
    set: function(value){
			switch(this.get('tag')){
				case 'select':
					this.getElements('option').each(function(op){
						var v = $pick(op.get('value'), op.get('text'));
						if (v=="") v = op.get('text');
						op.set('selected', $splat(value).contains(v));
					});
					break;
				case 'input':
					if (['radio','checkbox'].contains(this.get('type'))) {
						this.set('checked', $type(value)=="boolean"?value:$splat(value).contains(this.get('value')));
						break;
					}
				case 'textarea': case 'input':
					this.set('value', value);
					break;
				default:
					this.set('inputValue', value);
			}
			return this;
    },
		
	erase: function() {
		switch(this.get('tag')) {
			case 'select':
				this.getElements('option').each(function(op) {
					op.erase('selected');
				});
				break;
			case 'input':
				if (['radio','checkbox'].contains(this.get('type'))) {
					this.set('checked', false);
					break;
				}
			case 'input': case 'textarea':
				this.set('value', '');
				break;
			default:
				this.set('inputValue', '');
		}
		return this;
	}

};


/*
Script: FormValidator.js
	A css-class based form validation system.

License:
	http://www.clientcide.com/wiki/cnet-libraries#license
*/
var InputValidator = new Class({
	Implements: [Options],
	options: {
		errorMsg: 'Validation failed.',
		test: function(field){return true;}
	},
	initialize: function(className, options){
		this.setOptions(options);
		this.className = className;
	},
	test: function(field){
		if($(field)) { 
			if(!($(field).get('class').contains('equalsPassword')))
				$(field).set('value',$(field).get('value').trim());
			return this.options.test($(field), this.getProps(field));
		}
		else return false;
	},
	getError: function(field){
		var err = this.options.errorMsg;
		if($type(err) == "function") err = err($(field), this.getProps(field));
		return err;
	},
	getProps: function(field){
		if (!$(field)) return {};
		return field.get('validatorProps');
	}
});

Element.Properties.validatorProps = {

	set: function(props){
		return this.eliminate('validatorProps').store('validatorProps', props);
	},

	get: function(props){		
		if (props) this.set(props);
		if (this.retrieve('validatorProps')) return this.retrieve('validatorProps');
		if(this.getProperty('validatorProps')){
			try {
				this.store('validatorProps', JSON.decode(this.getProperty('validatorProps')));
			}catch(e){
				return {}
			}
		} else {
			var vals = this.get('class').split(' ').filter(function(cls) {
				//return cls.match(/[a-z].*\[\'.*\'\]/ig);
				return cls.test(':');
			});
			if (!vals.length) {
				this.store('validatorProps', {});
			} else {
				props = {};
				vals.each(function(cls){
					var split = cls.indexOf(':');
					props[cls.substring(0, split)] = JSON.decode(cls.substring(split+1, cls.length))
				});
				this.store('validatorProps', props);
			}
		}
		return this.retrieve('validatorProps');
	}

};

var FormValidator = new Class({
	Implements:[Options, Events],
	options: {
		fieldSelectors:"input, select, textarea",
		ignoreHidden: true,
		useTitles:false,
		evaluateOnSubmit:true,
		evaluateOnReset:true,
		evaluateFieldsOnBlur: true,
		evaluateFieldsOnChange: true,
		serial: true,
		stopOnFailure: true,
		scrollToErrorsOnSubmit: true,
		queryNeedOne: false,
		warningPrefix: function(){
			return FormValidator.resources[FormValidator.language].warningPrefix || '';
		},
		errorPrefix: function(){
			return FormValidator.resources[FormValidator.language].errorPrefix || '';
		}
//	onFormValidate: function(isValid, form){},
//	onElementValidate: function(isValid, field){}
	},
	initialize: function(form, options){
		this.setOptions(options);
		this.form = $(form);
		this.form.store('validator', this);
		this.warningPrefix = $lambda(this.options.warningPrefix)();
		this.errorPrefix = $lambda(this.options.errorPrefix)();

		if(this.options.evaluateOnSubmit) this.form.addEvent('submit', this.onSubmit.bind(this));
		if(this.options.evaluateOnReset) this.form.addEvent('reset', this.reset.bind(this));
		
		if(this.options.evaluateFieldsOnBlur) this.watchFields();
	},
	toElement: function(){
		return this.form;
	},
	getFields: function(){
		return this.fields = this.form.getElements(this.options.fieldSelectors);
	},
	watchFields: function(){
		this.getFields().each(function(el){
				el.addEvent('blur', this.validateField.pass([el, false], this));
			if(this.options.evaluateFieldsOnChange)
				el.addEvent('change', this.validateField.pass([el, true], this));
		}, this);
	},
	onSubmit: function(event){
		if(!this.validate(event) && event) event.preventDefault();
		//else this.reset();
	},
	reset: function() {
		this.getFields().each(this.resetField, this);
		return this;
	}, 
	validate: function(event) {
		var result = this.getFields().map(function(field) { 
			return this.validateField(field, true);
		}, this).every(function(v){ return v;});
		this.fireEvent('onFormValidate', [result, this.form, event]);
		if (this.options.stopOnFailure && !result && event) event.preventDefault();
		if (this.options.scrollToErrorsOnSubmit && !result) {
			var par = this.form.getParent();
			var isScrolled = function(p){
				return p.getScrollSize().y != p.getSize().y
			};
			var scrolls;
			while (par != document.body && !isScrolled(par)) {
				par = par.getParent();
			};
			var fx = par.retrieve('fvScroller');
			if (!fx && window.Fx) {
				fx = new Fx.Scroll(par, {
					transition: 'quad:out',
					offset: {
						y: -20
					}
				});
				par.store('fvScroller', fx);
			}
			var failed = this.form.getElement('.validation-failed');
			if (failed) {
				if (fx) fx.toElement(failed);
				else par.scrollTo(par.getScroll().x, failed.getPosition(par).y - 20);
			}
		}
		return result;
	},
	validateField: function(field, force){
		if (this.paused) return true;
		field = $(field);
		var passed = !field.hasClass('validation-failed');
		var failed, warned;
		if (this.options.serial && !force) {
			failed = this.form.getElement('.validation-failed');
			warned = this.form.getElement('.warning');
		}
		if(field && (!failed || force || field.hasClass('validation-failed') || (failed && !this.options.serial))){
			var validators = field.className.split(" ").some(function(cn){
				return this.getValidator(cn);
			}, this);
			var validatorsFailed = [];
			
			field.className.split(" ").each(function(className){
				if (!this.test(className,field)) 
					validatorsFailed.include(className);
			}, this);
			passed = validatorsFailed.length === 0;
			if (validators && !field.hasClass('warnOnly')){
				if(passed) {
					field.addClass('validation-passed').removeClass('validation-failed');
					this.fireEvent('onElementPass', field);
				} else {
					field.addClass('validation-failed').removeClass('validation-passed');
					this.fireEvent('onElementFail', [field, failed]);
				}
			}
			if(!warned) {
				var warnings = field.className.split(" ").some(function(cn){
					if(cn.test('^warn-') || field.hasClass('warnOnly')) 
						return this.getValidator(cn.replace(/^warn-/,""));
					else return null;
				}, this);
				field.removeClass('warning');
				var warnResult = field.className.split(" ").map(function(cn){
					if(cn.test('^warn-') || field.hasClass('warnOnly')) 
						return this.test(cn.replace(/^warn-/,""), field, true);
					else return null;
				}, this);
			}
		}
		return passed;
	},
	getPropName: function(className){
		return 'advice'+className;
	},
	test: function(className, field, warn){
		field = $(field);
		if(field.hasClass('ignoreValidation')) return true;
		warn = $pick(warn, false);
		if(field.hasClass('warnOnly')) warn = true;
		var isValid = true;
		var validator = this.getValidator(className);
		if(validator && this.isVisible(field)) {
			isValid = validator.test(field);
			if(!isValid && validator.getError(field)){
				if(warn) field.addClass('warning');
				var advice = this.makeAdvice(className, field, validator.getError(field), warn);
				this.insertAdvice(advice, field);
				this.showAdvice(className, field);
			} else this.hideAdvice(className, field);
			this.fireEvent('onElementValidate', [isValid, field, className]);
		}

		if(warn) return true;
		return isValid;
	},
	getAllAdviceMessages: function(field, force) {
		var advice = [];
		if (field.hasClass('ignoreValidation') && !force) return advice;
		var validators = field.className.split(" ").some(function(cn){
			var warner = cn.test('^warn-') || field.hasClass('warnOnly');
			if (warner) cn = cn.replace(/^warn-/,"");
			var validator = this.getValidator(cn);
			if (!validator) return;
			advice.push({
				message: validator.getError(field),
				warnOnly: warner,
				passed: validator.test(),
				validator: validator
			});
		}, this);
		return advice;
	},
	showAdvice: function(className, field){
		var advice = this.getAdvice(className, field);
		if(advice && !field.retrieve(this.getPropName(className))
			 && (advice.getStyle('display') == "none" 
			 || advice.getStyle('visiblity') == "hidden" 
			 || advice.getStyle('opacity')==0)){
			field.store(this.getPropName(className), true);
			if(advice.reveal) advice.reveal();	
			else advice.setStyle('display','inline');
		}
	},
	hideAdvice: function(className, field){
		var advice = this.getAdvice(className, field);
		if(advice && field.retrieve(this.getPropName(className))) {
			field.store(this.getPropName(className), false);
			//if Fx.Reveal.js is present, transition the advice out
			if(advice.dissolve) advice.dissolve();
			else advice.setStyle('display','none');
		}
	},
	isVisible : function(field) {
		if (!this.options.ignoreHidden) return true;
		while(field != document.body) {
			if($(field).getStyle('display') == "none") return false;
			field = field.getParent();
		}
		return true;
	},
	getAdvice: function(className, field) {
		return field.retrieve('advice-'+className);
	},
	makeAdvice: function(className, field, error, warn){
		var errorMsg = (warn)?this.warningPrefix:this.errorPrefix;
				errorMsg += (this.options.useTitles) ? field.title || error:error;
		var advice = this.getAdvice(className, field);
		if(!advice){
			var cssClass = (warn)?'warning-advice':'validation-advice';
			advice = new Element('div', {
				text: errorMsg,
				styles: { display: 'none' },
				id: 'advice-'+className+'-'+this.getFieldId(field)
			}).addClass(cssClass);
		} else{
			advice.set('html', errorMsg);
		}
		field.store('advice-'+className, advice);
		return advice;
	},
	insertAdvice: function(advice, field){
		//Check for error position prop
		var props = field.get('validatorProps');
		//Build advice
		if (!props.msgPos || !$(props.msgPos)) {
			switch (field.type.toLowerCase()) {
				case 'radio':case 'checkbox':
					var p = field.getParent().adopt(advice);
					break;
				default:
					advice.inject(field.getParent().getChildren().getLast(), 'after');
			};
		} else {
			$(props.msgPos).grab(advice);
		}
	},
	getFieldId : function(field) {
		return field.id ? field.id : field.id = "input_"+field.name;
	},
	resetField: function(field) {
		field = $(field);
		if(field) {
			var cn = field.className.split(" ");
			cn.each(function(className) {
				if(className.test('^warn-')) className = className.replace(/^warn-/,"");
				var prop = this.getPropName(className);
				if(field.retrieve(prop)) this.hideAdvice(className, field);
				field.removeClass('validation-failed');
				field.removeClass('warning');
				field.removeClass('validation-passed');
			}, this);
		}
		return this;
	},
	stop: function(){
		this.paused = true;
		return this;
	},
	start: function(){
		this.paused = false;
		return this;
	},
	ignoreField: function(field, warn){
		field = $(field);
		if(field){
			this.enforceField(field);
			if(warn) field.addClass('warnOnly');
			else field.addClass('ignoreValidation');
		}
		return this;
	},
	enforceField: function(field){
		field = $(field);
		if(field) field.removeClass('warnOnly').removeClass('ignoreValidation');
		return this;
	}
});

FormValidator.resources = {
	zhCn: {
		required:'请输入值',
		minLength:'最小长度为{minLength},当前长度为{length}！',
		maxLength:'最大长度为{maxLength},当前长度为{length}！',
		integer:'请输入正确的整数！',
		numeric:'请输入有效的数字！',
		digits:'请输入数字！',
		alpha:'请输入英文字母！',
		alphanum:'请输入英文字母或是数字,其它字符是不允许的！',
		dateSuchAs:'请输入有效的日期，像这样 {date}',
		dateInFormatMDY:'请输入有效的日期，格式为 YYYY-MM-DD (i.e. "2000-12-31")',
		email:'请输入有效的邮件地址，如 "fred@domain.com"。',
		url:'请输入有效的URL地址！',
		currencyDollar:'Please enter a valid $ amount. For example $100.00 。',
		oneRequired:'在前面选项至少选择一个！',
		errorPrefix: '',/*错误提示*/
		warningPrefix: '',/*警告提示*/
		qq:'请输入有效的QQ号码！',
		ip:'请输入正确的IP地址！',
		idnumber:'请输入合法的身份证号码！',
		currencyyuan:'请输入有效的钱数，比如100.0000或者100。',
		zip:'请输入有效的邮政编码！',
		mobilephone:'请输入正确的手机号码！',
		phone:'请输入正确的号码！',
		chinese:'请输入中文！',
		selection:'请选择！',
		minvalue:'最小值为{minValue}',
		maxValue:'最大值为{maxValue}',
		equals:'两次输入不一致，请重新输入',
		lessthan:'请输入小于前面的值',
		lessthanequal:'请输入小于或等于前面的值',
		greatthan:'请输入大于前面的值',
		greatthanequal:'请输入大于或等于前面的值',
		intrange:'输入值应该为 {range}的整数',
		floatrange:'输入值应该为 {range}的数字',
		lengthrange:'输入值的长度应该在 {range}之间,当前长度为{length}',
		missValidatorProps:'无法找到validatorProps属性或者validatorProps中的{value}值',
		filetype:'文件类型应该为{type}其中之一',
		pattern:'输入值不匹配'
	}
};
FormValidator.language = "zhCn";
FormValidator.getMsg = function(key, language){
	return FormValidator.resources[language||FormValidator.language][key];
};

FormValidator.adders = {
	validators:{},
	add : function(className, options) {
		this.validators[className] = new InputValidator(className, options);
		//if this is a class
		//extend these validators into it
		if(!this.initialize){
			this.implement({
				validators: this.validators
			});
		}
	},
	addAllThese : function(validators) {
		$A(validators).each(function(validator) {
			this.add(validator[0], validator[1]);
		}, this);
	},
	getValidator: function(className){
		return this.validators[className.split(":")[0]];
	}
};
$extend(FormValidator, FormValidator.adders);
FormValidator.implement(FormValidator.adders);

FormValidator.add('IsEmpty', {
	errorMsg: false,
	test: function(element) { 
		if(element.type == "select-one"||element.type == "select")
			return !(element.selectedIndex >= 0 && element.options[element.selectedIndex].value != "-1");
		else
			return ((element.get('value') == null) || (element.get('value').length == 0));
	}
});

FormValidator.addAllThese([
	['required', {
		errorMsg: function(){
			return FormValidator.getMsg('required');
		},
		test: function(element) { 
			return !FormValidator.getValidator('IsEmpty').test(element); 
		}
	}],
	['minLength', {
		errorMsg: function(element, props){
			if($type(props.minLength))
				return FormValidator.getMsg('minLength').substitute({minLength:props.minLength,length:element.get('value').length });
			else return '';
		}, 
		test: function(element, props) {
			if($type(props.minLength)){
				return FormValidator.getValidator('IsEmpty').test(element) || (element.get('value').length >= $pick(props.minLength, 0));				
			}
			else return true;
		}
	}],
	['maxLength', {
		errorMsg: function(element, props){
			if($type(props.maxLength))
				return FormValidator.getMsg('maxLength').substitute({maxLength:props.maxLength,length:element.get('value').length });
			else return '';
		}, 
		test: function(element, props) {
			return FormValidator.getValidator('IsEmpty').test(element) || (element.get('value').length <= $pick(props.maxLength, 10000));
		}
	}],
	['minValue',{
		errorMsg: function(element, props){
			if($type(props.minValue))
				return FormValidator.getMsg('minvalue').substitute({minValue:props.minValue});
			else return '';
		}, 
		test: function(element, props) {
			if(FormValidator.getValidator('IsEmpty').test(element) ||
			   (
				 FormValidator.getValidator('validate-numeric').test(element) &&
				 (element.get('value') >= $pick(props.minValue, 10000))
			   ) 
			  ){
				return true;	
			}else{
				return false;	
			}			
		}
	}],
	['maxValue',{
		errorMsg: function(element, props){
			if($type(props.maxValue))
				return FormValidator.getMsg('maxValue').substitute({maxValue:props.maxValue});
			else return '';
		}, 
		test: function(element, props) {
			if(FormValidator.getValidator('IsEmpty').test(element) ||
				  (
					FormValidator.getValidator('validate-numeric').test(element) &&
					(element.get('value') <= $pick(props.maxValue, 10000))
				  )
			   ){
				return true;	
			}else{
				return false;	
			}			
		}
	}],
	['intRange',{
		errorMsg:function(element, props){
			if(!props.intRange){
					return FormValidator.getMsg('missValidatorProps').substitute({value:'intRange'});
				}
			else{
					if($type(props.intRange))
						return FormValidator.getMsg('intrange').substitute({range:props.intRange});
					else return '';
				}
		} ,
		test: function(element, props) {
			if(props.intRange){
				var rangValue = ValidationUtils.getRangeValue(props.intRange)
				if(FormValidator.getValidator('IsEmpty').test(element) ||
				   (
					   FormValidator.getValidator('validate-integer').test(element)&&
					   parseInt(element.get('value')) >= parseInt(rangValue[0])&&
					   parseInt(element.get('value')) <= parseInt(rangValue[1])
					)
				   ){
					return true;
				}else{
					return false;
				}
			}else{
				return false;
			}		
		}
	}],
	['floatRange',{
		errorMsg:function(element, props){
			if(!props.floatRange){
					return FormValidator.getMsg('missValidatorProps').substitute({value:'floatRange'});
				}
			else{
					if($type(props.floatRange))
						return FormValidator.getMsg('floatrange').substitute({range:props.floatRange});
					else return '';
				}
		} ,
		test: function(element, props) {
			if(props.floatRange){
				var rangValue = ValidationUtils.getRangeValue(props.floatRange)		
				if(FormValidator.getValidator('IsEmpty').test(element) ||
				   (
					   FormValidator.getValidator('validate-numeric').test(element)&&
					   parseFloat(element.get('value')) >= parseFloat(rangValue[0])&&
					   parseFloat(element.get('value')) <= parseFloat(rangValue[1])
					)
				   ){
					return true;
				}else{
					return false;
				}	
			}else{
				return false;
			}		
		}
	}],
	['lengthRange',{
		errorMsg:function(element, props){
			if(!props.lengthRange){
					return FormValidator.getMsg('missValidatorProps').substitute({value:'lengthrange'});
				}
			else{
					if($type(props.lengthRange))
						return FormValidator.getMsg('lengthrange').substitute({range:props.lengthRange,length:element.get('value').length});
					else return '';
				}
		} ,
		test: function(element, props) {
			if(props.lengthRange){
				var rangValue = ValidationUtils.getRangeValue(props.lengthRange)
				if(FormValidator.getValidator('IsEmpty').test(element) ||
				   (
					   parseInt(element.get('value').length) >= parseInt(rangValue[0])&&
					   parseInt(element.get('value').length) <= parseInt(rangValue[1])
					)
				   ){
					return true;
				}else{
					return false;
				}
			}else{
				return false;
			}		
		}
	}],
	['fileType',{
		errorMsg:function(element, props){
			if(!props.fileType)
				return FormValidator.getMsg('missValidatorProps').substitute({value:'fileType'});
			else
				return FormValidator.getMsg('filetype').substitute({type:ValidationUtils.getRangeValue(props.fileType).join(',')});
		} ,
		test: function(element, props) {
			if(props.fileType){
				if(FormValidator.getValidator('IsEmpty').test(element)) return true;
				var rangValue = ValidationUtils.getRangeValue(props.fileType);
				var v = element.get('value');
				return $A(rangValue).some(function(extentionName) {
					return new RegExp('\\.'+extentionName+'$','i').test(v);
				});
			}else{				
				return false;
			}
		}
	}],
	['pattern',{
		errorMsg:function(element, props){
			if(!props.pattern)
				return FormValidator.getMsg('missValidatorProps').substitute({value:'pattern'});
			else
				return FormValidator.getMsg('pattern');
		} ,
		test: function(element, props) {
			if(props.pattern){
				if(FormValidator.getValidator('IsEmpty').test(element)) return true;
				var v = element.get('value');
				var m = props.pattern.substring(1,props.pattern.length)
				var last = m.substring(0,m.length-1)	
				p = new RegExp(last);
				return p.test(v);
			}else{				
				return false;
			}
		}
	}],
	['equalsPassword',{
		errorMsg:function(element, props){
		if(!props.equalsPassword)
				return FormValidator.getMsg('missValidatorProps').substitute({value:'equals'});
			else
				return FormValidator.getMsg('equals');
		} ,
		test: function(element, props) {
			if(props.equalsPassword){	
				if(FormValidator.getValidator('IsEmpty').test(element)) return true;
				return element.get('value') == $(props.equalsPassword).get('value')
			}else{
				return false;
			}
		}
	}],
	['equals',{
		errorMsg:function(element, props){
		if(!props.equals)
				return FormValidator.getMsg('missValidatorProps').substitute({value:'equals'});
			else
				return FormValidator.getMsg('equals');
		} ,
		test: function(element, props) {
			if(props.equals){
				if(FormValidator.getValidator('IsEmpty').test(element)) return true;
				return element.get('value') == $(props.equals).get('value')
			}else{
				return false;
			}
		}
	}],
	['lessThan',{
		errorMsg:function(element, props){
			if(!props.lessThan)
				return FormValidator.getMsg('missValidatorProps').substitute({value:'lessThan'});
			else
				return FormValidator.getMsg('lessthan');
		} ,
		test: function(element, props) {
			if(props.lessThan){
				if(FormValidator.getValidator('IsEmpty').test(element)) return true;
				if(FormValidator.getValidator('validate-numeric').test(element) && 
				   FormValidator.getValidator('validate-numeric').test($(props.lessThan))){
					return parseFloat(element.get('value')) < parseFloat($(props.lessThan).get('value'));	
				}else{
					return element.get('value') < $(props.lessThan).get('value');	
				}
			}else{
				return false;
			}		
		}
	}],
	['lessThanEqual',{
		errorMsg:function(element, props){
			if(!props.lessThanEqual)
				return FormValidator.getMsg('missValidatorProps').substitute({value:'lessthanequal'});
			else
				return FormValidator.getMsg('lessthanequal');
		} ,
		test: function(element, props) {
			if(props.lessThanEqual){
				if(FormValidator.getValidator('IsEmpty').test(element)) return true;
				if(FormValidator.getValidator('validate-numeric').test(element) && 
				   FormValidator.getValidator('validate-numeric').test($(props.lessThanEqual))){
					return parseFloat(element.get('value')) <= parseFloat($(props.lessThanEqual).get('value'));
				}else{
					return element.get('value') <= $(props.lessThanEqual).get('value');
				}
			}else{				
				return false;
			}
		}
	}],
	['greatThan',{
		errorMsg:function(element, props){
			if(!props.greatThan)
				return FormValidator.getMsg('missValidatorProps').substitute({value:'greatthan'});
			else
				return FormValidator.getMsg('greatthan');
		} ,
		test: function(element, props) {
			if(props.greatThan){
				if(FormValidator.getValidator('IsEmpty').test(element)) return true;
				if(FormValidator.getValidator('validate-numeric').test(element) && 
				   FormValidator.getValidator('validate-numeric').test($(props.greatThan)))
				{
					return parseFloat(element.get('value')) > parseFloat($(props.greatThan).get('value'));
				}else{
					return element.get('value') > $(props.greatThan).get('value')
				}
			}else{				
				return false;
			}
		}
	}],
	['greatThanEqual',{
		errorMsg:function(element, props){
			if(!props.greatThanEqual)
				return FormValidator.getMsg('missValidatorProps').substitute({value:'greatthanequal'});
			else
				return FormValidator.getMsg('greatthanequal');
		} ,
		test: function(element, props) {
			if(props.greatThanEqual){
				if(FormValidator.getValidator('IsEmpty').test(element)) return true;
				if(FormValidator.getValidator('validate-numeric').test(element) && 
				   FormValidator.getValidator('validate-numeric').test($(props.greatThanEqual))){
					return parseFloat(element.get('value')) >= parseFloat($(props.greatThanEqual).get('value'));
				}else{
					return element.get('value') >= $(props.greatThanEqual).get('value');
				}
			}else{				
				return false;
			}
		}
	}],
	['validate-integer', {
		errorMsg: FormValidator.getMsg.pass('integer'),
		test: function(element) {
			return FormValidator.getValidator('IsEmpty').test(element) || /^-?[1-9]\d*$/.test(element.get('value'));
		}
	}],
	['validate-numeric', {
		errorMsg: FormValidator.getMsg.pass('numeric'), 
		test: function(element) {
			return FormValidator.getValidator('IsEmpty').test(element) || 
				/^-?(?:0$0(?=\d*\.)|[1-9]|0)\d*(\.\d+)?$/.test(element.get('value'));
		}
	}],
	['validate-digits', {
		errorMsg: FormValidator.getMsg.pass('digits'), 
		test: function(element) {
			return FormValidator.getValidator('IsEmpty').test(element) || (/^[\d() .:\-\+#]+$/.test(element.get('value')));
		}
	}],
	['validate-alpha', {
		errorMsg: FormValidator.getMsg.pass('alpha'), 
		test: function (element) {
			return FormValidator.getValidator('IsEmpty').test(element) ||  /^[a-zA-Z]+$/.test(element.get('value'))
		}
	}],
	['validate-alphanum', {
		errorMsg: FormValidator.getMsg.pass('alphanum'), 
		test: function(element) {
			return FormValidator.getValidator('IsEmpty').test(element) || !/\W/.test(element.get('value'))
		}
	}],
	['validate-date', {
		errorMsg: function(element, props) {
			if (Date.parse) {
				var format = props.dateFormat || "%x";
				return FormValidator.getMsg('dateSuchAs').substitute({date:new Date().format(format)});
			} else {
				return FormValidator.getMsg('dateInFormatMDY');
			}
		},
		test: function(element, props) {
			if(FormValidator.getValidator('IsEmpty').test(element)) return true;
			if (Date.parse) {
				var format = props.dateFormat || "%x";
				var d = Date.parse(element.get('value'));
				var formatted = d.format(format);
				if (formatted != "invalid date") element.set('value', formatted);
				return !isNaN(d);
			} else {
				var regex = /^(\d{2})\/(\d{2})\/(\d{4})$/;
				if(!regex.test(element.get('value'))) return false;
					var d = new Date(element.get('value').replace(regex, '$1/$2/$3'));
				return (parseInt(RegExp.$1, 10) == (1+d.getMonth())) && 
						(parseInt(RegExp.$2, 10) == d.getDate()) && 
						(parseInt(RegExp.$3, 10) == d.getFullYear() );
			}
		}
	}],
	['validate-email', {
		errorMsg: FormValidator.getMsg.pass('email'), 
		test: function (element) {
			return FormValidator.getValidator('IsEmpty').test(element) || /^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,4}$/i.test(element.get('value'));
		}
	}],
	['validate-url', {
		errorMsg: FormValidator.getMsg.pass('url'), 
		test: function (element) {
			return FormValidator.getValidator('IsEmpty').test(element) || /^(https?|ftp|rmtp|mms):\/\/(([A-Z0-9][A-Z0-9_-]*)(\.[A-Z0-9][A-Z0-9_-]*)+)(:(\d+))?\/?/i.test(element.get('value'));
		}
	}],
	['validate-currency-dollar', {
		errorMsg: FormValidator.getMsg.pass('currencyDollar'), 
		test: function(element) {
			// [$]1[##][,###]+[.##]
			// [$]1###+[.##]
			// [$]0.##
			// [$].##
			return FormValidator.getValidator('IsEmpty').test(element) ||  /^\$?\-?([1-9]{1}[0-9]{0,2}(\,[0-9]{3})*(\.[0-9]{0,2})?|[1-9]{1}\d*(\.[0-9]{0,2})?|0(\.[0-9]{0,2})?|(\.[0-9]{1,2})?)$/.test(element.get('value'));
		}
	}],
	['validate-qq',{
		errorMsg: FormValidator.getMsg.pass('qq'), 
		test: function (element) {
			return FormValidator.getValidator('IsEmpty').test(element) ||  /^[1-9]\d{4,8}$/.test(element.get('value'));
		}
	}],
	['validate-ip',{
		errorMsg: FormValidator.getMsg.pass('ip'),
		test: function (element) {
			return FormValidator.getValidator('IsEmpty').test(element) ||
			  /^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/.test(element.get('value'));
		}
	}],
	['validate-id-number',{
		errorMsg: FormValidator.getMsg.pass('idnumber'),
	    test: function(element) {
			if (FormValidator.getValidator('IsEmpty').test(element)) 
				return true;
			else {
				var v = element.get('value');
				if (!(/^\d{17}(\d|x)$/i.test(v) || /^\d{15}$/i.test(v))) return false
				var provinceCode = parseInt(v.substr(0,2));
				if((provinceCode < 11) || (provinceCode > 91)) return false;
				var forTestDate = v.length == 18 ? v : v.substr(0,6)+"19"+v.substr(6,15);
				var birthday = forTestDate.substr(6,8);
				if(!ValidationUtils.isDate(birthday,'yyyyMMdd')) return false;
				if(v.length == 18) {
					v = v.replace(/x$/i,"a");
					var verifyCode = 0;
					for(var i = 17;i >= 0;i--)   
		            	verifyCode += (Math.pow(2,i) % 11) * parseInt(v.charAt(17 - i),11);
		            if(verifyCode % 11 != 1) return false;
				}
				return true;
			}
		}
	}],
	['validate-currency-yuan',{
		errorMsg: FormValidator.getMsg.pass('currencyyuan'),
		test: function (element) {
			return FormValidator.getValidator('IsEmpty').test(element) ||
			  /^\-?([1-9]{1}[0-9]{0,2}(\,[0-9]{3})*(\.[0-9]{0,4})?|[1-9]{1}\d*(\.[0-9]{0,2})?|0(\.[0-9]{0,2})?|(\.[0-9]{1,2})?)$/.test(element.get('value'));
		}
	}],
	['validate-zip',{
		errorMsg: FormValidator.getMsg.pass('zip'),
		test: function (element) {
			return FormValidator.getValidator('IsEmpty').test(element) ||/^[1-9]\d{5}$/.test(element.get('value'));
		}
	}],
	['validate-mobile-phone',{
		errorMsg: FormValidator.getMsg.pass('mobilephone'),
		test: function (element) {
			return FormValidator.getValidator('IsEmpty').test(element) ||/(^0?[1][358][0-9]{9}$)/.test(element.get('value'));
		}
	}],
	['validate-phone',{
		errorMsg: FormValidator.getMsg.pass('phone'),
		test: function (element) {
			return FormValidator.getValidator('IsEmpty').test(element) ||/^((0[1-9]{3})?(0[12][0-9])?[-]?)?\d{6,8}$/.test(element.get('value'));
		}	
	}],
	['validate-chinese',{
		errorMsg: FormValidator.getMsg.pass('chinese'),
		test: function (element) {
			return FormValidator.getValidator('IsEmpty').test(element) ||/^[\u4e00-\u9fa5]+$/.test(element.get('value'));
		}
	}],
	['validate-one-required', {
		errorMsg: FormValidator.getMsg.pass('oneRequired'), 
		test: function (element) {
			var p = element.parentNode;
			var result = p.getElements('input').some(function(el) {
							if (['checkbox', 'radio'].contains(el.get('type'))) return el.get('checked');
							return el.get('value');
						});
			if(result){
				p.getChildren().each(function(el){
					if(el.hasClass('validation-advice')){
						el.setStyle('display','none');
					}
				})
			}
			return result;
		}
	}],
	['validate-one-selected',{
		errorMsg: FormValidator.getMsg.pass('selection'),
		test: function (element) {
			if(FormValidator.getValidator('IsEmpty').test(element)) return false;
			var v = element.get('value');			
			return (element.get('tag') == 'select') ? element.selectedIndex >= 0 : !((v == null) || (v.length == 0));
		}
	}]
]);

Element.Properties.validator = {

	set: function(options){
		var validator = this.retrieve('validator');
		if (validator) validator.setOptions(options);
		return this.store('validator:options');
	},

	get: function(options){
		if (options || !this.retrieve('validator')){
			if (options || !this.retrieve('validator:options')) this.set('validator', options);
			this.store('validator', new FormValidator(this, this.retrieve('validator:options')));
		}
		return this.retrieve('validator');
	}

};

Element.implement({
	validate: function(options){
		this.set('validator', options);
		return this.get('validator', options).validate();
	}
});
