PageSourceSearch

https://rosenbergfamilylaw.com/SpryAssets/SpryValidationTextField.js

js rosenbergfamilylaw.com collected 2026-10-08 02:25:41 UTC 73,330 bytes, 2,323 lines download raw bytes

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

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.