PageSourceSearch

https://leru-spb.ru/components/com_formcalc/assets/js/formcheck.js

js leru-spb.ru collected 2026-10-04 23:59:33 UTC 43,215 bytes, 1,182 lines download raw bytes

1/*
2---
3script: formcheck.js
4
5description:     A MooTools class that allows you to perform different tests on forms to validate them before submission.
6
7authors:
8  - fyrye (http://torntech.com)
9  - weepaki
10  - floor.ch (http://mootools.floor.ch)
11  
12copyright: Copyright (c) 2010-2011 
13
14license:
15  - MIT License
16
17requires:
18  core/1.2.4: '*'
19  more/1.2.4.4:
20      - Fx.Scroll
21
22provides:
23  - FormCheck
24...
25*/
26/*
27	Class: FormCheck
28		Performs different tests on forms and indicates errors.
29
30	Usage:
31		Works with these types of fields :
32		- input (text, radio, checkbox)
33		- textarea
34		- select
35
36		You just need to add a specific class to each fields you want to check.
37		For example, if you add the class
38			(code)
39			validate['required','length[4, -1]','differs:email','digit']
40			(end code)
41		the value's field must be set (required) with a minimum length of four chars (4, -1),
42		must differs of the input named email (differs:email), and must be digit.
43
44		You can perform check during the datas entry or on the submit action, shows errors as tips or in a div before or after the field,
45		show errors one by one or all together, show a list of all errors at the top of the form, localize error messages, add new regex check, ...
46
47		The layout is design only with css. Now I added a hack to use transparent png with IE6, so you can use png images in formcheck.css (works only for theme, so the file must be named formcheck.css). It can also works with multiple forms on a single html page.
48		The class supports now internationalization. To use it, simply specify a new <script> element in your html head, like this : <script type="text/javascript" src="formcheck/lang/fr.js"></script>.
49
50		If you add the class
51			(code)
52			validate['submit']
53			(end code)
54		to an element like an anchor (or anything else), this element will act as a submit button.
55
56		N.B. : you must load the language script before the formcheck and this method overpass the old way. You can create new languages following existing ones. You can otherwise still specifiy the alerts' strings when you initialize the Class, with options.
57		If you don't use a language script, the alert will be displayed in english.
58
59	Test type:
60		You can perform various test on fields by adding them to the validate class. Be careful to *not use space chars*. Here is the list of them.
61
62		required 					- The field becomes required. This is a regex, you can change it with class options.
63		alpha 						- The value is restricted to alphabetic chars. This is a regex, you can change it with class options.
64		alphanum 					- The value is restricted to alphanumeric characters only. This is a regex, you can change it with class options.
65		nodigit 					- The field doesn't accept digit chars. This is a regex, you can change it with class options.
66		digit 						- The value is restricted to digit (no floating point number) chars, you can pass two arguments (f.e. digit[21,65]) to limit the number between them. Use -1 as second argument to set no maximum.
67		number 						- The value is restricted to number, including floating point number. This is a regex, you can change it with class options.
68		email 						- The value is restricted to valid email. This is a regex, you can change it with class options.
69		image						- The value is restricted to images (jpg, jpeg, png, gif, bmp). This is a regex, you can change it with class options.
70		phone 						- The value is restricted to phone chars. This is a regex, you can change it with class options.
71		phone_inter					- The value is restricted to international phone number. This is a regex, you can change it with class options.
72		url: 						- The value is restricted to url. This is a regex, you can change it with class options.
73		confirm 					- The value has to be the same as the specified. f.e. confirm:password.
74		differs 					- The value has to be diferent as the one specifies. f.e. differs:user.
75		length 						- The value length is restricted by argument (f.e. length[6,10]). Use -1 as second argument to set no maximum.
76		group						- Use to validate several checkboxes as a group. Requires 2 arguments, the second one being optional (1 by default): the group id and the minimum amount of boxes to check. The second argument may be set on any or all items of the group. See example below.
77		words						- The words number is limited by arguments. f.e. words[1,13]. Use -1 as second argument to don't have a max limit.
78		target						- It's not really a validation test, but it allows you to attach the error message to an other element, usefull if the input you validate is hidden. You must specifiy target id, f.e. target:myDiv.
79
80		You can also use a custom function to check a field. For example, if you have a field with class
81			(code)
82			validate['required','%customCheck']
83			(end code)
84		the function customCheck(el) will be called to validate the field. '%customcheck' works with other validate(s) together, and '~customcheck' works if the element pass the other validate(s).
85		Here is an example of what customCheck could look :
86			(code)
87			function customCheck(el){
88				if (!el.value.test(/^[A-Z]/)) {
89					el.errors.push("Username should begin with an uppercase letter");
90					return false;
91				} else {
92					return true;
93				}
94			}
95			(end code)
96
97		To validate checkoxes group, you could make something like :
98			(code)
99				<input type="checkbox" name="dog" class="validate['group[1,2]']">
100				<input type="checkbox" name="cat" class="validate['group[1]']">
101				<input type="checkbox" name="mouse" class="validate['group[1]']">
102			(end code)
103		For checkboxes from group 1, you will need to check at least 2 boxes.
104
105		It is now possible to register new fields after a new FormCheck call by using <FormCheck::register> (see <FormCheck::dispose> too). You need first to add the validate class to the element you want to register ( $('myInput').addClass("validate['required']") ).
106
107	Parameters:
108		When you initialize the class with addEvent, you can set some options. If you want to modify regex, you must do it in a hash, like for display or alert. You can also add new regex check method by adding the regex and an alert with the same name.
109
110		Required:
111
112			form_id - The id of the formular. This is required.
113
114		Optional:
115
116			submit					- If you turn this option to false, the FormCheck will only perform a validation, without submitting the form, even on success. You can use validateSuccess event to execute some code.
117
118			submitByAjax 			- you can set this to true if you want to submit your form with ajax. You should use provided events to handle the ajax request (see below). By default it is false.
119			ajaxResponseDiv 		- id of element to inject ajax response into (can also use onAjaxSuccess). By default it is false.
120			ajaxEvalScripts 		- use evalScripts in the Request response. Can be true or false, by default it is false.
121			onAjaxRequest 			- Function to fire when the Request event starts.
122			onAjaxComplete 			- Function to fire when the Request event completes regardless of and prior to Success or Failure.
123			onAjaxSuccess 			- Function to fire when the Request receives .  Args: response [the request response] - see Mootools docs for Request.onSuccess.
124			onAjaxFailure 			- Function to fire if the Request fails.
125
126			onSubmit				- Function to fire when form is submited (so before validation)
127			onValidateSuccess 		- Function to fire when validation pass (you should prevent form submission with option submit:false to use this)
128			onValidateFailure		- Function to fire when validation fails
129
130			tipsClass 				- The class to apply to tipboxes' errors. By default it is 'fc-tbx'.
131			errorClass 				- The class to apply to alertbox (not tips). By default it is 'fc-error'.
132			fieldErrorClass 		- The class to apply to fields with errors, except for radios. You should also turn on  options.addClassErrorToField. By default it is 'fc-field-error'
133
134			trimValue				- If set to true, strip whitespace (or other characters) from the beginning and end of values. By default it is false.
135			validateDisabled		- If set to true, disabled input will be validated too, otherwise not.
136
137		Display:
138			This is a hash of display settings. in here you can modify.
139
140			showErrors 				- 0 : onSubmit, 1 : onSubmit & onBlur, by default it is 0.
141			titlesInsteadNames		- 0 : When you do a check using differs or confirm, it takes the field name for the alert. If it's set to 1, it will use the title instead of the name.
142			errorsLocation 			- 1 : tips, 2 : before, 3 : after, by default it is 1.
143			indicateErrors 			- 0 : none, 1 : one by one, 2 : all, by default it is 1.
144			indicateErrorsInit		- 0 : determine if the form must be checked on initialize. Could be usefull to force the user to update fields that don't validate.
145			keepFocusOnError 		- 0 : normal behaviour, 1 : the current field keep the focus as it remain errors. By default it is 0.
146			checkValueIfEmpty 		- 0 : When you leave a field and you have set the showErrors option to 1, the value is tested only if a value has been set. 1 : The value is tested  in any case.  By default it is 1.
147			addClassErrorToField 	- 0 : no class is added to the field, 1 : the options.fieldErrorClass is added to the field with an error (except for radio). By default it is 0.
148			removeClassErrorOnTipClosure - 0 : Error class is kept when the tip is closed, 1 : Error class is removed when the tip is closed
149
150			fixPngForIe 			- 0 : do nothing, 1 : fix png alpha for IE6 in formcheck.css. By default it is 1.
151			replaceTipsEffect 		- 0 : No effect on tips replace when we resize the broswer, 1: tween transition on browser resize;
152			closeTipsButton 		- 0 : the close button of the tipbox is hidden, 1 : the close button of the tipbox is visible. By default it is 1.
153			flashTips 				- 0 : normal behaviour, 1 : the tipbox "flash" (disappear and reappear) if errors remain when the form is submitted. By default it is 0.
154			tipsPosition 			- 'right' : the tips box is placed on the right part of the field, 'left' to place it on the left part. By default it is 'right'.
155			tipsOffsetX 			- Horizontal position of the tips box (margin-left), , by default it is 100 (px).
156			tipsOffsetY				- Vertical position of the tips box (margin-bottom), , by default it is -10 (px).
157
158			listErrorsAtTop 		- List all errors at the top of the form, , by default it is false.
159			scrollToFirst 			- Smooth scroll the page to first error and focus on it, by default it is true.
160			fadeDuration 			- Transition duration (in ms), by default it is 300.
161
162		Alerts:
163			This is a hash of alerts settings. in here you can modify strings to localize or wathever else. %0 and %1 represent the argument.
164
165			required 				- "This field is required."
166			alpha 					- "This field accepts alphabetic characters only."
167			alphanum 				- "This field accepts alphanumeric characters only."
168			nodigit 				- "No digits are accepted."
169			digit 					- "Please enter a valid integer."
170			digitmin 				- "The number must be at least %0"
171			digitltd 				- "The value must be between %0 and %1"
172			number 					- "Please enter a valid number."
173			email 					- "Please enter a valid email: <br /><span>E.g. [email protected]</span>"
174			phone 					- "Please enter a valid phone."
175			phone_inter 			- "Please enter a valid international phone number."
176			url 					- "Please enter a valid url: <br /><span>E.g. http://www.domain.com</span>"
177			image					- "This field should only contain image types"
178			confirm 				- "This field is different from %0"
179			differs 				- "This value must be different of %0"
180			length_str 				- "The length is incorrect, it must be between %0 and %1"
181			length_fix 				- "The length is incorrect, it must be exactly %0 characters"
182			lengthmax 				- "The length is incorrect, it must be at max %0"
183			lengthmin 				- "The length is incorrect, it must be at least %0"
184			words_min				- "This field must concain at least %0 words, now it has %1 words"
185			words_range				- "This field must contain between %0 and %1 words, now it has %2 words"
186			words_max				- "This field must contain at max %0 words, now it has %1 words"
187			checkbox 				- "Please check the box"
188			checkboxes_group		- "Please check at least %0 box(es)"
189			radios 					- "Please select a radio"
190			select 					- "Please choose a value"
191
192	Example:
193		You can initialize a formcheck (no scroll, custom classes and alert) by adding for example this in your html head this code :
194
195		(code)
196		<script type="text/javascript">
197			window.addEvent('domready', function() {
198				var myCheck = new FormCheck('form_id', {
199					tipsClass : 'tips_box',
200					display : {
201						scrollToFirst : false
202					},
203					alerts : {
204						required : 'This field is ablolutely required! Please enter a value'
205					}
206				})
207			});
208		</script>
209		(end code)
210
211	About:
212		formcheck.js v.1.6 for mootools v1.2 - 03 / 2010
213
214		by Mootools.Floor (http://mootools.floor.ch) MIT-style license
215
216		Created by Luca Pillonel (luca-at-nolocation.org),
217		Last modified by Luca Pillonel
218
219	Credits:
220		This class was inspired by fValidator by Fabio Zendhi Nagao (http://zend.lojcomm.com.br)
221
222		Thanks to all contributors from groups.google.com/group/moofloor (and others as well!) providing ideas, translations, fixes and motivation!
223*/
224
225if(typeof $empty == 'undefined')$empty = function(){};
226
227
228var FormCheck = new Class({
229
230	Implements: [Options, Events],
231
232	options : {
233
234		tipsClass : 'fc-tbx',				//tips error class
235		errorClass : 'fc-error',			//div error class
236		fieldErrorClass : 'fc-field-error',	//error class for elements
237
238		submit : true,						//false : just validate the form and do nothing else. Use onValidateSuccess event to execute some code
239		submitAction: false,				//Action page used to submit the form data to.
240		submitMethod: false,				//Method used to submit the form, valid options : 'post' or 'get'
241
242		trimValue : false,					//trim (remove whitespaces before and after) the value
243		validateDisabled : false,			//skip validation on disabled input if set to false.
244
245		submitByAjax : false,				//false : standard submit way, true : submit by ajax
246		ajaxResponseDiv : false,			//element to inject ajax response into (can also use onAjaxSuccess) [cronix]
247		ajaxEvalScripts : false,			//use evalScripts in the Request response [cronix]
248		onAjaxRequest : $empty,				//Function to fire when the Request event starts
249		onAjaxComplete : $empty,			//Function to fire when the Request is complete, before and regardless of Success or Failure
250		onAjaxSuccess : $empty,				//Function to fire when the Request receives .  Args: response [the request response] - see Mootools docs for Request.onSuccess
251		onAjaxFailure : $empty,				//Function to fire if the Request fails
252
253		onSubmit		  : $empty,			//Function to fire when user submit the form
254		onValidateSuccess : $empty,			//Function to fire when validation pass
255		onValidateFailure : $empty,			//Function to fire when validation fails
256
257		display : {
258			showErrors : 0,
259			titlesInsteadNames : 0,
260			errorsLocation : 1,
261			indicateErrors : 1,
262			indicateErrorsInit : 0,
263			keepFocusOnError : 0,
264			checkValueIfEmpty : 1,
265			addClassErrorToField : 0,
266			removeClassErrorOnTipClosure : 0,
267			fixPngForIe : 1,
268			replaceTipsEffect : 1,
269			flashTips : 0,
270			closeTipsButton : 1,
271			tipsPosition : "right",
272			tipsOffsetX : -45,
273			tipsOffsetY : 0,
274			listErrorsAtTop : false,
275			scrollToFirst : true,
276			fadeDuration : 300
277		},
278
279		alerts : {
280			required : "This field is required.",
281			alpha : "This field accepts alphabetic characters only.",
282			alphanum : "This field accepts alphanumeric characters only.",
283			nodigit : "No digits are accepted.",
284			digit : "Please enter a valid integer.",
285			digitltd : "The value must be between %0 and %1",
286			number : "Please enter a valid number.",
287			email : "Please enter a valid email.",
288			image : 'This field should only contain image types',
289			phone : "Please enter a valid phone.",
290			phone_inter : "Please enter a valid international phone number.",
291			url : "Please enter a valid url.",
292
293			confirm : "This field is different from %0",
294			differs : "This value must be different of %0",
295			length_str : "The length is incorrect, it must be between %0 and %1",
296			length_fix : "The length is incorrect, it must be exactly %0 characters",
297			lengthmax : "The length is incorrect, it must be at max %0",
298			lengthmin : "The length is incorrect, it must be at least %0",
299			words_min : "This field must concain at least %0 words, currently: %1 words",
300			words_range : "This field must contain %0-%1 words, currently: %2 words",
301			words_max : "This field must contain at max %0 words, currently: %1 words",
302			checkbox : "Please check the box",
303			checkboxes_group : 'Please check at least %0 box(es)',
304			radios : "Please select a radio",
305			select : "Please choose a value",
306			select_multiple : "Please choose at least one value"
307		},
308
309		regexp : {
310			required : /[^.*]/,
311			alpha : /^[a-z ._-]+$/i,
312			alphanum : /^[a-z0-9 ._-]+$/i,
313			digit : /^[-+]?[0-9]+$/,
314			nodigit : /^[^0-9]+$/,
315			number : /^[-+]?\d*\.?\d+$/,
316			email : /^([a-zA-Z0-9_\.\-\+%])+\@(([a-zA-Z0-9\-])+\.)+([a-zA-Z0-9]{2,4})+$/,
317			image : /.(jpg|jpeg|png|gif|bmp)$/i,
318			phone : /^\+{0,1}[0-9 \(\)\.\-]+$/, // alternate regex : /^[\d\s ().-]+$/,/^((\+\d{1,3}(-| )?\(?\d\)?(-| )?\d{1,5})|(\(?\d{2,6}\)?))(-| )?(\d{3,4})(-| )?(\d{4})(( x| ext)\d{1,5}){0,1}$/
319			phone_inter : /^\+{0,1}[0-9 \(\)\.\-]+$/,
320			url : /^(http|https|ftp)\:\/\/[a-z0-9\-\.]+\.[a-z]{2,3}(:[a-z0-9]*)?\/?([a-z0-9\-\._\?\,\'\/\\\+&amp;%\$#\=~])*$/i
321		}
322	},
323
324	/*
325	Constructor: initialize
326		Constructor
327
328		Add event on formular and perform some stuff, you now, like settings, ...
329	*/
330	initialize : function(form, options) {
331		if (this.form = $(form)) {
332			this.form.isValid = true;
333			this.regex = ['length'];
334			this.groups = {};
335
336			//internalization
337			if (typeof(formcheckLanguage) != 'undefined') this.options.alerts = Object.merge({}, this.options.alerts, formcheckLanguage);
338
339			this.setOptions(options);
340
341			this.form.setProperty('action',
342				this.options.submitAction || this.form.getProperty('action') || 'post');
343
344			this.form.setProperty('method',
345				this.options.submitMethod || this.form.getProperty('method') || '');
346
347			this.validations = [];
348			this.alreadyIndicated = false;
349			this.firstError = false;
350
351			$H(this.options.regexp).each(function(el, key) {
352				this.regex.push(key);
353			}, this);
354
355			this.form.getElements("*[class*=validate]").each(function(el) {
356				this.register(el);
357			}, this);
358
359			this.form.addEvents({
360				"submit": this.onSubmit.bind(this)
361			});
362
363			if(this.options.display.fixPngForIe) this.fixIeStuffs();
364			document.addEvent('mousewheel', function(){
365				this.isScrolling = false;
366			}.bind(this));
367
368			if (this.options.display.indicateErrorsInit) {
369				this.validations.each(function(el) {
370					if(!this.manageError(el,'submit')) this.form.isValid = false;
371				}, this);
372			}
373		}
374	},
375
376	/*
377	Function: register
378		Allows you to declare afterward new fields to the formcheck, to check dynamically loaded fields for example.
379		By default it will be the last element to be validated as it's added after others inputs, but you can define a position with second parameter.
380
381	Example:
382		(code)
383		<script type="text/javascript">
384			window.addEvent('domready', function() {
385				formcheck = new FormCheck('form_id');
386			});
387
388			// ...some code...
389
390			var newField = new Element('input', {
391				class	: "validate['required']",
392				name	: "new-field"
393			}).inject('form_id');
394			formcheck.register(newField, 3);
395
396			new Element('input', {
397				class	: "validate['required']",
398				name	: "another-field",
399				id		: "another-field"
400			}).inject('form_id');
401			formcheck.register($('another-field'));
402		</script>
403		(end code)
404
405	See also:
406		<FormCheck::dispose>
407	*/
408	register : function(el, position) {
409		el.validation = [];
410		el.getProperty("class").split(' ').each(function(classX) {
411			if (classX.match(/^validate(\[.+\])$/)) {
412				var valid = true;
413
414				var validators = eval(classX.match(/^validate(\[.+\])$/)[1]);
415				for(var i = 0; i < validators.length; i++) {
416					el.validation.push(validators[i]);
417					if (validators[i].match(/^confirm:/)) {
418						var field = validators[i].match(/.+:(.+)$/)[1];
419						if (this.form[field].validation.contains('required')) el.validation.push('required');
420					}
421					if (validators[i].match(/^target:.+/)) {
422						el.target = validators[i].match(/^target:(.+)/)[1];
423					}
424				}
425
426				//we check if group is already registered
427				el.isChild = this.isChildType(el, validators);
428				if (el.isChild && el.type == 'radio') {
429					this.validations.each(function(registeredEl){
430						if (registeredEl.name == el.name) valid = false;
431					}, this);
432				}
433				if (el.isChild && el.type == 'checkbox') {
434					this.validations.each(function(registeredEl){
435						if (registeredEl.groupID == el.groupID) valid = false;
436					}, this);
437				}
438
439				if (position && position <= this.validations.length) {
440					var newValidations = [];
441					this.validations.each(function(valider, i){
442						if (position == i+1 && valid) {
443							newValidations.push(el);
444							this.addListener(el);
445						}
446						newValidations.push(valider);
447					}, this);
448					this.validations = newValidations;
449				} else if (valid) {
450					this.validations.push(el);
451					this.addListener(el);
452				}
453			}
454		}, this);
455	},
456
457	/*
458	Function: dispose
459		Allows you to remove a declared field from formCheck
460
461	Example:
462		(code)
463		<script type="text/javascript">
464			window.addEvent('domready', function() {
465				formcheck = new FormCheck('form_id');
466			});
467
468			// ...some code...
469
470			formcheck.dispose($('obsolete-field'));
471		</script>
472		(end code)
473
474	See also:
475		<FormCheck::register>
476	*/
477	dispose : function(element) {
478		this.validations.erase(element);
479	},
480
481	/*
482	Function: addListener
483		Private method
484
485		Add listener on fields
486	*/
487	addListener : function(el) {
488		el.errors = [];
489
490		if (el.validation[0] == 'submit') {
491			el.addEvent('click', function(e){
492				new Event(e).stop();
493				if (this.onSubmit(e)) this.form.submit();
494			}.bind(this));
495			return true;
496		}
497
498		if (!el.isChild) {
499			el.addEvent('blur', function() {
500				if(!this.fxRunning && (el.element || this.options.display.showErrors == 1) && (this.options.display.checkValueIfEmpty || el.value)) this.manageError(el, 'blur');
501			}.bind(this));
502		//We manage errors on radio
503		} else if(el.isChild && el.type == 'radio') {
504			//We get all radio from the same group and add a blur option
505			var radioGroup = this.form.getElements('input[name="'+ el.getProperty("name") +'"]');
506			radioGroup.each(function(radio){
507				radio.addEvent('blur', function(){
508					if(!this.fxRunning && (el.element || this.options.display.showErrors == 1) && (this.options.display.checkValueIfEmpty || el.value)) this.manageError(el, 'click');
509				}.bind(this));
510			},this);
511		}
512	},
513
514	/*
515	Function: manageError
516		Private method
517
518		Manage display of errors boxes
519	*/
520	manageError : function(el, method) {
521		var isValid = this.validate(el);
522		if (method == 'testonly') return isValid;
523		if ((!isValid && el.validation.contains('required')) || (el.value && !isValid)) {
524			if(this.options.display.listErrorsAtTop && method == 'submit') this.listErrorsAtTop(el);
525			if (this.options.display.indicateErrors == 2 ||this.alreadyIndicated == false || el == this.alreadyIndicated) {
526				if(!this.firstError) this.firstError = el;
527				this.alreadyIndicated = el;
528
529				if (this.options.display.keepFocusOnError && el == this.firstError) {
530					(function(){el.focus()}).delay(10);
531				}
532				this.addError(el);
533				return false;
534			}
535		} else if ((isValid || (!el.validation.contains('required') && !el.value))) {
536			this.removeError(el);
537			return true;
538		}
539		return true;
540	},
541
542	/*
543	Function: validate
544		Private method
545
546		Dispatch check to other methods
547	*/
548	validate : function(el) {
549		el.errors = [];
550		el.isOk = true;
551
552		//skip validation for disabled fields and trim if specified
553		if (!this.options.validateDisabled && el.get('disabled')) return true;
554		if (this.options.trimValue && el.value) el.value = el.value.trim();
555
556		el.validation.each(function(rule) {
557			if(el.isChild) {
558				if (!this.validateGroup(el)) el.isOk = false;
559			} else {
560				var ruleArgs = [];
561
562				if(rule.match(/target:.+/)) return;
563				var ruleMethod = rule;
564				if(rule.match(/^.+\[/)) {
565					ruleMethod = rule.split('[')[0];
566					ruleArgs = eval(rule.match(/^.+(\[.+\])$/)[1].replace(/([A-Z0-9\._-]+)/i, "'$1'"));
567				}
568
569				if (this.regex.contains(ruleMethod) && el.get('tag') != "select") {
570					if (this.validateRegex(el, ruleMethod, ruleArgs) == false) {
571						el.isOk = false;
572					}
573				}
574				if (rule.match(/confirm:.+/)) {
575					ruleArgs = [rule.match(/.+:(.+)$/)[1]];
576					if (this.validateConfirm(el, ruleArgs) == false) {
577						el.isOk = false;
578					}
579				}
580				if (rule.match(/differs:.+/)) {
581					ruleArgs = [rule.match(/.+:(.+)$/)[1]];
582					if (this.validateDiffers(el, ruleArgs) == false) {
583						el.isOk = false;
584					}
585				}
586				if (ruleMethod == 'words') {
587					if (this.validateWords(el, ruleArgs) == false) {
588						el.isOk = false;
589					}
590				}
591				if (ruleMethod == 'required' && (el.get('tag') == "select" || el.type == "checkbox")) {
592					if (this.simpleValidate(el) == false) {
593						el.isOk = false;
594					}
595				}
596				if(rule.match(/%[A-Z0-9\._-]+$/i) || (el.isOk && rule.match(/~[A-Z0-9\._-]+$/i))) {
597					if(eval(rule.slice(1)+'(el)') == false) {
598						el.isOk = false;
599					}
600				}
601			}
602		}, this);
603		return ( el.isOk ) ? true : false;
604	},
605
606	/*
607	Function: simpleValidate
608		Private method
609
610		Perform simple check for select fields and checkboxes
611	*/
612	simpleValidate : function(el) {
613		if(el.get('tag') == 'select'){
614			if(!el.multiple) {
615				if(el.selectedIndex <= 0) {
616					el.errors.push(this.options.alerts.select);
617					return false;
618				}
619			} else {
620				var selected = false;
621				el.getChildren('option').each(function(el){
622					if(el.selected) selected = true;
623				});
624
625				if(!selected){
626					el.errors.push(this.options.alerts.select_multiple);
627					return false;
628				}
629			}
630		} else if (el.type == "checkbox" && el.checked == false) {
631			el.errors.push(this.options.alerts.checkbox);
632			return false;
633		}
634		return true;
635	},
636
637	/*
638	Function: validateRegex
639		Private method
640
641		Perform regex validations
642	*/
643	validateRegex : function(el, ruleMethod, ruleArgs) {
644		var msg = "";
645		if (ruleMethod == 'length' && ruleArgs[1]) {
646			if (ruleArgs[1] == -1) {
647				this.options.regexp.length = new RegExp("^[\\s\\S]{"+ ruleArgs[0] +",}$");
648				msg = this.options.alerts.lengthmin.replace("%0",ruleArgs[0]);
649			} else if(ruleArgs[0] == ruleArgs[1]) {
650				this.options.regexp.length = new RegExp("^[\\s\\S]{"+ ruleArgs[0] +"}$");
651				msg = this.options.alerts.length_fix.replace("%0",ruleArgs[0]);
652			} else {
653				this.options.regexp.length = new RegExp("^[\\s\\S]{"+ ruleArgs[0] +","+ ruleArgs[1] +"}$");
654				msg = this.options.alerts.length_str.replace("%0",ruleArgs[0]).replace("%1",ruleArgs[1]);
655			}
656		} else if (ruleArgs[0] && ruleMethod == 'length') {
657			this.options.regexp.length = new RegExp("^.{0,"+ ruleArgs[0] +"}$");
658			msg = this.options.alerts.lengthmax.replace("%0",ruleArgs[0]);
659		} else {
660			msg = this.options.alerts[ruleMethod];
661		}
662		if ((ruleMethod == 'digit' || ruleMethod == 'number') && ruleArgs[1]) {
663			var valueres, regres = true;
664			if (!this.options.regexp[ruleMethod].test(el.value)) {
665				el.errors.push(this.options.alerts[ruleMethod]);
666				regres = false;
667			}
668			if (ruleArgs[1] == -1) {
669				valueres = ( el.value.toFloat() >= ruleArgs[0].toFloat() );
670				msg = this.options.alerts.digitmin.replace("%0",ruleArgs[0]);
671			} else {
672				valueres = ( el.value.toFloat() >
672= ruleArgs[0].toFloat() && el.value.toFloat() <= ruleArgs[1].toFloat() );
673				msg = this.options.alerts.digitltd.replace("%0",ruleArgs[0]).replace("%1",ruleArgs[1]);
674			}
675			if (regres == false || valueres == false) {
676				el.errors.push(msg);
677				return false;
678			}
679		} else if (this.options.regexp[ruleMethod].test(el.value) == false)  {
680			el.errors.push(msg);
681			return false;
682		}
683		return true;
684	},
685
686	/*
687	Function: validateConfirm
688		Private method
689
690		Perform confirm validations
691	*/
692	validateConfirm: function(el,ruleArgs) {
693		var confirm = ruleArgs[0];
694		if(el.value != this.form[confirm].value){
695			var msg = ( this.options.display.titlesInsteadNames ) ?
696				this.options.alerts.confirm.replace("%0",this.form[confirm].getProperty('title')) :
697				this.options.alerts.confirm.replace("%0",confirm);
698			el.errors.push(msg);
699			return false;
700		}
701		return true;
702	},
703
704	/*
705	Function: validateDiffers
706		Private method
707
708		Perform differs validations
709	*/
710	validateDiffers: function(el,ruleArgs) {
711		var differs = ruleArgs[0];
712		if(el.value == this.form[differs].value){
713			var msg = ( this.options.display.titlesInsteadNames ) ?
714				this.options.alerts.differs.replace("%0",this.form[differs].getProperty('title')) :
715				this.options.alerts.differs.replace("%0",differs);
716			el.errors.push(msg);
717			return false;
718		}
719		return true;
720	},
721
722	/*
723	Function: validateWords
724		Private method
725
726		Perform word count validation
727	*/
728	validateWords: function(el,ruleArgs) {
729		var min = ruleArgs[0];
730		var max = ruleArgs[1];
731
732		var words = el.value.replace(/[ \t\v\n\r\f\p]/m, ' ').replace(/[,.;:]/g, ' ').clean().split(' ');
733
734		if(max == -1) {
735			if(words.length < min) {
736				el.errors.push(this.options.alerts.words_min.replace("%0", min).replace("%1", words.length));
737				return false;
738			}
739		} else {
740			if(min > 0)	{
741				if(words.length < min || words.length > max) {
742					el.errors.push(this.options.alerts.words_range.replace("%0", min).replace("%1", max).replace("%2", words.length));
743					return false;
744				}
745			} else {
746				if(words.length > max) {
747					el.errors.push(this.options.alerts.words_max.replace("%0", max).replace("%1", words.length));
748					return false;
749				}
750			}
751		}
752		return true;
753	},
754
755
756	/*
757	Function: isFormValid
758		public method
759
760		Determine if the form is valid
761
762		Return true or false
763	*/
764    isFormValid: function() {
765		this.form.isValid = true;
766		this.validations.each(function(el) {
767			var validation = this.manageError(el,'testonly');
768			if(!validation) this.form.isValid = false;
769		}, this);
770		return this.form.isValid;
771	},
772
773	/*
774	Function: isChildType
775		Private method
776
777		Determine if the field is a group of radio, of checkboxes or not.
778	*/
779	isChildType: function(el, validators) {
780		var validator;
781		if(el.type && el.type == 'radio') {
782			return true;
783		} else if(validator = validators.join().match(/group(\[.*\])/)) {
784			var group = eval(validator[1]);
785			this.groups[group[0]] = this.groups[group[0]] || [];
786			this.groups[group[0]][0] = this.groups[group[0]][0] || [];
787			this.groups[group[0]][1] = group[1] || this.groups[group[0]][1] || 1;
788			this.groups[group[0]][0].push(el);
789			el.groupID = group[0];
790			return true;
791		}
792		return false;
793	},
794
795	/*
796	Function: validateGroup
797		Private method
798
799		Perform radios validations
800	*/
801	validateGroup : function(el) {
802		el.errors = [];
803		if(el.type == 'radio') {
804			var nlButtonGroup = this.form[el.getProperty("name")];
805			el.group = nlButtonGroup;
806			var cbCheckeds = false;
807
808			for(var i = 0; i < nlButtonGroup.length; i++) {
809				if(nlButtonGroup[i].checked) {
810					cbCheckeds = true;
811				}
812			}
813			if(cbCheckeds == false) {
814				el.errors.push(this.options.alerts.radios);
815				return false;
816			} else {
817				return true;
818			}
819		// we have group of checkboxes
820		} else if(el.type == 'checkbox') {
821			//we get length of checked elements
822			var checked = 0;
823			this.groups[el.groupID][0].each(function(groupEl){
824				if(groupEl.checked) checked++;
825			});
826			if(checked >= this.groups[el.groupID][1]) {
827				return true;
828			} else {
829				( this.groups[el.groupID][0].length > 1 ) ?
830					el.errors.push(this.options.alerts.checkboxes_group.replace('%0', this.groups[el.groupID][1])) :
831					el.errors.push(this.options.alerts.checkbox);
832				return false;
833			}
834		// we have unmanaged type
835		} else {
836			return false;
837		}
838	},
839
840	/*
841	Function: listErrorsAtTop
842		Private method
843
844		Display errors
845	*/
846	listErrorsAtTop : function(obj) {
847		if(!this.form.element) {
848			 this.form.element = new Element('div', {'id' : 'errorlist', 'class' : this.options.errorClass}).injectTop(this.form);
849		}
850		if ($type(obj) == 'collection') {
851			new Element('p').set('html',"<span>" + obj[0].name + " : </span>" + obj[0].errors[0]).inject(this.form.element);
852		} else {
853			if ((obj.validation.contains('required') && obj.errors.length > 0) || (obj.errors.length > 0 && obj.value && obj.validation.contains('required') == false)) {
854				obj.errors.each(function(error) {
855					new Element('p').set('html',"<span>" + obj.name + " : </span>" + error).inject(this.form.element);
856				}, this);
857			}
858		}
859		window.fireEvent('resize');
860	},
861
862	/*
863	Function: addError
864		Private method
865
866		Add error message
867	*/
868	addError : function(obj) {
869		//determine position
870		var coord = obj.target ? $(obj.target).getCoordinates() : obj.getCoordinates();
871
872		if(!obj.element && this.options.display.indicateErrors != 0) {
873			if (this.options.display.errorsLocation == 1) {
874				var pos = (this.options.display.tipsPosition == 'left') ? coord.left : coord.right;
875				var options = {
876					'opacity' : 0,
877					'position' : 'absolute',
878					'float' : 'left',
879					'left' : pos + this.options.display.tipsOffsetX
880				};
881				obj.element = new Element('div', {'class' : this.options.tipsClass, 'styles' : options}).inject(document.body);
882				this.addPositionEvent(obj);
883			} else if (this.options.display.errorsLocation == 2){
884				obj.element = new Element('div', {'class' : this.options.errorClass, 'styles' : {'opacity' : 0}}).injectBefore(obj);
885			} else if (this.options.display.errorsLocation == 3){
886				obj.element = new Element('div', {'class' : this.options.errorClass, 'styles' : {'opacity' : 0}});
887				if ($type(obj.group) == 'object' || $type(obj.group) == 'collection')
888					obj.element.injectAfter(obj.group[obj.group.length-1]);
889				else
890					obj.element.injectAfter(obj);
891			}
892		}
893		if (obj.element && obj.element != true) {
894			obj.element.empty();
895			if (this.options.display.errorsLocation == 1) {
896				var errors = [];
897				obj.errors.each(function(error) {
898					errors.push(new Element('p').set('html', error));
899				});
900				var tips = this.makeTips(errors).inject(obj.element);
901				if(this.options.display.closeTipsButton) {
902					tips.getElements('a.close').addEvent('mouseup', function(){
903						this.removeError(obj, 'tip');
904					}.bind(this));
905				}
906				obj.element.setStyle('top', coord.top - tips.getCoordinates().height + this.options.display.tipsOffsetY);
907			} else {
908				obj.errors.each(function(error) {
909					new Element('p').set('html',error).inject(obj.element);
910				});
911			}
912
913			if (!this.options.display.fadeDuration || Browser.ie && Browser.version == 5 && this.options.display.errorsLocation < 2) {
914				obj.element.setStyle('opacity', 1);
915			} else {
916				obj.fx = new Fx.Tween(obj.element, {
917					'duration' : this.options.display.fadeDuration,
918					'ignore' : true,
919					'onStart' : function(){
920						this.fxRunning = true;
921					}.bind(this),
922					'onComplete' : function() {
923						this.fxRunning = false;
924						if (obj.element && obj.element.getStyle('opacity').toInt() == 0) {
925							obj.element.destroy();
926							obj.element = false;
927						}
928					}.bind(this)
929				});
930				if(obj.element.getStyle('opacity').toInt() != 1) obj.fx.start('opacity', 1);
931			}
932		}
933		if (this.options.display.addClassErrorToField && !obj.isChild){
934			obj.addClass(this.options.fieldErrorClass);
935			obj.element = obj.element || true;
936		}
937
938	},
939
940	/*
941	Function: addPositionEvent
942
943		Update tips position after a browser resize
944	*/
945	addPositionEvent : function(obj) {
946		if(this.options.display.replaceTipsEffect) {
947			obj.event = function(){
948				var coord = obj.target ? $(obj.target).getCoordinates() : obj.getCoordinates();
949				new Fx.Morph(obj.element, {
950					'duration' : this.options.display.fadeDuration
951				}).start({
952					'left':[obj.element.getStyle('left'), coord.right + this.options.display.tipsOffsetX],
953					'top':[obj.element.getStyle('top'), coord.top - obj.element.getCoordinates().height + this.options.display.tipsOffsetY]
954				});
955			}.bind(this);
956
957		} else {
958			obj.event = function(){
959				var coord = obj.target ? $(obj.target).getCoordinates() : obj.getCoordinates();
960				obj.element.setStyles({
961					'left':coord.right + this.options.display.tipsOffsetX,
962					'top':coord.top - obj.element.getCoordinates().height + this.options.display.tipsOffsetY
963				});
964			}.bind(this);
965		}
966		window.addEvent('resize', obj.event);
967	},
968
969	/*
970	Function: removeError
971		Private method
972
973		Remove the error display
974	*/
975	removeError : function(obj, method) {
976		if ((this.options.display.addClassErrorToField && !obj.isChild && this.options.display.removeClassErrorOnTipClosure) || (this.options.display.addClassErrorToField && !obj.isChild && !this.options.display.removeClassErrorOnTipClosure && method != 'tip'))
977			obj.removeClass(this.options.fieldErrorClass);
978
979		if (!obj.element) return;
980		this.alreadyIndicated = false;
981		obj.errors = [];
982		obj.isOK = true;
983		window.removeEvent('resize', obj.event);
984		if (this.options.display.errorsLocation >= 2 && obj.element) {
985			new Fx.Tween(obj.element, {
986				'duration': this.options.display.fadeDuration
987			}).start('height', 0);
988		}
989		if (!this.options.display.fadeDuration || Browser.ie && Browser.Engine.version == 5 && this.options.display.errorsLocation == 1 && obj.element) {
990			this.fxRunning = true;
991			obj.element.destroy();
992			obj.element = false;
993			(function(){this.fxRunning = false}.bind(this)).delay(200);
994		} else if (obj.element && obj.element != true) {
995			obj.fx.start('opacity', 0);
996		}
997	},
998
999	/*
1000	Function: focusOnError
1001		Private method
1002
1003		Create set the focus to the first field with an error if needed
1004	*/
1005	focusOnError : function (obj) {
1006		if (this.options.display.scrollToFirst && !this.alreadyFocused && !this.isScrolling) {
1007			var dest; //moved this up to stop redclariations
1008			//This can changed to a single switch using default:
1009			if (!this.options.display.indicateErrors || !this.options.display.errorsLocation) {
1010				dest = obj.getCoordinates().top-30;
1011			} else {
1012				switch (this.options.display.errorsLocation){
1013					case 1 :
1014						dest = obj.element.getCoordinates().top;
1015						break;
1016					case 2 :
1017						dest = obj.element.getCoordinates().top-30;
1018						break;
1019					case 3 :
1020						dest = obj.getCoordinates().top-30;
1021						break;
1022				}
1023				this.isScrolling = true;
1024			}
1025			if (window.getScroll().y != dest) {
1026				new Fx.Scroll(window, {
1027					onComplete : function() {
1028						this.isScrolling = false;
1029						if (obj.getProperty('type') != 'hidden') obj.focus();
1030					}.bind(this)
1031				}).start(0,dest);
1032			} else {
1033				this.isScrolling = false;
1034				obj.focus();
1035			}
1036			this.alreadyFocused = true;
1037		}
1038	},
1039
1040	/*
1041	Function: fixIeStuffs
1042		Private method
1043
1044		Fix png for IE6
1045	*/
1046	fixIeStuffs : function () {
1047		if (Browser.ie) {
1048			//We fix png stuffs
1049			var rpng = new RegExp('url\\(([\.a-zA-Z0-9_/:-]+\.png)\\)');
1050			var search = new RegExp('(.+)formcheck\.css');
1051			for (var i = 0; i < document.styleSheets.length; i++){
1052				if (document.styleSheets[i].href.match(/formcheck\.css$/)) {
1053					var root = document.styleSheets[i].href.replace(search, '$1');
1054					var count = document.styleSheets[i].rules.length;
1055					for (var j = 0; j < count; j++){
1056						var cssstyle = document.styleSheets[i].rules[j].style;
1057						var bgimage = root + cssstyle.backgroundImage.replace(rpng, '$1');
1058						if (bgimage && bgimage.match(/\.png/i)){
1059							var scale = (cssstyle.backgroundRepeat == 'no-repeat') ? 'crop' : 'scale';
1060							cssstyle.filter =  'progid:DXImageTransform.Microsoft.AlphaImageLoader(enabled=true, src=\'' + bgimage + '\', sizingMethod=\''+ scale +'\')';
1061							cssstyle.backgroundImage = "none";
1062						}
1063					}
1064				}
1065			}
1066		}
1067	},
1068
1069	/*
1070	Function: makeTips
1071		Private method
1072
1073		Create tips boxes
1074	*/
1075	makeTips : function(txt) {
1076		var table = new Element('table');
1077			table.cellPadding ='0';
1078			table.cellSpacing ='0';
1079			table.border ='0';
1080
1081			var tbody = new Element('tbody').inject(table);
1082				var tr1 = new Element('tr').inject(tbody);
1083					new Element('td', {'class' : 'tl'}).inject(tr1);
1084					new Element('td', {'class' : 't'}).inject(tr1);
1085					new Element('td', {'class' : 'tr'}).inject(tr1);
1086				var tr2 = new Element('tr').inject(tbody);
1087					new Element('td', {'class' : 'l'}).inject(tr2);
1088					var cont = new Element('td', {'class' : 'c'}).inject(tr2);
1089						var errors = new Element('div', {'class' : 'err'}).inject(cont);
1090						txt.each(function(error) {
1091							error.inject(errors);
1092						});
1093						if (this.options.display.closeTipsButton) new Element('a',{'class' : 'close'}).inject(cont);
1094					new Element('td', {'class' : 'r'}).inject(tr2);
1095				var tr3 = new Element('tr').inject(tbody);
1096					new Element('td', {'class' : 'bl'}).inject(tr3);
1097					new Element('td', {'class' : 'b'}).inject(tr3);
1098					new Element('td', {'class' : 'br'}).inject(tr3);
1099		return table;
1100	},
1101
1102	/*
1103	Function: reinitialize
1104		Reinitialize form before submit check. You can use this also to remove all tips from a form, passing the argument "forced" ( formcheck.reinitialize('forced'); )
1105	*/
1106	reinitialize: function(forced) {
1107		this.validations.each(function(el) {
1108			if (el.element) {
1109				el.errors = [];
1110				el.isOK = true;
1111				if(this.options.display.flashTips == 1 || forced == 'forced') {
1112					el.element.destroy();
1113					el.element = false;
1114				}
1115			}
1116		}, this);
1117		if (this.form.element) this.form.element.empty();
1118		this.alreadyFocused = false;
1119		this.firstError = false;
1120		this.elementToRemove = this.alreadyIndicated;
1121		this.alreadyIndicated = false;
1122		this.form.isValid = true;
1123	},
1124
1125	/*
1126	Function: submitByAjax
1127		Private method
1128
1129		Send the form by ajax, and replace the form with response
1130	*/
1131
1132	submitByAjax: function() {
1133		this.fireEvent('ajaxRequest');
1134		new Request({
1135			url: this.form.action,
1136			method: this.form.method,
1137			data : this.form.toQueryString(),
1138			evalScripts: this.options.ajaxEvalScripts,
1139			onFailure: function(instance){
1140				this.fireEvent('ajaxFailure', instance);
1141			}.bind(this),
1142			onComplete: function(instance){
1143				this.fireEvent('ajaxComplete', instance);
1144			}.bind(this),
1145			onSuccess: function(result){
1146				this.fireEvent('ajaxSuccess', result);
1147				if(this.options.ajaxResponseDiv) $(this.options.ajaxResponseDiv).set('html',result);
1148			}.bind(this)
1149		}).send();
1150		return false;
1151	},
1152
1153	/*
1154	Function: onSubmit
1155		Private method
1156
1157		Perform check on submit action
1158	*/
1159	onSubmit: function(event) {
1160		this.reinitialize();
1161		this.fireEvent('onSubmit');
1162		
1163		this.validations.each(function(el) {
1164			var validation = this.manageError(el,'submit');
1165			if(!validation) this.form.isValid = false;
1166		}, this);
1167
1168		if (this.form.isValid) {
1169			this.fireEvent('validateSuccess');
1170			//moved above to allow optional settings to this.form.submit and submitByAjax to be triggered by this option
1171			return (this.options.submitByAjax)? this.submitByAjax():this.options.submit;
1172			//if this.options.submit is false it can still rely on validateSuccess event
1173		} else {
1174			if (this.elementToRemove && this.elementToRemove != this.firstError && this.options.display.indicateErrors == 1) {
1175				this.removeError(this.elementToRemove);
1176			}
1177			this.focusOnError(this.firstError);
1178			this.fireEvent('validateFailure');
1179			return false;
1180		}
1181	}
1182});

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.