PageSourceSearch

https://nicolinos.pt/js/mtm.js

js nicolinos.pt collected 2026-10-02 12:19:37 UTC 166,089 bytes, 7,024 lines download raw bytes

1// MooTools: the javascript framework.
2// Load this file's selection again by visiting: http://mootools.net/more/d06b1fcc692893a712456d0658524080 
3// Or build this file again with packager using: packager build More/More More/Array.Extras More/Date More/Date.Extras More/String.Extras More/String.QueryString More/Hash More/Hash.Extras More/Element.Forms More/Elements.From More/Element.Event.Pseudos More/Element.Event.Pseudos.Keys More/Element.Measure More/Element.Pin More/Element.Position More/Element.Shortcuts More/Form.Request More/Form.Request.Append More/Form.Validator More/Form.Validator.Inline More/Form.Validator.Extras More/OverText More/Fx.Elements More/Fx.Accordion More/Fx.Move More/Fx.Scroll More/Fx.Slide More/Fx.SmoothScroll More/Fx.Sort More/Assets More/Hash.Cookie More/Keyboard More/Keyboard.Extras More/Scroller More/Tips
4/*
5---
6
7script: More.js
8
9name: More
10
11description: MooTools More
12
13license: MIT-style license
14
15authors:
16  - Guillermo Rauch
17  - Thomas Aylott
18  - Scott Kyle
19  - Arian Stolwijk
20  - Tim Wienk
21  - Christoph Pojer
22  - Aaron Newton
23  - Jacob Thornton
24
25requires:
26  - Core/MooTools
27
28provides: [MooTools.More]
29
30...
31*/
32
33MooTools.More = {
34	'version': '1.4.0.1',
35	'build': 'a4244edf2aa97ac8a196fc96082dd35af1abab87'
36};
37
38
39/*
40---
41
42script: Array.Extras.js
43
44name: Array.Extras
45
46description: Extends the Array native object to include useful methods to work with arrays.
47
48license: MIT-style license
49
50authors:
51  - Christoph Pojer
52  - Sebastian Markbåge
53
54requires:
55  - Core/Array
56  - MooTools.More
57
58provides: [Array.Extras]
59
60...
61*/
62
63(function(nil){
64
65Array.implement({
66
67	min: function(){
68		return Math.min.apply(null, this);
69	},
70
71	max: function(){
72		return Math.max.apply(null, this);
73	},
74
75	average: function(){
76		return this.length ? this.sum() / this.length : 0;
77	},
78
79	sum: function(){
80		var result = 0, l = this.length;
81		if (l){
82			while (l--) result += this[l];
83		}
84		return result;
85	},
86
87	unique: function(){
88		return [].combine(this);
89	},
90
91	shuffle: function(){
92		for (var i = this.length; i && --i;){
93			var temp = this[i], r = Math.floor(Math.random() * ( i + 1 ));
94			this[i] = this[r];
95			this[r] = temp;
96		}
97		return this;
98	},
99
100	reduce: function(fn, value){
101		for (var i = 0, l = this.length; i < l; i++){
102			if (i in this) value = value === nil ? this[i] : fn.call(null, value, this[i], i, this);
103		}
104		return value;
105	},
106
107	reduceRight: function(fn, value){
108		var i = this.length;
109		while (i--){
110			if (i in this) value = value === nil ? this[i] : fn.call(null, value, this[i], i, this);
111		}
112		return value;
113	}
114
115});
116
117})();
118
119
120/*
121---
122
123script: Object.Extras.js
124
125name: Object.Extras
126
127description: Extra Object generics, like getFromPath which allows a path notation to child elements.
128
129license: MIT-style license
130
131authors:
132  - Aaron Newton
133
134requires:
135  - Core/Object
136  - /MooTools.More
137
138provides: [Object.Extras]
139
140...
141*/
142
143(function(){
144
145var defined = function(value){
146	return value != null;
147};
148
149var hasOwnProperty = Object.prototype.hasOwnProperty;
150
151Object.extend({
152
153	getFromPath: function(source, parts){
154		if (typeof parts == 'string') parts = parts.split('.');
155		for (var i = 0, l = parts.length; i < l; i++){
156			if (hasOwnProperty.call(source, parts[i])) source = source[parts[i]];
157			else return null;
158		}
159		return source;
160	},
161
162	cleanValues: function(object, method){
163		method = method || defined;
164		for (var key in object) if (!method(object[key])){
165			delete object[key];
166		}
167		return object;
168	},
169
170	erase: function(object, key){
171		if (hasOwnProperty.call(object, key)) delete object[key];
172		return object;
173	},
174
175	run: function(object){
176		var args = Array.slice(arguments, 1);
177		for (var key in object) if (object[key].apply){
178			object[key].apply(object, args);
179		}
180		return object;
181	}
182
183});
184
185})();
186
187
188/*
189---
190
191script: Locale.js
192
193name: Locale
194
195description: Provides methods for localization.
196
197license: MIT-style license
198
199authors:
200  - Aaron Newton
201  - Arian Stolwijk
202
203requires:
204  - Core/Events
205  - /Object.Extras
206  - /MooTools.More
207
208provides: [Locale, Lang]
209
210...
211*/
212
213(function(){
214
215var current = null,
216	locales = {},
217	inherits = {};
218
219var getSet = function(set){
220	if (instanceOf(set, Locale.Set)) return set;
221	else return locales[set];
222};
223
224var Locale = this.Locale = {
225
226	define: function(locale, set, key, value){
227		var name;
228		if (instanceOf(locale, Locale.Set)){
229			name = locale.name;
230			if (name) locales[name] = locale;
231		} else {
232			name = locale;
233			if (!locales[name]) locales[name] = new Locale.Set(name);
234			locale = locales[name];
235		}
236
237		if (set) locale.define(set, key, value);
238
239		/*<1.2compat>*/
240		if (set == 'cascade') return Locale.inherit(name, key);
241		/*</1.2compat>*/
242
243		if (!current) current = locale;
244
245		return locale;
246	},
247
248	use: function(locale){
249		locale = getSet(locale);
250
251		if (locale){
252			current = locale;
253
254			this.fireEvent('change', locale);
255
256			/*<1.2compat>*/
257			this.fireEvent('langChange', locale.name);
258			/*</1.2compat>*/
259		}
260
261		return this;
262	},
263
264	getCurrent: function(){
265		return current;
266	},
267
268	get: function(key, args){
269		return (current) ? current.get(key, args) : '';
270	},
271
272	inherit: function(locale, inherits, set){
273		locale = getSet(locale);
274
275		if (locale) locale.inherit(inherits, set);
276		return this;
277	},
278
279	list: function(){
280		return Object.keys(locales);
281	}
282
283};
284
285Object.append(Locale, new Events);
286
287Locale.Set = new Class({
288
289	sets: {},
290
291	inherits: {
292		locales: [],
293		sets: {}
294	},
295
296	initialize: function(name){
297		this.name = name || '';
298	},
299
300	define: function(set, key, value){
301		var defineData = this.sets[set];
302		if (!defineData) defineData = {};
303
304		if (key){
305			if (typeOf(key) == 'object') defineData = Object.merge(defineData, key);
306			else defineData[key] = value;
307		}
308		this.sets[set] = defineData;
309
310		return this;
311	},
312
313	get: function(key, args, _base){
314		var value = Object.getFromPath(this.sets, key);
315		if (value != null){
316			var type = typeOf(value);
317			if (type == 'function') value = value.apply(null, Array.from(args));
318			else if (type == 'object') value = Object.clone(value);
319			return value;
320		}
321
322		// get value of inherited locales
323		var index = key.indexOf('.'),
324			set = index < 0 ? key : key.substr(0, index),
325			names = (this.inherits.sets[set] || []).combine(this.inherits.locales).include('en-US');
326		if (!_base) _base = [];
327
328		for (var i = 0, l = names.length; i < l; i++){
329			if (_base.contains(names[i])) continue;
330			_base.include(names[i]);
331
332			var locale = locales[names[i]];
333			if (!locale) continue;
334
335			value = locale.get(key, args, _base);
336			if (value != null) return value;
337		}
338
339		return '';
340	},
341
342	inherit: function(names, set){
343		names = Array.from(names);
344
345		if (set && !this.inherits.sets[set]) this.inherits.sets[set] = [];
346
347		var l = names.length;
348		while (l--) (set ? this.inherits.sets[set] : this.inherits.locales).unshift(names[l]);
349
350		return this;
351	}
352
353});
354
355/*<1.2compat>*/
356var lang = MooTools.lang = {};
357
358Object.append(lang, Locale, {
359	setLanguage: Locale.use,
360	getCurrentLanguage: function(){
361		var current = Locale.getCurrent();
362		return (current) ? current.name : null;
363	},
364	set: function(){
365		Locale.define.apply(this, arguments);
366		return this;
367	},
368	get: function(set, key, args){
369		if (key) set += '.' + key;
370		return Locale.get(set, args);
371	}
372});
373/*</1.2compat>*/
374
375})();
376
377
378/*
379---
380
381name: Locale.en-US.Date
382
383description: Date messages for US English.
384
385license: MIT-style license
386
387authors:
388  - Aaron Newton
389
390requires:
391  - /Locale
392
393provides: [Locale.en-US.Date]
394
395...
396*/
397
398Locale.define('en-US', 'Date', {
399
400	months: ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'],
401	months_abbr: ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'],
402	days: ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'],
403	days_abbr: ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'],
404
405	// Culture's date order: MM/DD/YYYY
406	dateOrder: ['month', 'date', 'year'],
407	shortDate: '%m/%d/%Y',
408	shortTime: '%I:%M%p',
409	AM: 'AM',
410	PM: 'PM',
411	firstDayOfWeek: 0,
412
413	// Date.Extras
414	ordinal: function(dayOfMonth){
415		// 1st, 2nd, 3rd, etc.
416		return (dayOfMonth > 3 && dayOfMonth < 21) ? 'th' : ['th', 'st', 'nd', 'rd', 'th'][Math.min(dayOfMonth % 10, 4)];
417	},
418
419	lessThanMinuteAgo: 'less than a minute ago',
420	minuteAgo: 'about a minute ago',
421	minutesAgo: '{delta} minutes ago',
422	hourAgo: 'about an hour ago',
423	hoursAgo: 'about {delta} hours ago',
424	dayAgo: '1 day ago',
425	daysAgo: '{delta} days ago',
426	weekAgo: '1 week ago',
427	weeksAgo: '{delta} weeks ago',
428	monthAgo: '1 month ago',
429	monthsAgo: '{delta} months ago',
430	yearAgo: '1 year ago',
431	yearsAgo: '{delta} years ago',
432
433	lessThanMinuteUntil: 'less than a minute from now',
434	minuteUntil: 'about a minute from now',
435	minutesUntil: '{delta} minutes from now',
436	hourUntil: 'about an hour from now',
437	hoursUntil: 'about {delta} hours from now',
438	dayUntil: '1 day from now',
439	daysUntil: '{delta} days from now',
440	weekUntil: '1 week from now',
441	weeksUntil: '{delta} weeks from now',
442	monthUntil: '1 month from now',
443	monthsUntil: '{delta} months from now',
444	yearUntil: '1 year from now',
445	yearsUntil: '{delta} years from now'
446
447});
448
449
450/*
451---
452
453script: Date.js
454
455name: Date
456
457description: Extends the Date native object to include methods useful in managing dates.
458
459license: MIT-style license
460
461authors:
462  - Aaron Newton
463  - Nicholas Barthelemy - https://svn.nbarthelemy.com/date-js/
464  - Harald Kirshner - mail [at] digitarald.de; http://digitarald.de
465  - Scott Kyle - scott [at] appden.com; http://appden.com
466
467requires:
468  - Core/Array
469  - Core/String
470  - Core/Number
471  - MooTools.More
472  - Locale
473  - Locale.en-US.Date
474
475provides: [Date]
476
477...
478*/
479
480(function(){
481
482var Date = this.Date;
483
484var DateMethods = Date.Methods = {
485	ms: 'Milliseconds',
486	year: 'FullYear',
487	min: 'Minutes',
488	mo: 'Month',
489	sec: 'Seconds',
490	hr: 'Hours'
491};
492
493['Date', 'Day', 'FullYear', 'Hours', 'Milliseconds', 'Minutes', 'Month', 'Seconds', 'Time', 'TimezoneOffset',
494	'Week', 'Timezone', 'GMTOffset', 'DayOfYear', 'LastMonth', 'LastDayOfMonth', 'UTCDate', 'UTCDay', 'UTCFullYear',
495	'AMPM', 'Ordinal', 'UTCHours', 'UTCMilliseconds', 'UTCMinutes', 'UTCMonth', 'UTCSeconds', 'UTCMilliseconds'].each(function(method){
496	Date.Methods[method.toLowerCase()] = method;
497});
498
499var pad = function(n, digits, string){
500	if (digits == 1) return n;
501	return n < Math.pow(10, digits - 1) ? (string || '0') + pad(n, digits - 1, string) : n;
502};
503
504Date.implement({
505
506	set: function(prop, value){
507		prop = prop.toLowerCase();
508		var method = DateMethods[prop] && 'set' + DateMethods[prop];
509		if (method && this[method]) this[method](value);
510		return this;
511	}.overloadSetter(),
512
513	get: function(prop){
514		prop = prop.toLowerCase();
515		var method = DateMethods[prop] && 'get' + DateMethods[prop];
516		if (method && this[method]) return this[method]();
517		return null;
518	}.overloadGetter(),
519
520	clone: function(){
521		return new Date(this.get('time'));
522	},
523
524	increment: function(interval, times){
525		interval = interval || 'day';
526		times = times != null ? times : 1;
527
528		switch (interval){
529			case 'year':
530				return this.increment('month', times * 12);
531			case 'month':
532				var d = this.get('date');
533				this.set('date', 1).set('mo', this.get('mo') + times);
534				return this.set('date', d.min(this.get('lastdayofmonth')));
535			case 'week':
536				return this.increment('day', times * 7);
537			case 'day':
538				return this.set('date', this.get('date') + times);
539		}
540
541		if (!Date.units[interval]) throw new Error(interval + ' is not a supported interval');
542
543		return this.set('time', this.get('time') + times * Date.units[interval]());
544	},
545
546	decrement: function(interval, times){
547		return this.increment(interval, -1 * (times != null ? times : 1));
548	},
549
550	isLeapYear: function(){
551		return Date.isLeapYear(this.get('year'));
552	},
553
554	clearTime: function(){
555		return this.set({hr: 0, min: 0, sec: 0, ms: 0});
556	},
557
558	diff: function(date, resolution){
559		if (typeOf(date) == 'string') date = Date.parse(date);
560
561		return ((date - this) / Date.units[resolution || 'day'](3, 3)).round(); // non-leap year, 30-day month
562	},
563
564	getLastDayOfMonth: function(){
565		return Date.daysInMonth(this.get('mo'), this.get('year'));
566	},
567
568	getDayOfYear: function(){
569		return (Date.UTC(this.get('year'), this.get('mo'), this.get('date') + 1)
570			- Date.UTC(this.get('year'), 0, 1)) / Date.units.day();
571	},
572
573	setDay: function(day, firstDayOfWeek){
574		if (firstDayOfWeek == null){
575			firstDayOfWeek = Date.getMsg('firstDayOfWeek');
576			if (firstDayOfWeek === '') firstDayOfWeek = 1;
577		}
578
579		day = (7 + Date.parseDay(day, true) - firstDayOfWeek) % 7;
580		var currentDay = (7 + this.get('day') - firstDayOfWeek) % 7;
581
582		return this.increment('day', day - currentDay);
583	},
584
585	getWeek: function(firstDayOfWeek){
586		if (firstDayOfWeek == null){
587			firstDayOfWeek = Date.getMsg('firstDayOfWeek');
588			if (firstDayOfWeek === '') firstDayOfWeek = 1;
589		}
590
591		var date = this,
592			dayOfWeek = (7 + date.get('day') - firstDayOfWeek) % 7,
593			dividend = 0,
594			firstDayOfYear;
595
596		if (firstDayOfWeek == 1){
597			// ISO-8601, week belongs to year that has the most days of the week (i.e. has the thursday of the week)
598			var month = date.get('month'),
599				startOfWeek = date.get('date') - dayOfWeek;
600
601			if (month == 11 && startOfWeek > 28) return 1; // Week 1 of next year
602
603			if (month == 0 && startOfWeek < -2){
604				// Use a date from last year to determine the week
605				date = new Date(date).decrement('day', dayOfWeek);
606				dayOfWeek = 0;
607			}
608
609			firstDayOfYear = new Date(date.get('year'), 0, 1).get('day') || 7;
610			if (firstDayOfYear > 4) dividend = -7; // First week of the year is not week 1
611		} else {
612			// In other cultures the first week of the year is always week 1 and the last week always 53 or 54.
613			// Days in the same week can have a different weeknumber if the week spreads across two years.
614			firstDayOfYear = new Date(date.get('year'), 0, 1).get('day');
615		}
616
617		dividend += date.get('dayofyear');
618		dividend += 6 - dayOfWeek; // Add days so we calculate the current date's week as a full week
619		dividend += (7 + firstDayOfYear - firstDayOfWeek) % 7; // Make up for first week of the year not being a full week
620
621		return (dividend / 7);
622	},
623
624	getOrdinal: function(day){
625		return Date.getMsg('ordinal', day || this.get('date'));
626	},
627
628	getTimezone: function(){
629		return this.toString()
630			.replace(/^.*? ([A-Z]{3}).[0-9]{4}.*$/, '$1')
631			.replace(/^.*?\(([A-Z])[a-z]+ ([A-Z])[a-z]+ ([A-Z])[a-z]+\)$/, '$1$2$3');
632	},
633
634	getGMTOffset: function(){
635		var off = this.get('timezoneOffset');
636		return ((off > 0) ? '-' : '+') + pad((off.abs() / 60).floor(), 2) + pad(off % 60, 2);
637	},
638
639	setAMPM: function(ampm){
640		ampm = ampm.toUpperCase();
641		var hr = this.get('hr');
642		if (hr >
642 11 && ampm == 'AM') return this.decrement('hour', 12);
643		else if (hr < 12 && ampm == 'PM') return this.increment('hour', 12);
644		return this;
645	},
646
647	getAMPM: function(){
648		return (this.get('hr') < 12) ? 'AM' : 'PM';
649	},
650
651	parse: function(str){
652		this.set('time', Date.parse(str));
653		return this;
654	},
655
656	isValid: function(date){
657		if (!date) date = this;
658		return typeOf(date) == 'date' && !isNaN(date.valueOf());
659	},
660
661	format: function(format){
662		if (!this.isValid()) return 'invalid date';
663
664		if (!format) format = '%x %X';
665		if (typeof format == 'string') format = formats[format.toLowerCase()] || format;
666		if (typeof format == 'function') return format(this);
667
668		var d = this;
669		return format.replace(/%([a-z%])/gi,
670			function($0, $1){
671				switch ($1){
672					case 'a': return Date.getMsg('days_abbr')[d.get('day')];
673					case 'A': return Date.getMsg('days')[d.get('day')];
674					case 'b': return Date.getMsg('months_abbr')[d.get('month')];
675					case 'B': return Date.getMsg('months')[d.get('month')];
676					case 'c': return d.format('%a %b %d %H:%M:%S %Y');
677					case 'd': return pad(d.get('date'), 2);
678					case 'e': return pad(d.get('date'), 2, ' ');
679					case 'H': return pad(d.get('hr'), 2);
680					case 'I': return pad((d.get('hr') % 12) || 12, 2);
681					case 'j': return pad(d.get('dayofyear'), 3);
682					case 'k': return pad(d.get('hr'), 2, ' ');
683					case 'l': return pad((d.get('hr') % 12) || 12, 2, ' ');
684					case 'L': return pad(d.get('ms'), 3);
685					case 'm': return pad((d.get('mo') + 1), 2);
686					case 'M': return pad(d.get('min'), 2);
687					case 'o': return d.get('ordinal');
688					case 'p': return Date.getMsg(d.get('ampm'));
689					case 's': return Math.round(d / 1000);
690					case 'S': return pad(d.get('seconds'), 2);
691					case 'T': return d.format('%H:%M:%S');
692					case 'U': return pad(d.get('week'), 2);
693					case 'w': return d.get('day');
694					case 'x': return d.format(Date.getMsg('shortDate'));
695					case 'X': return d.format(Date.getMsg('shortTime'));
696					case 'y': return d.get('year').toString().substr(2);
697					case 'Y': return d.get('year');
698					case 'z': return d.get('GMTOffset');
699					case 'Z': return d.get('Timezone');
700				}
701				return $1;
702			}
703		);
704	},
705
706	toISOString: function(){
707		return this.format('iso8601');
708	}
709
710}).alias({
711	toJSON: 'toISOString',
712	compare: 'diff',
713	strftime: 'format'
714});
715
716// The day and month abbreviations are standardized, so we cannot use simply %a and %b because they will get localized
717var rfcDayAbbr = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'],
718	rfcMonthAbbr = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];
719
720var formats = {
721	db: '%Y-%m-%d %H:%M:%S',
722	compact: '%Y%m%dT%H%M%S',
723	'short': '%d %b %H:%M',
724	'long': '%B %d, %Y %H:%M',
725	rfc822: function(date){
726		return rfcDayAbbr[date.get('day')] + date.format(', %d ') + rfcMonthAbbr[date.get('month')] + date.format(' %Y %H:%M:%S %Z');
727	},
728	rfc2822: function(date){
729		return rfcDayAbbr[date.get('day')] + date.format(', %d ') + rfcMonthAbbr[date.get('month')] + date.format(' %Y %H:%M:%S %z');
730	},
731	iso8601: function(date){
732		return (
733			date.getUTCFullYear() + '-' +
734			pad(date.getUTCMonth() + 1, 2) + '-' +
735			pad(date.getUTCDate(), 2) + 'T' +
736			pad(date.getUTCHours(), 2) + ':' +
737			pad(date.getUTCMinutes(), 2) + ':' +
738			pad(date.getUTCSeconds(), 2) + '.' +
739			pad(date.getUTCMilliseconds(), 3) + 'Z'
740		);
741	}
742};
743
744var parsePatterns = [],
745	nativeParse = Date.parse;
746
747var parseWord = function(type, word, num){
748	var ret = -1,
749		translated = Date.getMsg(type + 's');
750	switch (typeOf(word)){
751		case 'object':
752			ret = translated[word.get(type)];
753			break;
754		case 'number':
755			ret = translated[word];
756			if (!ret) throw new Error('Invalid ' + type + ' index: ' + word);
757			break;
758		case 'string':
759			var match = translated.filter(function(name){
760				return this.test(name);
761			}, new RegExp('^' + word, 'i'));
762			if (!match.length) throw new Error('Invalid ' + type + ' string');
763			if (match.length > 1) throw new Error('Ambiguous ' + type);
764			ret = match[0];
765	}
766
767	return (num) ? translated.indexOf(ret) : ret;
768};
769
770var startCentury = 1900,
771	startYear = 70;
772
773Date.extend({
774
775	getMsg: function(key, args){
776		return Locale.get('Date.' + key, args);
777	},
778
779	units: {
780		ms: Function.from(1),
781		second: Function.from(1000),
782		minute: Function.from(60000),
783		hour: Function.from(3600000),
784		day: Function.from(86400000),
785		week: Function.from(608400000),
786		month: function(month, year){
787			var d = new Date;
788			return Date.daysInMonth(month != null ? month : d.get('mo'), year != null ? year : d.get('year')) * 86400000;
789		},
790		year: function(year){
791			year = year || new Date().get('year');
792			return Date.isLeapYear(year) ? 31622400000 : 31536000000;
793		}
794	},
795
796	daysInMonth: function(month, year){
797		return [31, Date.isLeapYear(year) ? 29 : 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31][month];
798	},
799
800	isLeapYear: function(year){
801		return ((year % 4 === 0) && (year % 100 !== 0)) || (year % 400 === 0);
802	},
803
804	parse: function(from){
805		var t = typeOf(from);
806		if (t == 'number') return new Date(from);
807		if (t != 'string') return from;
808		from = from.clean();
809		if (!from.length) return null;
810
811		var parsed;
812		parsePatterns.some(function(pattern){
813			var bits = pattern.re.exec(from);
814			return (bits) ? (parsed = pattern.handler(bits)) : false;
815		});
816
817		if (!(parsed && parsed.isValid())){
818			parsed = new Date(nativeParse(from));
819			if (!(parsed && parsed.isValid())) parsed = new Date(from.toInt());
820		}
821		return parsed;
822	},
823
824	parseDay: function(day, num){
825		return parseWord('day', day, num);
826	},
827
828	parseMonth: function(month, num){
829		return parseWord('month', month, num);
830	},
831
832	parseUTC: function(value){
833		var localDate = new Date(value);
834		var utcSeconds = Date.UTC(
835			localDate.get('year'),
836			localDate.get('mo'),
837			localDate.get('date'),
838			localDate.get('hr'),
839			localDate.get('min'),
840			localDate.get('sec'),
841			localDate.get('ms')
842		);
843		return new Date(utcSeconds);
844	},
845
846	orderIndex: function(unit){
847		return Date.getMsg('dateOrder').indexOf(unit) + 1;
848	},
849
850	defineFormat: function(name, format){
851		formats[name] = format;
852		return this;
853	},
854
855	//<1.2compat>
856	parsePatterns: parsePatterns,
857	//</1.2compat>
858
859	defineParser: function(pattern){
860		parsePatterns.push((pattern.re && pattern.handler) ? pattern : build(pattern));
861		return this;
862	},
863
864	defineParsers: function(){
865		Array.flatten(arguments).each(Date.defineParser);
866		return this;
867	},
868
869	define2DigitYearStart: function(year){
870		startYear = year % 100;
871		startCentury = year - startYear;
872		return this;
873	}
874
875}).extend({
876	defineFormats: Date.defineFormat.overloadSetter()
877});
878
879var regexOf = function(type){
880	return new RegExp('(?:' + Date.getMsg(type).map(function(name){
881		return name.substr(0, 3);
882	}).join('|') + ')[a-z]*');
883};
884
885var replacers = function(key){
886	switch (key){
887		case 'T':
888			return '%H:%M:%S';
889		case 'x': // iso8601 covers yyyy-mm-dd, so just check if month is first
890			return ((Date.orderIndex('month') == 1) ? '%m[-./]%d' : '%d[-./]%m') + '([-./]%y)?';
891		case 'X':
892			return '%H([.:]%M)?([.:]%S([.:]%s)?)? ?%p? ?%z?';
893	}
894	return null;
895};
896
897var keys = {
898	d: /[0-2]?[0-9]|3[01]/,
899	H: /[01]?[0-9]|2[0-3]/,
900	I: /0?[1-9]|1[0-2]/,
901	M: /[0-5]?\d/,
902	s: /\d+/,
903	o: /[a-z]*/,
904	p: /[ap]\.?m\.?/,
905	y: /\d{2}|\d{4}/,
906	Y: /\d{4}/,
907	z: /Z|[+-]\d{2}(?::?\d{2})?/
908};
909
910keys.m = keys.I;
911keys.S = keys.M;
912
913var currentLanguage;
914
915var recompile = function(language){
916	currentLanguage = language;
917
918	keys.a = keys.A = regexOf('days');
919	keys.b = keys.B = regexOf('months');
920
921	parsePatterns.each(function(pattern, i){
922		if (pattern.format) parsePatterns[i] = build(pattern.format);
923	});
924};
925
926var build = function(format){
927	if (!currentLanguage) return {format: format};
928
929	var parsed = [];
930	var re = (format.source || format) // allow format to be regex
931	 .replace(/%([a-z])/gi,
932		function($0, $1){
933			return replacers($1) || $0;
934		}
935	).replace(/\((?!\?)/g, '(?:') // make all groups non-capturing
936	 .replace(/ (?!\?|\*)/g, ',? ') // be forgiving with spaces and commas
937	 .replace(/%([a-z%])/gi,
938		function($0, $1){
939			var p = keys[$1];
940			if (!p) return $1;
941			parsed.push($1);
942			return '(' + p.source + ')';
943		}
944	).replace(/\[a-z\]/gi, '[a-z\\u00c0-\\uffff;\&]'); // handle unicode words
945
946	return {
947		format: format,
948		re: new RegExp('^' + re + '$', 'i'),
949		handler: function(bits){
950			bits = bits.slice(1).associate(parsed);
951			var date = new Date().clearTime(),
952				year = bits.y || bits.Y;
953
954			if (year != null) handle.call(date, 'y', year); // need to start in the right year
955			if ('d' in bits) handle.call(date, 'd', 1);
956			if ('m' in bits || bits.b || bits.B) handle.call(date, 'm', 1);
957
958			for (var key in bits) handle.call(date, key, bits[key]);
959			return date;
960		}
961	};
962};
963
964var handle = function(key, value){
965	if (!value) return this;
966
967	switch (key){
968		case 'a': case 'A': return this.set('day', Date.parseDay(value, true));
969		case 'b': case 'B': return this.set('mo', Date.parseMonth(value, true));
970		case 'd': return this.set('date', value);
971		case 'H': case 'I': return this.set('hr', value);
972		case 'm': return this.set('mo', value - 1);
973		case 'M': return this.set('min', value);
974		case 'p': return this.set('ampm', value.replace(/\./g, ''));
975		case 'S': return this.set('sec', value);
976		case 's': return this.set('ms', ('0.' + value) * 1000);
977		case 'w': return this.set('day', value);
978		case 'Y': return this.set('year', value);
979		case 'y':
980			value = +value;
981			if (value < 100) value += startCentury + (value < startYear ? 100 : 0);
982			return this.set('year', value);
983		case 'z':
984			if (value == 'Z') value = '+00';
985			var offset = value.match(/([+-])(\d{2}):?(\d{2})?/);
986			offset = (offset[1] + '1') * (offset[2] * 60 + (+offset[3] || 0)) + this.getTimezoneOffset();
987			return this.set('time', this - offset * 60000);
988	}
989
990	return this;
991};
992
993Date.defineParsers(
994	'%Y([-./]%m([-./]%d((T| )%X)?)?)?', // "1999-12-31", "1999-12-31 11:59pm", "1999-12-31 23:59:59", ISO8601
995	'%Y%m%d(T%H(%M%S?)?)?', // "19991231", "19991231T1159", compact
996	'%x( %X)?', // "12/31", "12.31.99", "12-31-1999", "12/31/2008 11:59 PM"
997	'%d%o( %b( %Y)?)?( %X)?', // "31st", "31st December", "31 Dec 1999", "31 Dec 1999 11:59pm"
998	'%b( %d%o)?( %Y)?( %X)?', // Same as above with month and day switched
999	'%Y %b( %d%o( %X)?)?', // Same as above with year coming first
1000	'%o %b %d %X %z %Y', // "Thu Oct 22 08:11:23 +0000 2009"
1001	'%T', // %H:%M:%S
1002	'%H:%M( ?%p)?' // "11:05pm", "11:05 am" and "11:05"
1003);
1004
1005Locale.addEvent('change', function(language){
1006	if (Locale.get('Date')) recompile(language);
1007}).fireEvent('change', Locale.getCurrent());
1008
1009})();
1010
1011
1012/*
1013---
1014
1015script: Date.Extras.js
1016
1017name: Date.Extras
1018
1019description: Extends the Date native object to include extra methods (on to
1019p of those in Date.js).
1020
1021license: MIT-style license
1022
1023authors:
1024  - Aaron Newton
1025  - Scott Kyle
1026
1027requires:
1028  - /Date
1029
1030provides: [Date.Extras]
1031
1032...
1033*/
1034
1035Date.implement({
1036
1037	timeDiffInWords: function(to){
1038		return Date.distanceOfTimeInWords(this, to || new Date);
1039	},
1040
1041	timeDiff: function(to, separator){
1042		if (to == null) to = new Date;
1043		var delta = ((to - this) / 1000).floor().abs();
1044
1045		var vals = [],
1046			durations = [60, 60, 24, 365, 0],
1047			names = ['s', 'm', 'h', 'd', 'y'],
1048			value, duration;
1049
1050		for (var item = 0; item < durations.length; item++){
1051			if (item && !delta) break;
1052			value = delta;
1053			if ((duration = durations[item])){
1054				value = (delta % duration);
1055				delta = (delta / duration).floor();
1056			}
1057			vals.unshift(value + (names[item] || ''));
1058		}
1059
1060		return vals.join(separator || ':');
1061	}
1062
1063}).extend({
1064
1065	distanceOfTimeInWords: function(from, to){
1066		return Date.getTimePhrase(((to - from) / 1000).toInt());
1067	},
1068
1069	getTimePhrase: function(delta){
1070		var suffix = (delta < 0) ? 'Until' : 'Ago';
1071		if (delta < 0) delta *= -1;
1072
1073		var units = {
1074			minute: 60,
1075			hour: 60,
1076			day: 24,
1077			week: 7,
1078			month: 52 / 12,
1079			year: 12,
1080			eon: Infinity
1081		};
1082
1083		var msg = 'lessThanMinute';
1084
1085		for (var unit in units){
1086			var interval = units[unit];
1087			if (delta < 1.5 * interval){
1088				if (delta > 0.75 * interval) msg = unit;
1089				break;
1090			}
1091			delta /= interval;
1092			msg = unit + 's';
1093		}
1094
1095		delta = delta.round();
1096		return Date.getMsg(msg + suffix, delta).substitute({delta: delta});
1097	}
1098
1099}).defineParsers(
1100
1101	{
1102		// "today", "tomorrow", "yesterday"
1103		re: /^(?:tod|tom|yes)/i,
1104		handler: function(bits){
1105			var d = new Date().clearTime();
1106			switch (bits[0]){
1107				case 'tom': return d.increment();
1108				case 'yes': return d.decrement();
1109				default: return d;
1110			}
1111		}
1112	},
1113
1114	{
1115		// "next Wednesday", "last Thursday"
1116		re: /^(next|last) ([a-z]+)$/i,
1117		handler: function(bits){
1118			var d = new Date().clearTime();
1119			var day = d.getDay();
1120			var newDay = Date.parseDay(bits[2], true);
1121			var addDays = newDay - day;
1122			if (newDay <= day) addDays += 7;
1123			if (bits[1] == 'last') addDays -= 7;
1124			return d.set('date', d.getDate() + addDays);
1125		}
1126	}
1127
1128).alias('timeAgoInWords', 'timeDiffInWords');
1129
1130
1131/*
1132---
1133
1134script: String.Extras.js
1135
1136name: String.Extras
1137
1138description: Extends the String native object to include methods useful in managing various kinds of strings (query strings, urls, html, etc).
1139
1140license: MIT-style license
1141
1142authors:
1143  - Aaron Newton
1144  - Guillermo Rauch
1145  - Christopher Pitt
1146
1147requires:
1148  - Core/String
1149  - Core/Array
1150  - MooTools.More
1151
1152provides: [String.Extras]
1153
1154...
1155*/
1156
1157(function(){
1158
1159var special = {
1160	'a': /[àáâãäåăą]/g,
1161	'A': /[ÀÁÂÃÄÅĂĄ]/g,
1162	'c': /[ćčç]/g,
1163	'C': /[ĆČÇ]/g,
1164	'd': /[ďđ]/g,
1165	'D': /[ĎÐ]/g,
1166	'e': /[èéêëěę]/g,
1167	'E': /[ÈÉÊËĚĘ]/g,
1168	'g': /[ğ]/g,
1169	'G': /[Ğ]/g,
1170	'i': /[ìíîï]/g,
1171	'I': /[ÌÍÎÏ]/g,
1172	'l': /[ĺľł]/g,
1173	'L': /[ĹĽŁ]/g,
1174	'n': /[ñňń]/g,
1175	'N': /[ÑŇŃ]/g,
1176	'o': /[òóôõöøő]/g,
1177	'O': /[ÒÓÔÕÖØ]/g,
1178	'r': /[řŕ]/g,
1179	'R': /[ŘŔ]/g,
1180	's': /[ššş]/g,
1181	'S': /[ŠŞŚ]/g,
1182	't': /[ťţ]/g,
1183	'T': /[ŤŢ]/g,
1184	'ue': /[ü]/g,
1185	'UE': /[Ü]/g,
1186	'u': /[ùúûůµ]/g,
1187	'U': /[ÙÚÛŮ]/g,
1188	'y': /[ÿý]/g,
1189	'Y': /[ŸÝ]/g,
1190	'z': /[žźż]/g,
1191	'Z': /[ŽŹŻ]/g,
1192	'th': /[þ]/g,
1193	'TH': /[Þ]/g,
1194	'dh': /[ð]/g,
1195	'DH': /[Ð]/g,
1196	'ss': /[ß]/g,
1197	'oe': /[œ]/g,
1198	'OE': /[Œ]/g,
1199	'ae': /[æ]/g,
1200	'AE': /[Æ]/g
1201},
1202
1203tidy = {
1204	' ': /[\xa0\u2002\u2003\u2009]/g,
1205	'*': /[\xb7]/g,
1206	'\'': /[\u2018\u2019]/g,
1207	'"': /[\u201c\u201d]/g,
1208	'...': /[\u2026]/g,
1209	'-': /[\u2013]/g,
1210//	'--': /[\u2014]/g,
1211	'&raquo;': /[\uFFFD]/g
1212};
1213
1214var walk = function(string, replacements){
1215	var result = string, key;
1216	for (key in replacements) result = result.replace(replacements[key], key);
1217	return result;
1218};
1219
1220var getRegexForTag = function(tag, contents){
1221	tag = tag || '';
1222	var regstr = contents ? "<" + tag + "(?!\\w)[^>]*>([\\s\\S]*?)<\/" + tag + "(?!\\w)>" : "<\/?" + tag + "([^>]+)?>",
1223		reg = new RegExp(regstr, "gi");
1224	return reg;
1225};
1226
1227String.implement({
1228
1229	standardize: function(){
1230		return walk(this, special);
1231	},
1232
1233	repeat: function(times){
1234		return new Array(times + 1).join(this);
1235	},
1236
1237	pad: function(length, str, direction){
1238		if (this.length >= length) return this;
1239
1240		var pad = (str == null ? ' ' : '' + str)
1241			.repeat(length - this.length)
1242			.substr(0, length - this.length);
1243
1244		if (!direction || direction == 'right') return this + pad;
1245		if (direction == 'left') return pad + this;
1246
1247		return pad.substr(0, (pad.length / 2).floor()) + this + pad.substr(0, (pad.length / 2).ceil());
1248	},
1249
1250	getTags: function(tag, contents){
1251		return this.match(getRegexForTag(tag, contents)) || [];
1252	},
1253
1254	stripTags: function(tag, contents){
1255		return this.replace(getRegexForTag(tag, contents), '');
1256	},
1257
1258	tidy: function(){
1259		return walk(this, tidy);
1260	},
1261
1262	truncate: function(max, trail, atChar){
1263		var string = this;
1264		if (trail == null && arguments.length == 1) trail = '…';
1265		if (string.length > max){
1266			string = string.substring(0, max);
1267			if (atChar){
1268				var index = string.lastIndexOf(atChar);
1269				if (index != -1) string = string.substr(0, index);
1270			}
1271			if (trail) string += trail;
1272		}
1273		return string;
1274	}
1275
1276});
1277
1278})();
1279
1280
1281/*
1282---
1283
1284script: String.QueryString.js
1285
1286name: String.QueryString
1287
1288description: Methods for dealing with URI query strings.
1289
1290license: MIT-style license
1291
1292authors:
1293  - Sebastian Markbåge
1294  - Aaron Newton
1295  - Lennart Pilon
1296  - Valerio Proietti
1297
1298requires:
1299  - Core/Array
1300  - Core/String
1301  - /MooTools.More
1302
1303provides: [String.QueryString]
1304
1305...
1306*/
1307
1308String.implement({
1309
1310	parseQueryString: function(decodeKeys, decodeValues){
1311		if (decodeKeys == null) decodeKeys = true;
1312		if (decodeValues == null) decodeValues = true;
1313
1314		var vars = this.split(/[&;]/),
1315			object = {};
1316		if (!vars.length) return object;
1317
1318		vars.each(function(val){
1319			var index = val.indexOf('=') + 1,
1320				value = index ? val.substr(index) : '',
1321				keys = index ? val.substr(0, index - 1).match(/([^\]\[]+|(\B)(?=\]))/g) : [val],
1322				obj = object;
1323			if (!keys) return;
1324			if (decodeValues) value = decodeURIComponent(value);
1325			keys.each(function(key, i){
1326				if (decodeKeys) key = decodeURIComponent(key);
1327				var current = obj[key];
1328
1329				if (i < keys.length - 1) obj = obj[key] = current || {};
1330				else if (typeOf(current) == 'array') current.push(value);
1331				else obj[key] = current != null ? [current, value] : value;
1332			});
1333		});
1334
1335		return object;
1336	},
1337
1338	cleanQueryString: function(method){
1339		return this.split('&').filter(function(val){
1340			var index = val.indexOf('='),
1341				key = index < 0 ? '' : val.substr(0, index),
1342				value = val.substr(index + 1);
1343
1344			return method ? method.call(null, key, value) : (value || value === 0);
1345		}).join('&');
1346	}
1347
1348});
1349
1350
1351/*
1352---
1353
1354name: Hash
1355
1356description: Contains Hash Prototypes. Provides a means for overcoming the JavaScript practical impossibility of extending native Objects.
1357
1358license: MIT-style license.
1359
1360requires:
1361  - Core/Object
1362  - /MooTools.More
1363
1364provides: [Hash]
1365
1366...
1367*/
1368
1369(function(){
1370
1371if (this.Hash) return;
1372
1373var Hash = this.Hash = new Type('Hash', function(object){
1374	if (typeOf(object) == 'hash') object = Object.clone(object.getClean());
1375	for (var key in object) this[key] = object[key];
1376	return this;
1377});
1378
1379this.$H = function(object){
1380	return new Hash(object);
1381};
1382
1383Hash.implement({
1384
1385	forEach: function(fn, bind){
1386		Object.forEach(this, fn, bind);
1387	},
1388
1389	getClean: function(){
1390		var clean = {};
1391		for (var key in this){
1392			if (this.hasOwnProperty(key)) clean[key] = this[key];
1393		}
1394		return clean;
1395	},
1396
1397	getLength: function(){
1398		var length = 0;
1399		for (var key in this){
1400			if (this.hasOwnProperty(key)) length++;
1401		}
1402		return length;
1403	}
1404
1405});
1406
1407Hash.alias('each', 'forEach');
1408
1409Hash.implement({
1410
1411	has: Object.prototype.hasOwnProperty,
1412
1413	keyOf: function(value){
1414		return Object.keyOf(this, value);
1415	},
1416
1417	hasValue: function(value){
1418		return Object.contains(this, value);
1419	},
1420
1421	extend: function(properties){
1422		Hash.each(properties || {}, function(value, key){
1423			Hash.set(this, key, value);
1424		}, this);
1425		return this;
1426	},
1427
1428	combine: function(properties){
1429		Hash.each(properties || {}, function(value, key){
1430			Hash.include(this, key, value);
1431		}, this);
1432		return this;
1433	},
1434
1435	erase: function(key){
1436		if (this.hasOwnProperty(key)) delete this[key];
1437		return this;
1438	},
1439
1440	get: function(key){
1441		return (this.hasOwnProperty(key)) ? this[key] : null;
1442	},
1443
1444	set: function(key, value){
1445		if (!this[key] || this.hasOwnProperty(key)) this[key] = value;
1446		return this;
1447	},
1448
1449	empty: function(){
1450		Hash.each(this, function(value, key){
1451			delete this[key];
1452		}, this);
1453		return this;
1454	},
1455
1456	include: function(key, value){
1457		if (this[key] == undefined) this[key] = value;
1458		return this;
1459	},
1460
1461	map: function(fn, bind){
1462		return new Hash(Object.map(this, fn, bind));
1463	},
1464
1465	filter: function(fn, bind){
1466		return new Hash(Object.filter(this, fn, bind));
1467	},
1468
1469	every: function(fn, bind){
1470		return Object.every(this, fn, bind);
1471	},
1472
1473	some: function(fn, bind){
1474		return Object.some(this, fn, bind);
1475	},
1476
1477	getKeys: function(){
1478		return Object.keys(this);
1479	},
1480
1481	getValues: function(){
1482		return Object.values(this);
1483	},
1484
1485	toQueryString: function(base){
1486		return Object.toQueryString(this, base);
1487	}
1488
1489});
1490
1491Hash.alias({indexOf: 'keyOf', contains: 'hasValue'});
1492
1493
1494})();
1495
1496
1497
1498/*
1499---
1500
1501script: Hash.Extras.js
1502
1503name: Hash.Extras
1504
1505description: Extends the Hash Type to include getFromPath which allows a path notation to child elements.
1506
1507license: MIT-style license
1508
1509authors:
1510  - Aaron Newton
1511
1512requires:
1513  - /Hash
1514  - /Object.Extras
1515
1516provides: [Hash.Extras]
1517
1518...
1519*/
1520
1521Hash.implement({
1522
1523	getFromPath: function(notation){
1524		return Object.getFromPath(this, notation);
1525	},
1526
1527	cleanValues: function(method){
1528		return new Hash(Object.cleanValues(this, method));
1529	},
1530
1531	run: function(){
1532		Object.run(arguments);
1533	}
1534
1535});
1536
1537
1538/*
1539---
1540
1541script: Element.Forms.js
1542
1543name: Element.Forms
1544
1545description: Extends the Element native object to include methods useful in managing inputs.
1546
1547license: MIT-style license
1548
1549authors:
1550  - Aaron Newton
1551
1552requires:
1553  - Core/Element
1554  - /String.Extras
1555  - /MooTools.More
1556
1557provides: [Element.Forms]
1558
1559...
1560*/
1561
1562Element.implement({
1563
1564	tidy: function(){
1565		this.set('value', this.get('value').tidy());
1566	},
1567
1568	getTextInRange: function(start, end){
1569		return this.get('value').substring(start, end);
1570	},
1571
1572	getSelectedText: function(){
1573		if (this.setSelectionRange) return this.getTextInRange(this.getSelectionStart(), this.getSelectionEnd());
1574		return document.selection.createRange().text;
1575	},
1576
1577	getSelectedRange: function(){
1578		if (this.selectionStart != null){
1579			return {
1580				start: this.selectionStart,
1581				end: this.selectionEnd
1582			};
1583		}
1584
1585		var pos = {
1586			start: 0,
1587			end: 0
1588		};
1589		var range = this.getDocument().selection.createRange();
1590		if (!range || range.parentElement() != this) return pos;
1591		var duplicate = range.duplicate();
1592
1593		if (this.type == 'text'){
1594			pos.start = 0 - duplicate.moveStart('character', -100000);
1595			pos.end = pos.start + range.text.length;
1596		} else {
1597			var value = this.get('value');
1598			var offset = value.length;
1599			duplicate.moveToElementText(this);
1600			duplicate.setEndPoint('StartToEnd', range);
1601			if (duplicate.text.length) offset -= value.match(/[\n\r]*$/)[0].length;
1602			pos.end = offset - duplicate.text.length;
1603			duplicate.setEndPoint('StartToStart', range);
1604			pos.start = offset - duplicate.text.length;
1605		}
1606		return pos;
1607	},
1608
1609	getSelectionStart: function(){
1610		return this.getSelectedRange().start;
1611	},
1612
1613	getSelectionEnd: function(){
1614		return this.getSelectedRange().end;
1615	},
1616
1617	setCaretPosition: function(pos){
1618		if (pos == 'end') pos = this.get('value').length;
1619		this.selectRange(pos, pos);
1620		return this;
1621	},
1622
1623	getCaretPosition: function(){
1624		return this.getSelectedRange().start;
1625	},
1626
1627	selectRange: function(start, end){
1628		if (this.setSelectionRange){
1629			this.focus();
1630			this.setSelectionRange(start, end);
1631		} else {
1632			var value = this.get('value');
1633			var diff = value.substr(start, end - start).replace(/\r/g, '').length;
1634			start = value.substr(0, start).replace(/\r/g, '').length;
1635			var range = this.createTextRange();
1636			range.collapse(true);
1637			range.moveEnd('character', start + diff);
1638			range.moveStart('character', start);
1639			range.select();
1640		}
1641		return this;
1642	},
1643
1644	insertAtCursor: function(value, select){
1645		var pos = this.getSelectedRange();
1646		var text = this.get('value');
1647		this.set('value', text.substring(0, pos.start) + value + text.substring(pos.end, text.length));
1648		if (select !== false) this.selectRange(pos.start, pos.start + value.length);
1649		else this.setCaretPosition(pos.start + value.length);
1650		return this;
1651	},
1652
1653	insertAroundCursor: function(options, select){
1654		options = Object.append({
1655			before: '',
1656			defaultMiddle: '',
1657			after: ''
1658		}, options);
1659
1660		var value = this.getSelectedText() || options.defaultMiddle;
1661		var pos = this.getSelectedRange();
1662		var text = this.get('value');
1663
1664		if (pos.start == pos.end){
1665			this.set('value', text.substring(0, pos.start) + options.before + value + options.after + text.substring(pos.end, text.length));
1666			this.selectRange(pos.start + options.before.length, pos.end + options.before.length + value.length);
1667		} else {
1668			var current = text.substring(pos.start, pos.end);
1669			this.set('value', text.substring(0, pos.start) + options.before + current + options.after + text.substring(pos.end, text.length));
1670			var selStart = pos.start + options.before.length;
1671			if (select !== false) this.selectRange(selStart, selStart + current.length);
1672			else this.setCaretPosition(selStart + text.length);
1673		}
1674		return this;
1675	}
1676
1677});
1678
1679
1680/*
1681---
1682
1683script: Elements.From.js
1684
1685name: Elements.From
1686
1687description: Returns a collection of elements from a string of html.
1688
1689license: MIT-style license
1690
1691authors:
1692  - Aaron Newton
1693
1694requires:
1695  - Core/String
1696  - Core/Element
1697  - /MooTools.More
1698
1699provides: [Elements.from, Elements.From]
1700
1701...
1702*/
1703
1704Elements.from = function(text, excludeScripts){
1705	if (excludeScripts || excludeScripts == null) text = text.stripScripts();
1706
1707	var container, match = text.match(/^\s*<(t[dhr]|tbody|tfoot|thead)/i);
1708
1709	if (match){
1710		container = new Element('table');
1711		var tag = match[1].toLowerCase();
1712		if (['td', 'th', 'tr'].contains(tag)){
1713			container = new Element('tbody').inject(container);
1714			if (tag != 'tr') container = new Element('tr').inject(container);
1715		}
1716	}
1717
1718	return (container || new Element('div')).set('html', text).getChildren();
1719};
1720
1721
1722/*
1723---
1724
1725name: Events.Pseudos
1726
1727description: Adds the functionality to add pseudo events
1728
1729license: MIT-style license
1730
1731authors:
1732  - Arian Stolwijk
1733
1734requires: [Core/Class.Extras, Core/Slick.Parser, More/MooTools.More]
1735
1736provides: [Events.Pseudos]
1737
1738...
1739*/
1740
1741(function(){
1742
1743Events.Pseudos = function(pseudos, addEvent, removeEvent){
1744
1745	var storeKey = '_monitorEvents:';
1746
1747	var storageOf = function(object){
1748		return {
1749			store: object.store ? function(key, value){
1750				object.store(storeKey + key, value);
1751			} : function(key, value){
1752				(object._monitorEvents || (object._monitorEvents = {}))[key] = value;
1753			},
1754			retrieve: object.retrieve ? function(key, dflt){
1755				return object.retrieve(storeKey + key, dflt);
1756			} : function(key, dflt){
1757				if (!object._monitorEvents) return dflt;
1758				return object._monitorEvents[key] || dflt;
1759			}
1760		};
1761	};
1762
1763	var splitType = function(type){
1764		if (type.indexOf(':') == -1 || !pseudos) return null;
1765
1766		var parsed = Slick.parse(type).expressions[0][0],
1767			parsedPseudos = parsed.pseudos,
1768			l = parsedPseudos.length,
1769			splits = [];
1770
1771		while (l--){
1772			var pseudo = parsedPseudos[l].key,
1773				listener = pseudos[pseudo];
1774			if (listener != null) splits.push({
1775				event: parsed.tag,
1776				value: parsedPseudos[l].value,
1777				pseudo: pseudo,
1778				original: type,
1779				listener: listener
1780			});
1781		}
1782		return splits.length ? splits : null;
1783	};
1784
1785	return {
1786
1787		addEvent: function(type, fn, internal){
1788			var split = splitType(type);
1789			if (!split) return addEvent.call(this, type, fn, internal);
1790
1791			var storage = storageOf(this),
1792				events = storage.retrieve(type, []),
1793				eventType = split[0].event,
1794				args = Array.slice(arguments, 2),
1795				stack = fn,
1796				self = this;
1797
1798			split.each(function(item){
1799				var listener = item.listener,
1800					stackFn = stack;
1801				if (listener == false) eventType += ':' + item.pseudo + '(' + item.value + ')';
1802				else stack = function(){
1803					listener.call(self, item, stackFn, arguments, stack);
1804				};
1805			});
1806
1807			events.include({type: eventType, event: fn, monitor: stack});
1808			storage.store(type, events);
1809
1810			if (type != eventType) addEvent.apply(this, [type, fn].concat(args));
1811			return addEvent.apply(this, [eventType, stack].concat(args));
1812		},
1813
1814		removeEvent: function(type, fn){
1815			var split = splitType(type);
1816			if (!split) return removeEvent.call(this, type, fn);
1817
1818			var storage = storageOf(this),
1819				events = storage.retrieve(type);
1820			if (!events) return this;
1821
1822			var args = Array.slice(arguments, 2);
1823
1824			removeEvent.apply(this, [type, fn].concat(args));
1825			events.each(function(monitor, i){
1826				if (!fn || monitor.event == fn) removeEvent.apply(this, [monitor.type, monitor.monitor].concat(args));
1827				delete events[i];
1828			}, this);
1829
1830			storage.store(type, events);
1831			return this;
1832		}
1833
1834	};
1835
1836};
1837
1838var pseudos = {
1839
1840	once: function(split, fn, args, monitor){
1841		fn.apply(this, args);
1842		this.removeEvent(split.event, monitor)
1843			.removeEvent(split.original, fn);
1844	},
1845
1846	throttle: function(split, fn, args){
1847		if (!fn._throttled){
1848			fn.apply(this, args);
1849			fn._throttled = setTimeout(function(){
1850				fn._throttled = false;
1851			}, split.value || 250);
1852		}
1853	},
1854
1855	pause: function(split, fn, args){
1856		clearTimeout(fn._pause);
1857		fn._pause = fn.delay(split.value || 250, this, args);
1858	}
1859
1860};
1861
1862Events.definePseudo = function(key, listener){
1863	pseudos[key] = listener;
1864	return this;
1865};
1866
1867Events.lookupPseudo = function(key){
1868	return pseudos[key];
1869};
1870
1871var proto = Events.prototype;
1872Events.implement(Events.Pseudos(pseudos, proto.addEvent, proto.removeEvent));
1873
1874['Request', 'Fx'].each(function(klass){
1875	if (this[klass]) this[klass].implement(Events.prototype);
1876});
1877
1878})();
1879
1880
1881/*
1882---
1883
1884name: Element.Event.Pseudos
1885
1886description: Adds the functionality to add pseudo events for Elements
1887
1888license: MIT-style license
1889
1890authors:
1891  - Arian Stolwijk
1892
1893requires: [Core/Element.Event, Core/Element.Delegation, Events.Pseudos]
1894
1895provides: [Element.Event.Pseudos, Element.Delegation]
1896
1897...
1898*/
1899
1900(function(){
1901
1902var pseudos = {relay: false},
1903	copyFromEvents = ['once', 'throttle', 'pause'],
1904	count = copyFromEvents.length;
1905
1906while (count--) pseudos[copyFromEvents[count]] = Events.lookupPseudo(copyFromEvents[count]);
1907
1908DOMEvent.definePseudo = function(key, listener){
1909	pseudos[key] = listener;
1910	return this;
1911};
1912
1913var proto = Element.prototype;
1914[Element, Window, Document].invoke('implement', Events.Pseudos(pseudos, proto.addEvent, proto.removeEvent));
1915
1916})();
1917
1918
1919/*
1920---
1921
1922name: Element.Event.Pseudos.Keys
1923
1924description: Adds functionality fire events if certain keycombinations are pressed
1925
1926license: MIT-style license
1927
1928authors:
1929  - Arian Stolwijk
1930
1931requires: [Element.Event.Pseudos]
1932
1933provides: [Element.Event.Pseudos.Keys]
1934
1935...
1936*/
1937
1938(function(){
1939
1940var keysStoreKey = '$moo:keys-pressed',
1941	keysKeyupStoreKey = '$moo:keys-keyup';
1942
1943
1944DOMEvent.definePseudo('keys', function(split, fn, args){
1945
1946	var event = args[0],
1947		keys = [],
1948		pressed = this.retrieve(keysStoreKey, []);
1949
1950	keys.append(split.value.replace('++', function(){
1951		keys.push('+'); // shift++ and shift+++a
1952		return '';
1953	}).split('+'));
1954
1955	pressed.include(event.key);
1956
1957	if (keys.every(function(key){
1958		return pressed.contains(key);
1959	})) fn.apply(this, args);
1960
1961	this.store(keysStoreKey, pressed);
1962
1963	if (!this.retrieve(keysKeyupStoreKey)){
1964		var keyup = function(event){
1965			(function(){
1966				pressed = this.retrieve(keysStoreKey, []).erase(event.key);
1967				this.store(keysStoreKey, pressed);
1968			}).delay(0, this); // Fix for IE
1969		};
1970		this.store(keysKeyupStoreKey, keyup).addEvent('keyup', keyup);
1971	}
1972
1973});
1974
1975DOMEvent.defineKeys({
1976	'16': 'shift',
1977	'17': 'control',
1978	'18': 'alt',
1979	'20': 'capslock',
1980	'33': 'pageup',
1981	'34': 'pagedown',
1982	'35': 'end',
1983	'36': 'home',
1984	'144': 'numlock',
1985	'145': 'scrolllock',
1986	'186': ';',
1987	'187': '=',
1988	'188': ',',
1989	'190': '.',
1990	'191': '/',
1991	'192': '`',
1992	'219': '[',
1993	'220': '\\',
1994	'221': ']',
1995	'222': "'",
1996	'107': '+'
1997}).defineKey(Browser.firefox ? 109 : 189, '-');
1998
1999})();
2000
2001
2002/*
2003---
2004
2005script: Element.Measure.js
2006
2007name: Element.Measure
2008
2009description: Extends the Element native object to include methods useful in measuring dimensions.
2010
2011credits: "Element.measure / .expose methods by Daniel Steigerwald License: MIT-style license. Copyright: Copyright (c) 2008 Daniel Steigerwald, daniel.steigerwald.cz"
2012
2013license: MIT-style license
2014
2015authors:
2016  - Aaron Newton
2017
2018requires:
2019  - Core/Element.Style
2020  - Core/Element.Dimensions
2021  - /MooTools.More
2022
2023provides: [Element.Measure]
2024
2025...
2026*/
2027
2028(function(){
2029
2030var getStylesList = function(styles, planes){
2031	var list = [];
2032	Object.each(planes, function(directions){
2033		Object.each(directions, function(edge){
2034			styles.each(function(style){
2035				list.push(style + '-' + edge + (style == 'border' ? '-width' : ''));
2036			});
2037		});
2038	});
2039	return list;
2040};
2041
2042var calculateEdgeSize = function(edge, styles){
2043	var total = 0;
2044	Object.each(styles, function(value, style){
2045		if (style.test(edge)) total = total + value.toInt();
2046	});
2047	return total;
2048};
2049
2050var isVisible = function(el){
2051	return !!(!el || el.offsetHeight || el.offsetWidth);
2052};
2053
2054
2055Element.implement({
2056
2057	measure: function(fn){
2058		if (isVisible(this)) return fn.call(this);
2059		var parent = this.getParent(),
2060			toMeasure = [];
2061		while (!isVisible(parent) && parent != document.body){
2062			toMeasure.push(parent.expose());
2063			parent = parent.getParent();
2064		}
2065		var restore = this.expose(),
2066			result = fn.call(this);
2067		restore();
2068		toMeasure.each(function(restore){
2069			restore();
2070		});
2071		return result;
2072	},
2073
2074	expose: function(){
2075		if (this.getStyle('display') != 'none') return function(){};
2076		var before = this.style.cssText;
2077		this.setStyles({
2078			display: 'block',
2079			position: 'absolute',
2080			visibility: 'hidden'
2081		});
2082		return function(){
2083			this.style.cssText = before;
2084		}.bind(this);
2085	},
2086
2087	getDimensions: function(options){
2088		options = Object.merge({computeSize: false}, options);
2089		var dim = {x: 0, y: 0};
2090
2091		var getSize = function(el, options){
2092			return (options.computeSize) ? el.getComputedSize(options) : el.getSize();
2093		};
2094
2095		var parent = this.getParent('body');
2096
2097		if (parent && this.getStyle('display') == 'none'){
2098			dim = this.measure(function(){
2099				return getSize(this, options);
2100			});
2101		} else if (parent){
2102			try { //safari sometimes crashes here, so catch it
2103				dim = getSize(this, options);
2104			}catch(e){}
2105		}
2106
2107		return Object.append(dim, (dim.x || dim.x === 0) ? {
2108				width: dim.x,
2109				height: dim.y
2110			} : {
2111				x: dim.width,
2112				y: dim.height
2113			}
2114		);
2115	},
2116
2117	getComputedSize: function(options){
2118		//<1.2compat>
2119		//legacy support for my stupid spelling error
2120		if (options && options.plains) options.planes = options.plains;
2121		//</1.2compat>
2122
2123		options = Object.merge({
2124			styles: ['padding','border'],
2125			planes: {
2126				height: ['top','bottom'],
2127				width: ['left','right']
2128			},
2129			mode: 'both'
2130		}, options);
2131
2132		var styles = {},
2133			size = {width: 0, height: 0},
2134			dimensions;
2135
2136		if (options.mode == 'vertical'){
2137			delete size.width;
2138			delete options.planes.width;
2139		} else if (options.mode == 'horizontal'){
2140			delete size.height;
2141			delete options.planes.height;
2142		}
2143
2144		getStylesList(options.styles, options.planes).each(function(style){
2145			styles[style] = this.getStyle(style).toInt();
2146		}, this);
2147
2148		Object.each(options.planes, function(edges, plane){
2149
2150			var capitalized = plane.capitalize(),
2151				style = this.getStyle(plane);
2152
2153			if (style == 'auto' && !dimensions) dimensions = this.getDimensions();
2154
2155			style = styles[plane] = (style == 'auto') ? dimensions[plane] : style.toInt();
2156			size['total' + capitalized] = style;
2157
2158			edges.each(function(edge){
2159				var edgesize = calculateEdgeSize(edge, styles);
2160				size['computed' + edge.capitalize()] = edgesize;
2161				size['total' + capitalized] += edgesize;
2162			});
2163
2164		}, this);
2165
2166		return Object.append(size, styles);
2167	}
2168
2169});
2170
2171})();
2172
2173
2174/*
2175---
2176
2177script: Element.Pin.js
2178
2179name: Element.Pin
2180
2181description: Extends the Element native object to include the pin method useful for fixed positioning for elements.
2182
2183license: MIT-style license
2184
2185authors:
2186  - Aaron Newton
2187
2188requires:
2189  - Core/Element.Event
2190  - Core/Element.Dimensions
2191  - Core/Element.Style
2192  - /MooTools.More
2193
2194provides: [Element.Pin]
2195
2196...
2197*/
2198
2199(function(){
2200	var supportsPositionFixed = false,
2201		supportTested = false;
2202
2203	var testPositionFixed = function(){
2204		var test = new Element('div').setStyles({
2205			position: 'fixed',
2206			top: 0,
2207			right: 0
2208		}).inject(document.body);
2209		supportsPositionFixed = (test.offsetTop === 0);
2210		test.dispose();
2211		supportTested = true;
2212	};
2213
2214	Element.implement({
2215
2216		pin: function(enable, forceScroll){
2217			if (!supportTested) testPositionFixed();
2218			if (this.getStyle('display') == 'none') return this;
2219
2220			var pinnedPosition,
2221				scroll = window.getScroll(),
2222				parent,
2223				scrollFixer;
2224
2225			if (enable !== false){
2226				pinnedPosition = this.getPosition(supportsPositionFixed ? document.body : this.getOffsetParent());
2227				if (!this.retrieve('pin:_pinned')){
2228					var currentPosition = {
2229						top: pinnedPosition.y - scroll.y,
2230						left: pinnedPosition.x - scroll.x
2231					};
2232
2233					if (supportsPositionFixed && !forceScroll){
2234						this.setStyle('position', 'fixed').setStyles(currentPosition);
2235					} else {
2236
2237						parent = this.getOffsetParent();
2238						var position = this.getPosition(parent),
2239							styles = this.getStyles('left', 'top');
2240
2241						if (parent && styles.left == 'auto' || styles.top == 'auto') this.setPosition(position);
2242						if (this.getStyle('position') == 'static') this.setStyle('position', 'absolute');
2243
2244						position = {
2245							x: styles.left.toInt() - scroll.x,
2246							y: styles.top.toInt() - scroll.y
2247						};
2248
2249						scrollFixer = function(){
2250							if (!this.retrieve('pin:_pinned')) return;
2251							var scroll = window.getScroll();
2252							this.setStyles({
2253								left: position.x + scroll.x,
2254								top: position.y + scroll.y
2255							});
2256						}.bind(this);
2257
2258						this.store('pin:_scrollFixer', scrollFixer);
2259						window.addEvent('scroll', scrollFixer);
2260					}
2261					this.store('pin:_pinned', true);
2262				}
2263
2264			} else {
2265				if (!this.retrieve('pin:_pinned')) return this;
2266
2267				parent = this.getParent();
2268				var offsetParent = (parent.getComputedStyle('position') != 'static' ? parent : parent.getOffsetParent());
2269
2270				pinnedPosition = this.getPosition(offsetParent);
2271
2272				this.store('pin:_pinned', false);
2273				scrollFixer = this.retrieve('pin:_scrollFixer');
2274				if (!scrollFixer){
2275					this.setStyles({
2276						position: 'absolute',
2277						top: pinnedPosition.y + scroll.y,
2278						left: pinnedPosition.x + scroll.x
2279					});
2280				} else {
2281					this.store('pin:_scrollFixer', null);
2282					window.removeEvent('scroll', scrollFixer);
2283				}
2284				this.removeClass('isPinned');
2285			}
2286			return this;
2287		},
2288
2289		unpin: function(){
2290			return this.pin(false);
2291		},
2292
2293		togglePin: function(){
2294			return this.pin(!this.retrieve('pin:_pinned'));
2295		}
2296
2297	});
2298
2299//<1.2compat>
2300Element.alias('togglepin', 'togglePin');
2301//</1.2compat>
2302
2303})();
2304
2305
2306/*
2307---
2308
2309script: Element.Position.js
2310
2311name: Element.Position
2312
2313description: Extends the Element native object to include methods useful positioning elements relative to others.
2314
2315license: MIT-style license
2316
2317authors:
2318  - Aaron Newton
2319  - Jacob Thornton
2320
2321requires:
2322  - Core/Options
2323  - Core/Element.Dimensions
2324  - Element.Measure
2325
2326provides: [Element.Position]
2327
2328...
2329*/
2330
2331(function(original){
2332
2333var local = Element.Position = {
2334
2335	options: {/*
2336		edge: false,
2337		returnPos: false,
2338		minimum: {x: 0, y: 0},
2339		maximum: {x: 0, y: 0},
2340		relFixedPosition: false,
2341		ignoreMargins: false,
2342		ignoreScroll: false,
2343		allowNegative: false,*/
2344		relativeTo: document.body,
2345		position: {
2346			x: 'center', //left, center, right
2347			y: 'center' //top, center, bottom
2348		},
2349		offset: {x: 0, y: 0}
2350	},
2351
2352	getOptions: function(element, options){
2353		options = Object.merge({}, local.options, options);
2354		local.setPositionOption(options);
2355		local.setEdgeOption(options);
2356		local.setOffsetOption(element, options);
2357		local.setDimensionsOption(element, options);
2358		return options;
2359	},
2360
2361	setPositionOption: function(options){
2362		options.position = local.getCoordinateFromValue(options.position);
2363	},
2364
2365	setEdgeOption: function(options){
2366		var edgeOption = local.getCoordinateFromValue(options.edge);
2367		options.edge = edgeOption ? edgeOption :
2368			(options.position.x == 'center' && options.position.y == 'center') ? {x: 'center', y: 'center'} :
2369			{x: 'left', y: 'top'};
2370	},
2371
2372	setOffsetOption: function(element, options){
2373		var parentOffset = {x: 0, y: 0},
2374			offsetParent = element.measure(function(){
2375				return document.id(this.getOffsetParent());
2376			}),
2377			parentScroll = offsetParent.getScroll();
2378
2379		if (!offsetParent || offsetParent == element.getDocument().body) return;
2380		parentOffset = offsetParent.measure(function(){
2381			var position = this.getPosition();
2382			if (this.getStyle('position') == 'fixed'){
2383				var scroll = window.getScroll();
2384				position.x += scroll.x;
2385				position.y += scroll.y;
2386			}
2387			return position;
2388		});
2389
2390		options.offset = {
2391			parentPositioned: offsetParent != document.id(options.relativeTo),
2392			x: options.offset.x - parentOffset.x + parentScroll.x,
2393			y: options.offset.y - parentOffset.y + parentScroll.y
2394		};
2395	},
2396
2397	setDimensionsOption: function(element, options){
2398		options.dimensions = element.getDimensions({
2399			computeSize: true,
2400			styles: ['padding', 'border', 'margin']
2401		});
2402	},
2403
2404	getPosition: function(element, options){
2405		var position = {};
2406		options = local.getOptions(element, options);
2407		var relativeTo = document.id(options.relativeTo) || document.body;
2408
2409		local.setPositionCoordinates(options, position, relativeTo);
2410		if (options.edge) local.toEdge(position, options);
2411
2412		var offset = options.offset;
2413		position.left = ((position.x >= 0 || offset.parentPositioned || options.allowNegative) ? position.x : 0).toInt();
2414		position.top = ((position.y >= 0 || offset.parentPositioned || options.allowNegative) ? position.y : 0).toInt();
2415
2416		local.toMinMax(position, options);
2417
2418		if (options.relFixedPosition || relativeTo.getStyle('position') == 'fixed') local.toRelFixedPosition(relativeTo, position);
2419		if (options.ignoreScroll) local.toIgnoreScroll(relativeTo, position);
2420		if (options.ignoreMargins) local.toIgnoreMargins(position, options);
2421
2422		position.left = Math.ceil(position.left);
2423		position.top = Math.ceil(position.top);
2424		delete position.x;
2425		delete position.y;
2426
2427		return position;
2428	},
2429
2430	setPositionCoordinates: function(options, position, relativeTo){
2431		var offsetY = options.offset.y,
2432			offsetX = options.offset.x,
2433			calc = (relativeTo == document.body) ? window.getScroll() : relativeTo.getPosition(),
2434			top = calc.y,
2435			left = calc.x,
2436			winSize = window.getSize();
2437
2438		switch(options.position.x){
2439			case 'left': position.x = left + offsetX; break;
2440			case 'right': position.x = left + offsetX + relativeTo.offsetWidth; break;
2441			default: position.x = left + ((relativeTo == document.body ? winSize.x : relativeTo.offsetWidth) / 2) + offsetX; break;
2442		}
2443
2444		switch(options.position.y){
2445			case 'top': position.y = top + offsetY; break;
2446			case 'bottom': position.y = top + offsetY + relativeTo.offsetHeight; break;
2447			default: position.y = top + ((relativeTo == document.body ? winSize.y : relativeTo.offsetHeight) / 2) + offsetY; break;
2448		}
2449	},
2450
2451	toMinMax: function(position, options){
2452		var xy = {left: 'x', top: 'y'}, value;
2453		['minimum', 'maximum'].each(function(minmax){
2454			['left', 'top'].each(function(lr){
2455				value = options[minmax] ? options[minmax][xy[lr]] : null;
2456				if (value != null && ((minmax == 'minimum') ? position[lr] < value : position[lr] > value)) position[lr] = value;
2457			});
2458		});
2459	},
2460
2461	toRelFixedPosition: function(relativeTo, position){
2462		var winScroll = window.getScroll();
2463		position.top += winScroll.y;
2464		position.left += winScroll.x;
2465	},
2466
2467	toIgnoreScroll: function(relativeTo, position){
2468		var relScroll = relativeTo.getScroll();
2469		position.top -= relScroll.y;
2470		position.left -= relScroll.x;
2471	},
2472
2473	toIgnoreMargins: function(position, options){
2474		position.left += options.edge.x == 'right'
2475			? options.dimensions['margin-right']
2476			: (options.edge.x != 'center'
2477				? -options.dimensions['margin-left']
2478				: -options.dimensions['margin-left'] + ((options.dimensions['margin-right'] + options.dimensions['margin-left']) / 2));
2479
2480		position.top += options.edge.y == 'bottom'
2481			? options.dimensions['margin-bottom']
2482			: (options.edge.y != 'center'
2483				? -options.dimensions['margin-top']
2484				: -options.dimensions['margin-top'] + ((options.dimensions['margin-bottom'] + options.dimensions['margin-top']) / 2));
2485	},
2486
2487	toEdge: function(position, options){
2488		var edgeOffset = {},
2489			dimensions = options.dimensions,
2490			edge = options.edge;
2491
2492		switch(edge.x){
2493			case 'left': edgeOffset.x = 0; break;
2494			case 'right': edgeOffset.x = -dimensions.x - dimensions.computedRight - dimensions.computedLeft; break;
2495			// center
2496			default: edgeOffset.x = -(Math.round(dimensions.totalWidth / 2)); break;
2497		}
2498
2499		switch(edge.y){
2500			case 'top': edgeOffset.y = 0; break;
2501			case 'bottom': edgeOffset.y = -dimensions.y - dimensions.computedTop - dimensions.computedBottom; break;
2502			// center
2503			default: edgeOffset.y = -(Math.round(dimensions.totalHeight / 2)); break;
2504		}
2505
2506		position.x += edgeOffset.x;
2507		position.y += edgeOffset.y;
2508	},
2509
2510	getCoordinateFromValue: function(option){
2511		if (typeOf(option) != 'string') return option;
2512		option = option.toLowerCase();
2513
2514		return {
2515			x: option.test('left') ? 'left'
2516				: (option.test('right') ? 'right' : 'center'),
2517			y: option.test(/upper|top/) ? 'top'
2518				: (option.test('bottom') ? 'bottom' : 'center')
2519		};
2520	}
2521
2522};
2523
2524Element.implement({
2525
2526	position: function(options){
2527		if (options && (options.x != null || options.y != null)){
2528			return (original ? original.apply(this, arguments) : this);
2529		}
2530		var position = this.setStyle('position', 'absolute').calculatePosition(options);
2531		return (options && options.returnPos) ? position : this.setStyles(position);
2532	},
2533
2534	calculatePosition: function(options){
2535		return local.getPosition(this, options);
2536	}
2537
2538});
2539
2540})(Element.prototype.position);
2541
2542
2543/*
2544---
2545
2546script: Element.Shortcuts.js
2547
2548name: Element.Shortcuts
2549
2550description: Extends the Element native object to include some shortcut methods.
2551
2552license: MIT-style license
2553
2554authors:
2555  - Aaron Newton
2556
2557requires:
2558  - Core/Element.Style
2559  - /MooTools.More
2560
2561provides: [Element.Shortcuts]
2562
2563...
2564*/
2565
2566Element.implement({
2567
2568	isDisplayed: function(){
2569		return this.getStyle('display') != 'none';
2570	},
2571
2572	isVisible: function(){
2573		var w = this.offsetWidth,
2574			h = this.offsetHeight;
2575		return (w == 0 && h == 0) ? false : (w > 0 && h > 0) ? true : this.style.display != 'none';
2576	},
2577
2578	toggle: function(){
2579		return this[this.isDisplayed() ? 'hide' : 'show']();
2580	},
2581
2582	hide: function(){
2583		var d;
2584		try {
2585			//IE fails here if the element is not in the dom
2586			d = this.getStyle('display');
2587		} catch(e){}
2588		if (d == 'none') return this;
2589		return this.store('element:_originalDisplay', d || '').setStyle('display', 'none');
2590	},
2591
2592	show: function(display){
2593		if (!display && this.isDisplayed()) return this;
2594		display = display || this.retrieve('element:_originalDisplay') || 'block';
2595		return this.setStyle('display', (display == 'none') ? 'block' : display);
2596	},
2597
2598	swapClass: function(remove, add){
2599		return this.removeClass(remove).addClass(add);
2600	}
2601
2602});
2603
2604Document.implement({
2605
2606	clearSelection: function(){
2607		if (window.getSelection){
2608			var selection = window.getSelection();
2609			if (selection && selection.removeAllRanges) selection.removeAllRanges();
2610		} else if (document.selection && document.selection.empty){
2611			try {
2612				//IE fails here if selected element is not in dom
2613				document.selection.empty();
2614			} catch(e){}
2615		}
2616	}
2617
2618});
2619
2620
2621/*
2622---
2623
2624script: Class.Binds.js
2625
2626name: Class.Binds
2627
2628description: Automagically binds specified methods in a class to the instance of the class.
2629
2630license: MIT-style license
2631
2632authors:
2633  - Aaron Newton
2634
2635requires:
2636  - Core/Class
2637  - /MooTools.More
2638
2639provides: [Class.Binds]
2640
2641...
2642*/
2643
2644Class.Mutators.Binds = function(binds){
2645	if (!this.prototype.initialize) this.implement('initialize', function(){});
2646	return Array.from(binds).concat(this.prototype.Binds || []);
2647};
2648
2649Class.Mutators.initialize = function(initialize){
2650	return function(){
2651		Array.from(this.Binds).each(function(name){
2652			var original = this[name];
2653			if (original) this[name] = original.bind(this);
2654		}, this);
2655		return initialize.apply(this, arguments);
2656	};
2657};
2658
2659
2660/*
2661---
2662
2663script: Class.Occlude.js
2664
2665name: Class.Occlude
2666
2667description: Prevents a class from being applied to a DOM element twice.
2668
2669license: MIT-style license.
2670
2671authors:
2672  - Aaron Newton
2673
2674requires:
2675  - Core/Class
2676  - Core/Element
2677  - /MooTools.More
2678
2679provides: [Class.Occlude]
2680
2681...
2682*/
2683
2684Class.Occlude = new Class({
2685
2686	occlude: function(property, element){
2687		element = document.id(element || this.element);
2688		var instance = element.retrieve(property || this.property);
2689		if (instance && !this.occluded)
2690			return (this.occluded = instance);
2691
2692		this.occluded = false;
2693		element.store(property || this.property, this);
2694		return this.occluded;
2695	}
2696
2697});
2698
2699
2700/*
2701---
2702
2703script: Class.Refactor.js
2704
2705name: Class.Refactor
2706
2707description: Extends a class onto itself with new property, preserving any items attached to the class's namespace.
2708
2709license: MIT-style license
2710
2711authors:
2712  - Aaron Newton
2713
2714requires:
2715  - Core/Class
2716  - /MooTools.More
2717
2718# Some modules declare themselves dependent on Class.Refactor
2719provides: [Class.refactor, Class.Refactor]
2720
2721...
2722*/
2723
2724Class.refactor = function(original, refactors){
2725
2726	Object.each(refactors, function(item, name){
2727		var origin = original.prototype[name];
2728		origin = (origin && origin.$origin) || origin || function(){};
2729		original.implement(name, (typeof item == 'function') ? function(){
2730			var old = this.previous;
2731			this.previous = origin;
2732			var value = item.apply(this, arguments);
2733			this.previous = old;
2734			return value;
2735		} : item);
2736	});
2737
2738	return original;
2739
2740};
2741
2742
2743/*
2744---
2745
2746script: IframeShim.js
2747
2748name: IframeShim
2749
2750description: Defines IframeShim, a class for obscuring select lists and flash objects in IE.
2751
2752license: MIT-style license
2753
2754authors:
2755  - Aaron Newton
2756
2757requires:
2758  - Core/Element.Event
2759  - Core/Element.Style
2760  - Core/Options
2761  - Core/Events
2762  - /Element.Position
2763  - /Class.Occlude
2764
2765provides: [IframeShim]
2766
2767...
2768*/
2769
2770var IframeShim = new Class({
2771
2772	Implements: [Options, Events, Class.Occlude],
2773
2774	options: {
2775		className: 'iframeShim',
2776		src: 'javascript:false;document.write("");',
2777		display: false,
2778		zIndex: null,
2779		margin: 0,
2780		offset: {x: 0, y: 0},
2781		browsers: (Browser.ie6 || (Browser.firefox && Browser.version < 3 && Browser.Platform.mac))
2782	},
2783
2784	property: 'IframeShim',
2785
2786	initialize: function(element, options){
2787		this.element = document.id(element);
2788		if (this.occlude()) return this.occluded;
2789		this.setOptions(options);
2790		this.makeShim();
2791		return this;
2792	},
2793
2794	makeShim: function(){
2795		if (this.options.browsers){
2796			var zIndex = this.element.getStyle('zIndex').toInt();
2797
2798			if (!zIndex){
2799				zIndex = 1;
2800				var pos = this.element.getStyle('position');
2801				if (pos == 'static' || !pos) this.element.setStyle('position', 'relative');
2802				this.element.setStyle('zIndex', zIndex);
2803			}
2804			zIndex = ((this.options.zIndex != null || this.options.zIndex === 0) && zIndex > this.options.zIndex) ? this.options.zIndex : zIndex - 1;
2805			if (zIndex < 0) zIndex = 1;
2806			this.shim = new Element('iframe', {
2807				src: this.options.src,
2808				scrolling: 'no',
2809				frameborder: 0,
2810				styles: {
2811					zIndex: zIndex,
2812					position: 'absolute',
2813					border: 'none',
2814					filter: 'progid:DXImageTransform.Microsoft.Alpha(style=0,opacity=0)'
2815				},
2816				'class': this.options.className
2817			}).store('IframeShim', this);
2818			var inject = (function(){
2819				this.shim.inject(this.element, 'after');
2820				this[this.options.display ? 'show' : 'hide']();
2821				this.fireEvent('inject');
2822			}).bind(this);
2823			if (!IframeShim.ready) window.addEvent('load', inject);
2824			else inject();
2825		} else {
2826			this.position = this.hide = this.show = this.dispose = Function.from(this);
2827		}
2828	},
2829
2830	position: function(){
2831		if (!IframeShim.ready || !this.shim) return this;
2832		var size = this.element.measure(function(){
2833			return this.getSize();
2834		});
2835		if (this.options.margin != undefined){
2836			size.x = size.x - (this.options.margin * 2);
2837			size.y = size.y - (this.options.margin * 2);
2838			this.options.offset.x += this.options.margin;
2839			this.options.offset.y += this.options.margin;
2840		}
2841		this.shim.set({width: size.x, height: size.y}).position({
2842			relativeTo: this.element,
2843			offset: this.options.offset
2844		});
2845		return this;
2846	},
2847
2848	hide: function(){
2849		if (this.shim) this.shim.setStyle('display', 'none');
2850		return this;
2851	},
2852
2853	show: function(){
2854		if (this.shim) this.shim.setStyle('display', 'block');
2855		return this.position();
2856	},
2857
2858	dispose: function(){
2859		if (this.shim) this.shim.dispose();
2860		return this;
2861	},
2862
2863	destroy: function(){
2864		if (this.shim) this.shim.destroy();
2865		return this;
2866	}
2867
2868});
2869
2870window.addEvent('load', function(){
2871	IframeShim.ready = true;
2872});
2873
2874
2875/*
2876---
2877
2878script: Mask.js
2879
2880name: Mask
2881
2882description: Creates a mask element to cover another.
2883
2884license: MIT-style license
2885
2886authors:
2887  - Aaron Newton
2888
2889requires:
2890  - Core/Options
2891  - Core/Events
2892  - Core/Element.Event
2893  - /Class.Binds
2894  - /Element.Position
2895  - /IframeShim
2896
2897provides: [Mask]
2898
2899...
2900*/
2901
2902var Mask = new Class({
2903
2904	Implements: [Options, Events],
2905
2906	Binds: ['position'],
2907
2908	options: {/*
2909		onShow: function(){},
2910		onHide: function(){},
2911		onDestroy: function(){},
2912		onClick: function(event){},
2913		inject: {
2914			where: 'after',
2915			target: null,
2916		},
2917		hideOnClick: false,
2918		id: null,
2919		destroyOnHide: false,*/
2920		style: {},
2921		'class': 'mask',
2922		maskMargins: false,
2923		useIframeShim: true,
2924		iframeShimOptions: {}
2925	},
2926
2927	initialize: function(target, options){
2928		this.target = document.id(target) || document.id(document.body);
2929		this.target.store('mask', this);
2930		this.setOptions(options);
2931		this.render();
2932		this.inject();
2933	},
2934
2935	render: function(){
2936		this.element = new Element('div', {
2937			'class': this.options['class'],
2938			id: this.options.id || 'mask-' + String.uniqueID(),
2939			styles: Object.merge({}, this.options.style, {
2940				display: 'none'
2941			}),
2942			events: {
2943				click: function(event){
2944					this.fireEvent('click', event);
2945					if (this.options.hideOnClick) this.hide();
2946				}.bind(this)
2947			}
2948		});
2949
2950		this.hidden = true;
2951	},
2952
2953	toElement: function(){
2954		return this.element;
2955	},
2956
2957	inject: function(target, where){
2958		where = where || (this.options.inject ? this.options.inject.where : '') || this.target == document.body ? 'inside' : 'after';
2959		target = target || (this.options.inject && this.options.inject.target) || this.target;
2960
2961		this.element.inject(target, where);
2962
2963		if (this.options.useIframeShim){
2964			this.shim = new IframeShim(this.element, this.options.iframeShimOptions);
2965
2966			this.addEvents({
2967				show: this.shim.show.bind(this.shim),
2968				hide: this.shim.hide.bind(this.shim),
2969				destroy: this.shim.destroy.bind(this.shim)
2970			});
2971		}
2972	},
2973
2974	position: function(){
2975		this.resize(this.options.width, this.options.height);
2976
2977		this.element.position({
2978			relativeTo: this.target,
2979			position: 'topLeft',
2980			ignoreMargins: !this.options.maskMargins,
2981			ignoreScroll: this.target == document.body
2982		});
2983
2984		return this;
2985	},
2986
2987	resize: function(x, y){
2988		var opt = {
2989			styles: ['padding', 'border']
2990		};
2991		if (this.options.maskMargins) opt.styles.push('margin');
2992
2993		var dim = this.target.getComputedSize(opt);
2994		if (this.target == document.body){
2995			this.element.setStyles({width: 0, height: 0});
2996			var win = window.getScrollSize();
2997			if (dim.totalHeight < win.y) dim.totalHeight = win.y;
2998			if (dim.totalWidth < win.x) dim.totalWidth = win.x;
2999		}
3000		this.element.setStyles({
3001			width: Array.pick([x, dim.totalWidth, dim.x]),
3002			height: Array.pick([y, dim.totalHeight, dim.y])
3003		});
3004
3005		return this;
3006	},
3007
3008	show: function(){
3009		if (!this.hidden) return this;
3010
3011		window.addEvent('resize', this.position);
3012		this.position();
3013		this.showMask.apply(this, arguments);
3014
3015		return this;
3016	},
3017
3018	showMask: function(){
3019		this.element.setStyle('display', 'block');
3020		this.hidden = false;
3021		this.fireEvent('show');
3022	},
3023
3024	hide: function(){
3025		if (this.hidden) return this;
3026
3027		window.removeEvent('resize', this.position);
3028		this.hideMask.apply(this, arguments);
3029		if (this.options.destroyOnHide) return this.destroy();
3030
3031		return this;
3032	},
3033
3034	hideMask: function(){
3035		this.element.setStyle('display', 'none');
3036		this.hidden = true;
3037		this.fireEvent('hide');
3038	},
3039
3040	toggle: function(){
3041		this[this.hidden ? 'show' : 'hide']();
3042	},
3043
3044	destroy: function(){
3045		this.hide();
3046		this.element.destroy();
3047		this.fireEvent('destroy');
3048		this.target.eliminate('mask');
3049	}
3050
3051});
3052
3053Element.Properties.mask = {
3054
3055	set: function(options){
3056		var mask = this.retrieve('mask');
3057		if (mask) mask.destroy();
3058		return this.eliminate('mask').store('mask:options', options);
3059	},
3060
3061	get: function(){
3062		var mask = this.retrieve('mask');
3063		if (!mask){
3064			mask = new Mask(this, this.retrieve('mask:options'));
3065			this.store('mask', mask);
3066		}
3067		return mask;
3068	}
3069
3070};
3071
3072Element.implement({
3073
3074	mask: function(options){
3075		if (options) this.set('mask', options);
3076		this.get('mask').show();
3077		return this;
3078	},
3079
3080	unmask: function(){
3081		this.get('mask').hide();
3082		return this;
3083	}
3084
3085});
3086
3087
3088/*
3089---
3090
3091script: Spinner.js
3092
3093name: Spinner
3094
3095description: Adds a semi-transparent overlay over a dom element with a spinnin ajax icon.
3096
3097license: MIT-style license
3098
3099authors:
3100  - Aaron Newton
3101
3102requires:
3103  - Core/Fx.Tween
3104  - Core/Request
3105  - /Class.refactor
3106  - /Mask
3107
3108provides: [Spinner]
3109
3110...
3111*/
3112
3113var Spinner = new Class({
3114
3115	Extends: Mask,
3116
3117	Implements: Chain,
3118
3119	options: {/*
3120		message: false,*/
3121		'class': 'spinner',
3122		containerPosition: {},
3123		content: {
3124			'class': 'spinner-content'
3125		},
3126		messageContainer: {
3127			'class': 'spinner-msg'
3128		},
3129		img: {
3130			'class': 'spinner-img'
3131		},
3132		fxOptions: {
3133			link: 'chain'
3134		}
3135	},
3136
3137	initialize: function(target, options){
3138		this.target = document.id(target) || document.id(document.body);
3139		this.target.store('spinner', this);
3140		this.setOptions(options);
3141		this.render();
3142		this.inject();
3143
3144		// Add this to events for when noFx is true; parent methods handle hide/show.
3145		var deactivate = function(){ this.active = false; }.bind(this);
3146		this.addEvents({
3147			hide: deactivate,
3148			show: deactivate
3149		});
3150	},
3151
3152	render: function(){
3153		this.parent();
3154
3155		this.element.set('id', this.options.id || 'spinner-' + String.uniqueID());
3156
3157		this.content = document.id(this.options.content) || new Element('div', this.options.content);
3158		this.content.inject(this.element);
3159
3160		if (this.options.message){
3161			this.msg = document.id(this.options.message) || new Element('p', this.options.messageContainer).appendText(this.options.message);
3162			this.msg.inject(this.content);
3163		}
3164
3165		if (this.options.img){
3166			this.img = document.id(this.options.img) || new Element('div', this.options.img);
3167			this.img.inject(this.content);
3168		}
3169
3170		this.element.set('tween', this.options.fxOptions);
3171	},
3172
3173	show: function(noFx){
3174		if (this.active) return this.chain(this.show.bind(this));
3175		if (!this.hidden){
3176			this.callChain.delay(20, this);
3177			return this;
3178		}
3179
3180		this.active = true;
3181
3182		return this.parent(noFx);
3183	},
3184
3185	showMask: function(noFx){
3186		var pos = function(){
3187			this.content.position(Object.merge({
3188				relativeTo: this.element
3189			}, this.options.containerPosition));
3190		}.bind(this);
3191
3192		if (noFx){
3193			this.parent();
3194			pos();
3195		} else {
3196			if (!this.options.style.opacity) this.options.style.opacity = this.element.getStyle('opacity').toFloat();
3197			this.element.setStyles({
3198				display: 'block',
3199				opacity: 0
3200			}).tween('opacity', this.options.style.opacity);
3201			pos();
3202			this.hidden = false;
3203			this.fireEvent('show');
3204			this.callChain();
3205		}
3206	},
3207
3208	hide: function(noFx){
3209		if (this.active) return this.chain(this.hide.bind(this));
3210		if (this.hidden){
3211			this.callChain.delay(20, this);
3212			return this;
3213		}
3214		this.active = true;
3215		return this.parent(noFx);
3216	},
3217
3218	hideMask: function(noFx){
3219		if (noFx) return this.parent();
3220		this.element.tween('opacity', 0).get('tween').chain(function(){
3221			this.element.setStyle('display', 'none');
3222			this.hidden = true;
3223			this.fireEvent('hide');
3224			this.callChain();
3225		}.bind(this));
3226	},
3227
3228	destroy: function(){
3229		this.content.destroy();
3230		this.parent();
3231		this.target.eliminate('spinner');
3232	}
3233
3234});
3235
3236Request = Class.refactor(Request, {
3237
3238	options: {
3239		useSpinner: false,
3240		spinnerOptions: {},
3241		spinnerTarget: false
3242	},
3243
3244	initialize: function(options){
3245		this._send = this.send;
3246		this.send = function(options){
3247			var spinner = this.getSpinner();
3248			if (spinner) spinner.chain(this._send.pass(options, this)).show();
3249			else this._send(options);
3250			return this;
3251		};
3252		this.previous(options);
3253	},
3254
3255	getSpinner: function(){
3256		if (!this.spinner){
3257			var update = document.id(this.options.spinnerTarget) || document.id(this.options.update);
3258			if (this.options.useSpinner && update){
3259				update.set('spinner', this.options.spinnerOptions);
3260				var spinner = this.spinner = update.get('spinner');
3261				['complete', 'exception', 'cancel'].each(function(event){
3262					this.addEvent(event, spinner.hide.bind(spinner));
3263				}, this);
3264			}
3265		}
3266		return this.spinner;
3267	}
3268
3269});
3270
3271Element.Properties.spinner = {
3272
3273	set: function(options){
3274		var spinner = this.retrieve('spinner');
3275		if (spinner) spinner.destroy();
3276		return this.eliminate('spinner').store('spinner:options', options);
3277	},
3278
3279	get: function(){
3280		var spinner = this.retrieve('spinner');
3281		if (!spinner){
3282			spinner = new Spinner(this, this.retrieve('spinner:options'));
3283			this.store('spinner', spinner);
3284		}
3285		return spinner;
3286	}
3287
3288};
3289
3290Element.implement({
3291
3292	spin: function(options){
3293		if (options) this.set('spinner', options);
3294		this.get('spinner').show();
3295		return this;
3296	},
3297
3298	unspin: function(){
3299		this.get('spinner').hide();
3300		return this;
3301	}
3302
3303});
3304
3305
3306/*
3307---
3308
3309script: Form.Request.js
3310
3311name: Form.Request
3312
3313description: Handles the basic functionality of submitting a form and updating a dom element with the result.
3314
3315license: MIT-style license
3316
3317authors:
3318  - Aaron Newton
3319
3320requires:
3321  - Core/Request.HTML
3322  - /Class.Binds
3323  - /Class.Occlude
3324  - /Spinner
3325  - /String.QueryString
3326  - /Element.Delegation
3327
3328provides: [Form.Request]
3329
3330...
3331*/
3332
3333if (!window.Form) window.Form = {};
3334
3335(function(){
3336
3337	Form.Request = new Class({
3338
3339		Binds: ['onSubmit', 'onFormValidate'],
3340
3341		Implements: [Options, Events, Class.Occlude],
3342
3343		options: {/*
3344			onFailure: function(){},
3345			onSuccess: function(){}, // aliased to onComplete,
3346			onSend: function(){}*/
3347			requestOptions: {
3348				evalScripts: true,
3349				useSpinner: true,
3350				emulation: false,
3351				link: 'ignore'
3352			},
3353			sendButtonClicked: true,
3354			extraData: {},
3355			resetForm: true
3356		},
3357
3358		property: 'form.request',
3359
3360		initialize: function(form, target, options){
3361			this.element = document.id(form);
3362			if (this.occlude()) return this.occluded;
3363			this.setOptions(options)
3364				.setTarget(target)
3365				.attach();
3366		},
3367
3368		setTarget: function(target){
3369			this.target = document.id(target);
3370			if (!this.request){
3371				this.makeRequest();
3372			} else {
3373				this.request.setOptions({
3374					update: this.target
3375				});
3376			}
3377			return this;
3378		},
3379
3380		toElement: function(){
3381			return this.element;
3382		},
3383
3384		makeRequest: function(){
3385			var self = this;
3386			this.request = new Request.HTML(Object.merge({
3387					update: this.target,
3388					emulation: false,
3389					spinnerTarget: this.element,
3390					method: this.element.get('method') || 'post'
3391			}, this.options.requestOptions)).addEvents({
3392				success: function(tree, elements, html, javascript){
3393					['complete', 'success'].each(function(evt){
3394						self.fireEvent(evt, [self.target, tree, elements, html, javascript]);
3395					});
3396				},
3397				failure: function(){
3398					self.fireEvent('complete', arguments).fireEvent('failure', arguments);
3399				},
3400				exception: function(){
3401					self.fireEvent('failure', arguments);
3402				}
3403			});
3404			return this.attachReset();
3405		},
3406
3407		attachReset: function(){
3408			if (!this.options.resetForm) return this;
3409			this.request.addEvent('success', function(){
3410				Function.attempt(function(){
3411					this.element.reset();
3412				}.bind(this));
3413				if (window.OverText) OverText.update();
3414			}.bind(this));
3415			return this;
3416		},
3417
3418		attach: function(attach){
3419			var method = (attach != false) ? 'addEvent' : 'removeEvent';
3420			this.element[method]('click:relay(button, input[type=submit])', this.saveClickedButton.bind(this));
3421
3422			var fv = this.element.retrieve('validator');
3423			if (fv) fv[method]('onFormValidate', this.onFormValidate);
3424			else this.element[method]('submit', this.onSubmit);
3425
3426			return this;
3427		},
3428
3429		detach: function(){
3430			return this.attach(false);
3431		},
3432
3433		//public method
3434		enable: function(){
3435			return this.attach();
3436		},
3437
3438		//public method
3439		disable: function(){
3440			return this.detach();
3441		},
3442
3443		onFormValidate: function(valid, form, event){
3444			//if there's no event, then this wasn't a submit event
3445			if (!event) return;
3446			var fv = this.element.retrieve('validator');
3447			if (valid || (fv && !fv.options.stopOnFailure)){
3448				event.stop();
3449				this.send();
3450			}
3451		},
3452
3453		onSubmit: function(event){
3454			var fv = this.element.retrieve('validator');
3455			if (fv){
3456				//form validator was created after Form.Request
3457				this.element.removeEvent('submit', this.onSubmit);
3458				fv.addEvent('onFormValidate', this.onFormValidate);
3459				this.element.validate();
3460				return;
3461			}
3462			if (event) event.stop();
3463			this.send();
3464		},
3465
3466		saveClickedButton: function(event, target){
3467			var targetName = target.get('name');
3468			if (!targetName || !this.options.sendButtonClicked) return;
3469			this.options.extraData[targetName] = target.get('value') || true;
3470			this.clickedCleaner = function(){
3471				delete this.options.extraData[targetName];
3472				this.clickedCleaner = function(){};
3473			}.bind(this);
3474		},
3475
3476		clickedCleaner: function(){},
3477
3478		send: function(){
3479			var str = this.element.toQueryString().trim(),
3480				data = Object.toQueryString(this.options.extraData);
3481
3482			if (str) str += "&" + data;
3483			else str = data;
3484
3485			this.fireEvent('send', [this.element, str.parseQueryString()]);
3486			this.request.send({
3487				data: str,
3488				url: this.options.requestOptions.url || this.element.get('action')
3489			});
3490			this.clickedCleaner();
3491			return this;
3492		}
3493
3494	});
3495
3496	Element.implement('formUpdate', function(update, options){
3497		var fq = this.retrieve('form.request');
3498		if (!fq){
3499			fq = new Form.Request(this, update, options);
3500		} else {
3501			if (update) fq.setTarget(update);
3502			if (options) fq.setOptions(options).makeRequest();
3503		}
3504		fq.send();
3505		return this;
3506	});
3507
3508})();
3509
3510
3511/*
3512---
3513
3514script: Fx.Reveal.js
3515
3516name: Fx.Reveal
3517
3518description: Defines Fx.Reveal, a class that shows and hides elements with a transition.
3519
3520license: MIT-style license
3521
3522authors:
3523  - Aaron Newton
3524
3525requires:
3526  - Core/Fx.Morph
3527  - /Element.Shortcuts
3528  - /Element.Measure
3529
3530provides: [Fx.Reveal]
3531
3532...
3533*/
3534
3535(function(){
3536
3537
3538var hideTheseOf = function(object){
3539	var hideThese = object.options.hideInputs;
3540	if (window.OverText){
3541		var otClasses = [null];
3542		OverText.each(function(ot){
3543			otClasses.include('.' + ot.options.labelClass);
3544		});
3545		if (otClasses) hideThese += otClasses.join(', ');
3546	}
3547	return (hideThese) ? object.element.getElements(hideThese) : null;
3548};
3549
3550
3551Fx.Reveal = new Class({
3552
3553	Extends: Fx.Morph,
3554
3555	options: {/*
3556		onShow: function(thisElement){},
3557		onHide: function(thisElement){},
3558		onComplete: function(thisElement){},
3559		heightOverride: null,
3560		widthOverride: null,*/
3561		link: 'cancel',
3562		styles: ['padding', 'border', 'margin'],
3563		transitionOpacity: !Browser.ie6,
3564		mode: 'vertical',
3565		display: function(){
3566			return this.element.get('tag') != 'tr' ? 'block' : 'table-row';
3567		},
3568		opacity: 1,
3569		hideInputs: Browser.ie ? 'select, input, textarea, object, embed' : null
3570	},
3571
3572	dissolve: function(){
3573		if (!this.hiding && !this.showing){
3574			if (this.element.getStyle('display') != 'none'){
3575				this.hiding = true;
3576				this.showing = false;
3577				this.hidden = true;
3578				this.cssText = this.element.style.cssText;
3579
3580				var startStyles = this.element.getComputedSize({
3581					styles: this.options.styles,
3582					mode: this.options.mode
3583				});
3584				if (this.options.transitionOpacity) startStyles.opacity = this.options.opacity;
3585
3586				var zero = {};
3587				Object.each(startStyles, function(style, name){
3588					zero[name] = [style, 0];
3589				});
3590
3591				this.element.setStyles({
3592					display: Function.from(this.options.display).call(this),
3593					overflow: 'hidden'
3594				});
3595
3596				var hideThese = hideTheseOf(this);
3597				if (hideThese) hideThese.setStyle('visibility', 'hidden');
3598
3599				this.$chain.unshift(function(){
3600					if (this.hidden){
3601						this.hiding = false;
3602						this.element.style.cssText = this.cssText;
3603						this.element.setStyle('display', 'none');
3604						if (hideThese) hideThese.setStyle('visibility', 'visible');
3605					}
3606					this.fireEvent('hide', this.element);
3607					this.callChain();
3608				}.bind(this));
3609
3610				this.start(zero);
3611			} else {
3612				this.callChain.delay(10, this);
3613				this.fireEvent('complete', this.element);
3614				this.fireEvent('hide', this.element);
3615			}
3616		} else if (this.options.link == 'chain'){
3617			this.chain(this.dissolve.bind(this));
3618		} else if (this.options.link == 'cancel' && !this.hiding){
3619			this.cancel();
3620			this.dissolve();
3621		}
3622		return this;
3623	},
3624
3625	reveal: function(){
3626		if (!this.showing && !this.hiding){
3627			if (this.element.getStyle('display') == 'none'){
3628				this.hiding = false;
3629				this.showing = true;
3630				this.hidden = false;
3631				this.cssText = this.element.style.cssText;
3632
3633				var startStyles;
3634				this.element.measure(function(){
3635					startStyles = this.element.getComputedSize({
3636						styles: this.options.styles,
3637						mode: this.options.mode
3638					});
3639				}.bind(this));
3640				if (this.options.heightOverride != null) startStyles.height = this.options.heightOverride.toInt();
3641				if (this.options.widthOverride != null) startStyles.width = this.options.widthOverride.toInt();
3642				if (this.options.transitionOpacity){
3643					this.element.setStyle('opacity', 0);
3644					startStyles.opacity = this.options.opacity;
3645				}
3646
3647				var zero = {
3648					height: 0,
3649					display: Function.from(this.options.display).call(this)
3650				};
3651				Object.each(startStyles, function(style, name){
3652					zero[name] = 0;
3653				});
3654				zero.overflow = 'hidden';
3655
3656				this.element.setStyles(zero);
3657
3658				var hideThese = hideTheseOf(this);
3659				if (hideThese) hideThese.setStyle('visibility', 'hidden');
3660
3661				this.$chain.unshift(function(){
3662					this.element.style.cssText = this.cssText;
3663					this.element.setStyle('display', Function.from(this.options.display).call(this));
3664					if (!this.hidden) this.showing = false;
3665					if (hideThese) hideThese.setStyle('visibility', 'visible');
3666					this.callChain();
3667					this.fireEvent('show', this.element);
3668				}.bind(this));
3669
3670				this.start(startStyles);
3671			} else {
3672				this.callChain();
3673				this.fireEvent('complete', this.element);
3674				this.fireEvent('show', this.element);
3675			}
3676		} else if (this.options.link == 'chain'){
3677			this.chain(this.reveal.bind(this));
3678		} else if (this.options.link == 'cancel' && !this.showing){
3679			this.cancel();
3680			this.reveal();
3681		}
3682		return this;
3683	},
3684
3685	toggle: function(){
3686		if (this.element.getStyle('display') == 'none'){
3687			this.reveal();
3688		} else {
3689			this.dissolve();
3690		}
3691		return this;
3692	},
3693
3694	cancel: function(){
3695		this.parent.apply(this, arguments);
3696		if (this.cssText != null) this.element.style.cssText = this.cssText;
3697		this.hiding = false;
3698		this.showing = false;
3699		return this;
3700	}
3701
3702});
3703
3704Element.Properties.reveal = {
3705
3706	set: function(options){
3707		this.get('reveal').cancel().setOptions(options);
3708		return this;
3709	},
3710
3711	get: function(){
3712		var reveal = this.retrieve('reveal');
3713		if (!reveal){
3714			reveal = new Fx.Reveal(this);
3715			this.store('reveal', reveal);
3716		}
3717		return reveal;
3718	}
3719
3720};
3721
3722Element.Properties.dissolve = Element.Properties.reveal;
3723
3724Element.implement({
3725
3726	reveal: function(options){
3727		this.get('reveal').setOptions(options).reveal();
3728		return this;
3729	},
3730
3731	dissolve: function(options){
3732		this.get('reveal').setOptions(options).dissolve();
3733		return this;
3734	},
3735
3736	nix: function(options){
3737		var params = Array.link(arguments, {destroy: Type.isBoolean, options: Type.isObject});
3738		this.get('reveal').setOptions(options).dissolve().chain(function(){
3739			this[params.destroy ? 'destroy' : 'dispose']();
3740		}.bind(this));
3741		return this;
3742	},
3743
3744	wink: function(){
3745		var params = Array.link(arguments, {duration: Type.isNumber, options: Type.isObject});
3746		var reveal = this.get('reveal').setOptions(params.options);
3747		reveal.reveal().chain(function(){
3748			(function(){
3749				reveal.dissolve();
3750			}).delay(params.duration || 2000);
3751		});
3752	}
3753
3754});
3755
3756})();
3757
3758
3759/*
3760---
3761
3762script: Form.Request.Append.js
3763
3764name: Form.Request.Append
3765
3766description: Handles the basic functionality of submitting a form and updating a dom element with the result. The result is appended to the DOM element instead of replacing its contents.
3767
3768license: MIT-style license
3769
3770authors:
3771  - Aaron Newton
3772
3773requires:
3774  - /Form.Request
3775  - /Fx.Reveal
3776  - /Elements.from
3777
3778provides: [Form.Request.Append]
3779
3780...
3781*/
3782
3783Form.Request.Append = new Class({
3784
3785	Extends: Form.Request,
3786
3787	options: {
3788		//onBeforeEffect: function(){},
3789		useReveal: true,
3790		revealOptions: {},
3791		inject: 'bottom'
3792	},
3793
3794	makeRequest: function(){
3795		this.request = new Request.HTML(Object.merge({
3796				url: this.element.get('action'),
3797				method: this.element.get('method') || 'post',
3798				spinnerTarget: this.element
3799			}, this.options.requestOptions, {
3800				evalScripts: false
3801			})
3802		).addEvents({
3803			success: function(tree, elements, html, javascript){
3804				var container;
3805				var kids = Elements.from(html);
3806				if (kids.length == 1){
3807					container = kids[0];
3808				} else {
3809					 container = new Element('div', {
3810						styles: {
3811							display: 'none'
3812						}
3813					}).adopt(kids);
3814				}
3815				container.inject(this.target, this.options.inject);
3816				if (this.options.requestOptions.evalScripts) Browser.exec(javascript);
3817				this.fireEvent('beforeEffect', container);
3818				var finish = function(){
3819					this.fireEvent('success', [container, this.target, tree, elements, html, javascript]);
3820				}.bind(this);
3821				if (this.options.useReveal){
3822					container.set('reveal', this.options.revealOptions).get('reveal').chain(finish);
3823					container.reveal();
3824				} else {
3825					finish();
3826				}
3827			}.bind(this),
3828			failure: function(xhr){
3829				this.fireEvent('failure', xhr);
3830			}.bind(this)
3831		});
3832		this.attachReset();
3833	}
3834
3835});
3836
3837
3838/*
3839---
3840
3841name: Locale.en-US.Form.Validator
3842
3843description: Form Validator messages for English.
3844
3845license: MIT-style license
3846
3847authors:
3848  - Aaron Newton
3849
3850requires:
3851  - /Locale
3852
3853provides: [Locale.en-US.Form.Validator]
3854
3855...
3856*/
3857
3858Locale.define('en-US', 'FormValidator', {
3859
3860	required: 'This field is required.',
3861	length: 'Please enter {length} characters (you entered {elLength} characters)',
3862	minLength: 'Please enter at least {minLength} characters (you entered {length} characters).',
3863	maxLength: 'Please enter no more than {maxLength} characters (you entered {length} characters).',
3864	integer: 'Please enter an integer in this field. Numbers with decimals (e.g. 1.25) are not permitted.',
3865	numeric: 'Please enter only numeric values in this field (i.e. "1" or "1.1" or "-1" or "-1.1").',
3866	digits: 'Please use numbers and punctuation only in this field (for example, a phone number with dashes or dots is permitted).',
3867	alpha: 'Please use only letters (a-z) within this field. No spaces or other characters are allowed.',
3868	alphanum: 'Please use only letters (a-z) or numbers (0-9) in this field. No spaces or other characters are allowed.',
3869	dateSuchAs: 'Please enter a valid date such as {date}',
3870	dateInFormatMDY: 'Please enter a valid date such as MM/DD/YYYY (i.e. "12/31/1999")',
3871	email: 'Please enter a valid email address. For example "[email protected]".',
3872	url: 'Please enter a valid URL such as http://www.example.com.',
3873	currencyDollar: 'Please enter a valid $ amount. For example $100.00 .',
3874	oneRequired: 'Please enter something for at least one of these inputs.',
3875	errorPrefix: 'Error: ',
3876	warningPrefix: 'Warning: ',
3877
3878	// Form.Validator.Extras
3879	noSpace: 'There can be no spaces in this input.',
3880	reqChkByNode: 'No items are selected.',
3881	requiredChk: 'This field is required.',
3882	reqChkByName: 'Please select a {label}.',
3883	match: 'This field needs to match the {matchName} field',
3884	startDate: 'the start date',
3885	endDate: 'the end date',
3886	currendDate: 'the current date',
3887	afterDate: 'The date should be the same or after {label}.',
3888	beforeDate: 'The date should be the same or before {label}.',
3889	startMonth: 'Please select a start month',
3890	sameMonth: 'These two dates must be in the same month - you must change one or the other.',
3891	creditcard: 'The credit card number entered is invalid. Please check the number and try again. {length} digits entered.'
3892
3893});
3894
3895
3896/*
3897---
3898
3899script: Form.Validator.js
3900
3901name: Form.Validator
3902
3903description: A css-class based form validation system.
3904
3905license: MIT-style license
3906
3907authors:
3908  - Aaron Newton
3909
3910requires:
3911  - Core/Options
3912  - Core/Events
3913  - Core/Slick.Finder
3914  - Core/Element.Event
3915  - Core/Element.Style
3916  - Core/JSON
3917  - /Locale
3918  - /Class.Binds
3919  - /Date
3920  - /Element.Forms
3921  - /Locale.en-US.Form.Validator
3922  - /Element.Shortcuts
3923
3924provides: [Form.Validator, InputValidator, FormValidator.BaseValidators]
3925
3926...
3927*/
3928if (!window.Form) window.Form = {};
3929
3930var InputValidator = this.InputValidator = new Class({
3931
3932	Implements: [Options],
3933
3934	options: {
3935		errorMsg: 'Validation failed.',
3936		test: Function.from(true)
3937	},
3938
3939	initialize: function(className, options){
3940		this.setOptions(options);
3941		this.className = className;
3942	},
3943
3944	test: function(field, props){
3945		field = document.id(field);
3946		return (field) ? this.options.test(field, props || this.getProps(field)) : false;
3947	},
3948
3949	getError: function(field, props){
3950		field = document.id(field);
3951		var err = this.options.errorMsg;
3952		if (typeOf(err) == 'function') err = err(field, props || this.getProps(field));
3953		return err;
3954	},
3955
3956	getProps: function(field){
3957		field = document.id(field);
3958		return (field) ? field.get('validatorProps') : {};
3959	}
3960
3961});
3962
3963Element.Properties.validators = {
3964
3965	get: function(){
3966		return (this.get('data-validators') || this.className).clean().split(' ');
3967	}
3968
3969};
3970
3971Element.Properties.validatorProps = {
3972
3973	set: function(props){
3974		return this.eliminate('$moo:validatorProps').store('$moo:validatorProps', props);
3975	},
3976
3977	get: function(props){
3978		if (props) this.set(props);
3979		if (this.retrieve('$moo:validatorProps')) return this.retrieve('$moo:validatorProps');
3980		if (this.getProperty('data-validator-properties') || this.getProperty('validatorProps')){
3981			try {
3982				this.store('$moo:validatorProps', JSON.decode(this.getProperty('validatorProps') || this.getProperty('data-validator-properties')));
3983			}catch(e){
3984				return {};
3985			}
3986		} else {
3987			var vals = this.get('validators').filter(function(cls){
3988				return cls.test(':');
3989			});
3990			if (!vals.length){
3991				this.store('$moo:validatorProps', {});
3992			} else {
3993				props = {};
3994				vals.each(function(cls){
3995					var split = cls.split(':');
3996					if (split[1]){
3997						try {
3998							props[split[0]] = JSON.decode(split[1]);
3999						} catch(e){}
4000					}
4001				});
4002				this.store('$moo:validatorProps', props);
4003			}
4004		}
4005		return this.retrieve('$moo:validatorProps');
4006	}
4007
4008};
4009
4010Form.Validator = new Class({
4011
4012	Implements: [Options, Events],
4013
4014	Binds: ['onSubmit'],
4015
4016	options: {/*
4017		onFormValidate: function(isValid, form, event){},
4018		onElementValidate: function(isValid, field, className, warn){},
4019		onElementPass: function(field){},
4020		onElementFail: function(field, validatorsFailed){}, */
4021		fieldSelectors: 'input, select, textarea',
4022		ignoreHidden: true,
4023		ignoreDisabled: true,
4024		useTitles: false,
4025		evaluateOnSubmit: true,
4026		evaluateFieldsOnBlur: true,
4027		evaluateFieldsOnChange: true,
4028		serial: true,
4029		stopOnFailure: true,
4030		warningPrefix: function(){
4031			return Form.Validator.getMsg('warningPrefix') || 'Warning: ';
4032		},
4033		errorPrefix: function(){
4034			return Form.Validator.getMsg('errorPrefix') || 'Error: ';
4035		}
4036	},
4037
4038	initialize: function(form, options){
4039		this.setOptions(options);
4040		this.element = document.id(form);
4041		this.element.store('validator', this);
4042		this.warningPrefix = Function.from(this.options.warningPrefix)();
4043		this.errorPrefix = Function.from(this.options.errorPrefix)();
4044		if (this.options.evaluateOnSubmit) this.element.addEvent('submit', this.onSubmit);
4045		if (this.options.evaluateFieldsOnBlur || this.options.evaluateFieldsOnChange) this.watchFields(this.getFields());
4046	},
4047
4048	toElement: function(){
4049		return this.element;
4050	},
4051
4052	getFields: function(){
4053		return (this.fields = this.element.getElements(this.options.fieldSelectors));
4054	},
4055
4056	watchFields: function(fields){
4057		fields.each(function(el){
4058			if (this.options.evaluateFieldsOnBlur)
4059				el.addEvent('blur', this.validationMonitor.pass([el, false], this));
4060			if (this.options.evaluateFieldsOnChange)
4061				el.addEvent('change', this.validationMonitor.pass([el, true], this));
4062		}, this);
4063	},
4064
4065	validationMonitor: function(){
4066		clearTimeout(this.timer);
4067		this.timer = this.validateField.delay(50, this, arguments);
4068	},
4069
4070	onSubmit: function(event){
4071		if (this.validate(event)) this.reset();
4072	},
4073
4074	reset: function(){
4075		this.getFields().each(this.resetField, this);
4076		return this;
4077	},
4078
4079	validate: function(event){
4080		var result = this.getFields().map(function(field){
4081			return this.validateField(field, true);
4082		}, this).every(function(v){
4083			return v;
4084		});
4085		this.fireEvent('formValidate', [result, this.element, event]);
4086		if (this.options.stopOnFailure && !result && event) event.preventDefault();
4087		return result;
4088	},
4089
4090	validateField: function(field, force){
4091		if (this.paused) return true;
4092		field = document.id(field);
4093		var passed = !field.hasClass('validation-failed');
4094		var failed, warned;
4095		if (this.options.serial && !force){
4096			failed = this.element.getElement('.validation-failed');
4097			warned = this.element.getElement('.warning');
4098		}
4099		if (field && (!failed || force || field.hasClass('validation-failed') || (failed && !this.options.serial))){
4100			var validationTypes = field.get('validators');
4101			var validators = validationTypes.some(function(cn){
4102				return this.getValidator(cn);
4103			}, this);
4104			var validatorsFailed = [];
4105			validationTypes.each(function(className){
4106				if (className && !this.test(className, field)) validatorsFailed.include(className);
4107			}, this);
4108			passed = validatorsFailed.length === 0;
4109			if (validators && !this.hasValidator(field, 'warnOnly')){
4110				if (passed){
4111					field.addClass('validation-passed').removeClass('validation-failed');
4112					this.fireEvent('elementPass', [field]);
4113				} else {
4114					field.addClass('validation-failed').removeClass('validation-passed');
4115					this.fireEvent('elementFail', [field, validatorsFailed]);
4116				}
4117			}
4118			if (!warned){
4119				var warnings = validationTypes.some(function(cn){
4120					if (cn.test('^warn'))
4121						return this.getValidator(cn.replace(/^warn-/,''));
4122					else return null;
4123				}, this);
4124				field.removeClass('warning');
4125				var warnResult = validationTypes.map(function(cn){
4126					if (cn.test('^warn'))
4127						return this.test(cn.replace(/^warn-/,''), field, true);
4128					else return null;
4129				}, this);
4130			}
4131		}
4132		return passed;
4133	},
4134
4135	test: function(className, field, warn){
4136		field = document.id(field);
4137		if ((this.options.ignoreHidden && !field.isVisible()) || (this.options.ignoreDisabled && field.get('disabled'))) return true;
4138		var validator = this.getValidator(className);
4139		if (warn != null) warn = false;
4140		if (this.hasValidator(field, 'warnOnly')) warn = true;
4141		var isValid = this.hasValidator(field, 'ignoreValidation') || (validator ? validator.test(field) : true);
4142		if (validator && field.isVisible()) this.fireEvent('elementValidate', [isValid, field, className, warn]);
4143		if (warn) return true;
4144		return isValid;
4145	},
4146
4147	hasValidator: function(field, value){
4148		return field.get('validators').contains(value);
4149	},
4150
4151	resetField: function(field){
4152		field = document.id(field);
4153		if (field){
4154			field.get('validators').each(function(className){
4155				if (className.test('^warn-')) className = className.replace(/^warn-/, '');
4156				field.removeClass('validation-failed');
4157				field.removeClass('warning');
4158				field.removeClass('validation-passed');
4159			}, this);
4160		}
4161		return this;
4162	},
4163
4164	stop: function(){
4165		this.paused = true;
4166		return this;
4167	},
4168
4169	start: function(){
4170		this.paused = false;
4171		return this;
4172	},
4173
4174	ignoreField: function(field, warn){
4175		field = document.id(field);
4176		if (field){
4177			this.enforceField(field);
4178			if (warn) field.addClass('warnOnly');
4179			else field.addClass('ignoreValidation');
4180		}
4181		return this;
4182	},
4183
4184	enforceField: function(field){
4185		field = document.id(field);
4186		if (field) field.removeClass('warnOnly').removeClass('ignoreValidation');
4187		return this;
4188	}
4189
4190});
4191
4192Form.Validator.getMsg = function(key){
4193	return Locale.get('FormValidator.' + key);
4194};
4195
4196Form.Validator.adders = {
4197
4198	validators:{},
4199
4200	add : function(className, options){
4201		this.validators[className] = new InputValidator(className, options);
4202		//if this is a class (this method is used by instances of Form.Validator and the Form.Validator namespace)
4203		//extend these validators into it
4204		//this allows validators to be global and/or per instance
4205		if (!this.initialize){
4206			this.implement({
4207				validators: this.validators
4208			});
4209		}
4210	},
4211
4212	addAllThese : function(validators){
4213		Array.from(validators).each(function(validator){
4214			this.add(validator[0], validator[1]);
4215		}, this);
4216	},
4217
4218	getValidator: function(className){
4219		return this.validators[className.split(':')[0]];
4220	}
4221
4222};
4223
4224Object.append(Form.Validator, Form.Validator.adders);
4225
4226Form.Validator.implement(Form.Validator.adders);
4227
4228Form.Validator.add('IsEmpty', {
4229
4230	errorMsg: false,
4231	test: function(element){
4232		if (element.type == 'select-one' || element.type == 'select')
4233			return !(element.selectedIndex >= 0 && element.options[element.selectedIndex].value != '');
4234		else
4235			return ((element.get('value') == null) || (element.get('value').length == 0));
4236	}
4237
4238});
4239
4240Form.Validator.addAllThese([
4241
4242	['required', {
4243		errorMsg: function(){
4244			return Form.Validator.getMsg('required');
4245		},
4246		test: function(element){
4247			return !Form.Validator.getValidator('IsEmpty').test(element);
4248		}
4249	}],
4250
4251	['length', {
4252		errorMsg: function(element, props){
4253			if (typeOf(props.length) != 'null')
4254				return Form.Validator.getMsg('length').substitute({length: props.length, elLength: element.get('value').length});
4255			else return '';
4256		},
4257		test: function(element, props){
4258			if (typeOf(props.length) != 'null') return (element.get('value').length == props.length || element.get('value').length == 0);
4259			else return true;
4260		}
4261	}],	
4262
4263	['minLength', {
4264		errorMsg: function(element, props){
4265			if (typeOf(props.minLength) != 'null')
4266				return Form.Validator.getMsg('minLength').substitute({minLength: props.minLength, length: element.get('value').length});
4267			else return '';
4268		},
4269		test: function(element, props){
4270			if (typeOf(props.minLength) != 'null') return (element.get('value').length >= (props.minLength || 0));
4271			else return true;
4272		}
4273	}],
4274
4275	['maxLength', {
4276		errorMsg: function(element, props){
4277			//props is {maxLength:10}
4278			if (typeOf(props.maxLength) != 'null')
4279				return Form.Validator.getMsg('maxLength').substitute({maxLength: props.maxLength, length: element.get('value').length});
4280			else return '';
4281		},
4282		test: function(element, props){
4283			return element.get('value').length <= (props.maxLength || 10000);
4284		}
4285	}],
4286
4287	['validate-integer', {
4288		errorMsg: Form.Validator.getMsg.pass('integer'),
4289		test: function(element){
4290			return Form.Validator.getValidator('IsEmpty').test(element) || (/^(-?[1-9]\d*|0)$/).test(element.get('value'));
4291		}
4292	}],
4293
4294	['validate-numeric', {
4295		errorMsg: Form.Validator.getMsg.pass('numeric'),
4296		test: function(element){
4297			return Form.Validator.getValidator('IsEmpty').test(element) ||
4298				(/^-?(?:0$0(?=\d*\.)|[1-9]|0)\d*(\.\d+)?$/).test(element.get('value'));
4299		}
4300	}],
4301
4302	['validate-digits', {
4303		errorMsg: Form.Validator.getMsg.pass('digits'),
4304		test: function(element){
4305			return Form.Validator.getValidator('IsEmpty').test(element) || (/^[\d() .:\-\+#]+$/.test(element.get('value')));
4306		}
4307	}],
4308
4309	['validate-alpha', {
4310		errorMsg: Form.Validator.getMsg.pass('alpha'),
4311		test: function(element){
4312			return Form.Validator.getValidator('IsEmpty').test(element) || (/^[a-zA-Z]+$/).test(element.get('value'));
4313		}
4314	}],
4315
4316	['validate-alphanum', {
4317		errorMsg: Form.Validator.getMsg.pass('alphanum'),
4318		test: function(element){
4319			return Form.Validator.getValidator('IsEmpty').test(element) || !(/\W/).test(element.get('value'));
4320		}
4321	}],
4322
4323	['validate-date', {
4324		errorMsg: function(element, props){
4325			if (Date.parse){
4326				var format = props.dateFormat || '%x';
4327				return Form.Validator.getMsg('dateSuchAs').substitute({date: new Date().format(format)});
4328			} else {
4329				return Form.Validator.getMsg('dateInFormatMDY');
4330			}
4331		},
4332		test: function(element, props){
4333			if (Form.Validator.getValidator('IsEmpty').test(element)) return true;
4334			var dateLocale = Locale.getCurrent().sets.Date,
4335				dateNouns = new RegExp([dateLocale.days, dateLocale.days_abbr, dateLocale.months, dateLocale.months_abbr].flatten().join('|'), 'i'),
4336				value = element.get('value'),
4337				wordsInValue = value.match(/[a-z]+/gi);
4338
4339				if (wordsInValue && !wordsInValue.every(dateNouns.exec, dateNouns)) return false;
4340
4341				var date = Date.parse(value),
4342					format = props.dateFormat || '%x',
4343					formatted = date.format(format);
4344
4345				if (formatted != 'invalid date') element.set('value', formatted);
4346				return date.isValid();
4347		}
4348	}],
4349
4350	['validate-email', {
4351		errorMsg: Form.Validator.getMsg.pass('email'),
4352		test: function(element){
4353			/*
4354			var chars = "[a-z0-9!#$%&'*+/=?^_`{|}~-]",
4355				local = '(?:' + chars + '\\.?){0,63}' + chars,
4356
4357				label = '[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?',
4358				hostname = '(?:' + label + '\\.)*' + label;
4359
4360				octet = '(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)',
4361				ipv4 = '\\[(?:' + octet + '\\.){3}' + octet + '\\]',
4362
4363				domain = '(?:' + hostname + '|' + ipv4 + ')';
4364
4365			var regex = new RegExp('^' + local + '@' + domain + '$', 'i');
4366			*/
4367			return Form.Validator.getValidator('IsEmpty').test(element) || (/^(?:[a-z0-9!#$%&'*+\/=?^_`{|}~-]\.?){0,63}[a-z0-9!#$%&'*+\/=?^_`{|}~-]@(?:(?:[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?\.)*[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?|\[(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\])$/i).test(element.get('value'));
4368		}
4369	}],
4370
4371	['validate-url', {
4372		errorMsg: Form.Validator.getMsg.pass('url'),
4373		test: function(element){
4374			return Form.Validator.getValidator('IsEmpty').test(element) || (/^(https?|ftp|rmtp|mms):\/\/(([A-Z0-9][A-Z0-9_-]*)(\.[A-Z0-9][A-Z0-9_-]*)+)(:(\d+))?\/?/i).test(element.get('value'));
4375		}
4376	}],
4377
4378	['validate-currency-dollar', {
4379		errorMsg: Form.Validator.getMsg.pass('currencyDollar'),
4380		test: function(element){
4381			return Form.Validator.getValidator('IsEmpty').test(element) || (/^\$?\-?([1-9]{1}[0-9]{0,2}(\,[0-9]{3})*(\.[0-9]{0,2})?|[1-9]{1}\d*(\.[0-9]{0,2})?|0(\.[0-9]{0,2})?|(\.[0-9]{1,2})?)$/).test(element.get('value'));
4382		}
4383	}],
4384
4385	['validate-one-required', {
4386		errorMsg: Form.Validator.getMsg.pass('oneRequired'),
4387		test: function(element, props){
4388			var p = document.id(props['validate-one-required']) || element.getParent(props['validate-one-required']);
4389			return p.getElements('input').some(function(el){
4390				if (['checkbox', 'radio'].contains(el.get('type'))) return el.get('checked');
4391				return el.get('value');
4392			});
4393		}
4394	}]
4395
4396]);
4397
4398Element.Properties.validator = {
4399
4400	set: function(options){
4401		this.get('validator').setOptions(options);
4402	},
4403
4404	get: function(){
4405		var validator = this.retrieve('validator');
4406		if (!validator){
4407			validator = new Form.Validator(this);
4408			this.store('validator', validator);
4409		}
4410		return validator;
4411	}
4412
4413};
4414
4415Element.implement({
4416
4417	validate: function(options){
4418		if (options) this.set('validator', options);
4419		return this.get('validator').validate();
4420	}
4421
4422});
4423
4424
4425//<1.2compat>
4426//legacy
4427var FormValidator = Form.Validator;
4428//</1.2compat>
4429
4430
4431
4432
4433/*
4434---
4435
4436script: Form.Validator.Inline.js
4437
4438name: Form.Validator.Inline
4439
4440description: Extends Form.Validator to add inline messages.
4441
4442license: MIT-style license
4443
4444authors:
4445  - Aaron Newton
4446
4447requires:
4448  - /Form.Validator
4449
4450provides: [Form.Validator.Inline]
4451
4452...
4453*/
4454
4455Form.Validator.Inline = new Class({
4456
4457	Extends: Form.Validator,
4458
4459	options: {
4460		showError: function(errorElement){
4461			if (errorElement.reveal) errorElement.reveal();
4462			else errorElement.setStyle('display', 'block');
4463		},
4464		hideError: function(errorElement){
4465			if (errorElement.dissolve) errorElement.dissolve();
4466			else errorElement.setStyle('display', 'none');
4467		},
4468		scrollToErrorsOnSubmit: true,
4469		scrollToErrorsOnBlur: false,
4470		scrollToErrorsOnChange: false,
4471		scrollFxOptions: {
4472			transition: 'quad:out',
4473			offset: {
4474				y: -20
4475			}
4476		}
4477	},
4478
4479	initialize: function(form, options){
4480		this.parent(form, options);
4481		this.addEvent('onElementValidate', function(isValid, field, className, warn){
4482			var validator = this.getValidator(className);
4483			if (!isValid && validator.getError(field)){
4484				if (warn) field.addClass('warning');
4485				var advice = this.makeAdvice(className, field, validator.getError(field), warn);
4486				this.insertAdvice(advice, field);
4487				this.showAdvice(className, field);
4488			} else {
4489				this.hideAdvice(className, field);
4490			}
4491		});
4492	},
4493
4494	makeAdvice: function(className, field, error, warn){
4495		var errorMsg = (warn) ? this.warningPrefix : this.errorPrefix;
4496			errorMsg += (this.options.useTitles) ? field.title || error:error;
4497		var cssClass = (warn) ? 'warning-advice' : 'validation-advice';
4498		var advice = this.getAdvice(className, field);
4499		if (advice){
4500			advice = advice.set('html', errorMsg);
4501		} else {
4502			advice = new Element('div', {
4503				html: errorMsg,
4504				styles: { display: 'none' },
4505				id: 'advice-' + className.split(':')[0] + '-' + this.getFieldId(field)
4506			}).addClass(cssClass);
4507		}
4508		field.store('$moo:advice-' + className, advice);
4509		return advice;
4510	},
4511
4512	getFieldId : function(field){
4513		return field.id ? field.id : field.id = 'input_' + field.name;
4514	},
4515
4516	showAdvice: function(className, field){
4517		var advice = this.getAdvice(className, field);
4518		if (
4519			advice &&
4520			!field.retrieve('$moo:' + this.getPropName(className)) &&
4521			(
4522				advice.getStyle('display') == 'none' ||
4523				advice.getStyle('visiblity') == 'hidden' ||
4524				advice.getStyle('opacity') == 0
4525			)
4526		){
4527			field.store('$moo:' + this.getPropName(className), true);
4528			this.options.showError(advice);
4529			this.fireEvent('showAdvice', [field, advice, className]);
4530		}
4531	},
4532
4533	hideAdvice: function(className, field){
4534		var advice = this.getAdvice(className, field);
4535		if (advice && field.retrieve('$moo:' + this.getPropName(className))){
4536			field.store('$moo:' + this.getPropName(className), false);
4537			this.options.hideError(advice);
4538			this.fireEvent('hideAdvice', [field, advice, className]);
4539		}
4540	},
4541
4542	getPropName: function(className){
4543		return 'advice' + className;
4544	},
4545
4546	resetField: function(field){
4547		field = document.id(field);
4548		if (!field) return this;
4549		this.parent(field);
4550		field.get('validators').each(function(className){
4551			this.hideAdvice(className, field);
4552		}, this);
4553		return this;
4554	},
4555
4556	getAllAdviceMessages: function(field, force){
4557		var advice = [];
4558		if (field.hasClass('ignoreValidation') && !force) return advice;
4559		var validators = field.get('validators').some(function(cn){
4560			var warner = cn.test('^warn-') || field.hasClass('warnOnly');
4561			if (warner) cn = cn.replace(/^warn-/, '');
4562			var validator = this.getValidator(cn);
4563			if (!validator) return;
4564			advice.push({
4565				message: validator.getError(field),
4566				warnOnly: warner,
4567				passed: validator.test(),
4568				validator: validator
4569			});
4570		}, this);
4571		return advice;
4572	},
4573
4574	getAdvice: function(className, field){
4575		return field.retrieve('$moo:advice-' + className);
4576	},
4577
4578	insertAdvice: function(advice, field){
4579		//Check for error position prop
4580		var props = field.get('validatorProps');
4581		//Build advice
4582		if (!props.msgPos || !document.id(props.msgPos)){
4583			if (field.type && field.type.toLowerCase() == 'radio') field.getParent().adopt(advice);
4584			else advice.inject(document.id(field), 'after');
4585		} else {
4586			document.id(props.msgPos).grab(advice);
4587		}
4588	},
4589
4590	validateField: function(field, force, scroll){
4591		var result = this.parent(field, force);
4592		if (((this.options.scrollToErrorsOnSubmit && scroll == null) || scroll) && !result){
4593			var failed = document.id(this).getElement('.validation-failed');
4594			var par = document.id(this).getParent();
4595			while (par != document.body && par.getScrollSize().y == par.getSize().y){
4596				par = par.getParent();
4597			}
4598			var fx = par.retrieve('$moo:fvScroller');
4599			if (!fx && window.Fx && Fx.Scroll){
4600				fx = new Fx.Scroll(par, this.options.scrollFxOptions);
4601				par.store('$moo:fvScroller', fx);
4602			}
4603			if (failed){
4604				if (fx) fx.toElement(failed);
4605				else par.scrollTo(par.getScroll().x, failed.getPosition(par).y - 20);
4606			}
4607		}
4608		return result;
4609	},
4610
4611	watchFields: function(fields){
4612		fields.each(function(el){
4613		if (this.options.evaluateFieldsOnBlur){
4614			el.addEvent('blur', this.validationMonitor.pass([el, false, this.options.scrollToErrorsOnBlur], this));
4615		}
4616		if (this.options.evaluateFieldsOnChange){
4617				el.addEvent('change', this.validationMonitor.pass([el, true, this.options.scrollToErrorsOnChange], this));
4618			}
4619		}, this);
4620	}
4621
4622});
4623
4624
4625/*
4626---
4627
4628script: Form.Validator.Extras.js
4629
4630name: Form.Validator.Extras
4631
4632description: Additional validators for the Form.Validator class.
4633
4634license: MIT-style license
4635
4636authors:
4637  - Aaron Newton
4638
4639requires:
4640  - /Form.Validator
4641
4642provides: [Form.Validator.Extras]
4643
4644...
4645*/
4646Form.Validator.addAllThese([
4647
4648	['validate-enforce-oncheck', {
4649		test: function(element, props){
4650			var fv = element.getParent('form').retrieve('validator');
4651			if (!fv) return true;
4652			(props.toEnforce || document.id(props.enforceChildrenOf).getElements('input, select, textarea')).map(function(item){
4653				if (element.checked){
4654					fv.enforceField(item);
4655				} else {
4656					fv.ignoreField(item);
4657					fv.resetField(item);
4658				}
4659			});
4660			return true;
4661		}
4662	}],
4663
4664	['validate-ignore-oncheck', {
4665		test: function(element, props){
4666			var fv = element.getParent('form').retrieve('validator');
4667			if (!fv) return true;
4668			(props.toIgnore || document.id(props.ignoreChildrenOf).getElements('input, select, textarea')).each(function(item){
4669				if (element.checked){
4670					fv.ignoreField(item);
4671					fv.resetField(item);
4672				} else {
4673					fv.enforceField(item);
4674				}
4675			});
4676			return true;
4677		}
4678	}],
4679
4680	['validate-nospace', {
4681		errorMsg: function(){
4682			return Form.Validator.getMsg('noSpace');
4683		},
4684		test: function(element, props){
4685			return !element.get('value').test(/\s/);
4686		}
4687	}],
4688
4689	['validate-toggle-oncheck', {
4690		test: function(element, props){
4691			var fv = element.getParent('form').retrieve('validator');
4692			if (!fv) return true;
4693			var eleArr = props.toToggle || document.id(props.toToggleChildrenOf).getElements('input, select, textarea');
4694			if (!element.checked){
4695				eleArr.each(function(item){
4696					fv.ignoreField(item);
4697					fv.resetField(item);
4698				});
4699			} else {
4700				eleArr.each(function(item){
4701					fv.enforceField(item);
4702				});
4703			}
4704			return true;
4705		}
4706	}],
4707
4708	['validate-reqchk-bynode', {
4709		errorMsg: function(){
4710			return Form.Validator.getMsg('reqChkByNode');
4711		},
4712		test: function(element, props){
4713			return (document.id(props.nodeId).getElements(props.selector || 'input[type=checkbox], input[type=radio]')).some(function(item){
4714				return item.checked;
4715			});
4716		}
4717	}],
4718
4719	['validate-required-check', {
4720		errorMsg: function(element, props){
4721			return props.useTitle ? element.get('title') : Form.Validator.getMsg('requiredChk');
4722		},
4723		test: function(element, props){
4724			return !!element.checked;
4725		}
4726	}],
4727
4728	['validate-reqchk-byname', {
4729		errorMsg: function(element, props){
4730			return Form.Validator.getMsg('reqChkByName').substitute({label: props.label || element.get('type')});
4731		},
4732		test: function(element, props){
4733			var grpName = props.groupName || element.get('name');
4734			var oneCheckedItem = $$(document.getElementsByName(grpName)).some(function(item, index){
4735				return item.checked;
4736			});
4737			var fv = element.getParent('form').retrieve('validator');
4738			if (oneCheckedItem && fv) fv.resetField(element);
4739			return oneCheckedItem;
4740		}
4741	}],
4742
4743	['validate-match', {
4744		errorMsg: function(element, props){
4745			return Form.Validator.getMsg('match').substitute({matchName: props.matchName || document.id(props.matchInput).get('name')});
4746		},
4747		test: function(element, props){
4748			var eleVal = element.get('value');
4749			var matchVal = document.id(props.matchInput) && document.id(props.matchInput).get('value');
4750			return eleVal && matchVal ? eleVal == matchVal : true;
4751		}
4752	}],
4753
4754	['validate-after-date', {
4755		errorMsg: function(element, props){
4756			return Form.Validator.getMsg('afterDate').substitute({
4757				label: props.afterLabel || (props.afterElement ? Form.Validator.getMsg('startDate') : Form.Validator.getMsg('currentDate'))
4758			});
4759		},
4760		test: function(element, props){
4761			var start = document.id(props.afterElement) ? Date.parse(document.id(props.afterElement).get('value')) : new Date();
4762			var end = Date.parse(element.get('value'));
4763			return end && start ? end >= start : true;
4764		}
4765	}],
4766
4767	['validate-before-date', {
4768		errorMsg: function(element, props){
4769			return Form.Validator.getMsg('beforeDate').substitute({
4770				label: props.beforeLabel || (props.beforeElement ? Form.Validator.getMsg('endDate') : Form.Validator.getMsg('currentDate'))
4771			});
4772		},
4773		test: function(element, props){
4774			var start = Date.parse(element.get('value'));
4775			var end = document.id(props.beforeElement) ? Date.parse(document.id(props.beforeElement).get('value')) : new Date();
4776			return end && start ? end >= start : true;
4777		}
4778	}],
4779
4780	['validate-custom-required', {
4781		errorMsg: function(){
4782			return Form.Validator.getMsg('required');
4783		},
4784		test: function(element, props){
4785			return element.get('value') != props.emptyValue;
4786		}
4787	}],
4788
4789	['validate-same-month', {
4790		errorMsg: function(element, props){
4791			var startMo = document.id(props.sameMonthAs) && document.id(props.sameMonthAs).get('value');
4792			var eleVal = element.get('value');
4793			if (eleVal != '') return Form.Validator.getMsg(startMo ? 'sameMonth' : 'startMonth');
4794		},
4795		test: function(element, props){
4796			var d1 = Date.parse(element.get('value'));
4797			var d2 = Date.parse(document.id(props.sameMonthAs) && document.id(props.sameMonthAs).get('value'));
4798			return d1 && d2 ? d1.format('%B') == d2.format('%B') : true;
4799		}
4800	}],
4801
4802
4803	['validate-cc-num', {
4804		errorMsg: function(element){
4805			var ccNum = element.get('value').replace(/[^0-9]/g, '');
4806			return Form.Validator.getMsg('creditcard').substitute({length: ccNum.length});
4807		},
4808		test: function(element){
4809			// required is a different test
4810			if (Form.Validator.getValidator('IsEmpty').test(element)) return true;
4811
4812			// Clean number value
4813			var ccNum = element.get('value');
4814			ccNum = ccNum.replace(/[^0-9]/g, '');
4815
4816			var valid_type = false;
4817
4818			if (ccNum.test(/^4[0-9]{12}([0-9]{3})?$/)) valid_type = 'Visa';
4819			else if (ccNum.test(/^5[1-5]([0-9]{14})$/)) valid_type = 'Master Card';
4820			else if (ccNum.test(/^3[47][0-9]{13}$/)) valid_type = 'American Express';
4821			else if (ccNum.test(/^6011[0-9]{12}$/)) valid_type = 'Discover';
4822
4823			if (valid_type){
4824				var sum = 0;
4825				var cur = 0;
4826
4827				for (var i=ccNum.length-1; i>=0; --i){
4828					cur = ccNum.charAt(i).toInt();
4829					if (cur == 0) continue;
4830
4831					if ((ccNum.length-i) % 2 == 0) cur += cur;
4832					if (cur > 9){
4833						cur = cur.toString().charAt(0).toInt() + cur.toString().charAt(1).toInt();
4834					}
4835
4836					sum += cur;
4837				}
4838				if ((sum % 10) == 0) return true;
4839			}
4840
4841			var chunks = '';
4842			while (ccNum != ''){
4843				chunks += ' ' + ccNum.substr(0,4);
4844				ccNum = ccNum.substr(4);
4845			}
4846
4847			element.getParent('form').retrieve('validator').ignoreField(element);
4848			element.set('value', chunks.clean());
4849			element.getParent('form').retrieve('validator').enforceField(element);
4850			return false;
4851		}
4852	}]
4853
4854
4855]);
4856
4857
4858/*
4859---
4860
4861script: OverText.js
4862
4863name: OverText
4864
4865description: Shows text over an input that disappears when the user clicks into it. The text remains hidden if the user adds a value.
4866
4867license: MIT-style license
4868
4869authors:
4870  - Aaron Newton
4871
4872requires:
4873  - Core/Options
4874  - Core/Events
4875  - Core/Element.Event
4876  - Class.Binds
4877  - Class.Occlude
4878  - Element.Position
4879  - Element.Shortcuts
4880
4881provides: [OverText]
4882
4883...
4884*/
4885
4886var OverText = new Class({
4887
4888	Implements: [Options, Events, Class.Occlude],
4889
4890	Binds: ['reposition', 'assert', 'focus', 'hide'],
4891
4892	options: {/*
4893		textOverride: null,
4894		onFocus: function(){},
4895		onTextHide: function(textEl, inputEl){},
4896		onTextShow: function(textEl, inputEl){}, */
4897		element: 'label',
4898		labelClass: 'overTxtLabel',
4899		positionOptions: {
4900			position: 'upperLeft',
4901			edge: 'upperLeft',
4902			offset: {
4903				x: 4,
4904				y: 2
4905			}
4906		},
4907		poll: false,
4908		pollInterval: 250,
4909		wrap: false
4910	},
4911
4912	property: 'OverText',
4913
4914	initialize: function(element, options){
4915		element = this.element = document.id(element);
4916
4917		if (this.occlude()) return this.occluded;
4918		this.setOptions(options);
4919
4920		this.attach(element);
4921		OverText.instances.push(this);
4922
4923		if (this.options.poll) this.poll();
4924	},
4925
4926	toElement: function(){
4927		return this.element;
4928	},
4929
4930	attach: function(){
4931		var element = this.element,
4932			options = this.options,
4933			value = options.textOverride || element.get('alt') || element.get('title');
4934
4935		if (!value) return this;
4936
4937		var text = this.text = new Element(options.element, {
4938			'class': options.labelClass,
4939			styles: {
4940				lineHeight: 'normal',
4941				position: 'absolute',
4942				cursor: 'text'
4943			},
4944			html: value,
4945			events: {
4946				click: this.hide.pass(options.element == 'label', this)
4947			}
4948		}).inject(element, 'after');
4949
4950		if (options.element == 'label'){
4951			if (!element.get('id')) element.set('id', 'input_' + String.uniqueID());
4952			text.set('for', element.get('id'));
4953		}
4954
4955		if (options.wrap){
4956			this.textHolder = new Element('div.overTxtWrapper', {
4957				styles: {
4958					lineHeight: 'normal',
4959					position: 'relative'
4960				}
4961			}).grab(text).inject(element, 'before');
4962		}
4963
4964		return this.enable();
4965	},
4966
4967	destroy: function(){
4968		this.element.eliminate(this.property); // Class.Occlude storage
4969		this.disable();
4970		if (this.text) this.text.destroy();
4971		if (this.textHolder) this.textHolder.destroy();
4972		return this;
4973	},
4974
4975	disable: function(){
4976		this.element.removeEvents({
4977			focus: this.focus,
4978			blur: this.assert,
4979			change: this.assert
4980		});
4981		window.removeEvent('resize', this.reposition);
4982		this.hide(true, true);
4983		return this;
4984	},
4985
4986	enable: function(){
4987		this.element.addEvents({
4988			focus: this.focus,
4989			blur: this.assert,
4990			change: this.assert
4991		});
4992		window.addEvent('resize', this.reposition);
4993		this.reposition();
4994		return this;
4995	},
4996
4997	wrap: function(){
4998		if (this.options.element == 'label'){
4999			if (!this.element.get('id')) this.element.set('id', 'input_' + String.uniqueID());
5000			this.text.set('for', this.element.get('id'));
5001		}
5002	},
5003
5004	startPolling: function(){
5005		this.pollingPaused = false;
5006		return this.poll();
5007	},
5008
5009	poll: function(stop){
5010		//start immediately
5011		//pause on focus
5012		//resumeon blur
5013		if (this.poller && !stop) return this;
5014		if (stop){
5015			clearInterval(this.poller);
5016		} else {
5017			this.poller = (function(){
5018				if (!this.pollingPaused) this.assert(true);
5019			}).periodical(this.options.pollInterval, this);
5020		}
5021
5022		return this;
5023	},
5024
5025	stopPolling: function(){
5026		this.pollingPaused = true;
5027		return this.poll(true);
5028	},
5029
5030	focus: function(){
5031		if (this.text && (!this.text.isDisplayed() || this.element.get('disabled'))) return this;
5032		return this.hide();
5033	},
5034
5035	hide: function(suppressFocus, force){
5036		if (this.text && (this.text.isDisplayed() && (!this.element.get('disabled') || force))){
5037			this.text.hide();
5038			this.fireEvent('textHide', [this.text, this.element]);
5039			this.pollingPaused = true;
5040			if (!suppressFocus){
5041				try {
5042					this.element.fireEvent('focus');
5043					this.element.focus();
5044				} catch(e){} //IE barfs if you call focus on hidden elements
5045			}
5046		}
5047		return this;
5048	},
5049
5050	show: function(){
5051		if (this.text && !this.text.isDisplayed()){
5052			this.text.show();
5053			this.reposition();
5054			this.fireEvent('textShow', [this.text, this.element]);
5055			this.pollingPaused = false;
5056		}
5057		return this;
5058	},
5059
5060	test: function(){
5061		return !this.element.get('value');
5062	},
5063
5064	assert: function(suppressFocus){
5065		return this[this.test() ? 'show' : 'hide'](suppressFocus);
5066	},
5067
5068	reposition: function(){
5069		this.assert(true);
5070		if (!this.element.isVisible()) return this.stopPolling().hide();
5071		if (this.text && this.test()){
5072			this.text.position(Object.merge(this.options.positionOptions, {
5073				relativeTo: this.element
5074			}));
5075		}
5076		return this;
5077	}
5078
5079});
5080
5081OverText.instances = [];
5082
5083Object.append(OverText, {
5084
5085	each: function(fn){
5086		return OverText.instances.each(function(ot, i){
5087			if (ot.element && ot.text) fn.call(OverText, ot, i);
5088		});
5089	},
5090
5091	update: function(){
5092
5093		return OverText.each(function(ot){
5094			return ot.reposition();
5095		});
5096
5097	},
5098
5099	hideAll: function(){
5100
5101		return OverText.each(function(ot){
5102			return ot.hide(true, true);
5103		});
5104
5105	},
5106
5107	showAll: function(){
5108		return OverText.each(function(ot){
5109			return ot.show();
5110		});
5111	}
5112
5113});
5114
5115
5116
5117/*
5118---
5119
5120script: Fx.Elements.js
5121
5122name: Fx.Elements
5123
5124description: Effect to change any number of CSS properties of any number of Elements.
5125
5126license: MIT-style license
5127
5128authors:
5129  - Valerio Proietti
5130
5131requires:
5132  - Core/Fx.CSS
5133  - /MooTools.More
5134
5135provides: [Fx.Elements]
5136
5137...
5138*/
5139
5140Fx.Elements = new Class({
5141
5142	Extends: Fx.CSS,
5143
5144	initialize: function(elements, options){
5145		this.elements = this.subject = $$(elements);
5146		this.parent(options);
5147	},
5148
5149	compute: function(from, to, delta){
5150		var now = {};
5151
5152		for (var i in from){
5153			var iFrom = from[i], iTo = to[i], iNow = now[i] = {};
5154			for (var p in iFrom) iNow[p] = this.parent(iFrom[p], iTo[p], delta);
5155		}
5156
5157		return now;
5158	},
5159
5160	set: function(now){
5161		for (var i in now){
5162			if (!this.elements[i]) continue;
5163
5164			var iNow = now[i];
5165			for (var p in iNow) this.render(this.elements[i], p, iNow[p], this.options.unit);
5166		}
5167
5168		return this;
5169	},
5170
5171	start: function(obj){
5172		if (!this.check(obj)) return this;
5173		var from = {}, to = {};
5174
5175		for (var i in obj){
5176			if (!this.elements[i]) continue;
5177
5178			var iProps = obj[i], iFrom = from[i] = {}, iTo = to[i] = {};
5179
5180			for (var p in iProps){
5181				var parsed = this.prepare(this.elements[i], p, iProps[p]);
5182				iFrom[p] = parsed.from;
5183				iTo[p] = parsed.to;
5184			}
5185		}
5186
5187		return this.parent(from, to);
5188	}
5189
5190});
5191
5192
5193/*
5194---
5195
5196script: Fx.Accordion.js
5197
5198name: Fx.Accordion
5199
5200description: An Fx.Elements extension which allows you to easily create accordion type controls.
5201
5202license: MIT-style license
5203
5204authors:
5205  - Valerio Proietti
5206
5207requires:
5208  - Core/Element.Event
5209  - /Fx.Elements
5210
5211provides: [Fx.Accordion]
5212
5213...
5214*/
5215
5216Fx.Accordion = new Class({
5217
5218	Extends: Fx.Elements,
5219
5220	options: {/*
5221		onActive: function(toggler, section){},
5222		onBackground: function(toggler, section){},*/
5223		fixedHeight: false,
5224		fixedWidth: false,
5225		display: 0,
5226		show: false,
5227		height: true,
5228		width: false,
5229		opacity: true,
5230		alwaysHide: false,
5231		trigger: 'click',
5232		initialDisplayFx: true,
5233		resetHeight: true
5234	},
5235
5236	initialize: function(){
5237		var defined = function(obj){
5238			return obj != null;
5239		};
5240
5241		var params = Array.link(arguments, {
5242			'container': Type.isElement, //deprecated
5243			'options': Type.isObject,
5244			'togglers': defined,
5245			'elements': defined
5246		});
5247		this.parent(params.elements, params.options);
5248
5249		var options = this.options,
5250			togglers = this.togglers = $$(params.togglers);
5251
5252		this.previous = -1;
5253		this.internalChain = new Chain();
5254
5255		if (options.alwaysHide) this.options.link = 'chain';
5256
5257		if (options.show || this.options.show === 0){
5258			options.display = false;
5259			this.previous = options.show;
5260		}
5261
5262		if (options.start){
5263			options.display = false;
5264			options.show = false;
5265		}
5266
5267		var effects = this.effects = {};
5268
5269		if (options.opacity) effects.opacity = 'fullOpacity';
5270		if (options.width) effects.width = options.fixedWidth ? 'fullWidth' : 'offsetWidth';
5271		if (options.height) effects.height = options.fixedHeight ? 'fullHeight' : 'scrollHeight';
5272
5273		for (var i = 0, l = togglers.length; i < l; i++) this.addSection(togglers[i], this.elements[i]);
5274
5275		this.elements.each(function(el, i){
5276			if (options.show === i){
5277				this.fireEvent('active', [togglers[i], el]);
5278			} else {
5279				for (var fx in effects) el.setStyle(fx, 0);
5280			}
5281		}, this);
5282
5283		if (options.display || options.display === 0 || options.initialDisplayFx === false){
5284			this.display(options.display, options.initialDisplayFx);
5285		}
5286
5287		if (options.fixedHeight !== false) options.resetHeight = false;
5288		this.addEvent('complete', this.internalChain.callChain.bind(this.internalChain));
5289	},
5290
5291	addSection: function(toggler, element){
5292		toggler = document.id(toggler);
5293		element = document.id(element);
5294		this.togglers.include(toggler);
5295		this.elements.include(element);
5296
5297		var togglers = this.togglers,
5298			options = this.options,
5299			test = togglers.contains(toggler),
5300			idx = togglers.indexOf(toggler),
5301			displayer = this.display.pass(idx, this);
5302
5303		toggler.store('accordion:display', displayer)
5304			.addEvent(options.trigger, displayer);
5305
5306		if (options.height) element.setStyles({'padding-top': 0, 'border-top': 'none', 'padding-bottom': 0, 'border-bottom': 'none'});
5307		if (options.width) element.setStyles({'padding-left': 0, 'border-left': 'none', 'padding-right': 0, 'border-right': 'none'});
5308
5309		element.fullOpacity = 1;
5310		if (options.fixedWidth) element.fullWidth = options.fixedWidth;
5311		if (options.fixedHeight) element.fullHeight = options.fixedHeight;
5312		element.setStyle('overflow', 'hidden');
5313
5314		if (!test) for (var fx in this.effects){
5315			element.setStyle(fx, 0);
5316		}
5317		return this;
5318	},
5319
5320	removeSection: function(toggler, displayIndex){
5321		var togglers = this.togglers,
5322			idx = togglers.indexOf(toggler),
5323			element = this.elements[idx];
5324
5325		var remover = function(){
5326			togglers.erase(toggler);
5327			this.elements.erase(element);
5328			this.detach(toggler);
5329		}.bind(this);
5330
5331		if (this.now == idx || displayIndex != null){
5332			this.display(displayIndex != null ? displayIndex : (idx - 1 >= 0 ? idx - 1 : 0)).chain(remover);
5333		} else {
5334			remover();
5335		}
5336		return this;
5337	},
5338
5339	detach: function(toggler){
5340		var remove = function(toggler){
5341			toggler.removeEvent(this.options.trigger, toggler.retrieve('accordion:display'));
5342		}.bind(this);
5343
5344		if (!toggler) this.togglers.each(remove);
5345		else remove(toggler);
5346		return this;
5347	},
5348
5349	display: function(index, useFx){
5350		if (!this.check(index, useFx)) return this;
5351
5352		var obj = {},
5353			elements = this.elements,
5354			options = this.options,
5355			effects = this.effects;
5356
5357		if (useFx == null) useFx = true;
5358		if (typeOf(index) == 'element') index = elements.indexOf(index);
5359		if (index == this.previous && !options.alwaysHide) return this;
5360
5361		if (options.resetHeight){
5362			var prev = elements[this.previous];
5363			if (prev && !this.selfHidden){
5364				for (var fx in effects) prev.setStyle(fx, prev[effects[fx]]);
5365			}
5366		}
5367
5368		if ((this.timer && options.link == 'chain') || (index === this.previous && !options.alwaysHide)) return this;
5369
5370		this.previous = index;
5371		this.selfHidden = false;
5372
5373		elements.each(function(el, i){
5374			obj[i] = {};
5375			var hide;
5376			if (i != index){
5377				hide = true;
5378			} else if (options.alwaysHide && ((el.offsetHeight > 0 && options.height) || el.offsetWidth > 0 && options.width)){
5379				hide = true;
5380				this.selfHidden = true;
5381			}
5382			this.fireEvent(hide ? 'background' : 'active', [this.togglers[i], el]);
5383			for (var fx in effects) obj[i][fx] = hide ? 0 : el[effects[fx]];
5384			if (!useFx && !hide && options.resetHeight) obj[i].height = 'auto';
5385		}, this);
5386
5387		this.internalChain.clearChain();
5388		this.internalChain.chain(function(){
5389			if (options.resetHeight && !this.selfHidden){
5390				var el = elements[index];
5391				if (el) el.setStyle('height', 'auto');
5392			}
5393		}.bind(this));
5394
5395		return useFx ? this.start(obj) : this.set(obj).internalChain.callChain();
5396	}
5397
5398});
5399
5400/*<1.2compat>*/
5401/*
5402	Compatibility with 1.2.0
5403*/
5404var Accordion = new Class({
5405
5406	Extends: Fx.Accordion,
5407
5408	initialize: function(){
5409		this.parent.apply(this, arguments);
5410		var params = Array.link(arguments, {'container': Type.isElement});
5411		this.container = params.container;
5412	},
5413
5414	addSection: function(toggler, element, pos){
5415		toggler = document.id(toggler);
5416		element = document.id(element);
5417
5418		var test = this.togglers.contains(toggler);
5419		var len = this.togglers.length;
5420		if (len && (!test || pos)){
5421			pos = pos != null ? pos : len - 1;
5422			toggler.inject(this.togglers[pos], 'before');
5423			element.inject(toggler, 'after');
5424		} else if (this.container && !test){
5425			toggler.inject(this.container);
5426			element.inject(this.container);
5427		}
5428		return this.parent.apply(this, arguments);
5429	}
5430
5431});
5432/*</1.2compat>*/
5433
5434
5435/*
5436---
5437
5438script: Fx.Move.js
5439
5440name: Fx.Move
5441
5442description: Defines Fx.Move, a class that works with Element.Position.js to transition an element from one location to another.
5443
5444license: MIT-style license
5445
5446authors:
5447  - Aaron Newton
5448
5449requires:
5450  - Core/Fx.Morph
5451  - /Element.Position
5452
5453provides: [Fx.Move]
5454
5455...
5456*/
5457
5458Fx.Move = new Class({
5459
5460	Extends: Fx.Morph,
5461
5462	options: {
5463		relativeTo: document.body,
5464		position: 'center',
5465		edge: false,
5466		offset: {x: 0, y: 0}
5467	},
5468
5469	start: function(destination){
5470		var element = this.element,
5471			topLeft = element.getStyles('top', 'left');
5472		if (topLeft.top == 'auto' || topLeft.left == 'auto'){
5473			element.setPosition(element.getPosition(element.getOffsetParent()));
5474		}
5475		return this.parent(element.position(Object.merge({}, this.options, destination, {returnPos: true})));
5476	}
5477
5478});
5479
5480Element.Properties.move = {
5481
5482	set: function(options){
5483		this.get('move').cancel().setOptions(options);
5484		return this;
5485	},
5486
5487	get: function(){
5488		var move = this.retrieve('move');
5489		if (!move){
5490			move = new Fx.Move(this, {link: 'cancel'});
5491			this.store('move', move);
5492		}
5493		return move;
5494	}
5495
5496};
5497
5498Element.implement({
5499
5500	move: function(options){
5501		this.get('move').start(options);
5502		return this;
5503	}
5504
5505});
5506
5507
5508/*
5509---
5510
5511script: Fx.Scroll.js
5512
5513name: Fx.Scroll
5514
5515description: Effect to smoothly scroll any element, including the window.
5516
5517license: MIT-style license
5518
5519authors:
5520  - Valerio Proietti
5521
5522requires:
5523  - Core/Fx
5524  - Core/Element.Event
5525  - Core/Element.Dimensions
5526  - /MooTools.More
5527
5528provides: [Fx.Scroll]
5529
5530...
5531*/
5532
5533(function(){
5534
5535Fx.Scroll = new Class({
5536
5537	Extends: Fx,
5538
5539	options: {
5540		offset: {x: 0, y: 0},
5541		wheelStops: true
5542	},
5543
5544	initialize: function(element, options){
5545		this.element = this.subject = document.id(element);
5546		this.parent(options);
5547
5548		if (typeOf(this.element) != 'element') this.element = document.id(this.element.getDocument().body);
5549
5550		if (this.options.wheelStops){
5551			var stopper = this.element,
5552				cancel = this.cancel.pass(false, this);
5553			this.addEvent('start', function(){
5554				stopper.addEvent('mousewheel', cancel);
5555			}, true);
5556			this.addEvent('complete', function(){
5557				stopper.removeEvent('mousewheel', cancel);
5558			}, true);
5559		}
5560	},
5561
5562	set: function(){
5563		var now = Array.flatten(arguments);
5564		if (Browser.firefox) now = [Math.round(now[0]), Math.round(now[1])]; // not needed anymore in newer firefox versions
5565		this.element.scrollTo(now[0], now[1]);
5566		return this;
5567	},
5568
5569	compute: function(from, to, delta){
5570		return [0, 1].map(function(i){
5571			return Fx.compute(from[i], to[i], delta);
5572		});
5573	},
5574
5575	start: function(x, y){
5576		if (!this.check(x, y)) return this;
5577		var scroll = this.element.getScroll();
5578		return this.parent([scroll.x, scroll.y], [x, y]);
5579	},
5580
5581	calculateScroll: function(x, y){
5582		var element = this.element,
5583			scrollSize = element.getScrollSize(),
5584			scroll = element.getScroll(),
5585			size = element.getSize(),
5586			offset = this.options.offset,
5587			values = {x: x, y: y};
5588
5589		for (var z in values){
5590			if (!values[z] && values[z] !== 0) values[z] = scroll[z];
5591			if (typeOf(values[z]) != 'number') values[z] = scrollSize[z] - size[z];
5592			values[z] += offset[z];
5593		}
5594
5595		return [values.x, values.y];
5596	},
5597
5598	toTop: function(){
5599		return this.start.apply(this, this.calculateScroll(false, 0));
5600	},
5601
5602	toLeft: function(){
5603		return this.start.apply(this, this.calculateScroll(0, false));
5604	},
5605
5606	toRight: function(){
5607		return this.start.apply(this, this.calculateScroll('right', false));
5608	},
5609
5610	toBottom: function(){
5611		return this.start.apply(this, this.calculateScroll(false, 'bottom'));
5612	},
5613
5614	toElement: function(el, axes){
5615		axes = axes ? Array.from(axes) : ['x', 'y'];
5616		var scroll = isBody(this.element) ? {x: 0, y: 0} : this.element.getScroll();
5617		var position = Object.map(document.id(el).getPosition(this.element), function(value, axis){
5618			return axes.contains(axis) ? value + scroll[axis] : false;
5619		});
5620		return this.start.apply(this, this.calculateScroll(position.x, position.y));
5621	},
5622
5623	toElementEdge: function(el, axes, offset){
5624		axes = axes ? Array.from(axes) : ['x', 'y'];
5625		el = document.id(el);
5626		var to = {},
5627			position = el.getPosition(this.element),
5628			size = el.getSize(),
5629			scroll = this.element.getScroll(),
5630			containerSize = this.element.getSize(),
5631			edge = {
5632				x: position.x + size.x,
5633				y: position.y + size.y
5634			};
5635
5636		['x', 'y'].each(function(axis){
5637			if (axes.contains(axis)){
5638				if (edge[axis] > scroll[axis] + containerSize[axis]) to[axis] = edge[axis] - containerSize[axis];
5639				if (position[axis] < scroll[axis]) to[axis] = position[axis];
5640			}
5641			if (to[axis] == null) to[axis] = scroll[axis];
5642			if (offset && offset[axis]) to[axis] = to[axis] + offset[axis];
5643		}, this);
5644
5645		if (to.x != scroll.x || to.y != scroll.y) this.start(to.x, to.y);
5646		return this;
5647	},
5648
5649	toElementCenter: function(el, axes, offset){
5650		axes = axes ? Array.from(axes) : ['x', 'y'];
5651		el = document.id(el);
5652		var to = {},
5653			position = el.getPosition(this.element),
5654			size = el.getSize(),
5655			scroll = this.element.getScroll(),
5656			containerSize = this.element.getSize();
5657
5658		['x', 'y'].each(function(axis){
5659			if (axes.contains(axis)){
5660				to[axis] = position[axis] - (containerSize[axis] - size[axis]) / 2;
5661			}
5662			if (to[axis] == null) to[axis] = scroll[axis];
5663			if (offset && offset[axis]) to[axis] = to[axis] + offset[axis];
5664		}, this);
5665
5666		if (to.x != scroll.x || to.y != scroll.y) this.start(to.x, to.y);
5667		return this;
5668	}
5669
5670});
5671
5672//<1.2compat>
5673Fx.Scroll.implement({
5674	scrollToCenter: function(){
5675		return this.toElementCenter.apply(this, arguments);
5676	},
5677	scrollIntoView: function(){
5678		return this.toElementEdge.apply(this, arguments);
5679	}
5680});
5681//</1.2compat>
5682
5683function isBody(element){
5684	return (/^(?:body|html)$/i).test(element.tagName);
5685}
5686
5687})();
5688
5689
5690/*
5691---
5692
5693script: Fx.Slide.js
5694
5695name: Fx.Slide
5696
5697description: Effect to slide an element in and out of view.
5698
5699license: MIT-style license
5700
5701authors:
5702  - Valerio Proietti
5703
5704requires:
5705  - Core/Fx
5706  - Core/Element.Style
5707  - /MooTools.More
5708
5709provides: [Fx.Slide]
5710
5711...
5712*/
5713
5714Fx.Slide = new Class({
5715
5716	Extends: Fx,
5717
5718	options: {
5719		mode: 'vertical',
5720		wrapper: false,
5721		hideOverflow: true,
5722		resetHeight: false
5723	},
5724
5725	initialize: function(element, options){
5726		element = this.element = this.subject = document.id(element);
5727		this.parent(options);
5728		options = this.options;
5729
5730		var wrapper = element.retrieve('wrapper'),
5731			styles = element.getStyles('margin', 'position', 'overflow');
5732
5733		if (options.hideOverflow) styles = Object.append(styles, {overflow: 'hidden'});
5734		if (options.wrapper) wrapper = document.id(options.wrapper).setStyles(styles);
5735
5736		if (!wrapper) wrapper = new Element('div', {
5737			styles: styles
5738		}).wraps(element);
5739
5740		element.store('wrapper', wrapper).setStyle('margin', 0);
5741		if (element.getStyle('overflow') == 'visible') element.setStyle('overflow', 'hidden');
5742
5743		this.now = [];
5744		this.open = true;
5745		this.wrapper = wrapper;
5746
5747		this.addEvent('complete', function(){
5748			this.open = (wrapper['offset' + this.layout.capitalize()] != 0);
5749			if (this.open && this.options.resetHeight) wrapper.setStyle('height', '');
5750		}, true);
5751	},
5752
5753	vertical: function(){
5754		this.margin = 'margin-top';
5755		this.layout = 'height';
5756		this.offset = this.element.offsetHeight;
5757	},
5758
5759	horizontal: function(){
5760		this.margin = 'margin-left';
5761		this.layout = 'width';
5762		this.offset = this.element.offsetWidth;
5763	},
5764
5765	set: function(now){
5766		this.element.setStyle(this.margin, now[0]);
5767		this.wrapper.setStyle(this.layout, now[1]);
5768		return this;
5769	},
5770
5771	compute: function(from, to, delta){
5772		return [0, 1].map(function(i){
5773			return Fx.compute(from[i], to[i], delta);
5774		});
5775	},
5776
5777	start: function(how, mode){
5778		if (!this.check(how, mode)) return this;
5779		this[mode || this.options.mode]();
5780
5781		var margin = this.element.getStyle(this.margin).toInt(),
5782			layout = this.wrapper.getStyle(this.layout).toInt(),
5783			caseIn = [[margin, layout], [0, this.offset]],
5784			caseOut = [[margin, layout], [-this.offset, 0]],
5785			start;
5786
5787		switch (how){
5788			case 'in': start = caseIn; break;
5789			case 'out': start = caseOut; break;
5790			case 'toggle': start = (layout == 0) ? caseIn : caseOut;
5791		}
5792		return this.parent(start[0], start[1]);
5793	},
5794
5795	slideIn: function(mode){
5796		return this.start('in', mode);
5797	},
5798
5799	slideOut: function(mode){
5800		return this.start('out', mode);
5801	},
5802
5803	hide: function(mode){
5804		this[mode || this.options.mode]();
5805		this.open = false;
5806		return this.set([-this.offset, 0]);
5807	},
5808
5809	show: function(mode){
5810		this[mode || this.options.mode]();
5811		this.open = true;
5812		return this.set([0, this.offset]);
5813	},
5814
5815	toggle: function(mode){
5816		return this.start('toggle', mode);
5817	}
5818
5819});
5820
5821Element.Properties.slide = {
5822
5823	set: function(options){
5824		this.get('slide').cancel().setOptions(options);
5825		return this;
5826	},
5827
5828	get: function(){
5829		var slide = this.retrieve('slide');
5830		if (!slide){
5831			slide = new Fx.Slide(this, {link: 'cancel'});
5832			this.store('slide', slide);
5833		}
5834		return slide;
5835	}
5836
5837};
5838
5839Element.implement({
5840
5841	slide: function(how, mode){
5842		how = how || 'toggle';
5843		var slide = this.get('slide'), toggle;
5844		switch (how){
5845			case 'hide': slide.hide(mode); break;
5846			case 'show': slide.show(mode); break;
5847			case 'toggle':
5848				var flag = this.retrieve('slide:flag', slide.open);
5849				slide[flag ? 'slideOut' : 'slideIn'](mode);
5850				this.store('slide:flag', !flag);
5851				toggle = true;
5852			break;
5853			default: slide.start(how, mode);
5854		}
5855		if (!toggle) this.eliminate('slide:flag');
5856		return this;
5857	}
5858
5859});
5860
5861
5862/*
5863---
5864
5865script: Fx.SmoothScroll.js
5866
5867name: Fx.SmoothScroll
5868
5869description: Class for creating a smooth scrolling effect to all internal links on the page.
5870
5871license: MIT-style license
5872
5873authors:
5874  - Valerio Proietti
5875
5876requires:
5877  - Core/Slick.Finder
5878  - /Fx.Scroll
5879
5880provides: [Fx.SmoothScroll]
5881
5882...
5883*/
5884
5885/*<1.2compat>*/var SmoothScroll = /*</1.2compat>*/Fx.SmoothScroll = new Class({
5886
5887	Extends: Fx.Scroll,
5888
5889	options: {
5890		axes: ['x', 'y']
5891	},
5892
5893	initialize: function(options, context){
5894		context = context || document;
5895		this.doc = context.getDocument();
5896		this.parent(this.doc, options);
5897
5898		var win = context.getWindow(),
5899			location = win.location.href.match(/^[^#]*/)[0] + '#',
5900			links = $$(this.options.links || this.doc.links);
5901
5902		links.each(function(link){
5903			if (link.href.indexOf(location) != 0) return;
5904			var anchor = link.href.substr(location.length);
5905			if (anchor) this.useLink(link, anchor);
5906		}, this);
5907
5908		this.addEvent('complete', function(){
5909			win.location.hash = this.anchor;
5910			this.element.scrollTo(this.to[0], this.to[1]);
5911		}, true);
5912	},
5913
5914	useLink: function(link, anchor){
5915
5916		link.addEvent('click', function(event){
5917			var el = document.id(anchor) || this.doc.getElement('a[name=' + anchor + ']');
5918			if (!el) return;
5919
5920			event.preventDefault();
5921			this.toElement(el, this.options.axes).chain(function(){
5922				this.fireEvent('scrolledTo', [link, el]);
5923			}.bind(this));
5924
5925			this.anchor = anchor;
5926
5927		}.bind(this));
5928
5929		return this;
5930	}
5931});
5932
5933
5934/*
5935---
5936
5937script: Fx.Sort.js
5938
5939name: Fx.Sort
5940
5941description: Defines Fx.Sort, a class that reorders lists with a transition.
5942
5943license: MIT-style license
5944
5945authors:
5946  - Aaron Newton
5947
5948requires:
5949  - Core/Element.Dimensions
5950  - /Fx.Elements
5951  - /Element.Measure
5952
5953provides: [Fx.Sort]
5954
5955...
5956*/
5957
5958Fx.Sort = new Class({
5959
5960	Extends: Fx.Elements,
5961
5962	options: {
5963		mode: 'vertical'
5964	},
5965
5966	initialize: function(elements, options){
5967		this.parent(elements, options);
5968		this.elements.each(function(el){
5969			if (el.getStyle('position') == 'static') el.setStyle('position', 'relative');
5970		});
5971		this.setDefaultOrder();
5972	},
5973
5974	setDefaultOrder: function(){
5975		this.currentOrder = this.elements.map(function(el, index){
5976			return index;
5977		});
5978	},
5979
5980	sort: function(){
5981		if (!this.check(arguments)) return this;
5982		var newOrder = Array.flatten(arguments);
5983
5984		var top = 0,
5985			left = 0,
5986			next = {},
5987			zero = {},
5988			vert = this.options.mode == 'vertical';
5989
5990		var current = this.elements.map(function(el, index){
5991			var size = el.getComputedSize({styles: ['border', 'padding', 'margin']});
5992			var val;
5993			if (vert){
5994				val = {
5995					top: top,
5996					margin: size['margin-top'],
5997					height: size.totalHeight
5998				};
5999				top += val.height - size['margin-top'];
6000			} else {
6001				val = {
6002					left: left,
6003					margin: size['margin-left'],
6004					width: size.totalWidth
6005				};
6006				left += val.width;
6007			}
6008			var plane = vert ? 'top' : 'left';
6009			zero[index] = {};
6010			var start = el.getStyle(plane).toInt();
6011			zero[index][plane] = start || 0;
6012			return val;
6013		}, this);
6014
6015		this.set(zero);
6016		newOrder = newOrder.map(function(i){ return i.toInt(); });
6017		if (newOrder.length != this.elements.length){
6018			this.currentOrder.each(function(index){
6019				if (!newOrder.contains(index)) newOrder.push(index);
6020			});
6021			if (newOrder.length > this.elements.length)
6022				newOrder.splice(this.elements.length-1, newOrder.length - this.elements.length);
6023		}
6024		var margin = 0;
6025		top = left = 0;
6026		newOrder.each(function(item){
6027			var newPos = {};
6028			if (vert){
6029				newPos.top = top - current[item].top - margin;
6030				top += current[item].height;
6031			} else {
6032				newPos.left = left - current[item].left;
6033				left += current[item].width;
6034			}
6035			margin = margin + current[item].margin;
6036			next[item]=newPos;
6037		}, this);
6038		var mapped = {};
6039		Array.clone(newOrder).sort().each(function(index){
6040			mapped[index] = next[index];
6041		});
6042		this.start(mapped);
6043		this.currentOrder = newOrder;
6044
6045		return this;
6046	},
6047
6048	rearrangeDOM: function(newOrder){
6049		newOrder = newOrder || this.currentOrder;
6050		var parent = this.elements[0].getParent();
6051		var rearranged = [];
6052		this.elements.setStyle('opacity', 0);
6053		//move each element and store the new default order
6054		newOrder.each(function(index){
6055			rearranged.push(this.elements[index].inject(parent).setStyles({
6056				top: 0,
6057				left: 0
6058			}));
6059		}, this);
6060		this.elements.setStyle('opacity', 1);
6061		this.elements = $$(rearranged);
6062		this.setDefaultOrder();
6063		return this;
6064	},
6065
6066	getDefaultOrder: function(){
6067		return this.elements.map(function(el, index){
6068			return index;
6069		});
6070	},
6071
6072	getCurrentOrder: function(){
6073		return this.currentOrder;
6074	},
6075
6076	forward: function(){
6077		return this.sort(this.getDefaultOrder());
6078	},
6079
6080	backward: function(){
6081		return this.sort(this.getDefaultOrder().reverse());
6082	},
6083
6084	reverse: function(){
6085		return this.sort(this.currentOrder.reverse());
6086	},
6087
6088	sortByElements: function(elements){
6089		return this.sort(elements.map(function(el){
6090			return this.elements.indexOf(el);
6091		}, this));
6092	},
6093
6094	swap: function(one, two){
6095		if (typeOf(one) == 'element') one = this.elements.indexOf(one);
6096		if (typeOf(two) == 'element') two = this.elements.indexOf(two);
6097
6098		var newOrder = Array.clone(this.currentOrder);
6099		newOrder[this.currentOrder.indexOf(one)] = two;
6100		newOrder[this.currentOrder.indexOf(two)] = one;
6101
6102		return this.sort(newOrder);
6103	}
6104
6105});
6106
6107
6108/*
6109---
6110
6111script: Assets.js
6112
6113name: Assets
6114
6115description: Provides methods to dynamically load JavaScript, CSS, and Image files into the document.
6116
6117license: MIT-style license
6118
6119authors:
6120  - Valerio Proietti
6121
6122requires:
6123  - Core/Element.Event
6124  - /MooTools.More
6125
6126provides: [Assets]
6127
6128...
6129*/
6130
6131var Asset = {
6132
6133	javascript: function(source, properties){
6134		if (!properties) properties = {};
6135
6136		var script = new Element('script', {src: source, type: 'text/javascript'}),
6137			doc = properties.document || document,
6138			load = properties.onload || properties.onLoad;
6139
6140		delete properties.onload;
6141		delete properties.onLoad;
6142		delete properties.document;
6143
6144		if (load){
6145			if (typeof script.onreadystatechange != 'undefined'){
6146				script.addEvent('readystatechange', function(){
6147					if (['loaded', 'complete'].contains(this.readyState)) load.call(this);
6148				});
6149			} else {
6150				script.addEvent('load', load);
6151			}
6152		}
6153
6154		return script.set(properties).inject(doc.head);
6155	},
6156
6157	css: function(source, properties){
6158		if (!properties) properties = {};
6159
6160		var link = new Element('link', {
6161			rel: 'stylesheet',
6162			media: 'screen',
6163			type: 'text/css',
6164			href: source
6165		});
6166
6167		var load = properties.onload || properties.onLoad,
6168			doc = properties.document || document;
6169
6170		delete properties.onload;
6171		delete properties.onLoad;
6172		delete properties.document;
6173
6174		if (load) link.addEvent('load', load);
6175		return link.set(properties).inject(doc.head);
6176	},
6177
6178	image: function(source, properties){
6179		if (!properties) properties = {};
6180
6181		var image = new Image(),
6182			element = document.id(image) || new Element('img');
6183
6184		['load', 'abort', 'error'].each(function(name){
6185			var type = 'on' + name,
6186				cap = 'on' + name.capitalize(),
6187				event = properties[type] || properties[cap] || function(){};
6188
6189			delete properties[cap];
6190			delete properties[type];
6191
6192			image[type] = function(){
6193				if (!image) return;
6194				if (!element.parentNode){
6195					element.width = image.width;
6196					element.height = image.height;
6197				}
6198				image = image.onload = image.onabort = image.onerror = null;
6199				event.delay(1, element, element);
6200				element.fireEvent(name, element, 1);
6201			};
6202		});
6203
6204		image.src = element.src = source;
6205		if (image && image.complete) image.onload.delay(1);
6206		return element.set(properties);
6207	},
6208
6209	images: function(sources, options){
6210		sources = Array.from(sources);
6211
6212		var fn = function(){},
6213			counter = 0;
6214
6215		options = Object.merge({
6216			onComplete: fn,
6217			onProgress: fn,
6218			onError: fn,
6219			properties: {}
6220		}, options);
6221
6222		return new Elements(sources.map(function(source, index){
6223			return Asset.image(source, Object.append(options.properties, {
6224				onload: function(){
6225					counter++;
6226					options.onProgress.call(this, counter, index, source);
6227					if (counter == sources.length) options.onComplete();
6228				},
6229				onerror: function(){
6230					counter++;
6231					options.onError.call(this, counter, index, source);
6232					if (counter == sources.length) options.onComplete();
6233				}
6234			}));
6235		}));
6236	}
6237
6238};
6239
6240
6241/*
6242---
6243
6244script: Hash.Cookie.js
6245
6246name: Hash.Cookie
6247
6248description: Class for creating, reading, and deleting Cookies in JSON format.
6249
6250license: MIT-style license
6251
6252authors:
6253  - Valerio Proietti
6254  - Aaron Newton
6255
6256requires:
6257  - Core/Cookie
6258  - Core/JSON
6259  - /MooTools.More
6260  - /Hash
6261
6262provides: [Hash.Cookie]
6263
6264...
6265*/
6266
6267Hash.Cookie = new Class({
6268
6269	Extends: Cookie,
6270
6271	options: {
6272		autoSave: true
6273	},
6274
6275	initialize: function(name, options){
6276		this.parent(name, options);
6277		this.load();
6278	},
6279
6280	save: function(){
6281		var value = JSON.encode(this.hash);
6282		if (!value || value.length > 4096) return false; //cookie would be truncated!
6283		if (value == '{}') this.dispose();
6284		else this.write(value);
6285		return true;
6286	},
6287
6288	load: function(){
6289		this.hash = new Hash(JSON.decode(this.read(), true));
6290		return this;
6291	}
6292
6293});
6294
6295Hash.each(Hash.prototype, function(method, name){
6296	if (typeof method == 'function') Hash.Cookie.implement(name, function(){
6297		var value = method.apply(this.hash, arguments);
6298		if (this.options.autoSave) this.save();
6299		return value;
6300	});
6301});
6302
6303
6304/*
6305---
6306
6307script: Keyboard.js
6308
6309name: Keyboard
6310
6311description: KeyboardEvents used to intercept events on a class for keyboard and format modifiers in a specific order so as to make alt+shift+c the same as shift+alt+c.
6312
6313license: MIT-style license
6314
6315authors:
6316  - Perrin Westrich
6317  - Aaron Newton
6318  - Scott Kyle
6319
6320requires:
6321  - Core/Events
6322  - Core/Options
6323  - Core/Element.Event
6324  - Element.Event.Pseudos.Keys
6325
6326provides: [Keyboard]
6327
6328...
6329*/
6330
6331(function(){
6332
6333	var Keyboard = this.Keyboard = new Class({
6334
6335		Extends: Events,
6336
6337		Implements: [Options],
6338
6339		options: {/*
6340			onActivate: function(){},
6341			onDeactivate: function(){},*/
6342			defaultEventType: 'keydown',
6343			active: false,
6344			manager: null,
6345			events: {},
6346			nonParsedEvents: ['activate', 'deactivate', 'onactivate', 'ondeactivate', 'changed', 'onchanged']
6347		},
6348
6349		initialize: function(options){
6350			if (options && options.manager){
6351				this._manager = options.manager;
6352				delete options.manager;
6353			}
6354			this.setOptions(options);
6355			this._setup();
6356		},
6357
6358		addEvent: function(type, fn, internal){
6359			return this.parent(Keyboard.parse(type, this.options.defaultEventType, this.options.nonParsedEvents), fn, internal);
6360		},
6361
6362		removeEvent: function(type, fn){
6363			return this.parent(Keyboard.parse(type, this.options.defaultEventType, this.options.nonParsedEvents), fn);
6364		},
6365
6366		toggleActive: function(){
6367			return this[this.isActive() ? 'deactivate' : 'activate']();
6368		},
6369
6370		activate: function(instance){
6371			if (instance){
6372				if (instance.isActive()) return this;
6373				//if we're stealing focus, store the last keyboard to have it so the relinquish command works
6374				if (this._activeKB && instance != this._activeKB){
6375					this.previous = this._activeKB;
6376					this.previous.fireEvent('deactivate');
6377				}
6378				//if we're enabling a child, assign it so that events are now passed to it
6379				this._activeKB = instance.fireEvent('activate');
6380				Keyboard.manager.fireEvent('changed');
6381			} else if (this._manager){
6382				//else we're enabling ourselves, we must ask our parent to do it for us
6383				this._manager.activate(this);
6384			}
6385			return this;
6386		},
6387
6388		isActive: function(){
6389			return this._manager ? (this._manager._activeKB == this) : (Keyboard.manager == this);
6390		},
6391
6392		deactivate: function(instance){
6393			if (instance){
6394				if (instance === this._activeKB){
6395					this._activeKB = null;
6396					instance.fireEvent('deactivate');
6397					Keyboard.manager.fireEvent('changed');
6398				}
6399			} else if (this._manager){
6400				this._manager.deactivate(this);
6401			}
6402			return this;
6403		},
6404
6405		relinquish: function(){
6406			if (this.isActive() && this._manager && this._manager.previous) this._manager.activate(this._manager.previous);
6407			else this.deactivate();
6408			return this;
6409		},
6410
6411		//management logic
6412		manage: function(instance){
6413			if (instance._manager) instance._manager.drop(instance);
6414			this._instances.push(instance);
6415			instance._manager = this;
6416			if (!this._activeKB) this.activate(instance);
6417			return this;
6418		},
6419
6420		drop: function(instance){
6421			instance.relinquish();
6422			this._instances.erase(instance);
6423			if (this._activeKB == instance){
6424				if (this.previous && this._instances.contains(this.previous)) this.activate(this.previous);
6425				else this._activeKB = this._instances[0];
6426			}
6427			return this;
6428		},
6429
6430		trace: function(){
6431			Keyboard.trace(this);
6432		},
6433
6434		each: function(fn){
6435			Keyboard.each(this, fn);
6436		},
6437
6438		/*
6439			PRIVATE METHODS
6440		*/
6441
6442		_instances: [],
6443
6444		_disable: function(instance){
6445			if (this._activeKB == instance) this._activeKB = null;
6446		},
6447
6448		_setup: function(){
6449			this.addEvents(this.options.events);
6450			//if this is the root manager, nothing manages it
6451			if (Keyboard.manager && !this._manager) Keyboard.manager.manage(this);
6452			if (this.options.active) this.activate();
6453			else this.relinquish();
6454		},
6455
6456		_handle: function(event, type){
6457			//Keyboard.stop(event) prevents key propagation
6458			if (event.preventKeyboardPropagation) return;
6459
6460			var bubbles = !!this._manager;
6461			if (bubbles && this._activeKB){
6462				this._activeKB._handle(event, type);
6463				if (event.preventKeyboardPropagation) return;
6464			}
6465			this.fireEvent(type, event);
6466
6467			if (!bubbles && this._activeKB) this._activeKB._handle(event, type);
6468		}
6469
6470	});
6471
6472	var parsed = {};
6473	var modifiers = ['shift', 'control', 'alt', 'meta'];
6474	var regex = /^(?:shift|control|ctrl|alt|meta)$/;
6475
6476	Keyboard.parse = function(type, eventType, ignore){
6477		if (ignore && ignore.contains(type.toLowerCase())) return type;
6478
6479		type = type.toLowerCase().replace(/^(keyup|keydown):/, function($0, $1){
6480			eventType = $1;
6481			return '';
6482		});
6483
6484		if (!parsed[type]){
6485			var key, mods = {};
6486			type.split('+').each(function(part){
6487				if (regex.test(part)) mods[part] = true;
6488				else key = part;
6489			});
6490
6491			mods.control = mods.control || mods.ctrl; // allow both control and ctrl
6492
6493			var keys = [];
6494			modifiers.each(function(mod){
6495				if (mods[mod]) keys.push(mod);
6496			});
6497
6498			if (key) keys.push(key);
6499			parsed[type] = keys.join('+');
6500		}
6501
6502		return eventType + ':keys(' + parsed[type] + ')';
6503	};
6504
6505	Keyboard.each = function(keyboard, fn){
6506		var current = keyboard || Keyboard.manager;
6507		while (current){
6508			fn.run(current);
6509			current = current._activeKB;
6510		}
6511	};
6512
6513	Keyboard.stop = function(event){
6514		event.preventKeyboardPropagation = true;
6515	};
6516
6517	Keyboard.manager = new Keyboard({
6518		active: true
6519	});
6520
6521	Keyboard.trace = function(keyboard){
6522		keyboard = keyboard || Keyboard.manager;
6523		var hasConsole = window.console && console.log;
6524		if (hasConsole) console.log('the following items have focus: ');
6525		Keyboard.each(keyboard, function(current){
6526			if (hasConsole) console.log(document.id(current.widget) || current.wiget || current);
6527		});
6528	};
6529
6530	var handler = function(event){
6531		var keys = [];
6532		modifiers.each(function(mod){
6533			if (event[mod]) keys.push(mod);
6534		});
6535
6536		if (!regex.test(event.key)) keys.push(event.key);
6537		Keyboard.manager._handle(event, event.type + ':keys(' + keys.join('+') + ')');
6538	};
6539
6540	document.addEvents({
6541		'keyup': handler,
6542		'keydown': handler
6543	});
6544
6545})();
6546
6547
6548/*
6549---
6550
6551script: Keyboard.Extras.js
6552
6553name: Keyboard.Extras
6554
6555description: Enhances Keyboard by adding the ability to name and describe keyboard shortcuts, and the ability to grab shortcuts by name and bind the shortcut to different keys.
6556
6557license: MIT-style license
6558
6559authors:
6560  - Perrin Westrich
6561
6562requires:
6563  - /Keyboard
6564  - /MooTools.More
6565
6566provides: [Keyboard.Extras]
6567
6568...
6569*/
6570Keyboard.prototype.options.nonParsedEvents.combine(['rebound', 'onrebound']);
6571
6572Keyboard.implement({
6573
6574	/*
6575		shortcut should be in the format of:
6576		{
6577			'keys': 'shift+s', // the default to add as an event.
6578			'description': 'blah blah blah', // a brief description of the functionality.
6579			'handler': function(){} // the event handler to run when keys are pressed.
6580		}
6581	*/
6582	addShortcut: function(name, shortcut){
6583		this._shortcuts = this._shortcuts || [];
6584		this._shortcutIndex = this._shortcutIndex || {};
6585
6586		shortcut.getKeyboard = Function.from(this);
6587		shortcut.name = name;
6588		this._shortcutIndex[name] = shortcut;
6589		this._shortcuts.push(shortcut);
6590		if (shortcut.keys) this.addEvent(shortcut.keys, shortcut.handler);
6591		return this;
6592	},
6593
6594	addShortcuts: function(obj){
6595		for (var name in obj) this.addShortcut(name, obj[name]);
6596		return this;
6597	},
6598
6599	removeShortcut: function(name){
6600		var shortcut = this.getShortcut(name);
6601		if (shortcut && shortcut.keys){
6602			this.removeEvent(shortcut.keys, shortcut.handler);
6603			delete this._shortcutIndex[name];
6604			this._shortcuts.erase(shortcut);
6605		}
6606		return this;
6607	},
6608
6609	removeShortcuts: function(names){
6610		names.each(this.removeShortcut, this);
6611		return this;
6612	},
6613
6614	getShortcuts: function(){
6615		return this._shortcuts || [];
6616	},
6617
6618	getShortcut: function(name){
6619		return (this._shortcutIndex || {})[name];
6620	}
6621
6622});
6623
6624Keyboard.rebind = function(newKeys, shortcuts){
6625	Array.from(shortcuts).each(function(shortcut){
6626		shortcut.getKeyboard().removeEvent(shortcut.keys, shortcut.handler);
6627		shortcut.getKeyboard().addEvent(newKeys, shortcut.handler);
6628		shortcut.keys = newKeys;
6629		shortcut.getKeyboard().fireEvent('rebound');
6630	});
6631};
6632
6633
6634Keyboard.getActiveShortcuts = function(keyboard){
6635	var activeKBS = [], activeSCS = [];
6636	Keyboard.each(keyboard, [].push.bind(activeKBS));
6637	activeKBS.each(function(kb){ activeSCS.extend(kb.getShortcuts()); });
6638	return activeSCS;
6639};
6640
6641Keyboard.getShortcut = function(name, keyboard, opts){
6642	opts = opts || {};
6643	var shortcuts = opts.many ? [] : null,
6644		set = opts.many ? function(kb){
6645				var shortcut = kb.getShortcut(name);
6646				if (shortcut) shortcuts.push(shortcut);
6647			} : function(kb){
6648				if (!shortcuts) shortcuts = kb.getShortcut(name);
6649			};
6650	Keyboard.each(keyboard, set);
6651	return shortcuts;
6652};
6653
6654Keyboard.getShortcuts = function(name, keyboard){
6655	return Keyboard.getShortcut(name, keyboard, { many: true });
6656};
6657
6658
6659/*
6660---
6661
6662script: Scroller.js
6663
6664name: Scroller
6665
6666description: Class which scrolls the contents of any Element (including the window) when the mouse reaches the Element's boundaries.
6667
6668license: MIT-style license
6669
6670authors:
6671  - Valerio Proietti
6672
6673requires:
6674  - Core/Events
6675  - Core/Options
6676  - Core/Element.Event
6677  - Core/Element.Dimensions
6678  - MooTools.More
6679
6680provides: [Scroller]
6681
6682...
6683*/
6684
6685var Scroller = new Class({
6686
6687	Implements: [Events, Options],
6688
6689	options: {
6690		area: 20,
6691		velocity: 1,
6692		onChange: function(x, y){
6693			this.element.scrollTo(x, y);
6694		},
6695		fps: 50
6696	},
6697
6698	initialize: function(element, options){
6699		this.setOptions(options);
6700		this.element = document.id(element);
6701		this.docBody = document.id(this.element.getDocument().body);
6702		this.listener = (typeOf(this.element) != 'element') ? this.docBody : this.element;
6703		this.timer = null;
6704		this.bound = {
6705			attach: this.attach.bind(this),
6706			detach: this.detach.bind(this),
6707			getCoords: this.getCoords.bind(this)
6708		};
6709	},
6710
6711	start: function(){
6712		this.listener.addEvents({
6713			mouseover: this.bound.attach,
6714			mouseleave: this.bound.detach
6715		});
6716		return this;
6717	},
6718
6719	stop: function(){
6720		this.listener.removeEvents({
6721			mouseover: this.bound.attach,
6722			mouseleave: this.bound.detach
6723		});
6724		this.detach();
6725		this.timer = clearInterval(this.timer);
6726		return this;
6727	},
6728
6729	attach: function(){
6730		this.listener.addEvent('mousemove', this.bound.getCoords);
6731	},
6732
6733	detach: function(){
6734		this.listener.removeEvent('mousemove', this.bound.getCoords);
6735		this.timer = clearInterval(this.timer);
6736	},
6737
6738	getCoords: function(event){
6739		this.page = (this.listener.get('tag') == 'body') ? event.client : event.page;
6740		if (!this.timer) this.timer = this.scroll.periodical(Math.round(1000 / this.options.fps), this);
6741	},
6742
6743	scroll: function(){
6744		var size = this.element.getSize(),
6745			scroll = this.element.getScroll(),
6746			pos = this.element != this.docBody ? this.element.getOffsets() : {x: 0, y:0},
6747			scrollSize = this.element.getScrollSize(),
6748			change = {x: 0, y: 0},
6749			top = this.options.area.top || this.options.area,
6750			bottom = this.options.area.bottom || this.options.area;
6751		for (var z in this.page){
6752			if (this.page[z] < (top + pos[z]) && scroll[z] != 0){
6753				change[z] = (this.page[z] - top - pos[z]) * this.options.velocity;
6754			} else if (this.page[z] + bottom > (size[z] + pos[z]) && scroll[z] + size[z] != scrollSize[z]){
6755				change[z] = (this.page[z] - size[z] + bottom - pos[z]) * this.options.velocity;
6756			}
6757			change[z] = change[z].round();
6758		}
6759		if (change.y || change.x) this.fireEvent('change', [scroll.x + change.x, scroll.y + change.y]);
6760	}
6761
6762});
6763
6764
6765/*
6766---
6767
6768script: Tips.js
6769
6770name: Tips
6771
6772description: Class for creating nice tips that follow the mouse 
6772cursor when hovering an element.
6773
6774license: MIT-style license
6775
6776authors:
6777  - Valerio Proietti
6778  - Christoph Pojer
6779  - Luis Merino
6780
6781requires:
6782  - Core/Options
6783  - Core/Events
6784  - Core/Element.Event
6785  - Core/Element.Style
6786  - Core/Element.Dimensions
6787  - /MooTools.More
6788
6789provides: [Tips]
6790
6791...
6792*/
6793
6794(function(){
6795
6796var read = function(option, element){
6797	return (option) ? (typeOf(option) == 'function' ? option(element) : element.get(option)) : '';
6798};
6799
6800this.Tips = new Class({
6801
6802	Implements: [Events, Options],
6803
6804	options: {/*
6805		id: null,
6806		onAttach: function(element){},
6807		onDetach: function(element){},
6808		onBound: function(coords){},*/
6809		onShow: function(){
6810			this.tip.setStyle('display', 'block');
6811		},
6812		onHide: function(){
6813			this.tip.setStyle('display', 'none');
6814		},
6815		title: 'title',
6816		text: function(element){
6817			return element.get('rel') || element.get('href');
6818		},
6819		showDelay: 100,
6820		hideDelay: 100,
6821		className: 'tip-wrap',
6822		offset: {x: 16, y: 16},
6823		windowPadding: {x:0, y:0},
6824		fixed: false,
6825		waiAria: true
6826	},
6827
6828	initialize: function(){
6829		var params = Array.link(arguments, {
6830			options: Type.isObject,
6831			elements: function(obj){
6832				return obj != null;
6833			}
6834		});
6835		this.setOptions(params.options);
6836		if (params.elements) this.attach(params.elements);
6837		this.container = new Element('div', {'class': 'tip'});
6838
6839		if (this.options.id){
6840			this.container.set('id', this.options.id);
6841			if (this.options.waiAria) this.attachWaiAria();
6842		}
6843	},
6844
6845	toElement: function(){
6846		if (this.tip) return this.tip;
6847
6848		this.tip = new Element('div', {
6849			'class': this.options.className,
6850			styles: {
6851				position: 'absolute',
6852				top: 0,
6853				left: 0
6854			}
6855		}).adopt(
6856			new Element('div', {'class': 'tip-top'}),
6857			this.container,
6858			new Element('div', {'class': 'tip-bottom'})
6859		);
6860
6861		return this.tip;
6862	},
6863
6864	attachWaiAria: function(){
6865		var id = this.options.id;
6866		this.container.set('role', 'tooltip');
6867
6868		if (!this.waiAria){
6869			this.waiAria = {
6870				show: function(element){
6871					if (id) element.set('aria-describedby', id);
6872					this.container.set('aria-hidden', 'false');
6873				},
6874				hide: function(element){
6875					if (id) element.erase('aria-describedby');
6876					this.container.set('aria-hidden', 'true');
6877				}
6878			};
6879		}
6880		this.addEvents(this.waiAria);
6881	},
6882
6883	detachWaiAria: function(){
6884		if (this.waiAria){
6885			this.container.erase('role');
6886			this.container.erase('aria-hidden');
6887			this.removeEvents(this.waiAria);
6888		}
6889	},
6890
6891	attach: function(elements){
6892		$$(elements).each(function(element){
6893			var title = read(this.options.title, element),
6894				text = read(this.options.text, element);
6895
6896			element.set('title', '').store('tip:native', title).retrieve('tip:title', title);
6897			element.retrieve('tip:text', text);
6898			this.fireEvent('attach', [element]);
6899
6900			var events = ['enter', 'leave'];
6901			if (!this.options.fixed) events.push('move');
6902
6903			events.each(function(value){
6904				var event = element.retrieve('tip:' + value);
6905				if (!event) event = function(event){
6906					this['element' + value.capitalize()].apply(this, [event, element]);
6907				}.bind(this);
6908
6909				element.store('tip:' + value, event).addEvent('mouse' + value, event);
6910			}, this);
6911		}, this);
6912
6913		return this;
6914	},
6915
6916	detach: function(elements){
6917		$$(elements).each(function(element){
6918			['enter', 'leave', 'move'].each(function(value){
6919				element.removeEvent('mouse' + value, element.retrieve('tip:' + value)).eliminate('tip:' + value);
6920			});
6921
6922			this.fireEvent('detach', [element]);
6923
6924			if (this.options.title == 'title'){ // This is necessary to check if we can revert the title
6925				var original = element.retrieve('tip:native');
6926				if (original) element.set('title', original);
6927			}
6928		}, this);
6929
6930		return this;
6931	},
6932
6933	elementEnter: function(event, element){
6934		clearTimeout(this.timer);
6935		this.timer = (function(){
6936			this.container.empty();
6937
6938			['title', 'text'].each(function(value){
6939				var content = element.retrieve('tip:' + value);
6940				var div = this['_' + value + 'Element'] = new Element('div', {
6941						'class': 'tip-' + value
6942					}).inject(this.container);
6943				if (content) this.fill(div, content);
6944			}, this);
6945			this.show(element);
6946			this.position((this.options.fixed) ? {page: element.getPosition()} : event);
6947		}).delay(this.options.showDelay, this);
6948	},
6949
6950	elementLeave: function(event, element){
6951		clearTimeout(this.timer);
6952		this.timer = this.hide.delay(this.options.hideDelay, this, element);
6953		this.fireForParent(event, element);
6954	},
6955
6956	setTitle: function(title){
6957		if (this._titleElement){
6958			this._titleElement.empty();
6959			this.fill(this._titleElement, title);
6960		}
6961		return this;
6962	},
6963
6964	setText: function(text){
6965		if (this._textElement){
6966			this._textElement.empty();
6967			this.fill(this._textElement, text);
6968		}
6969		return this;
6970	},
6971
6972	fireForParent: function(event, element){
6973		element = element.getParent();
6974		if (!element || element == document.body) return;
6975		if (element.retrieve('tip:enter')) element.fireEvent('mouseenter', event);
6976		else this.fireForParent(event, element);
6977	},
6978
6979	elementMove: function(event, element){
6980		this.position(event);
6981	},
6982
6983	position: function(event){
6984		if (!this.tip) document.id(this);
6985
6986		var size = window.getSize(), scroll = window.getScroll(),
6987			tip = {x: this.tip.offsetWidth, y: this.tip.offsetHeight},
6988			props = {x: 'left', y: 'top'},
6989			bounds = {y: false, x2: false, y2: false, x: false},
6990			obj = {};
6991
6992		for (var z in props){
6993			obj[props[z]] = event.page[z] + this.options.offset[z];
6994			if (obj[props[z]] < 0) bounds[z] = true;
6995			if ((obj[props[z]] + tip[z] - scroll[z]) > size[z] - this.options.windowPadding[z]){
6996				obj[props[z]] = event.page[z] - this.options.offset[z] - tip[z];
6997				bounds[z+'2'] = true;
6998			}
6999		}
7000
7001		this.fireEvent('bound', bounds);
7002		this.tip.setStyles(obj);
7003	},
7004
7005	fill: function(element, contents){
7006		if (typeof contents == 'string') element.set('html', contents);
7007		else element.adopt(contents);
7008	},
7009
7010	show: function(element){
7011		if (!this.tip) document.id(this);
7012		if (!this.tip.getParent()) this.tip.inject(document.body);
7013		this.fireEvent('show', [this.tip, element]);
7014	},
7015
7016	hide: function(element){
7017		if (!this.tip) document.id(this);
7018		this.fireEvent('hide', [this.tip, element]);
7019	}
7020
7021});
7022
7023})();
7024

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.