PageSourceSearch

https://taka-cho.jp/Spry/SpryValidationTextField.js

js taka-cho.jp collected 2026-10-04 03:22:29 UTC 77,490 bytes, 2,332 lines download raw bytes

1// SpryValidationTextField.js - version 0.38 - Spry Pre-Release 1.6.1
2//
3// Copyright (c) 2006. Adobe Systems Incorporated. All rights reserved.
4//
5// Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
6//
7//   * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
8//   * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
9//   * Neither the name of Adobe Systems Incorporated nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
10//
11// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
12
13(function() { // BeginSpryComponent
14
15if (typeof Spry == "undefined") window.Spry = {}; if (!Spry.Widget) Spry.Widget = {};
16
17Spry.Widget.BrowserSniff = function()
18{
19	var b = navigator.appName.toString();
20	var up = navigator.platform.toString();
21	var ua = navigator.userAgent.toString();
22
23	this.mozilla = this.ie = this.opera = this.safari = false;
24	var re_opera = /Opera.([0-9\.]*)/i;
25	var re_msie = /MSIE.([0-9\.]*)/i;
26	var re_gecko = /gecko/i;
27	var re_safari = /(applewebkit|safari)\/([\d\.]*)/i;
28	var r = false;
29
30	if ( (r = ua.match(re_opera))) {
31		this.opera = true;
32		this.version = parseFloat(r[1]);
33	} else if ( (r = ua.match(re_msie))) {
34		this.ie = true;
35		this.version = parseFloat(r[1]);
36	} else if ( (r = ua.match(re_safari))) {
37		this.safari = true;
38		this.version = parseFloat(r[2]);
39	} else if (ua.match(re_gecko)) {
40		var re_gecko_version = /rv:\s*([0-9\.]+)/i;
41		r = ua.match(re_gecko_version);
42		this.mozilla = true;
43		this.version = parseFloat(r[1]);
44	}
45	this.windows = this.mac = this.linux = false;
46
47	this.Platform = ua.match(/windows/i) ? "windows" :
48					(ua.match(/linux/i) ? "linux" :
49					(ua.match(/mac/i) ? "mac" :
50					ua.match(/unix/i)? "unix" : "unknown"));
51	this[this.Platform] = true;
52	this.v = this.version;
53
54	if (this.safari && this.mac && this.mozilla) {
55		this.mozilla = false;
56	}
57};
58
59Spry.is = new Spry.Widget.BrowserSniff();
60
61Spry.Widget.ValidationTextField = function(element, type, options)
62{
63	type = Spry.Widget.Utils.firstValid(type, "none");
64	if (typeof type != 'string') {
65		this.showError('The second parameter in the constructor should be the validation type, the options are the third parameter.');
66		return;
67	}
68	if (typeof Spry.Widget.ValidationTextField.ValidationDescriptors[type] == 'undefined') {
69		this.showError('Unknown validation type received as the second parameter.');
70		return;
71	}
72	options = Spry.Widget.Utils.firstValid(options, {});
73	this.type = type;
74	if (!this.isBrowserSupported()) {
75		//disable character masking and pattern behaviors for low level browsers
76		options.useCharacterMasking = false;
77	}
78	this.init(element, options);
79
80	//make sure we validate at least on submit
81	var validateOn = ['submit'].concat(Spry.Widget.Utils.firstValid(this.options.validateOn, []));
82	validateOn = validateOn.join(",");
83
84	this.validateOn = 0;
85	this.validateOn = this.validateOn | (validateOn.indexOf('submit') != -1 ? Spry.Widget.ValidationTextField.ONSUBMIT : 0);
86	this.validateOn = this.validateOn | (validateOn.indexOf('blur') != -1 ? Spry.Widget.ValidationTextField.ONBLUR : 0);
87	this.validateOn = this.validateOn | (validateOn.indexOf('change') != -1 ? Spry.Widget.ValidationTextField.ONCHANGE : 0);
88
89	if (Spry.Widget.ValidationTextField.onloadDidFire)
90		this.attachBehaviors();
91	else
92		Spry.Widget.ValidationTextField.loadQueue.push(this);
93};
94
95Spry.Widget.ValidationTextField.ONCHANGE = 1;
96Spry.Widget.ValidationTextField.ONBLUR = 2;
97Spry.Widget.ValidationTextField.ONSUBMIT = 4;
98
99Spry.Widget.ValidationTextField.ERROR_REQUIRED = 1;
100Spry.Widget.ValidationTextField.ERROR_FORMAT = 2;
101Spry.Widget.ValidationTextField.ERROR_RANGE_MIN = 4;
102Spry.Widget.ValidationTextField.ERROR_RANGE_MAX = 8;
103Spry.Widget.ValidationTextField.ERROR_CHARS_MIN = 16;
104Spry.Widget.ValidationTextField.ERROR_CHARS_MAX = 32;
105
106/* validation parameters: - characterMasking : prevent typing of characters not matching an regular expression - regExpFilter : additional regular expression to disalow typing of characters (like the "-" sign in the middle of the value); use for partial matching of the currently typed value; the typed value must match regExpFilter at any moment - pattern : enforce character on each position inside a pattern (AX0?) - validation : function performing logic validation; return false if failed and the typedValue value on success - minValue, maxValue : range validation; check if typedValue inside the specified range - minChars, maxChars : value length validation; at least/at most number of characters */
107Spry.Widget.ValidationTextField.ValidationDescriptors = {
108	'none': {
109	},
110	'custom': {
111	},
112	'integer': {
113		characterMasking: /[\-\+\d]/,
114		regExpFilter: /^[\-\+]?\d*$/,
115		validation: function(value, options) {
116			if (value == '' || value == '-' || value == '+') {
117				return false;
118			}
119			var regExp = /^[\-\+]?\d*$/;
120			if (!regExp.test(value)) {
121				return false;
122			}
123			options = options || {allowNegative:false};
124			var ret = parseInt(value, 10);
125			if (!isNaN(ret)) {
126				var allowNegative = true;
127				if (typeof options.allowNegative != 'undefined' && options.allowNegative == false) {
128					allowNegative = false;
129				}
130				if (!allowNegative && value < 0) {
131					ret = false;
132				}
133			} else {
134				ret = false;
135			}
136			return ret;
137		}
138	},
139	'real': {
140		characterMasking: /[\d\.,\-\+e]/i,
141		regExpFilter: /^[\-\+]?\d(?:|\.,\d{0,2})|(?:|e{0,1}[\-\+]?\d{0,})$/i,
142		validation: function (value, options) {
143			var regExp = /^[\+\-]?[0-9]+([\.,][0-9]+)?([eE]{0,1}[\-\+]?[0-9]+)?$/;
144			if (!regExp.test(value)) {
145				return false;
146			}
147			var ret = parseFloat(value);
148			if (isNaN(ret)) {
149				ret = false;
150			}
151			return ret;
152		}
153	},
154	'currency': {
155		formats: {
156			'dot_comma': {
157				characterMasking: /[\d\.\,\-\+\$]/,
158				regExpFilter: /^[\-\+]?(?:[\d\.]*)+(|\,\d{0,2})$/,
159				validation: function(value, options) {
160					var ret = false;
161					//2 or no digits after the comma
162					if (/^(\-|\+)?\d{1,3}(?:\.\d{3})*(?:\,\d{2}|)$/.test(value) || /^(\-|\+)?\d+(?:\,\d{2}|)$/.test(value)) {
163						value = value.toString().replace(/\./gi, '').replace(/\,/, '.');
164						ret = parseFloat(value);
165					}
166					return ret;
167				}
168			},
169			'comma_dot': {
170				characterMasking: /[\d\.\,\-\+\$]/,
171				regExpFilter: /^[\-\+]?(?:[\d\,]*)+(|\.\d{0,2})$/,
172				validation: function(value, options) {
173					var ret = false;
174					//2 or no digits after the comma
175					if (/^(\-|\+)?\d{1,3}(?:\,\d{3})*(?:\.\d{2}|)$/.test(value) || /^(\-|\+)?\d+(?:\.\d{2}|)$/.test(value)) {
176						value = value.toString().replace(/\,/gi, '');
177						ret = parseFloat(value);
178					}
179					return ret;
180				}
181			}
182		}
183	},
184	'email': {
185		characterMasking: /[^\s]/,
186		validation: function(value, options) {
187			var rx = /^[\w\.-]+@[\w\.-]+\.\w+$/i;
188			return rx.test(value);
189		}
190	},
191	'date': {
192		validation: function(value, options) {
193			var formatRegExp = /^([mdy]+)[\.\-\/\\\s]+([mdy]+)[\.\-\/\\\s]+([mdy]+)$/i;
194			var valueRegExp = this.dateValidationPattern;
195			var formatGroups = options.format.match(formatRegExp);
196			var valueGroups = value.match(valueRegExp);
197			if (formatGroups !== null && valueGroups !== null) {
198				var dayIndex = -1;
199				var monthIndex = -1;
200				var yearIndex = -1;
201				for (var i=1; i<formatGroups.length; i++) {
202					switch (formatGroups[i].toLowerCase()) {
203						case "dd":
204							dayIndex = i;
205							break;
206						case "mm":
207							monthIndex = i;
208							break;
209						case "yy":
210						case "yyyy":
211							yearIndex = i;
212							break;
213					}
214				}
215				if (dayIndex != -1 && monthIndex != -1 && yearIndex != -1) {
216					var maxDay = -1;
217					var theDay = parseInt(valueGroups[dayIndex], 10);
218					var theMonth = parseInt(valueGroups[monthIndex], 10);
219					var theYear = parseInt(valueGroups[yearIndex], 10);
220
221					// Check month value to be between 1..12
222					if (theMonth < 1 || theMonth > 12) {
223						return false;
224					}
225					
226					// Calculate the maxDay according to the current month
227					switch (theMonth) {
228						case 1:	// January
229						case 3: // March
230						case 5: // May
231						case 7: // July
232						case 8: // August
233						case 10: // October
234						case 12: // December
235							maxDay = 31;
236							break;
237						case 4:	// April
238						case 6: // June
239						case 9: // September
240						case 11: // November
241							maxDay = 30;
242							break;
243						case 2: // February
244							if ((parseInt(theYear/4, 10) * 4 == theYear) && (theYear % 100 != 0 || theYear % 400 == 0)) {
245								maxDay = 29;
246							} else {
247								maxDay = 28;
248							}
249							break;
250					}
251
252					// Check day value to be between 1..maxDay
253					if (theDay < 1 || theDay > maxDay) {
254						return false;
255					}
256					
257					// If successfull we'll return the date object
258					return (new Date(theYear, theMonth - 1, theDay));   //JavaScript requires a month between 0 and 11
259				}
260			} else {
261				return false;
262			}
263		}
264	},
265	'time': {
266		validation: function(value, options) {
267			//	HH:MM:SS T
268			var formatRegExp = /([hmst]+)/gi;
269			var valueRegExp = /(\d+|AM?|PM?)/gi;
270			var formatGroups = options.format.match(formatRegExp);
271			var valueGroups = value.match(valueRegExp);
272			//mast match and have same length
273			if (formatGroups !== null && valueGroups !== null) {
274				if (formatGroups.length != valueGroups.length) {
275					return false;
276				}
277
278				var hourIndex = -1;
279				var minuteIndex = -1;
280				var secondIndex = -1;
281				//T is AM or PM
282				var tIndex = -1;
283				var theHour = 0, theMinute = 0, theSecond = 0, theT = 'AM';
284				for (var i=0; i<formatGroups.length; i++) {
285					switch (formatGroups[i].toLowerCase()) {
286						case "hh":
287							hourIndex = i;
288							break;
289						case "mm":
290							minuteIndex = i;
291							break;
292						case "ss":
293							secondIndex = i;
294							break;
295						case "t":
296						case "tt":
297							tIndex = i;
298							break;
299					}
300				}
301				if (hourIndex != -1) {
302					var theHour = parseInt(valueGroups[hourIndex], 10);
303					if (isNaN(theHour) || theHour > (formatGroups[hourIndex] == 'HH' ? 23 : 12 )) {
304						return false;
305					}
306				}
307				if (minuteIndex != -1) {
308					var theMinute = parseInt(valueGroups[minuteIndex], 10);
309					if (isNaN(theMinute) || theMinute > 59) {
310						return false;
311					}
312				}
313				if (secondIndex != -1) {
314					var theSecond = parseInt(valueGroups[secondIndex], 10);
315					if (isNaN(theSecond) || theSecond > 59) {
316						return false;
317					}
318				}
319				if (tIndex != -1) {
320					var theT = valueGroups[tIndex].toUpperCase();
321					if (
322						formatGroups[tIndex].toUpperCase() == 'TT' && !/^a|pm$/i.test(theT) || 
323						formatGroups[tIndex].toUpperCase() == 'T' && !/^a|p$/i.test(theT)
324					) {
325						return false;
326					}
327				}
328				var date = new Date(2000, 0, 1, theHour + (theT.charAt(0) == 'P'?12:0), theMinute, theSecond);
329				return date;
330			} else {
331				return false;
332			}
333		}
334	},
335	'credit_card': {
336		characterMasking: /\d/,
337		validation: function(value, options) {
338			var regExp = null;
339			options.format = options.format || 'ALL';
340			switch (options.format.toUpperCase()) {
341				case 'ALL': regExp = /^[3-6]{1}[0-9]{12,18}$/; break;
342				case 'VISA': regExp = /^4(?:[0-9]{12}|[0-9]{15})$/; break;
343				case 'MASTERCARD': regExp = /^5[1-5]{1}[0-9]{14}$/; break;
344				case 'AMEX': regExp = /^3(4|7){1}[0-9]{13}$/; break;
345				case 'DISCOVER': regExp = /^6011[0-9]{12}$/; break;
346				case 'DINERSCLUB': regExp = /^3(?:(0[0-5]{1}[0-9]{11}
346)|(6[0-9]{12})|(8[0-9]{12}))$/; break;
347			}
348			if (!regExp.test(value)) {
349				return false;
350			}
351			var digits = [];
352			var j = 1, digit = '';
353			for (var i = value.length - 1; i >= 0; i--) {
354				if ((j%2) == 0) {
355					digit = parseInt(value.charAt(i), 10) * 2;
356					digits[digits.length] = digit.toString().charAt(0);
357					if (digit.toString().length == 2) {
358						digits[digits.length] = digit.toString().charAt(1);
359					}
360				} else {
361					digit = value.charAt(i);
362					digits[digits.length] = digit;
363				}
364				j++;
365			}
366			var sum = 0;
367			for(i=0; i < digits.length; i++ ) {
368				sum += parseInt(digits[i], 10);
369			}
370			if ((sum%10) == 0) {
371				return true;
372			}
373			return false;
374		}
375	},
376	'zip_code': {
377		formats: {
378			'zip_us9': {
379				pattern:'00000-0000'
380			},
381			'zip_us5': {
382				pattern:'00000'
383			},
384			'zip_uk': {
385				characterMasking: /[\dA-Z\s]/,
386				validation: function(value, options) {
387					//check one of the following masks
388					// AN NAA, ANA NAA, ANN NAA, AAN NAA, AANA NAA, AANN NAA
389					return /^[A-Z]{1,2}\d[\dA-Z]?\s?\d[A-Z]{2}$/.test(value);
390				}
391			},
392			'zip_canada': {
393				characterMasking: /[\dA-Z\s]/,
394				pattern: 'A0A 0A0'
395			},
396			'zip_custom': {}
397		}
398	},
399	'phone_number': {
400		formats: {
401			//US phone number; 10 digits
402			'phone_us': {
403				pattern:'(000) 000-0000'
404			},
405			'phone_custom': {}
406		}
407	},
408	'social_security_number': {
409		pattern:'000-00-0000'
410	},
411	'ip': {
412		characterMaskingFormats: {
413			'ipv4': /[\d\.]/i,
414			'ipv6_ipv4': /[\d\.\:A-F\/]/i,
415			'ipv6': /[\d\.\:A-F\/]/i
416		},
417		validation: function (value, options) {
418			return Spry.Widget.ValidationTextField.validateIP(value, options.format);
419		}
420	},
421
422	'url': {
423		characterMasking: /[^\s]/,
424		validation: function(value, options) {
425			//fix for ?ID=223429 and ?ID=223387
426			/* the following regexp matches components of an URI as specified in http://tools.ietf.org/html/rfc3986#page-51 page 51, Appendix B.
427				scheme    = $2
428				authority = $4
429				path      = $5
430				query     = $7
431				fragment  = $9
432			*/
433			var URI_spliter = /^(([^:\/?#]+):)?(\/\/([^\/?#]*))?([^?#]*)(\?([^#]*))?(#(.*))?/;
434			var parts = value.match(URI_spliter);
435			if (parts && parts[4]) {
436				//encode each component of the domain name using Punycode encoding scheme: http://tools.ietf.org/html/rfc3492
437				var host  = parts[4].split(".");
438				var punyencoded = '';
439				for (var i=0; i<host.length; i++) {
440					punyencoded = Spry.Widget.Utils.punycode_encode(host[i], 64);
441					if (!punyencoded) {
442						return false;
443					} else {
444						if (punyencoded != (host[i] + "-")) {
445							host[i] = 'xn--' + punyencoded;
446						}
447					}
448				}
449				host = host .join(".");
450				//the encoded domain name is replaced into the original URL to be validated again later as URL
451				value = value.replace(URI_spliter, "$1//" + host + "$5$6$8");
452			}
453
454			//fix for ?ID=223358 and ?ID=223594
455			//the following validates an URL using ABNF rules as defined in http://tools.ietf.org/html/rfc3986 , Appendix A., page 49
456			//except host which is extracted by match[1] and validated separately
457			/*
458			 * userinfo=	(?:(?:[a-z0-9\-\._~\!\$\&\'\(\)\*\+\,\;\=:]|%[0-9a-f]{2,2})*\@)?
459			 * host=			(?:((?:(?:[a-z0-9][a-z0-9\-]*[a-z0-9]|[a-z0-9])\.)*(?:[a-z][a-z0-9\-]*[a-z0-9]|[a-z])|(?:\[[^\]]*\]))
460			 * pathname=	(?:\/(?:[a-z0-9\-\._~\!\$\&\'\(\)\*\+\,\;\=\:\@]|%[0-9a-f]{2,2})*)*
461			 * query=			(?:\?(?:[a-z0-9\-\._~\!\$\&\'\(\)\*\+\,\;\=\:\@\/\?]|%[0-9a-f]{2,2})*)?
462			 * anchor=		(?:\#(?:[a-z0-9\-\._~\!\$\&\'\(\)\*\+\,\;\=\:\@\/\?]|%[0-9a-f]{2,2})*)?
463			 */
464			var regExp = /^(?:https?|ftp)\:\/\/(?:(?:[a-z0-9\-\._~\!\$\&\'\(\)\*\+\,\;\=:]|%[0-9a-f]{2,2})*\@)?(?:((?:(?:[a-z0-9][a-z0-9\-]*[a-z0-9]|[a-z0-9])\.)*(?:[a-z][a-z0-9\-]*[a-z0-9]|[a-z])|(?:\[[^\]]*\]))(?:\:[0-9]*)?)(?:\/(?:[a-z0-9\-\._~\!\$\&\'\(\)\*\+\,\;\=\:\@]|%[0-9a-f]{2,2})*)*(?:\?(?:[a-z0-9\-\._~\!\$\&\'\(\)\*\+\,\;\=\:\@\/\?]|%[0-9a-f]{2,2})*)?(?:\#(?:[a-z0-9\-\._~\!\$\&\'\(\)\*\+\,\;\=\:\@\/\?]|%[0-9a-f]{2,2})*)?$/i;
465
466			var valid = value.match(regExp);
467			if (valid) {
468				//extract the  address from URL
469				var address = valid[1];
470
471				if (address) {
472					if (address == '[]') {
473						return false;
474					}
475					if (address.charAt(0) == '[' ) {
476						//IPv6 address or IPv4 enclosed in square brackets
477						address = address.replace(/^\[|\]$/gi, '');
478						return Spry.Widget.ValidationTextField.validateIP(address, 'ipv6_ipv4');
479					} else {
480						if (/[^0-9\.]/.test(address)) {
481							return true;
482						} else {
483							//check if hostname is all digits and dots and then check for IPv4
484							return Spry.Widget.ValidationTextField.validateIP(address, 'ipv4');
485						}
486					}
487				} else {
488					return true;
489				}
490			} else {
491				return false;
492			}
493		}
494	}
495};
496
497/*
4982.2.1. Preferred
499x:x:x:x:x:x:x:x, where the 'x's are the hexadecimal values of the eight 16-bit pieces of the address.
500Examples:
501	FEDC:BA98:7654:3210:FEDC:BA98:7654:3210
502	1080:0:0:0:8:800:200C:417A
503Note that it is not necessary to write the leading zeros in an
504individual field, but there must be at least one numeral in every
505field (except for the case described in 2.2.2.).
506
5072.2.2. Compressed
508The use of "::" indicates multiple groups of 16-bits of zeros.
509The "::" can only appear once in an address.  The "::" can also be
510used to compress the leading and/or trailing zeros in an address.
511	1080:0:0:0:8:800:200C:417A --> 1080::8:800:200C:417A
512	FF01:0:0:0:0:0:0:101 --> FF01::101
513	0:0:0:0:0:0:0:1 --> ::1
514	0:0:0:0:0:0:0:0 --> ::
515
5162.5.4 IPv6 Addresses with Embedded IPv4 Addresses
517	IPv4-compatible IPv6 address (tunnel IPv6 packets over IPv4 routing infrastructures)
518	::0:129.144.52.38
519	IPv4-mapped IPv6 address (represent the addresses of IPv4-only nodes as IPv6 addresses)
520	::ffff:129.144.52.38
521
522The text representation of IPv6 addresses and prefixes in Augmented BNF (Backus-Naur Form) [ABNF] for reference purposes.
523[ABNF http://tools.ietf.org/html/rfc2234]
524      IPv6address = hexpart [ ":" IPv4address ]
525      IPv4address = 1*3DIGIT "." 1*3DIGIT "." 1*3DIGIT "." 1*3DIGIT
526
527      IPv6prefix  = hexpart "/" 1*2DIGIT
528
529      hexpart = hexseq | hexseq "::" [ hexseq ] | "::" [ hexseq ]
530      hexseq  = hex4 *( ":" hex4)
531      hex4    = 1*4HEXDIG
532*/
533Spry.Widget.ValidationTextField.validateIP = function (value, format)
534{
535	var validIPv6Addresses = [
536		//preferred
537		/^(?:[a-f0-9]{1,4}:){7}[a-f0-9]{1,4}(?:\/\d{1,3})?$/i,
538
539		//various compressed
540		/^[a-f0-9]{0,4}::(?:\/\d{1,3})?$/i,
541		/^:(?::[a-f0-9]{1,4}){1,6}(?:\/\d{1,3})?$/i,
542		/^(?:[a-f0-9]{1,4}:){1,6}:(?:\/\d{1,3})?$/i,
543		/^(?:[a-f0-9]{1,4}:)(?::[a-f0-9]{1,4}){1,6}(?:\/\d{1,3})?$/i,
544		/^(?:[a-f0-9]{1,4}:){2}(?::[a-f0-9]{1,4}){1,5}(?:\/\d{1,3})?$/i,
545		/^(?:[a-f0-9]{1,4}:){3}(?::[a-f0-9]{1,4}){1,4}(?:\/\d{1,3})?$/i,
546		/^(?:[a-f0-9]{1,4}:){4}(?::[a-f0-9]{1,4}){1,3}(?:\/\d{1,3})?$/i,
547		/^(?:[a-f0-9]{1,4}:){5}(?::[a-f0-9]{1,4}){1,2}(?:\/\d{1,3})?$/i,
548		/^(?:[a-f0-9]{1,4}:){6}(?::[a-f0-9]{1,4})(?:\/\d{1,3})?$/i,
549
550
551		//IPv6 mixes with IPv4
552		/^(?:[a-f0-9]{1,4}:){6}(?:\d{1,3}\.){3}\d{1,3}(?:\/\d{1,3})?$/i,
553		/^:(?::[a-f0-9]{1,4}){0,4}:(?:\d{1,3}\.){3}\d{1,3}(?:\/\d{1,3})?$/i,
554		/^(?:[a-f0-9]{1,4}:){1,5}:(?:\d{1,3}\.){3}\d{1,3}(?:\/\d{1,3})?$/i,
555		/^(?:[a-f0-9]{1,4}:)(?::[a-f0-9]{1,4}){1,4}:(?:\d{1,3}\.){3}\d{1,3}(?:\/\d{1,3})?$/i,
556		/^(?:[a-f0-9]{1,4}:){2}(?::[a-f0-9]{1,4}){1,3}:(?:\d{1,3}\.){3}\d{1,3}(?:\/\d{1,3})?$/i,	
557		/^(?:[a-f0-9]{1,4}:){3}(?::[a-f0-9]{1,4}){1,2}:(?:\d{1,3}\.){3}\d{1,3}(?:\/\d{1,3})?$/i,
558		/^(?:[a-f0-9]{1,4}:){4}(?::[a-f0-9]{1,4}):(?:\d{1,3}\.){3}\d{1,3}(?:\/\d{1,3})?$/i
559	];
560	var validIPv4Addresses = [
561		//IPv4
562		/^(\d{1,3}\.){3}\d{1,3}$/i
563	];
564	var validAddresses = [];
565	if (format == 'ipv6' || format == 'ipv6_ipv4') {
566		validAddresses = validAddresses.concat(validIPv6Addresses);
567	}
568	if (format == 'ipv4' || format == 'ipv6_ipv4') {
569		validAddresses = validAddresses.concat(validIPv4Addresses);
570	}
571
572	var ret = false;
573	for (var i=0; i<validAddresses.length; i++) {
574		if (validAddresses[i].test(value)) {
575			ret = true;
576			break;
577		}
578	}
579
580	if (ret && value.indexOf(".") != -1) {
581		//if address contains IPv4 fragment, it must be valid; all 4 groups must be less than 256
582		var ipv4 = value.match(/:?(?:\d{1,3}\.){3}\d{1,3}/i);
583		if(!ipv4) {
584			return false;
585		}
586		ipv4 = ipv4[0].replace(/^:/, '');
587		var pieces = ipv4.split('.');
588		if (pieces.length != 4) {
589			return false;
590		}
591		var regExp = /^[\-\+]?\d*$/;
592		for (var i=0; i< pieces.length; i++) {
593			if (pieces[i] == '') {
594				return false;
595			}
596			var piece = parseInt(pieces[i], 10);
597			if (isNaN(piece) || piece > 255 || !regExp.test(pieces[i]) || pieces[i].length>3 || /^0{2,3}$/.test(pieces[i])) {
598				return false;
599			}
600		}
601	}
602	if (ret && value.indexOf("/") != -1) {
603		// if prefix-length is specified must be in [1-128]
604		var prefLen = value.match(/\/\d{1,3}$/);
605		if (!prefLen) return false;
606		var prefLenVal = parseInt(prefLen[0].replace(/^\//,''), 10);
607		if (isNaN(prefLenVal) || prefLenVal > 128 || prefLenVal < 1) {
608			return false;
609		}
610	}
611	return ret;
612};
613
614Spry.Widget.ValidationTextField.onloadDidFire = false;
615Spry.Widget.ValidationTextField.loadQueue = [];
616
617Spry.Widget.ValidationTextField.prototype.isBrowserSupported = function()
618{
619	return Spry.is.ie && Spry.is.v >= 5 && Spry.is.windows
620		||
621	Spry.is.mozilla && Spry.is.v >= 1.4
622		||
623	Spry.is.safari
624		||
625	Spry.is.opera && Spry.is.v >= 9;
626};
627
628Spry.Widget.ValidationTextField.prototype.init = function(element, options)
629{
630	this.element = this.getElement(element);
631	this.errors = 0;
632	this.flags = {locked: false, restoreSelection: true};
633	this.options = {};
634	this.event_handlers = [];
635
636	this.validClass = "textfieldValidState";
637	this.focusClass = "textfieldFocusState";
638	this.requiredClass = "textfieldRequiredState";
639	this.hintClass = "textfieldHintState";
640	this.invalidFormatClass = "textfieldInvalidFormatState";
641	this.invalidRangeMinClass = "textfieldMinValueState";
642	this.invalidRangeMaxClass = "textfieldMaxValueState";
643	this.invalidCharsMinClass = "textfieldMinCharsState";
644	this.invalidCharsMaxClass = "textfieldMaxCharsState";
645	this.textfieldFlashTextClass = "textfieldFlashText";
646	if (Spry.is.safari) {
647		this.flags.lastKeyPressedTimeStamp = 0;
648	}
649
650	switch (this.type) {
651		case 'phone_number':options.format = Spry.Widget.Utils.firstValid(options.format, 'phone_us');break;
652		case 'currency':options.format = Spry.Widget.Utils.firstValid(options.format, 'comma_dot');break;
653		case 'zip_code':options.format = Spry.Widget.Utils.firstValid(options.format, 'zip_us5');break;
654		case 'date':
655			options.format = Spry.Widget.Utils.firstValid(options.format, 'mm/dd/yy');
656			break;
657		case 'time':
658			options.format = Spry.Widget.Utils.firstValid(options.format, 'HH:mm');
659			options.pattern = options.format.replace(/[hms]/gi, "0").replace(/TT/gi, 'AM').replace(/T/gi, 'A');
660			break;
661		case 'ip':
662			options.format = Spry.Widget.Utils.firstValid(options.format, 'ipv4');
663			options.characterMasking = Spry.Widget.ValidationTextField.ValidationDescriptors[this.type].characterMaskingFormats[options.format]; 
664			break;
665	}
666
667	//retrieve the validation type descriptor to be used with this instance (base on type and format)
668	//widgets may have different validations depending on format (like zip_code with formats)
669	var validationDescriptor = {};
670	if (options.format && Spry.Widget.ValidationTextField.ValidationDescriptors[this.type].formats) {
671		if (Spry.Widget.ValidationTextField.ValidationDescriptors[this.type].formats[options.format]) {
672			Spry.Widget.Utils.setOptions(validationDescriptor, Spry.Widget.ValidationTextField.ValidationDescriptors[this.type].formats[options.format]);
673		}
674	} else {
675		Spry.Widget.Utils.setOptions(validationDescriptor, Spry.Widget.ValidationTextField.ValidationDescriptors[this.type]);
676	}
677
678	//set default values for some parameters which were not aspecified
679	options.useCharacterMasking = Spry.Widget.Utils.firstValid(options.useCharacterMasking, false);
680	options.hint = Spry.Widget.Utils.firstValid(options.hint, '');
681	options.isRequired = Spry.Widget.Utils.firstValid(options.isRequired, true);
682	options.additionalError = Spry.Widget.Utils.firstValid(options.additionalError, false);
683	if (options.additionalError)
684		options.additionalError = this.getElement(options.additionalError);
685
686	//set widget validation parameters
687	//get values from validation type descriptor
688	//use the user specified values, if defined
689	options.characterMasking = Spry.Widget.Utils.firstValid(options.characterMasking, validationDescriptor.characterMasking);
690	options.regExpFilter = Spry.Widget.Utils.firstValid(options.regExpFilter, validationDescriptor.regExpFilter);
691	options.pattern = Spry.Widget.Utils.firstValid(options.pattern, validationDescriptor.pattern);
692	options.validation = Spry.Widget.Utils.firstValid(options.validation, validationDescriptor.validation);
693	if (typeof options.validation == 'string') {
694		options.validation = eval(options.validation);
695	}
696
697	options.minValue = Spry.Widget.Utils.firstValid(options.minValue, validationDescriptor.minValue);
698	options.maxValue = Spry.Widget.Utils.firstValid(options.maxValue, validationDescriptor.maxValue);
699
700	options.minChars = Spry.Widget.Utils.firstValid(options.minChars, validationDescriptor.minChars);
701	options.maxChars = Spry.Widget.Utils.firstValid(options.maxChars, validationDescriptor.maxChars);
702
703	Spry.Widget.Utils.setOptions(this, options);
704	Spry.Widget.Utils.setOptions(this.options, options);
705};
706
707Spry.Widget.ValidationTextField.prototype.destroy = function() {
708	if (this.event_handlers)
709		for (var i=0; i<this.event_handlers.length; i++) {
710			Spry.Widget.Utils.removeEventListener(this.event_handlers[i][0], this.event_handlers[i][1], this.event_handlers[i][2], false);
711		}
712	try { delete this.element; } catch(err) {}
713	try { delete this.input; } catch(err) {}
714	try { delete this.form; } catch(err) {}
715	try { delete this.event_handlers; } catch(err) {}
716	try { this.selection.destroy(); } catch(err) {}
717	try { delete this.selection; } catch(err) {}
718
719	var q = Spry.Widget.Form.onSubmitWidgetQueue;
720	var qlen = q.length;
721	for (var i = 0; i < qlen; i++) {
722		if (q[i] == this) {
723			q.splice(i, 1);
724			break;
725		}
726	}
727};
728
729Spry.Widget.ValidationTextField.prototype.attachBehaviors = function()
730{
731	if (this.element) {
732		if (this.element.nodeName == "INPUT") {
733			this.input = this.element;
734		} else {
735			this.input = Spry.Widget.Utils.getFirstChildWithNodeNameAtAnyLevel(this.element, "INPUT");
736		}
737	}
738
739	if (this.input) {
740		if (this.maxChars) {
741			this.input.removeAttribute("maxLength");
742		}
743		this.putHint();
744		this.compilePattern();
745		if (this.type == 'date') {
746			this.compileDatePattern();
747		}
748		this.input.setAttribute("AutoComplete", "off");
749		this.selection = new Spry.Widget.SelectionDescriptor(this.input);
750		this.oldValue = this.input.value;
751
752		var self = this;
753		this.event_handlers = [];
754
755		this.event_handlers.push([this.input, "keydown", function(e) { if (self.isDisabled()) return true; return self.onKeyDown(e || event); }]);
756		this.event_handlers.push([this.input, "keypress", function(e) { if (self.isDisabled()) return true; return self.onKeyPress(e || event); }]);
757		if (Spry.is.opera) {
758			this.event_handlers.push([this.input, "keyup", function(e) { if (self.isDisabled()) return true; return self.onKeyUp(e || event); }]);
759		}
760
761		this.event_handlers.push([this.input, "focus", function(e) { if (self.isDisabled()) return true; return self.onFocus(e || event); }]);
762		this.event_handlers.push([this.input, "blur", function(e) { if (self.isDisabled()) return true; return self.onBlur(e || event); }]);
763
764		this.event_handlers.push([this.input, "mousedown", function(e) { if (self.isDisabled()) return true; return self.onMouseDown(e || event); }]);
765
766		var changeEvent = 
767			Spry.is.mozilla || Spry.is.opera || Spry.is.safari?"input":
768			Spry.is.ie?"propertychange":
769			"change";
770		this.event_handlers.push([this.input, changeEvent, function(e) { if (self.isDisabled()) return true; return self.onChange(e || event); }]);
771
772		if (Spry.is.mozilla || Spry.is.safari) {
773			//oninput event on mozilla does not fire ondragdrop
774			this.event_handlers.push([this.input, "dragdrop", function(e) { if (self.isDisabled()) return true; self.removeHint();return self.onChange(e || event); }]);
775		} else if (Spry.is.ie){
776			//ondrop&onpropertychange crash on IE 
777			this.event_handlers.push([this.input, "drop", function(e) { if (self.isDisabled()) return true; return self.onDrop(e || event); }]);
778		}
779
780		for (var i=0; i<this.event_handlers.length; i++) {
781			Spry.Widget.Utils.addEventListener(this.event_handlers[i][0], this.event_handlers[i][1], this.event_handlers[i][2], false);
782		}
783
784		// submit
785		this.form = Spry.Widget.Utils.getFirstParentWithNodeName(this.input, "FORM");
786		if (this.form) {
787			// if no "onSubmit" handler has been attached to the current form, attach one
788			if (!this.form.attachedSubmitHandler && !this.form.onsubmit) {
789				this.form.onsubmit = function(e) { e = e || event; return Spry.Widget.Form.onSubmit(e, e.srcElement || e.currentTarget) };
790				this.form.attachedSubmitHandler = true;                 
791			}
792			if (!this.form.attachedResetHandler) {
793				Spry.Widget.Utils.addEventListener(this.form, "reset", function(e) { e = e || event; return Spry.Widget.Form.onReset(e, e.srcElement || e.currentTarget) }, false);
794				this.form.attachedResetHandler = true;                 
795			}
796			// add the currrent widget to the "onSubmit" check queue;
797			Spry.Widget.Form.onSubmitWidgetQueue.push(this);
798		}
799	}	
800};
801
802Spry.Widget.ValidationTextField.prototype.isDisabled = function() {
803	return this.input && (this.input.disabled || this.input.readOnly) || !this.input;
804};
805
806Spry.Widget.ValidationTextField.prototype.getElement = function(ele)
807{
808	if (ele && typeof ele == "string")
809		return document.getElementById(ele);
810	return ele;
811};
812
813Spry.Widget.ValidationTextField.addLoadListener = function(handler)
814{
815	if (typeof window.addEventListener != 'undefined')
816		window.addEventListener('load', handler, false);
817	else if (typeof document.addEventListener != 'undefined')
818		document.addEventListener('load', handler, false);
819	else if (typeof window.attachEvent != 'undefined')
820		window.attachEvent('onload', handler);
821};
822
823Spry.Widget.ValidationTextField.processLoadQueue = function(handler)
824{
825	Spry.Widget.ValidationTextField.onloadDidFire = true;
826	var q = Spry.Widget.ValidationTextField.loadQueue;
827	var qlen = q.length;
828	for (var i = 0; i < qlen; i++)
829		q[i].attachBehaviors();
830};
831
832Spry.Widget.ValidationTextField.addLoadListener(Spry.Widget.ValidationTextField.processLoadQueue);
833Spry.Widget.ValidationTextField.addLoadListener(function(){
834	Spry.Widget.Utils.addEventListener(window, "unload", Spry.Widget.Form.destroyAll, false);
835});
836
837Spry.Widget.ValidationTextField.prototype.setValue = function(newValue) {
838	this.flags.locked = true;
839	this.input.value = newValue;
840	this.flags.locked = false;
841	this.oldValue = newValue;
842	if (!Spry.is.ie) {
843		this.onChange();
844	}
845};
846
847/**
848 * save the state of the input (selection and value) so we can revert to it
849 * should call this just before modifying the input value
850 */
851Spry.Widget.ValidationTextField.prototype.saveState = function()
852{
853	this.oldValue = this.input.value;
854	this.selection.update();
855};
856
857Spry.Widget.ValidationTextField.prototype.revertState = function(revertValue)
858{
859	if (revertValue != this.input.value) {
860		this.input.readOnly = true;
861		this.input.value = revertValue;
862		this.input.readOnly = false;
863		if (Spry.is.safari && this.flags.active) {
864			this.input.focus();
865		}
866	}
867  if (this.flags.restoreSelection) {
868	this.selection.moveTo(this.selection.start, this.selection.end);
869  }
870
871	this.redTextFlash();
872};
873
874Spry.Widget.ValidationTextField.prototype.removeHint = function()
875{
876	if (this.flags.hintOn) {
877		this.input.value = "";
878		this.flags.hintOn = false;
879		this.removeClassName(this.element, this.hintClass);
880		this.removeClassName(this.additionalError, this.hintClass);
881	}
882};
883
884Spry.Widget.ValidationTextField.prototype.putHint = function()
885{
886	if(this.hint && this.input && this.input.type == "text" && this.input.value == "") {
887		this.flags.hintOn = true;
888		this.input.value = this.hint;
889		this.addClassName(this.element, this.hintClass);
890		this.addClassName(this.additionalError, this.hintClass);
891	}
892};
893
894Spry.Widget.ValidationTextField.prototype.redTextFlash = function()
895{
896	var self = this;
897	this.addClassName(this.element, this.textfieldFlashTextClass);
898	setTimeout(function() {
899		self.removeClassName(self.element, self.textfieldFlashTextClass)
900	}, 100);
901};
902
903Spry.Widget.ValidationTextField.prototype.doValidations = function(testValue, revertValue)
904{
905	if (this.isDisabled()) return false;
906
907	if (this.flags.locked) {
908		return false;
909	}
910
911	if (testValue.length == 0 && !this.isRequired) {
912		this.errors = 0;
913		return false;
914	}
915	this.flags.locked = true;
916
917	var mustRevert = false;
918	var continueValidations = true;
919	if (!this.options.isRequired && testValue.length == 0) {
920		continueValidations = false;
921	}
922
923	var errors = 0;
924	var fixedValue = testValue;
925
926	//characterMasking - test if all characters are valid with the characterMasking (keyboard filter)
927	if (this.useCharacterMasking && this.characterMasking) {
928		for(var i=0; i<testValue.length; i++) {
929			if (!this.characterMasking.test(testValue.charAt(i))) {
930				errors = errors | Spry.Widget.ValidationTextField.ERROR_FORMAT;
931				fixedValue = revertValue;
932				mustRevert = true;
933				break;
934			}
935		}
936	}
937
938	//regExpFilter - character mask positioning (additional mask to restrict some characters only in some position)
939	if (!mustRevert && this.useCharacterMasking && this.regExpFilter) {
940		if (!this.regExpFilter.test(fixedValue)) {
941			errors = errors | Spry.Widget.ValidationTextField.ERROR_FORMAT;
942			mustRevert = true;
943		}
944	}
945
946	//pattern - testValue matches the pattern so far
947	if (!mustRevert && this.pattern) {
948		var currentRegExp = this.patternToRegExp(testValue.length);
949		if (!currentRegExp.test(testValue)) {
950			errors = errors | Spry.Widget.ValidationTextField.ERROR_FORMAT;
951			mustRevert = true;
952		} else if (this.patternLength != testValue.length) {
953			//testValue matches pattern so far, but it's not ok if it does not have the proper length
954			//do not revert, but should show the error
955			errors = errors | Spry.Widget.ValidationTextField.ERROR_FORMAT;
956		}
957	}
958
959	if (fixedValue == '') {
960		errors = errors | Spry.Widget.ValidationTextField.ERROR_REQUIRED;
961	}
962
963	if (!mustRevert && this.pattern && this.useCharacterMasking) {
964		var n = this.getAutoComplete(testValue.length);
965		if (n) {
966			fixedValue += n;
967		}
968	}
969
970	if(!mustRevert && this.minChars !== null  && continueValidations) {
971		if (testValue.length < this.minChars) {
972			errors = errors | Spry.Widget.ValidationTextField.ERROR_CHARS_MIN;
973			continueValidations = false;
974		}
975	}
976
977	if(!mustRevert && this.maxChars !== null && continueValidations) {
978		if (testValue.length > this.maxChars) {
979			errors = errors | Spry.Widget.ValidationTextField.ERROR_CHARS_MAX;
980			continueValidations = false;
981		}
982	}
983
984	//validation - testValue passes widget validation function
985	if (!mustRevert && this.validation && continueValidations) {
986		var value = this.validation(fixedValue, this.options);
987		if (false === value) {
988			errors = errors | Spry.Widget.ValidationTextField.ERROR_FORMAT;
989			continueValidations = false;
990		} else {
991			this.typedValue = value;
992		}
993	}
994
995	if(!mustRevert && this.validation && this.minValue !== null && continueValidations) {
996		var minValue = this.validation(this.minValue.toString(), this.options);
997		if (minValue !== false) {
998			if (this.typedValue < minValue) {
999				errors = errors | Spry.Widget.ValidationTextField.ERROR_RANGE_MIN;
1000				continueValidations = false;
1001			}
1002		}
1003	}
1004
1005	if(!mustRevert && this.validation && this.maxValue !== null && continueValidations) {
1006		var maxValue = this.validation(this.maxValue.toString(), this.options);
1007		if (maxValue !== false) {
1008			if( this.typedValue > maxValue) {
1009				errors = errors | Spry.Widget.ValidationTextField.ERROR_RANGE_MAX;
1010				continueValidations = false;
1011			}
1012		}
1013	}
1014
1015	//an invalid value was tested; must make sure it does not get inside the input
1016	if (this.useCharacterMasking && mustRevert) {
1017		this.revertState(revertValue);
1018	}
1019
1020	this.errors = errors;
1021	this.fixedValue = fixedValue;
1022
1023	this.flags.locked = false;
1024
1025	return mustRevert;
1026};
1027
1028Spry.Widget.ValidationTextField.prototype.onChange = function(e)
1029{
1030	if (Spry.is.opera && this.flags.operaRevertOnKeyUp) {
1031		return true;
1032	}
1033	if (Spry.is.ie && e && e.propertyName != 'value') {
1034		return true;
1035	}
1036
1037	if (this.flags.drop) {
1038		//delay this if it's a drop operation
1039		var self = this;
1040		setTimeout(function() {
1041			self.flags.drop = false;
1042			self.onChange(null);
1043		}, 0);
1044		return;
1045	}
1046
1047	if (this.flags.hintOn) {
1048		return true;
1049	}
1050
1051	if (this.keyCode == 8 || this.keyCode == 46 ) {
1052		var mustRevert = this.doValidations(this.input.value, this.input.value);
1053		this.oldValue = this.input.value;
1054		if ((mustRevert || this.errors) && this.validateOn & Spry.Widget.ValidationTextField.ONCHANGE) {
1055			var self = this;
1056			setTimeout(function() {self.validate();}, 0);
1057			return true;
1058		}
1059	}
1060
1061	var mustRevert = this.doValidations(this.input.value, this.oldValue);
1062	if ((!mustRevert || this.errors) && this.validateOn & Spry.Widget.ValidationTextField.ONCHANGE) {
1063		var self = this;
1064		setTimeout(function() {self.validate();}, 0);
1065	}
1066	return true;
1067};
1068
1069Spry.Widget.ValidationTextField.prototype.onKeyUp = function(e) {
1070	if (this.flags.operaRevertOnKeyUp) {
1071		this.setValue(this.oldValue);
1072		Spry.Widget.Utils.stopEvent(e);
1073		this.selection.moveTo(this.selection.start, this.selection.start);
1074		this.flags.operaRevertOnKeyUp = false;
1075		return false;
1076	}
1077	if (this.flags.operaPasteOperation) {
1078		window.clearInterval(this.flags.operaPasteOperation);
1079		this.flags.operaPasteOperation = null;
1080	}
1081};
1082
1083Spry.Widget.ValidationTextField.prototype.operaPasteMonitor = function() {
1084	if (this.input.value != this.oldValue) {
1085		var mustRevert = this.doValidations(this.input.value, this.input.value);
1086		if (mustRevert) {
1087			this.setValue(this.oldValue);
1088			this.selection.moveTo(this.selection.start, this.selection.start);
1089		} else {
1090			this.onChange();
1091		}
1092	}
1093};
1094
1095
1096Spry.Widget.ValidationTextField.prototype.compileDatePattern = function () 
1097{
1098	var dateValidationPatternString = "";
1099	var groupPatterns = [];
1100	var fullGroupPatterns = [];
1101	var autocompleteCharacters = [];
1102	
1103	
1104	var formatRegExp = /^([mdy]+)([\.\-\/\\\s]+)([mdy]+)([\.\-\/\\\s]+)([mdy]+)$/i;
1105	var formatGroups = this.options.format.match(formatRegExp);
1106	if (formatGroups !== null) {
1107		for (var i=1; i<formatGroups.length; i++) {
1108			switch (formatGroups[i].toLowerCase()) {
1109				case "dd":
1110					groupPatterns[i-1] = "\\d{1,2}";
1111					fullGroupPatterns[i-1] = "\\d\\d";
1112					dateValidationPatternString += "(" + groupPatterns[i-1] + ")";
1113					autocompleteCharacters[i-1] = null;
1114					break;
1115				case "mm":
1116					groupPatterns[i-1] = "\\d{1,2}";
1117					fullGroupPatterns[i-1] = "\\d\\d";
1118					dateValidationPatternString += "(" + groupPatterns[i-1] + ")";
1119					autocompleteCharacters[i-1] = null;
1120					break;
1121				case "yy":
1122					groupPatterns[i-1] = "\\d{1,2}";
1123					fullGroupPatterns[i-1] = "\\d\\d";
1124					dateValidationPatternString += "(\\d\\d)";
1125					autocompleteCharacters[i-1] = null;
1126					break;
1127				case "yyyy":
1128					groupPatterns[i-1] = "\\d{1,4}";
1129					fullGroupPatterns[i-1] = "\\d\\d\\d\\d";
1130					dateValidationPatternString += "(\\d\\d\\d\\d)";
1131					autocompleteCharacters[i-1] = null;
1132					break;
1133				default:
1134					groupPatterns[i-1] = fullGroupPatterns[i-1] = Spry.Widget.ValidationTextField.regExpFromChars(formatGroups[i]);
1135					dateValidationPatternString += "["+ groupPatterns[i-1] + "]";
1136					autocompleteCharacters[i-1] = formatGroups[i];
1137			}
1138		}
1139	}
1140	this.dateValidationPattern = new RegExp("^" + dateValidationPatternString + "$" , "");
1141	this.dateAutocompleteCharacters = autocompleteCharacters;
1142	this.dateGroupPatterns = groupPatterns;
1143	this.dateFullGroupPatterns = fullGroupPatterns;
1144	this.lastDateGroup = formatGroups.length-2;
1145};
1146
1147Spry.Widget.ValidationTextField.prototype.getRegExpForGroup = function (group) 
1148{
1149	var ret = '^';
1150	for (var j = 0; j <= group; j++) ret += this.dateGroupPatterns[j];
1151	ret += '$';
1152	return new RegExp(ret, "");	
1153};
1154
1155Spry.Widget.ValidationTextField.prototype.getRegExpForFullGroup = function (group) 
1156{
1157	var ret = '^';
1158	for (var j = 0; j < group; j++) ret += this.dateGroupPatterns[j];
1159	ret += this.dateFullGroupPatterns[group];
1160	return new RegExp(ret, "");	
1161};
1162
1163Spry.Widget.ValidationTextField.prototype.getDateGroup = function(value, pos) 
1164{
1165	if (pos == 0) return 0;
1166	var test_value = value.substring(0, pos);
1167	for (var i=0; i <= this.lastDateGroup; i++) 
1168		if (this.getRegExpForGroup(i).test(test_value)) return i;
1169	return -1;
1170};
1171
1172
1173Spry.Widget.ValidationTextField.prototype.isDateGroupFull = function(value, group) 
1174{
1175	return this.getRegExpForFullGroup(group).test(value);
1176};
1177
1178Spry.Widget.ValidationTextField.prototype.isValueValid = function(value, pos, group) 
1179{
1180	var test_value = value.substring(0, pos);
1181	return this.getRegExpForGroup(group).test(test_value);
1182};
1183
1184
1185Spry.Widget.ValidationTextField.prototype.isPositionAtEndOfGroup = function (value, pos, group)
1186{
1187	var test_value = value.substring(0, pos);
1188	return this.getRegExpForFullGroup(group).test(test_value);
1189};
1190
1191Spry.Widget.ValidationTextField.prototype.nextDateDelimiterExists = function (value, pos, group)
1192{
1193	var autocomplete = this.dateAutocompleteCharacters[group+1];
1194	if (value.length < pos  + autocomplete.length) 
1195		return false;
1196	else 
1197	{
1198		var test_value = value.substring(pos, pos+autocomplete.length);
1199		if (test_value == autocomplete) 
1200			return true;
1201	}
1202	return false;
1203};
1204
1205
1206
1207Spry.Widget.ValidationTextField.prototype.onKeyPress = function(e)
1208{
1209	if (this.flags.skp) {
1210		this.flags.skp = false;
1211		Spry.Widget.Utils.stopEvent(e);
1212		return false;
1213	}
1214
1215	if (e.ctrlKey || e.metaKey || !this.useCharacterMasking) {
1216		return true;
1217	}
1218/*
1219	if (Spry.is.safari) {
1220		if ( (e.timeStamp - this.flags.lastKeyPressedTimeStamp)<10 ) {
1221			return true;
1222		}
1223		this.flags.lastKeyPressedTimeStamp = e.timeStamp;
1224	}
1225*/
1226	if (Spry.is.opera && this.flags.operaRevertOnKeyUp) {
1227		Spry.Widget.Utils.stopEvent(e);
1228		return false;
1229	}
1230
1231	if (this.keyCode == 8 || this.keyCode == 46) {
1232		var mr = this.doValidations(this.input.value, this.input.value);
1233		if (mr) {
1234			return true;
1235		}
1236	}
1237
1238	var pressed = Spry.Widget.Utils.getCharacterFromEvent(e);
1239
1240	if (pressed && this.characterMasking) {
1241		if (!this.characterMasking.test(pressed)) {
1242			Spry.Widget.Utils.stopEvent(e);
1243			this.redTextFlash();
1244			return false;
1245		}
1246	}
1247
1248	if(pressed && this.pattern) {
1249		var currentPatternChar = this.patternCharacters[this.selection.start];
1250		if (/[ax]/i.test(currentPatternChar)) {
1251			//convert the entered character to the pattern character case
1252			if (currentPatternChar.toLowerCase() == currentPatternChar) {
1253				pressed = pressed.toLowerCase();
1254			} else {
1255				pressed = pressed.toUpperCase();
1256			}
1257		}
1258
1259		var autocomplete = this.getAutoComplete(this.selection.start);
1260		if (this.selection.start == this.oldValue.length) {
1261			if (this.oldValue.length < this.patternLength) {
1262				if (autocomplete) {
1263					Spry.Widget.Utils.stopEvent(e);
1264					var futureValue = this.oldValue.substring(0, this.selection.start) + autocomplete + pressed;
1265					var mustRevert = this.doValidations(futureValue, this.oldValue);
1266					if (!mustRevert) {
1267						this.setValue(this.fixedValue);
1268						this.selection.moveTo(this.fixedValue.length, this.fixedValue.length);
1269					} else {
1270						this.setValue(this.oldValue.substring(0, this.selection.start) + autocomplete);
1271						this.selection.moveTo(this.selection.start + autocomplete.length, this.selection.start + autocomplete.length);
1272					}
1273					return false;
1274				}
1275			} else {
1276				Spry.Widget.Utils.stopEvent(e);
1277				this.setValue(this.input.value);
1278				return false;
1279			}
1280		} else if (autocomplete) {
1281			Spry.Widget.Utils.stopEvent(e);
1282			this.selection.moveTo(this.selection.start + autocomplete.length, this.selection.start + autocomplete.length);
1283			return false;
1284		}
1285
1286		Spry.Widget.Utils.stopEvent(e);
1287
1288		var futureValue = this.oldValue.substring(0, this.selection.start) + pressed + this.oldValue.substring(this.selection.start + 1);
1289		var mustRevert = this.doValidations(futureValue, this.oldValue);
1290
1291		if (!mustRevert) {
1292			autocomplete = this.getAutoComplete(this.selection.start + 1);
1293			this.setValue(this.fixedValue);
1294			this.selection.moveTo(this.selection.start + 1 + autocomplete.length, this.selection.start + 1 + autocomplete.length);
1295		} else {
1296			this.selection.moveTo(this.selection.start, this.selection.start);
1297		}
1298
1299		return false;
1300	}
1301	
1302	
1303	if (pressed && this.type == 'date' && this.useCharacterMasking) 
1304	{
1305		var group = this.getDateGroup(this.oldValue, this.selection.start);
1306		if (group != -1) {
1307			Spry.Widget.Utils.stopEvent(e);
1308			if ( (group % 2) !=0 ) 
1309				group ++;
1310			
1311			if (this.isDateGroupFull(this.oldValue, group)) 
1312			{
1313				if(this.isPositionAtEndOfGroup(this.oldValue, this.selection.start, group))
1314				{
1315					if(group == this.lastDateGroup) 
1316					{
1317						this.redTextFlash(); return false;
1318					}
1319					else 
1320					{
1321						// add or jump over autocomplete delimiter
1322						var autocomplete = this.dateAutocompleteCharacters[group+1];
1323						
1324						if (this.nextDateDelimiterExists(this.oldValue, this.selection.start, group))
1325						{
1326							var autocomplete = this.dateAutocompleteCharacters[group+1];
1327							
1328							this.selection.moveTo(this.selection.start + autocomplete.length, this.selection.start + autocomplete.length);
1329							if (pressed == autocomplete) 
1330								return false;
1331							
1332							if (this.isDateGroupFull(this.oldValue, group+2)) 
1333								// need to overwrite first char in the next digit group
1334								futureValue = this.oldValue.substring(0, this.selection.start) + pressed + this.oldValue.substring(this.selection.start + 1);
1335							else
1336								futureValue = this.oldValue.substring(0, this.selection.start) + pressed + this.oldValue.substring(this.selection.start);
1337								
1338							if (!this.isValueValid(futureValue, this.selection.start + 1, group +2 )) 
1339							{
1340								this.redTextFlash(); return false;						
1341							}
1342							else
1343							{
1344								this.setValue (futureValue);
1345								this.selection.moveTo(this.selection.start + 1, this.selection.start + 1);									
1346							}
1347							return false;					
1348						}
1349						else 
1350						{
1351							var autocomplete = this.dateAutocompleteCharacters[group+1];
1352							
1353							var insertedValue = autocomplete + pressed;
1354							futureValue = this.oldValue.substring(0, this.selection.start) + insertedValue + this.oldValue.substring(this.selection.start);
1355							if (!this.isValueValid(futureValue, this.selection.start + insertedValue.length, group +2 )) 
1356							{
1357								// block this type
1358								insertedValue = autocomplete;
1359								futureValue = this.oldValue.substring(0, this.selection.start) + insertedValue + this.oldValue.substring(this.selection.start);
1360								this.setValue (futureValue);
1361								this.selection.moveTo(this.selection.start + insertedValue.length, this.selection.start + insertedValue.length);									
1362								this.redTextFlash(); return false;
1363							}
1364							else 
1365							{
1366								this.setValue (futureValue);
1367								this.selection.moveTo(this.selection.start + insertedValue.length, this.selection.start + insertedValue.length);									
1368								return false;
1369							}
1370						}
1371						
1372					}
1373				}
1374				else
1375				{
1376					// it's not the end of the full digits group
1377					
1378					// overwrite
1379					var movePosition = 1;
1380					futureValue = this.oldValue.substring(0, this.selection.start) + pressed + this.oldValue.substring(this.selection.start + 1);
1381					if (!this.isValueValid(futureValue, this.selection.start + 1, group)) 
1382					{
1383						this.redTextFlash(); return false;
1384					}
1385					else 
1386					{
1387						if(this.isPositionAtEndOfGroup(futureValue, this.selection.start+1, group)) 
1388						{
1389							if (group != this.lastDateGroup)
1390							{
1391								if (this.nextDateDelimiterExists(futureValue, this.selection.start + 1, group))
1392								{
1393									var autocomplete = this.dateAutocompleteCharacters[group+1];
1394									movePosition = 1 + autocomplete.length;
1395								}
1396								else
1397								{
1398									var autocomplete = this.dateAutocompleteCharacters[group+1];
1399									futureValue = this.oldValue.substring(0, this.selection.start) + pressed + autocomplete + this.oldValue.substring(this.selection.start + 1);
1400									movePosition = 1 + autocomplete.length;
1401								}
1402							}
1403						}
1404						this.setValue (futureValue);
1405						this.selection.moveTo(this.selection.start + movePosition, this.selection.start + movePosition);									
1406						return false;							
1407					}			
1408				}
1409			}
1410			else
1411			{
1412				// date group is not full
1413				// insert
1414				futureValue = this.oldValue.substring(0, this.selection.start) + pressed + this.oldValue.substring(this.selection.start);
1415				var movePosition = 1;
1416				if (!this.isValueValid(futureValue, this.selection.start + 1, group) && !this.isValueValid(futureValue, this.selection.start + 1, group+1)) 
1417				{
1418					this.redTextFlash(); return false;
1419				}
1420				else 
1421				{
1422					var autocomplete = this.dateAutocompleteCharacters[group+1];
1423					if (pressed == autocomplete) 
1424					{
1425						if (this.nextDateDelimiterExists(this.oldValue, this.selection.start, group))
1426						{
1427							futureValue = this.oldValue;
1428							movePosition = 1;
1429						}
1430					}
1431					else
1432					{
1433						if(this.isPositionAtEndOfGroup(futureValue, this.selection.start+1, group)) 
1434						{
1435							if (group != this.lastDateGroup)
1436							{
1437								if (this.nextDateDelimiterExists(futureValue, this.selection.start + 1, group))
1438								{
1439									var autocomplete = this.dateAutocompleteCharacters[group+1];
1440									movePosition = 1 + autocomplete.length;
1441								}
1442								else
1443								{
1444									var autocomplete = this.dateAutocompleteCharacters[group+1];
1445									futureValue = this.oldValue.substring(0, this.selection.start) + pressed + autocomplete + this.oldValue.substring(this.selection.start + 1);
1446									movePosition = 1 + autocomplete.length;
1447								}
1448							}
1449						}
1450					}
1451					this.setValue (futureValue);
1452					this.selection.moveTo(this.selection.start + movePosition, this.selection.start + movePosition);									
1453					return false;						
1454				}	
1455			}
1456		}
1457		return false;
1458	}
1459	
1460};
1461
1462Spry.Widget.ValidationTextField.prototype.onKeyDown = function(e)
1463{
1464	this.saveState();
1465	this.keyCode = e.keyCode;
1466
1467	if (Spry.is.opera) {
1468		if (this.flags.operaPasteOperation) {
1469			window.clearInterval(this.flags.operaPasteOperation);
1470			this.flags.operaPasteOperation = null;
1471		}
1472		if (e.ctrlKey) {
1473			var pressed = Spry.Widget.Utils.getCharacterFromEvent(e);
1474			if (pressed && 'vx'.indexOf(pressed.toLowerCase()) != -1) {
1475				var self = this;
1476				this.flags.operaPasteOperation = window.setInterval(function() { self.operaPasteMonitor();}, 1);
1477				return true;
1478			}
1479		}
1480	}
1481
1482	if (this.keyCode != 8 && this.keyCode != 46 && Spry.Widget.Utils.isSpecialKey(e)) {
1483		return true;
1484	}
1485	if (this.keyCode == 8 || this.keyCode == 46 ) {
1486		var mr = this.doValidations(this.input.value, this.input.value);
1487		if (mr) {
1488			return true;
1489		}
1490	}
1491
1492	//DELETE
1493	if (this.useCharacterMasking && this.pattern && this.keyCode == 46) {
1494		if (e.ctrlKey) {
1495			//delete from selection until end
1496			this.setValue(this.input.value.substring(0, this.selection.start));
1497		} else if (this.selection.end == this.input.value.length || this.selection.start == this.input.value.length-1){
1498			//allow key if selection is at end (will delete selection)
1499			return true;
1500		} else {
1501			this.flags.operaRevertOnKeyUp = true;
1502		}
1503		if (Spry.is.mozilla && Spry.is.mac) {
1504			this.flags.skp = true;
1505		}
1506		Spry.Widget.Utils.stopEvent(e);
1507		return false;
1508	}
1509
1510	//BACKSPACE
1511	if (this.useCharacterMasking && this.pattern && !e.ctrlKey && this.keyCode == 8) {
1512		if (this.selection.start == this.input.value.length) {
1513			//delete with BACKSPACE from the end of the input value only
1514			var n = this.getAutoComplete(this.selection.start, -1);
1515			this.setValue(this.input.value.substring(0, this.input.value.length - (Spry.is.opera?0:1) - n.length));
1516			if (Spry.is.opera) {
1517				//cant stop the event on Opera, we'll just preserve the selection so delete will act on it
1518				this.selection.start = this.selection.start - 1 - n.length;
1519				this.selection.end = this.selection.end - 1 - n.length;
1520			}
1521		} else if (this.selection.end == this.input.value.length){
1522			//allow BACKSPACE if selection is at end (will delete selection)
1523			return true;
1524		} else {
1525			this.flags.operaRevertOnKeyUp = true;
1526		}
1527		if (Spry.is.mozilla && Spry.is.mac) {
1528			this.flags.skp = true;
1529		} 
1530		Spry.Widget.Utils.stopEvent(e);
1531		return false;
1532	}
1533
1534	return true;
1535};
1536
1537Spry.Widget.ValidationTextField.prototype.onMouseDown = function(e)
1538{
1539	if (this.flags.active) {
1540		//mousedown fires before focus
1541		//avoid double saveState on first focus by mousedown by checking if the control has focus
1542		//do nothing if it's not focused because saveState will be called onfocus
1543		this.saveState();
1544	}
1545};
1546
1547Spry.Widget.ValidationTextField.prototype.onDrop = function(e)
1548{
1549	//mark that a drop operation is in progress to avoid race conditions with event handlers for other events
1550	//especially onchange and onfocus
1551	this.flags.drop = true;
1552	this.removeHint();
1553	this.saveState();
1554	this.flags.active = true;
1555	this.addClassName(this.element, this.focusClass);
1556	this.addClassName(this.additionalError, this.focusClass);
1557};
1558
1559Spry.Widget.ValidationTextField.prototype.onFocus = function(e)
1560{
1561	if (this.flags.drop) {
1562		return;
1563	}
1564	this.removeHint();
1565
1566	if (this.pattern && this.useCharacterMasking) {
1567		var autocomplete = this.getAutoComplete(this.selection.start);
1568		this.setValue(this.input.value + autocomplete);
1569		this.selection.moveTo(this.input.value.length, this.input.value.length);
1570	}
1571	
1572	this.saveState();
1573	this.flags.active = true;
1574	this.addClassName(this.element, this.focusClass);
1575	this.addClassName(this.additionalError, this.focusClass);
1576};
1577	
1578Spry.Widget.ValidationTextField.prototype.onBlur = function(e)
1579{
1580	this.flags.active = false;
1581	this.removeClassName(this.element, this.focusClass);
1582	this.removeClassName(this.additionalError, this.focusClass);
1583	this.flags.restoreSelection = false;
1584	var mustRevert = this.doValidations(this.input.value, this.input.value);
1585	this.flags.restoreSelection = true;
1586
1587	if (this.validateOn & Spry.Widget.ValidationTextField.ONBLUR) {
1588		this.validate();
1589	}
1590	var self = this;
1591	setTimeout(function() {self.putHint();}, 10);
1592	return true;
1593};
1594
1595Spry.Widget.ValidationTextField.prototype.compilePattern = function() {
1596	if (!this.pattern) {
1597		return;
1598	}
1599	var compiled = [];
1600	var regexps = [];
1601	var patternCharacters = [];
1602	var idx = 0;
1603	var c = '', p = '';
1604	for (var i=0; i<this.pattern.length; i++) {
1605		c = this.pattern.charAt(i);
1606		if (p == '\\') {
1607			if (/[0ABXY\?]/i.test(c)) {
1608				regexps[idx - 1] = c;
1609			} else {
1610				regexps[idx - 1] = Spry.Widget.ValidationTextField.regExpFromChars(c);
1611			}
1612			compiled[idx - 1] = c;
1613			patternCharacters[idx - 1] = null;
1614			p = '';
1615			continue;
1616		}
1617		regexps[idx] = Spry.Widget.ValidationTextField.regExpFromChars(c);
1618		if (/[0ABXY\?]/i.test(c)) {
1619			compiled[idx] = null;
1620			patternCharacters[idx] = c;
1621		} else if (c == '\\') {
1622			compiled[idx] = c;
1623			patternCharacters[idx] = '\\';
1624		} else {
1625			compiled[idx] = c;
1626			patternCharacters[idx] = null;
1627		}
1628		idx++;
1629		p = c;
1630	}
1631
1632	this.autoCompleteCharacters = compiled;
1633	this.compiledPattern = regexps;
1634	this.patternCharacters = patternCharacters;
1635	this.patternLength = compiled.length;
1636};
1637
1638Spry.Widget.ValidationTextField.prototype.getAutoComplete = function(from, direction) {
1639	if (direction == -1) {
1640		var n = '', m = '';
1641		while(from && (n = this.getAutoComplete(--from) )) {
1642			m = n;
1643		}
1644		return m;
1645	}
1646	var ret = '', c = '';
1647	for (var i=from; i<this.autoCompleteCharacters.length; i++) {
1648		c = this.autoCompleteCharacters[i];
1649		if (c) {
1650			ret += c;
1651		} else {
1652			break;
1653		}
1654	}
1655	return ret;
1656};
1657
1658Spry.Widget.ValidationTextField.regExpFromChars = function (string) {
1659	//string contains pattern characters
1660	var ret = '', character = '';
1661	for (var i = 0; i<string.length; i++) {
1662		character = string.charAt(i);
1663		switch (character) {
1664			case '0': ret += '\\d';break;
1665			case 'A': ret += '[A-Z]';break;
1666//			case 'A': ret += '[\u0041-\u005A\u0061-\u007A\u0100-\u017E\u0180-\u0233\u0391-\u03CE\u0410-\u044F\u05D0-\u05EA\u0621-\u063A\u0641-\u064A\u0661-\u06D3\u06F1-\u06FE]';break;
1667			case 'a': ret += '[a-z]';break;
1668//			case 'a': ret += '[\u0080-\u00FF]';break;
1669			case 'B': case 'b': ret += '[a-zA-Z]';break;
1670			case 'x': ret += '[0-9a-z]';break;
1671			case 'X': ret += '[0-9A-Z]';break;
1672			case 'Y': case 'y': ret += '[0-9a-zA-Z]';break;
1673			case '?': ret += '.';break;
1674			case '1':case '2':case '3':case '4':case '5':case '6':case '7':case '8':case '9':
1675				ret += character;
1676				break;
1677			case 'c': case 'C': case 'e': case 'E': case 'f': case 'F':case 'r':case 'd': case 'D':case 'n':case 's':case 'S':case 'w':case 'W':case 't':case 'v':
1678				ret += character;
1679				break;
1680			default: ret += '\\' + character;
1681		}
1682	}
1683	return ret;
1684};
1685
1686Spry.Widget.ValidationTextField.prototype.patternToRegExp = function(len) {
1687	var ret = '^';
1688	var end = Math.min(this.compiledPattern.length, len);
1689	for (var i=0; i < end; i++) {
1690		ret += this.compiledPattern[i];
1691	}
1692	ret += '$';
1693	ret = new RegExp(ret, "");
1694	return ret;
1695};
1696
1697Spry.Widget.ValidationTextField.prototype.resetClasses = function() {
1698	var classes = [this.requiredClass, this.invalidFormatClass, this.invalidRangeMinClass, this.invalidRangeMaxClass, this.invalidCharsMinClass, this.invalidCharsMaxClass, this.validClass];
1699	for (var i=0; i < classes.length; i++)
1700	{
1701		this.removeClassName(this.element, classes[i]);
1702		this.removeClassName(this.additionalError, classes[i]);
1703	}
1704};
1705
1706Spry.Widget.ValidationTextField.prototype.reset = function() {
1707	this.removeHint();
1708	this.oldValue = this.input.defaultValue;
1709	
1710	this.resetClasses();
1711	if (Spry.is.ie) {
1712		//this will fire the onpropertychange event right after the className changed on the container element
1713		//IE6 will not fire the first onpropertychange on an input type text after a onreset handler if inside that handler the className of one of the elements inside the form has been changed
1714		//to reproduce: change the className of one of the elements inside the form from within the onreset handler; then the onpropertychange does not fire the first time
1715		this.input.forceFireFirstOnPropertyChange = true;
1716		this.input.removeAttribute("forceFireFirstOnPropertyChange");
1717	}
1718	var self = this;
1719	setTimeout(function() {self.putHint();}, 10);
1720};
1721
1722Spry.Widget.ValidationTextField.prototype.validate = function() {
1723
1724	this.resetClasses();
1725	//possible states: required, format, rangeMin, rangeMax, charsMin, charsMax
1726	if (this.validateOn & Spry.Widget.ValidationTextField.ONSUBMIT) {
1727
1728		this.removeHint();
1729		this.doValidations(this.input.value, this.input.value);
1730
1731		if(!this.flags.active) {
1732			var self = this;
1733			setTimeout(function() {self.putHint();}, 10);
1734		}
1735	}
1736
1737	if (this.isRequired && this.errors & Spry.Widget.ValidationTextField.ERROR_REQUIRED) {
1738		this.addClassName(this.element, this.requiredClass);
1739		this.addClassName(this.additionalError, this.requiredClass);
1740		return false;
1741	}
1742
1743	if (this.errors & Spry.Widget.ValidationTextField.ERROR_FORMAT) {
1744		this.addClassName(this.element, this.invalidFormatClass);
1745		this.addClassName(this.additionalError, this.invalidFormatClass);
1746		return false;
1747	}
1748
1749	if (this.errors & Spry.Widget.ValidationTextField.ERROR_RANGE_MIN) {
1750		this.addClassName(this.element, this.invalidRangeMinClass);
1751		this.addClassName(this.additionalError, this.invalidRangeMinClass);
1752		return false;
1753	}
1754
1755	if (this.errors & Spry.Widget.ValidationTextField.ERROR_RANGE_MAX) {
1756		this.addClassName(this.element, this.invalidRangeMaxClass);
1757		this.addClassName(this.additionalError, this.invalidRangeMaxClass);
1758		return false;
1759	}
1760
1761	if (this.errors & Spry.Widget.ValidationTextField.ERROR_CHARS_MIN) {
1762		this.addClassName(this.element, this.invalidCharsMinClass);
1763		this.addClassName(this.additionalError, this.invalidCharsMinClass);
1764		return false;
1765	}
1766
1767	if (this.errors & Spry.Widget.ValidationTextField.ERROR_CHARS_MAX) {
1768		this.addClassName(this.element, this.invalidCharsMaxClass);
1769		this.addClassName(this.additionalError, this.invalidCharsMaxClass);
1770		return false;
1771	}
1772
1773	this.addClassName(this.element, this.validClass);
1774	this.addClassName(this.additionalError, this.validClass);
1775	return true;
1776};
1777
1778Spry.Widget.ValidationTextField.prototype.addClassName = function(ele, className)
1779{
1780	if (!ele || !className || (ele.className && ele.className.search(new RegExp("\\b" + className + "\\b")) != -1))
1781		return;
1782	ele.className += (ele.className ? " " : "") + className;
1783};
1784
1785Spry.Widget.ValidationTextField.prototype.removeClassName = function(ele, className)
1786{
1787	if (!ele || !className || (ele.className && ele.className.search(new RegExp("\\b" + className + "\\b")) == -1))
1788		return;
1789	ele.className = ele.className.replace(new RegExp("\\s*\\b" + className + "\\b", "g"), "");
1790};
1791Spry.Widget.ValidationTextField.prototype.showError = function(msg)
1792{
1793	alert('Spry.Widget.TextField ERR: ' + msg);
1794};
1795/**
1796 * SelectionDescriptor is a wrapper for input type text selection methods and properties 
1797 * as implemented by various  browsers
1798 */
1799Spry.Widget.SelectionDescriptor = function (element)
1800{
1801	this.element = element;
1802	this.update();
1803};
1804
1805Spry.Widget.SelectionDescriptor.prototype.update = function()
1806{
1807	if (Spry.is.ie && Spry.is.windows) {
1808		var sel = this.element.ownerDocument.selection;
1809		if (this.element.nodeName == "TEXTAREA") {
1810			if (sel.type != 'None') {
1811				try{var range = sel.createRange();}catch(err){return;}
1812				if (range.parentElement() == this.element){
1813					var range_all = this.element.ownerDocument.body.createTextRange();
1814					range_all.moveToElementText(this.element);
1815					for (var sel_start = 0; range_all.compareEndPoints('StartToStart', range) < 0; sel_start ++){
1816						range_all.moveStart('character', 1);
1817					}
1818					this.start = sel_start;
1819					// create a selection of the whole this.element
1820					range_all = this.element.ownerDocument.body.createTextRange();
1821					range_all.moveToElementText(this.element);
1822					for (var sel_end = 0; range_all.compareEndPoints('StartToEnd', range) < 0; sel_end++){
1823						range_all.moveStart('character', 1);
1824					}
1825					this.end = sel_end;
1826					this.length = this.end - this.start;
1827					// get selected and surrounding text
1828					this.text = range.text;
1829		 		}
1830			}        
1831		} else if (this.element.nodeName == "INPUT"){
1832			try{this.range = sel.createRange();}catch(err){return;}
1833			this.length = this.range.text.length;
1834			var clone = this.range.duplicate();
1835			this.start = -clone.moveStart("character", -10000);
1836			clone = this.range.duplicate();
1837			clone.collapse(false);
1838			this.end = -clone.moveStart("character", -10000);
1839			this.text = this.range.text;
1840		}
1841	} else {
1842		var tmp = this.element;
1843		var selectionStart = 0;
1844		var selectionEnd = 0;
1845        
1846		try { selectionStart = tmp.selectionStart;} catch(err) {}
1847		try { selectionEnd = tmp.selectionEnd;} catch(err) {}
1848
1849		if (Spry.is.safari) {
1850			if (selectionStart == 2147483647) {
1851				selectionStart = 0;
1852			}
1853			if (selectionEnd == 2147483647) {
1854				selectionEnd = 0;
1855			}
1856		}
1857		this.start = selectionStart;
1858		this.end = selectionEnd;
1859		this.length = selectionEnd - selectionStart;
1860		this.text = this.element.value.substring(selectionStart, selectionEnd);
1861	}
1862};
1863
1864Spry.Widget.SelectionDescriptor.prototype.destroy = function() {
1865	try { delete this.range} catch(err) {}
1866	try { delete this.element} catch(err) {}
1867};
1868
1869Spry.Widget.SelectionDescriptor.prototype.move = function(amount)
1870{
1871	if (Spry.is.ie && Spry.is.windows) {
1872		this.range.move("character", amount);
1873		this.range.select();
1874	} else {
1875		try { this.element.selectionStart++;}catch(err) {}
1876	}
1877	this.update();
1878};
1879
1880Spry.Widget.SelectionDescriptor.prototype.moveTo = function(start, end)
1881{
1882	if (Spry.is.ie && Spry.is.windows) {
1883		if (this.element.nodeName == "TEXTAREA") {
1884			var ta_range = this.element.createTextRange();
1885			this.range = this.element.createTextRange();
1886			this.range.move("character", start);
1887			this.range.moveEnd("character", end - start);
1888			
1889			var c1 = this.range.compareEndPoints("StartToStart", ta_range);
1890			if (c1 < 0) {
1891				this.range.setEndPoint("StartToStart", ta_range);
1892			}
1893
1894			var c2 = this.range.compareEndPoints("EndToEnd", ta_range);
1895			if (c2 > 0) {
1896				this.range.setEndPoint("EndToEnd", ta_range);
1897			}
1898		} else if (this.element.nodeName == "INPUT"){
1899			this.range = this.element.ownerDocument.selection.createRange();
1900			this.range.move("character", -10000);
1901			this.start = this.range.moveStart("character", start);
1902			this.end = this.start + this.range.moveEnd("character", end - start);
1903		}
1904		this.range.select();
1905	} else {
1906		this.start = start;
1907		try { this.element.selectionStart = start;} catch(err) {}
1908		this.end = end;
1909		try { this.element.selectionEnd = end;} catch(err) {}
1910	}
1911	this.ignore = true;
1912	this.update();
1913};
1914
1915Spry.Widget.SelectionDescriptor.prototype.moveEnd = function(amount)
1916{
1917	if (Spry.is.ie && Spry.is.windows) {
1918		this.range.moveEnd("character", amount);
1919		this.range.select();
1920	} else {
1921		try { this.element.selectionEnd++;} catch(err) {}
1922	}
1923	this.update();
1924};
1925
1926Spry.Widget.SelectionDescriptor.prototype.collapse = function(begin)
1927{
1928	if (Spry.is.ie && Spry.is.windows) {
1929		this.range = this.element.ownerDocument.selection.createRange();
1930		this.range.collapse(begin);
1931		this.range.select();
1932	} else {
1933		if (begin) {
1934			try { this.element.selectionEnd = this.element.selectionStart;} catch(err) {}
1935		} else {
1936			try { this.element.selectionStart = this.element.selectionEnd;} catch(err) {}
1937		}
1938	}
1939
1940	this.update();
1941};
1942
1943//////////////////////////////////////////////////////////////////////
1944//
1945// Spry.Widget.Form - common for all widgets
1946//
1947//////////////////////////////////////////////////////////////////////
1948
1949if (!Spry.Widget.Form) Spry.Widget.Form = {};
1950if (!Spry.Widget.Form.onSubmitWidgetQueue) Spry.Widget.Form.onSubmitWidgetQueue = [];
1951
1952if (!Spry.Widget.Form.validate) {
1953	Spry.Widget.Form.validate = function(vform) {
1954		var isValid = true;
1955		var isElementValid = true;
1956		var q = Spry.Widget.Form.onSubmitWidgetQueue;
1957		var qlen = q.length;
1958		for (var i = 0; i < qlen; i++) {
1959			if (!q[i].isDisabled() && q[i].form == vform) {
1960				isElementValid = q[i].validate();
1961				isValid = isElementValid && isValid;
1962			}
1963		}
1964		return isValid;
1965	}
1966};
1967
1968if (!Spry.Widget.Form.onSubmit) {
1969	Spry.Widget.Form.onSubmit = function(e, form)
1970	{
1971		if (Spry.Widget.Form.validate(form) == false) {
1972			return false;
1973		}
1974		return true;
1975	};
1976};
1977
1978if (!Spry.Widget.Form.onReset) {
1979	Spry.Widget.Form.onReset = function(e, vform)
1980	{
1981		var q = Spry.Widget.Form.onSubmitWidgetQueue;
1982		var qlen = q.length;
1983		for (var i = 0; i < qlen; i++) {
1984			if (!q[i].isDisabled() && q[i].form == vform && typeof(q[i].reset) == 'function') {
1985				q[i].reset();
1986			}
1987		}
1988		return true;
1989	};
1990};
1991
1992if (!Spry.Widget.Form.destroy) {
1993	Spry.Widget.Form.destroy = function(form)
1994	{
1995		var q = Spry.Widget.Form.onSubmitWidgetQueue;
1996		for (var i = 0; i < Spry.Widget.Form.onSubmitWidgetQueue.length; i++) {
1997			if (q[i].form == form && typeof(q[i].destroy) == 'function') {
1998				q[i].destroy();
1999				i--;
2000			}
2001		}
2002	}
2003};
2004
2005if (!Spry.Widget.Form.destroyAll) {
2006	Spry.Widget.Form.destroyAll = function()
2007	{
2008		var q = Spry.Widget.Form.onSubmitWidgetQueue;
2009		for (var i = 0; i < Spry.Widget.Form.onSubmitWidgetQueue.length; i++) {
2010			if (typeof(q[i].destroy) == 'function') {
2011				q[i].destroy();
2012				i--;
2013			}
2014		}
2015	}
2016};
2017
2018//////////////////////////////////////////////////////////////////////
2019//
2020// Spry.Widget.Utils
2021//
2022//////////////////////////////////////////////////////////////////////
2023
2024if (!Spry.Widget.Utils)	Spry.Widget.Utils = {};
2025
2026Spry.Widget.Utils.punycode_constants = {
2027	base : 36, tmin : 1, tmax : 26, skew : 38, damp : 700,
2028  initial_bias : 72, initial_n : 0x80, delimiter : 0x2D,
2029  maxint : 2<<26-1
2030};
2031
2032Spry.Widget.Utils.punycode_encode_digit = function (d) {
2033  return String.fromCharCode(d + 22 + 75 * (d < 26));
2034};
2035
2036Spry.Widget.Utils.punycode_adapt = function (delta, numpoints, firsttime) {
2037	delta = firsttime ? delta / this.punycode_constants.damp : delta >> 1;
2038	delta += delta / numpoints;
2039	
2040	for (var k = 0; delta > ((this.punycode_constants.base - this.punycode_constants.tmin) * this.punycode_constants.tmax) / 2; k += this.punycode_constants.base) {
2041		delta /= this.punycode_constants.base - this.punycode_constants.tmin;
2042	}
2043	return k + (this.punycode_constants.base - this.punycode_constants.tmin + 1) * delta / (delta + this.punycode_constants.skew);
2044};
2045
2046/**
2047 * returns a 	Punicode representation of a UTF-8 string
2048 * adapted from http://tools.ietf.org/html/rfc3492
2049 */
2050Spry.Widget.Utils.punycode_encode = function (input, max_out) {
2051	var inputc = input.split("");
2052	input = [];
2053	for(var i=0; i<inputc.length; i++) {
2054		input.push(inputc[i].charCodeAt(0));
2055	}
2056	var output = '';
2057
2058  var h, b, j, m, q, k, t;
2059	var input_len = input.length;
2060  var n = this.punycode_constants.initial_n;
2061  var delta = 0;
2062  var bias = this.punycode_constants.initial_bias;
2063  var out = 0;
2064
2065  for (j = 0; j < input_len; j++) {
2066		if (input[j] < 128) {
2067			if (max_out - out < 2) {
2068				return false;
2069			}
2070			output += String.fromCharCode(input[j]);
2071			out++;
2072		}
2073	}
2074
2075	h = b = out;
2076	if (b > 0) {
2077		output += String.fromCharCode(this.punycode_constants.delimiter);
2078		out++;
2079	}
2080
2081  while (h < input_len)	{
2082		for (m = this.punycode_constants.maxint, j = 0; j < input_len; j++) {
2083			if (input[j] >= n && input[j] < m) {
2084				m = input[j];
2085			}
2086		}
2087		if (m - n > (this.punycode_constants.maxint - delta) / (h + 1)) {
2088			return false;
2089		}
2090		
2091		delta += (m - n) * (h + 1);
2092		n = m;
2093
2094		for (j = 0; j < input_len; j++) {
2095			if (input[j] < n ) {
2096				if (++delta == 0) {
2097					return false;
2098				}
2099			}
2100
2101			if (input[j] == n) {
2102				for (q = delta, k = this.punycode_constants.base; true; k += this.punycode_constants.base) {
2103					if (out >= max_out) {
2104						return false;
2105					}
2106
2107					t = k <= bias ? this.punycode_constants.tmin : k >= bias + this.punycode_constants.tmax ? this.punycode_constants.tmax : k - bias;
2108					if (q < t) {
2109						break;
2110					}
2111
2112					output += this.punycode_encode_digit(t + (q - t) % (this.punycode_constants.base - t));
2113					out++;
2114					q = (q - t) / (this.punycode_constants.base - t);
2115				}
2116
2117				output += this.punycode_encode_digit(q);
2118				out++;
2119				bias = this.punycode_adapt(delta, h + 1, h == b);
2120				delta = 0;
2121				h++;
2122			}
2123		}
2124		delta++, n++;
2125	}
2126
2127  return output;
2128};
2129
2130Spry.Widget.Utils.setOptions = function(obj, optionsObj, ignoreUndefinedProps)
2131{
2132	if (!optionsObj)
2133		return;
2134	for (var optionName in optionsObj)
2135	{
2136		if (ignoreUndefinedProps && optionsObj[optionName] == undefined)
2137			continue;
2138		obj[optionName] = optionsObj[optionName];
2139	}
2140};
2141
2142Spry.Widget.Utils.firstValid = function() {
2143	var ret = null;
2144	for(var i=0; i<Spry.Widget.Utils.firstValid.arguments.length; i++) {
2145		if (typeof(Spry.Widget.Utils.firstValid.arguments[i]) != 'undefined') {
2146			ret = Spry.Widget.Utils.firstValid.arguments[i];
2147			break;
2148		}
2149	}
2150	return ret;
2151};
2152
2153
2154Spry.Widget.Utils.specialCharacters = ",8,9,16,17,18,20,27,33,34,35,36,37,38,40,45,144,192,63232,";
2155Spry.Widget.Utils.specialSafariNavKeys = "63232,63233,63234,63235,63272,63273,63275,63276,63277,63289,";
2156Spry.Widget.Utils.specialNotSafariCharacters = "39,46,91,92,93,";
2157
2158Spry.Widget.Utils.specialCharacters += Spry.Widget.Utils.specialSafariNavKeys;
2159
2160if (!Spry.is.safari) {
2161	Spry.Widget.Utils.specialCharacters += Spry.Widget.Utils.specialNotSafariCharacters;
2162}
2163
2164Spry.Widget.Utils.isSpecialKey = function (ev) {
2165	return Spry.Widget.Utils.specialCharacters.indexOf("," + ev.keyCode + ",") != -1;
2166};
2167
2168Spry.Widget.Utils.getCharacterFromEvent = function(e){
2169	var keyDown = e.type == "keydown";
2170
2171	var code = null;
2172	var character = null;
2173	if(Spry.is.mozilla && !keyDown){
2174		if(e.charCode){
2175			character = String.fromCharCode(e.charCode);
2176		} else {
2177			code = e.keyCode;
2178		}
2179	} else {
2180		code = e.keyCode || e.which;
2181		if (code != 13) {
2182			character = String.fromCharCode(code);
2183		}
2184	}
2185
2186	if (Spry.is.safari) {
2187		if (keyDown) {
2188			code = e.keyCode || e.which;
2189			character = String.fromCharCode(code);
2190		} else {
2191			code = e.keyCode || e.which;
2192			if (Spry.Widget.Utils.specialCharacters.indexOf("," + code + ",") != -1) {
2193				character = null;
2194			} else {
2195				character = String.fromCharCode(code);
2196			}
2197		}
2198	}
2199
2200	if(Spry.is.opera) {
2201		if (Spry.Widget.Utils.specialCharacters.indexOf("," + code + ",") != -1) {
2202			character = null;
2203		} else {
2204			character = String.fromCharCode(code);
2205		}
2206	}
2207
2208	return character;
2209};
2210
2211Spry.Widget.Utils.getFirstChildWithNodeNameAtAnyLevel = function(node, nodeName)
2212{
2213	var elements  = node.getElementsByTagName(nodeName);
2214	if (elements) {
2215		return elements[0];
2216	}
2217	return null;
2218};
2219
2220Spry.Widget.Utils.getFirstParentWithNodeName = function(node, nodeName)
2221{
2222	while (node.parentNode
2223			&& node.parentNode.nodeName.toLowerCase() != nodeName.toLowerCase()
2224			&& node.parentNode.nodeName != 'BODY') {
2225		node = node.parentNode;
2226	}
2227
2228	if (node.parentNode && node.parentNode.nodeName.toLowerCase() == nodeName.toLowerCase()) {
2229		return node.parentNode;
2230	} else {
2231		return null;
2232	}
2233};
2234
2235Spry.Widget.Utils.destroyWidgets = function (container)
2236{
2237	if (typeof container == 'string') {
2238		container = document.getElementById(container);
2239	}
2240
2241	var q = Spry.Widget.Form.onSubmitWidgetQueue;
2242	for (var i = 0; i < Spry.Widget.Form.onSubmitWidgetQueue.length; i++) {
2243		if (typeof(q[i].destroy) == 'function' && Spry.Widget.Utils.contains(container, q[i].element)) {
2244			q[i].destroy();
2245			i--;
2246		}
2247	}
2248};
2249
2250Spry.Widget.Utils.contains = function (who, what)
2251{
2252	if (typeof who.contains == 'object') {
2253		return what && who && (who == what || who.contains(what));
2254	} else {
2255		var el = what;
2256		while(el) {
2257			if (el == who) {
2258				return true;
2259			}
2260			el = el.parentNode;
2261		}
2262		return false;
2263	}
2264};
2265
2266Spry.Widget.Utils.addEventListener = function(element, eventType, handler, capture)
2267{
2268	try
2269	{
2270		if (element.addEventListener)
2271			element.addEventListener(eventType, handler, capture);
2272		else if (element.attachEvent)
2273			element.attachEvent("on" + eventType, handler, capture);
2274	}
2275	catch (e) {}
2276};
2277
2278Spry.Widget.Utils.removeEventListener = function(element, eventType, handler, capture)
2279{
2280	try
2281	{
2282		if (element.removeEventListener)
2283			element.removeEventListener(eventType, handler, capture);
2284		else if (element.detachEvent)
2285			element.detachEvent("on" + eventType, handler, capture);
2286	}
2287	catch (e) {}
2288};
2289
2290Spry.Widget.Utils.stopEvent = function(ev)
2291{
2292	try
2293	{
2294		this.stopPropagation(ev);
2295		this.preventDefault(ev);
2296	}
2297	catch (e) {}
2298};
2299
2300/**
2301 * Stops event propagation
2302 * @param {Event} ev the event
2303 */
2304Spry.Widget.Utils.stopPropagation = function(ev)
2305{
2306	if (ev.stopPropagation)
2307	{
2308		ev.stopPropagation();
2309	}
2310	else
2311	{
2312		ev.cancelBubble = true;
2313	}
2314};
2315
2316/**
2317 * Prevents the default behavior of the event
2318 * @param {Event} ev the event
2319 */
2320Spry.Widget.Utils.preventDefault = function(ev)
2321{
2322	if (ev.preventDefault)
2323	{
2324		ev.preventDefault();
2325	}
2326	else
2327	{
2328		ev.returnValue = false;
2329	}
2330};
2331
2332})(); // EndSpryComponent

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.