Mootools/more : Added date format parameter to Date::Parse() method (propose on githu...
[ldapsaisie.git] / public_html / includes / js / mootools-more.js
1 // MooTools: the javascript framework.
2 // Load this file's selection again by visiting: http://mootools.net/more/9cc6f160bc261365539063600b3d6bfe 
3 // Or build this file again with packager using: packager build More/More More/Events.Pseudos More/Class.Refactor More/Class.Binds More/Class.Occlude More/Chain.Wait More/Array.Extras More/Date More/Date.Extras More/Number.Format More/Object.Extras More/String.Extras More/String.QueryString More/URI More/URI.Relative More/Hash More/Hash.Extras More/Element.Forms More/Elements.From More/Element.Event.Pseudos More/Element.Event.Pseudos.Keys More/Element.Delegation 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.Reveal More/Fx.Scroll More/Fx.Slide More/Fx.SmoothScroll More/Fx.Sort More/Drag More/Drag.Move More/Slider More/Sortables More/Request.JSONP More/Request.Queue More/Request.Periodical More/Assets More/Color More/Group More/Hash.Cookie More/IframeShim More/Table More/HtmlTable More/HtmlTable.Zebra More/HtmlTable.Sort More/HtmlTable.Select More/Keyboard More/Keyboard.Extras More/Mask More/Scroller More/Tips More/Spinner More/Locale More/Locale.Set.From More/Locale.en-US.Date More/Locale.en-US.Form.Validator More/Locale.en-US.Number More/Locale.ar.Date More/Locale.ar.Form.Validator More/Locale.ca-CA.Date More/Locale.ca-CA.Form.Validator More/Locale.cs-CZ.Date More/Locale.cs-CZ.Form.Validator More/Locale.da-DK.Date More/Locale.da-DK.Form.Validator More/Locale.de-CH.Date More/Locale.de-CH.Form.Validator More/Locale.de-DE.Date More/Locale.de-DE.Form.Validator More/Locale.de-DE.Number More/Locale.en-GB.Date More/Locale.es-AR.Date More/Locale.es-AR.Form.Validator More/Locale.es-ES.Date More/Locale.es-ES.Form.Validator More/Locale.et-EE.Date More/Locale.et-EE.Form.Validator More/Locale.EU.Number More/Locale.fa.Date More/Locale.fa.Form.Validator More/Locale.fi-FI.Date More/Locale.fi-FI.Form.Validator More/Locale.fi-FI.Number More/Locale.fr-FR.Date More/Locale.fr-FR.Form.Validator More/Locale.fr-FR.Number More/Locale.he-IL.Date More/Locale.he-IL.Form.Validator More/Locale.he-IL.Number More/Locale.hu-HU.Date More/Locale.hu-HU.Form.Validator More/Locale.it-IT.Date More/Locale.it-IT.Form.Validator More/Locale.ja-JP.Date More/Locale.ja-JP.Form.Validator More/Locale.ja-JP.Number More/Locale.nl-NL.Date More/Locale.nl-NL.Form.Validator More/Locale.nl-NL.Number More/Locale.no-NO.Date More/Locale.no-NO.Form.Validator More/Locale.pl-PL.Date More/Locale.pl-PL.Form.Validator More/Locale.pt-BR.Date More/Locale.pt-BR.Form.Validator More/Locale.pt-PT.Date More/Locale.pt-PT.Form.Validator More/Locale.ru-RU-unicode.Date More/Locale.ru-RU-unicode.Form.Validator More/Locale.si-SI.Date More/Locale.si-SI.Form.Validator More/Locale.sv-SE.Date More/Locale.sv-SE.Form.Validator More/Locale.uk-UA.Date More/Locale.uk-UA.Form.Validator More/Locale.zh-CH.Date More/Locale.zh-CH.Form.Validator
4 /*
5 ---
6
7 script: More.js
8
9 name: More
10
11 description: MooTools More
12
13 license: MIT-style license
14
15 authors:
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
25 requires:
26   - Core/MooTools
27
28 provides: [MooTools.More]
29
30 ...
31 */
32
33 MooTools.More = {
34         'version': '1.3.2.1',
35         'build': 'e586bcd2496e9b22acfde32e12f84d49ce09e59d'
36 };
37
38
39 /*
40 ---
41
42 name: Events.Pseudos
43
44 description: Adds the functionality to add pseudo events
45
46 license: MIT-style license
47
48 authors:
49   - Arian Stolwijk
50
51 requires: [Core/Class.Extras, Core/Slick.Parser, More/MooTools.More]
52
53 provides: [Events.Pseudos]
54
55 ...
56 */
57
58 Events.Pseudos = function(pseudos, addEvent, removeEvent){
59
60         var storeKey = 'monitorEvents:';
61
62         var storageOf = function(object){
63                 return {
64                         store: object.store ? function(key, value){
65                                 object.store(storeKey + key, value);
66                         } : function(key, value){
67                                 (object.$monitorEvents || (object.$monitorEvents = {}))[key] = value;
68                         },
69                         retrieve: object.retrieve ? function(key, dflt){
70                                 return object.retrieve(storeKey + key, dflt);
71                         } : function(key, dflt){
72                                 if (!object.$monitorEvents) return dflt;
73                                 return object.$monitorEvents[key] || dflt;
74                         }
75                 };
76         };
77
78         var splitType = function(type){
79                 if (type.indexOf(':') == -1 || !pseudos) return null;
80
81                 var parsed = Slick.parse(type).expressions[0][0],
82                         parsedPseudos = parsed.pseudos,
83                         l = parsedPseudos.length,
84                         splits = [];
85
86                 while (l--) if (pseudos[parsedPseudos[l].key]){
87                         splits.push({
88                                 event: parsed.tag,
89                                 value: parsedPseudos[l].value,
90                                 pseudo: parsedPseudos[l].key,
91                                 original: type
92                         });
93                 }
94
95                 return splits.length ? splits : null;
96         };
97
98         var mergePseudoOptions = function(split){
99                 return Object.merge.apply(this, split.map(function(item){
100                         return pseudos[item.pseudo].options || {};
101                 }));
102         };
103
104         return {
105
106                 addEvent: function(type, fn, internal){
107                         var split = splitType(type);
108                         if (!split) return addEvent.call(this, type, fn, internal);
109
110                         var storage = storageOf(this),
111                                 events = storage.retrieve(type, []),
112                                 eventType = split[0].event,
113                                 options = mergePseudoOptions(split),
114                                 stack = fn,
115                                 eventOptions = options[eventType] || {},
116                                 args = Array.slice(arguments, 2),
117                                 self = this,
118                                 monitor;
119
120                         if (eventOptions.args) args.append(Array.from(eventOptions.args));
121                         if (eventOptions.base) eventType = eventOptions.base;
122                         if (eventOptions.onAdd) eventOptions.onAdd(this);
123
124                         split.each(function(item){
125                                 var stackFn = stack;
126                                 stack = function(){
127                                         (eventOptions.listener || pseudos[item.pseudo].listener).call(self, item, stackFn, arguments, monitor, options);
128                                 };
129                         });
130                         monitor = stack.bind(this);
131
132                         events.include({event: fn, monitor: monitor});
133                         storage.store(type, events);
134
135                         addEvent.apply(this, [type, fn].concat(args));
136                         return addEvent.apply(this, [eventType, monitor].concat(args));
137                 },
138
139                 removeEvent: function(type, fn){
140                         var split = splitType(type);
141                         if (!split) return removeEvent.call(this, type, fn);
142
143                         var storage = storageOf(this),
144                                 events = storage.retrieve(type);
145                         if (!events) return this;
146
147                         var eventType = split[0].event,
148                                 options = mergePseudoOptions(split),
149                                 eventOptions = options[eventType] || {},
150                                 args = Array.slice(arguments, 2);
151
152                         if (eventOptions.args) args.append(Array.from(eventOptions.args));
153                         if (eventOptions.base) eventType = eventOptions.base;
154                         if (eventOptions.onRemove) eventOptions.onRemove(this);
155
156                         removeEvent.apply(this, [type, fn].concat(args));
157                         events.each(function(monitor, i){
158                                 if (!fn || monitor.event == fn) removeEvent.apply(this, [eventType, monitor.monitor].concat(args));
159                                 delete events[i];
160                         }, this);
161
162                         storage.store(type, events);
163                         return this;
164                 }
165
166         };
167
168 };
169
170 (function(){
171
172 var pseudos = {
173
174         once: {
175                 listener: function(split, fn, args, monitor){
176                         fn.apply(this, args);
177                         this.removeEvent(split.event, monitor)
178                                 .removeEvent(split.original, fn);
179                 }
180         },
181
182         throttle: {
183                 listener: function(split, fn, args){
184                         if (!fn._throttled){
185                                 fn.apply(this, args);
186                                 fn._throttled = setTimeout(function(){
187                                         fn._throttled = false;
188                                 }, split.value || 250);
189                         }
190                 }
191         },
192
193         pause: {
194                 listener: function(split, fn, args){
195                         clearTimeout(fn._pause);
196                         fn._pause = fn.delay(split.value || 250, this, args);
197                 }
198         }
199
200 };
201
202 Events.definePseudo = function(key, listener){
203         pseudos[key] = Type.isFunction(listener) ? {listener: listener} : listener;
204         return this;
205 };
206
207 Events.lookupPseudo = function(key){
208         return pseudos[key];
209 };
210
211 var proto = Events.prototype;
212 Events.implement(Events.Pseudos(pseudos, proto.addEvent, proto.removeEvent));
213
214 ['Request', 'Fx'].each(function(klass){
215         if (this[klass]) this[klass].implement(Events.prototype);
216 });
217
218 })();
219
220
221 /*
222 ---
223
224 script: Class.Refactor.js
225
226 name: Class.Refactor
227
228 description: Extends a class onto itself with new property, preserving any items attached to the class's namespace.
229
230 license: MIT-style license
231
232 authors:
233   - Aaron Newton
234
235 requires:
236   - Core/Class
237   - /MooTools.More
238
239 # Some modules declare themselves dependent on Class.Refactor
240 provides: [Class.refactor, Class.Refactor]
241
242 ...
243 */
244
245 Class.refactor = function(original, refactors){
246
247         Object.each(refactors, function(item, name){
248                 var origin = original.prototype[name];
249                 origin = (origin && origin.$origin) || origin || function(){};
250                 original.implement(name, (typeof item == 'function') ? function(){
251                         var old = this.previous;
252                         this.previous = origin;
253                         var value = item.apply(this, arguments);
254                         this.previous = old;
255                         return value;
256                 } : item);
257         });
258
259         return original;
260
261 };
262
263
264 /*
265 ---
266
267 script: Class.Binds.js
268
269 name: Class.Binds
270
271 description: Automagically binds specified methods in a class to the instance of the class.
272
273 license: MIT-style license
274
275 authors:
276   - Aaron Newton
277
278 requires:
279   - Core/Class
280   - /MooTools.More
281
282 provides: [Class.Binds]
283
284 ...
285 */
286
287 Class.Mutators.Binds = function(binds){
288         if (!this.prototype.initialize) this.implement('initialize', function(){});
289         return Array.from(binds).concat(this.prototype.Binds || []);
290 };
291
292 Class.Mutators.initialize = function(initialize){
293         return function(){
294                 Array.from(this.Binds).each(function(name){
295                         var original = this[name];
296                         if (original) this[name] = original.bind(this);
297                 }, this);
298                 return initialize.apply(this, arguments);
299         };
300 };
301
302
303 /*
304 ---
305
306 script: Class.Occlude.js
307
308 name: Class.Occlude
309
310 description: Prevents a class from being applied to a DOM element twice.
311
312 license: MIT-style license.
313
314 authors:
315   - Aaron Newton
316
317 requires:
318   - Core/Class
319   - Core/Element
320   - /MooTools.More
321
322 provides: [Class.Occlude]
323
324 ...
325 */
326
327 Class.Occlude = new Class({
328
329         occlude: function(property, element){
330                 element = document.id(element || this.element);
331                 var instance = element.retrieve(property || this.property);
332                 if (instance && !this.occluded)
333                         return (this.occluded = instance);
334
335                 this.occluded = false;
336                 element.store(property || this.property, this);
337                 return this.occluded;
338         }
339
340 });
341
342
343 /*
344 ---
345
346 script: Chain.Wait.js
347
348 name: Chain.Wait
349
350 description: value, Adds a method to inject pauses between chained events.
351
352 license: MIT-style license.
353
354 authors:
355   - Aaron Newton
356
357 requires:
358   - Core/Chain
359   - Core/Element
360   - Core/Fx
361   - /MooTools.More
362
363 provides: [Chain.Wait]
364
365 ...
366 */
367
368 (function(){
369
370         var wait = {
371                 wait: function(duration){
372                         return this.chain(function(){
373                                 this.callChain.delay(duration == null ? 500 : duration, this);
374                                 return this;
375                         }.bind(this));
376                 }
377         };
378
379         Chain.implement(wait);
380
381         if (this.Fx) Fx.implement(wait);
382
383         if (this.Element && Element.implement && this.Fx){
384                 Element.implement({
385
386                         chains: function(effects){
387                                 Array.from(effects || ['tween', 'morph', 'reveal']).each(function(effect){
388                                         effect = this.get(effect);
389                                         if (!effect) return;
390                                         effect.setOptions({
391                                                 link:'chain'
392                                         });
393                                 }, this);
394                                 return this;
395                         },
396
397                         pauseFx: function(duration, effect){
398                                 this.chains(effect).get(effect || 'tween').wait(duration);
399                                 return this;
400                         }
401
402                 });
403         }
404
405 })();
406
407
408 /*
409 ---
410
411 script: Array.Extras.js
412
413 name: Array.Extras
414
415 description: Extends the Array native object to include useful methods to work with arrays.
416
417 license: MIT-style license
418
419 authors:
420   - Christoph Pojer
421   - Sebastian Markbåge
422
423 requires:
424   - Core/Array
425   - MooTools.More
426
427 provides: [Array.Extras]
428
429 ...
430 */
431
432 (function(nil){
433
434 Array.implement({
435
436         min: function(){
437                 return Math.min.apply(null, this);
438         },
439
440         max: function(){
441                 return Math.max.apply(null, this);
442         },
443
444         average: function(){
445                 return this.length ? this.sum() / this.length : 0;
446         },
447
448         sum: function(){
449                 var result = 0, l = this.length;
450                 if (l){
451                         while (l--) result += this[l];
452                 }
453                 return result;
454         },
455
456         unique: function(){
457                 return [].combine(this);
458         },
459
460         shuffle: function(){
461                 for (var i = this.length; i && --i;){
462                         var temp = this[i], r = Math.floor(Math.random() * ( i + 1 ));
463                         this[i] = this[r];
464                         this[r] = temp;
465                 }
466                 return this;
467         },
468
469         reduce: function(fn, value){
470                 for (var i = 0, l = this.length; i < l; i++){
471                         if (i in this) value = value === nil ? this[i] : fn.call(null, value, this[i], i, this);
472                 }
473                 return value;
474         },
475
476         reduceRight: function(fn, value){
477                 var i = this.length;
478                 while (i--){
479                         if (i in this) value = value === nil ? this[i] : fn.call(null, value, this[i], i, this);
480                 }
481                 return value;
482         }
483
484 });
485
486 })();
487
488
489 /*
490 ---
491
492 script: Object.Extras.js
493
494 name: Object.Extras
495
496 description: Extra Object generics, like getFromPath which allows a path notation to child elements.
497
498 license: MIT-style license
499
500 authors:
501   - Aaron Newton
502
503 requires:
504   - Core/Object
505   - /MooTools.More
506
507 provides: [Object.Extras]
508
509 ...
510 */
511
512 (function(){
513
514 var defined = function(value){
515         return value != null;
516 };
517
518 var hasOwnProperty = Object.prototype.hasOwnProperty;
519
520 Object.extend({
521
522         getFromPath: function(source, parts){
523                 if (typeof parts == 'string') parts = parts.split('.');
524                 for (var i = 0, l = parts.length; i < l; i++){
525                         if (hasOwnProperty.call(source, parts[i])) source = source[parts[i]];
526                         else return null;
527                 }
528                 return source;
529         },
530
531         cleanValues: function(object, method){
532                 method = method || defined;
533                 for (var key in object) if (!method(object[key])){
534                         delete object[key];
535                 }
536                 return object;
537         },
538
539         erase: function(object, key){
540                 if (hasOwnProperty.call(object, key)) delete object[key];
541                 return object;
542         },
543
544         run: function(object){
545                 var args = Array.slice(arguments, 1);
546                 for (var key in object) if (object[key].apply){
547                         object[key].apply(object, args);
548                 }
549                 return object;
550         }
551
552 });
553
554 })();
555
556
557 /*
558 ---
559
560 script: Locale.js
561
562 name: Locale
563
564 description: Provides methods for localization.
565
566 license: MIT-style license
567
568 authors:
569   - Aaron Newton
570   - Arian Stolwijk
571
572 requires:
573   - Core/Events
574   - /Object.Extras
575   - /MooTools.More
576
577 provides: [Locale, Lang]
578
579 ...
580 */
581
582 (function(){
583
584 var current = null,
585         locales = {},
586         inherits = {};
587
588 var getSet = function(set){
589         if (instanceOf(set, Locale.Set)) return set;
590         else return locales[set];
591 };
592
593 var Locale = this.Locale = {
594
595         define: function(locale, set, key, value){
596                 var name;
597                 if (instanceOf(locale, Locale.Set)){
598                         name = locale.name;
599                         if (name) locales[name] = locale;
600                 } else {
601                         name = locale;
602                         if (!locales[name]) locales[name] = new Locale.Set(name);
603                         locale = locales[name];
604                 }
605
606                 if (set) locale.define(set, key, value);
607
608                 /*<1.2compat>*/
609                 if (set == 'cascade') return Locale.inherit(name, key);
610                 /*</1.2compat>*/
611
612                 if (!current) current = locale;
613
614                 return locale;
615         },
616
617         use: function(locale){
618                 locale = getSet(locale);
619
620                 if (locale){
621                         current = locale;
622
623                         this.fireEvent('change', locale);
624
625                         /*<1.2compat>*/
626                         this.fireEvent('langChange', locale.name);
627                         /*</1.2compat>*/
628                 }
629
630                 return this;
631         },
632
633         getCurrent: function(){
634                 return current;
635         },
636
637         get: function(key, args){
638                 return (current) ? current.get(key, args) : '';
639         },
640
641         inherit: function(locale, inherits, set){
642                 locale = getSet(locale);
643
644                 if (locale) locale.inherit(inherits, set);
645                 return this;
646         },
647
648         list: function(){
649                 return Object.keys(locales);
650         }
651
652 };
653
654 Object.append(Locale, new Events);
655
656 Locale.Set = new Class({
657
658         sets: {},
659
660         inherits: {
661                 locales: [],
662                 sets: {}
663         },
664
665         initialize: function(name){
666                 this.name = name || '';
667         },
668
669         define: function(set, key, value){
670                 var defineData = this.sets[set];
671                 if (!defineData) defineData = {};
672
673                 if (key){
674                         if (typeOf(key) == 'object') defineData = Object.merge(defineData, key);
675                         else defineData[key] = value;
676                 }
677                 this.sets[set] = defineData;
678
679                 return this;
680         },
681
682         get: function(key, args, _base){
683                 var value = Object.getFromPath(this.sets, key);
684                 if (value != null){
685                         var type = typeOf(value);
686                         if (type == 'function') value = value.apply(null, Array.from(args));
687                         else if (type == 'object') value = Object.clone(value);
688                         return value;
689                 }
690
691                 // get value of inherited locales
692                 var index = key.indexOf('.'),
693                         set = index < 0 ? key : key.substr(0, index),
694                         names = (this.inherits.sets[set] || []).combine(this.inherits.locales).include('en-US');
695                 if (!_base) _base = [];
696
697                 for (var i = 0, l = names.length; i < l; i++){
698                         if (_base.contains(names[i])) continue;
699                         _base.include(names[i]);
700
701                         var locale = locales[names[i]];
702                         if (!locale) continue;
703
704                         value = locale.get(key, args, _base);
705                         if (value != null) return value;
706                 }
707
708                 return '';
709         },
710
711         inherit: function(names, set){
712                 names = Array.from(names);
713
714                 if (set && !this.inherits.sets[set]) this.inherits.sets[set] = [];
715
716                 var l = names.length;
717                 while (l--) (set ? this.inherits.sets[set] : this.inherits.locales).unshift(names[l]);
718
719                 return this;
720         }
721
722 });
723
724 /*<1.2compat>*/
725 var lang = MooTools.lang = {};
726
727 Object.append(lang, Locale, {
728         setLanguage: Locale.use,
729         getCurrentLanguage: function(){
730                 var current = Locale.getCurrent();
731                 return (current) ? current.name : null;
732         },
733         set: function(){
734                 Locale.define.apply(this, arguments);
735                 return this;
736         },
737         get: function(set, key, args){
738                 if (key) set += '.' + key;
739                 return Locale.get(set, args);
740         }
741 });
742 /*</1.2compat>*/
743
744 })();
745
746
747 /*
748 ---
749
750 name: Locale.en-US.Date
751
752 description: Date messages for US English.
753
754 license: MIT-style license
755
756 authors:
757   - Aaron Newton
758
759 requires:
760   - /Locale
761
762 provides: [Locale.en-US.Date]
763
764 ...
765 */
766
767 Locale.define('en-US', 'Date', {
768
769         months: ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'],
770         months_abbr: ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'],
771         days: ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'],
772         days_abbr: ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'],
773
774         // Culture's date order: MM/DD/YYYY
775         dateOrder: ['month', 'date', 'year'],
776         shortDate: '%m/%d/%Y',
777         shortTime: '%I:%M%p',
778         AM: 'AM',
779         PM: 'PM',
780         firstDayOfWeek: 0,
781
782         // Date.Extras
783         ordinal: function(dayOfMonth){
784                 // 1st, 2nd, 3rd, etc.
785                 return (dayOfMonth > 3 && dayOfMonth < 21) ? 'th' : ['th', 'st', 'nd', 'rd', 'th'][Math.min(dayOfMonth % 10, 4)];
786         },
787
788         lessThanMinuteAgo: 'less than a minute ago',
789         minuteAgo: 'about a minute ago',
790         minutesAgo: '{delta} minutes ago',
791         hourAgo: 'about an hour ago',
792         hoursAgo: 'about {delta} hours ago',
793         dayAgo: '1 day ago',
794         daysAgo: '{delta} days ago',
795         weekAgo: '1 week ago',
796         weeksAgo: '{delta} weeks ago',
797         monthAgo: '1 month ago',
798         monthsAgo: '{delta} months ago',
799         yearAgo: '1 year ago',
800         yearsAgo: '{delta} years ago',
801
802         lessThanMinuteUntil: 'less than a minute from now',
803         minuteUntil: 'about a minute from now',
804         minutesUntil: '{delta} minutes from now',
805         hourUntil: 'about an hour from now',
806         hoursUntil: 'about {delta} hours from now',
807         dayUntil: '1 day from now',
808         daysUntil: '{delta} days from now',
809         weekUntil: '1 week from now',
810         weeksUntil: '{delta} weeks from now',
811         monthUntil: '1 month from now',
812         monthsUntil: '{delta} months from now',
813         yearUntil: '1 year from now',
814         yearsUntil: '{delta} years from now'
815
816 });
817
818
819 /*
820 ---
821
822 script: Date.js
823
824 name: Date
825
826 description: Extends the Date native object to include methods useful in managing dates.
827
828 license: MIT-style license
829
830 authors:
831   - Aaron Newton
832   - Nicholas Barthelemy - https://svn.nbarthelemy.com/date-js/
833   - Harald Kirshner - mail [at] digitarald.de; http://digitarald.de
834   - Scott Kyle - scott [at] appden.com; http://appden.com
835
836 requires:
837   - Core/Array
838   - Core/String
839   - Core/Number
840   - MooTools.More
841   - Locale
842   - Locale.en-US.Date
843
844 provides: [Date]
845
846 ...
847 */
848
849 (function(){
850
851 var Date = this.Date;
852
853 var DateMethods = Date.Methods = {
854         ms: 'Milliseconds',
855         year: 'FullYear',
856         min: 'Minutes',
857         mo: 'Month',
858         sec: 'Seconds',
859         hr: 'Hours'
860 };
861
862 ['Date', 'Day', 'FullYear', 'Hours', 'Milliseconds', 'Minutes', 'Month', 'Seconds', 'Time', 'TimezoneOffset',
863         'Week', 'Timezone', 'GMTOffset', 'DayOfYear', 'LastMonth', 'LastDayOfMonth', 'UTCDate', 'UTCDay', 'UTCFullYear',
864         'AMPM', 'Ordinal', 'UTCHours', 'UTCMilliseconds', 'UTCMinutes', 'UTCMonth', 'UTCSeconds', 'UTCMilliseconds'].each(function(method){
865         Date.Methods[method.toLowerCase()] = method;
866 });
867
868 var pad = function(n, digits, string){
869         if (digits == 1) return n;
870         return n < Math.pow(10, digits - 1) ? (string || '0') + pad(n, digits - 1, string) : n;
871 };
872
873 Date.implement({
874
875         set: function(prop, value){
876                 prop = prop.toLowerCase();
877                 var method = DateMethods[prop] && 'set' + DateMethods[prop];
878                 if (method && this[method]) this[method](value);
879                 return this;
880         }.overloadSetter(),
881
882         get: function(prop){
883                 prop = prop.toLowerCase();
884                 var method = DateMethods[prop] && 'get' + DateMethods[prop];
885                 if (method && this[method]) return this[method]();
886                 return null;
887         }.overloadGetter(),
888
889         clone: function(){
890                 return new Date(this.get('time'));
891         },
892
893         increment: function(interval, times){
894                 interval = interval || 'day';
895                 times = times != null ? times : 1;
896
897                 switch (interval){
898                         case 'year':
899                                 return this.increment('month', times * 12);
900                         case 'month':
901                                 var d = this.get('date');
902                                 this.set('date', 1).set('mo', this.get('mo') + times);
903                                 return this.set('date', d.min(this.get('lastdayofmonth')));
904                         case 'week':
905                                 return this.increment('day', times * 7);
906                         case 'day':
907                                 return this.set('date', this.get('date') + times);
908                 }
909
910                 if (!Date.units[interval]) throw new Error(interval + ' is not a supported interval');
911
912                 return this.set('time', this.get('time') + times * Date.units[interval]());
913         },
914
915         decrement: function(interval, times){
916                 return this.increment(interval, -1 * (times != null ? times : 1));
917         },
918
919         isLeapYear: function(){
920                 return Date.isLeapYear(this.get('year'));
921         },
922
923         clearTime: function(){
924                 return this.set({hr: 0, min: 0, sec: 0, ms: 0});
925         },
926
927         diff: function(date, resolution){
928                 if (typeOf(date) == 'string') date = Date.parse(date);
929
930                 return ((date - this) / Date.units[resolution || 'day'](3, 3)).round(); // non-leap year, 30-day month
931         },
932
933         getLastDayOfMonth: function(){
934                 return Date.daysInMonth(this.get('mo'), this.get('year'));
935         },
936
937         getDayOfYear: function(){
938                 return (Date.UTC(this.get('year'), this.get('mo'), this.get('date') + 1)
939                         - Date.UTC(this.get('year'), 0, 1)) / Date.units.day();
940         },
941
942         setDay: function(day, firstDayOfWeek){
943                 if (firstDayOfWeek == null){
944                         firstDayOfWeek = Date.getMsg('firstDayOfWeek');
945                         if (firstDayOfWeek === '') firstDayOfWeek = 1;
946                 }
947
948                 day = (7 + Date.parseDay(day, true) - firstDayOfWeek) % 7;
949                 var currentDay = (7 + this.get('day') - firstDayOfWeek) % 7;
950
951                 return this.increment('day', day - currentDay);
952         },
953
954         getWeek: function(firstDayOfWeek){
955                 if (firstDayOfWeek == null){
956                         firstDayOfWeek = Date.getMsg('firstDayOfWeek');
957                         if (firstDayOfWeek === '') firstDayOfWeek = 1;
958                 }
959
960                 var date = this,
961                         dayOfWeek = (7 + date.get('day') - firstDayOfWeek) % 7,
962                         dividend = 0,
963                         firstDayOfYear;
964
965                 if (firstDayOfWeek == 1){
966                         // ISO-8601, week belongs to year that has the most days of the week (i.e. has the thursday of the week)
967                         var month = date.get('month'),
968                                 startOfWeek = date.get('date') - dayOfWeek;
969
970                         if (month == 11 && startOfWeek > 28) return 1; // Week 1 of next year
971
972                         if (month == 0 && startOfWeek < -2){
973                                 // Use a date from last year to determine the week
974                                 date = new Date(date).decrement('day', dayOfWeek);
975                                 dayOfWeek = 0;
976                         }
977
978                         firstDayOfYear = new Date(date.get('year'), 0, 1).get('day') || 7;
979                         if (firstDayOfYear > 4) dividend = -7; // First week of the year is not week 1
980                 } else {
981                         // In other cultures the first week of the year is always week 1 and the last week always 53 or 54.
982                         // Days in the same week can have a different weeknumber if the week spreads across two years.
983                         firstDayOfYear = new Date(date.get('year'), 0, 1).get('day');
984                 }
985
986                 dividend += date.get('dayofyear');
987                 dividend += 6 - dayOfWeek; // Add days so we calculate the current date's week as a full week
988                 dividend += (7 + firstDayOfYear - firstDayOfWeek) % 7; // Make up for first week of the year not being a full week
989
990                 return (dividend / 7);
991         },
992
993         getOrdinal: function(day){
994                 return Date.getMsg('ordinal', day || this.get('date'));
995         },
996
997         getTimezone: function(){
998                 return this.toString()
999                         .replace(/^.*? ([A-Z]{3}).[0-9]{4}.*$/, '$1')
1000                         .replace(/^.*?\(([A-Z])[a-z]+ ([A-Z])[a-z]+ ([A-Z])[a-z]+\)$/, '$1$2$3');
1001         },
1002
1003         getGMTOffset: function(){
1004                 var off = this.get('timezoneOffset');
1005                 return ((off > 0) ? '-' : '+') + pad((off.abs() / 60).floor(), 2) + pad(off % 60, 2);
1006         },
1007
1008         setAMPM: function(ampm){
1009                 ampm = ampm.toUpperCase();
1010                 var hr = this.get('hr');
1011                 if (hr > 11 && ampm == 'AM') return this.decrement('hour', 12);
1012                 else if (hr < 12 && ampm == 'PM') return this.increment('hour', 12);
1013                 return this;
1014         },
1015
1016         getAMPM: function(){
1017                 return (this.get('hr') < 12) ? 'AM' : 'PM';
1018         },
1019
1020         parse: function(str){
1021                 this.set('time', Date.parse(str));
1022                 return this;
1023         },
1024
1025         isValid: function(date){
1026                 return !isNaN((date || this).valueOf());
1027         },
1028
1029         format: function(f){
1030                 if (!this.isValid()) return 'invalid date';
1031                 if (!f) f = '%x %X';
1032
1033                 var formatLower = f.toLowerCase();
1034                 if (formatters[formatLower]) return formatters[formatLower](this); // it's a formatter!
1035                 f = formats[formatLower] || f; // replace short-hand with actual format
1036
1037                 var d = this;
1038                 return f.replace(/%([a-z%])/gi,
1039                         function($0, $1){
1040                                 switch ($1){
1041                                         case 'a': return Date.getMsg('days_abbr')[d.get('day')];
1042                                         case 'A': return Date.getMsg('days')[d.get('day')];
1043                                         case 'b': return Date.getMsg('months_abbr')[d.get('month')];
1044                                         case 'B': return Date.getMsg('months')[d.get('month')];
1045                                         case 'c': return d.format('%a %b %d %H:%M:%S %Y');
1046                                         case 'd': return pad(d.get('date'), 2);
1047                                         case 'e': return pad(d.get('date'), 2, ' ');
1048                                         case 'H': return pad(d.get('hr'), 2);
1049                                         case 'I': return pad((d.get('hr') % 12) || 12, 2);
1050                                         case 'j': return pad(d.get('dayofyear'), 3);
1051                                         case 'k': return pad(d.get('hr'), 2, ' ');
1052                                         case 'l': return pad((d.get('hr') % 12) || 12, 2, ' ');
1053                                         case 'L': return pad(d.get('ms'), 3);
1054                                         case 'm': return pad((d.get('mo') + 1), 2);
1055                                         case 'M': return pad(d.get('min'), 2);
1056                                         case 'o': return d.get('ordinal');
1057                                         case 'p': return Date.getMsg(d.get('ampm'));
1058                                         case 's': return Math.round(d / 1000);
1059                                         case 'S': return pad(d.get('seconds'), 2);
1060                                         case 'T': return d.format('%H:%M:%S');
1061                                         case 'U': return pad(d.get('week'), 2);
1062                                         case 'w': return d.get('day');
1063                                         case 'x': return d.format(Date.getMsg('shortDate'));
1064                                         case 'X': return d.format(Date.getMsg('shortTime'));
1065                                         case 'y': return d.get('year').toString().substr(2);
1066                                         case 'Y': return d.get('year');
1067                                         case 'z': return d.get('GMTOffset');
1068                                         case 'Z': return d.get('Timezone');
1069                                 }
1070                                 return $1;
1071                         }
1072                 );
1073         },
1074
1075         toISOString: function(){
1076                 return this.format('iso8601');
1077         }
1078
1079 }).alias({
1080         toJSON: 'toISOString',
1081         compare: 'diff',
1082         strftime: 'format'
1083 });
1084
1085 var formats = {
1086         db: '%Y-%m-%d %H:%M:%S',
1087         compact: '%Y%m%dT%H%M%S',
1088         'short': '%d %b %H:%M',
1089         'long': '%B %d, %Y %H:%M'
1090 };
1091
1092 // The day and month abbreviations are standardized, so we cannot use simply %a and %b because they will get localized
1093 var rfcDayAbbr = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'],
1094         rfcMonthAbbr = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];
1095
1096 var formatters = {
1097         rfc822: function(date){
1098                 return rfcDayAbbr[date.get('day')] + date.format(', %d ') + rfcMonthAbbr[date.get('month')] + date.format(' %Y %H:%M:%S %Z');
1099         },
1100         rfc2822: function(date){
1101                 return rfcDayAbbr[date.get('day')] + date.format(', %d ') + rfcMonthAbbr[date.get('month')] + date.format(' %Y %H:%M:%S %z');
1102         },
1103         iso8601: function(date){
1104                 return (
1105                         date.getUTCFullYear() + '-' +
1106                         pad(date.getUTCMonth() + 1, 2) + '-' +
1107                         pad(date.getUTCDate(), 2) + 'T' +
1108                         pad(date.getUTCHours(), 2) + ':' +
1109                         pad(date.getUTCMinutes(), 2) + ':' +
1110                         pad(date.getUTCSeconds(), 2) + '.' +
1111                         pad(date.getUTCMilliseconds(), 3) + 'Z'
1112                 );
1113         }
1114 };
1115
1116
1117 var parsePatterns = [],
1118         nativeParse = Date.parse;
1119
1120 var parseWord = function(type, word, num){
1121         var ret = -1,
1122                 translated = Date.getMsg(type + 's');
1123         switch (typeOf(word)){
1124                 case 'object':
1125                         ret = translated[word.get(type)];
1126                         break;
1127                 case 'number':
1128                         ret = translated[word];
1129                         if (!ret) throw new Error('Invalid ' + type + ' index: ' + word);
1130                         break;
1131                 case 'string':
1132                         var match = translated.filter(function(name){
1133                                 return this.test(name);
1134                         }, new RegExp('^' + word, 'i'));
1135                         if (!match.length) throw new Error('Invalid ' + type + ' string');
1136                         if (match.length > 1) throw new Error('Ambiguous ' + type);
1137                         ret = match[0];
1138         }
1139
1140         return (num) ? translated.indexOf(ret) : ret;
1141 };
1142
1143 var startCentury = 1900,
1144         startYear = 70;
1145
1146 Date.extend({
1147
1148         getMsg: function(key, args){
1149                 return Locale.get('Date.' + key, args);
1150         },
1151
1152         units: {
1153                 ms: Function.from(1),
1154                 second: Function.from(1000),
1155                 minute: Function.from(60000),
1156                 hour: Function.from(3600000),
1157                 day: Function.from(86400000),
1158                 week: Function.from(608400000),
1159                 month: function(month, year){
1160                         var d = new Date;
1161                         return Date.daysInMonth(month != null ? month : d.get('mo'), year != null ? year : d.get('year')) * 86400000;
1162                 },
1163                 year: function(year){
1164                         year = year || new Date().get('year');
1165                         return Date.isLeapYear(year) ? 31622400000 : 31536000000;
1166                 }
1167         },
1168
1169         daysInMonth: function(month, year){
1170                 return [31, Date.isLeapYear(year) ? 29 : 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31][month];
1171         },
1172
1173         isLeapYear: function(year){
1174                 return ((year % 4 === 0) && (year % 100 !== 0)) || (year % 400 === 0);
1175         },
1176
1177         parse: function(from,format){
1178                 var t = typeOf(from);
1179                 if (t == 'number') return new Date(from);
1180                 if (t != 'string') return from;
1181                 from = from.clean();
1182                 if (!from.length) return null;
1183
1184                 var parsed;
1185                 ((format) ? [build(format)] : parsePatterns).some(function(pattern){
1186                         var bits = pattern.re.exec(from);
1187                         return (bits) ? (parsed = pattern.handler(bits)) : false;
1188                 });
1189
1190                 if (!(parsed && parsed.isValid())){
1191                         parsed = new Date(nativeParse(from));
1192                         if (!(parsed && parsed.isValid())) parsed = new Date(from.toInt());
1193                 }
1194                 return parsed;
1195         },
1196
1197         parseDay: function(day, num){
1198                 return parseWord('day', day, num);
1199         },
1200
1201         parseMonth: function(month, num){
1202                 return parseWord('month', month, num);
1203         },
1204
1205         parseUTC: function(value){
1206                 var localDate = new Date(value);
1207                 var utcSeconds = Date.UTC(
1208                         localDate.get('year'),
1209                         localDate.get('mo'),
1210                         localDate.get('date'),
1211                         localDate.get('hr'),
1212                         localDate.get('min'),
1213                         localDate.get('sec'),
1214                         localDate.get('ms')
1215                 );
1216                 return new Date(utcSeconds);
1217         },
1218
1219         orderIndex: function(unit){
1220                 return Date.getMsg('dateOrder').indexOf(unit) + 1;
1221         },
1222
1223         defineFormat: function(name, format){
1224                 formats[name] = format;
1225                 return this;
1226         },
1227
1228         defineFormats: function(formats){
1229                 for (var name in formats) Date.defineFormat(name, formats[name]);
1230                 return this;
1231         },
1232
1233         //<1.2compat>
1234         parsePatterns: parsePatterns,
1235         //</1.2compat>
1236
1237         defineParser: function(pattern){
1238                 parsePatterns.push((pattern.re && pattern.handler) ? pattern : build(pattern));
1239                 return this;
1240         },
1241
1242         defineParsers: function(){
1243                 Array.flatten(arguments).each(Date.defineParser);
1244                 return this;
1245         },
1246
1247         define2DigitYearStart: function(year){
1248                 startYear = year % 100;
1249                 startCentury = year - startYear;
1250                 return this;
1251         }
1252
1253 });
1254
1255 var regexOf = function(type){
1256         return new RegExp('(?:' + Date.getMsg(type).map(function(name){
1257                 return name.substr(0, 3);
1258         }).join('|') + ')[a-z]*');
1259 };
1260
1261 var replacers = function(key){
1262         switch (key){
1263                 case 'T':
1264                         return '%H:%M:%S';
1265                 case 'x': // iso8601 covers yyyy-mm-dd, so just check if month is first
1266                         return ((Date.orderIndex('month') == 1) ? '%m[-./]%d' : '%d[-./]%m') + '([-./]%y)?';
1267                 case 'X':
1268                         return '%H([.:]%M)?([.:]%S([.:]%s)?)? ?%p? ?%z?';
1269         }
1270         return null;
1271 };
1272
1273 var keys = {
1274         d: /[0-2]?[0-9]|3[01]/,
1275         H: /[01]?[0-9]|2[0-3]/,
1276         I: /0?[1-9]|1[0-2]/,
1277         M: /[0-5]?\d/,
1278         s: /\d+/,
1279         o: /[a-z]*/,
1280         p: /[ap]\.?m\.?/,
1281         y: /\d{2}|\d{4}/,
1282         Y: /\d{4}/,
1283         z: /Z|[+-]\d{2}(?::?\d{2})?/
1284 };
1285
1286 keys.m = keys.I;
1287 keys.S = keys.M;
1288
1289 var currentLanguage;
1290
1291 var recompile = function(language){
1292         currentLanguage = language;
1293
1294         keys.a = keys.A = regexOf('days');
1295         keys.b = keys.B = regexOf('months');
1296
1297         parsePatterns.each(function(pattern, i){
1298                 if (pattern.format) parsePatterns[i] = build(pattern.format);
1299         });
1300 };
1301
1302 var build = function(format){
1303         if (!currentLanguage) return {format: format};
1304
1305         var parsed = [];
1306         var re = (format.source || format) // allow format to be regex
1307          .replace(/%([a-z])/gi,
1308                 function($0, $1){
1309                         return replacers($1) || $0;
1310                 }
1311         ).replace(/\((?!\?)/g, '(?:') // make all groups non-capturing
1312          .replace(/ (?!\?|\*)/g, ',? ') // be forgiving with spaces and commas
1313          .replace(/%([a-z%])/gi,
1314                 function($0, $1){
1315                         var p = keys[$1];
1316                         if (!p) return $1;
1317                         parsed.push($1);
1318                         return '(' + p.source + ')';
1319                 }
1320         ).replace(/\[a-z\]/gi, '[a-z\\u00c0-\\uffff;\&]'); // handle unicode words
1321
1322         return {
1323                 format: format,
1324                 re: new RegExp('^' + re + '$', 'i'),
1325                 handler: function(bits){
1326                         bits = bits.slice(1).associate(parsed);
1327                         var date = new Date().clearTime(),
1328                                 year = bits.y || bits.Y;
1329
1330                         if (year != null) handle.call(date, 'y', year); // need to start in the right year
1331                         if ('d' in bits) handle.call(date, 'd', 1);
1332                         if ('m' in bits || bits.b || bits.B) handle.call(date, 'm', 1);
1333
1334                         for (var key in bits) handle.call(date, key, bits[key]);
1335                         return date;
1336                 }
1337         };
1338 };
1339
1340 var handle = function(key, value){
1341         if (!value) return this;
1342
1343         switch (key){
1344                 case 'a': case 'A': return this.set('day', Date.parseDay(value, true));
1345                 case 'b': case 'B': return this.set('mo', Date.parseMonth(value, true));
1346                 case 'd': return this.set('date', value);
1347                 case 'H': case 'I': return this.set('hr', value);
1348                 case 'm': return this.set('mo', value - 1);
1349                 case 'M': return this.set('min', value);
1350                 case 'p': return this.set('ampm', value.replace(/\./g, ''));
1351                 case 'S': return this.set('sec', value);
1352                 case 's': return this.set('ms', ('0.' + value) * 1000);
1353                 case 'w': return this.set('day', value);
1354                 case 'Y': return this.set('year', value);
1355                 case 'y':
1356                         value = +value;
1357                         if (value < 100) value += startCentury + (value < startYear ? 100 : 0);
1358                         return this.set('year', value);
1359                 case 'z':
1360                         if (value == 'Z') value = '+00';
1361                         var offset = value.match(/([+-])(\d{2}):?(\d{2})?/);
1362                         offset = (offset[1] + '1') * (offset[2] * 60 + (+offset[3] || 0)) + this.getTimezoneOffset();
1363                         return this.set('time', this - offset * 60000);
1364         }
1365
1366         return this;
1367 };
1368
1369 Date.defineParsers(
1370         '%Y([-./]%m([-./]%d((T| )%X)?)?)?', // "1999-12-31", "1999-12-31 11:59pm", "1999-12-31 23:59:59", ISO8601
1371         '%Y%m%d(T%H(%M%S?)?)?', // "19991231", "19991231T1159", compact
1372         '%x( %X)?', // "12/31", "12.31.99", "12-31-1999", "12/31/2008 11:59 PM"
1373         '%d%o( %b( %Y)?)?( %X)?', // "31st", "31st December", "31 Dec 1999", "31 Dec 1999 11:59pm"
1374         '%b( %d%o)?( %Y)?( %X)?', // Same as above with month and day switched
1375         '%Y %b( %d%o( %X)?)?', // Same as above with year coming first
1376         '%o %b %d %X %z %Y', // "Thu Oct 22 08:11:23 +0000 2009"
1377         '%T', // %H:%M:%S
1378         '%H:%M( ?%p)?' // "11:05pm", "11:05 am" and "11:05"
1379 );
1380
1381 Locale.addEvent('change', function(language){
1382         if (Locale.get('Date')) recompile(language);
1383 }).fireEvent('change', Locale.getCurrent());
1384
1385 })();
1386
1387
1388 /*
1389 ---
1390
1391 script: Date.Extras.js
1392
1393 name: Date.Extras
1394
1395 description: Extends the Date native object to include extra methods (on top of those in Date.js).
1396
1397 license: MIT-style license
1398
1399 authors:
1400   - Aaron Newton
1401   - Scott Kyle
1402
1403 requires:
1404   - /Date
1405
1406 provides: [Date.Extras]
1407
1408 ...
1409 */
1410
1411 Date.implement({
1412
1413         timeDiffInWords: function(to){
1414                 return Date.distanceOfTimeInWords(this, to || new Date);
1415         },
1416
1417         timeDiff: function(to, separator){
1418                 if (to == null) to = new Date;
1419                 var delta = ((to - this) / 1000).floor().abs();
1420
1421                 var vals = [],
1422                         durations = [60, 60, 24, 365, 0],
1423                         names = ['s', 'm', 'h', 'd', 'y'],
1424                         value, duration;
1425
1426                 for (var item = 0; item < durations.length; item++){
1427                         if (item && !delta) break;
1428                         value = delta;
1429                         if ((duration = durations[item])){
1430                                 value = (delta % duration);
1431                                 delta = (delta / duration).floor();
1432                         }
1433                         vals.unshift(value + (names[item] || ''));
1434                 }
1435
1436                 return vals.join(separator || ':');
1437         }
1438
1439 }).extend({
1440
1441         distanceOfTimeInWords: function(from, to){
1442                 return Date.getTimePhrase(((to - from) / 1000).toInt());
1443         },
1444
1445         getTimePhrase: function(delta){
1446                 var suffix = (delta < 0) ? 'Until' : 'Ago';
1447                 if (delta < 0) delta *= -1;
1448
1449                 var units = {
1450                         minute: 60,
1451                         hour: 60,
1452                         day: 24,
1453                         week: 7,
1454                         month: 52 / 12,
1455                         year: 12,
1456                         eon: Infinity
1457                 };
1458
1459                 var msg = 'lessThanMinute';
1460
1461                 for (var unit in units){
1462                         var interval = units[unit];
1463                         if (delta < 1.5 * interval){
1464                                 if (delta > 0.75 * interval) msg = unit;
1465                                 break;
1466                         }
1467                         delta /= interval;
1468                         msg = unit + 's';
1469                 }
1470
1471                 delta = delta.round();
1472                 return Date.getMsg(msg + suffix, delta).substitute({delta: delta});
1473         }
1474
1475 }).defineParsers(
1476
1477         {
1478                 // "today", "tomorrow", "yesterday"
1479                 re: /^(?:tod|tom|yes)/i,
1480                 handler: function(bits){
1481                         var d = new Date().clearTime();
1482                         switch (bits[0]){
1483                                 case 'tom': return d.increment();
1484                                 case 'yes': return d.decrement();
1485                                 default: return d;
1486                         }
1487                 }
1488         },
1489
1490         {
1491                 // "next Wednesday", "last Thursday"
1492                 re: /^(next|last) ([a-z]+)$/i,
1493                 handler: function(bits){
1494                         var d = new Date().clearTime();
1495                         var day = d.getDay();
1496                         var newDay = Date.parseDay(bits[2], true);
1497                         var addDays = newDay - day;
1498                         if (newDay <= day) addDays += 7;
1499                         if (bits[1] == 'last') addDays -= 7;
1500                         return d.set('date', d.getDate() + addDays);
1501                 }
1502         }
1503
1504 ).alias('timeAgoInWords', 'timeDiffInWords');
1505
1506
1507 /*
1508 ---
1509
1510 name: Locale.en-US.Number
1511
1512 description: Number messages for US English.
1513
1514 license: MIT-style license
1515
1516 authors:
1517   - Arian Stolwijk
1518
1519 requires:
1520   - /Locale
1521
1522 provides: [Locale.en-US.Number]
1523
1524 ...
1525 */
1526
1527 Locale.define('en-US', 'Number', {
1528
1529         decimal: '.',
1530         group: ',',
1531
1532 /*      Commented properties are the defaults for Number.format
1533         decimals: 0,
1534         precision: 0,
1535         scientific: null,
1536
1537         prefix: null,
1538         suffic: null,
1539
1540         // Negative/Currency/percentage will mixin Number
1541         negative: {
1542                 prefix: '-'
1543         },*/
1544
1545         currency: {
1546 //              decimals: 2,
1547                 prefix: '$ '
1548         }/*,
1549
1550         percentage: {
1551                 decimals: 2,
1552                 suffix: '%'
1553         }*/
1554
1555 });
1556
1557
1558
1559
1560 /*
1561 ---
1562 name: Number.Format
1563 description: Extends the Number Type object to include a number formatting method.
1564 license: MIT-style license
1565 authors: [Arian Stolwijk]
1566 requires: [Core/Number, Locale.en-US.Number]
1567 # Number.Extras is for compatibility
1568 provides: [Number.Format, Number.Extras]
1569 ...
1570 */
1571
1572
1573 Number.implement({
1574
1575         format: function(options){
1576                 // Thanks dojo and YUI for some inspiration
1577                 var value = this;
1578                 options = options ? Object.clone(options) : {};
1579                 var getOption = function(key){
1580                         if (options[key] != null) return options[key];
1581                         return Locale.get('Number.' + key);
1582                 };
1583
1584                 var negative = value < 0,
1585                         decimal = getOption('decimal'),
1586                         precision = getOption('precision'),
1587                         group = getOption('group'),
1588                         decimals = getOption('decimals');
1589
1590                 if (negative){
1591                         var negativeLocale = getOption('negative') || {};
1592                         if (negativeLocale.prefix == null && negativeLocale.suffix == null) negativeLocale.prefix = '-';
1593                         ['prefix', 'suffix'].each(function(key){
1594                                 if (negativeLocale[key]) options[key] = getOption(key) + negativeLocale[key];
1595                         });
1596
1597                         value = -value;
1598                 }
1599
1600                 var prefix = getOption('prefix'),
1601                         suffix = getOption('suffix');
1602
1603                 if (decimals !== '' && decimals >= 0 && decimals <= 20) value = value.toFixed(decimals);
1604                 if (precision >= 1 && precision <= 21) value = (+value).toPrecision(precision);
1605
1606                 value += '';
1607                 var index;
1608                 if (getOption('scientific') === false && value.indexOf('e') > -1){
1609                         var match = value.split('e'),
1610                                 zeros = +match[1];
1611                         value = match[0].replace('.', '');
1612
1613                         if (zeros < 0){
1614                                 zeros = -zeros - 1;
1615                                 index = match[0].indexOf('.');
1616                                 if (index > -1) zeros -= index - 1;
1617                                 while (zeros--) value = '0' + value;
1618                                 value = '0.' + value;
1619                         } else {
1620                                 index = match[0].lastIndexOf('.');
1621                                 if (index > -1) zeros -= match[0].length - index - 1;
1622                                 while (zeros--) value += '0';
1623                         }
1624                 }
1625
1626                 if (decimal != '.') value = value.replace('.', decimal);
1627
1628                 if (group){
1629                         index = value.lastIndexOf(decimal);
1630                         index = (index > -1) ? index : value.length;
1631                         var newOutput = value.substring(index),
1632                                 i = index;
1633
1634                         while (i--){
1635                                 if ((index - i - 1) % 3 == 0 && i != (index - 1)) newOutput = group + newOutput;
1636                                 newOutput = value.charAt(i) + newOutput;
1637                         }
1638
1639                         value = newOutput;
1640                 }
1641
1642                 if (prefix) value = prefix + value;
1643                 if (suffix) value += suffix;
1644
1645                 return value;
1646         },
1647
1648         formatCurrency: function(){
1649                 var locale = Locale.get('Number.currency') || {};
1650                 if (locale.scientific == null) locale.scientific = false;
1651                 if (locale.decimals == null) locale.decimals = 2;
1652
1653                 return this.format(locale);
1654         },
1655
1656         formatPercentage: function(){
1657                 var locale = Locale.get('Number.percentage') || {};
1658                 if (locale.suffix == null) locale.suffix = '%';
1659                 if (locale.decimals == null) locale.decimals = 2;
1660
1661                 return this.format(locale);
1662         }
1663
1664 });
1665
1666
1667 /*
1668 ---
1669
1670 script: String.Extras.js
1671
1672 name: String.Extras
1673
1674 description: Extends the String native object to include methods useful in managing various kinds of strings (query strings, urls, html, etc).
1675
1676 license: MIT-style license
1677
1678 authors:
1679   - Aaron Newton
1680   - Guillermo Rauch
1681   - Christopher Pitt
1682
1683 requires:
1684   - Core/String
1685   - Core/Array
1686   - MooTools.More
1687
1688 provides: [String.Extras]
1689
1690 ...
1691 */
1692
1693 (function(){
1694
1695 var special = {
1696         'a': /[àáâãäåăą]/g,
1697         'A': /[ÀÁÂÃÄÅĂĄ]/g,
1698         'c': /[ćčç]/g,
1699         'C': /[ĆČÇ]/g,
1700         'd': /[ďđ]/g,
1701         'D': /[ĎÐ]/g,
1702         'e': /[èéêëěę]/g,
1703         'E': /[ÈÉÊËĚĘ]/g,
1704         'g': /[ğ]/g,
1705         'G': /[Ğ]/g,
1706         'i': /[ìíîï]/g,
1707         'I': /[ÌÍÎÏ]/g,
1708         'l': /[ĺľł]/g,
1709         'L': /[ĹĽŁ]/g,
1710         'n': /[ñňń]/g,
1711         'N': /[ÑŇŃ]/g,
1712         'o': /[òóôõöøő]/g,
1713         'O': /[ÒÓÔÕÖØ]/g,
1714         'r': /[řŕ]/g,
1715         'R': /[ŘŔ]/g,
1716         's': /[ššş]/g,
1717         'S': /[ŠŞŚ]/g,
1718         't': /[ťţ]/g,
1719         'T': /[ŤŢ]/g,
1720         'ue': /[ü]/g,
1721         'UE': /[Ü]/g,
1722         'u': /[ùúûůµ]/g,
1723         'U': /[ÙÚÛŮ]/g,
1724         'y': /[ÿý]/g,
1725         'Y': /[ŸÝ]/g,
1726         'z': /[žźż]/g,
1727         'Z': /[ŽŹŻ]/g,
1728         'th': /[þ]/g,
1729         'TH': /[Þ]/g,
1730         'dh': /[ð]/g,
1731         'DH': /[Ð]/g,
1732         'ss': /[ß]/g,
1733         'oe': /[œ]/g,
1734         'OE': /[Œ]/g,
1735         'ae': /[æ]/g,
1736         'AE': /[Æ]/g
1737 },
1738
1739 tidy = {
1740         ' ': /[\xa0\u2002\u2003\u2009]/g,
1741         '*': /[\xb7]/g,
1742         '\'': /[\u2018\u2019]/g,
1743         '"': /[\u201c\u201d]/g,
1744         '...': /[\u2026]/g,
1745         '-': /[\u2013]/g,
1746 //      '--': /[\u2014]/g,
1747         '&raquo;': /[\uFFFD]/g
1748 };
1749
1750 var walk = function(string, replacements){
1751         var result = string, key;
1752         for (key in replacements) result = result.replace(replacements[key], key);
1753         return result;
1754 };
1755
1756 var getRegexForTag = function(tag, contents){
1757         tag = tag || '';
1758         var regstr = contents ? "<" + tag + "(?!\\w)[^>]*>([\\s\\S]*?)<\/" + tag + "(?!\\w)>" : "<\/?" + tag + "([^>]+)?>",
1759                 reg = new RegExp(regstr, "gi");
1760         return reg;
1761 };
1762
1763 String.implement({
1764
1765         standardize: function(){
1766                 return walk(this, special);
1767         },
1768
1769         repeat: function(times){
1770                 return new Array(times + 1).join(this);
1771         },
1772
1773         pad: function(length, str, direction){
1774                 if (this.length >= length) return this;
1775
1776                 var pad = (str == null ? ' ' : '' + str)
1777                         .repeat(length - this.length)
1778                         .substr(0, length - this.length);
1779
1780                 if (!direction || direction == 'right') return this + pad;
1781                 if (direction == 'left') return pad + this;
1782
1783                 return pad.substr(0, (pad.length / 2).floor()) + this + pad.substr(0, (pad.length / 2).ceil());
1784         },
1785
1786         getTags: function(tag, contents){
1787                 return this.match(getRegexForTag(tag, contents)) || [];
1788         },
1789
1790         stripTags: function(tag, contents){
1791                 return this.replace(getRegexForTag(tag, contents), '');
1792         },
1793
1794         tidy: function(){
1795                 return walk(this, tidy);
1796         },
1797
1798         truncate: function(max, trail, atChar){
1799                 var string = this;
1800                 if (trail == null && arguments.length == 1) trail = '…';
1801                 if (string.length > max){
1802                         string = string.substring(0, max);
1803                         if (atChar){
1804                                 var index = string.lastIndexOf(atChar);
1805                                 if (index != -1) string = string.substr(0, index);
1806                         }
1807                         if (trail) string += trail;
1808                 }
1809                 return string;
1810         }
1811
1812 });
1813
1814 })();
1815
1816
1817 /*
1818 ---
1819
1820 script: String.QueryString.js
1821
1822 name: String.QueryString
1823
1824 description: Methods for dealing with URI query strings.
1825
1826 license: MIT-style license
1827
1828 authors:
1829   - Sebastian Markbåge
1830   - Aaron Newton
1831   - Lennart Pilon
1832   - Valerio Proietti
1833
1834 requires:
1835   - Core/Array
1836   - Core/String
1837   - /MooTools.More
1838
1839 provides: [String.QueryString]
1840
1841 ...
1842 */
1843
1844 String.implement({
1845
1846         parseQueryString: function(decodeKeys, decodeValues){
1847                 if (decodeKeys == null) decodeKeys = true;
1848                 if (decodeValues == null) decodeValues = true;
1849
1850                 var vars = this.split(/[&;]/),
1851                         object = {};
1852                 if (!vars.length) return object;
1853
1854                 vars.each(function(val){
1855                         var index = val.indexOf('=') + 1,
1856                                 value = index ? val.substr(index) : '',
1857                                 keys = index ? val.substr(0, index - 1).match(/([^\]\[]+|(\B)(?=\]))/g) : [val],
1858                                 obj = object;
1859                         if (!keys) return;
1860                         if (decodeValues) value = decodeURIComponent(value);
1861                         keys.each(function(key, i){
1862                                 if (decodeKeys) key = decodeURIComponent(key);
1863                                 var current = obj[key];
1864
1865                                 if (i < keys.length - 1) obj = obj[key] = current || {};
1866                                 else if (typeOf(current) == 'array') current.push(value);
1867                                 else obj[key] = current != null ? [current, value] : value;
1868                         });
1869                 });
1870
1871                 return object;
1872         },
1873
1874         cleanQueryString: function(method){
1875                 return this.split('&').filter(function(val){
1876                         var index = val.indexOf('='),
1877                                 key = index < 0 ? '' : val.substr(0, index),
1878                                 value = val.substr(index + 1);
1879
1880                         return method ? method.call(null, key, value) : (value || value === 0);
1881                 }).join('&');
1882         }
1883
1884 });
1885
1886
1887 /*
1888 ---
1889
1890 script: URI.js
1891
1892 name: URI
1893
1894 description: Provides methods useful in managing the window location and uris.
1895
1896 license: MIT-style license
1897
1898 authors:
1899   - Sebastian Markbåge
1900   - Aaron Newton
1901
1902 requires:
1903   - Core/Object
1904   - Core/Class
1905   - Core/Class.Extras
1906   - Core/Element
1907   - /String.QueryString
1908
1909 provides: [URI]
1910
1911 ...
1912 */
1913
1914 (function(){
1915
1916 var toString = function(){
1917         return this.get('value');
1918 };
1919
1920 var URI = this.URI = new Class({
1921
1922         Implements: Options,
1923
1924         options: {
1925                 /*base: false*/
1926         },
1927
1928         regex: /^(?:(\w+):)?(?:\/\/(?:(?:([^:@\/]*):?([^:@\/]*))?@)?([^:\/?#]*)(?::(\d*))?)?(\.\.?$|(?:[^?#\/]*\/)*)([^?#]*)(?:\?([^#]*))?(?:#(.*))?/,
1929         parts: ['scheme', 'user', 'password', 'host', 'port', 'directory', 'file', 'query', 'fragment'],
1930         schemes: {http: 80, https: 443, ftp: 21, rtsp: 554, mms: 1755, file: 0},
1931
1932         initialize: function(uri, options){
1933                 this.setOptions(options);
1934                 var base = this.options.base || URI.base;
1935                 if (!uri) uri = base;
1936
1937                 if (uri && uri.parsed) this.parsed = Object.clone(uri.parsed);
1938                 else this.set('value', uri.href || uri.toString(), base ? new URI(base) : false);
1939         },
1940
1941         parse: function(value, base){
1942                 var bits = value.match(this.regex);
1943                 if (!bits) return false;
1944                 bits.shift();
1945                 return this.merge(bits.associate(this.parts), base);
1946         },
1947
1948         merge: function(bits, base){
1949                 if ((!bits || !bits.scheme) && (!base || !base.scheme)) return false;
1950                 if (base){
1951                         this.parts.every(function(part){
1952                                 if (bits[part]) return false;
1953                                 bits[part] = base[part] || '';
1954                                 return true;
1955                         });
1956                 }
1957                 bits.port = bits.port || this.schemes[bits.scheme.toLowerCase()];
1958                 bits.directory = bits.directory ? this.parseDirectory(bits.directory, base ? base.directory : '') : '/';
1959                 return bits;
1960         },
1961
1962         parseDirectory: function(directory, baseDirectory){
1963                 directory = (directory.substr(0, 1) == '/' ? '' : (baseDirectory || '/')) + directory;
1964                 if (!directory.test(URI.regs.directoryDot)) return directory;
1965                 var result = [];
1966                 directory.replace(URI.regs.endSlash, '').split('/').each(function(dir){
1967                         if (dir == '..' && result.length > 0) result.pop();
1968                         else if (dir != '.') result.push(dir);
1969                 });
1970                 return result.join('/') + '/';
1971         },
1972
1973         combine: function(bits){
1974                 return bits.value || bits.scheme + '://' +
1975                         (bits.user ? bits.user + (bits.password ? ':' + bits.password : '') + '@' : '') +
1976                         (bits.host || '') + (bits.port && bits.port != this.schemes[bits.scheme] ? ':' + bits.port : '') +
1977                         (bits.directory || '/') + (bits.file || '') +
1978                         (bits.query ? '?' + bits.query : '') +
1979                         (bits.fragment ? '#' + bits.fragment : '');
1980         },
1981
1982         set: function(part, value, base){
1983                 if (part == 'value'){
1984                         var scheme = value.match(URI.regs.scheme);
1985                         if (scheme) scheme = scheme[1];
1986                         if (scheme && this.schemes[scheme.toLowerCase()] == null) this.parsed = { scheme: scheme, value: value };
1987                         else this.parsed = this.parse(value, (base || this).parsed) || (scheme ? { scheme: scheme, value: value } : { value: value });
1988                 } else if (part == 'data'){
1989                         this.setData(value);
1990                 } else {
1991                         this.parsed[part] = value;
1992                 }
1993                 return this;
1994         },
1995
1996         get: function(part, base){
1997                 switch (part){
1998                         case 'value': return this.combine(this.parsed, base ? base.parsed : false);
1999                         case 'data' : return this.getData();
2000                 }
2001                 return this.parsed[part] || '';
2002         },
2003
2004         go: function(){
2005                 document.location.href = this.toString();
2006         },
2007
2008         toURI: function(){
2009                 return this;
2010         },
2011
2012         getData: function(key, part){
2013                 var qs = this.get(part || 'query');
2014                 if (!(qs || qs === 0)) return key ? null : {};
2015                 var obj = qs.parseQueryString();
2016                 return key ? obj[key] : obj;
2017         },
2018
2019         setData: function(values, merge, part){
2020                 if (typeof values == 'string'){
2021                         var data = this.getData();
2022                         data[arguments[0]] = arguments[1];
2023                         values = data;
2024                 } else if (merge){
2025                         values = Object.merge(this.getData(), values);
2026                 }
2027                 return this.set(part || 'query', Object.toQueryString(values));
2028         },
2029
2030         clearData: function(part){
2031                 return this.set(part || 'query', '');
2032         },
2033
2034         toString: toString,
2035         valueOf: toString
2036
2037 });
2038
2039 URI.regs = {
2040         endSlash: /\/$/,
2041         scheme: /^(\w+):/,
2042         directoryDot: /\.\/|\.$/
2043 };
2044
2045 URI.base = new URI(Array.from(document.getElements('base[href]', true)).getLast(), {base: document.location});
2046
2047 String.implement({
2048
2049         toURI: function(options){
2050                 return new URI(this, options);
2051         }
2052
2053 });
2054
2055 })();
2056
2057
2058 /*
2059 ---
2060
2061 script: URI.Relative.js
2062
2063 name: URI.Relative
2064
2065 description: Extends the URI class to add methods for computing relative and absolute urls.
2066
2067 license: MIT-style license
2068
2069 authors:
2070   - Sebastian Markbåge
2071
2072
2073 requires:
2074   - /Class.refactor
2075   - /URI
2076
2077 provides: [URI.Relative]
2078
2079 ...
2080 */
2081
2082 URI = Class.refactor(URI, {
2083
2084         combine: function(bits, base){
2085                 if (!base || bits.scheme != base.scheme || bits.host != base.host || bits.port != base.port)
2086                         return this.previous.apply(this, arguments);
2087                 var end = bits.file + (bits.query ? '?' + bits.query : '') + (bits.fragment ? '#' + bits.fragment : '');
2088
2089                 if (!base.directory) return (bits.directory || (bits.file ? '' : './')) + end;
2090
2091                 var baseDir = base.directory.split('/'),
2092                         relDir = bits.directory.split('/'),
2093                         path = '',
2094                         offset;
2095
2096                 var i = 0;
2097                 for (offset = 0; offset < baseDir.length && offset < relDir.length && baseDir[offset] == relDir[offset]; offset++);
2098                 for (i = 0; i < baseDir.length - offset - 1; i++) path += '../';
2099                 for (i = offset; i < relDir.length - 1; i++) path += relDir[i] + '/';
2100
2101                 return (path || (bits.file ? '' : './')) + end;
2102         },
2103
2104         toAbsolute: function(base){
2105                 base = new URI(base);
2106                 if (base) base.set('directory', '').set('file', '');
2107                 return this.toRelative(base);
2108         },
2109
2110         toRelative: function(base){
2111                 return this.get('value', new URI(base));
2112         }
2113
2114 });
2115
2116
2117 /*
2118 ---
2119
2120 name: Hash
2121
2122 description: Contains Hash Prototypes. Provides a means for overcoming the JavaScript practical impossibility of extending native Objects.
2123
2124 license: MIT-style license.
2125
2126 requires:
2127   - Core/Object
2128   - /MooTools.More
2129
2130 provides: [Hash]
2131
2132 ...
2133 */
2134
2135 (function(){
2136
2137 if (this.Hash) return;
2138
2139 var Hash = this.Hash = new Type('Hash', function(object){
2140         if (typeOf(object) == 'hash') object = Object.clone(object.getClean());
2141         for (var key in object) this[key] = object[key];
2142         return this;
2143 });
2144
2145 this.$H = function(object){
2146         return new Hash(object);
2147 };
2148
2149 Hash.implement({
2150
2151         forEach: function(fn, bind){
2152                 Object.forEach(this, fn, bind);
2153         },
2154
2155         getClean: function(){
2156                 var clean = {};
2157                 for (var key in this){
2158                         if (this.hasOwnProperty(key)) clean[key] = this[key];
2159                 }
2160                 return clean;
2161         },
2162
2163         getLength: function(){
2164                 var length = 0;
2165                 for (var key in this){
2166                         if (this.hasOwnProperty(key)) length++;
2167                 }
2168                 return length;
2169         }
2170
2171 });
2172
2173 Hash.alias('each', 'forEach');
2174
2175 Hash.implement({
2176
2177         has: Object.prototype.hasOwnProperty,
2178
2179         keyOf: function(value){
2180                 return Object.keyOf(this, value);
2181         },
2182
2183         hasValue: function(value){
2184                 return Object.contains(this, value);
2185         },
2186
2187         extend: function(properties){
2188                 Hash.each(properties || {}, function(value, key){
2189                         Hash.set(this, key, value);
2190                 }, this);
2191                 return this;
2192         },
2193
2194         combine: function(properties){
2195                 Hash.each(properties || {}, function(value, key){
2196                         Hash.include(this, key, value);
2197                 }, this);
2198                 return this;
2199         },
2200
2201         erase: function(key){
2202                 if (this.hasOwnProperty(key)) delete this[key];
2203                 return this;
2204         },
2205
2206         get: function(key){
2207                 return (this.hasOwnProperty(key)) ? this[key] : null;
2208         },
2209
2210         set: function(key, value){
2211                 if (!this[key] || this.hasOwnProperty(key)) this[key] = value;
2212                 return this;
2213         },
2214
2215         empty: function(){
2216                 Hash.each(this, function(value, key){
2217                         delete this[key];
2218                 }, this);
2219                 return this;
2220         },
2221
2222         include: function(key, value){
2223                 if (this[key] == undefined) this[key] = value;
2224                 return this;
2225         },
2226
2227         map: function(fn, bind){
2228                 return new Hash(Object.map(this, fn, bind));
2229         },
2230
2231         filter: function(fn, bind){
2232                 return new Hash(Object.filter(this, fn, bind));
2233         },
2234
2235         every: function(fn, bind){
2236                 return Object.every(this, fn, bind);
2237         },
2238
2239         some: function(fn, bind){
2240                 return Object.some(this, fn, bind);
2241         },
2242
2243         getKeys: function(){
2244                 return Object.keys(this);
2245         },
2246
2247         getValues: function(){
2248                 return Object.values(this);
2249         },
2250
2251         toQueryString: function(base){
2252                 return Object.toQueryString(this, base);
2253         }
2254
2255 });
2256
2257 Hash.alias({indexOf: 'keyOf', contains: 'hasValue'});
2258
2259
2260 })();
2261
2262
2263
2264 /*
2265 ---
2266
2267 script: Hash.Extras.js
2268
2269 name: Hash.Extras
2270
2271 description: Extends the Hash Type to include getFromPath which allows a path notation to child elements.
2272
2273 license: MIT-style license
2274
2275 authors:
2276   - Aaron Newton
2277
2278 requires:
2279   - /Hash
2280   - /Object.Extras
2281
2282 provides: [Hash.Extras]
2283
2284 ...
2285 */
2286
2287 Hash.implement({
2288
2289         getFromPath: function(notation){
2290                 return Object.getFromPath(this, notation);
2291         },
2292
2293         cleanValues: function(method){
2294                 return new Hash(Object.cleanValues(this, method));
2295         },
2296
2297         run: function(){
2298                 Object.run(arguments);
2299         }
2300
2301 });
2302
2303
2304 /*
2305 ---
2306
2307 script: Element.Forms.js
2308
2309 name: Element.Forms
2310
2311 description: Extends the Element native object to include methods useful in managing inputs.
2312
2313 license: MIT-style license
2314
2315 authors:
2316   - Aaron Newton
2317
2318 requires:
2319   - Core/Element
2320   - /String.Extras
2321   - /MooTools.More
2322
2323 provides: [Element.Forms]
2324
2325 ...
2326 */
2327
2328 Element.implement({
2329
2330         tidy: function(){
2331                 this.set('value', this.get('value').tidy());
2332         },
2333
2334         getTextInRange: function(start, end){
2335                 return this.get('value').substring(start, end);
2336         },
2337
2338         getSelectedText: function(){
2339                 if (this.setSelectionRange) return this.getTextInRange(this.getSelectionStart(), this.getSelectionEnd());
2340                 return document.selection.createRange().text;
2341         },
2342
2343         getSelectedRange: function(){
2344                 if (this.selectionStart != null){
2345                         return {
2346                                 start: this.selectionStart,
2347                                 end: this.selectionEnd
2348                         };
2349                 }
2350
2351                 var pos = {
2352                         start: 0,
2353                         end: 0
2354                 };
2355                 var range = this.getDocument().selection.createRange();
2356                 if (!range || range.parentElement() != this) return pos;
2357                 var duplicate = range.duplicate();
2358
2359                 if (this.type == 'text'){
2360                         pos.start = 0 - duplicate.moveStart('character', -100000);
2361                         pos.end = pos.start + range.text.length;
2362                 } else {
2363                         var value = this.get('value');
2364                         var offset = value.length;
2365                         duplicate.moveToElementText(this);
2366                         duplicate.setEndPoint('StartToEnd', range);
2367                         if (duplicate.text.length) offset -= value.match(/[\n\r]*$/)[0].length;
2368                         pos.end = offset - duplicate.text.length;
2369                         duplicate.setEndPoint('StartToStart', range);
2370                         pos.start = offset - duplicate.text.length;
2371                 }
2372                 return pos;
2373         },
2374
2375         getSelectionStart: function(){
2376                 return this.getSelectedRange().start;
2377         },
2378
2379         getSelectionEnd: function(){
2380                 return this.getSelectedRange().end;
2381         },
2382
2383         setCaretPosition: function(pos){
2384                 if (pos == 'end') pos = this.get('value').length;
2385                 this.selectRange(pos, pos);
2386                 return this;
2387         },
2388
2389         getCaretPosition: function(){
2390                 return this.getSelectedRange().start;
2391         },
2392
2393         selectRange: function(start, end){
2394                 if (this.setSelectionRange){
2395                         this.focus();
2396                         this.setSelectionRange(start, end);
2397                 } else {
2398                         var value = this.get('value');
2399                         var diff = value.substr(start, end - start).replace(/\r/g, '').length;
2400                         start = value.substr(0, start).replace(/\r/g, '').length;
2401                         var range = this.createTextRange();
2402                         range.collapse(true);
2403                         range.moveEnd('character', start + diff);
2404                         range.moveStart('character', start);
2405                         range.select();
2406                 }
2407                 return this;
2408         },
2409
2410         insertAtCursor: function(value, select){
2411                 var pos = this.getSelectedRange();
2412                 var text = this.get('value');
2413                 this.set('value', text.substring(0, pos.start) + value + text.substring(pos.end, text.length));
2414                 if (select !== false) this.selectRange(pos.start, pos.start + value.length);
2415                 else this.setCaretPosition(pos.start + value.length);
2416                 return this;
2417         },
2418
2419         insertAroundCursor: function(options, select){
2420                 options = Object.append({
2421                         before: '',
2422                         defaultMiddle: '',
2423                         after: ''
2424                 }, options);
2425
2426                 var value = this.getSelectedText() || options.defaultMiddle;
2427                 var pos = this.getSelectedRange();
2428                 var text = this.get('value');
2429
2430                 if (pos.start == pos.end){
2431                         this.set('value', text.substring(0, pos.start) + options.before + value + options.after + text.substring(pos.end, text.length));
2432                         this.selectRange(pos.start + options.before.length, pos.end + options.before.length + value.length);
2433                 } else {
2434                         var current = text.substring(pos.start, pos.end);
2435                         this.set('value', text.substring(0, pos.start) + options.before + current + options.after + text.substring(pos.end, text.length));
2436                         var selStart = pos.start + options.before.length;
2437                         if (select !== false) this.selectRange(selStart, selStart + current.length);
2438                         else this.setCaretPosition(selStart + text.length);
2439                 }
2440                 return this;
2441         }
2442
2443 });
2444
2445
2446 /*
2447 ---
2448
2449 script: Elements.From.js
2450
2451 name: Elements.From
2452
2453 description: Returns a collection of elements from a string of html.
2454
2455 license: MIT-style license
2456
2457 authors:
2458   - Aaron Newton
2459
2460 requires:
2461   - Core/String
2462   - Core/Element
2463   - /MooTools.More
2464
2465 provides: [Elements.from, Elements.From]
2466
2467 ...
2468 */
2469
2470 Elements.from = function(text, excludeScripts){
2471         if (excludeScripts || excludeScripts == null) text = text.stripScripts();
2472
2473         var container, match = text.match(/^\s*<(t[dhr]|tbody|tfoot|thead)/i);
2474
2475         if (match){
2476                 container = new Element('table');
2477                 var tag = match[1].toLowerCase();
2478                 if (['td', 'th', 'tr'].contains(tag)){
2479                         container = new Element('tbody').inject(container);
2480                         if (tag != 'tr') container = new Element('tr').inject(container);
2481                 }
2482         }
2483
2484         return (container || new Element('div')).set('html', text).getChildren();
2485 };
2486
2487
2488 /*
2489 ---
2490
2491 name: Element.Event.Pseudos
2492
2493 description: Adds the functionality to add pseudo events for Elements
2494
2495 license: MIT-style license
2496
2497 authors:
2498   - Arian Stolwijk
2499
2500 requires: [Core/Element.Event, Events.Pseudos]
2501
2502 provides: [Element.Event.Pseudos]
2503
2504 ...
2505 */
2506
2507 (function(){
2508
2509 var pseudos = {},
2510         copyFromEvents = ['once', 'throttle', 'pause'],
2511         count = copyFromEvents.length;
2512
2513 while (count--) pseudos[copyFromEvents[count]] = Events.lookupPseudo(copyFromEvents[count]);
2514
2515 Event.definePseudo = function(key, listener){
2516         pseudos[key] = Type.isFunction(listener) ? {listener: listener} : listener;
2517         return this;
2518 };
2519
2520 var proto = Element.prototype;
2521 [Element, Window, Document].invoke('implement', Events.Pseudos(pseudos, proto.addEvent, proto.removeEvent));
2522
2523 })();
2524
2525
2526 /*
2527 ---
2528
2529 name: Element.Event.Pseudos.Keys
2530
2531 description: Adds functionality fire events if certain keycombinations are pressed
2532
2533 license: MIT-style license
2534
2535 authors:
2536   - Arian Stolwijk
2537
2538 requires: [Element.Event.Pseudos]
2539
2540 provides: [Element.Event.Pseudos.Keys]
2541
2542 ...
2543 */
2544
2545 (function(){
2546
2547 var keysStoreKey = '$moo:keys-pressed',
2548         keysKeyupStoreKey = '$moo:keys-keyup';
2549
2550
2551 Event.definePseudo('keys', function(split, fn, args){
2552
2553         var event = args[0],
2554                 keys = [],
2555                 pressed = this.retrieve(keysStoreKey, []);
2556
2557         keys.append(split.value.replace('++', function(){
2558                 keys.push('+'); // shift++ and shift+++a
2559                 return '';
2560         }).split('+'));
2561
2562         pressed.include(event.key);
2563
2564         if (keys.every(function(key){
2565                 return pressed.contains(key);
2566         })) fn.apply(this, args);
2567
2568         this.store(keysStoreKey, pressed);
2569
2570         if (!this.retrieve(keysKeyupStoreKey)){
2571                 var keyup = function(event){
2572                         (function(){
2573                                 pressed = this.retrieve(keysStoreKey, []).erase(event.key);
2574                                 this.store(keysStoreKey, pressed);
2575                         }).delay(0, this); // Fix for IE
2576                 };
2577                 this.store(keysKeyupStoreKey, keyup).addEvent('keyup', keyup);
2578         }
2579
2580 });
2581
2582 Object.append(Event.Keys, {
2583         'shift': 16,
2584         'control': 17,
2585         'alt': 18,
2586         'capslock': 20,
2587         'pageup': 33,
2588         'pagedown': 34,
2589         'end': 35,
2590         'home': 36,
2591         'numlock': 144,
2592         'scrolllock': 145,
2593         ';': 186,
2594         '=': 187,
2595         ',': 188,
2596         '-': Browser.firefox ? 109 : 189,
2597         '.': 190,
2598         '/': 191,
2599         '`': 192,
2600         '[': 219,
2601         '\\': 220,
2602         ']': 221,
2603         "'": 222,
2604         '+': 107
2605 });
2606
2607 })();
2608
2609
2610 /*
2611 ---
2612
2613 script: Element.Delegation.js
2614
2615 name: Element.Delegation
2616
2617 description: Extends the Element native object to include the delegate method for more efficient event management.
2618
2619 credits:
2620   - "Event checking based on the work of Daniel Steigerwald. License: MIT-style license. Copyright: Copyright (c) 2008 Daniel Steigerwald, daniel.steigerwald.cz"
2621
2622 license: MIT-style license
2623
2624 authors:
2625   - Aaron Newton
2626   - Daniel Steigerwald
2627
2628 requires: [/MooTools.More, Element.Event.Pseudos]
2629
2630 provides: [Element.Delegation]
2631
2632 ...
2633 */
2634
2635 (function(){
2636
2637 var eventListenerSupport = !(window.attachEvent && !window.addEventListener),
2638         nativeEvents = Element.NativeEvents;
2639
2640 nativeEvents.focusin = 2;
2641 nativeEvents.focusout = 2;
2642
2643 var check = function(split, target, event){
2644         var elementEvent = Element.Events[split.event], condition;
2645         if (elementEvent) condition = elementEvent.condition;
2646         return Slick.match(target, split.value) && (!condition || condition.call(target, event));
2647 };
2648
2649 var bubbleUp = function(split, event, fn){
2650         for (var target = event.target; target && target != this; target = document.id(target.parentNode)){
2651                 if (target && check(split, target, event)) return fn.call(target, event, target);
2652         }
2653 };
2654
2655 var formObserver = function(eventName){
2656
2657         var $delegationKey = '$delegation:';
2658
2659         return {
2660                 base: 'focusin',
2661
2662                 onRemove: function(element){
2663                         element.retrieve($delegationKey + 'forms', []).each(function(el){
2664                                 el.retrieve($delegationKey + 'listeners', []).each(function(listener){
2665                                         el.removeEvent(eventName, listener);
2666                                 });
2667                                 el.eliminate($delegationKey + eventName + 'listeners')
2668                                         .eliminate($delegationKey + eventName + 'originalFn');
2669                         });
2670                 },
2671
2672                 listener: function(split, fn, args, monitor, options){
2673                         var event = args[0],
2674                                 forms = this.retrieve($delegationKey + 'forms', []),
2675                                 target = event.target,
2676                                 form = (target.get('tag') == 'form') ? target : event.target.getParent('form');
2677                                 
2678                         if (!form) return;
2679                                 
2680                         var formEvents = form.retrieve($delegationKey + 'originalFn', []),
2681                                 formListeners = form.retrieve($delegationKey + 'listeners', []),
2682                                 self = this;
2683
2684                         forms.include(form);
2685                         this.store($delegationKey + 'forms', forms);
2686
2687                         if (!formEvents.contains(fn)){
2688                                 var formListener = function(event){
2689                                         bubbleUp.call(self, split, event, fn);
2690                                 };
2691                                 form.addEvent(eventName, formListener);
2692
2693                                 formEvents.push(fn);
2694                                 formListeners.push(formListener);
2695
2696                                 form.store($delegationKey + eventName + 'originalFn', formEvents)
2697                                         .store($delegationKey + eventName + 'listeners', formListeners);
2698                         }
2699                 }
2700         };
2701 };
2702
2703 var inputObserver = function(eventName){
2704         return {
2705                 base: 'focusin',
2706                 listener: function(split, fn, args){
2707                         var events = {blur: function(){
2708                                 this.removeEvents(events);
2709                         }}, self = this;
2710                         events[eventName] = function(event){
2711                                 bubbleUp.call(self, split, event, fn);
2712                         };
2713                         args[0].target.addEvents(events);
2714                 }
2715         };
2716 };
2717
2718 var eventOptions = {
2719         mouseenter: {
2720                 base: 'mouseover'
2721         },
2722         mouseleave: {
2723                 base: 'mouseout'
2724         },
2725         focus: {
2726                 base: 'focus' + (eventListenerSupport ? '' : 'in'),
2727                 args: [true]
2728         },
2729         blur: {
2730                 base: eventListenerSupport ? 'blur' : 'focusout',
2731                 args: [true]
2732         }
2733 };
2734
2735 if (!eventListenerSupport) Object.append(eventOptions, {
2736         submit: formObserver('submit'),
2737         reset: formObserver('reset'),
2738         change: inputObserver('change'),
2739         select: inputObserver('select')
2740 });
2741
2742 Event.definePseudo('relay', {
2743         listener: function(split, fn, args){
2744                 bubbleUp.call(this, split, args[0], fn);
2745         },
2746         options: eventOptions
2747 });
2748
2749 })();
2750
2751
2752 /*
2753 ---
2754
2755 script: Element.Measure.js
2756
2757 name: Element.Measure
2758
2759 description: Extends the Element native object to include methods useful in measuring dimensions.
2760
2761 credits: "Element.measure / .expose methods by Daniel Steigerwald License: MIT-style license. Copyright: Copyright (c) 2008 Daniel Steigerwald, daniel.steigerwald.cz"
2762
2763 license: MIT-style license
2764
2765 authors:
2766   - Aaron Newton
2767
2768 requires:
2769   - Core/Element.Style
2770   - Core/Element.Dimensions
2771   - /MooTools.More
2772
2773 provides: [Element.Measure]
2774
2775 ...
2776 */
2777
2778 (function(){
2779
2780 var getStylesList = function(styles, planes){
2781         var list = [];
2782         Object.each(planes, function(directions){
2783                 Object.each(directions, function(edge){
2784                         styles.each(function(style){
2785                                 list.push(style + '-' + edge + (style == 'border' ? '-width' : ''));
2786                         });
2787                 });
2788         });
2789         return list;
2790 };
2791
2792 var calculateEdgeSize = function(edge, styles){
2793         var total = 0;
2794         Object.each(styles, function(value, style){
2795                 if (style.test(edge)) total = total + value.toInt();
2796         });
2797         return total;
2798 };
2799
2800 var isVisible = function(el){
2801         return !!(!el || el.offsetHeight || el.offsetWidth);
2802 };
2803
2804
2805 Element.implement({
2806
2807         measure: function(fn){
2808                 if (isVisible(this)) return fn.call(this);
2809                 var parent = this.getParent(),
2810                         toMeasure = [];
2811                 while (!isVisible(parent) && parent != document.body){
2812                         toMeasure.push(parent.expose());
2813                         parent = parent.getParent();
2814                 }
2815                 var restore = this.expose(),
2816                         result = fn.call(this);
2817                 restore();
2818                 toMeasure.each(function(restore){
2819                         restore();
2820                 });
2821                 return result;
2822         },
2823
2824         expose: function(){
2825                 if (this.getStyle('display') != 'none') return function(){};
2826                 var before = this.style.cssText;
2827                 this.setStyles({
2828                         display: 'block',
2829                         position: 'absolute',
2830                         visibility: 'hidden'
2831                 });
2832                 return function(){
2833                         this.style.cssText = before;
2834                 }.bind(this);
2835         },
2836
2837         getDimensions: function(options){
2838                 options = Object.merge({computeSize: false}, options);
2839                 var dim = {x: 0, y: 0};
2840
2841                 var getSize = function(el, options){
2842                         return (options.computeSize) ? el.getComputedSize(options) : el.getSize();
2843                 };
2844
2845                 var parent = this.getParent('body');
2846
2847                 if (parent && this.getStyle('display') == 'none'){
2848                         dim = this.measure(function(){
2849                                 return getSize(this, options);
2850                         });
2851                 } else if (parent){
2852                         try { //safari sometimes crashes here, so catch it
2853                                 dim = getSize(this, options);
2854                         }catch(e){}
2855                 }
2856
2857                 return Object.append(dim, (dim.x || dim.x === 0) ? {
2858                                 width: dim.x,
2859                                 height: dim.y
2860                         } : {
2861                                 x: dim.width,
2862                                 y: dim.height
2863                         }
2864                 );
2865         },
2866
2867         getComputedSize: function(options){
2868                 //<1.2compat>
2869                 //legacy support for my stupid spelling error
2870                 if (options && options.plains) options.planes = options.plains;
2871                 //</1.2compat>
2872
2873                 options = Object.merge({
2874                         styles: ['padding','border'],
2875                         planes: {
2876                                 height: ['top','bottom'],
2877                                 width: ['left','right']
2878                         },
2879                         mode: 'both'
2880                 }, options);
2881
2882                 var styles = {},
2883                         size = {width: 0, height: 0},
2884                         dimensions;
2885
2886                 if (options.mode == 'vertical'){
2887                         delete size.width;
2888                         delete options.planes.width;
2889                 } else if (options.mode == 'horizontal'){
2890                         delete size.height;
2891                         delete options.planes.height;
2892                 }
2893
2894                 getStylesList(options.styles, options.planes).each(function(style){
2895                         styles[style] = this.getStyle(style).toInt();
2896                 }, this);
2897
2898                 Object.each(options.planes, function(edges, plane){
2899
2900                         var capitalized = plane.capitalize(),
2901                                 style = this.getStyle(plane);
2902
2903                         if (style == 'auto' && !dimensions) dimensions = this.getDimensions();
2904
2905                         style = styles[plane] = (style == 'auto') ? dimensions[plane] : style.toInt();
2906                         size['total' + capitalized] = style;
2907
2908                         edges.each(function(edge){
2909                                 var edgesize = calculateEdgeSize(edge, styles);
2910                                 size['computed' + edge.capitalize()] = edgesize;
2911                                 size['total' + capitalized] += edgesize;
2912                         });
2913
2914                 }, this);
2915
2916                 return Object.append(size, styles);
2917         }
2918
2919 });
2920
2921 })();
2922
2923
2924 /*
2925 ---
2926
2927 script: Element.Pin.js
2928
2929 name: Element.Pin
2930
2931 description: Extends the Element native object to include the pin method useful for fixed positioning for elements.
2932
2933 license: MIT-style license
2934
2935 authors:
2936   - Aaron Newton
2937
2938 requires:
2939   - Core/Element.Event
2940   - Core/Element.Dimensions
2941   - Core/Element.Style
2942   - /MooTools.More
2943
2944 provides: [Element.Pin]
2945
2946 ...
2947 */
2948
2949 (function(){
2950         var supportsPositionFixed = false,
2951                 supportTested = false;
2952
2953         var testPositionFixed = function(){
2954                 var test = new Element('div').setStyles({
2955                         position: 'fixed',
2956                         top: 0,
2957                         right: 0
2958                 }).inject(document.body);
2959                 supportsPositionFixed = (test.offsetTop === 0);
2960                 test.dispose();
2961                 supportTested = true;
2962         };
2963
2964         Element.implement({
2965
2966                 pin: function(enable, forceScroll){
2967                         if (!supportTested) testPositionFixed();
2968                         if (this.getStyle('display') == 'none') return this;
2969
2970                         var pinnedPosition,
2971                                 scroll = window.getScroll(),
2972                                 parent,
2973                                 scrollFixer;
2974
2975                         if (enable !== false){
2976                                 pinnedPosition = this.getPosition(supportsPositionFixed ? document.body : this.getOffsetParent());
2977                                 if (!this.retrieve('pin:_pinned')){
2978                                         var currentPosition = {
2979                                                 top: pinnedPosition.y - scroll.y,
2980                                                 left: pinnedPosition.x - scroll.x
2981                                         };
2982
2983                                         if (supportsPositionFixed && !forceScroll){
2984                                                 this.setStyle('position', 'fixed').setStyles(currentPosition);
2985                                         } else {
2986
2987                                                 parent = this.getOffsetParent();
2988                                                 var position = this.getPosition(parent),
2989                                                         styles = this.getStyles('left', 'top');
2990
2991                                                 if (parent && styles.left == 'auto' || styles.top == 'auto') this.setPosition(position);
2992                                                 if (this.getStyle('position') == 'static') this.setStyle('position', 'absolute');
2993
2994                                                 position = {
2995                                                         x: styles.left.toInt() - scroll.x,
2996                                                         y: styles.top.toInt() - scroll.y
2997                                                 };
2998
2999                                                 scrollFixer = function(){
3000                                                         if (!this.retrieve('pin:_pinned')) return;
3001                                                         var scroll = window.getScroll();
3002                                                         this.setStyles({
3003                                                                 left: position.x + scroll.x,
3004                                                                 top: position.y + scroll.y
3005                                                         });
3006                                                 }.bind(this);
3007
3008                                                 this.store('pin:_scrollFixer', scrollFixer);
3009                                                 window.addEvent('scroll', scrollFixer);
3010                                         }
3011                                         this.store('pin:_pinned', true);
3012                                 }
3013
3014                         } else {
3015                                 if (!this.retrieve('pin:_pinned')) return this;
3016
3017                                 parent = this.getParent();
3018                                 var offsetParent = (parent.getComputedStyle('position') != 'static' ? parent : parent.getOffsetParent());
3019
3020                                 pinnedPosition = this.getPosition(offsetParent);
3021
3022                                 this.store('pin:_pinned', false);
3023                                 scrollFixer = this.retrieve('pin:_scrollFixer');
3024                                 if (!scrollFixer){
3025                                         this.setStyles({
3026                                                 position: 'absolute',
3027                                                 top: pinnedPosition.y + scroll.y,
3028                                                 left: pinnedPosition.x + scroll.x
3029                                         });
3030                                 } else {
3031                                         this.store('pin:_scrollFixer', null);
3032                                         window.removeEvent('scroll', scrollFixer);
3033                                 }
3034                                 this.removeClass('isPinned');
3035                         }
3036                         return this;
3037                 },
3038
3039                 unpin: function(){
3040                         return this.pin(false);
3041                 },
3042
3043                 togglePin: function(){
3044                         return this.pin(!this.retrieve('pin:_pinned'));
3045                 }
3046
3047         });
3048
3049 //<1.2compat>
3050 Element.alias('togglepin', 'togglePin');
3051 //</1.2compat>
3052
3053 })();
3054
3055
3056 /*
3057 ---
3058
3059 script: Element.Position.js
3060
3061 name: Element.Position
3062
3063 description: Extends the Element native object to include methods useful positioning elements relative to others.
3064
3065 license: MIT-style license
3066
3067 authors:
3068   - Aaron Newton
3069   - Jacob Thornton
3070
3071 requires:
3072   - Core/Options
3073   - Core/Element.Dimensions
3074   - Element.Measure
3075
3076 provides: [Element.Position]
3077
3078 ...
3079 */
3080
3081 (function(original){
3082
3083 var local = Element.Position = {
3084
3085         options: {/*
3086                 edge: false,
3087                 returnPos: false,
3088                 minimum: {x: 0, y: 0},
3089                 maximum: {x: 0, y: 0},
3090                 relFixedPosition: false,
3091                 ignoreMargins: false,
3092                 ignoreScroll: false,
3093                 allowNegative: false,*/
3094                 relativeTo: document.body,
3095                 position: {
3096                         x: 'center', //left, center, right
3097                         y: 'center' //top, center, bottom
3098                 },
3099                 offset: {x: 0, y: 0}
3100         },
3101
3102         getOptions: function(element, options){
3103                 options = Object.merge({}, local.options, options);
3104                 local.setPositionOption(options);
3105                 local.setEdgeOption(options);
3106                 local.setOffsetOption(element, options);
3107                 local.setDimensionsOption(element, options);
3108                 return options;
3109         },
3110
3111         setPositionOption: function(options){
3112                 options.position = local.getCoordinateFromValue(options.position);
3113         },
3114
3115         setEdgeOption: function(options){
3116                 var edgeOption = local.getCoordinateFromValue(options.edge);
3117                 options.edge = edgeOption ? edgeOption :
3118                         (options.position.x == 'center' && options.position.y == 'center') ? {x: 'center', y: 'center'} :
3119                         {x: 'left', y: 'top'};
3120         },
3121
3122         setOffsetOption: function(element, options){
3123                 var parentOffset = {x: 0, y: 0},
3124                         offsetParent = element.measure(function(){
3125                                 return document.id(this.getOffsetParent());
3126                         }),
3127                         parentScroll = offsetParent.getScroll();
3128
3129                 if (!offsetParent || offsetParent == element.getDocument().body) return;
3130                 parentOffset = offsetParent.measure(function(){
3131                         var position = this.getPosition();
3132                         if (this.getStyle('position') == 'fixed'){
3133                                 var scroll = window.getScroll();
3134                                 position.x += scroll.x;
3135                                 position.y += scroll.y;
3136                         }
3137                         return position;
3138                 });
3139
3140                 options.offset = {
3141                         parentPositioned: offsetParent != document.id(options.relativeTo),
3142                         x: options.offset.x - parentOffset.x + parentScroll.x,
3143                         y: options.offset.y - parentOffset.y + parentScroll.y
3144                 };
3145         },
3146
3147         setDimensionsOption: function(element, options){
3148                 options.dimensions = element.getDimensions({
3149                         computeSize: true,
3150                         styles: ['padding', 'border', 'margin']
3151                 });
3152         },
3153
3154         getPosition: function(element, options){
3155                 var position = {};
3156                 options = local.getOptions(element, options);
3157                 var relativeTo = document.id(options.relativeTo) || document.body;
3158
3159                 local.setPositionCoordinates(options, position, relativeTo);
3160                 if (options.edge) local.toEdge(position, options);
3161
3162                 var offset = options.offset;
3163                 position.left = ((position.x >= 0 || offset.parentPositioned || options.allowNegative) ? position.x : 0).toInt();
3164                 position.top = ((position.y >= 0 || offset.parentPositioned || options.allowNegative) ? position.y : 0).toInt();
3165
3166                 local.toMinMax(position, options);
3167
3168                 if (options.relFixedPosition || relativeTo.getStyle('position') == 'fixed') local.toRelFixedPosition(relativeTo, position);
3169                 if (options.ignoreScroll) local.toIgnoreScroll(relativeTo, position);
3170                 if (options.ignoreMargins) local.toIgnoreMargins(position, options);
3171
3172                 position.left = Math.ceil(position.left);
3173                 position.top = Math.ceil(position.top);
3174                 delete position.x;
3175                 delete position.y;
3176
3177                 return position;
3178         },
3179
3180         setPositionCoordinates: function(options, position, relativeTo){
3181                 var offsetY = options.offset.y,
3182                         offsetX = options.offset.x,
3183                         calc = (relativeTo == document.body) ? window.getScroll() : relativeTo.getPosition(),
3184                         top = calc.y,
3185                         left = calc.x,
3186                         winSize = window.getSize();
3187
3188                 switch(options.position.x){
3189                         case 'left': position.x = left + offsetX; break;
3190                         case 'right': position.x = left + offsetX + relativeTo.offsetWidth; break;
3191                         default: position.x = left + ((relativeTo == document.body ? winSize.x : relativeTo.offsetWidth) / 2) + offsetX; break;
3192                 }
3193
3194                 switch(options.position.y){
3195                         case 'top': position.y = top + offsetY; break;
3196                         case 'bottom': position.y = top + offsetY + relativeTo.offsetHeight; break;
3197                         default: position.y = top + ((relativeTo == document.body ? winSize.y : relativeTo.offsetHeight) / 2) + offsetY; break;
3198                 }
3199         },
3200
3201         toMinMax: function(position, options){
3202                 var xy = {left: 'x', top: 'y'}, value;
3203                 ['minimum', 'maximum'].each(function(minmax){
3204                         ['left', 'top'].each(function(lr){
3205                                 value = options[minmax] ? options[minmax][xy[lr]] : null;
3206                                 if (value != null && ((minmax == 'minimum') ? position[lr] < value : position[lr] > value)) position[lr] = value;
3207                         });
3208                 });
3209         },
3210
3211         toRelFixedPosition: function(relativeTo, position){
3212                 var winScroll = window.getScroll();
3213                 position.top += winScroll.y;
3214                 position.left += winScroll.x;
3215         },
3216
3217         toIgnoreScroll: function(relativeTo, position){
3218                 var relScroll = relativeTo.getScroll();
3219                 position.top -= relScroll.y;
3220                 position.left -= relScroll.x;
3221         },
3222
3223         toIgnoreMargins: function(position, options){
3224                 position.left += options.edge.x == 'right'
3225                         ? options.dimensions['margin-right']
3226                         : (options.edge.x != 'center'
3227                                 ? -options.dimensions['margin-left']
3228                                 : -options.dimensions['margin-left'] + ((options.dimensions['margin-right'] + options.dimensions['margin-left']) / 2));
3229
3230                 position.top += options.edge.y == 'bottom'
3231                         ? options.dimensions['margin-bottom']
3232                         : (options.edge.y != 'center'
3233                                 ? -options.dimensions['margin-top']
3234                                 : -options.dimensions['margin-top'] + ((options.dimensions['margin-bottom'] + options.dimensions['margin-top']) / 2));
3235         },
3236
3237         toEdge: function(position, options){
3238                 var edgeOffset = {},
3239                         dimensions = options.dimensions,
3240                         edge = options.edge;
3241
3242                 switch(edge.x){
3243                     &nbs