Source: /media/system/js/mootools-more-uncompressed.js - 13373 lines - 348861 bytes - Summary - Text - Print
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33 MooTools.More = {
34 'version': '1.4.0.1',
35 'build': 'a4244edf2aa97ac8a196fc96082dd35af1abab87'
36 };
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58 (function(){
59
60 Events.Pseudos = function(pseudos, addEvent, removeEvent){
61
62 var storeKey = '_monitorEvents:';
63
64 var storageOf = function(object){
65 return {
66 store: object.store ? function(key, value){
67 object.store(storeKey + key, value);
68 } : function(key, value){
69 (object._monitorEvents || (object._monitorEvents = {}))[key] = value;
70 },
71 retrieve: object.retrieve ? function(key, dflt){
72 return object.retrieve(storeKey + key, dflt);
73 } : function(key, dflt){
74 if (!object._monitorEvents) return dflt;
75 return object._monitorEvents[key] || dflt;
76 }
77 };
78 };
79
80 var splitType = function(type){
81 if (type.indexOf(':') == -1 || !pseudos) return null;
82
83 var parsed = Slick.parse(type).expressions[0][0],
84 parsedPseudos = parsed.pseudos,
85 l = parsedPseudos.length,
86 splits = [];
87
88 while (l--){
89 var pseudo = parsedPseudos[l].key,
90 listener = pseudos[pseudo];
91 if (listener != null) splits.push({
92 event: parsed.tag,
93 value: parsedPseudos[l].value,
94 pseudo: pseudo,
95 original: type,
96 listener: listener
97 });
98 }
99 return splits.length ? splits : null;
100 };
101
102 return {
103
104 addEvent: function(type, fn, internal){
105 var split = splitType(type);
106 if (!split) return addEvent.call(this, type, fn, internal);
107
108 var storage = storageOf(this),
109 events = storage.retrieve(type, []),
110 eventType = split[0].event,
111 args = Array.slice(arguments, 2),
112 stack = fn,
113 self = this;
114
115 split.each(function(item){
116 var listener = item.listener,
117 stackFn = stack;
118 if (listener == false) eventType += ':' + item.pseudo + '(' + item.value + ')';
119 else stack = function(){
120 listener.call(self, item, stackFn, arguments, stack);
121 };
122 });
123
124 events.include({type: eventType, event: fn, monitor: stack});
125 storage.store(type, events);
126
127 if (type != eventType) addEvent.apply(this, [type, fn].concat(args));
128 return addEvent.apply(this, [eventType, stack].concat(args));
129 },
130
131 removeEvent: function(type, fn){
132 var split = splitType(type);
133 if (!split) return removeEvent.call(this, type, fn);
134
135 var storage = storageOf(this),
136 events = storage.retrieve(type);
137 if (!events) return this;
138
139 var args = Array.slice(arguments, 2);
140
141 removeEvent.apply(this, [type, fn].concat(args));
142 events.each(function(monitor, i){
143 if (!fn || monitor.event == fn) removeEvent.apply(this, [monitor.type, monitor.monitor].concat(args));
144 delete events[i];
145 }, this);
146
147 storage.store(type, events);
148 return this;
149 }
150
151 };
152
153 };
154
155 var pseudos = {
156
157 once: function(split, fn, args, monitor){
158 fn.apply(this, args);
159 this.removeEvent(split.event, monitor)
160 .removeEvent(split.original, fn);
161 },
162
163 throttle: function(split, fn, args){
164 if (!fn._throttled){
165 fn.apply(this, args);
166 fn._throttled = setTimeout(function(){
167 fn._throttled = false;
168 }, split.value || 250);
169 }
170 },
171
172 pause: function(split, fn, args){
173 clearTimeout(fn._pause);
174 fn._pause = fn.delay(split.value || 250, this, args);
175 }
176
177 };
178
179 Events.definePseudo = function(key, listener){
180 pseudos[key] = listener;
181 return this;
182 };
183
184 Events.lookupPseudo = function(key){
185 return pseudos[key];
186 };
187
188 var proto = Events.prototype;
189 Events.implement(Events.Pseudos(pseudos, proto.addEvent, proto.removeEvent));
190
191 ['Request', 'Fx'].each(function(klass){
192 if (this[klass]) this[klass].implement(Events.prototype);
193 });
194
195 })();
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222 Class.refactor = function(original, refactors){
223
224 Object.each(refactors, function(item, name){
225 var origin = original.prototype[name];
226 origin = (origin && origin.$origin) || origin || function(){};
227 original.implement(name, (typeof item == 'function') ? function(){
228 var old = this.previous;
229 this.previous = origin;
230 var value = item.apply(this, arguments);
231 this.previous = old;
232 return value;
233 } : item);
234 });
235
236 return original;
237
238 };
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264 Class.Mutators.Binds = function(binds){
265 if (!this.prototype.initialize) this.implement('initialize', function(){});
266 return Array.from(binds).concat(this.prototype.Binds || []);
267 };
268
269 Class.Mutators.initialize = function(initialize){
270 return function(){
271 Array.from(this.Binds).each(function(name){
272 var original = this[name];
273 if (original) this[name] = original.bind(this);
274 }, this);
275 return initialize.apply(this, arguments);
276 };
277 };
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304 Class.Occlude = new Class({
305
306 occlude: function(property, element){
307 element = document.id(element || this.element);
308 var instance = element.retrieve(property || this.property);
309 if (instance && !this.occluded)
310 return (this.occluded = instance);
311
312 this.occluded = false;
313 element.store(property || this.property, this);
314 return this.occluded;
315 }
316
317 });
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345 (function(){
346
347 var wait = {
348 wait: function(duration){
349 return this.chain(function(){
350 this.callChain.delay(duration == null ? 500 : duration, this);
351 return this;
352 }.bind(this));
353 }
354 };
355
356 Chain.implement(wait);
357
358 if (this.Fx) Fx.implement(wait);
359
360 if (this.Element && Element.implement && this.Fx){
361 Element.implement({
362
363 chains: function(effects){
364 Array.from(effects || ['tween', 'morph', 'reveal']).each(function(effect){
365 effect = this.get(effect);
366 if (!effect) return;
367 effect.setOptions({
368 link:'chain'
369 });
370 }, this);
371 return this;
372 },
373
374 pauseFx: function(duration, effect){
375 this.chains(effect).get(effect || 'tween').wait(duration);
376 return this;
377 }
378
379 });
380 }
381
382 })();
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409 (function(nil){
410
411 Array.implement({
412
413 min: function(){
414 return Math.min.apply(null, this);
415 },
416
417 max: function(){
418 return Math.max.apply(null, this);
419 },
420
421 average: function(){
422 return this.length ? this.sum() / this.length : 0;
423 },
424
425 sum: function(){
426 var result = 0, l = this.length;
427 if (l){
428 while (l--) result += this[l];
429 }
430 return result;
431 },
432
433 unique: function(){
434 return [].combine(this);
435 },
436
437 shuffle: function(){
438 for (var i = this.length; i && --i;){
439 var temp = this[i], r = Math.floor(Math.random() * ( i + 1 ));
440 this[i] = this[r];
441 this[r] = temp;
442 }
443 return this;
444 },
445
446 reduce: function(fn, value){
447 for (var i = 0, l = this.length; i < l; i++){
448 if (i in this) value = value === nil ? this[i] : fn.call(null, value, this[i], i, this);
449 }
450 return value;
451 },
452
453 reduceRight: function(fn, value){
454 var i = this.length;
455 while (i--){
456 if (i in this) value = value === nil ? this[i] : fn.call(null, value, this[i], i, this);
457 }
458 return value;
459 }
460
461 });
462
463 })();
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489 (function(){
490
491 var defined = function(value){
492 return value != null;
493 };
494
495 var hasOwnProperty = Object.prototype.hasOwnProperty;
496
497 Object.extend({
498
499 getFromPath: function(source, parts){
500 if (typeof parts == 'string') parts = parts.split('.');
501 for (var i = 0, l = parts.length; i < l; i++){
502 if (hasOwnProperty.call(source, parts[i])) source = source[parts[i]];
503 else return null;
504 }
505 return source;
506 },
507
508 cleanValues: function(object, method){
509 method = method || defined;
510 for (var key in object) if (!method(object[key])){
511 delete object[key];
512 }
513 return object;
514 },
515
516 erase: function(object, key){
517 if (hasOwnProperty.call(object, key)) delete object[key];
518 return object;
519 },
520
521 run: function(object){
522 var args = Array.slice(arguments, 1);
523 for (var key in object) if (object[key].apply){
524 object[key].apply(object, args);
525 }
526 return object;
527 }
528
529 });
530
531 })();
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559 (function(){
560
561 var current = null,
562 locales = {},
563 inherits = {};
564
565 var getSet = function(set){
566 if (instanceOf(set, Locale.Set)) return set;
567 else return locales[set];
568 };
569
570 var Locale = this.Locale = {
571
572 define: function(locale, set, key, value){
573 var name;
574 if (instanceOf(locale, Locale.Set)){
575 name = locale.name;
576 if (name) locales[name] = locale;
577 } else {
578 name = locale;
579 if (!locales[name]) locales[name] = new Locale.Set(name);
580 locale = locales[name];
581 }
582
583 if (set) locale.define(set, key, value);
584
585
586
587 if (!current) current = locale;
588
589 return locale;
590 },
591
592 use: function(locale){
593 locale = getSet(locale);
594
595 if (locale){
596 current = locale;
597
598 this.fireEvent('change', locale);
599
600
601 }
602
603 return this;
604 },
605
606 getCurrent: function(){
607 return current;
608 },
609
610 get: function(key, args){
611 return (current) ? current.get(key, args) : '';
612 },
613
614 inherit: function(locale, inherits, set){
615 locale = getSet(locale);
616
617 if (locale) locale.inherit(inherits, set);
618 return this;
619 },
620
621 list: function(){
622 return Object.keys(locales);
623 }
624
625 };
626
627 Object.append(Locale, new Events);
628
629 Locale.Set = new Class({
630
631 sets: {},
632
633 inherits: {
634 locales: [],
635 sets: {}
636 },
637
638 initialize: function(name){
639 this.name = name || '';
640 },
641
642 define: function(set, key, value){
643 var defineData = this.sets[set];
644 if (!defineData) defineData = {};
645
646 if (key){
647 if (typeOf(key) == 'object') defineData = Object.merge(defineData, key);
648 else defineData[key] = value;
649 }
650 this.sets[set] = defineData;
651
652 return this;
653 },
654
655 get: function(key, args, _base){
656 var value = Object.getFromPath(this.sets, key);
657 if (value != null){
658 var type = typeOf(value);
659 if (type == 'function') value = value.apply(null, Array.from(args));
660 else if (type == 'object') value = Object.clone(value);
661 return value;
662 }
663
664
665 var index = key.indexOf('.'),
666 set = index < 0 ? key : key.substr(0, index),
667 names = (this.inherits.sets[set] || []).combine(this.inherits.locales).include('en-US');
668 if (!_base) _base = [];
669
670 for (var i = 0, l = names.length; i < l; i++){
671 if (_base.contains(names[i])) continue;
672 _base.include(names[i]);
673
674 var locale = locales[names[i]];
675 if (!locale) continue;
676
677 value = locale.get(key, args, _base);
678 if (value != null) return value;
679 }
680
681 return '';
682 },
683
684 inherit: function(names, set){
685 names = Array.from(names);
686
687 if (set && !this.inherits.sets[set]) this.inherits.sets[set] = [];
688
689 var l = names.length;
690 while (l--) (set ? this.inherits.sets[set] : this.inherits.locales).unshift(names[l]);
691
692 return this;
693 }
694
695 });
696
697
698
699 })();
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722 Locale.define('en-US', 'Date', {
723
724 months: ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'],
725 months_abbr: ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'],
726 days: ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'],
727 days_abbr: ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'],
728
729
730 dateOrder: ['month', 'date', 'year'],
731 shortDate: '%m/%d/%Y',
732 shortTime: '%I:%M%p',
733 AM: 'AM',
734 PM: 'PM',
735 firstDayOfWeek: 0,
736
737
738 ordinal: function(dayOfMonth){
739
740 return (dayOfMonth > 3 && dayOfMonth < 21) ? 'th' : ['th', 'st', 'nd', 'rd', 'th'][Math.min(dayOfMonth % 10, 4)];
741 },
742
743 lessThanMinuteAgo: 'less than a minute ago',
744 minuteAgo: 'about a minute ago',
745 minutesAgo: '{delta} minutes ago',
746 hourAgo: 'about an hour ago',
747 hoursAgo: 'about {delta} hours ago',
748 dayAgo: '1 day ago',
749 daysAgo: '{delta} days ago',
750 weekAgo: '1 week ago',
751 weeksAgo: '{delta} weeks ago',
752 monthAgo: '1 month ago',
753 monthsAgo: '{delta} months ago',
754 yearAgo: '1 year ago',
755 yearsAgo: '{delta} years ago',
756
757 lessThanMinuteUntil: 'less than a minute from now',
758 minuteUntil: 'about a minute from now',
759 minutesUntil: '{delta} minutes from now',
760 hourUntil: 'about an hour from now',
761 hoursUntil: 'about {delta} hours from now',
762 dayUntil: '1 day from now',
763 daysUntil: '{delta} days from now',
764 weekUntil: '1 week from now',
765 weeksUntil: '{delta} weeks from now',
766 monthUntil: '1 month from now',
767 monthsUntil: '{delta} months from now',
768 yearUntil: '1 year from now',
769 yearsUntil: '{delta} years from now'
770
771 });
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804 (function(){
805
806 var Date = this.Date;
807
808 var DateMethods = Date.Methods = {
809 ms: 'Milliseconds',
810 year: 'FullYear',
811 min: 'Minutes',
812 mo: 'Month',
813 sec: 'Seconds',
814 hr: 'Hours'
815 };
816
817 ['Date', 'Day', 'FullYear', 'Hours', 'Milliseconds', 'Minutes', 'Month', 'Seconds', 'Time', 'TimezoneOffset',
818 'Week', 'Timezone', 'GMTOffset', 'DayOfYear', 'LastMonth', 'LastDayOfMonth', 'UTCDate', 'UTCDay', 'UTCFullYear',
819 'AMPM', 'Ordinal', 'UTCHours', 'UTCMilliseconds', 'UTCMinutes', 'UTCMonth', 'UTCSeconds', 'UTCMilliseconds'].each(function(method){
820 Date.Methods[method.toLowerCase()] = method;
821 });
822
823 var pad = function(n, digits, string){
824 if (digits == 1) return n;
825 return n < Math.pow(10, digits - 1) ? (string || '0') + pad(n, digits - 1, string) : n;
826 };
827
828 Date.implement({
829
830 set: function(prop, value){
831 prop = prop.toLowerCase();
832 var method = DateMethods[prop] && 'set' + DateMethods[prop];
833 if (method && this[method]) this[method](value);
834 return this;
835 }.overloadSetter(),
836
837 get: function(prop){
838 prop = prop.toLowerCase();
839 var method = DateMethods[prop] && 'get' + DateMethods[prop];
840 if (method && this[method]) return this[method]();
841 return null;
842 }.overloadGetter(),
843
844 clone: function(){
845 return new Date(this.get('time'));
846 },
847
848 increment: function(interval, times){
849 interval = interval || 'day';
850 times = times != null ? times : 1;
851
852 switch (interval){
853 case 'year':
854 return this.increment('month', times * 12);
855 case 'month':
856 var d = this.get('date');
857 this.set('date', 1).set('mo', this.get('mo') + times);
858 return this.set('date', d.min(this.get('lastdayofmonth')));
859 case 'week':
860 return this.increment('day', times * 7);
861 case 'day':
862 return this.set('date', this.get('date') + times);
863 }
864
865 if (!Date.units[interval]) throw new Error(interval + ' is not a supported interval');
866
867 return this.set('time', this.get('time') + times * Date.units[interval]());
868 },
869
870 decrement: function(interval, times){
871 return this.increment(interval, -1 * (times != null ? times : 1));
872 },
873
874 isLeapYear: function(){
875 return Date.isLeapYear(this.get('year'));
876 },
877
878 clearTime: function(){
879 return this.set({hr: 0, min: 0, sec: 0, ms: 0});
880 },
881
882 diff: function(date, resolution){
883 if (typeOf(date) == 'string') date = Date.parse(date);
884
885 return ((date - this) / Date.units[resolution || 'day'](3, 3)).round();
886 },
887
888 getLastDayOfMonth: function(){
889 return Date.daysInMonth(this.get('mo'), this.get('year'));
890 },
891
892 getDayOfYear: function(){
893 return (Date.UTC(this.get('year'), this.get('mo'), this.get('date') + 1)
894 - Date.UTC(this.get('year'), 0, 1)) / Date.units.day();
895 },
896
897 setDay: function(day, firstDayOfWeek){
898 if (firstDayOfWeek == null){
899 firstDayOfWeek = Date.getMsg('firstDayOfWeek');
900 if (firstDayOfWeek === '') firstDayOfWeek = 1;
901 }
902
903 day = (7 + Date.parseDay(day, true) - firstDayOfWeek) % 7;
904 var currentDay = (7 + this.get('day') - firstDayOfWeek) % 7;
905
906 return this.increment('day', day - currentDay);
907 },
908
909 getWeek: function(firstDayOfWeek){
910 if (firstDayOfWeek == null){
911 firstDayOfWeek = Date.getMsg('firstDayOfWeek');
912 if (firstDayOfWeek === '') firstDayOfWeek = 1;
913 }
914
915 var date = this,
916 dayOfWeek = (7 + date.get('day') - firstDayOfWeek) % 7,
917 dividend = 0,
918 firstDayOfYear;
919
920 if (firstDayOfWeek == 1){
921
922 var month = date.get('month'),
923 startOfWeek = date.get('date') - dayOfWeek;
924
925 if (month == 11 && startOfWeek > 28) return 1;
926
927 if (month == 0 && startOfWeek < -2){
928
929 date = new Date(date).decrement('day', dayOfWeek);
930 dayOfWeek = 0;
931 }
932
933 firstDayOfYear = new Date(date.get('year'), 0, 1).get('day') || 7;
934 if (firstDayOfYear > 4) dividend = -7;
935 } else {
936
937
938 firstDayOfYear = new Date(date.get('year'), 0, 1).get('day');
939 }
940
941 dividend += date.get('dayofyear');
942 dividend += 6 - dayOfWeek;
943 dividend += (7 + firstDayOfYear - firstDayOfWeek) % 7;
944
945 return (dividend / 7);
946 },
947
948 getOrdinal: function(day){
949 return Date.getMsg('ordinal', day || this.get('date'));
950 },
951
952 getTimezone: function(){
953 return this.toString()
954 .replace(/^.*? ([A-Z]{3}).[0-9]{4}.*$/, '$1')
955 .replace(/^.*?\(([A-Z])[a-z]+ ([A-Z])[a-z]+ ([A-Z])[a-z]+\)$/, '$1$2$3');
956 },
957
958 getGMTOffset: function(){
959 var off = this.get('timezoneOffset');
960 return ((off > 0) ? '-' : '+') + pad((off.abs() / 60).floor(), 2) + pad(off % 60, 2);
961 },
962
963 setAMPM: function(ampm){
964 ampm = ampm.toUpperCase();
965 var hr = this.get('hr');
966 if (hr > 11 && ampm == 'AM') return this.decrement('hour', 12);
967 else if (hr < 12 && ampm == 'PM') return this.increment('hour', 12);
968 return this;
969 },
970
971 getAMPM: function(){
972 return (this.get('hr') < 12) ? 'AM' : 'PM';
973 },
974
975 parse: function(str){
976 this.set('time', Date.parse(str));
977 return this;
978 },
979
980 isValid: function(date){
981 if (!date) date = this;
982 return typeOf(date) == 'date' && !isNaN(date.valueOf());
983 },
984
985 format: function(format){
986 if (!this.isValid()) return 'invalid date';
987
988 if (!format) format = '%x %X';
989 if (typeof format == 'string') format = formats[format.toLowerCase()] || format;
990 if (typeof format == 'function') return format(this);
991
992 var d = this;
993 return format.replace(/%([a-z%])/gi,
994 function($0, $1){
995 switch ($1){
996 case 'a': return Date.getMsg('days_abbr')[d.get('day')];
997 case 'A': return Date.getMsg('days')[d.get('day')];
998 case 'b': return Date.getMsg('months_abbr')[d.get('month')];
999 case 'B': return Date.getMsg('months')[d.get('month')];
1000 case 'c': return d.format('%a %b %d %H:%M:%S %Y');
1001 case 'd': return pad(d.get('date'), 2);
1002 case 'e': return pad(d.get('date'), 2, ' ');
1003 case 'H': return pad(d.get('hr'), 2);
1004 case 'I': return pad((d.get('hr') % 12) || 12, 2);
1005 case 'j': return pad(d.get('dayofyear'), 3);
1006 case 'k': return pad(d.get('hr'), 2, ' ');
1007 case 'l': return pad((d.get('hr') % 12) || 12, 2, ' ');
1008 case 'L': return pad(d.get('ms'), 3);
1009 case 'm': return pad((d.get('mo') + 1), 2);
1010 case 'M': return pad(d.get('min'), 2);
1011 case 'o': return d.get('ordinal');
1012 case 'p': return Date.getMsg(d.get('ampm'));
1013 case 's': return Math.round(d / 1000);
1014 case 'S': return pad(d.get('seconds'), 2);
1015 case 'T': return d.format('%H:%M:%S');
1016 case 'U': return pad(d.get('week'), 2);
1017 case 'w': return d.get('day');
1018 case 'x': return d.format(Date.getMsg('shortDate'));
1019 case 'X': return d.format(Date.getMsg('shortTime'));
1020 case 'y': return d.get('year').toString().substr(2);
1021 case 'Y': return d.get('year');
1022 case 'z': return d.get('GMTOffset');
1023 case 'Z': return d.get('Timezone');
1024 }
1025 return $1;
1026 }
1027 );
1028 },
1029
1030 toISOString: function(){
1031 return this.format('iso8601');
1032 }
1033
1034 }).alias({
1035 toJSON: 'toISOString',
1036 compare: 'diff',
1037 strftime: 'format'
1038 });
1039
1040
1041 var rfcDayAbbr = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'],
1042 rfcMonthAbbr = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];
1043
1044 var formats = {
1045 db: '%Y-%m-%d %H:%M:%S',
1046 compact: '%Y%m%dT%H%M%S',
1047 'short': '%d %b %H:%M',
1048 'long': '%B %d, %Y %H:%M',
1049 rfc822: function(date){
1050 return rfcDayAbbr[date.get('day')] + date.format(', %d ') + rfcMonthAbbr[date.get('month')] + date.format(' %Y %H:%M:%S %Z');
1051 },
1052 rfc2822: function(date){
1053 return rfcDayAbbr[date.get('day')] + date.format(', %d ') + rfcMonthAbbr[date.get('month')] + date.format(' %Y %H:%M:%S %z');
1054 },
1055 iso8601: function(date){
1056 return (
1057 date.getUTCFullYear() + '-' +
1058 pad(date.getUTCMonth() + 1, 2) + '-' +
1059 pad(date.getUTCDate(), 2) + 'T' +
1060 pad(date.getUTCHours(), 2) + ':' +
1061 pad(date.getUTCMinutes(), 2) + ':' +
1062 pad(date.getUTCSeconds(), 2) + '.' +
1063 pad(date.getUTCMilliseconds(), 3) + 'Z'
1064 );
1065 }
1066 };
1067
1068 var parsePatterns = [],
1069 nativeParse = Date.parse;
1070
1071 var parseWord = function(type, word, num){
1072 var ret = -1,
1073 translated = Date.getMsg(type + 's');
1074 switch (typeOf(word)){
1075 case 'object':
1076 ret = translated[word.get(type)];
1077 break;
1078 case 'number':
1079 ret = translated[word];
1080 if (!ret) throw new Error('Invalid ' + type + ' index: ' + word);
1081 break;
1082 case 'string':
1083 var match = translated.filter(function(name){
1084 return this.test(name);
1085 }, new RegExp('^' + word, 'i'));
1086 if (!match.length) throw new Error('Invalid ' + type + ' string');
1087 if (match.length > 1) throw new Error('Ambiguous ' + type);
1088 ret = match[0];
1089 }
1090
1091 return (num) ? translated.indexOf(ret) : ret;
1092 };
1093
1094 var startCentury = 1900,
1095 startYear = 70;
1096
1097 Date.extend({
1098
1099 getMsg: function(key, args){
1100 return Locale.get('Date.' + key, args);
1101 },
1102
1103 units: {
1104 ms: Function.from(1),
1105 second: Function.from(1000),
1106 minute: Function.from(60000),
1107 hour: Function.from(3600000),
1108 day: Function.from(86400000),
1109 week: Function.from(608400000),
1110 month: function(month, year){
1111 var d = new Date;
1112 return Date.daysInMonth(month != null ? month : d.get('mo'), year != null ? year : d.get('year')) * 86400000;
1113 },
1114 year: function(year){
1115 year = year || new Date().get('year');
1116 return Date.isLeapYear(year) ? 31622400000 : 31536000000;
1117 }
1118 },
1119
1120 daysInMonth: function(month, year){
1121 return [31, Date.isLeapYear(year) ? 29 : 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31][month];
1122 },
1123
1124 isLeapYear: function(year){
1125 return ((year % 4 === 0) && (year % 100 !== 0)) || (year % 400 === 0);
1126 },
1127
1128 parse: function(from){
1129 var t = typeOf(from);
1130 if (t == 'number') return new Date(from);
1131 if (t != 'string') return from;
1132 from = from.clean();
1133 if (!from.length) return null;
1134
1135 var parsed;
1136 parsePatterns.some(function(pattern){
1137 var bits = pattern.re.exec(from);
1138 return (bits) ? (parsed = pattern.handler(bits)) : false;
1139 });
1140
1141 if (!(parsed && parsed.isValid())){
1142 parsed = new Date(nativeParse(from));
1143 if (!(parsed && parsed.isValid())) parsed = new Date(from.toInt());
1144 }
1145 return parsed;
1146 },
1147
1148 parseDay: function(day, num){
1149 return parseWord('day', day, num);
1150 },
1151
1152 parseMonth: function(month, num){
1153 return parseWord('month', month, num);
1154 },
1155
1156 parseUTC: function(value){
1157 var localDate = new Date(value);
1158 var utcSeconds = Date.UTC(
1159 localDate.get('year'),
1160 localDate.get('mo'),
1161 localDate.get('date'),
1162 localDate.get('hr'),
1163 localDate.get('min'),
1164 localDate.get('sec'),
1165 localDate.get('ms')
1166 );
1167 return new Date(utcSeconds);
1168 },
1169
1170 orderIndex: function(unit){
1171 return Date.getMsg('dateOrder').indexOf(unit) + 1;
1172 },
1173
1174 defineFormat: function(name, format){
1175 formats[name] = format;
1176 return this;
1177 },
1178
1179
1180
1181 defineParser: function(pattern){
1182 parsePatterns.push((pattern.re && pattern.handler) ? pattern : build(pattern));
1183 return this;
1184 },
1185
1186 defineParsers: function(){
1187 Array.flatten(arguments).each(Date.defineParser);
1188 return this;
1189 },
1190
1191 define2DigitYearStart: function(year){
1192 startYear = year % 100;
1193 startCentury = year - startYear;
1194 return this;
1195 }
1196
1197 }).extend({
1198 defineFormats: Date.defineFormat.overloadSetter()
1199 });
1200
1201 var regexOf = function(type){
1202 return new RegExp('(?:' + Date.getMsg(type).map(function(name){
1203 return name.substr(0, 3);
1204 }).join('|') + ')[a-z]*');
1205 };
1206
1207 var replacers = function(key){
1208 switch (key){
1209 case 'T':
1210 return '%H:%M:%S';
1211 case 'x': // iso8601 covers yyyy-mm-dd, so just check if month is first
1212 return ((Date.orderIndex('month') == 1) ? '%m[-./]%d' : '%d[-./]%m') + '([-./]%y)?';
1213 case 'X':
1214 return '%H([.:]%M)?([.:]%S([.:]%s)?)? ?%p? ?%z?';
1215 }
1216 return null;
1217 };
1218
1219 var keys = {
1220 d: /[0-2]?[0-9]|3[01]/,
1221 H: /[01]?[0-9]|2[0-3]/,
1222 I: /0?[1-9]|1[0-2]/,
1223 M: /[0-5]?\d/,
1224 s: /\d+/,
1225 o: /[a-z]*/,
1226 p: /[ap]\.?m\.?/,
1227 y: /\d{2}|\d{4}/,
1228 Y: /\d{4}/,
1229 z: /Z|[+-]\d{2}(?::?\d{2})?/
1230 };
1231
1232 keys.m = keys.I;
1233 keys.S = keys.M;
1234
1235 var currentLanguage;
1236
1237 var recompile = function(language){
1238 currentLanguage = language;
1239
1240 keys.a = keys.A = regexOf('days');
1241 keys.b = keys.B = regexOf('months');
1242
1243 parsePatterns.each(function(pattern, i){
1244 if (pattern.format) parsePatterns[i] = build(pattern.format);
1245 });
1246 };
1247
1248 var build = function(format){
1249 if (!currentLanguage) return {format: format};
1250
1251 var parsed = [];
1252 var re = (format.source || format) // allow format to be regex
1253 .replace(/%([a-z])/gi,
1254 function($0, $1){
1255 return replacers($1) || $0;
1256 }
1257 ).replace(/\((?!\?)/g, '(?:') // make all groups non-capturing
1258 .replace(/ (?!\?|\*)/g, ',? ') // be forgiving with spaces and commas
1259 .replace(/%([a-z%])/gi,
1260 function($0, $1){
1261 var p = keys[$1];
1262 if (!p) return $1;
1263 parsed.push($1);
1264 return '(' + p.source + ')';
1265 }
1266 ).replace(/\[a-z\]/gi, '[a-z\\u00c0-\\uffff;\&]');
1267
1268 return {
1269 format: format,
1270 re: new RegExp('^' + re + '$', 'i'),
1271 handler: function(bits){
1272 bits = bits.slice(1).associate(parsed);
1273 var date = new Date().clearTime(),
1274 year = bits.y || bits.Y;
1275
1276 if (year != null) handle.call(date, 'y', year);
1277 if ('d' in bits) handle.call(date, 'd', 1);
1278 if ('m' in bits || bits.b || bits.B) handle.call(date, 'm', 1);
1279
1280 for (var key in bits) handle.call(date, key, bits[key]);
1281 return date;
1282 }
1283 };
1284 };
1285
1286 var handle = function(key, value){
1287 if (!value) return this;
1288
1289 switch (key){
1290 case 'a': case 'A': return this.set('day', Date.parseDay(value, true));
1291 case 'b': case 'B': return this.set('mo', Date.parseMonth(value, true));
1292 case 'd': return this.set('date', value);
1293 case 'H': case 'I': return this.set('hr', value);
1294 case 'm': return this.set('mo', value - 1);
1295 case 'M': return this.set('min', value);
1296 case 'p': return this.set('ampm', value.replace(/\./g, ''));
1297 case 'S': return this.set('sec', value);
1298 case 's': return this.set('ms', ('0.' + value) * 1000);
1299 case 'w': return this.set('day', value);
1300 case 'Y': return this.set('year', value);
1301 case 'y':
1302 value = +value;
1303 if (value < 100) value += startCentury + (value < startYear ? 100 : 0);
1304 return this.set('year', value);
1305 case 'z':
1306 if (value == 'Z') value = '+00';
1307 var offset = value.match(/([+-])(\d{2}):?(\d{2})?/);
1308 offset = (offset[1] + '1') * (offset[2] * 60 + (+offset[3] || 0)) + this.getTimezoneOffset();
1309 return this.set('time', this - offset * 60000);
1310 }
1311
1312 return this;
1313 };
1314
1315 Date.defineParsers(
1316 '%Y([-./]%m([-./]%d((T| )%X)?)?)?', // "1999-12-31", "1999-12-31 11:59pm", "1999-12-31 23:59:59", ISO8601
1317 '%Y%m%d(T%H(%M%S?)?)?', // "19991231", "19991231T1159", compact
1318 '%x( %X)?', // "12/31", "12.31.99", "12-31-1999", "12/31/2008 11:59 PM"
1319 '%d%o( %b( %Y)?)?( %X)?', // "31st", "31st December", "31 Dec 1999", "31 Dec 1999 11:59pm"
1320 '%b( %d%o)?( %Y)?( %X)?', // Same as above with month and day switched
1321 '%Y %b( %d%o( %X)?)?', // Same as above with year coming first
1322 '%o %b %d %X %z %Y', // "Thu Oct 22 08:11:23 +0000 2009"
1323 '%T', // %H:%M:%S
1324 '%H:%M( ?%p)?' // "11:05pm", "11:05 am" and "11:05"
1325 );
1326
1327 Locale.addEvent('change', function(language){
1328 if (Locale.get('Date')) recompile(language);
1329 }).fireEvent('change', Locale.getCurrent());
1330
1331 })();
1332
1333
1334
1335
1336
1337
1338
1339
1340
1341
1342
1343
1344
1345
1346
1347
1348
1349
1350
1351
1352
1353
1354
1355
1356
1357 Date.implement({
1358
1359 timeDiffInWords: function(to){
1360 return Date.distanceOfTimeInWords(this, to || new Date);
1361 },
1362
1363 timeDiff: function(to, separator){
1364 if (to == null) to = new Date;
1365 var delta = ((to - this) / 1000).floor().abs();
1366
1367 var vals = [],
1368 durations = [60, 60, 24, 365, 0],
1369 names = ['s', 'm', 'h', 'd', 'y'],
1370 value, duration;
1371
1372 for (var item = 0; item < durations.length; item++){
1373 if (item && !delta) break;
1374 value = delta;
1375 if ((duration = durations[item])){
1376 value = (delta % duration);
1377 delta = (delta / duration).floor();
1378 }
1379 vals.unshift(value + (names[item] || ''));
1380 }
1381
1382 return vals.join(separator || ':');
1383 }
1384
1385 }).extend({
1386
1387 distanceOfTimeInWords: function(from, to){
1388 return Date.getTimePhrase(((to - from) / 1000).toInt());
1389 },
1390
1391 getTimePhrase: function(delta){
1392 var suffix = (delta < 0) ? 'Until' : 'Ago';
1393 if (delta < 0) delta *= -1;
1394
1395 var units = {
1396 minute: 60,
1397 hour: 60,
1398 day: 24,
1399 week: 7,
1400 month: 52 / 12,
1401 year: 12,
1402 eon: Infinity
1403 };
1404
1405 var msg = 'lessThanMinute';
1406
1407 for (var unit in units){
1408 var interval = units[unit];
1409 if (delta < 1.5 * interval){
1410 if (delta > 0.75 * interval) msg = unit;
1411 break;
1412 }
1413 delta /= interval;
1414 msg = unit + 's';
1415 }
1416
1417 delta = delta.round();
1418 return Date.getMsg(msg + suffix, delta).substitute({delta: delta});
1419 }
1420
1421 }).defineParsers(
1422
1423 {
1424
1425 re: /^(?:tod|tom|yes)/i,
1426 handler: function(bits){
1427 var d = new Date().clearTime();
1428 switch (bits[0]){
1429 case 'tom': return d.increment();
1430 case 'yes': return d.decrement();
1431 default: return d;
1432 }
1433 }
1434 },
1435
1436 {
1437
1438 re: /^(next|last) ([a-z]+)$/i,
1439 handler: function(bits){
1440 var d = new Date().clearTime();
1441 var day = d.getDay();
1442 var newDay = Date.parseDay(bits[2], true);
1443 var addDays = newDay - day;
1444 if (newDay <= day) addDays += 7;
1445 if (bits[1] == 'last') addDays -= 7;
1446 return d.set('date', d.getDate() + addDays);
1447 }
1448 }
1449
1450 ).alias('timeAgoInWords', 'timeDiffInWords');
1451
1452
1453
1454
1455
1456
1457
1458
1459
1460
1461
1462
1463
1464
1465
1466
1467
1468
1469
1470
1471
1472
1473 Locale.define('en-US', 'Number', {
1474
1475 decimal: '.',
1476 group: ',',
1477
1478
1479
1480
1481
1482
1483
1484
1485
1486
1487
1488
1489
1490
1491 currency: {
1492
1493 prefix: '$ '
1494 }
1495
1496
1497
1498
1499
1500
1501 });
1502
1503
1504
1505
1506
1507
1508
1509
1510
1511
1512
1513
1514
1515
1516
1517
1518
1519 Number.implement({
1520
1521 format: function(options){
1522
1523 var value = this;
1524 options = options ? Object.clone(options) : {};
1525 var getOption = function(key){
1526 if (options[key] != null) return options[key];
1527 return Locale.get('Number.' + key);
1528 };
1529
1530 var negative = value < 0,
1531 decimal = getOption('decimal'),
1532 precision = getOption('precision'),
1533 group = getOption('group'),
1534 decimals = getOption('decimals');
1535
1536 if (negative){
1537 var negativeLocale = getOption('negative') || {};
1538 if (negativeLocale.prefix == null && negativeLocale.suffix == null) negativeLocale.prefix = '-';
1539 ['prefix', 'suffix'].each(function(key){
1540 if (negativeLocale[key]) options[key] = getOption(key) + negativeLocale[key];
1541 });
1542
1543 value = -value;
1544 }
1545
1546 var prefix = getOption('prefix'),
1547 suffix = getOption('suffix');
1548
1549 if (decimals !== '' && decimals >= 0 && decimals <= 20) value = value.toFixed(decimals);
1550 if (precision >= 1 && precision <= 21) value = (+value).toPrecision(precision);
1551
1552 value += '';
1553 var index;
1554 if (getOption('scientific') === false && value.indexOf('e') > -1){
1555 var match = value.split('e'),
1556 zeros = +match[1];
1557 value = match[0].replace('.', '');
1558
1559 if (zeros < 0){
1560 zeros = -zeros - 1;
1561 index = match[0].indexOf('.');
1562 if (index > -1) zeros -= index - 1;
1563 while (zeros--) value = '0' + value;
1564 value = '0.' + value;
1565 } else {
1566 index = match[0].lastIndexOf('.');
1567 if (index > -1) zeros -= match[0].length - index - 1;
1568 while (zeros--) value += '0';
1569 }
1570 }
1571
1572 if (decimal != '.') value = value.replace('.', decimal);
1573
1574 if (group){
1575 index = value.lastIndexOf(decimal);
1576 index = (index > -1) ? index : value.length;
1577 var newOutput = value.substring(index),
1578 i = index;
1579
1580 while (i--){
1581 if ((index - i - 1) % 3 == 0 && i != (index - 1)) newOutput = group + newOutput;
1582 newOutput = value.charAt(i) + newOutput;
1583 }
1584
1585 value = newOutput;
1586 }
1587
1588 if (prefix) value = prefix + value;
1589 if (suffix) value += suffix;
1590
1591 return value;
1592 },
1593
1594 formatCurrency: function(decimals){
1595 var locale = Locale.get('Number.currency') || {};
1596 if (locale.scientific == null) locale.scientific = false;
1597 locale.decimals = decimals != null ? decimals
1598 : (locale.decimals == null ? 2 : locale.decimals);
1599
1600 return this.format(locale);
1601 },
1602
1603 formatPercentage: function(decimals){
1604 var locale = Locale.get('Number.percentage') || {};
1605 if (locale.suffix == null) locale.suffix = '%';
1606 locale.decimals = decimals != null ? decimals
1607 : (locale.decimals == null ? 2 : locale.decimals);
1608
1609 return this.format(locale);
1610 }
1611
1612 });
1613
1614
1615
1616
1617
1618
1619
1620
1621
1622
1623
1624
1625
1626
1627
1628
1629
1630
1631
1632
1633
1634
1635
1636
1637
1638
1639
1640
1641 (function(){
1642
1643 var special = {
1644 'a': /[àáâãäåăą]/g,
1645 'A': /[ÀÁÂÃÄÅĂĄ]/g,
1646 'c': /[ćčç]/g,
1647 'C': /[ĆČÇ]/g,
1648 'd': /[ďđ]/g,
1649 'D': /[ĎÐ]/g,
1650 'e': /[èéêëěę]/g,
1651 'E': /[ÈÉÊËĚĘ]/g,
1652 'g': /[ğ]/g,
1653 'G': /[Ğ]/g,
1654 'i': /[ìíîï]/g,
1655 'I': /[ÌÍÎÏ]/g,
1656 'l': /[ĺľł]/g,
1657 'L': /[ĹĽŁ]/g,
1658 'n': /[ñňń]/g,
1659 'N': /[ÑŇŃ]/g,
1660 'o': /[òóôõöøő]/g,
1661 'O': /[ÒÓÔÕÖØ]/g,
1662 'r': /[řŕ]/g,
1663 'R': /[ŘŔ]/g,
1664 's': /[ššş]/g,
1665 'S': /[ŠŞŚ]/g,
1666 't': /[ťţ]/g,
1667 'T': /[ŤŢ]/g,
1668 'ue': /[ü]/g,
1669 'UE': /[Ü]/g,
1670 'u': /[ùúûůµ]/g,
1671 'U': /[ÙÚÛŮ]/g,
1672 'y': /[ÿý]/g,
1673 'Y': /[ŸÝ]/g,
1674 'z': /[žźż]/g,
1675 'Z': /[ŽŹŻ]/g,
1676 'th': /[þ]/g,
1677 'TH': /[Þ]/g,
1678 'dh': /[ð]/g,
1679 'DH': /[Ð]/g,
1680 'ss': /[ß]/g,
1681 'oe': /[œ]/g,
1682 'OE': /[Œ]/g,
1683 'ae': /[æ]/g,
1684 'AE': /[Æ]/g
1685 },
1686
1687 tidy = {
1688 ' ': /[\xa0\u2002\u2003\u2009]/g,
1689 '*': /[\xb7]/g,
1690 '\'': /[\u2018\u2019]/g,
1691 '"': /[\u201c\u201d]/g,
1692 '...': /[\u2026]/g,
1693 '-': /[\u2013]/g,
1694
1695 '»': /[\uFFFD]/g
1696 };
1697
1698 var walk = function(string, replacements){
1699 var result = string, key;
1700 for (key in replacements) result = result.replace(replacements[key], key);
1701 return result;
1702 };
1703
1704 var getRegexForTag = function(tag, contents){
1705 tag = tag || '';
1706 var regstr = contents ? "<" + tag + "(?!\\w)[^>]*>([\\s\\S]*?)<\/" + tag + "(?!\\w)>" : "<\/?" + tag + "([^>]+)?>",
1707 reg = new RegExp(regstr, "gi");
1708 return reg;
1709 };
1710
1711 String.implement({
1712
1713 standardize: function(){
1714 return walk(this, special);
1715 },
1716
1717 repeat: function(times){
1718 return new Array(times + 1).join(this);
1719 },
1720
1721 pad: function(length, str, direction){
1722 if (this.length >= length) return this;
1723
1724 var pad = (str == null ? ' ' : '' + str)
1725 .repeat(length - this.length)
1726 .substr(0, length - this.length);
1727
1728 if (!direction || direction == 'right') return this + pad;
1729 if (direction == 'left') return pad + this;
1730
1731 return pad.substr(0, (pad.length / 2).floor()) + this + pad.substr(0, (pad.length / 2).ceil());
1732 },
1733
1734 getTags: function(tag, contents){
1735 return this.match(getRegexForTag(tag, contents)) || [];
1736 },
1737
1738 stripTags: function(tag, contents){
1739 return this.replace(getRegexForTag(tag, contents), '');
1740 },
1741
1742 tidy: function(){
1743 return walk(this, tidy);
1744 },
1745
1746 truncate: function(max, trail, atChar){
1747 var string = this;
1748 if (trail == null && arguments.length == 1) trail = '…';
1749 if (string.length > max){
1750 string = string.substring(0, max);
1751 if (atChar){
1752 var index = string.lastIndexOf(atChar);
1753 if (index != -1) string = string.substr(0, index);
1754 }
1755 if (trail) string += trail;
1756 }
1757 return string;
1758 }
1759
1760 });
1761
1762 })();
1763
1764
1765
1766
1767
1768
1769
1770
1771
1772
1773
1774
1775
1776
1777
1778
1779
1780
1781
1782
1783
1784
1785
1786
1787
1788
1789
1790
1791
1792 String.implement({
1793
1794 parseQueryString: function(decodeKeys, decodeValues){
1795 if (decodeKeys == null) decodeKeys = true;
1796 if (decodeValues == null) decodeValues = true;
1797
1798 var vars = this.split(/[&;]/),
1799 object = {};
1800 if (!vars.length) return object;
1801
1802 vars.each(function(val){
1803 var index = val.indexOf('=') + 1,
1804 value = index ? val.substr(index) : '',
1805 keys = index ? val.substr(0, index - 1).match(/([^\]\[]+|(\B)(?=\]))/g) : [val],
1806 obj = object;
1807 if (!keys) return;
1808 if (decodeValues) value = decodeURIComponent(value);
1809 keys.each(function(key, i){
1810 if (decodeKeys) key = decodeURIComponent(key);
1811 var current = obj[key];
1812
1813 if (i < keys.length - 1) obj = obj[key] = current || {};
1814 else if (typeOf(current) == 'array') current.push(value);
1815 else obj[key] = current != null ? [current, value] : value;
1816 });
1817 });
1818
1819 return object;
1820 },
1821
1822 cleanQueryString: function(method){
1823 return this.split('&').filter(function(val){
1824 var index = val.indexOf('='),
1825 key = index < 0 ? '' : val.substr(0, index),
1826 value = val.substr(index + 1);
1827
1828 return method ? method.call(null, key, value) : (value || value === 0);
1829 }).join('&');
1830 }
1831
1832 });
1833
1834
1835
1836
1837
1838
1839
1840
1841
1842
1843
1844
1845
1846
1847
1848
1849
1850
1851
1852
1853
1854
1855
1856
1857
1858
1859
1860
1861
1862 (function(){
1863
1864 var toString = function(){
1865 return this.get('value');
1866 };
1867
1868 var URI = this.URI = new Class({
1869
1870 Implements: Options,
1871
1872 options: {
1873
1874 },
1875
1876 regex: /^(?:(\w+):)?(?:\/\/(?:(?:([^:@\/]*):?([^:@\/]*))?@)?([^:\/?#]*)(?::(\d*))?)?(\.\.?$|(?:[^?#\/]*\/)*)([^?#]*)(?:\?([^#]*))?(?:#(.*))?/,
1877 parts: ['scheme', 'user', 'password', 'host', 'port', 'directory', 'file', 'query', 'fragment'],
1878 schemes: {http: 80, https: 443, ftp: 21, rtsp: 554, mms: 1755, file: 0},
1879
1880 initialize: function(uri, options){
1881 this.setOptions(options);
1882 var base = this.options.base || URI.base;
1883 if (!uri) uri = base;
1884
1885 if (uri && uri.parsed) this.parsed = Object.clone(uri.parsed);
1886 else this.set('value', uri.href || uri.toString(), base ? new URI(base) : false);
1887 },
1888
1889 parse: function(value, base){
1890 var bits = value.match(this.regex);
1891 if (!bits) return false;
1892 bits.shift();
1893 return this.merge(bits.associate(this.parts), base);
1894 },
1895
1896 merge: function(bits, base){
1897 if ((!bits || !bits.scheme) && (!base || !base.scheme)) return false;
1898 if (base){
1899 this.parts.every(function(part){
1900 if (bits[part]) return false;
1901 bits[part] = base[part] || '';
1902 return true;
1903 });
1904 }
1905 bits.port = bits.port || this.schemes[bits.scheme.toLowerCase()];
1906 bits.directory = bits.directory ? this.parseDirectory(bits.directory, base ? base.directory : '') : '/';
1907 return bits;
1908 },
1909
1910 parseDirectory: function(directory, baseDirectory){
1911 directory = (directory.substr(0, 1) == '/' ? '' : (baseDirectory || '/')) + directory;
1912 if (!directory.test(URI.regs.directoryDot)) return directory;
1913 var result = [];
1914 directory.replace(URI.regs.endSlash, '').split('/').each(function(dir){
1915 if (dir == '..' && result.length > 0) result.pop();
1916 else if (dir != '.') result.push(dir);
1917 });
1918 return result.join('/') + '/';
1919 },
1920
1921 combine: function(bits){
1922 return bits.value || bits.scheme + '://' +
1923 (bits.user ? bits.user + (bits.password ? ':' + bits.password : '') + '@' : '') +
1924 (bits.host || '') + (bits.port && bits.port != this.schemes[bits.scheme] ? ':' + bits.port : '') +
1925 (bits.directory || '/') + (bits.file || '') +
1926 (bits.query ? '?' + bits.query : '') +
1927 (bits.fragment ? '#' + bits.fragment : '');
1928 },
1929
1930 set: function(part, value, base){
1931 if (part == 'value'){
1932 var scheme = value.match(URI.regs.scheme);
1933 if (scheme) scheme = scheme[1];
1934 if (scheme && this.schemes[scheme.toLowerCase()] == null) this.parsed = { scheme: scheme, value: value };
1935 else this.parsed = this.parse(value, (base || this).parsed) || (scheme ? { scheme: scheme, value: value } : { value: value });
1936 } else if (part == 'data'){
1937 this.setData(value);
1938 } else {
1939 this.parsed[part] = value;
1940 }
1941 return this;
1942 },
1943
1944 get: function(part, base){
1945 switch (part){
1946 case 'value': return this.combine(this.parsed, base ? base.parsed : false);
1947 case 'data' : return this.getData();
1948 }
1949 return this.parsed[part] || '';
1950 },
1951
1952 go: function(){
1953 document.location.href = this.toString();
1954 },
1955
1956 toURI: function(){
1957 return this;
1958 },
1959
1960 getData: function(key, part){
1961 var qs = this.get(part || 'query');
1962 if (!(qs || qs === 0)) return key ? null : {};
1963 var obj = qs.parseQueryString();
1964 return key ? obj[key] : obj;
1965 },
1966
1967 setData: function(values, merge, part){
1968 if (typeof values == 'string'){
1969 var data = this.getData();
1970 data[arguments[0]] = arguments[1];
1971 values = data;
1972 } else if (merge){
1973 values = Object.merge(this.getData(), values);
1974 }
1975 return this.set(part || 'query', Object.toQueryString(values));
1976 },
1977
1978 clearData: function(part){
1979 return this.set(part || 'query', '');
1980 },
1981
1982 toString: toString,
1983 valueOf: toString
1984
1985 });
1986
1987 URI.regs = {
1988 endSlash: /\/$/,
1989 scheme: /^(\w+):/,
1990 directoryDot: /\.\/|\.$/
1991 };
1992
1993 URI.base = new URI(Array.from(document.getElements('base[href]', true)).getLast(), {base: document.location});
1994
1995 String.implement({
1996
1997 toURI: function(options){
1998 return new URI(this, options);
1999 }
2000
2001 });
2002
2003 })();
2004
2005
2006
2007
2008
2009
2010
2011
2012
2013
2014
2015
2016
2017
2018
2019
2020
2021
2022
2023
2024
2025
2026
2027
2028
2029
2030 URI = Class.refactor(URI, {
2031
2032 combine: function(bits, base){
2033 if (!base || bits.scheme != base.scheme || bits.host != base.host || bits.port != base.port)
2034 return this.previous.apply(this, arguments);
2035 var end = bits.file + (bits.query ? '?' + bits.query : '') + (bits.fragment ? '#' + bits.fragment : '');
2036
2037 if (!base.directory) return (bits.directory || (bits.file ? '' : './')) + end;
2038
2039 var baseDir = base.directory.split('/'),
2040 relDir = bits.directory.split('/'),
2041 path = '',
2042 offset;
2043
2044 var i = 0;
2045 for (offset = 0; offset < baseDir.length && offset < relDir.length && baseDir[offset] == relDir[offset]; offset++);
2046 for (i = 0; i < baseDir.length - offset - 1; i++) path += '../';
2047 for (i = offset; i < relDir.length - 1; i++) path += relDir[i] + '/';
2048
2049 return (path || (bits.file ? '' : './')) + end;
2050 },
2051
2052 toAbsolute: function(base){
2053 base = new URI(base);
2054 if (base) base.set('directory', '').set('file', '');
2055 return this.toRelative(base);
2056 },
2057
2058 toRelative: function(base){
2059 return this.get('value', new URI(base));
2060 }
2061
2062 });
2063
2064
2065
2066
2067
2068
2069
2070
2071
2072
2073
2074
2075
2076
2077
2078
2079
2080
2081
2082
2083 (function(){
2084
2085 if (this.Hash) return;
2086
2087 var Hash = this.Hash = new Type('Hash', function(object){
2088 if (typeOf(object) == 'hash') object = Object.clone(object.getClean());
2089 for (var key in object) this[key] = object[key];
2090 return this;
2091 });
2092
2093 this.$H = function(object){
2094 return new Hash(object);
2095 };
2096
2097 Hash.implement({
2098
2099 forEach: function(fn, bind){
2100 Object.forEach(this, fn, bind);
2101 },
2102
2103 getClean: function(){
2104 var clean = {};
2105 for (var key in this){
2106 if (this.hasOwnProperty(key)) clean[key] = this[key];
2107 }
2108 return clean;
2109 },
2110
2111 getLength: function(){
2112 var length = 0;
2113 for (var key in this){
2114 if (this.hasOwnProperty(key)) length++;
2115 }
2116 return length;
2117 }
2118
2119 });
2120
2121 Hash.alias('each', 'forEach');
2122
2123 Hash.implement({
2124
2125 has: Object.prototype.hasOwnProperty,
2126
2127 keyOf: function(value){
2128 return Object.keyOf(this, value);
2129 },
2130
2131 hasValue: function(value){
2132 return Object.contains(this, value);
2133 },
2134
2135 extend: function(properties){
2136 Hash.each(properties || {}, function(value, key){
2137 Hash.set(this, key, value);
2138 }, this);
2139 return this;
2140 },
2141
2142 combine: function(properties){
2143 Hash.each(properties || {}, function(value, key){
2144 Hash.include(this, key, value);
2145 }, this);
2146 return this;
2147 },
2148
2149 erase: function(key){
2150 if (this.hasOwnProperty(key)) delete this[key];
2151 return this;
2152 },
2153
2154 get: function(key){
2155 return (this.hasOwnProperty(key)) ? this[key] : null;
2156 },
2157
2158 set: function(key, value){
2159 if (!this[key] || this.hasOwnProperty(key)) this[key] = value;
2160 return this;
2161 },
2162
2163 empty: function(){
2164 Hash.each(this, function(value, key){
2165 delete this[key];
2166 }, this);
2167 return this;
2168 },
2169
2170 include: function(key, value){
2171 if (this[key] == undefined) this[key] = value;
2172 return this;
2173 },
2174
2175 map: function(fn, bind){
2176 return new Hash(Object.map(this, fn, bind));
2177 },
2178
2179 filter: function(fn, bind){
2180 return new Hash(Object.filter(this, fn, bind));
2181 },
2182
2183 every: function(fn, bind){
2184 return Object.every(this, fn, bind);
2185 },
2186
2187 some: function(fn, bind){
2188 return Object.some(this, fn, bind);
2189 },
2190
2191 getKeys: function(){
2192 return Object.keys(this);
2193 },
2194
2195 getValues: function(){
2196 return Object.values(this);
2197 },
2198
2199 toQueryString: function(base){
2200 return Object.toQueryString(this, base);
2201 }
2202
2203 });
2204
2205 Hash.alias({indexOf: 'keyOf', contains: 'hasValue'});
2206
2207
2208 })();
2209
2210
2211
2212
2213
2214
2215
2216
2217
2218
2219
2220
2221
2222
2223
2224
2225
2226
2227
2228
2229
2230
2231
2232
2233
2234
2235 Hash.implement({
2236
2237 getFromPath: function(notation){
2238 return Object.getFromPath(this, notation);
2239 },
2240
2241 cleanValues: function(method){
2242 return new Hash(Object.cleanValues(this, method));
2243 },
2244
2245 run: function(){
2246 Object.run(arguments);
2247 }
2248
2249 });
2250
2251
2252
2253
2254
2255
2256
2257
2258
2259
2260
2261
2262
2263
2264
2265
2266
2267
2268
2269
2270
2271
2272
2273
2274
2275
2276 Element.implement({
2277
2278 tidy: function(){
2279 this.set('value', this.get('value').tidy());
2280 },
2281
2282 getTextInRange: function(start, end){
2283 return this.get('value').substring(start, end);
2284 },
2285
2286 getSelectedText: function(){
2287 if (this.setSelectionRange) return this.getTextInRange(this.getSelectionStart(), this.getSelectionEnd());
2288 return document.selection.createRange().text;
2289 },
2290
2291 getSelectedRange: function(){
2292 if (this.selectionStart != null){
2293 return {
2294 start: this.selectionStart,
2295 end: this.selectionEnd
2296 };
2297 }
2298
2299 var pos = {
2300 start: 0,
2301 end: 0
2302 };
2303 var range = this.getDocument().selection.createRange();
2304 if (!range || range.parentElement() != this) return pos;
2305 var duplicate = range.duplicate();
2306
2307 if (this.type == 'text'){
2308 pos.start = 0 - duplicate.moveStart('character', -100000);
2309 pos.end = pos.start + range.text.length;
2310 } else {
2311 var value = this.get('value');
2312 var offset = value.length;
2313 duplicate.moveToElementText(this);
2314 duplicate.setEndPoint('StartToEnd', range);
2315 if (duplicate.text.length) offset -= value.match(/[\n\r]*$/)[0].length;
2316 pos.end = offset - duplicate.text.length;
2317 duplicate.setEndPoint('StartToStart', range);
2318 pos.start = offset - duplicate.text.length;
2319 }
2320 return pos;
2321 },
2322
2323 getSelectionStart: function(){
2324 return this.getSelectedRange().start;
2325 },
2326
2327 getSelectionEnd: function(){
2328 return this.getSelectedRange().end;
2329 },
2330
2331 setCaretPosition: function(pos){
2332 if (pos == 'end') pos = this.get('value').length;
2333 this.selectRange(pos, pos);
2334 return this;
2335 },
2336
2337 getCaretPosition: function(){
2338 return this.getSelectedRange().start;
2339 },
2340
2341 selectRange: function(start, end){
2342 if (this.setSelectionRange){
2343 this.focus();
2344 this.setSelectionRange(start, end);
2345 } else {
2346 var value = this.get('value');
2347 var diff = value.substr(start, end - start).replace(/\r/g, '').length;
2348 start = value.substr(0, start).replace(/\r/g, '').length;
2349 var range = this.createTextRange();
2350 range.collapse(true);
2351 range.moveEnd('character', start + diff);
2352 range.moveStart('character', start);
2353 range.select();
2354 }
2355 return this;
2356 },
2357
2358 insertAtCursor: function(value, select){
2359 var pos = this.getSelectedRange();
2360 var text = this.get('value');
2361 this.set('value', text.substring(0, pos.start) + value + text.substring(pos.end, text.length));
2362 if (select !== false) this.selectRange(pos.start, pos.start + value.length);
2363 else this.setCaretPosition(pos.start + value.length);
2364 return this;
2365 },
2366
2367 insertAroundCursor: function(options, select){
2368 options = Object.append({
2369 before: '',
2370 defaultMiddle: '',
2371 after: ''
2372 }, options);
2373
2374 var value = this.getSelectedText() || options.defaultMiddle;
2375 var pos = this.getSelectedRange();
2376 var text = this.get('value');
2377
2378 if (pos.start == pos.end){
2379 this.set('value', text.substring(0, pos.start) + options.before + value + options.after + text.substring(pos.end, text.length));
2380 this.selectRange(pos.start + options.before.length, pos.end + options.before.length + value.length);
2381 } else {
2382 var current = text.substring(pos.start, pos.end);
2383 this.set('value', text.substring(0, pos.start) + options.before + current + options.after + text.substring(pos.end, text.length));
2384 var selStart = pos.start + options.before.length;
2385 if (select !== false) this.selectRange(selStart, selStart + current.length);
2386 else this.setCaretPosition(selStart + text.length);
2387 }
2388 return this;
2389 }
2390
2391 });
2392
2393
2394
2395
2396
2397
2398
2399
2400
2401
2402
2403
2404
2405
2406
2407
2408
2409
2410
2411
2412
2413
2414
2415
2416
2417
2418 Elements.from = function(text, excludeScripts){
2419 if (excludeScripts || excludeScripts == null) text = text.stripScripts();
2420
2421 var container, match = text.match(/^\s*<(t[dhr]|tbody|tfoot|thead)/i);
2422
2423 if (match){
2424 container = new Element('table');
2425 var tag = match[1].toLowerCase();
2426 if (['td', 'th', 'tr'].contains(tag)){
2427 container = new Element('tbody').inject(container);
2428 if (tag != 'tr') container = new Element('tr').inject(container);
2429 }
2430 }
2431
2432 return (container || new Element('div')).set('html', text).getChildren();
2433 };
2434
2435
2436
2437
2438
2439
2440
2441
2442
2443
2444
2445
2446
2447
2448
2449
2450
2451
2452
2453
2454
2455 (function(){
2456
2457 var pseudos = {relay: false},
2458 copyFromEvents = ['once', 'throttle', 'pause'],
2459 count = copyFromEvents.length;
2460
2461 while (count--) pseudos[copyFromEvents[count]] = Events.lookupPseudo(copyFromEvents[count]);
2462
2463 DOMEvent.definePseudo = function(key, listener){
2464 pseudos[key] = listener;
2465 return this;
2466 };
2467
2468 var proto = Element.prototype;
2469 [Element, Window, Document].invoke('implement', Events.Pseudos(pseudos, proto.addEvent, proto.removeEvent));
2470
2471 })();
2472
2473
2474
2475
2476
2477
2478
2479
2480
2481
2482
2483
2484
2485
2486
2487
2488
2489
2490
2491
2492
2493 (function(){
2494
2495 var keysStoreKey = '$moo:keys-pressed',
2496 keysKeyupStoreKey = '$moo:keys-keyup';
2497
2498
2499 DOMEvent.definePseudo('keys', function(split, fn, args){
2500
2501 var event = args[0],
2502 keys = [],
2503 pressed = this.retrieve(keysStoreKey, []);
2504
2505 keys.append(split.value.replace('++', function(){
2506 keys.push('+');
2507 return '';
2508 }).split('+'));
2509
2510 pressed.include(event.key);
2511
2512 if (keys.every(function(key){
2513 return pressed.contains(key);
2514 })) fn.apply(this, args);
2515
2516 this.store(keysStoreKey, pressed);
2517
2518 if (!this.retrieve(keysKeyupStoreKey)){
2519 var keyup = function(event){
2520 (function(){
2521 pressed = this.retrieve(keysStoreKey, []).erase(event.key);
2522 this.store(keysStoreKey, pressed);
2523 }).delay(0, this);
2524 };
2525 this.store(keysKeyupStoreKey, keyup).addEvent('keyup', keyup);
2526 }
2527
2528 });
2529
2530 DOMEvent.defineKeys({
2531 '16': 'shift',
2532 '17': 'control',
2533 '18': 'alt',
2534 '20': 'capslock',
2535 '33': 'pageup',
2536 '34': 'pagedown',
2537 '35': 'end',
2538 '36': 'home',
2539 '144': 'numlock',
2540 '145': 'scrolllock',
2541 '186': ';',
2542 '187': '=',
2543 '188': ',',
2544 '190': '.',
2545 '191': '/',
2546 '192': '`',
2547 '219': '[',
2548 '220': '\\',
2549 '221': ']',
2550 '222': "'",
2551 '107': '+'
2552 }).defineKey(Browser.firefox ? 109 : 189, '-');
2553
2554 })();
2555
2556
2557
2558
2559
2560
2561
2562
2563
2564
2565
2566
2567
2568
2569
2570
2571
2572
2573
2574
2575
2576
2577
2578
2579
2580
2581
2582
2583 (function(){
2584
2585 var getStylesList = function(styles, planes){
2586 var list = [];
2587 Object.each(planes, function(directions){
2588 Object.each(directions, function(edge){
2589 styles.each(function(style){
2590 list.push(style + '-' + edge + (style == 'border' ? '-width' : ''));
2591 });
2592 });
2593 });
2594 return list;
2595 };
2596
2597 var calculateEdgeSize = function(edge, styles){
2598 var total = 0;
2599 Object.each(styles, function(value, style){
2600 if (style.test(edge)) total = total + value.toInt();
2601 });
2602 return total;
2603 };
2604
2605 var isVisible = function(el){
2606 return !!(!el || el.offsetHeight || el.offsetWidth);
2607 };
2608
2609
2610 Element.implement({
2611
2612 measure: function(fn){
2613 if (isVisible(this)) return fn.call(this);
2614 var parent = this.getParent(),
2615 toMeasure = [];
2616 while (!isVisible(parent) && parent != document.body){
2617 toMeasure.push(parent.expose());
2618 parent = parent.getParent();
2619 }
2620 var restore = this.expose(),
2621 result = fn.call(this);
2622 restore();
2623 toMeasure.each(function(restore){
2624 restore();
2625 });
2626 return result;
2627 },
2628
2629 expose: function(){
2630 if (this.getStyle('display') != 'none') return function(){};
2631 var before = this.style.cssText;
2632 this.setStyles({
2633 display: 'block',
2634 position: 'absolute',
2635 visibility: 'hidden'
2636 });
2637 return function(){
2638 this.style.cssText = before;
2639 }.bind(this);
2640 },
2641
2642 getDimensions: function(options){
2643 options = Object.merge({computeSize: false}, options);
2644 var dim = {x: 0, y: 0};
2645
2646 var getSize = function(el, options){
2647 return (options.computeSize) ? el.getComputedSize(options) : el.getSize();
2648 };
2649
2650 var parent = this.getParent('body');
2651
2652 if (parent && this.getStyle('display') == 'none'){
2653 dim = this.measure(function(){
2654 return getSize(this, options);
2655 });
2656 } else if (parent){
2657 try { //safari sometimes crashes here, so catch it
2658 dim = getSize(this, options);
2659 }catch(e){}
2660 }
2661
2662 return Object.append(dim, (dim.x || dim.x === 0) ? {
2663 width: dim.x,
2664 height: dim.y
2665 } : {
2666 x: dim.width,
2667 y: dim.height
2668 }
2669 );
2670 },
2671
2672 getComputedSize: function(options){
2673
2674
2675 options = Object.merge({
2676 styles: ['padding','border'],
2677 planes: {
2678 height: ['top','bottom'],
2679 width: ['left','right']
2680 },
2681 mode: 'both'
2682 }, options);
2683
2684 var styles = {},
2685 size = {width: 0, height: 0},
2686 dimensions;
2687
2688 if (options.mode == 'vertical'){
2689 delete size.width;
2690 delete options.planes.width;
2691 } else if (options.mode == 'horizontal'){
2692 delete size.height;
2693 delete options.planes.height;
2694 }
2695
2696 getStylesList(options.styles, options.planes).each(function(style){
2697 styles[style] = this.getStyle(style).toInt();
2698 }, this);
2699
2700 Object.each(options.planes, function(edges, plane){
2701
2702 var capitalized = plane.capitalize(),
2703 style = this.getStyle(plane);
2704
2705 if (style == 'auto' && !dimensions) dimensions = this.getDimensions();
2706
2707 style = styles[plane] = (style == 'auto') ? dimensions[plane] : style.toInt();
2708 size['total' + capitalized] = style;
2709
2710 edges.each(function(edge){
2711 var edgesize = calculateEdgeSize(edge, styles);
2712 size['computed' + edge.capitalize()] = edgesize;
2713 size['total' + capitalized] += edgesize;
2714 });
2715
2716 }, this);
2717
2718 return Object.append(size, styles);
2719 }
2720
2721 });
2722
2723 })();
2724
2725
2726
2727
2728
2729
2730
2731
2732
2733
2734
2735
2736
2737
2738
2739
2740
2741
2742
2743
2744
2745
2746
2747
2748
2749
2750
2751 (function(){
2752 var supportsPositionFixed = false,
2753 supportTested = false;
2754
2755 var testPositionFixed = function(){
2756 var test = new Element('div').setStyles({
2757 position: 'fixed',
2758 top: 0,
2759 right: 0
2760 }).inject(document.body);
2761 supportsPositionFixed = (test.offsetTop === 0);
2762 test.dispose();
2763 supportTested = true;
2764 };
2765
2766 Element.implement({
2767
2768 pin: function(enable, forceScroll){
2769 if (!supportTested) testPositionFixed();
2770 if (this.getStyle('display') == 'none') return this;
2771
2772 var pinnedPosition,
2773 scroll = window.getScroll(),
2774 parent,
2775 scrollFixer;
2776
2777 if (enable !== false){
2778 pinnedPosition = this.getPosition(supportsPositionFixed ? document.body : this.getOffsetParent());
2779 if (!this.retrieve('pin:_pinned')){
2780 var currentPosition = {
2781 top: pinnedPosition.y - scroll.y,
2782 left: pinnedPosition.x - scroll.x
2783 };
2784
2785 if (supportsPositionFixed && !forceScroll){
2786 this.setStyle('position', 'fixed').setStyles(currentPosition);
2787 } else {
2788
2789 parent = this.getOffsetParent();
2790 var position = this.getPosition(parent),
2791 styles = this.getStyles('left', 'top');
2792
2793 if (parent && styles.left == 'auto' || styles.top == 'auto') this.setPosition(position);
2794 if (this.getStyle('position') == 'static') this.setStyle('position', 'absolute');
2795
2796 position = {
2797 x: styles.left.toInt() - scroll.x,
2798 y: styles.top.toInt() - scroll.y
2799 };
2800
2801 scrollFixer = function(){
2802 if (!this.retrieve('pin:_pinned')) return;
2803 var scroll = window.getScroll();
2804 this.setStyles({
2805 left: position.x + scroll.x,
2806 top: position.y + scroll.y
2807 });
2808 }.bind(this);
2809
2810 this.store('pin:_scrollFixer', scrollFixer);
2811 window.addEvent('scroll', scrollFixer);
2812 }
2813 this.store('pin:_pinned', true);
2814 }
2815
2816 } else {
2817 if (!this.retrieve('pin:_pinned')) return this;
2818
2819 parent = this.getParent();
2820 var offsetParent = (parent.getComputedStyle('position') != 'static' ? parent : parent.getOffsetParent());
2821
2822 pinnedPosition = this.getPosition(offsetParent);
2823
2824 this.store('pin:_pinned', false);
2825 scrollFixer = this.retrieve('pin:_scrollFixer');
2826 if (!scrollFixer){
2827 this.setStyles({
2828 position: 'absolute',
2829 top: pinnedPosition.y + scroll.y,
2830 left: pinnedPosition.x + scroll.x
2831 });
2832 } else {
2833 this.store('pin:_scrollFixer', null);
2834 window.removeEvent('scroll', scrollFixer);
2835 }
2836 this.removeClass('isPinned');
2837 }
2838 return this;
2839 },
2840
2841 unpin: function(){
2842 return this.pin(false);
2843 },
2844
2845 togglePin: function(){
2846 return this.pin(!this.retrieve('pin:_pinned'));
2847 }
2848
2849 });
2850
2851
2852
2853 })();
2854
2855
2856
2857
2858
2859
2860
2861
2862
2863
2864
2865
2866
2867
2868
2869
2870
2871
2872
2873
2874
2875
2876
2877
2878
2879
2880
2881 (function(original){
2882
2883 var local = Element.Position = {
2884
2885 options: {
2886
2887
2888
2889
2890
2891
2892
2893
2894 relativeTo: document.body,
2895 position: {
2896 x: 'center', //left, center, right
2897 y: 'center' //top, center, bottom
2898 },
2899 offset: {x: 0, y: 0}
2900 },
2901
2902 getOptions: function(element, options){
2903 options = Object.merge({}, local.options, options);
2904 local.setPositionOption(options);
2905 local.setEdgeOption(options);
2906 local.setOffsetOption(element, options);
2907 local.setDimensionsOption(element, options);
2908 return options;
2909 },
2910
2911 setPositionOption: function(options){
2912 options.position = local.getCoordinateFromValue(options.position);
2913 },
2914
2915 setEdgeOption: function(options){
2916 var edgeOption = local.getCoordinateFromValue(options.edge);
2917 options.edge = edgeOption ? edgeOption :
2918 (options.position.x == 'center' && options.position.y == 'center') ? {x: 'center', y: 'center'} :
2919 {x: 'left', y: 'top'};
2920 },
2921
2922 setOffsetOption: function(element, options){
2923 var parentOffset = {x: 0, y: 0},
2924 offsetParent = element.measure(function(){
2925 return document.id(this.getOffsetParent());
2926 }),
2927 parentScroll = offsetParent.getScroll();
2928
2929 if (!offsetParent || offsetParent == element.getDocument().body) return;
2930 parentOffset = offsetParent.measure(function(){
2931 var position = this.getPosition();
2932 if (this.getStyle('position') == 'fixed'){
2933 var scroll = window.getScroll();
2934 position.x += scroll.x;
2935 position.y += scroll.y;
2936 }
2937 return position;
2938 });
2939
2940 options.offset = {
2941 parentPositioned: offsetParent != document.id(options.relativeTo),
2942 x: options.offset.x - parentOffset.x + parentScroll.x,
2943 y: options.offset.y - parentOffset.y + parentScroll.y
2944 };
2945 },
2946
2947 setDimensionsOption: function(element, options){
2948 options.dimensions = element.getDimensions({
2949 computeSize: true,
2950 styles: ['padding', 'border', 'margin']
2951 });
2952 },
2953
2954 getPosition: function(element, options){
2955 var position = {};
2956 options = local.getOptions(element, options);
2957 var relativeTo = document.id(options.relativeTo) || document.body;
2958
2959 local.setPositionCoordinates(options, position, relativeTo);
2960 if (options.edge) local.toEdge(position, options);
2961
2962 var offset = options.offset;
2963 position.left = ((position.x >= 0 || offset.parentPositioned || options.allowNegative) ? position.x : 0).toInt();
2964 position.top = ((position.y >= 0 || offset.parentPositioned || options.allowNegative) ? position.y : 0).toInt();
2965
2966 local.toMinMax(position, options);
2967
2968 if (options.relFixedPosition || relativeTo.getStyle('position') == 'fixed') local.toRelFixedPosition(relativeTo, position);
2969 if (options.ignoreScroll) local.toIgnoreScroll(relativeTo, position);
2970 if (options.ignoreMargins) local.toIgnoreMargins(position, options);
2971
2972 position.left = Math.ceil(position.left);
2973 position.top = Math.ceil(position.top);
2974 delete position.x;
2975 delete position.y;
2976
2977 return position;
2978 },
2979
2980 setPositionCoordinates: function(options, position, relativeTo){
2981 var offsetY = options.offset.y,
2982 offsetX = options.offset.x,
2983 calc = (relativeTo == document.body) ? window.getScroll() : relativeTo.getPosition(),
2984 top = calc.y,
2985 left = calc.x,
2986 winSize = window.getSize();
2987
2988 switch(options.position.x){
2989 case 'left': position.x = left + offsetX; break;
2990 case 'right': position.x = left + offsetX + relativeTo.offsetWidth; break;
2991 default: position.x = left + ((relativeTo == document.body ? winSize.x : relativeTo.offsetWidth) / 2) + offsetX; break;
2992 }
2993
2994 switch(options.position.y){
2995 case 'top': position.y = top + offsetY; break;
2996 case 'bottom': position.y = top + offsetY + relativeTo.offsetHeight; break;
2997 default: position.y = top + ((relativeTo == document.body ? winSize.y : relativeTo.offsetHeight) / 2) + offsetY; break;
2998 }
2999 },
3000
3001 toMinMax: function(position, options){
3002 var xy = {left: 'x', top: 'y'}, value;
3003 ['minimum', 'maximum'].each(function(minmax){
3004 ['left', 'top'].each(function(lr){
3005 value = options[minmax] ? options[minmax][xy[lr]] : null;
3006 if (value != null && ((minmax == 'minimum') ? position[lr] < value : position[lr] > value)) position[lr] = value;
3007 });
3008 });
3009 },
3010
3011 toRelFixedPosition: function(relativeTo, position){
3012 var winScroll = window.getScroll();
3013 position.top += winScroll.y;
3014 position.left += winScroll.x;
3015 },
3016
3017 toIgnoreScroll: function(relativeTo, position){
3018 var relScroll = relativeTo.getScroll();
3019 position.top -= relScroll.y;
3020 position.left -= relScroll.x;
3021 },
3022
3023 toIgnoreMargins: function(position, options){
3024 position.left += options.edge.x == 'right'
3025 ? options.dimensions['margin-right']
3026 : (options.edge.x != 'center'
3027 ? -options.dimensions['margin-left']
3028 : -options.dimensions['margin-left'] + ((options.dimensions['margin-right'] + options.dimensions['margin-left']) / 2));
3029
3030 position.top += options.edge.y == 'bottom'
3031 ? options.dimensions['margin-bottom']
3032 : (options.edge.y != 'center'
3033 ? -options.dimensions['margin-top']
3034 : -options.dimensions['margin-top'] + ((options.dimensions['margin-bottom'] + options.dimensions['margin-top']) / 2));
3035 },
3036
3037 toEdge: function(position, options){
3038 var edgeOffset = {},
3039 dimensions = options.dimensions,
3040 edge = options.edge;
3041
3042 switch(edge.x){
3043 case 'left': edgeOffset.x = 0; break;
3044 case 'right': edgeOffset.x = -dimensions.x - dimensions.computedRight - dimensions.computedLeft; break;
3045
3046 default: edgeOffset.x = -(Math.round(dimensions.totalWidth / 2)); break;
3047 }
3048
3049 switch(edge.y){
3050 case 'top': edgeOffset.y = 0; break;
3051 case 'bottom': edgeOffset.y = -dimensions.y - dimensions.computedTop - dimensions.computedBottom; break;
3052
3053 default: edgeOffset.y = -(Math.round(dimensions.totalHeight / 2)); break;
3054 }
3055
3056 position.x += edgeOffset.x;
3057 position.y += edgeOffset.y;
3058 },
3059
3060 getCoordinateFromValue: function(option){
3061 if (typeOf(option) != 'string') return option;
3062 option = option.toLowerCase();
3063
3064 return {
3065 x: option.test('left') ? 'left'
3066 : (option.test('right') ? 'right' : 'center'),
3067 y: option.test(/upper|top/) ? 'top'
3068 : (option.test('bottom') ? 'bottom' : 'center')
3069 };
3070 }
3071
3072 };
3073
3074 Element.implement({
3075
3076 position: function(options){
3077 if (options && (options.x != null || options.y != null)){
3078 return (original ? original.apply(this, arguments) : this);
3079 }
3080 var position = this.setStyle('position', 'absolute').calculatePosition(options);
3081 return (options && options.returnPos) ? position : this.setStyles(position);
3082 },
3083
3084 calculatePosition: function(options){
3085 return local.getPosition(this, options);
3086 }
3087
3088 });
3089
3090 })(Element.prototype.position);
3091
3092
3093
3094
3095
3096
3097
3098
3099
3100
3101
3102
3103
3104
3105
3106
3107
3108
3109
3110
3111
3112
3113
3114
3115
3116 Element.implement({
3117
3118 isDisplayed: function(){
3119 return this.getStyle('display') != 'none';
3120 },
3121
3122 isVisible: function(){
3123 var w = this.offsetWidth,
3124 h = this.offsetHeight;
3125 return (w == 0 && h == 0) ? false : (w > 0 && h > 0) ? true : this.style.display != 'none';
3126 },
3127
3128 toggle: function(){
3129 return this[this.isDisplayed() ? 'hide' : 'show']();
3130 },
3131
3132 hide: function(){
3133 var d;
3134 try {
3135
3136 d = this.getStyle('display');
3137 } catch(e){}
3138 if (d == 'none') return this;
3139 return this.store('element:_originalDisplay', d || '').setStyle('display', 'none');
3140 },
3141
3142 show: function(display){
3143 if (!display && this.isDisplayed()) return this;
3144 display = display || this.retrieve('element:_originalDisplay') || 'block';
3145 return this.setStyle('display', (display == 'none') ? 'block' : display);
3146 },
3147
3148 swapClass: function(remove, add){
3149 return this.removeClass(remove).addClass(add);
3150 }
3151
3152 });
3153
3154 Document.implement({
3155
3156 clearSelection: function(){
3157 if (window.getSelection){
3158 var selection = window.getSelection();
3159 if (selection && selection.removeAllRanges) selection.removeAllRanges();
3160 } else if (document.selection && document.selection.empty){
3161 try {
3162
3163 document.selection.empty();
3164 } catch(e){}
3165 }
3166 }
3167
3168 });
3169
3170
3171
3172
3173
3174
3175
3176
3177
3178
3179
3180
3181
3182
3183
3184
3185
3186
3187
3188
3189
3190
3191
3192
3193
3194
3195
3196
3197
3198 var IframeShim = new Class({
3199
3200 Implements: [Options, Events, Class.Occlude],
3201
3202 options: {
3203 className: 'iframeShim',
3204 src: 'javascript:false;document.write("");',
3205 display: false,
3206 zIndex: null,
3207 margin: 0,
3208 offset: {x: 0, y: 0},
3209 browsers: (Browser.ie6 || (Browser.firefox && Browser.version < 3 && Browser.Platform.mac))
3210 },
3211
3212 property: 'IframeShim',
3213
3214 initialize: function(element, options){
3215 this.element = document.id(element);
3216 if (this.occlude()) return this.occluded;
3217 this.setOptions(options);
3218 this.makeShim();
3219 return this;
3220 },
3221
3222 makeShim: function(){
3223 if (this.options.browsers){
3224 var zIndex = this.element.getStyle('zIndex').toInt();
3225
3226 if (!zIndex){
3227 zIndex = 1;
3228 var pos = this.element.getStyle('position');
3229 if (pos == 'static' || !pos) this.element.setStyle('position', 'relative');
3230 this.element.setStyle('zIndex', zIndex);
3231 }
3232 zIndex = ((this.options.zIndex != null || this.options.zIndex === 0) && zIndex > this.options.zIndex) ? this.options.zIndex : zIndex - 1;
3233 if (zIndex < 0) zIndex = 1;
3234 this.shim = new Element('iframe', {
3235 src: this.options.src,
3236 scrolling: 'no',
3237 frameborder: 0,
3238 styles: {
3239 zIndex: zIndex,
3240 position: 'absolute',
3241 border: 'none',
3242 filter: 'progid:DXImageTransform.Microsoft.Alpha(style=0,opacity=0)'
3243 },
3244 'class': this.options.className
3245 }).store('IframeShim', this);
3246 var inject = (function(){
3247 this.shim.inject(this.element, 'after');
3248 this[this.options.display ? 'show' : 'hide']();
3249 this.fireEvent('inject');
3250 }).bind(this);
3251 if (!IframeShim.ready) window.addEvent('load', inject);
3252 else inject();
3253 } else {
3254 this.position = this.hide = this.show = this.dispose = Function.from(this);
3255 }
3256 },
3257
3258 position: function(){
3259 if (!IframeShim.ready || !this.shim) return this;
3260 var size = this.element.measure(function(){
3261 return this.getSize();
3262 });
3263 if (this.options.margin != undefined){
3264 size.x = size.x - (this.options.margin * 2);
3265 size.y = size.y - (this.options.margin * 2);
3266 this.options.offset.x += this.options.margin;
3267 this.options.offset.y += this.options.margin;
3268 }
3269 this.shim.set({width: size.x, height: size.y}).position({
3270 relativeTo: this.element,
3271 offset: this.options.offset
3272 });
3273 return this;
3274 },
3275
3276 hide: function(){
3277 if (this.shim) this.shim.setStyle('display', 'none');
3278 return this;
3279 },
3280
3281 show: function(){
3282 if (this.shim) this.shim.setStyle('display', 'block');
3283 return this.position();
3284 },
3285
3286 dispose: function(){
3287 if (this.shim) this.shim.dispose();
3288 return this;
3289 },
3290
3291 destroy: function(){
3292 if (this.shim) this.shim.destroy();
3293 return this;
3294 }
3295
3296 });
3297
3298 window.addEvent('load', function(){
3299 IframeShim.ready = true;
3300 });
3301
3302
3303
3304
3305
3306
3307
3308
3309
3310
3311
3312
3313
3314
3315
3316
3317
3318
3319
3320
3321
3322
3323
3324
3325
3326
3327
3328
3329
3330 var Mask = new Class({
3331
3332 Implements: [Options, Events],
3333
3334 Binds: ['position'],
3335
3336 options: {
3337
3338
3339
3340
3341
3342
3343
3344
3345
3346
3347
3348 style: {},
3349 'class': 'mask',
3350 maskMargins: false,
3351 useIframeShim: true,
3352 iframeShimOptions: {}
3353 },
3354
3355 initialize: function(target, options){
3356 this.target = document.id(target) || document.id(document.body);
3357 this.target.store('mask', this);
3358 this.setOptions(options);
3359 this.render();
3360 this.inject();
3361 },
3362
3363 render: function(){
3364 this.element = new Element('div', {
3365 'class': this.options['class'],
3366 id: this.options.id || 'mask-' + String.uniqueID(),
3367 styles: Object.merge({}, this.options.style, {
3368 display: 'none'
3369 }),
3370 events: {
3371 click: function(event){
3372 this.fireEvent('click', event);
3373 if (this.options.hideOnClick) this.hide();
3374 }.bind(this)
3375 }
3376 });
3377
3378 this.hidden = true;
3379 },
3380
3381 toElement: function(){
3382 return this.element;
3383 },
3384
3385 inject: function(target, where){
3386 where = where || (this.options.inject ? this.options.inject.where : '') || this.target == document.body ? 'inside' : 'after';
3387 target = target || (this.options.inject && this.options.inject.target) || this.target;
3388
3389 this.element.inject(target, where);
3390
3391 if (this.options.useIframeShim){
3392 this.shim = new IframeShim(this.element, this.options.iframeShimOptions);
3393
3394 this.addEvents({
3395 show: this.shim.show.bind(this.shim),
3396 hide: this.shim.hide.bind(this.shim),
3397 destroy: this.shim.destroy.bind(this.shim)
3398 });
3399 }
3400 },
3401
3402 position: function(){
3403 this.resize(this.options.width, this.options.height);
3404
3405 this.element.position({
3406 relativeTo: this.target,
3407 position: 'topLeft',
3408 ignoreMargins: !this.options.maskMargins,
3409 ignoreScroll: this.target == document.body
3410 });
3411
3412 return this;
3413 },
3414
3415 resize: function(x, y){
3416 var opt = {
3417 styles: ['padding', 'border']
3418 };
3419 if (this.options.maskMargins) opt.styles.push('margin');
3420
3421 var dim = this.target.getComputedSize(opt);
3422 if (this.target == document.body){
3423 this.element.setStyles({width: 0, height: 0});
3424 var win = window.getScrollSize();
3425 if (dim.totalHeight < win.y) dim.totalHeight = win.y;
3426 if (dim.totalWidth < win.x) dim.totalWidth = win.x;
3427 }
3428 this.element.setStyles({
3429 width: Array.pick([x, dim.totalWidth, dim.x]),
3430 height: Array.pick([y, dim.totalHeight, dim.y])
3431 });
3432
3433 return this;
3434 },
3435
3436 show: function(){
3437 if (!this.hidden) return this;
3438
3439 window.addEvent('resize', this.position);
3440 this.position();
3441 this.showMask.apply(this, arguments);
3442
3443 return this;
3444 },
3445
3446 showMask: function(){
3447 this.element.setStyle('display', 'block');
3448 this.hidden = false;
3449 this.fireEvent('show');
3450 },
3451
3452 hide: function(){
3453 if (this.hidden) return this;
3454
3455 window.removeEvent('resize', this.position);
3456 this.hideMask.apply(this, arguments);
3457 if (this.options.destroyOnHide) return this.destroy();
3458
3459 return this;
3460 },
3461
3462 hideMask: function(){
3463 this.element.setStyle('display', 'none');
3464 this.hidden = true;
3465 this.fireEvent('hide');
3466 },
3467
3468 toggle: function(){
3469 this[this.hidden ? 'show' : 'hide']();
3470 },
3471
3472 destroy: function(){
3473 this.hide();
3474 this.element.destroy();
3475 this.fireEvent('destroy');
3476 this.target.eliminate('mask');
3477 }
3478
3479 });
3480
3481 Element.Properties.mask = {
3482
3483 set: function(options){
3484 var mask = this.retrieve('mask');
3485 if (mask) mask.destroy();
3486 return this.eliminate('mask').store('mask:options', options);
3487 },
3488
3489 get: function(){
3490 var mask = this.retrieve('mask');
3491 if (!mask){
3492 mask = new Mask(this, this.retrieve('mask:options'));
3493 this.store('mask', mask);
3494 }
3495 return mask;
3496 }
3497
3498 };
3499
3500 Element.implement({
3501
3502 mask: function(options){
3503 if (options) this.set('mask', options);
3504 this.get('mask').show();
3505 return this;
3506 },
3507
3508 unmask: function(){
3509 this.get('mask').hide();
3510 return this;
3511 }
3512
3513 });
3514
3515
3516
3517
3518
3519
3520
3521
3522
3523
3524
3525
3526
3527
3528
3529
3530
3531
3532
3533
3534
3535
3536
3537
3538
3539
3540
3541 var Spinner = new Class({
3542
3543 Extends: Mask,
3544
3545 Implements: Chain,
3546
3547 options: {
3548
3549 'class': 'spinner',
3550 containerPosition: {},
3551 content: {
3552 'class': 'spinner-content'
3553 },
3554 messageContainer: {
3555 'class': 'spinner-msg'
3556 },
3557 img: {
3558 'class': 'spinner-img'
3559 },
3560 fxOptions: {
3561 link: 'chain'
3562 }
3563 },
3564
3565 initialize: function(target, options){
3566 this.target = document.id(target) || document.id(document.body);
3567 this.target.store('spinner', this);
3568 this.setOptions(options);
3569 this.render();
3570 this.inject();
3571
3572
3573 var deactivate = function(){ this.active = false; }.bind(this);
3574 this.addEvents({
3575 hide: deactivate,
3576 show: deactivate
3577 });
3578 },
3579
3580 render: function(){
3581 this.parent();
3582
3583 this.element.set('id', this.options.id || 'spinner-' + String.uniqueID());
3584
3585 this.content = document.id(this.options.content) || new Element('div', this.options.content);
3586 this.content.inject(this.element);
3587
3588 if (this.options.message){
3589 this.msg = document.id(this.options.message) || new Element('p', this.options.messageContainer).appendText(this.options.message);
3590 this.msg.inject(this.content);
3591 }
3592
3593 if (this.options.img){
3594 this.img = document.id(this.options.img) || new Element('div', this.options.img);
3595 this.img.inject(this.content);
3596 }
3597
3598 this.element.set('tween', this.options.fxOptions);
3599 },
3600
3601 show: function(noFx){
3602 if (this.active) return this.chain(this.show.bind(this));
3603 if (!this.hidden){
3604 this.callChain.delay(20, this);
3605 return this;
3606 }
3607
3608 this.active = true;
3609
3610 return this.parent(noFx);
3611 },
3612
3613 showMask: function(noFx){
3614 var pos = function(){
3615 this.content.position(Object.merge({
3616 relativeTo: this.element
3617 }, this.options.containerPosition));
3618 }.bind(this);
3619
3620 if (noFx){
3621 this.parent();
3622 pos();
3623 } else {
3624 if (!this.options.style.opacity) this.options.style.opacity = this.element.getStyle('opacity').toFloat();
3625 this.element.setStyles({
3626 display: 'block',
3627 opacity: 0
3628 }).tween('opacity', this.options.style.opacity);
3629 pos();
3630 this.hidden = false;
3631 this.fireEvent('show');
3632 this.callChain();
3633 }
3634 },
3635
3636 hide: function(noFx){
3637 if (this.active) return this.chain(this.hide.bind(this));
3638 if (this.hidden){
3639 this.callChain.delay(20, this);
3640 return this;
3641 }
3642 this.active = true;
3643 return this.parent(noFx);
3644 },
3645
3646 hideMask: function(noFx){
3647 if (noFx) return this.parent();
3648 this.element.tween('opacity', 0).get('tween').chain(function(){
3649 this.element.setStyle('display', 'none');
3650 this.hidden = true;
3651 this.fireEvent('hide');
3652 this.callChain();
3653 }.bind(this));
3654 },
3655
3656 destroy: function(){
3657 this.content.destroy();
3658 this.parent();
3659 this.target.eliminate('spinner');
3660 }
3661
3662 });
3663
3664 Request = Class.refactor(Request, {
3665
3666 options: {
3667 useSpinner: false,
3668 spinnerOptions: {},
3669 spinnerTarget: false
3670 },
3671
3672 initialize: function(options){
3673 this._send = this.send;
3674 this.send = function(options){
3675 var spinner = this.getSpinner();
3676 if (spinner) spinner.chain(this._send.pass(options, this)).show();
3677 else this._send(options);
3678 return this;
3679 };
3680 this.previous(options);
3681 },
3682
3683 getSpinner: function(){
3684 if (!this.spinner){
3685 var update = document.id(this.options.spinnerTarget) || document.id(this.options.update);
3686 if (this.options.useSpinner && update){
3687 update.set('spinner', this.options.spinnerOptions);
3688 var spinner = this.spinner = update.get('spinner');
3689 ['complete', 'exception', 'cancel'].each(function(event){
3690 this.addEvent(event, spinner.hide.bind(spinner));
3691 }, this);
3692 }
3693 }
3694 return this.spinner;
3695 }
3696
3697 });
3698
3699 Element.Properties.spinner = {
3700
3701 set: function(options){
3702 var spinner = this.retrieve('spinner');
3703 if (spinner) spinner.destroy();
3704 return this.eliminate('spinner').store('spinner:options', options);
3705 },
3706
3707 get: function(){
3708 var spinner = this.retrieve('spinner');
3709 if (!spinner){
3710 spinner = new Spinner(this, this.retrieve('spinner:options'));
3711 this.store('spinner', spinner);
3712 }
3713 return spinner;
3714 }
3715
3716 };
3717
3718 Element.implement({
3719
3720 spin: function(options){
3721 if (options) this.set('spinner', options);
3722 this.get('spinner').show();
3723 return this;
3724 },
3725
3726 unspin: function(){
3727 this.get('spinner').hide();
3728 return this;
3729 }
3730
3731 });
3732
3733
3734
3735
3736
3737
3738
3739
3740
3741
3742
3743
3744
3745
3746
3747
3748
3749
3750
3751
3752
3753
3754
3755
3756
3757
3758
3759
3760
3761 if (!window.Form) window.Form = {};
3762
3763 (function(){
3764
3765 Form.Request = new Class({
3766
3767 Binds: ['onSubmit', 'onFormValidate'],
3768
3769 Implements: [Options, Events, Class.Occlude],
3770
3771 options: {
3772
3773
3774
3775 requestOptions: {
3776 evalScripts: true,
3777 useSpinner: true,
3778 emulation: false,
3779 link: 'ignore'
3780 },
3781 sendButtonClicked: true,
3782 extraData: {},
3783 resetForm: true
3784 },
3785
3786 property: 'form.request',
3787
3788 initialize: function(form, target, options){
3789 this.element = document.id(form);
3790 if (this.occlude()) return this.occluded;
3791 this.setOptions(options)
3792 .setTarget(target)
3793 .attach();
3794 },
3795
3796 setTarget: function(target){
3797 this.target = document.id(target);
3798 if (!this.request){
3799 this.makeRequest();
3800 } else {
3801 this.request.setOptions({
3802 update: this.target
3803 });
3804 }
3805 return this;
3806 },
3807
3808 toElement: function(){
3809 return this.element;
3810 },
3811
3812 makeRequest: function(){
3813 var self = this;
3814 this.request = new Request.HTML(Object.merge({
3815 update: this.target,
3816 emulation: false,
3817 spinnerTarget: this.element,
3818 method: this.element.get('method') || 'post'
3819 }, this.options.requestOptions)).addEvents({
3820 success: function(tree, elements, html, javascript){
3821 ['complete', 'success'].each(function(evt){
3822 self.fireEvent(evt, [self.target, tree, elements, html, javascript]);
3823 });
3824 },
3825 failure: function(){
3826 self.fireEvent('complete', arguments).fireEvent('failure', arguments);
3827 },
3828 exception: function(){
3829 self.fireEvent('failure', arguments);
3830 }
3831 });
3832 return this.attachReset();
3833 },
3834
3835 attachReset: function(){
3836 if (!this.options.resetForm) return this;
3837 this.request.addEvent('success', function(){
3838 Function.attempt(function(){
3839 this.element.reset();
3840 }.bind(this));
3841 if (window.OverText) OverText.update();
3842 }.bind(this));
3843 return this;
3844 },
3845
3846 attach: function(attach){
3847 var method = (attach != false) ? 'addEvent' : 'removeEvent';
3848 this.element[method]('click:relay(button, input[type=submit])', this.saveClickedButton.bind(this));
3849
3850 var fv = this.element.retrieve('validator');
3851 if (fv) fv[method]('onFormValidate', this.onFormValidate);
3852 else this.element[method]('submit', this.onSubmit);
3853
3854 return this;
3855 },
3856
3857 detach: function(){
3858 return this.attach(false);
3859 },
3860
3861
3862 enable: function(){
3863 return this.attach();
3864 },
3865
3866
3867 disable: function(){
3868 return this.detach();
3869 },
3870
3871 onFormValidate: function(valid, form, event){
3872
3873 if (!event) return;
3874 var fv = this.element.retrieve('validator');
3875 if (valid || (fv && !fv.options.stopOnFailure)){
3876 event.stop();
3877 this.send();
3878 }
3879 },
3880
3881 onSubmit: function(event){
3882 var fv = this.element.retrieve('validator');
3883 if (fv){
3884
3885 this.element.removeEvent('submit', this.onSubmit);
3886 fv.addEvent('onFormValidate', this.onFormValidate);
3887 this.element.validate();
3888 return;
3889 }
3890 if (event) event.stop();
3891 this.send();
3892 },
3893
3894 saveClickedButton: function(event, target){
3895 var targetName = target.get('name');
3896 if (!targetName || !this.options.sendButtonClicked) return;
3897 this.options.extraData[targetName] = target.get('value') || true;
3898 this.clickedCleaner = function(){
3899 delete this.options.extraData[targetName];
3900 this.clickedCleaner = function(){};
3901 }.bind(this);
3902 },
3903
3904 clickedCleaner: function(){},
3905
3906 send: function(){
3907 var str = this.element.toQueryString().trim(),
3908 data = Object.toQueryString(this.options.extraData);
3909
3910 if (str) str += "&" + data;
3911 else str = data;
3912
3913 this.fireEvent('send', [this.element, str.parseQueryString()]);
3914 this.request.send({
3915 data: str,
3916 url: this.options.requestOptions.url || this.element.get('action')
3917 });
3918 this.clickedCleaner();
3919 return this;
3920 }
3921
3922 });
3923
3924 Element.implement('formUpdate', function(update, options){
3925 var fq = this.retrieve('form.request');
3926 if (!fq){
3927 fq = new Form.Request(this, update, options);
3928 } else {
3929 if (update) fq.setTarget(update);
3930 if (options) fq.setOptions(options).makeRequest();
3931 }
3932 fq.send();
3933 return this;
3934 });
3935
3936 })();
3937
3938
3939
3940
3941
3942
3943
3944
3945
3946
3947
3948
3949
3950
3951
3952
3953
3954
3955
3956
3957
3958
3959
3960
3961
3962
3963 (function(){
3964
3965
3966 var hideTheseOf = function(object){
3967 var hideThese = object.options.hideInputs;
3968 if (window.OverText){
3969 var otClasses = [null];
3970 OverText.each(function(ot){
3971 otClasses.include('.' + ot.options.labelClass);
3972 });
3973 if (otClasses) hideThese += otClasses.join(', ');
3974 }
3975 return (hideThese) ? object.element.getElements(hideThese) : null;
3976 };
3977
3978
3979 Fx.Reveal = new Class({
3980
3981 Extends: Fx.Morph,
3982
3983 options: {
3984
3985
3986
3987
3988
3989 link: 'cancel',
3990 styles: ['padding', 'border', 'margin'],
3991 transitionOpacity: !Browser.ie6,
3992 mode: 'vertical',
3993 display: function(){
3994 return this.element.get('tag') != 'tr' ? 'block' : 'table-row';
3995 },
3996 opacity: 1,
3997 hideInputs: Browser.ie ? 'select, input, textarea, object, embed' : null
3998 },
3999
4000 dissolve: function(){
4001 if (!this.hiding && !this.showing){
4002 if (this.element.getStyle('display') != 'none'){
4003 this.hiding = true;
4004 this.showing = false;
4005 this.hidden = true;
4006 this.cssText = this.element.style.cssText;
4007
4008 var startStyles = this.element.getComputedSize({
4009 styles: this.options.styles,
4010 mode: this.options.mode
4011 });
4012 if (this.options.transitionOpacity) startStyles.opacity = this.options.opacity;
4013
4014 var zero = {};
4015 Object.each(startStyles, function(style, name){
4016 zero[name] = [style, 0];
4017 });
4018
4019 this.element.setStyles({
4020 display: Function.from(this.options.display).call(this),
4021 overflow: 'hidden'
4022 });
4023
4024 var hideThese = hideTheseOf(this);
4025 if (hideThese) hideThese.setStyle('visibility', 'hidden');
4026
4027 this.$chain.unshift(function(){
4028 if (this.hidden){
4029 this.hiding = false;
4030 this.element.style.cssText = this.cssText;
4031 this.element.setStyle('display', 'none');
4032 if (hideThese) hideThese.setStyle('visibility', 'visible');
4033 }
4034 this.fireEvent('hide', this.element);
4035 this.callChain();
4036 }.bind(this));
4037
4038 this.start(zero);
4039 } else {
4040 this.callChain.delay(10, this);
4041 this.fireEvent('complete', this.element);
4042 this.fireEvent('hide', this.element);
4043 }
4044 } else if (this.options.link == 'chain'){
4045 this.chain(this.dissolve.bind(this));
4046 } else if (this.options.link == 'cancel' && !this.hiding){
4047 this.cancel();
4048 this.dissolve();
4049 }
4050 return this;
4051 },
4052
4053 reveal: function(){
4054 if (!this.showing && !this.hiding){
4055 if (this.element.getStyle('display') == 'none'){
4056 this.hiding = false;
4057 this.showing = true;
4058 this.hidden = false;
4059 this.cssText = this.element.style.cssText;
4060
4061 var startStyles;
4062 this.element.measure(function(){
4063 startStyles = this.element.getComputedSize({
4064 styles: this.options.styles,
4065 mode: this.options.mode
4066 });
4067 }.bind(this));
4068 if (this.options.heightOverride != null) startStyles.height = this.options.heightOverride.toInt();
4069 if (this.options.widthOverride != null) startStyles.width = this.options.widthOverride.toInt();
4070 if (this.options.transitionOpacity){
4071 this.element.setStyle('opacity', 0);
4072 startStyles.opacity = this.options.opacity;
4073 }
4074
4075 var zero = {
4076 height: 0,
4077 display: Function.from(this.options.display).call(this)
4078 };
4079 Object.each(startStyles, function(style, name){
4080 zero[name] = 0;
4081 });
4082 zero.overflow = 'hidden';
4083
4084 this.element.setStyles(zero);
4085
4086 var hideThese = hideTheseOf(this);
4087 if (hideThese) hideThese.setStyle('visibility', 'hidden');
4088
4089 this.$chain.unshift(function(){
4090 this.element.style.cssText = this.cssText;
4091 this.element.setStyle('display', Function.from(this.options.display).call(this));
4092 if (!this.hidden) this.showing = false;
4093 if (hideThese) hideThese.setStyle('visibility', 'visible');
4094 this.callChain();
4095 this.fireEvent('show', this.element);
4096 }.bind(this));
4097
4098 this.start(startStyles);
4099 } else {
4100 this.callChain();
4101 this.fireEvent('complete', this.element);
4102 this.fireEvent('show', this.element);
4103 }
4104 } else if (this.options.link == 'chain'){
4105 this.chain(this.reveal.bind(this));
4106 } else if (this.options.link == 'cancel' && !this.showing){
4107 this.cancel();
4108 this.reveal();
4109 }
4110 return this;
4111 },
4112
4113 toggle: function(){
4114 if (this.element.getStyle('display') == 'none'){
4115 this.reveal();
4116 } else {
4117 this.dissolve();
4118 }
4119 return this;
4120 },
4121
4122 cancel: function(){
4123 this.parent.apply(this, arguments);
4124 if (this.cssText != null) this.element.style.cssText = this.cssText;
4125 this.hiding = false;
4126 this.showing = false;
4127 return this;
4128 }
4129
4130 });
4131
4132 Element.Properties.reveal = {
4133
4134 set: function(options){
4135 this.get('reveal').cancel().setOptions(options);
4136 return this;
4137 },
4138
4139 get: function(){
4140 var reveal = this.retrieve('reveal');
4141 if (!reveal){
4142 reveal = new Fx.Reveal(this);
4143 this.store('reveal', reveal);
4144 }
4145 return reveal;
4146 }
4147
4148 };
4149
4150 Element.Properties.dissolve = Element.Properties.reveal;
4151
4152 Element.implement({
4153
4154 reveal: function(options){
4155 this.get('reveal').setOptions(options).reveal();
4156 return this;
4157 },
4158
4159 dissolve: function(options){
4160 this.get('reveal').setOptions(options).dissolve();
4161 return this;
4162 },
4163
4164 nix: function(options){
4165 var params = Array.link(arguments, {destroy: Type.isBoolean, options: Type.isObject});
4166 this.get('reveal').setOptions(options).dissolve().chain(function(){
4167 this[params.destroy ? 'destroy' : 'dispose']();
4168 }.bind(this));
4169 return this;
4170 },
4171
4172 wink: function(){
4173 var params = Array.link(arguments, {duration: Type.isNumber, options: Type.isObject});
4174 var reveal = this.get('reveal').setOptions(params.options);
4175 reveal.reveal().chain(function(){
4176 (function(){
4177 reveal.dissolve();
4178 }).delay(params.duration || 2000);
4179 });
4180 }
4181
4182 });
4183
4184 })();
4185
4186
4187
4188
4189
4190
4191
4192
4193
4194
4195
4196
4197
4198
4199
4200
4201
4202
4203
4204
4205
4206
4207
4208
4209
4210
4211 Form.Request.Append = new Class({
4212
4213 Extends: Form.Request,
4214
4215 options: {
4216
4217 useReveal: true,
4218 revealOptions: {},
4219 inject: 'bottom'
4220 },
4221
4222 makeRequest: function(){
4223 this.request = new Request.HTML(Object.merge({
4224 url: this.element.get('action'),
4225 method: this.element.get('method') || 'post',
4226 spinnerTarget: this.element
4227 }, this.options.requestOptions, {
4228 evalScripts: false
4229 })
4230 ).addEvents({
4231 success: function(tree, elements, html, javascript){
4232 var container;
4233 var kids = Elements.from(html);
4234 if (kids.length == 1){
4235 container = kids[0];
4236 } else {
4237 container = new Element('div', {
4238 styles: {
4239 display: 'none'
4240 }
4241 }).adopt(kids);
4242 }
4243 container.inject(this.target, this.options.inject);
4244 if (this.options.requestOptions.evalScripts) Browser.exec(javascript);
4245 this.fireEvent('beforeEffect', container);
4246 var finish = function(){
4247 this.fireEvent('success', [container, this.target, tree, elements, html, javascript]);
4248 }.bind(this);
4249 if (this.options.useReveal){
4250 container.set('reveal', this.options.revealOptions).get('reveal').chain(finish);
4251 container.reveal();
4252 } else {
4253 finish();
4254 }
4255 }.bind(this),
4256 failure: function(xhr){
4257 this.fireEvent('failure', xhr);
4258 }.bind(this)
4259 });
4260 this.attachReset();
4261 }
4262
4263 });
4264
4265
4266
4267
4268
4269
4270
4271
4272
4273
4274
4275
4276
4277
4278
4279
4280
4281
4282
4283
4284
4285
4286 Locale.define('en-US', 'FormValidator', {
4287
4288 required: 'This field is required.',
4289 length: 'Please enter {length} characters (you entered {elLength} characters)',
4290 minLength: 'Please enter at least {minLength} characters (you entered {length} characters).',
4291 maxLength: 'Please enter no more than {maxLength} characters (you entered {length} characters).',
4292 integer: 'Please enter an integer in this field. Numbers with decimals (e.g. 1.25) are not permitted.',
4293 numeric: 'Please enter only numeric values in this field (i.e. "1" or "1.1" or "-1" or "-1.1").',
4294 digits: 'Please use numbers and punctuation only in this field (for example, a phone number with dashes or dots is permitted).',
4295 alpha: 'Please use only letters (a-z) within this field. No spaces or other characters are allowed.',
4296 alphanum: 'Please use only letters (a-z) or numbers (0-9) in this field. No spaces or other characters are allowed.',
4297 dateSuchAs: 'Please enter a valid date such as {date}',
4298 dateInFormatMDY: 'Please enter a valid date such as MM/DD/YYYY (i.e. "12/31/1999")',
4299 email: 'Please enter a valid email address. For example "fred@domain.com".',
4300 url: 'Please enter a valid URL such as http://www.example.com.',
4301 currencyDollar: 'Please enter a valid $ amount. For example $100.00 .',
4302 oneRequired: 'Please enter something for at least one of these inputs.',
4303 errorPrefix: 'Error: ',
4304 warningPrefix: 'Warning: ',
4305
4306
4307 noSpace: 'There can be no spaces in this input.',
4308 reqChkByNode: 'No items are selected.',
4309 requiredChk: 'This field is required.',
4310 reqChkByName: 'Please select a {label}.',
4311 match: 'This field needs to match the {matchName} field',
4312 startDate: 'the start date',
4313 endDate: 'the end date',
4314 currendDate: 'the current date',
4315 afterDate: 'The date should be the same or after {label}.',
4316 beforeDate: 'The date should be the same or before {label}.',
4317 startMonth: 'Please select a start month',
4318 sameMonth: 'These two dates must be in the same month - you must change one or the other.',
4319 creditcard: 'The credit card number entered is invalid. Please check the number and try again. {length} digits entered.'
4320
4321 });
4322
4323
4324
4325
4326
4327
4328
4329
4330
4331
4332
4333
4334
4335
4336
4337
4338
4339
4340
4341
4342
4343
4344
4345
4346
4347
4348
4349
4350
4351
4352
4353
4354
4355
4356 if (!window.Form) window.Form = {};
4357
4358 var InputValidator = this.InputValidator = new Class({
4359
4360 Implements: [Options],
4361
4362 options: {
4363 errorMsg: 'Validation failed.',
4364 test: Function.from(true)
4365 },
4366
4367 initialize: function(className, options){
4368 this.setOptions(options);
4369 this.className = className;
4370 },
4371
4372 test: function(field, props){
4373 field = document.id(field);
4374 return (field) ? this.options.test(field, props || this.getProps(field)) : false;
4375 },
4376
4377 getError: function(field, props){
4378 field = document.id(field);
4379 var err = this.options.errorMsg;
4380 if (typeOf(err) == 'function') err = err(field, props || this.getProps(field));
4381 return err;
4382 },
4383
4384 getProps: function(field){
4385 field = document.id(field);
4386 return (field) ? field.get('validatorProps') : {};
4387 }
4388
4389 });
4390
4391 Element.Properties.validators = {
4392
4393 get: function(){
4394 return (this.get('data-validators') || this.className).clean().split(' ');
4395 }
4396
4397 };
4398
4399 Element.Properties.validatorProps = {
4400
4401 set: function(props){
4402 return this.eliminate('$moo:validatorProps').store('$moo:validatorProps', props);
4403 },
4404
4405 get: function(props){
4406 if (props) this.set(props);
4407 if (this.retrieve('$moo:validatorProps')) return this.retrieve('$moo:validatorProps');
4408 if (this.getProperty('data-validator-properties') || this.getProperty('validatorProps')){
4409 try {
4410 this.store('$moo:validatorProps', JSON.decode(this.getProperty('validatorProps') || this.getProperty('data-validator-properties')));
4411 }catch(e){
4412 return {};
4413 }
4414 } else {
4415 var vals = this.get('validators').filter(function(cls){
4416 return cls.test(':');
4417 });
4418 if (!vals.length){
4419 this.store('$moo:validatorProps', {});
4420 } else {
4421 props = {};
4422 vals.each(function(cls){
4423 var split = cls.split(':');
4424 if (split[1]){
4425 try {
4426 props[split[0]] = JSON.decode(split[1]);
4427 } catch(e){}
4428 }
4429 });
4430 this.store('$moo:validatorProps', props);
4431 }
4432 }
4433 return this.retrieve('$moo:validatorProps');
4434 }
4435
4436 };
4437
4438 Form.Validator = new Class({
4439
4440 Implements: [Options, Events],
4441
4442 Binds: ['onSubmit'],
4443
4444 options: {
4445
4446
4447
4448
4449 fieldSelectors: 'input, select, textarea',
4450 ignoreHidden: true,
4451 ignoreDisabled: true,
4452 useTitles: false,
4453 evaluateOnSubmit: true,
4454 evaluateFieldsOnBlur: true,
4455 evaluateFieldsOnChange: true,
4456 serial: true,
4457 stopOnFailure: true,
4458 warningPrefix: function(){
4459 return Form.Validator.getMsg('warningPrefix') || 'Warning: ';
4460 },
4461 errorPrefix: function(){
4462 return Form.Validator.getMsg('errorPrefix') || 'Error: ';
4463 }
4464 },
4465
4466 initialize: function(form, options){
4467 this.setOptions(options);
4468 this.element = document.id(form);
4469 this.element.store('validator', this);
4470 this.warningPrefix = Function.from(this.options.warningPrefix)();
4471 this.errorPrefix = Function.from(this.options.errorPrefix)();
4472 if (this.options.evaluateOnSubmit) this.element.addEvent('submit', this.onSubmit);
4473 if (this.options.evaluateFieldsOnBlur || this.options.evaluateFieldsOnChange) this.watchFields(this.getFields());
4474 },
4475
4476 toElement: function(){
4477 return this.element;
4478 },
4479
4480 getFields: function(){
4481 return (this.fields = this.element.getElements(this.options.fieldSelectors));
4482 },
4483
4484 watchFields: function(fields){
4485 fields.each(function(el){
4486 if (this.options.evaluateFieldsOnBlur)
4487 el.addEvent('blur', this.validationMonitor.pass([el, false], this));
4488 if (this.options.evaluateFieldsOnChange)
4489 el.addEvent('change', this.validationMonitor.pass([el, true], this));
4490 }, this);
4491 },
4492
4493 validationMonitor: function(){
4494 clearTimeout(this.timer);
4495 this.timer = this.validateField.delay(50, this, arguments);
4496 },
4497
4498 onSubmit: function(event){
4499 if (this.validate(event)) this.reset();
4500 },
4501
4502 reset: function(){
4503 this.getFields().each(this.resetField, this);
4504 return this;
4505 },
4506
4507 validate: function(event){
4508 var result = this.getFields().map(function(field){
4509 return this.validateField(field, true);
4510 }, this).every(function(v){
4511 return v;
4512 });
4513 this.fireEvent('formValidate', [result, this.element, event]);
4514 if (this.options.stopOnFailure && !result && event) event.preventDefault();
4515 return result;
4516 },
4517
4518 validateField: function(field, force){
4519 if (this.paused) return true;
4520 field = document.id(field);
4521 var passed = !field.hasClass('validation-failed');
4522 var failed, warned;
4523 if (this.options.serial && !force){
4524 failed = this.element.getElement('.validation-failed');
4525 warned = this.element.getElement('.warning');
4526 }
4527 if (field && (!failed || force || field.hasClass('validation-failed') || (failed && !this.options.serial))){
4528 var validationTypes = field.get('validators');
4529 var validators = validationTypes.some(function(cn){
4530 return this.getValidator(cn);
4531 }, this);
4532 var validatorsFailed = [];
4533 validationTypes.each(function(className){
4534 if (className && !this.test(className, field)) validatorsFailed.include(className);
4535 }, this);
4536 passed = validatorsFailed.length === 0;
4537 if (validators && !this.hasValidator(field, 'warnOnly')){
4538 if (passed){
4539 field.addClass('validation-passed').removeClass('validation-failed');
4540 this.fireEvent('elementPass', [field]);
4541 } else {
4542 field.addClass('validation-failed').removeClass('validation-passed');
4543 this.fireEvent('elementFail', [field, validatorsFailed]);
4544 }
4545 }
4546 if (!warned){
4547 var warnings = validationTypes.some(function(cn){
4548 if (cn.test('^warn'))
4549 return this.getValidator(cn.replace(/^warn-/,''));
4550 else return null;
4551 }, this);
4552 field.removeClass('warning');
4553 var warnResult = validationTypes.map(function(cn){
4554 if (cn.test('^warn'))
4555 return this.test(cn.replace(/^warn-/,''), field, true);
4556 else return null;
4557 }, this);
4558 }
4559 }
4560 return passed;
4561 },
4562
4563 test: function(className, field, warn){
4564 field = document.id(field);
4565 if ((this.options.ignoreHidden && !field.isVisible()) || (this.options.ignoreDisabled && field.get('disabled'))) return true;
4566 var validator = this.getValidator(className);
4567 if (warn != null) warn = false;
4568 if (this.hasValidator(field, 'warnOnly')) warn = true;
4569 var isValid = this.hasValidator(field, 'ignoreValidation') || (validator ? validator.test(field) : true);
4570 if (validator && field.isVisible()) this.fireEvent('elementValidate', [isValid, field, className, warn]);
4571 if (warn) return true;
4572 return isValid;
4573 },
4574
4575 hasValidator: function(field, value){
4576 return field.get('validators').contains(value);
4577 },
4578
4579 resetField: function(field){
4580 field = document.id(field);
4581 if (field){
4582 field.get('validators').each(function(className){
4583 if (className.test('^warn-')) className = className.replace(/^warn-/, '');
4584 field.removeClass('validation-failed');
4585 field.removeClass('warning');
4586 field.removeClass('validation-passed');
4587 }, this);
4588 }
4589 return this;
4590 },
4591
4592 stop: function(){
4593 this.paused = true;
4594 return this;
4595 },
4596
4597 start: function(){
4598 this.paused = false;
4599 return this;
4600 },
4601
4602 ignoreField: function(field, warn){
4603 field = document.id(field);
4604 if (field){
4605 this.enforceField(field);
4606 if (warn) field.addClass('warnOnly');
4607 else field.addClass('ignoreValidation');
4608 }
4609 return this;
4610 },
4611
4612 enforceField: function(field){
4613 field = document.id(field);
4614 if (field) field.removeClass('warnOnly').removeClass('ignoreValidation');
4615 return this;
4616 }
4617
4618 });
4619
4620 Form.Validator.getMsg = function(key){
4621 return Locale.get('FormValidator.' + key);
4622 };
4623
4624 Form.Validator.adders = {
4625
4626 validators:{},
4627
4628 add : function(className, options){
4629 this.validators[className] = new InputValidator(className, options);
4630
4631
4632
4633 if (!this.initialize){
4634 this.implement({
4635 validators: this.validators
4636 });
4637 }
4638 },
4639
4640 addAllThese : function(validators){
4641 Array.from(validators).each(function(validator){
4642 this.add(validator[0], validator[1]);
4643 }, this);
4644 },
4645
4646 getValidator: function(className){
4647 return this.validators[className.split(':')[0]];
4648 }
4649
4650 };
4651
4652 Object.append(Form.Validator, Form.Validator.adders);
4653
4654 Form.Validator.implement(Form.Validator.adders);
4655
4656 Form.Validator.add('IsEmpty', {
4657
4658 errorMsg: false,
4659 test: function(element){
4660 if (element.type == 'select-one' || element.type == 'select')
4661 return !(element.selectedIndex >= 0 && element.options[element.selectedIndex].value != '');
4662 else
4663 return ((element.get('value') == null) || (element.get('value').length == 0));
4664 }
4665
4666 });
4667
4668 Form.Validator.addAllThese([
4669
4670 ['required', {
4671 errorMsg: function(){
4672 return Form.Validator.getMsg('required');
4673 },
4674 test: function(element){
4675 return !Form.Validator.getValidator('IsEmpty').test(element);
4676 }
4677 }],
4678
4679 ['length', {
4680 errorMsg: function(element, props){
4681 if (typeOf(props.length) != 'null')
4682 return Form.Validator.getMsg('length').substitute({length: props.length, elLength: element.get('value').length});
4683 else return '';
4684 },
4685 test: function(element, props){
4686 if (typeOf(props.length) != 'null') return (element.get('value').length == props.length || element.get('value').length == 0);
4687 else return true;
4688 }
4689 }],
4690
4691 ['minLength', {
4692 errorMsg: function(element, props){
4693 if (typeOf(props.minLength) != 'null')
4694 return Form.Validator.getMsg('minLength').substitute({minLength: props.minLength, length: element.get('value').length});
4695 else return '';
4696 },
4697 test: function(element, props){
4698 if (typeOf(props.minLength) != 'null') return (element.get('value').length >= (props.minLength || 0));
4699 else return true;
4700 }
4701 }],
4702
4703 ['maxLength', {
4704 errorMsg: function(element, props){
4705
4706 if (typeOf(props.maxLength) != 'null')
4707 return Form.Validator.getMsg('maxLength').substitute({maxLength: props.maxLength, length: element.get('value').length});
4708 else return '';
4709 },
4710 test: function(element, props){
4711 return element.get('value').length <= (props.maxLength || 10000);
4712 }
4713 }],
4714
4715 ['validate-integer', {
4716 errorMsg: Form.Validator.getMsg.pass('integer'),
4717 test: function(element){
4718 return Form.Validator.getValidator('IsEmpty').test(element) || (/^(-?[1-9]\d*|0)$/).test(element.get('value'));
4719 }
4720 }],
4721
4722 ['validate-numeric', {
4723 errorMsg: Form.Validator.getMsg.pass('numeric'),
4724 test: function(element){
4725 return Form.Validator.getValidator('IsEmpty').test(element) ||
4726 (/^-?(?:0$0(?=\d*\.)|[1-9]|0)\d*(\.\d+)?$/).test(element.get('value'));
4727 }
4728 }],
4729
4730 ['validate-digits', {
4731 errorMsg: Form.Validator.getMsg.pass('digits'),
4732 test: function(element){
4733 return Form.Validator.getValidator('IsEmpty').test(element) || (/^[\d() .:\-\+#]+$/.test(element.get('value')));
4734 }
4735 }],
4736
4737 ['validate-alpha', {
4738 errorMsg: Form.Validator.getMsg.pass('alpha'),
4739 test: function(element){
4740 return Form.Validator.getValidator('IsEmpty').test(element) || (/^[a-zA-Z]+$/).test(element.get('value'));
4741 }
4742 }],
4743
4744 ['validate-alphanum', {
4745 errorMsg: Form.Validator.getMsg.pass('alphanum'),
4746 test: function(element){
4747 return Form.Validator.getValidator('IsEmpty').test(element) || !(/\W/).test(element.get('value'));
4748 }
4749 }],
4750
4751 ['validate-date', {
4752 errorMsg: function(element, props){
4753 if (Date.parse){
4754 var format = props.dateFormat || '%x';
4755 return Form.Validator.getMsg('dateSuchAs').substitute({date: new Date().format(format)});
4756 } else {
4757 return Form.Validator.getMsg('dateInFormatMDY');
4758 }
4759 },
4760 test: function(element, props){
4761 if (Form.Validator.getValidator('IsEmpty').test(element)) return true;
4762 var dateLocale = Locale.getCurrent().sets.Date,
4763 dateNouns = new RegExp([dateLocale.days, dateLocale.days_abbr, dateLocale.months, dateLocale.months_abbr].flatten().join('|'), 'i'),
4764 value = element.get('value'),
4765 wordsInValue = value.match(/[a-z]+/gi);
4766
4767 if (wordsInValue && !wordsInValue.every(dateNouns.exec, dateNouns)) return false;
4768
4769 var date = Date.parse(value),
4770 format = props.dateFormat || '%x',
4771 formatted = date.format(format);
4772
4773 if (formatted != 'invalid date') element.set('value', formatted);
4774 return date.isValid();
4775 }
4776 }],
4777
4778 ['validate-email', {
4779 errorMsg: Form.Validator.getMsg.pass('email'),
4780 test: function(element){
4781
4782
4783
4784
4785
4786
4787
4788
4789
4790
4791
4792
4793
4794
4795 return Form.Validator.getValidator('IsEmpty').test(element) || (/^(?:[a-z0-9!#$%&'*+\/=?^_`{|}~-]\.?){0,63}[a-z0-9!#$%&'*+\/=?^_`{|}~-]@(?:(?:[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?\.)*[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?|\[(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\])$/i).test(element.get('value'));
4796 }
4797 }],
4798
4799 ['validate-url', {
4800 errorMsg: Form.Validator.getMsg.pass('url'),
4801 test: function(element){
4802 return Form.Validator.getValidator('IsEmpty').test(element) || (/^(https?|ftp|rmtp|mms):\/\/(([A-Z0-9][A-Z0-9_-]*)(\.[A-Z0-9][A-Z0-9_-]*)+)(:(\d+))?\/?/i).test(element.get('value'));
4803 }
4804 }],
4805
4806 ['validate-currency-dollar', {
4807 errorMsg: Form.Validator.getMsg.pass('currencyDollar'),
4808 test: function(element){
4809 return Form.Validator.getValidator('IsEmpty').test(element) || (/^\$?\-?([1-9]{1}[0-9]{0,2}(\,[0-9]{3})*(\.[0-9]{0,2})?|[1-9]{1}\d*(\.[0-9]{0,2})?|0(\.[0-9]{0,2})?|(\.[0-9]{1,2})?)$/).test(element.get('value'));
4810 }
4811 }],
4812
4813 ['validate-one-required', {
4814 errorMsg: Form.Validator.getMsg.pass('oneRequired'),
4815 test: function(element, props){
4816 var p = document.id(props['validate-one-required']) || element.getParent(props['validate-one-required']);
4817 return p.getElements('input').some(function(el){
4818 if (['checkbox', 'radio'].contains(el.get('type'))) return el.get('checked');
4819 return el.get('value');
4820 });
4821 }
4822 }]
4823
4824 ]);
4825
4826 Element.Properties.validator = {
4827
4828 set: function(options){
4829 this.get('validator').setOptions(options);
4830 },
4831
4832 get: function(){
4833 var validator = this.retrieve('validator');
4834 if (!validator){
4835 validator = new Form.Validator(this);
4836 this.store('validator', validator);
4837 }
4838 return validator;
4839 }
4840
4841 };
4842
4843 Element.implement({
4844
4845 validate: function(options){
4846 if (options) this.set('validator', options);
4847 return this.get('validator').validate();
4848 }
4849
4850 });
4851
4852
4853
4854
4855
4856
4857
4858
4859
4860
4861
4862
4863
4864
4865
4866
4867
4868
4869
4870
4871
4872
4873
4874
4875
4876
4877
4878
4879
4880 Form.Validator.Inline = new Class({
4881
4882 Extends: Form.Validator,
4883
4884 options: {
4885 showError: function(errorElement){
4886 if (errorElement.reveal) errorElement.reveal();
4887 else errorElement.setStyle('display', 'block');
4888 },
4889 hideError: function(errorElement){
4890 if (errorElement.dissolve) errorElement.dissolve();
4891 else errorElement.setStyle('display', 'none');
4892 },
4893 scrollToErrorsOnSubmit: true,
4894 scrollToErrorsOnBlur: false,
4895 scrollToErrorsOnChange: false,
4896 scrollFxOptions: {
4897 transition: 'quad:out',
4898 offset: {
4899 y: -20
4900 }
4901 }
4902 },
4903
4904 initialize: function(form, options){
4905 this.parent(form, options);
4906 this.addEvent('onElementValidate', function(isValid, field, className, warn){
4907 var validator = this.getValidator(className);
4908 if (!isValid && validator.getError(field)){
4909 if (warn) field.addClass('warning');
4910 var advice = this.makeAdvice(className, field, validator.getError(field), warn);
4911 this.insertAdvice(advice, field);
4912 this.showAdvice(className, field);
4913 } else {
4914 this.hideAdvice(className, field);
4915 }
4916 });
4917 },
4918
4919 makeAdvice: function(className, field, error, warn){
4920 var errorMsg = (warn) ? this.warningPrefix : this.errorPrefix;
4921 errorMsg += (this.options.useTitles) ? field.title || error:error;
4922 var cssClass = (warn) ? 'warning-advice' : 'validation-advice';
4923 var advice = this.getAdvice(className, field);
4924 if (advice){
4925 advice = advice.set('html', errorMsg);
4926 } else {
4927 advice = new Element('div', {
4928 html: errorMsg,
4929 styles: { display: 'none' },
4930 id: 'advice-' + className.split(':')[0] + '-' + this.getFieldId(field)
4931 }).addClass(cssClass);
4932 }
4933 field.store('$moo:advice-' + className, advice);
4934 return advice;
4935 },
4936
4937 getFieldId : function(field){
4938 return field.id ? field.id : field.id = 'input_' + field.name;
4939 },
4940
4941 showAdvice: function(className, field){
4942 var advice = this.getAdvice(className, field);
4943 if (
4944 advice &&
4945 !field.retrieve('$moo:' + this.getPropName(className)) &&
4946 (
4947 advice.getStyle('display') == 'none' ||
4948 advice.getStyle('visiblity') == 'hidden' ||
4949 advice.getStyle('opacity') == 0
4950 )
4951 ){
4952 field.store('$moo:' + this.getPropName(className), true);
4953 this.options.showError(advice);
4954 this.fireEvent('showAdvice', [field, advice, className]);
4955 }
4956 },
4957
4958 hideAdvice: function(className, field){
4959 var advice = this.getAdvice(className, field);
4960 if (advice && field.retrieve('$moo:' + this.getPropName(className))){
4961 field.store('$moo:' + this.getPropName(className), false);
4962 this.options.hideError(advice);
4963 this.fireEvent('hideAdvice', [field, advice, className]);
4964 }
4965 },
4966
4967 getPropName: function(className){
4968 return 'advice' + className;
4969 },
4970
4971 resetField: function(field){
4972 field = document.id(field);
4973 if (!field) return this;
4974 this.parent(field);
4975 field.get('validators').each(function(className){
4976 this.hideAdvice(className, field);
4977 }, this);
4978 return this;
4979 },
4980
4981 getAllAdviceMessages: function(field, force){
4982 var advice = [];
4983 if (field.hasClass('ignoreValidation') && !force) return advice;
4984 var validators = field.get('validators').some(function(cn){
4985 var warner = cn.test('^warn-') || field.hasClass('warnOnly');
4986 if (warner) cn = cn.replace(/^warn-/, '');
4987 var validator = this.getValidator(cn);
4988 if (!validator) return;
4989 advice.push({
4990 message: validator.getError(field),
4991 warnOnly: warner,
4992 passed: validator.test(),
4993 validator: validator
4994 });
4995 }, this);
4996 return advice;
4997 },
4998
4999 getAdvice: function(className, field){
5000 return field.retrieve('$moo:advice-' + className);
5001 },
5002
5003 insertAdvice: function(advice, field){
5004
5005 var props = field.get('validatorProps');
5006
5007 if (!props.msgPos || !document.id(props.msgPos)){
5008 if (field.type && field.type.toLowerCase() == 'radio') field.getParent().adopt(advice);
5009 else advice.inject(document.id(field), 'after');
5010 } else {
5011 document.id(props.msgPos).grab(advice);
5012 }
5013 },
5014
5015 validateField: function(field, force, scroll){
5016 var result = this.parent(field, force);
5017 if (((this.options.scrollToErrorsOnSubmit && scroll == null) || scroll) && !result){
5018 var failed = document.id(this).getElement('.validation-failed');
5019 var par = document.id(this).getParent();
5020 while (par != document.body && par.getScrollSize().y == par.getSize().y){
5021 par = par.getParent();
5022 }
5023 var fx = par.retrieve('$moo:fvScroller');
5024 if (!fx && window.Fx && Fx.Scroll){
5025 fx = new Fx.Scroll(par, this.options.scrollFxOptions);
5026 par.store('$moo:fvScroller', fx);
5027 }
5028 if (failed){
5029 if (fx) fx.toElement(failed);
5030 else par.scrollTo(par.getScroll().x, failed.getPosition(par).y - 20);
5031 }
5032 }
5033 return result;
5034 },
5035
5036 watchFields: function(fields){
5037 fields.each(function(el){
5038 if (this.options.evaluateFieldsOnBlur){
5039 el.addEvent('blur', this.validationMonitor.pass([el, false, this.options.scrollToErrorsOnBlur], this));
5040 }
5041 if (this.options.evaluateFieldsOnChange){
5042 el.addEvent('change', this.validationMonitor.pass([el, true, this.options.scrollToErrorsOnChange], this));
5043 }
5044 }, this);
5045 }
5046
5047 });
5048
5049
5050
5051
5052
5053
5054
5055
5056
5057
5058
5059
5060
5061
5062
5063
5064
5065
5066
5067
5068
5069
5070
5071 Form.Validator.addAllThese([
5072
5073 ['validate-enforce-oncheck', {
5074 test: function(element, props){
5075 var fv = element.getParent('form').retrieve('validator');
5076 if (!fv) return true;
5077 (props.toEnforce || document.id(props.enforceChildrenOf).getElements('input, select, textarea')).map(function(item){
5078 if (element.checked){
5079 fv.enforceField(item);
5080 } else {
5081 fv.ignoreField(item);
5082 fv.resetField(item);
5083 }
5084 });
5085 return true;
5086 }
5087 }],
5088
5089 ['validate-ignore-oncheck', {
5090 test: function(element, props){
5091 var fv = element.getParent('form').retrieve('validator');
5092 if (!fv) return true;
5093 (props.toIgnore || document.id(props.ignoreChildrenOf).getElements('input, select, textarea')).each(function(item){
5094 if (element.checked){
5095 fv.ignoreField(item);
5096 fv.resetField(item);
5097 } else {
5098 fv.enforceField(item);
5099 }
5100 });
5101 return true;
5102 }
5103 }],
5104
5105 ['validate-nospace', {
5106 errorMsg: function(){
5107 return Form.Validator.getMsg('noSpace');
5108 },
5109 test: function(element, props){
5110 return !element.get('value').test(/\s/);
5111 }
5112 }],
5113
5114 ['validate-toggle-oncheck', {
5115 test: function(element, props){
5116 var fv = element.getParent('form').retrieve('validator');
5117 if (!fv) return true;
5118 var eleArr = props.toToggle || document.id(props.toToggleChildrenOf).getElements('input, select, textarea');
5119 if (!element.checked){
5120 eleArr.each(function(item){
5121 fv.ignoreField(item);
5122 fv.resetField(item);
5123 });
5124 } else {
5125 eleArr.each(function(item){
5126 fv.enforceField(item);
5127 });
5128 }
5129 return true;
5130 }
5131 }],
5132
5133 ['validate-reqchk-bynode', {
5134 errorMsg: function(){
5135 return Form.Validator.getMsg('reqChkByNode');
5136 },
5137 test: function(element, props){
5138 return (document.id(props.nodeId).getElements(props.selector || 'input[type=checkbox], input[type=radio]')).some(function(item){
5139 return item.checked;
5140 });
5141 }
5142 }],
5143
5144 ['validate-required-check', {
5145 errorMsg: function(element, props){
5146 return props.useTitle ? element.get('title') : Form.Validator.getMsg('requiredChk');
5147 },
5148 test: function(element, props){
5149 return !!element.checked;
5150 }
5151 }],
5152
5153 ['validate-reqchk-byname', {
5154 errorMsg: function(element, props){
5155 return Form.Validator.getMsg('reqChkByName').substitute({label: props.label || element.get('type')});
5156 },
5157 test: function(element, props){
5158 var grpName = props.groupName || element.get('name');
5159 var oneCheckedItem = $$(document.getElementsByName(grpName)).some(function(item, index){
5160 return item.checked;
5161 });
5162 var fv = element.getParent('form').retrieve('validator');
5163 if (oneCheckedItem && fv) fv.resetField(element);
5164 return oneCheckedItem;
5165 }
5166 }],
5167
5168 ['validate-match', {
5169 errorMsg: function(element, props){
5170 return Form.Validator.getMsg('match').substitute({matchName: props.matchName || document.id(props.matchInput).get('name')});
5171 },
5172 test: function(element, props){
5173 var eleVal = element.get('value');
5174 var matchVal = document.id(props.matchInput) && document.id(props.matchInput).get('value');
5175 return eleVal && matchVal ? eleVal == matchVal : true;
5176 }
5177 }],
5178
5179 ['validate-after-date', {
5180 errorMsg: function(element, props){
5181 return Form.Validator.getMsg('afterDate').substitute({
5182 label: props.afterLabel || (props.afterElement ? Form.Validator.getMsg('startDate') : Form.Validator.getMsg('currentDate'))
5183 });
5184 },
5185 test: function(element, props){
5186 var start = document.id(props.afterElement) ? Date.parse(document.id(props.afterElement).get('value')) : new Date();
5187 var end = Date.parse(element.get('value'));
5188 return end && start ? end >= start : true;
5189 }
5190 }],
5191
5192 ['validate-before-date', {
5193 errorMsg: function(element, props){
5194 return Form.Validator.getMsg('beforeDate').substitute({
5195 label: props.beforeLabel || (props.beforeElement ? Form.Validator.getMsg('endDate') : Form.Validator.getMsg('currentDate'))
5196 });
5197 },
5198 test: function(element, props){
5199 var start = Date.parse(element.get('value'));
5200 var end = document.id(props.beforeElement) ? Date.parse(document.id(props.beforeElement).get('value')) : new Date();
5201 return end && start ? end >= start : true;
5202 }
5203 }],
5204
5205 ['validate-custom-required', {
5206 errorMsg: function(){
5207 return Form.Validator.getMsg('required');
5208 },
5209 test: function(element, props){
5210 return element.get('value') != props.emptyValue;
5211 }
5212 }],
5213
5214 ['validate-same-month', {
5215 errorMsg: function(element, props){
5216 var startMo = document.id(props.sameMonthAs) && document.id(props.sameMonthAs).get('value');
5217 var eleVal = element.get('value');
5218 if (eleVal != '') return Form.Validator.getMsg(startMo ? 'sameMonth' : 'startMonth');
5219 },
5220 test: function(element, props){
5221 var d1 = Date.parse(element.get('value'));
5222 var d2 = Date.parse(document.id(props.sameMonthAs) && document.id(props.sameMonthAs).get('value'));
5223 return d1 && d2 ? d1.format('%B') == d2.format('%B') : true;
5224 }
5225 }],
5226
5227
5228 ['validate-cc-num', {
5229 errorMsg: function(element){
5230 var ccNum = element.get('value').replace(/[^0-9]/g, '');
5231 return Form.Validator.getMsg('creditcard').substitute({length: ccNum.length});
5232 },
5233 test: function(element){
5234
5235 if (Form.Validator.getValidator('IsEmpty').test(element)) return true;
5236
5237
5238 var ccNum = element.get('value');
5239 ccNum = ccNum.replace(/[^0-9]/g, '');
5240
5241 var valid_type = false;
5242
5243 if (ccNum.test(/^4[0-9]{12}([0-9]{3})?$/)) valid_type = 'Visa';
5244 else if (ccNum.test(/^5[1-5]([0-9]{14})$/)) valid_type = 'Master Card';
5245 else if (ccNum.test(/^3[47][0-9]{13}$/)) valid_type = 'American Express';
5246 else if (ccNum.test(/^6011[0-9]{12}$/)) valid_type = 'Discover';
5247
5248 if (valid_type){
5249 var sum = 0;
5250 var cur = 0;
5251
5252 for (var i=ccNum.length-1; i>=0; --i){
5253 cur = ccNum.charAt(i).toInt();
5254 if (cur == 0) continue;
5255
5256 if ((ccNum.length-i) % 2 == 0) cur += cur;
5257 if (cur > 9){
5258 cur = cur.toString().charAt(0).toInt() + cur.toString().charAt(1).toInt();
5259 }
5260
5261 sum += cur;
5262 }
5263 if ((sum % 10) == 0) return true;
5264 }
5265
5266 var chunks = '';
5267 while (ccNum != ''){
5268 chunks += ' ' + ccNum.substr(0,4);
5269 ccNum = ccNum.substr(4);
5270 }
5271
5272 element.getParent('form').retrieve('validator').ignoreField(element);
5273 element.set('value', chunks.clean());
5274 element.getParent('form').retrieve('validator').enforceField(element);
5275 return false;
5276 }
5277 }]
5278
5279
5280 ]);
5281
5282
5283
5284
5285
5286
5287
5288
5289
5290
5291
5292
5293
5294
5295
5296
5297
5298
5299
5300
5301
5302
5303
5304
5305
5306
5307
5308
5309
5310
5311 var OverText = new Class({
5312
5313 Implements: [Options, Events, Class.Occlude],
5314
5315 Binds: ['reposition', 'assert', 'focus', 'hide'],
5316
5317 options: {
5318
5319
5320
5321
5322 element: 'label',
5323 labelClass: 'overTxtLabel',
5324 positionOptions: {
5325 position: 'upperLeft',
5326 edge: 'upperLeft',
5327 offset: {
5328 x: 4,
5329 y: 2
5330 }
5331 },
5332 poll: false,
5333 pollInterval: 250,
5334 wrap: false
5335 },
5336
5337 property: 'OverText',
5338
5339 initialize: function(element, options){
5340 element = this.element = document.id(element);
5341
5342 if (this.occlude()) return this.occluded;
5343 this.setOptions(options);
5344
5345 this.attach(element);
5346 OverText.instances.push(this);
5347
5348 if (this.options.poll) this.poll();
5349 },
5350
5351 toElement: function(){
5352 return this.element;
5353 },
5354
5355 attach: function(){
5356 var element = this.element,
5357 options = this.options,
5358 value = options.textOverride || element.get('alt') || element.get('title');
5359
5360 if (!value) return this;
5361
5362 var text = this.text = new Element(options.element, {
5363 'class': options.labelClass,
5364 styles: {
5365 lineHeight: 'normal',
5366 position: 'absolute',
5367 cursor: 'text'
5368 },
5369 html: value,
5370 events: {
5371 click: this.hide.pass(options.element == 'label', this)
5372 }
5373 }).inject(element, 'after');
5374
5375 if (options.element == 'label'){
5376 if (!element.get('id')) element.set('id', 'input_' + String.uniqueID());
5377 text.set('for', element.get('id'));
5378 }
5379
5380 if (options.wrap){
5381 this.textHolder = new Element('div.overTxtWrapper', {
5382 styles: {
5383 lineHeight: 'normal',
5384 position: 'relative'
5385 }
5386 }).grab(text).inject(element, 'before');
5387 }
5388
5389 return this.enable();
5390 },
5391
5392 destroy: function(){
5393 this.element.eliminate(this.property);
5394 this.disable();
5395 if (this.text) this.text.destroy();
5396 if (this.textHolder) this.textHolder.destroy();
5397 return this;
5398 },
5399
5400 disable: function(){
5401 this.element.removeEvents({
5402 focus: this.focus,
5403 blur: this.assert,
5404 change: this.assert
5405 });
5406 window.removeEvent('resize', this.reposition);
5407 this.hide(true, true);
5408 return this;
5409 },
5410
5411 enable: function(){
5412 this.element.addEvents({
5413 focus: this.focus,
5414 blur: this.assert,
5415 change: this.assert
5416 });
5417 window.addEvent('resize', this.reposition);
5418 this.reposition();
5419 return this;
5420 },
5421
5422 wrap: function(){
5423 if (this.options.element == 'label'){
5424 if (!this.element.get('id')) this.element.set('id', 'input_' + String.uniqueID());
5425 this.text.set('for', this.element.get('id'));
5426 }
5427 },
5428
5429 startPolling: function(){
5430 this.pollingPaused = false;
5431 return this.poll();
5432 },
5433
5434 poll: function(stop){
5435
5436
5437
5438 if (this.poller && !stop) return this;
5439 if (stop){
5440 clearInterval(this.poller);
5441 } else {
5442 this.poller = (function(){
5443 if (!this.pollingPaused) this.assert(true);
5444 }).periodical(this.options.pollInterval, this);
5445 }
5446
5447 return this;
5448 },
5449
5450 stopPolling: function(){
5451 this.pollingPaused = true;
5452 return this.poll(true);
5453 },
5454
5455 focus: function(){
5456 if (this.text && (!this.text.isDisplayed() || this.element.get('disabled'))) return this;
5457 return this.hide();
5458 },
5459
5460 hide: function(suppressFocus, force){
5461 if (this.text && (this.text.isDisplayed() && (!this.element.get('disabled') || force))){
5462 this.text.hide();
5463 this.fireEvent('textHide', [this.text, this.element]);
5464 this.pollingPaused = true;
5465 if (!suppressFocus){
5466 try {
5467 this.element.fireEvent('focus');
5468 this.element.focus();
5469 } catch(e){}
5470 }
5471 }
5472 return this;
5473 },
5474
5475 show: function(){
5476 if (this.text && !this.text.isDisplayed()){
5477 this.text.show();
5478 this.reposition();
5479 this.fireEvent('textShow', [this.text, this.element]);
5480 this.pollingPaused = false;
5481 }
5482 return this;
5483 },
5484
5485 test: function(){
5486 return !this.element.get('value');
5487 },
5488
5489 assert: function(suppressFocus){
5490 return this[this.test() ? 'show' : 'hide'](suppressFocus);
5491 },
5492
5493 reposition: function(){
5494 this.assert(true);
5495 if (!this.element.isVisible()) return this.stopPolling().hide();
5496 if (this.text && this.test()){
5497 this.text.position(Object.merge(this.options.positionOptions, {
5498 relativeTo: this.element
5499 }));
5500 }
5501 return this;
5502 }
5503
5504 });
5505
5506 OverText.instances = [];
5507
5508 Object.append(OverText, {
5509
5510 each: function(fn){
5511 return OverText.instances.each(function(ot, i){
5512 if (ot.element && ot.text) fn.call(OverText, ot, i);
5513 });
5514 },
5515
5516 update: function(){
5517
5518 return OverText.each(function(ot){
5519 return ot.reposition();
5520 });
5521
5522 },
5523
5524 hideAll: function(){
5525
5526 return OverText.each(function(ot){
5527 return ot.hide(true, true);
5528 });
5529
5530 },
5531
5532 showAll: function(){
5533 return OverText.each(function(ot){
5534 return ot.show();
5535 });
5536 }
5537
5538 });
5539
5540
5541
5542
5543
5544
5545
5546
5547
5548
5549
5550
5551
5552
5553
5554
5555
5556
5557
5558
5559
5560
5561
5562
5563
5564
5565 Fx.Elements = new Class({
5566
5567 Extends: Fx.CSS,
5568
5569 initialize: function(elements, options){
5570 this.elements = this.subject = $$(elements);
5571 this.parent(options);
5572 },
5573
5574 compute: function(from, to, delta){
5575 var now = {};
5576
5577 for (var i in from){
5578 var iFrom = from[i], iTo = to[i], iNow = now[i] = {};
5579 for (var p in iFrom) iNow[p] = this.parent(iFrom[p], iTo[p], delta);
5580 }
5581
5582 return now;
5583 },
5584
5585 set: function(now){
5586 for (var i in now){
5587 if (!this.elements[i]) continue;
5588
5589 var iNow = now[i];
5590 for (var p in iNow) this.render(this.elements[i], p, iNow[p], this.options.unit);
5591 }
5592
5593 return this;
5594 },
5595
5596 start: function(obj){
5597 if (!this.check(obj)) return this;
5598 var from = {}, to = {};
5599
5600 for (var i in obj){
5601 if (!this.elements[i]) continue;
5602
5603 var iProps = obj[i], iFrom = from[i] = {}, iTo = to[i] = {};
5604
5605 for (var p in iProps){
5606 var parsed = this.prepare(this.elements[i], p, iProps[p]);
5607 iFrom[p] = parsed.from;
5608 iTo[p] = parsed.to;
5609 }
5610 }
5611
5612 return this.parent(from, to);
5613 }
5614
5615 });
5616
5617
5618
5619
5620
5621
5622
5623
5624
5625
5626
5627
5628
5629
5630
5631
5632
5633
5634
5635
5636
5637
5638
5639
5640
5641 Fx.Accordion = new Class({
5642
5643 Extends: Fx.Elements,
5644
5645 options: {
5646
5647
5648 fixedHeight: false,
5649 fixedWidth: false,
5650 display: 0,
5651 show: false,
5652 height: true,
5653 width: false,
5654 opacity: true,
5655 alwaysHide: false,
5656 trigger: 'click',
5657 initialDisplayFx: true,
5658 resetHeight: true
5659 },
5660
5661 initialize: function(){
5662 var defined = function(obj){
5663 return obj != null;
5664 };
5665
5666 var params = Array.link(arguments, {
5667 'container': Type.isElement, //deprecated
5668 'options': Type.isObject,
5669 'togglers': defined,
5670 'elements': defined
5671 });
5672 this.parent(params.elements, params.options);
5673
5674 var options = this.options,
5675 togglers = this.togglers = $$(params.togglers);
5676
5677 this.previous = -1;
5678 this.internalChain = new Chain();
5679
5680 if (options.alwaysHide) this.options.link = 'chain';
5681
5682 if (options.show || this.options.show === 0){
5683 options.display = false;
5684 this.previous = options.show;
5685 }
5686
5687 if (options.start){
5688 options.display = false;
5689 options.show = false;
5690 }
5691
5692 var effects = this.effects = {};
5693
5694 if (options.opacity) effects.opacity = 'fullOpacity';
5695 if (options.width) effects.width = options.fixedWidth ? 'fullWidth' : 'offsetWidth';
5696 if (options.height) effects.height = options.fixedHeight ? 'fullHeight' : 'scrollHeight';
5697
5698 for (var i = 0, l = togglers.length; i < l; i++) this.addSection(togglers[i], this.elements[i]);
5699
5700 this.elements.each(function(el, i){
5701 if (options.show === i){
5702 this.fireEvent('active', [togglers[i], el]);
5703 } else {
5704 for (var fx in effects) el.setStyle(fx, 0);
5705 }
5706 }, this);
5707
5708 if (options.display || options.display === 0 || options.initialDisplayFx === false){
5709 this.display(options.display, options.initialDisplayFx);
5710 }
5711
5712 if (options.fixedHeight !== false) options.resetHeight = false;
5713 this.addEvent('complete', this.internalChain.callChain.bind(this.internalChain));
5714 },
5715
5716 addSection: function(toggler, element){
5717 toggler = document.id(toggler);
5718 element = document.id(element);
5719 this.togglers.include(toggler);
5720 this.elements.include(element);
5721
5722 var togglers = this.togglers,
5723 options = this.options,
5724 test = togglers.contains(toggler),
5725 idx = togglers.indexOf(toggler),
5726 displayer = this.display.pass(idx, this);
5727
5728 toggler.store('accordion:display', displayer)
5729 .addEvent(options.trigger, displayer);
5730
5731 if (options.height) element.setStyles({'padding-top': 0, 'border-top': 'none', 'padding-bottom': 0, 'border-bottom': 'none'});
5732 if (options.width) element.setStyles({'padding-left': 0, 'border-left': 'none', 'padding-right': 0, 'border-right': 'none'});
5733
5734 element.fullOpacity = 1;
5735 if (options.fixedWidth) element.fullWidth = options.fixedWidth;
5736 if (options.fixedHeight) element.fullHeight = options.fixedHeight;
5737 element.setStyle('overflow', 'hidden');
5738
5739 if (!test) for (var fx in this.effects){
5740 element.setStyle(fx, 0);
5741 }
5742 return this;
5743 },
5744
5745 removeSection: function(toggler, displayIndex){
5746 var togglers = this.togglers,
5747 idx = togglers.indexOf(toggler),
5748 element = this.elements[idx];
5749
5750 var remover = function(){
5751 togglers.erase(toggler);
5752 this.elements.erase(element);
5753 this.detach(toggler);
5754 }.bind(this);
5755
5756 if (this.now == idx || displayIndex != null){
5757 this.display(displayIndex != null ? displayIndex : (idx - 1 >= 0 ? idx - 1 : 0)).chain(remover);
5758 } else {
5759 remover();
5760 }
5761 return this;
5762 },
5763
5764 detach: function(toggler){
5765 var remove = function(toggler){
5766 toggler.removeEvent(this.options.trigger, toggler.retrieve('accordion:display'));
5767 }.bind(this);
5768
5769 if (!toggler) this.togglers.each(remove);
5770 else remove(toggler);
5771 return this;
5772 },
5773
5774 display: function(index, useFx){
5775 if (!this.check(index, useFx)) return this;
5776
5777 var obj = {},
5778 elements = this.elements,
5779 options = this.options,
5780 effects = this.effects;
5781
5782 if (useFx == null) useFx = true;
5783 if (typeOf(index) == 'element') index = elements.indexOf(index);
5784 if (index == this.previous && !options.alwaysHide) return this;
5785
5786 if (options.resetHeight){
5787 var prev = elements[this.previous];
5788 if (prev && !this.selfHidden){
5789 for (var fx in effects) prev.setStyle(fx, prev[effects[fx]]);
5790 }
5791 }
5792
5793 if ((this.timer && options.link == 'chain') || (index === this.previous && !options.alwaysHide)) return this;
5794
5795 this.previous = index;
5796 this.selfHidden = false;
5797
5798 elements.each(function(el, i){
5799 obj[i] = {};
5800 var hide;
5801 if (i != index){
5802 hide = true;
5803 } else if (options.alwaysHide && ((el.offsetHeight > 0 && options.height) || el.offsetWidth > 0 && options.width)){
5804 hide = true;
5805 this.selfHidden = true;
5806 }
5807 this.fireEvent(hide ? 'background' : 'active', [this.togglers[i], el]);
5808 for (var fx in effects) obj[i][fx] = hide ? 0 : el[effects[fx]];
5809 if (!useFx && !hide && options.resetHeight) obj[i].height = 'auto';
5810 }, this);
5811
5812 this.internalChain.clearChain();
5813 this.internalChain.chain(function(){
5814 if (options.resetHeight && !this.selfHidden){
5815 var el = elements[index];
5816 if (el) el.setStyle('height', 'auto');
5817 }
5818 }.bind(this));
5819
5820 return useFx ? this.start(obj) : this.set(obj).internalChain.callChain();
5821 }
5822
5823 });
5824
5825
5826
5827
5828
5829
5830
5831
5832
5833
5834
5835
5836
5837
5838
5839
5840
5841
5842
5843
5844
5845
5846
5847
5848
5849
5850
5851 Fx.Move = new Class({
5852
5853 Extends: Fx.Morph,
5854
5855 options: {
5856 relativeTo: document.body,
5857 position: 'center',
5858 edge: false,
5859 offset: {x: 0, y: 0}
5860 },
5861
5862 start: function(destination){
5863 var element = this.element,
5864 topLeft = element.getStyles('top', 'left');
5865 if (topLeft.top == 'auto' || topLeft.left == 'auto'){
5866 element.setPosition(element.getPosition(element.getOffsetParent()));
5867 }
5868 return this.parent(element.position(Object.merge({}, this.options, destination, {returnPos: true})));
5869 }
5870
5871 });
5872
5873 Element.Properties.move = {
5874
5875 set: function(options){
5876 this.get('move').cancel().setOptions(options);
5877 return this;
5878 },
5879
5880 get: function(){
5881 var move = this.retrieve('move');
5882 if (!move){
5883 move = new Fx.Move(this, {link: 'cancel'});
5884 this.store('move', move);
5885 }
5886 return move;
5887 }
5888
5889 };
5890
5891 Element.implement({
5892
5893 move: function(options){
5894 this.get('move').start(options);
5895 return this;
5896 }
5897
5898 });
5899
5900
5901
5902
5903
5904
5905
5906
5907
5908
5909
5910
5911
5912
5913
5914
5915
5916
5917
5918
5919
5920
5921
5922
5923
5924
5925
5926 (function(){
5927
5928 Fx.Scroll = new Class({
5929
5930 Extends: Fx,
5931
5932 options: {
5933 offset: {x: 0, y: 0},
5934 wheelStops: true
5935 },
5936
5937 initialize: function(element, options){
5938 this.element = this.subject = document.id(element);
5939 this.parent(options);
5940
5941 if (typeOf(this.element) != 'element') this.element = document.id(this.element.getDocument().body);
5942
5943 if (this.options.wheelStops){
5944 var stopper = this.element,
5945 cancel = this.cancel.pass(false, this);
5946 this.addEvent('start', function(){
5947 stopper.addEvent('mousewheel', cancel);
5948 }, true);
5949 this.addEvent('complete', function(){
5950 stopper.removeEvent('mousewheel', cancel);
5951 }, true);
5952 }
5953 },
5954
5955 set: function(){
5956 var now = Array.flatten(arguments);
5957 if (Browser.firefox) now = [Math.round(now[0]), Math.round(now[1])];
5958 this.element.scrollTo(now[0], now[1]);
5959 return this;
5960 },
5961
5962 compute: function(from, to, delta){
5963 return [0, 1].map(function(i){
5964 return Fx.compute(from[i], to[i], delta);
5965 });
5966 },
5967
5968 start: function(x, y){
5969 if (!this.check(x, y)) return this;
5970 var scroll = this.element.getScroll();
5971 return this.parent([scroll.x, scroll.y], [x, y]);
5972 },
5973
5974 calculateScroll: function(x, y){
5975 var element = this.element,
5976 scrollSize = element.getScrollSize(),
5977 scroll = element.getScroll(),
5978 size = element.getSize(),
5979 offset = this.options.offset,
5980 values = {x: x, y: y};
5981
5982 for (var z in values){
5983 if (!values[z] && values[z] !== 0) values[z] = scroll[z];
5984 if (typeOf(values[z]) != 'number') values[z] = scrollSize[z] - size[z];
5985 values[z] += offset[z];
5986 }
5987
5988 return [values.x, values.y];
5989 },
5990
5991 toTop: function(){
5992 return this.start.apply(this, this.calculateScroll(false, 0));
5993 },
5994
5995 toLeft: function(){
5996 return this.start.apply(this, this.calculateScroll(0, false));
5997 },
5998
5999 toRight: function(){
6000 return this.start.apply(this, this.calculateScroll('right', false));
6001 },
6002
6003 toBottom: function(){
6004 return this.start.apply(this, this.calculateScroll(false, 'bottom'));
6005 },
6006
6007 toElement: function(el, axes){
6008 axes = axes ? Array.from(axes) : ['x', 'y'];
6009 var scroll = isBody(this.element) ? {x: 0, y: 0} : this.element.getScroll();
6010 var position = Object.map(document.id(el).getPosition(this.element), function(value, axis){
6011 return axes.contains(axis) ? value + scroll[axis] : false;
6012 });
6013 return this.start.apply(this, this.calculateScroll(position.x, position.y));
6014 },
6015
6016 toElementEdge: function(el, axes, offset){
6017 axes = axes ? Array.from(axes) : ['x', 'y'];
6018 el = document.id(el);
6019 var to = {},
6020 position = el.getPosition(this.element),
6021 size = el.getSize(),
6022 scroll = this.element.getScroll(),
6023 containerSize = this.element.getSize(),
6024 edge = {
6025 x: position.x + size.x,
6026 y: position.y + size.y
6027 };
6028
6029 ['x', 'y'].each(function(axis){
6030 if (axes.contains(axis)){
6031 if (edge[axis] > scroll[axis] + containerSize[axis]) to[axis] = edge[axis] - containerSize[axis];
6032 if (position[axis] < scroll[axis]) to[axis] = position[axis];
6033 }
6034 if (to[axis] == null) to[axis] = scroll[axis];
6035 if (offset && offset[axis]) to[axis] = to[axis] + offset[axis];
6036 }, this);
6037
6038 if (to.x != scroll.x || to.y != scroll.y) this.start(to.x, to.y);
6039 return this;
6040 },
6041
6042 toElementCenter: function(el, axes, offset){
6043 axes = axes ? Array.from(axes) : ['x', 'y'];
6044 el = document.id(el);
6045 var to = {},
6046 position = el.getPosition(this.element),
6047 size = el.getSize(),
6048 scroll = this.element.getScroll(),
6049 containerSize = this.element.getSize();
6050
6051 ['x', 'y'].each(function(axis){
6052 if (axes.contains(axis)){
6053 to[axis] = position[axis] - (containerSize[axis] - size[axis]) / 2;
6054 }
6055 if (to[axis] == null) to[axis] = scroll[axis];
6056 if (offset && offset[axis]) to[axis] = to[axis] + offset[axis];
6057 }, this);
6058
6059 if (to.x != scroll.x || to.y != scroll.y) this.start(to.x, to.y);
6060 return this;
6061 }
6062
6063 });
6064
6065
6066
6067 function isBody(element){
6068 return (/^(?:body|html)$/i).test(element.tagName);
6069 }
6070
6071 })();
6072
6073
6074
6075
6076
6077
6078
6079
6080
6081
6082
6083
6084
6085
6086
6087
6088
6089
6090
6091
6092
6093
6094
6095
6096
6097
6098 Fx.Slide = new Class({
6099
6100 Extends: Fx,
6101
6102 options: {
6103 mode: 'vertical',
6104 wrapper: false,
6105 hideOverflow: true,
6106 resetHeight: false
6107 },
6108
6109 initialize: function(element, options){
6110 element = this.element = this.subject = document.id(element);
6111 this.parent(options);
6112 options = this.options;
6113
6114 var wrapper = element.retrieve('wrapper'),
6115 styles = element.getStyles('margin', 'position', 'overflow');
6116
6117 if (options.hideOverflow) styles = Object.append(styles, {overflow: 'hidden'});
6118 if (options.wrapper) wrapper = document.id(options.wrapper).setStyles(styles);
6119
6120 if (!wrapper) wrapper = new Element('div', {
6121 styles: styles
6122 }).wraps(element);
6123
6124 element.store('wrapper', wrapper).setStyle('margin', 0);
6125 if (element.getStyle('overflow') == 'visible') element.setStyle('overflow', 'hidden');
6126
6127 this.now = [];
6128 this.open = true;
6129 this.wrapper = wrapper;
6130
6131 this.addEvent('complete', function(){
6132 this.open = (wrapper['offset' + this.layout.capitalize()] != 0);
6133 if (this.open && this.options.resetHeight) wrapper.setStyle('height', '');
6134 }, true);
6135 },
6136
6137 vertical: function(){
6138 this.margin = 'margin-top';
6139 this.layout = 'height';
6140 this.offset = this.element.offsetHeight;
6141 },
6142
6143 horizontal: function(){
6144 this.margin = 'margin-left';
6145 this.layout = 'width';
6146 this.offset = this.element.offsetWidth;
6147 },
6148
6149 set: function(now){
6150 this.element.setStyle(this.margin, now[0]);
6151 this.wrapper.setStyle(this.layout, now[1]);
6152 return this;
6153 },
6154
6155 compute: function(from, to, delta){
6156 return [0, 1].map(function(i){
6157 return Fx.compute(from[i], to[i], delta);
6158 });
6159 },
6160
6161 start: function(how, mode){
6162 if (!this.check(how, mode)) return this;
6163 this[mode || this.options.mode]();
6164
6165 var margin = this.element.getStyle(this.margin).toInt(),
6166 layout = this.wrapper.getStyle(this.layout).toInt(),
6167 caseIn = [[margin, layout], [0, this.offset]],
6168 caseOut = [[margin, layout], [-this.offset, 0]],
6169 start;
6170
6171 switch (how){
6172 case 'in': start = caseIn; break;
6173 case 'out': start = caseOut; break;
6174 case 'toggle': start = (layout == 0) ? caseIn : caseOut;
6175 }
6176 return this.parent(start[0], start[1]);
6177 },
6178
6179 slideIn: function(mode){
6180 return this.start('in', mode);
6181 },
6182
6183 slideOut: function(mode){
6184 return this.start('out', mode);
6185 },
6186
6187 hide: function(mode){
6188 this[mode || this.options.mode]();
6189 this.open = false;
6190 return this.set([-this.offset, 0]);
6191 },
6192
6193 show: function(mode){
6194 this[mode || this.options.mode]();
6195 this.open = true;
6196 return this.set([0, this.offset]);
6197 },
6198
6199 toggle: function(mode){
6200 return this.start('toggle', mode);
6201 }
6202
6203 });
6204
6205 Element.Properties.slide = {
6206
6207 set: function(options){
6208 this.get('slide').cancel().setOptions(options);
6209 return this;
6210 },
6211
6212 get: function(){
6213 var slide = this.retrieve('slide');
6214 if (!slide){
6215 slide = new Fx.Slide(this, {link: 'cancel'});
6216 this.store('slide', slide);
6217 }
6218 return slide;
6219 }
6220
6221 };
6222
6223 Element.implement({
6224
6225 slide: function(how, mode){
6226 how = how || 'toggle';
6227 var slide = this.get('slide'), toggle;
6228 switch (how){
6229 case 'hide': slide.hide(mode); break;
6230 case 'show': slide.show(mode); break;
6231 case 'toggle':
6232 var flag = this.retrieve('slide:flag', slide.open);
6233 slide[flag ? 'slideOut' : 'slideIn'](mode);
6234 this.store('slide:flag', !flag);
6235 toggle = true;
6236 break;
6237 default: slide.start(how, mode);
6238 }
6239 if (!toggle) this.eliminate('slide:flag');
6240 return this;
6241 }
6242
6243 });
6244
6245
6246
6247
6248
6249
6250
6251
6252
6253
6254
6255
6256
6257
6258
6259
6260
6261
6262
6263
6264
6265
6266
6267
6268
6269 Fx.SmoothScroll = new Class({
6270
6271 Extends: Fx.Scroll,
6272
6273 options: {
6274 axes: ['x', 'y']
6275 },
6276
6277 initialize: function(options, context){
6278 context = context || document;
6279 this.doc = context.getDocument();
6280 this.parent(this.doc, options);
6281
6282 var win = context.getWindow(),
6283 location = win.location.href.match(/^[^#]*/)[0] + '#',
6284 links = $$(this.options.links || this.doc.links);
6285
6286 links.each(function(link){
6287 if (link.href.indexOf(location) != 0) return;
6288 var anchor = link.href.substr(location.length);
6289 if (anchor) this.useLink(link, anchor);
6290 }, this);
6291
6292 this.addEvent('complete', function(){
6293 win.location.hash = this.anchor;
6294 this.element.scrollTo(this.to[0], this.to[1]);
6295 }, true);
6296 },
6297
6298 useLink: function(link, anchor){
6299
6300 link.addEvent('click', function(event){
6301 var el = document.id(anchor) || this.doc.getElement('a[name=' + anchor + ']');
6302 if (!el) return;
6303
6304 event.preventDefault();
6305 this.toElement(el, this.options.axes).chain(function(){
6306 this.fireEvent('scrolledTo', [link, el]);
6307 }.bind(this));
6308
6309 this.anchor = anchor;
6310
6311 }.bind(this));
6312
6313 return this;
6314 }
6315 });
6316
6317
6318
6319
6320
6321
6322
6323
6324
6325
6326
6327
6328
6329
6330
6331
6332
6333
6334
6335
6336
6337
6338
6339
6340
6341
6342 Fx.Sort = new Class({
6343
6344 Extends: Fx.Elements,
6345
6346 options: {
6347 mode: 'vertical'
6348 },
6349
6350 initialize: function(elements, options){
6351 this.parent(elements, options);
6352 this.elements.each(function(el){
6353 if (el.getStyle('position') == 'static') el.setStyle('position', 'relative');
6354 });
6355 this.setDefaultOrder();
6356 },
6357
6358 setDefaultOrder: function(){
6359 this.currentOrder = this.elements.map(function(el, index){
6360 return index;
6361 });
6362 },
6363
6364 sort: function(){
6365 if (!this.check(arguments)) return this;
6366 var newOrder = Array.flatten(arguments);
6367
6368 var top = 0,
6369 left = 0,
6370 next = {},
6371 zero = {},
6372 vert = this.options.mode == 'vertical';
6373
6374 var current = this.elements.map(function(el, index){
6375 var size = el.getComputedSize({styles: ['border', 'padding', 'margin']});
6376 var val;
6377 if (vert){
6378 val = {
6379 top: top,
6380 margin: size['margin-top'],
6381 height: size.totalHeight
6382 };
6383 top += val.height - size['margin-top'];
6384 } else {
6385 val = {
6386 left: left,
6387 margin: size['margin-left'],
6388 width: size.totalWidth
6389 };
6390 left += val.width;
6391 }
6392 var plane = vert ? 'top' : 'left';
6393 zero[index] = {};
6394 var start = el.getStyle(plane).toInt();
6395 zero[index][plane] = start || 0;
6396 return val;
6397 }, this);
6398
6399 this.set(zero);
6400 newOrder = newOrder.map(function(i){ return i.toInt(); });
6401 if (newOrder.length != this.elements.length){
6402 this.currentOrder.each(function(index){
6403 if (!newOrder.contains(index)) newOrder.push(index);
6404 });
6405 if (newOrder.length > this.elements.length)
6406 newOrder.splice(this.elements.length-1, newOrder.length - this.elements.length);
6407 }
6408 var margin = 0;
6409 top = left = 0;
6410 newOrder.each(function(item){
6411 var newPos = {};
6412 if (vert){
6413 newPos.top = top - current[item].top - margin;
6414 top += current[item].height;
6415 } else {
6416 newPos.left = left - current[item].left;
6417 left += current[item].width;
6418 }
6419 margin = margin + current[item].margin;
6420 next[item]=newPos;
6421 }, this);
6422 var mapped = {};
6423 Array.clone(newOrder).sort().each(function(index){
6424 mapped[index] = next[index];
6425 });
6426 this.start(mapped);
6427 this.currentOrder = newOrder;
6428
6429 return this;
6430 },
6431
6432 rearrangeDOM: function(newOrder){
6433 newOrder = newOrder || this.currentOrder;
6434 var parent = this.elements[0].getParent();
6435 var rearranged = [];
6436 this.elements.setStyle('opacity', 0);
6437
6438 newOrder.each(function(index){
6439 rearranged.push(this.elements[index].inject(parent).setStyles({
6440 top: 0,
6441 left: 0
6442 }));
6443 }, this);
6444 this.elements.setStyle('opacity', 1);
6445 this.elements = $$(rearranged);
6446 this.setDefaultOrder();
6447 return this;
6448 },
6449
6450 getDefaultOrder: function(){
6451 return this.elements.map(function(el, index){
6452 return index;
6453 });
6454 },
6455
6456 getCurrentOrder: function(){
6457 return this.currentOrder;
6458 },
6459
6460 forward: function(){
6461 return this.sort(this.getDefaultOrder());
6462 },
6463
6464 backward: function(){
6465 return this.sort(this.getDefaultOrder().reverse());
6466 },
6467
6468 reverse: function(){
6469 return this.sort(this.currentOrder.reverse());
6470 },
6471
6472 sortByElements: function(elements){
6473 return this.sort(elements.map(function(el){
6474 return this.elements.indexOf(el);
6475 }, this));
6476 },
6477
6478 swap: function(one, two){
6479 if (typeOf(one) == 'element') one = this.elements.indexOf(one);
6480 if (typeOf(two) == 'element') two = this.elements.indexOf(two);
6481
6482 var newOrder = Array.clone(this.currentOrder);
6483 newOrder[this.currentOrder.indexOf(one)] = two;
6484 newOrder[this.currentOrder.indexOf(two)] = one;
6485
6486 return this.sort(newOrder);
6487 }
6488
6489 });
6490
6491
6492
6493
6494
6495
6496
6497
6498
6499
6500
6501
6502
6503
6504
6505
6506
6507
6508
6509
6510
6511
6512
6513
6514
6515
6516
6517
6518
6519
6520
6521 var Drag = new Class({
6522
6523 Implements: [Events, Options],
6524
6525 options: {
6526
6527
6528
6529
6530
6531
6532 snap: 6,
6533 unit: 'px',
6534 grid: false,
6535 style: true,
6536 limit: false,
6537 handle: false,
6538 invert: false,
6539 preventDefault: false,
6540 stopPropagation: false,
6541 modifiers: {x: 'left', y: 'top'}
6542 },
6543
6544 initialize: function(){
6545 var params = Array.link(arguments, {
6546 'options': Type.isObject,
6547 'element': function(obj){
6548 return obj != null;
6549 }
6550 });
6551
6552 this.element = document.id(params.element);
6553 this.document = this.element.getDocument();
6554 this.setOptions(params.options || {});
6555 var htype = typeOf(this.options.handle);
6556 this.handles = ((htype == 'array' || htype == 'collection') ? $$(this.options.handle) : document.id(this.options.handle)) || this.element;
6557 this.mouse = {'now': {}, 'pos': {}};
6558 this.value = {'start': {}, 'now': {}};
6559
6560 this.selection = (Browser.ie) ? 'selectstart' : 'mousedown';
6561
6562
6563 if (Browser.ie && !Drag.ondragstartFixed){
6564 document.ondragstart = Function.from(false);
6565 Drag.ondragstartFixed = true;
6566 }
6567
6568 this.bound = {
6569 start: this.start.bind(this),
6570 check: this.check.bind(this),
6571 drag: this.drag.bind(this),
6572 stop: this.stop.bind(this),
6573 cancel: this.cancel.bind(this),
6574 eventStop: Function.from(false)
6575 };
6576 this.attach();
6577 },
6578
6579 attach: function(){
6580 this.handles.addEvent('mousedown', this.bound.start);
6581 return this;
6582 },
6583
6584 detach: function(){
6585 this.handles.removeEvent('mousedown', this.bound.start);
6586 return this;
6587 },
6588
6589 start: function(event){
6590 var options = this.options;
6591
6592 if (event.rightClick) return;
6593
6594 if (options.preventDefault) event.preventDefault();
6595 if (options.stopPropagation) event.stopPropagation();
6596 this.mouse.start = event.page;
6597
6598 this.fireEvent('beforeStart', this.element);
6599
6600 var limit = options.limit;
6601 this.limit = {x: [], y: []};
6602
6603 var z, coordinates;
6604 for (z in options.modifiers){
6605 if (!options.modifiers[z]) continue;
6606
6607 var style = this.element.getStyle(options.modifiers[z]);
6608
6609
6610 if (style && !style.match(/px$/)){
6611 if (!coordinates) coordinates = this.element.getCoordinates(this.element.getOffsetParent());
6612 style = coordinates[options.modifiers[z]];
6613 }
6614
6615 if (options.style) this.value.now[z] = (style || 0).toInt();
6616 else this.value.now[z] = this.element[options.modifiers[z]];
6617
6618 if (options.invert) this.value.now[z] *= -1;
6619
6620 this.mouse.pos[z] = event.page[z] - this.value.now[z];
6621
6622 if (limit && limit[z]){
6623 var i = 2;
6624 while (i--){
6625 var limitZI = limit[z][i];
6626 if (limitZI || limitZI === 0) this.limit[z][i] = (typeof limitZI == 'function') ? limitZI() : limitZI;
6627 }
6628 }
6629 }
6630
6631 if (typeOf(this.options.grid) == 'number') this.options.grid = {
6632 x: this.options.grid,
6633 y: this.options.grid
6634 };
6635
6636 var events = {
6637 mousemove: this.bound.check,
6638 mouseup: this.bound.cancel
6639 };
6640 events[this.selection] = this.bound.eventStop;
6641 this.document.addEvents(events);
6642 },
6643
6644 check: function(event){
6645 if (this.options.preventDefault) event.preventDefault();
6646 var distance = Math.round(Math.sqrt(Math.pow(event.page.x - this.mouse.start.x, 2) + Math.pow(event.page.y - this.mouse.start.y, 2)));
6647 if (distance > this.options.snap){
6648 this.cancel();
6649 this.document.addEvents({
6650 mousemove: this.bound.drag,
6651 mouseup: this.bound.stop
6652 });
6653 this.fireEvent('start', [this.element, event]).fireEvent('snap', this.element);
6654 }
6655 },
6656
6657 drag: function(event){
6658 var options = this.options;
6659
6660 if (options.preventDefault) event.preventDefault();
6661 this.mouse.now = event.page;
6662
6663 for (var z in options.modifiers){
6664 if (!options.modifiers[z]) continue;
6665 this.value.now[z] = this.mouse.now[z] - this.mouse.pos[z];
6666
6667 if (options.invert) this.value.now[z] *= -1;
6668
6669 if (options.limit && this.limit[z]){
6670 if ((this.limit[z][1] || this.limit[z][1] === 0) && (this.value.now[z] > this.limit[z][1])){
6671 this.value.now[z] = this.limit[z][1];
6672 } else if ((this.limit[z][0] || this.limit[z][0] === 0) && (this.value.now[z] < this.limit[z][0])){
6673 this.value.now[z] = this.limit[z][0];
6674 }
6675 }
6676
6677 if (options.grid[z]) this.value.now[z] -= ((this.value.now[z] - (this.limit[z][0]||0)) % options.grid[z]);
6678
6679 if (options.style) this.element.setStyle(options.modifiers[z], this.value.now[z] + options.unit);
6680 else this.element[options.modifiers[z]] = this.value.now[z];
6681 }
6682
6683 this.fireEvent('drag', [this.element, event]);
6684 },
6685
6686 cancel: function(event){
6687 this.document.removeEvents({
6688 mousemove: this.bound.check,
6689 mouseup: this.bound.cancel
6690 });
6691 if (event){
6692 this.document.removeEvent(this.selection, this.bound.eventStop);
6693 this.fireEvent('cancel', this.element);
6694 }
6695 },
6696
6697 stop: function(event){
6698 var events = {
6699 mousemove: this.bound.drag,
6700 mouseup: this.bound.stop
6701 };
6702 events[this.selection] = this.bound.eventStop;
6703 this.document.removeEvents(events);
6704 if (event) this.fireEvent('complete', [this.element, event]);
6705 }
6706
6707 });
6708
6709 Element.implement({
6710
6711 makeResizable: function(options){
6712 var drag = new Drag(this, Object.merge({
6713 modifiers: {
6714 x: 'width',
6715 y: 'height'
6716 }
6717 }, options));
6718
6719 this.store('resizer', drag);
6720 return drag.addEvent('drag', function(){
6721 this.fireEvent('resize', drag);
6722 }.bind(this));
6723 }
6724
6725 });
6726
6727
6728
6729
6730
6731
6732
6733
6734
6735
6736
6737
6738
6739
6740
6741
6742
6743
6744
6745
6746
6747
6748
6749
6750
6751
6752
6753
6754
6755 Drag.Move = new Class({
6756
6757 Extends: Drag,
6758
6759 options: {
6760
6761
6762
6763 droppables: [],
6764 container: false,
6765 precalculate: false,
6766 includeMargins: true,
6767 checkDroppables: true
6768 },
6769
6770 initialize: function(element, options){
6771 this.parent(element, options);
6772 element = this.element;
6773
6774 this.droppables = $$(this.options.droppables);
6775 this.container = document.id(this.options.container);
6776
6777 if (this.container && typeOf(this.container) != 'element')
6778 this.container = document.id(this.container.getDocument().body);
6779
6780 if (this.options.style){
6781 if (this.options.modifiers.x == 'left' && this.options.modifiers.y == 'top'){
6782 var parent = element.getOffsetParent(),
6783 styles = element.getStyles('left', 'top');
6784 if (parent && (styles.left == 'auto' || styles.top == 'auto')){
6785 element.setPosition(element.getPosition(parent));
6786 }
6787 }
6788
6789 if (element.getStyle('position') == 'static') element.setStyle('position', 'absolute');
6790 }
6791
6792 this.addEvent('start', this.checkDroppables, true);
6793 this.overed = null;
6794 },
6795
6796 start: function(event){
6797 if (this.container) this.options.limit = this.calculateLimit();
6798
6799 if (this.options.precalculate){
6800 this.positions = this.droppables.map(function(el){
6801 return el.getCoordinates();
6802 });
6803 }
6804
6805 this.parent(event);
6806 },
6807
6808 calculateLimit: function(){
6809 var element = this.element,
6810 container = this.container,
6811
6812 offsetParent = document.id(element.getOffsetParent()) || document.body,
6813 containerCoordinates = container.getCoordinates(offsetParent),
6814 elementMargin = {},
6815 elementBorder = {},
6816 containerMargin = {},
6817 containerBorder = {},
6818 offsetParentPadding = {};
6819
6820 ['top', 'right', 'bottom', 'left'].each(function(pad){
6821 elementMargin[pad] = element.getStyle('margin-' + pad).toInt();
6822 elementBorder[pad] = element.getStyle('border-' + pad).toInt();
6823 containerMargin[pad] = container.getStyle('margin-' + pad).toInt();
6824 containerBorder[pad] = container.getStyle('border-' + pad).toInt();
6825 offsetParentPadding[pad] = offsetParent.getStyle('padding-' + pad).toInt();
6826 }, this);
6827
6828 var width = element.offsetWidth + elementMargin.left + elementMargin.right,
6829 height = element.offsetHeight + elementMargin.top + elementMargin.bottom,
6830 left = 0,
6831 top = 0,
6832 right = containerCoordinates.right - containerBorder.right - width,
6833 bottom = containerCoordinates.bottom - containerBorder.bottom - height;
6834
6835 if (this.options.includeMargins){
6836 left += elementMargin.left;
6837 top += elementMargin.top;
6838 } else {
6839 right += elementMargin.right;
6840 bottom += elementMargin.bottom;
6841 }
6842
6843 if (element.getStyle('position') == 'relative'){
6844 var coords = element.getCoordinates(offsetParent);
6845 coords.left -= element.getStyle('left').toInt();
6846 coords.top -= element.getStyle('top').toInt();
6847
6848 left -= coords.left;
6849 top -= coords.top;
6850 if (container.getStyle('position') != 'relative'){
6851 left += containerBorder.left;
6852 top += containerBorder.top;
6853 }
6854 right += elementMargin.left - coords.left;
6855 bottom += elementMargin.top - coords.top;
6856
6857 if (container != offsetParent){
6858 left += containerMargin.left + offsetParentPadding.left;
6859 top += ((Browser.ie6 || Browser.ie7) ? 0 : containerMargin.top) + offsetParentPadding.top;
6860 }
6861 } else {
6862 left -= elementMargin.left;
6863 top -= elementMargin.top;
6864 if (container != offsetParent){
6865 left += containerCoordinates.left + containerBorder.left;
6866 top += containerCoordinates.top + containerBorder.top;
6867 }
6868 }
6869
6870 return {
6871 x: [left, right],
6872 y: [top, bottom]
6873 };
6874 },
6875
6876 getDroppableCoordinates: function(element){
6877 var position = element.getCoordinates();
6878 if (element.getStyle('position') == 'fixed'){
6879 var scroll = window.getScroll();
6880 position.left += scroll.x;
6881 position.right += scroll.x;
6882 position.top += scroll.y;
6883 position.bottom += scroll.y;
6884 }
6885 return position;
6886 },
6887
6888 checkDroppables: function(){
6889 var overed = this.droppables.filter(function(el, i){
6890 el = this.positions ? this.positions[i] : this.getDroppableCoordinates(el);
6891 var now = this.mouse.now;
6892 return (now.x > el.left && now.x < el.right && now.y < el.bottom && now.y > el.top);
6893 }, this).getLast();
6894
6895 if (this.overed != overed){
6896 if (this.overed) this.fireEvent('leave', [this.element, this.overed]);
6897 if (overed) this.fireEvent('enter', [this.element, overed]);
6898 this.overed = overed;
6899 }
6900 },
6901
6902 drag: function(event){
6903 this.parent(event);
6904 if (this.options.checkDroppables && this.droppables.length) this.checkDroppables();
6905 },
6906
6907 stop: function(event){
6908 this.checkDroppables();
6909 this.fireEvent('drop', [this.element, this.overed, event]);
6910 this.overed = null;
6911 return this.parent(event);
6912 }
6913
6914 });
6915
6916 Element.implement({
6917
6918 makeDraggable: function(options){
6919 var drag = new Drag.Move(this, options);
6920 this.store('dragger', drag);
6921 return drag;
6922 }
6923
6924 });
6925
6926
6927
6928
6929
6930
6931
6932
6933
6934
6935
6936
6937
6938
6939
6940
6941
6942
6943
6944
6945
6946
6947
6948
6949
6950
6951
6952 var Slider = new Class({
6953
6954 Implements: [Events, Options],
6955
6956 Binds: ['clickedElement', 'draggedKnob', 'scrolledElement'],
6957
6958 options: {
6959
6960
6961
6962 onTick: function(position){
6963 this.setKnobPosition(position);
6964 },
6965 initialStep: 0,
6966 snap: false,
6967 offset: 0,
6968 range: false,
6969 wheel: false,
6970 steps: 100,
6971 mode: 'horizontal'
6972 },
6973
6974 initialize: function(element, knob, options){
6975 this.setOptions(options);
6976 options = this.options;
6977 this.element = document.id(element);
6978 knob = this.knob = document.id(knob);
6979 this.previousChange = this.previousEnd = this.step = -1;
6980
6981 var limit = {},
6982 modifiers = {x: false, y: false};
6983
6984 switch (options.mode){
6985 case 'vertical':
6986 this.axis = 'y';
6987 this.property = 'top';
6988 this.offset = 'offsetHeight';
6989 break;
6990 case 'horizontal':
6991 this.axis = 'x';
6992 this.property = 'left';
6993 this.offset = 'offsetWidth';
6994 }
6995
6996 this.setSliderDimensions();
6997 this.setRange(options.range);
6998
6999 if (knob.getStyle('position') == 'static') knob.setStyle('position', 'relative');
7000 knob.setStyle(this.property, -options.offset);
7001 modifiers[this.axis] = this.property;
7002 limit[this.axis] = [-options.offset, this.full - options.offset];
7003
7004 var dragOptions = {
7005 snap: 0,
7006 limit: limit,
7007 modifiers: modifiers,
7008 onDrag: this.draggedKnob,
7009 onStart: this.draggedKnob,
7010 onBeforeStart: (function(){
7011 this.isDragging = true;
7012 }).bind(this),
7013 onCancel: function(){
7014 this.isDragging = false;
7015 }.bind(this),
7016 onComplete: function(){
7017 this.isDragging = false;
7018 this.draggedKnob();
7019 this.end();
7020 }.bind(this)
7021 };
7022 if (options.snap) this.setSnap(dragOptions);
7023
7024 this.drag = new Drag(knob, dragOptions);
7025 this.attach();
7026 if (options.initialStep != null) this.set(options.initialStep);
7027 },
7028
7029 attach: function(){
7030 this.element.addEvent('mousedown', this.clickedElement);
7031 if (this.options.wheel) this.element.addEvent('mousewheel', this.scrolledElement);
7032 this.drag.attach();
7033 return this;
7034 },
7035
7036 detach: function(){
7037 this.element.removeEvent('mousedown', this.clickedElement)
7038 .removeEvent('mousewheel', this.scrolledElement);
7039 this.drag.detach();
7040 return this;
7041 },
7042
7043 autosize: function(){
7044 this.setSliderDimensions()
7045 .setKnobPosition(this.toPosition(this.step));
7046 this.drag.options.limit[this.axis] = [-this.options.offset, this.full - this.options.offset];
7047 if (this.options.snap) this.setSnap();
7048 return this;
7049 },
7050
7051 setSnap: function(options){
7052 if (!options) options = this.drag.options;
7053 options.grid = Math.ceil(this.stepWidth);
7054 options.limit[this.axis][1] = this.full;
7055 return this;
7056 },
7057
7058 setKnobPosition: function(position){
7059 if (this.options.snap) position = this.toPosition(this.step);
7060 this.knob.setStyle(this.property, position);
7061 return this;
7062 },
7063
7064 setSliderDimensions: function(){
7065 this.full = this.element.measure(function(){
7066 this.half = this.knob[this.offset] / 2;
7067 return this.element[this.offset] - this.knob[this.offset] + (this.options.offset * 2);
7068 }.bind(this));
7069 return this;
7070 },
7071
7072 set: function(step){
7073 if (!((this.range > 0) ^ (step < this.min))) step = this.min;
7074 if (!((this.range > 0) ^ (step > this.max))) step = this.max;
7075
7076 this.step = Math.round(step);
7077 return this.checkStep()
7078 .fireEvent('tick', this.toPosition(this.step))
7079 .end();
7080 },
7081
7082 setRange: function(range, pos){
7083 this.min = Array.pick([range[0], 0]);
7084 this.max = Array.pick([range[1], this.options.steps]);
7085 this.range = this.max - this.min;
7086 this.steps = this.options.steps || this.full;
7087 this.stepSize = Math.abs(this.range) / this.steps;
7088 this.stepWidth = this.stepSize * this.full / Math.abs(this.range);
7089 if (range) this.set(Array.pick([pos, this.step]).floor(this.min).max(this.max));
7090 return this;
7091 },
7092
7093 clickedElement: function(event){
7094 if (this.isDragging || event.target == this.knob) return;
7095
7096 var dir = this.range < 0 ? -1 : 1,
7097 position = event.page[this.axis] - this.element.getPosition()[this.axis] - this.half;
7098
7099 position = position.limit(-this.options.offset, this.full - this.options.offset);
7100
7101 this.step = Math.round(this.min + dir * this.toStep(position));
7102
7103 this.checkStep()
7104 .fireEvent('tick', position)
7105 .end();
7106 },
7107
7108 scrolledElement: function(event){
7109 var mode = (this.options.mode == 'horizontal') ? (event.wheel < 0) : (event.wheel > 0);
7110 this.set(this.step + (mode ? -1 : 1) * this.stepSize);
7111 event.stop();
7112 },
7113
7114 draggedKnob: function(){
7115 var dir = this.range < 0 ? -1 : 1,
7116 position = this.drag.value.now[this.axis];
7117
7118 position = position.limit(-this.options.offset, this.full -this.options.offset);
7119
7120 this.step = Math.round(this.min + dir * this.toStep(position));
7121 this.checkStep();
7122 },
7123
7124 checkStep: function(){
7125 var step = this.step;
7126 if (this.previousChange != step){
7127 this.previousChange = step;
7128 this.fireEvent('change', step);
7129 }
7130 return this;
7131 },
7132
7133 end: function(){
7134 var step = this.step;
7135 if (this.previousEnd !== step){
7136 this.previousEnd = step;
7137 this.fireEvent('complete', step + '');
7138 }
7139 return this;
7140 },
7141
7142 toStep: function(position){
7143 var step = (position + this.options.offset) * this.stepSize / this.full * this.steps;
7144 return this.options.steps ? Math.round(step -= step % this.stepSize) : step;
7145 },
7146
7147 toPosition: function(step){
7148 return (this.full * Math.abs(this.min - step)) / (this.steps * this.stepSize) - this.options.offset;
7149 }
7150
7151 });
7152
7153
7154
7155
7156
7157
7158
7159
7160
7161
7162
7163
7164
7165
7166
7167
7168
7169
7170
7171
7172
7173
7174
7175
7176
7177 var Sortables = new Class({
7178
7179 Implements: [Events, Options],
7180
7181 options: {
7182
7183
7184
7185 opacity: 1,
7186 clone: false,
7187 revert: false,
7188 handle: false,
7189 dragOptions: {}
7190 },
7191
7192 initialize: function(lists, options){
7193 this.setOptions(options);
7194
7195 this.elements = [];
7196 this.lists = [];
7197 this.idle = true;
7198
7199 this.addLists($$(document.id(lists) || lists));
7200
7201 if (!this.options.clone) this.options.revert = false;
7202 if (this.options.revert) this.effect = new Fx.Morph(null, Object.merge({
7203 duration: 250,
7204 link: 'cancel'
7205 }, this.options.revert));
7206 },
7207
7208 attach: function(){
7209 this.addLists(this.lists);
7210 return this;
7211 },
7212
7213 detach: function(){
7214 this.lists = this.removeLists(this.lists);
7215 return this;
7216 },
7217
7218 addItems: function(){
7219 Array.flatten(arguments).each(function(element){
7220 this.elements.push(element);
7221 var start = element.retrieve('sortables:start', function(event){
7222 this.start.call(this, event, element);
7223 }.bind(this));
7224 (this.options.handle ? element.getElement(this.options.handle) || element : element).addEvent('mousedown', start);
7225 }, this);
7226 return this;
7227 },
7228
7229 addLists: function(){
7230 Array.flatten(arguments).each(function(list){
7231 this.lists.include(list);
7232 this.addItems(list.getChildren());
7233 }, this);
7234 return this;
7235 },
7236
7237 removeItems: function(){
7238 return $$(Array.flatten(arguments).map(function(element){
7239 this.elements.erase(element);
7240 var start = element.retrieve('sortables:start');
7241 (this.options.handle ? element.getElement(this.options.handle) || element : element).removeEvent('mousedown', start);
7242
7243 return element;
7244 }, this));
7245 },
7246
7247 removeLists: function(){
7248 return $$(Array.flatten(arguments).map(function(list){
7249 this.lists.erase(list);
7250 this.removeItems(list.getChildren());
7251
7252 return list;
7253 }, this));
7254 },
7255
7256 getClone: function(event, element){
7257 if (!this.options.clone) return new Element(element.tagName).inject(document.body);
7258 if (typeOf(this.options.clone) == 'function') return this.options.clone.call(this, event, element, this.list);
7259 var clone = element.clone(true).setStyles({
7260 margin: 0,
7261 position: 'absolute',
7262 visibility: 'hidden',
7263 width: element.getStyle('width')
7264 }).addEvent('mousedown', function(event){
7265 element.fireEvent('mousedown', event);
7266 });
7267
7268 if (clone.get('html').test('radio')){
7269 clone.getElements('input[type=radio]').each(function(input, i){
7270 input.set('name', 'clone_' + i);
7271 if (input.get('checked')) element.getElements('input[type=radio]')[i].set('checked', true);
7272 });
7273 }
7274
7275 return clone.inject(this.list).setPosition(element.getPosition(element.getOffsetParent()));
7276 },
7277
7278 getDroppables: function(){
7279 var droppables = this.list.getChildren().erase(this.clone).erase(this.element);
7280 if (!this.options.constrain) droppables.append(this.lists).erase(this.list);
7281 return droppables;
7282 },
7283
7284 insert: function(dragging, element){
7285 var where = 'inside';
7286 if (this.lists.contains(element)){
7287 this.list = element;
7288 this.drag.droppables = this.getDroppables();
7289 } else {
7290 where = this.element.getAllPrevious().contains(element) ? 'before' : 'after';
7291 }
7292 this.element.inject(element, where);
7293 this.fireEvent('sort', [this.element, this.clone]);
7294 },
7295
7296 start: function(event, element){
7297 if (
7298 !this.idle ||
7299 event.rightClick ||
7300 ['button', 'input', 'a', 'textarea'].contains(event.target.get('tag'))
7301 ) return;
7302
7303 this.idle = false;
7304 this.element = element;
7305 this.opacity = element.getStyle('opacity');
7306 this.list = element.getParent();
7307 this.clone = this.getClone(event, element);
7308
7309 this.drag = new Drag.Move(this.clone, Object.merge({
7310
7311 droppables: this.getDroppables()
7312 }, this.options.dragOptions)).addEvents({
7313 onSnap: function(){
7314 event.stop();
7315 this.clone.setStyle('visibility', 'visible');
7316 this.element.setStyle('opacity', this.options.opacity || 0);
7317 this.fireEvent('start', [this.element, this.clone]);
7318 }.bind(this),
7319 onEnter: this.insert.bind(this),
7320 onCancel: this.end.bind(this),
7321 onComplete: this.end.bind(this)
7322 });
7323
7324 this.clone.inject(this.element, 'before');
7325 this.drag.start(event);
7326 },
7327
7328 end: function(){
7329 this.drag.detach();
7330 this.element.setStyle('opacity', this.opacity);
7331 if (this.effect){
7332 var dim = this.element.getStyles('width', 'height'),
7333 clone = this.clone,
7334 pos = clone.computePosition(this.element.getPosition(this.clone.getOffsetParent()));
7335
7336 var destroy = function(){
7337 this.removeEvent('cancel', destroy);
7338 clone.destroy();
7339 };
7340
7341 this.effect.element = clone;
7342 this.effect.start({
7343 top: pos.top,
7344 left: pos.left,
7345 width: dim.width,
7346 height: dim.height,
7347 opacity: 0.25
7348 }).addEvent('cancel', destroy).chain(destroy);
7349 } else {
7350 this.clone.destroy();
7351 }
7352 this.reset();
7353 },
7354
7355 reset: function(){
7356 this.idle = true;
7357 this.fireEvent('complete', this.element);
7358 },
7359
7360 serialize: function(){
7361 var params = Array.link(arguments, {
7362 modifier: Type.isFunction,
7363 index: function(obj){
7364 return obj != null;
7365 }
7366 });
7367 var serial = this.lists.map(function(list){
7368 return list.getChildren().map(params.modifier || function(element){
7369 return element.get('id');
7370 }, this);
7371 }, this);
7372
7373 var index = params.index;
7374 if (this.lists.length == 1) index = 0;
7375 return (index || index === 0) && index >= 0 && index < this.lists.length ? serial[index] : serial;
7376 }
7377
7378 });
7379
7380
7381
7382
7383
7384
7385
7386
7387
7388
7389
7390
7391
7392
7393
7394
7395
7396
7397
7398
7399
7400
7401
7402
7403
7404
7405
7406
7407 Request.JSONP = new Class({
7408
7409 Implements: [Chain, Events, Options],
7410
7411 options: {
7412
7413
7414
7415
7416
7417
7418 onRequest: function(src){
7419 if (this.options.log && window.console && console.log){
7420 console.log('JSONP retrieving script with url:' + src);
7421 }
7422 },
7423 onError: function(src){
7424 if (this.options.log && window.console && console.warn){
7425 console.warn('JSONP '+ src +' will fail in Internet Explorer, which enforces a 2083 bytes length limit on URIs');
7426 }
7427 },
7428 url: '',
7429 callbackKey: 'callback',
7430 injectScript: document.head,
7431 data: '',
7432 link: 'ignore',
7433 timeout: 0,
7434 log: false
7435 },
7436
7437 initialize: function(options){
7438 this.setOptions(options);
7439 },
7440
7441 send: function(options){
7442 if (!Request.prototype.check.call(this, options)) return this;
7443 this.running = true;
7444
7445 var type = typeOf(options);
7446 if (type == 'string' || type == 'element') options = {data: options};
7447 options = Object.merge(this.options, options || {});
7448
7449 var data = options.data;
7450 switch (typeOf(data)){
7451 case 'element': data = document.id(data).toQueryString(); break;
7452 case 'object': case 'hash': data = Object.toQueryString(data);
7453 }
7454
7455 var index = this.index = Request.JSONP.counter++;
7456
7457 var src = options.url +
7458 (options.url.test('\\?') ? '&' :'?') +
7459 (options.callbackKey) +
7460 '=Request.JSONP.request_map.request_'+ index +
7461 (data ? '&' + data : '');
7462
7463 if (src.length > 2083) this.fireEvent('error', src);
7464
7465 Request.JSONP.request_map['request_' + index] = function(){
7466 this.success(arguments, index);
7467 }.bind(this);
7468
7469 var script = this.getScript(src).inject(options.injectScript);
7470 this.fireEvent('request', [src, script]);
7471
7472 if (options.timeout) this.timeout.delay(options.timeout, this);
7473
7474 return this;
7475 },
7476
7477 getScript: function(src){
7478 if (!this.script) this.script = new Element('script', {
7479 type: 'text/javascript',
7480 async: true,
7481 src: src
7482 });
7483 return this.script;
7484 },
7485
7486 success: function(args, index){
7487 if (!this.running) return;
7488 this.clear()
7489 .fireEvent('complete', args).fireEvent('success', args)
7490 .callChain();
7491 },
7492
7493 cancel: function(){
7494 if (this.running) this.clear().fireEvent('cancel');
7495 return this;
7496 },
7497
7498 isRunning: function(){
7499 return !!this.running;
7500 },
7501
7502 clear: function(){
7503 this.running = false;
7504 if (this.script){
7505 this.script.destroy();
7506 this.script = null;
7507 }
7508 return this;
7509 },
7510
7511 timeout: function(){
7512 if (this.running){
7513 this.running = false;
7514 this.fireEvent('timeout', [this.script.get('src'), this.script]).fireEvent('failure').cancel();
7515 }
7516 return this;
7517 }
7518
7519 });
7520
7521 Request.JSONP.counter = 0;
7522 Request.JSONP.request_map = {};
7523
7524
7525
7526
7527
7528
7529
7530
7531
7532
7533
7534
7535
7536
7537
7538
7539
7540
7541
7542
7543
7544
7545
7546
7547
7548
7549 Request.Queue = new Class({
7550
7551 Implements: [Options, Events],
7552
7553 Binds: ['attach', 'request', 'complete', 'cancel', 'success', 'failure', 'exception'],
7554
7555 options: {
7556
7557
7558
7559
7560
7561
7562
7563
7564 stopOnFailure: true,
7565 autoAdvance: true,
7566 concurrent: 1,
7567 requests: {}
7568 },
7569
7570 initialize: function(options){
7571 var requests;
7572 if (options){
7573 requests = options.requests;
7574 delete options.requests;
7575 }
7576 this.setOptions(options);
7577 this.requests = {};
7578 this.queue = [];
7579 this.reqBinders = {};
7580
7581 if (requests) this.addRequests(requests);
7582 },
7583
7584 addRequest: function(name, request){
7585 this.requests[name] = request;
7586 this.attach(name, request);
7587 return this;
7588 },
7589
7590 addRequests: function(obj){
7591 Object.each(obj, function(req, name){
7592 this.addRequest(name, req);
7593 }, this);
7594 return this;
7595 },
7596
7597 getName: function(req){
7598 return Object.keyOf(this.requests, req);
7599 },
7600
7601 attach: function(name, req){
7602 if (req._groupSend) return this;
7603 ['request', 'complete', 'cancel', 'success', 'failure', 'exception'].each(function(evt){
7604 if (!this.reqBinders[name]) this.reqBinders[name] = {};
7605 this.reqBinders[name][evt] = function(){
7606 this['on' + evt.capitalize()].apply(this, [name, req].append(arguments));
7607 }.bind(this);
7608 req.addEvent(evt, this.reqBinders[name][evt]);
7609 }, this);
7610 req._groupSend = req.send;
7611 req.send = function(options){
7612 this.send(name, options);
7613 return req;
7614 }.bind(this);
7615 return this;
7616 },
7617
7618 removeRequest: function(req){
7619 var name = typeOf(req) == 'object' ? this.getName(req) : req;
7620 if (!name && typeOf(name) != 'string') return this;
7621 req = this.requests[name];
7622 if (!req) return this;
7623 ['request', 'complete', 'cancel', 'success', 'failure', 'exception'].each(function(evt){
7624 req.removeEvent(evt, this.reqBinders[name][evt]);
7625 }, this);
7626 req.send = req._groupSend;
7627 delete req._groupSend;
7628 return this;
7629 },
7630
7631 getRunning: function(){
7632 return Object.filter(this.requests, function(r){
7633 return r.running;
7634 });
7635 },
7636
7637 isRunning: function(){
7638 return !!(Object.keys(this.getRunning()).length);
7639 },
7640
7641 send: function(name, options){
7642 var q = function(){
7643 this.requests[name]._groupSend(options);
7644 this.queue.erase(q);
7645 }.bind(this);
7646
7647 q.name = name;
7648 if (Object.keys(this.getRunning()).length >= this.options.concurrent || (this.error && this.options.stopOnFailure)) this.queue.push(q);
7649 else q();
7650 return this;
7651 },
7652
7653 hasNext: function(name){
7654 return (!name) ? !!this.queue.length : !!this.queue.filter(function(q){ return q.name == name; }).length;
7655 },
7656
7657 resume: function(){
7658 this.error = false;
7659 (this.options.concurrent - Object.keys(this.getRunning()).length).times(this.runNext, this);
7660 return this;
7661 },
7662
7663 runNext: function(name){
7664 if (!this.queue.length) return this;
7665 if (!name){
7666 this.queue[0]();
7667 } else {
7668 var found;
7669 this.queue.each(function(q){
7670 if (!found && q.name == name){
7671 found = true;
7672 q();
7673 }
7674 });
7675 }
7676 return this;
7677 },
7678
7679 runAll: function(){
7680 this.queue.each(function(q){
7681 q();
7682 });
7683 return this;
7684 },
7685
7686 clear: function(name){
7687 if (!name){
7688 this.queue.empty();
7689 } else {
7690 this.queue = this.queue.map(function(q){
7691 if (q.name != name) return q;
7692 else return false;
7693 }).filter(function(q){
7694 return q;
7695 });
7696 }
7697 return this;
7698 },
7699
7700 cancel: function(name){
7701 this.requests[name].cancel();
7702 return this;
7703 },
7704
7705 onRequest: function(){
7706 this.fireEvent('request', arguments);
7707 },
7708
7709 onComplete: function(){
7710 this.fireEvent('complete', arguments);
7711 if (!this.queue.length) this.fireEvent('end');
7712 },
7713
7714 onCancel: function(){
7715 if (this.options.autoAdvance && !this.error) this.runNext();
7716 this.fireEvent('cancel', arguments);
7717 },
7718
7719 onSuccess: function(){
7720 if (this.options.autoAdvance && !this.error) this.runNext();
7721 this.fireEvent('success', arguments);
7722 },
7723
7724 onFailure: function(){
7725 this.error = true;
7726 if (!this.options.stopOnFailure && this.options.autoAdvance) this.runNext();
7727 this.fireEvent('failure', arguments);
7728 },
7729
7730 onException: function(){
7731 this.error = true;
7732 if (!this.options.stopOnFailure && this.options.autoAdvance) this.runNext();
7733 this.fireEvent('exception', arguments);
7734 }
7735
7736 });
7737
7738
7739
7740
7741
7742
7743
7744
7745
7746
7747
7748
7749
7750
7751
7752
7753
7754
7755
7756
7757
7758
7759
7760
7761
7762 Request.implement({
7763
7764 options: {
7765 initialDelay: 5000,
7766 delay: 5000,
7767 limit: 60000
7768 },
7769
7770 startTimer: function(data){
7771 var fn = function(){
7772 if (!this.running) this.send({data: data});
7773 };
7774 this.lastDelay = this.options.initialDelay;
7775 this.timer = fn.delay(this.lastDelay, this);
7776 this.completeCheck = function(response){
7777 clearTimeout(this.timer);
7778 this.lastDelay = (response) ? this.options.delay : (this.lastDelay + this.options.delay).min(this.options.limit);
7779 this.timer = fn.delay(this.lastDelay, this);
7780 };
7781 return this.addEvent('complete', this.completeCheck);
7782 },
7783
7784 stopTimer: function(){
7785 clearTimeout(this.timer);
7786 return this.removeEvent('complete', this.completeCheck);
7787 }
7788
7789 });
7790
7791
7792
7793
7794
7795
7796
7797
7798
7799
7800
7801
7802
7803
7804
7805
7806
7807
7808
7809
7810
7811
7812
7813
7814
7815 var Asset = {
7816
7817 javascript: function(source, properties){
7818 if (!properties) properties = {};
7819
7820 var script = new Element('script', {src: source, type: 'text/javascript'}),
7821 doc = properties.document || document,
7822 load = properties.onload || properties.onLoad;
7823
7824 delete properties.onload;
7825 delete properties.onLoad;
7826 delete properties.document;
7827
7828 if (load){
7829 if (typeof script.onreadystatechange != 'undefined'){
7830 script.addEvent('readystatechange', function(){
7831 if (['loaded', 'complete'].contains(this.readyState)) load.call(this);
7832 });
7833 } else {
7834 script.addEvent('load', load);
7835 }
7836 }
7837
7838 return script.set(properties).inject(doc.head);
7839 },
7840
7841 css: function(source, properties){
7842 if (!properties) properties = {};
7843
7844 var link = new Element('link', {
7845 rel: 'stylesheet',
7846 media: 'screen',
7847 type: 'text/css',
7848 href: source
7849 });
7850
7851 var load = properties.onload || properties.onLoad,
7852 doc = properties.document || document;
7853
7854 delete properties.onload;
7855 delete properties.onLoad;
7856 delete properties.document;
7857
7858 if (load) link.addEvent('load', load);
7859 return link.set(properties).inject(doc.head);
7860 },
7861
7862 image: function(source, properties){
7863 if (!properties) properties = {};
7864
7865 var image = new Image(),
7866 element = document.id(image) || new Element('img');
7867
7868 ['load', 'abort', 'error'].each(function(name){
7869 var type = 'on' + name,
7870 cap = 'on' + name.capitalize(),
7871 event = properties[type] || properties[cap] || function(){};
7872
7873 delete properties[cap];
7874 delete properties[type];
7875
7876 image[type] = function(){
7877 if (!image) return;
7878 if (!element.parentNode){
7879 element.width = image.width;
7880 element.height = image.height;
7881 }
7882 image = image.onload = image.onabort = image.onerror = null;
7883 event.delay(1, element, element);
7884 element.fireEvent(name, element, 1);
7885 };
7886 });
7887
7888 image.src = element.src = source;
7889 if (image && image.complete) image.onload.delay(1);
7890 return element.set(properties);
7891 },
7892
7893 images: function(sources, options){
7894 sources = Array.from(sources);
7895
7896 var fn = function(){},
7897 counter = 0;
7898
7899 options = Object.merge({
7900 onComplete: fn,
7901 onProgress: fn,
7902 onError: fn,
7903 properties: {}
7904 }, options);
7905
7906 return new Elements(sources.map(function(source, index){
7907 return Asset.image(source, Object.append(options.properties, {
7908 onload: function(){
7909 counter++;
7910 options.onProgress.call(this, counter, index, source);
7911 if (counter == sources.length) options.onComplete();
7912 },
7913 onerror: function(){
7914 counter++;
7915 options.onError.call(this, counter, index, source);
7916 if (counter == sources.length) options.onComplete();
7917 }
7918 }));
7919 }));
7920 }
7921
7922 };
7923
7924
7925
7926
7927
7928
7929
7930
7931
7932
7933
7934
7935
7936
7937
7938
7939
7940
7941
7942
7943
7944
7945
7946
7947
7948
7949
7950
7951
7952 (function(){
7953
7954 var Color = this.Color = new Type('Color', function(color, type){
7955 if (arguments.length >= 3){
7956 type = 'rgb'; color = Array.slice(arguments, 0, 3);
7957 } else if (typeof color == 'string'){
7958 if (color.match(/rgb/)) color = color.rgbToHex().hexToRgb(true);
7959 else if (color.match(/hsb/)) color = color.hsbToRgb();
7960 else color = color.hexToRgb(true);
7961 }
7962 type = type || 'rgb';
7963 switch (type){
7964 case 'hsb':
7965 var old = color;
7966 color = color.hsbToRgb();
7967 color.hsb = old;
7968 break;
7969 case 'hex': color = color.hexToRgb(true); break;
7970 }
7971 color.rgb = color.slice(0, 3);
7972 color.hsb = color.hsb || color.rgbToHsb();
7973 color.hex = color.rgbToHex();
7974 return Object.append(color, this);
7975 });
7976
7977 Color.implement({
7978
7979 mix: function(){
7980 var colors = Array.slice(arguments);
7981 var alpha = (typeOf(colors.getLast()) == 'number') ? colors.pop() : 50;
7982 var rgb = this.slice();
7983 colors.each(function(color){
7984 color = new Color(color);
7985 for (var i = 0; i < 3; i++) rgb[i] = Math.round((rgb[i] / 100 * (100 - alpha)) + (color[i] / 100 * alpha));
7986 });
7987 return new Color(rgb, 'rgb');
7988 },
7989
7990 invert: function(){
7991 return new Color(this.map(function(value){
7992 return 255 - value;
7993 }));
7994 },
7995
7996 setHue: function(value){
7997 return new Color([value, this.hsb[1], this.hsb[2]], 'hsb');
7998 },
7999
8000 setSaturation: function(percent){
8001 return new Color([this.hsb[0], percent, this.hsb[2]], 'hsb');
8002 },
8003
8004 setBrightness: function(percent){
8005 return new Color([this.hsb[0], this.hsb[1], percent], 'hsb');
8006 }
8007
8008 });
8009
8010 this.$RGB = function(r, g, b){
8011 return new Color([r, g, b], 'rgb');
8012 };
8013
8014 this.$HSB = function(h, s, b){
8015 return new Color([h, s, b], 'hsb');
8016 };
8017
8018 this.$HEX = function(hex){
8019 return new Color(hex, 'hex');
8020 };
8021
8022 Array.implement({
8023
8024 rgbToHsb: function(){
8025 var red = this[0],
8026 green = this[1],
8027 blue = this[2],
8028 hue = 0;
8029 var max = Math.max(red, green, blue),
8030 min = Math.min(red, green, blue);
8031 var delta = max - min;
8032 var brightness = max / 255,
8033 saturation = (max != 0) ? delta / max : 0;
8034 if (saturation != 0){
8035 var rr = (max - red) / delta;
8036 var gr = (max - green) / delta;
8037 var br = (max - blue) / delta;
8038 if (red == max) hue = br - gr;
8039 else if (green == max) hue = 2 + rr - br;
8040 else hue = 4 + gr - rr;
8041 hue /= 6;
8042 if (hue < 0) hue++;
8043 }
8044 return [Math.round(hue * 360), Math.round(saturation * 100), Math.round(brightness * 100)];
8045 },
8046
8047 hsbToRgb: function(){
8048 var br = Math.round(this[2] / 100 * 255);
8049 if (this[1] == 0){
8050 return [br, br, br];
8051 } else {
8052 var hue = this[0] % 360;
8053 var f = hue % 60;
8054 var p = Math.round((this[2] * (100 - this[1])) / 10000 * 255);
8055 var q = Math.round((this[2] * (6000 - this[1] * f)) / 600000 * 255);
8056 var t = Math.round((this[2] * (6000 - this[1] * (60 - f))) / 600000 * 255);
8057 switch (Math.floor(hue / 60)){
8058 case 0: return [br, t, p];
8059 case 1: return [q, br, p];
8060 case 2: return [p, br, t];
8061 case 3: return [p, q, br];
8062 case 4: return [t, p, br];
8063 case 5: return [br, p, q];
8064 }
8065 }
8066 return false;
8067 }
8068
8069 });
8070
8071 String.implement({
8072
8073 rgbToHsb: function(){
8074 var rgb = this.match(/\d{1,3}/g);
8075 return (rgb) ? rgb.rgbToHsb() : null;
8076 },
8077
8078 hsbToRgb: function(){
8079 var hsb = this.match(/\d{1,3}/g);
8080 return (hsb) ? hsb.hsbToRgb() : null;
8081 }
8082
8083 });
8084
8085 })();
8086
8087
8088
8089
8090
8091
8092
8093
8094
8095
8096
8097
8098
8099
8100
8101
8102
8103
8104
8105
8106
8107
8108
8109
8110
8111
8112 (function(){
8113
8114 this.Group = new Class({
8115
8116 initialize: function(){
8117 this.instances = Array.flatten(arguments);
8118 },
8119
8120 addEvent: function(type, fn){
8121 var instances = this.instances,
8122 len = instances.length,
8123 togo = len,
8124 args = new Array(len),
8125 self = this;
8126
8127 instances.each(function(instance, i){
8128 instance.addEvent(type, function(){
8129 if (!args[i]) togo--;
8130 args[i] = arguments;
8131 if (!togo){
8132 fn.call(self, instances, instance, args);
8133 togo = len;
8134 args = new Array(len);
8135 }
8136 });
8137 });
8138 }
8139
8140 });
8141
8142 })();
8143
8144
8145
8146
8147
8148
8149
8150
8151
8152
8153
8154
8155
8156
8157
8158
8159
8160
8161
8162
8163
8164
8165
8166
8167
8168
8169
8170
8171 Hash.Cookie = new Class({
8172
8173 Extends: Cookie,
8174
8175 options: {
8176 autoSave: true
8177 },
8178
8179 initialize: function(name, options){
8180 this.parent(name, options);
8181 this.load();
8182 },
8183
8184 save: function(){
8185 var value = JSON.encode(this.hash);
8186 if (!value || value.length > 4096) return false;
8187 if (value == '{}') this.dispose();
8188 else this.write(value);
8189 return true;
8190 },
8191
8192 load: function(){
8193 this.hash = new Hash(JSON.decode(this.read(), true));
8194 return this;
8195 }
8196
8197 });
8198
8199 Hash.each(Hash.prototype, function(method, name){
8200 if (typeof method == 'function') Hash.Cookie.implement(name, function(){
8201 var value = method.apply(this.hash, arguments);
8202 if (this.options.autoSave) this.save();
8203 return value;
8204 });
8205 });
8206
8207
8208
8209
8210
8211
8212
8213
8214
8215
8216
8217
8218
8219
8220 (function(){
8221
8222 var Table = this.Table = function(){
8223
8224 this.length = 0;
8225 var keys = [],
8226 values = [];
8227
8228 this.set = function(key, value){
8229 var index = keys.indexOf(key);
8230 if (index == -1){
8231 var length = keys.length;
8232 keys[length] = key;
8233 values[length] = value;
8234 this.length++;
8235 } else {
8236 values[index] = value;
8237 }
8238 return this;
8239 };
8240
8241 this.get = function(key){
8242 var index = keys.indexOf(key);
8243 return (index == -1) ? null : values[index];
8244 };
8245
8246 this.erase = function(key){
8247 var index = keys.indexOf(key);
8248 if (index != -1){
8249 this.length--;
8250 keys.splice(index, 1);
8251 return values.splice(index, 1)[0];
8252 }
8253 return null;
8254 };
8255
8256 this.each = this.forEach = function(fn, bind){
8257 for (var i = 0, l = this.length; i < l; i++) fn.call(bind, keys[i], values[i], this);
8258 };
8259
8260 };
8261
8262 if (this.Type) new Type('Table', Table);
8263
8264 })();
8265
8266
8267
8268
8269
8270
8271
8272
8273
8274
8275
8276
8277
8278
8279
8280
8281
8282
8283
8284
8285
8286
8287
8288
8289
8290
8291 var HtmlTable = new Class({
8292
8293 Implements: [Options, Events, Class.Occlude],
8294
8295 options: {
8296 properties: {
8297 cellpadding: 0,
8298 cellspacing: 0,
8299 border: 0
8300 },
8301 rows: [],
8302 headers: [],
8303 footers: []
8304 },
8305
8306 property: 'HtmlTable',
8307
8308 initialize: function(){
8309 var params = Array.link(arguments, {options: Type.isObject, table: Type.isElement, id: Type.isString});
8310 this.setOptions(params.options);
8311 if (!params.table && params.id) params.table = document.id(params.id);
8312 this.element = params.table || new Element('table', this.options.properties);
8313 if (this.occlude()) return this.occluded;
8314 this.build();
8315 },
8316
8317 build: function(){
8318 this.element.store('HtmlTable', this);
8319
8320 this.body = document.id(this.element.tBodies[0]) || new Element('tbody').inject(this.element);
8321 $$(this.body.rows);
8322
8323 if (this.options.headers.length) this.setHeaders(this.options.headers);
8324 else this.thead = document.id(this.element.tHead);
8325
8326 if (this.thead) this.head = this.getHead();
8327 if (this.options.footers.length) this.setFooters(this.options.footers);
8328
8329 this.tfoot = document.id(this.element.tFoot);
8330 if (this.tfoot) this.foot = document.id(this.tfoot.rows[0]);
8331
8332 this.options.rows.each(function(row){
8333 this.push(row);
8334 }, this);
8335 },
8336
8337 toElement: function(){
8338 return this.element;
8339 },
8340
8341 empty: function(){
8342 this.body.empty();
8343 return this;
8344 },
8345
8346 set: function(what, items){
8347 var target = (what == 'headers') ? 'tHead' : 'tFoot',
8348 lower = target.toLowerCase();
8349
8350 this[lower] = (document.id(this.element[target]) || new Element(lower).inject(this.element, 'top')).empty();
8351 var data = this.push(items, {}, this[lower], what == 'headers' ? 'th' : 'td');
8352
8353 if (what == 'headers') this.head = this.getHead();
8354 else this.foot = this.getHead();
8355
8356 return data;
8357 },
8358
8359 getHead: function(){
8360 var rows = this.thead.rows;
8361 return rows.length > 1 ? $$(rows) : rows.length ? document.id(rows[0]) : false;
8362 },
8363
8364 setHeaders: function(headers){
8365 this.set('headers', headers);
8366 return this;
8367 },
8368
8369 setFooters: function(footers){
8370 this.set('footers', footers);
8371 return this;
8372 },
8373
8374 update: function(tr, row, tag){
8375 var tds = tr.getChildren(tag || 'td'), last = tds.length - 1;
8376
8377 row.each(function(data, index){
8378 var td = tds[index] || new Element(tag || 'td').inject(tr),
8379 content = (data ? data.content : '') || data,
8380 type = typeOf(content);
8381
8382 if (data && data.properties) td.set(data.properties);
8383 if (/(element(s?)|array|collection)/.test(type)) td.empty().adopt(content);
8384 else td.set('html', content);
8385
8386 if (index > last) tds.push(td);
8387 else tds[index] = td;
8388 });
8389
8390 return {
8391 tr: tr,
8392 tds: tds
8393 };
8394 },
8395
8396 push: function(row, rowProperties, target, tag, where){
8397 if (typeOf(row) == 'element' && row.get('tag') == 'tr'){
8398 row.inject(target || this.body, where);
8399 return {
8400 tr: row,
8401 tds: row.getChildren('td')
8402 };
8403 }
8404 return this.update(new Element('tr', rowProperties).inject(target || this.body, where), row, tag);
8405 },
8406
8407 pushMany: function(rows, rowProperties, target, tag, where){
8408 return rows.map(function(row){
8409 return this.push(row, rowProperties, target, tag, where);
8410 }, this);
8411 }
8412
8413 });
8414
8415
8416 ['adopt', 'inject', 'wraps', 'grab', 'replaces', 'dispose'].each(function(method){
8417 HtmlTable.implement(method, function(){
8418 this.element[method].apply(this.element, arguments);
8419 return this;
8420 });
8421 });
8422
8423
8424
8425
8426
8427
8428
8429
8430
8431
8432
8433
8434
8435
8436
8437
8438
8439
8440
8441
8442
8443
8444
8445
8446
8447
8448
8449
8450
8451 HtmlTable = Class.refactor(HtmlTable, {
8452
8453 options: {
8454 classZebra: 'table-tr-odd',
8455 zebra: true,
8456 zebraOnlyVisibleRows: true
8457 },
8458
8459 initialize: function(){
8460 this.previous.apply(this, arguments);
8461 if (this.occluded) return this.occluded;
8462 if (this.options.zebra) this.updateZebras();
8463 },
8464
8465 updateZebras: function(){
8466 var index = 0;
8467 Array.each(this.body.rows, function(row){
8468 if (!this.options.zebraOnlyVisibleRows || row.isDisplayed()){
8469 this.zebra(row, index++);
8470 }
8471 }, this);
8472 },
8473
8474 setRowStyle: function(row, i){
8475 if (this.previous) this.previous(row, i);
8476 this.zebra(row, i);
8477 },
8478
8479 zebra: function(row, i){
8480 return row[((i % 2) ? 'remove' : 'add')+'Class'](this.options.classZebra);
8481 },
8482
8483 push: function(){
8484 var pushed = this.previous.apply(this, arguments);
8485 if (this.options.zebra) this.updateZebras();
8486 return pushed;
8487 }
8488
8489 });
8490
8491
8492
8493
8494
8495
8496
8497
8498
8499
8500
8501
8502
8503
8504
8505
8506
8507
8508
8509
8510
8511
8512
8513
8514
8515
8516
8517
8518
8519
8520
8521 HtmlTable = Class.refactor(HtmlTable, {
8522
8523 options: {
8524
8525 sortIndex: 0,
8526 sortReverse: false,
8527 parsers: [],
8528 defaultParser: 'string',
8529 classSortable: 'table-sortable',
8530 classHeadSort: 'table-th-sort',
8531 classHeadSortRev: 'table-th-sort-rev',
8532 classNoSort: 'table-th-nosort',
8533 classGroupHead: 'table-tr-group-head',
8534 classGroup: 'table-tr-group',
8535 classCellSort: 'table-td-sort',
8536 classSortSpan: 'table-th-sort-span',
8537 sortable: false,
8538 thSelector: 'th'
8539 },
8540
8541 initialize: function (){
8542 this.previous.apply(this, arguments);
8543 if (this.occluded) return this.occluded;
8544 this.sorted = {index: null, dir: 1};
8545 if (!this.bound) this.bound = {};
8546 this.bound.headClick = this.headClick.bind(this);
8547 this.sortSpans = new Elements();
8548 if (this.options.sortable){
8549 this.enableSort();
8550 if (this.options.sortIndex != null) this.sort(this.options.sortIndex, this.options.sortReverse);
8551 }
8552 },
8553
8554 attachSorts: function(attach){
8555 this.detachSorts();
8556 if (attach !== false) this.element.addEvent('click:relay(' + this.options.thSelector + ')', this.bound.headClick);
8557 },
8558
8559 detachSorts: function(){
8560 this.element.removeEvents('click:relay(' + this.options.thSelector + ')');
8561 },
8562
8563 setHeaders: function(){
8564 this.previous.apply(this, arguments);
8565 if (this.sortEnabled) this.setParsers();
8566 },
8567
8568 setParsers: function(){
8569 this.parsers = this.detectParsers();
8570 },
8571
8572 detectParsers: function(){
8573 return this.head && this.head.getElements(this.options.thSelector).flatten().map(this.detectParser, this);
8574 },
8575
8576 detectParser: function(cell, index){
8577 if (cell.hasClass(this.options.classNoSort) || cell.retrieve('htmltable-parser')) return cell.retrieve('htmltable-parser');
8578 var thDiv = new Element('div');
8579 thDiv.adopt(cell.childNodes).inject(cell);
8580 var sortSpan = new Element('span', {'class': this.options.classSortSpan}).inject(thDiv, 'top');
8581 this.sortSpans.push(sortSpan);
8582 var parser = this.options.parsers[index],
8583 rows = this.body.rows,
8584 cancel;
8585 switch (typeOf(parser)){
8586 case 'function': parser = {convert: parser}; cancel = true; break;
8587 case 'string': parser = parser; cancel = true; break;
8588 }
8589 if (!cancel){
8590 HtmlTable.ParserPriority.some(function(parserName){
8591 var current = HtmlTable.Parsers[parserName],
8592 match = current.match;
8593 if (!match) return false;
8594 for (var i = 0, j = rows.length; i < j; i++){
8595 var cell = document.id(rows[i].cells[index]),
8596 text = cell ? cell.get('html').clean() : '';
8597 if (text && match.test(text)){
8598 parser = current;
8599 return true;
8600 }
8601 }
8602 });
8603 }
8604 if (!parser) parser = this.options.defaultParser;
8605 cell.store('htmltable-parser', parser);
8606 return parser;
8607 },
8608
8609 headClick: function(event, el){
8610 if (!this.head || el.hasClass(this.options.classNoSort)) return;
8611 return this.sort(Array.indexOf(this.head.getElements(this.options.thSelector).flatten(), el) % this.body.rows[0].cells.length);
8612 },
8613
8614 serialize: function(){
8615 var previousSerialization = this.previous.apply(this, arguments) || {};
8616 if (this.options.sortable){
8617 previousSerialization.sortIndex = this.sorted.index;
8618 previousSerialization.sortReverse = this.sorted.reverse;
8619 }
8620 return previousSerialization;
8621 },
8622
8623 restore: function(tableState){
8624 if(this.options.sortable && tableState.sortIndex){
8625 this.sort(tableState.sortIndex, tableState.sortReverse);
8626 }
8627 this.previous.apply(this, arguments);
8628 },
8629
8630 setSortedState: function(index, reverse){
8631 if (reverse != null) this.sorted.reverse = reverse;
8632 else if (this.sorted.index == index) this.sorted.reverse = !this.sorted.reverse;
8633 else this.sorted.reverse = this.sorted.index == null;
8634
8635 if (index != null) this.sorted.index = index;
8636 },
8637
8638 setHeadSort: function(sorted){
8639 var head = $$(!this.head.length ? this.head.cells[this.sorted.index] : this.head.map(function(row){
8640 return row.getElements(this.options.thSelector)[this.sorted.index];
8641 }, this).clean());
8642 if (!head.length) return;
8643 if (sorted){
8644 head.addClass(this.options.classHeadSort);
8645 if (this.sorted.reverse) head.addClass(this.options.classHeadSortRev);
8646 else head.removeClass(this.options.classHeadSortRev);
8647 } else {
8648 head.removeClass(this.options.classHeadSort).removeClass(this.options.classHeadSortRev);
8649 }
8650 },
8651
8652 setRowSort: function(data, pre){
8653 var count = data.length,
8654 body = this.body,
8655 group,
8656 rowIndex;
8657
8658 while (count){
8659 var item = data[--count],
8660 position = item.position,
8661 row = body.rows[position];
8662
8663 if (row.disabled) continue;
8664 if (!pre){
8665 group = this.setGroupSort(group, row, item);
8666 this.setRowStyle(row, count);
8667 }
8668 body.appendChild(row);
8669
8670 for (rowIndex = 0; rowIndex < count; rowIndex++){
8671 if (data[rowIndex].position > position) data[rowIndex].position--;
8672 }
8673 }
8674 },
8675
8676 setRowStyle: function(row, i){
8677 this.previous(row, i);
8678 row.cells[this.sorted.index].addClass(this.options.classCellSort);
8679 },
8680
8681 setGroupSort: function(group, row, item){
8682 if (group == item.value) row.removeClass(this.options.classGroupHead).addClass(this.options.classGroup);
8683 else row.removeClass(this.options.classGroup).addClass(this.options.classGroupHead);
8684 return item.value;
8685 },
8686
8687 getParser: function(){
8688 var parser = this.parsers[this.sorted.index];
8689 return typeOf(parser) == 'string' ? HtmlTable.Parsers[parser] : parser;
8690 },
8691
8692 sort: function(index, reverse, pre){
8693 if (!this.head) return;
8694
8695 if (!pre){
8696 this.clearSort();
8697 this.setSortedState(index, reverse);
8698 this.setHeadSort(true);
8699 }
8700
8701 var parser = this.getParser();
8702 if (!parser) return;
8703
8704 var rel;
8705 if (!Browser.ie){
8706 rel = this.body.getParent();
8707 this.body.dispose();
8708 }
8709
8710 var data = this.parseData(parser).sort(function(a, b){
8711 if (a.value === b.value) return 0;
8712 return a.value > b.value ? 1 : -1;
8713 });
8714
8715 if (this.sorted.reverse == (parser == HtmlTable.Parsers['input-checked'])) data.reverse(true);
8716 this.setRowSort(data, pre);
8717
8718 if (rel) rel.grab(this.body);
8719 this.fireEvent('stateChanged');
8720 return this.fireEvent('sort', [this.body, this.sorted.index]);
8721 },
8722
8723 parseData: function(parser){
8724 return Array.map(this.body.rows, function(row, i){
8725 var value = parser.convert.call(document.id(row.cells[this.sorted.index]));
8726 return {
8727 position: i,
8728 value: value
8729 };
8730 }, this);
8731 },
8732
8733 clearSort: function(){
8734 this.setHeadSort(false);
8735 this.body.getElements('td').removeClass(this.options.classCellSort);
8736 },
8737
8738 reSort: function(){
8739 if (this.sortEnabled) this.sort.call(this, this.sorted.index, this.sorted.reverse);
8740 return this;
8741 },
8742
8743 enableSort: function(){
8744 this.element.addClass(this.options.classSortable);
8745 this.attachSorts(true);
8746 this.setParsers();
8747 this.sortEnabled = true;
8748 return this;
8749 },
8750
8751 disableSort: function(){
8752 this.element.removeClass(this.options.classSortable);
8753 this.attachSorts(false);
8754 this.sortSpans.each(function(span){
8755 span.destroy();
8756 });
8757 this.sortSpans.empty();
8758 this.sortEnabled = false;
8759 return this;
8760 }
8761
8762 });
8763
8764 HtmlTable.ParserPriority = ['date', 'input-checked', 'input-value', 'float', 'number'];
8765
8766 HtmlTable.Parsers = {
8767
8768 'date': {
8769 match: /^\d{2}[-\/ ]\d{2}[-\/ ]\d{2,4}$/,
8770 convert: function(){
8771 var d = Date.parse(this.get('text').stripTags());
8772 return (typeOf(d) == 'date') ? d.format('db') : '';
8773 },
8774 type: 'date'
8775 },
8776 'input-checked': {
8777 match: / type="(radio|checkbox)" /,
8778 convert: function(){
8779 return this.getElement('input').checked;
8780 }
8781 },
8782 'input-value': {
8783 match: /<input/,
8784 convert: function(){
8785 return this.getElement('input').value;
8786 }
8787 },
8788 'number': {
8789 match: /^\d+[^\d.,]*$/,
8790 convert: function(){
8791 return this.get('text').stripTags().toInt();
8792 },
8793 number: true
8794 },
8795 'numberLax': {
8796 match: /^[^\d]+\d+$/,
8797 convert: function(){
8798 return this.get('text').replace(/[^-?^0-9]/, '').stripTags().toInt();
8799 },
8800 number: true
8801 },
8802 'float': {
8803 match: /^[\d]+\.[\d]+/,
8804 convert: function(){
8805 return this.get('text').replace(/[^-?^\d.]/, '').stripTags().toFloat();
8806 },
8807 number: true
8808 },
8809 'floatLax': {
8810 match: /^[^\d]+[\d]+\.[\d]+$/,
8811 convert: function(){
8812 return this.get('text').replace(/[^-?^\d.]/, '').stripTags();
8813 },
8814 number: true
8815 },
8816 'string': {
8817 match: null,
8818 convert: function(){
8819 return this.get('text').stripTags().toLowerCase();
8820 }
8821 },
8822 'title': {
8823 match: null,
8824 convert: function(){
8825 return this.title;
8826 }
8827 }
8828
8829 };
8830
8831
8832
8833 HtmlTable.defineParsers = function(parsers){
8834 HtmlTable.Parsers = Object.append(HtmlTable.Parsers, parsers);
8835 for (var parser in parsers){
8836 HtmlTable.ParserPriority.unshift(parser);
8837 }
8838 };
8839
8840
8841
8842
8843
8844
8845
8846
8847
8848
8849
8850
8851
8852
8853
8854
8855
8856
8857
8858
8859
8860
8861
8862
8863
8864
8865
8866
8867
8868 (function(){
8869
8870 var Keyboard = this.Keyboard = new Class({
8871
8872 Extends: Events,
8873
8874 Implements: [Options],
8875
8876 options: {
8877
8878
8879 defaultEventType: 'keydown',
8880 active: false,
8881 manager: null,
8882 events: {},
8883 nonParsedEvents: ['activate', 'deactivate', 'onactivate', 'ondeactivate', 'changed', 'onchanged']
8884 },
8885
8886 initialize: function(options){
8887 if (options && options.manager){
8888 this._manager = options.manager;
8889 delete options.manager;
8890 }
8891 this.setOptions(options);
8892 this._setup();
8893 },
8894
8895 addEvent: function(type, fn, internal){
8896 return this.parent(Keyboard.parse(type, this.options.defaultEventType, this.options.nonParsedEvents), fn, internal);
8897 },
8898
8899 removeEvent: function(type, fn){
8900 return this.parent(Keyboard.parse(type, this.options.defaultEventType, this.options.nonParsedEvents), fn);
8901 },
8902
8903 toggleActive: function(){
8904 return this[this.isActive() ? 'deactivate' : 'activate']();
8905 },
8906
8907 activate: function(instance){
8908 if (instance){
8909 if (instance.isActive()) return this;
8910
8911 if (this._activeKB && instance != this._activeKB){
8912 this.previous = this._activeKB;
8913 this.previous.fireEvent('deactivate');
8914 }
8915
8916 this._activeKB = instance.fireEvent('activate');
8917 Keyboard.manager.fireEvent('changed');
8918 } else if (this._manager){
8919
8920 this._manager.activate(this);
8921 }
8922 return this;
8923 },
8924
8925 isActive: function(){
8926 return this._manager ? (this._manager._activeKB == this) : (Keyboard.manager == this);
8927 },
8928
8929 deactivate: function(instance){
8930 if (instance){
8931 if (instance === this._activeKB){
8932 this._activeKB = null;
8933 instance.fireEvent('deactivate');
8934 Keyboard.manager.fireEvent('changed');
8935 }
8936 } else if (this._manager){
8937 this._manager.deactivate(this);
8938 }
8939 return this;
8940 },
8941
8942 relinquish: function(){
8943 if (this.isActive() && this._manager && this._manager.previous) this._manager.activate(this._manager.previous);
8944 else this.deactivate();
8945 return this;
8946 },
8947
8948
8949 manage: function(instance){
8950 if (instance._manager) instance._manager.drop(instance);
8951 this._instances.push(instance);
8952 instance._manager = this;
8953 if (!this._activeKB) this.activate(instance);
8954 return this;
8955 },
8956
8957 drop: function(instance){
8958 instance.relinquish();
8959 this._instances.erase(instance);
8960 if (this._activeKB == instance){
8961 if (this.previous && this._instances.contains(this.previous)) this.activate(this.previous);
8962 else this._activeKB = this._instances[0];
8963 }
8964 return this;
8965 },
8966
8967 trace: function(){
8968 Keyboard.trace(this);
8969 },
8970
8971 each: function(fn){
8972 Keyboard.each(this, fn);
8973 },
8974
8975
8976
8977
8978
8979 _instances: [],
8980
8981 _disable: function(instance){
8982 if (this._activeKB == instance) this._activeKB = null;
8983 },
8984
8985 _setup: function(){
8986 this.addEvents(this.options.events);
8987
8988 if (Keyboard.manager && !this._manager) Keyboard.manager.manage(this);
8989 if (this.options.active) this.activate();
8990 else this.relinquish();
8991 },
8992
8993 _handle: function(event, type){
8994
8995 if (event.preventKeyboardPropagation) return;
8996
8997 var bubbles = !!this._manager;
8998 if (bubbles && this._activeKB){
8999 this._activeKB._handle(event, type);
9000 if (event.preventKeyboardPropagation) return;
9001 }
9002 this.fireEvent(type, event);
9003
9004 if (!bubbles && this._activeKB) this._activeKB._handle(event, type);
9005 }
9006
9007 });
9008
9009 var parsed = {};
9010 var modifiers = ['shift', 'control', 'alt', 'meta'];
9011 var regex = /^(?:shift|control|ctrl|alt|meta)$/;
9012
9013 Keyboard.parse = function(type, eventType, ignore){
9014 if (ignore && ignore.contains(type.toLowerCase())) return type;
9015
9016 type = type.toLowerCase().replace(/^(keyup|keydown):/, function($0, $1){
9017 eventType = $1;
9018 return '';
9019 });
9020
9021 if (!parsed[type]){
9022 var key, mods = {};
9023 type.split('+').each(function(part){
9024 if (regex.test(part)) mods[part] = true;
9025 else key = part;
9026 });
9027
9028 mods.control = mods.control || mods.ctrl;
9029
9030 var keys = [];
9031 modifiers.each(function(mod){
9032 if (mods[mod]) keys.push(mod);
9033 });
9034
9035 if (key) keys.push(key);
9036 parsed[type] = keys.join('+');
9037 }
9038
9039 return eventType + ':keys(' + parsed[type] + ')';
9040 };
9041
9042 Keyboard.each = function(keyboard, fn){
9043 var current = keyboard || Keyboard.manager;
9044 while (current){
9045 fn.run(current);
9046 current = current._activeKB;
9047 }
9048 };
9049
9050 Keyboard.stop = function(event){
9051 event.preventKeyboardPropagation = true;
9052 };
9053
9054 Keyboard.manager = new Keyboard({
9055 active: true
9056 });
9057
9058 Keyboard.trace = function(keyboard){
9059 keyboard = keyboard || Keyboard.manager;
9060 var hasConsole = window.console && console.log;
9061 if (hasConsole) console.log('the following items have focus: ');
9062 Keyboard.each(keyboard, function(current){
9063 if (hasConsole) console.log(document.id(current.widget) || current.wiget || current);
9064 });
9065 };
9066
9067 var handler = function(event){
9068 var keys = [];
9069 modifiers.each(function(mod){
9070 if (event[mod]) keys.push(mod);
9071 });
9072
9073 if (!regex.test(event.key)) keys.push(event.key);
9074 Keyboard.manager._handle(event, event.type + ':keys(' + keys.join('+') + ')');
9075 };
9076
9077 document.addEvents({
9078 'keyup': handler,
9079 'keydown': handler
9080 });
9081
9082 })();
9083
9084
9085
9086
9087
9088
9089
9090
9091
9092
9093
9094
9095
9096
9097
9098
9099
9100
9101
9102
9103
9104
9105
9106
9107 Keyboard.prototype.options.nonParsedEvents.combine(['rebound', 'onrebound']);
9108
9109 Keyboard.implement({
9110
9111
9112
9113
9114
9115
9116
9117
9118
9119 addShortcut: function(name, shortcut){
9120 this._shortcuts = this._shortcuts || [];
9121 this._shortcutIndex = this._shortcutIndex || {};
9122
9123 shortcut.getKeyboard = Function.from(this);
9124 shortcut.name = name;
9125 this._shortcutIndex[name] = shortcut;
9126 this._shortcuts.push(shortcut);
9127 if (shortcut.keys) this.addEvent(shortcut.keys, shortcut.handler);
9128 return this;
9129 },
9130
9131 addShortcuts: function(obj){
9132 for (var name in obj) this.addShortcut(name, obj[name]);
9133 return this;
9134 },
9135
9136 removeShortcut: function(name){
9137 var shortcut = this.getShortcut(name);
9138 if (shortcut && shortcut.keys){
9139 this.removeEvent(shortcut.keys, shortcut.handler);
9140 delete this._shortcutIndex[name];
9141 this._shortcuts.erase(shortcut);
9142 }
9143 return this;
9144 },
9145
9146 removeShortcuts: function(names){
9147 names.each(this.removeShortcut, this);
9148 return this;
9149 },
9150
9151 getShortcuts: function(){
9152 return this._shortcuts || [];
9153 },
9154
9155 getShortcut: function(name){
9156 return (this._shortcutIndex || {})[name];
9157 }
9158
9159 });
9160
9161 Keyboard.rebind = function(newKeys, shortcuts){
9162 Array.from(shortcuts).each(function(shortcut){
9163 shortcut.getKeyboard().removeEvent(shortcut.keys, shortcut.handler);
9164 shortcut.getKeyboard().addEvent(newKeys, shortcut.handler);
9165 shortcut.keys = newKeys;
9166 shortcut.getKeyboard().fireEvent('rebound');
9167 });
9168 };
9169
9170
9171 Keyboard.getActiveShortcuts = function(keyboard){
9172 var activeKBS = [], activeSCS = [];
9173 Keyboard.each(keyboard, [].push.bind(activeKBS));
9174 activeKBS.each(function(kb){ activeSCS.extend(kb.getShortcuts()); });
9175 return activeSCS;
9176 };
9177
9178 Keyboard.getShortcut = function(name, keyboard, opts){
9179 opts = opts || {};
9180 var shortcuts = opts.many ? [] : null,
9181 set = opts.many ? function(kb){
9182 var shortcut = kb.getShortcut(name);
9183 if (shortcut) shortcuts.push(shortcut);
9184 } : function(kb){
9185 if (!shortcuts) shortcuts = kb.getShortcut(name);
9186 };
9187 Keyboard.each(keyboard, set);
9188 return shortcuts;
9189 };
9190
9191 Keyboard.getShortcuts = function(name, keyboard){
9192 return Keyboard.getShortcut(name, keyboard, { many: true });
9193 };
9194
9195
9196
9197
9198
9199
9200
9201
9202
9203
9204
9205
9206
9207
9208
9209
9210
9211
9212
9213
9214
9215
9216
9217
9218
9219
9220
9221
9222
9223
9224 HtmlTable = Class.refactor(HtmlTable, {
9225
9226 options: {
9227
9228
9229 useKeyboard: true,
9230 classRowSelected: 'table-tr-selected',
9231 classRowHovered: 'table-tr-hovered',
9232 classSelectable: 'table-selectable',
9233 shiftForMultiSelect: true,
9234 allowMultiSelect: true,
9235 selectable: false,
9236 selectHiddenRows: false
9237 },
9238
9239 initialize: function(){
9240 this.previous.apply(this, arguments);
9241 if (this.occluded) return this.occluded;
9242
9243 this.selectedRows = new Elements();
9244
9245 if (!this.bound) this.bound = {};
9246 this.bound.mouseleave = this.mouseleave.bind(this);
9247 this.bound.clickRow = this.clickRow.bind(this);
9248 this.bound.activateKeyboard = function(){
9249 if (this.keyboard && this.selectEnabled) this.keyboard.activate();
9250 }.bind(this);
9251
9252 if (this.options.selectable) this.enableSelect();
9253 },
9254
9255 empty: function(){
9256 this.selectNone();
9257 return this.previous();
9258 },
9259
9260 enableSelect: function(){
9261 this.selectEnabled = true;
9262 this.attachSelects();
9263 this.element.addClass(this.options.classSelectable);
9264 return this;
9265 },
9266
9267 disableSelect: function(){
9268 this.selectEnabled = false;
9269 this.attachSelects(false);
9270 this.element.removeClass(this.options.classSelectable);
9271 return this;
9272 },
9273
9274 push: function(){
9275 var ret = this.previous.apply(this, arguments);
9276 this.updateSelects();
9277 return ret;
9278 },
9279
9280 toggleRow: function(row){
9281 return this[(this.isSelected(row) ? 'de' : '') + 'selectRow'](row);
9282 },
9283
9284 selectRow: function(row, _nocheck){
9285
9286
9287 if (this.isSelected(row) || (!_nocheck && !this.body.getChildren().contains(row))) return;
9288 if (!this.options.allowMultiSelect) this.selectNone();
9289
9290 if (!this.isSelected(row)){
9291 this.selectedRows.push(row);
9292 row.addClass(this.options.classRowSelected);
9293 this.fireEvent('rowFocus', [row, this.selectedRows]);
9294 this.fireEvent('stateChanged');
9295 }
9296
9297 this.focused = row;
9298 document.clearSelection();
9299
9300 return this;
9301 },
9302
9303 isSelected: function(row){
9304 return this.selectedRows.contains(row);
9305 },
9306
9307 getSelected: function(){
9308 return this.selectedRows;
9309 },
9310
9311 getSelected: function(){
9312 return this.selectedRows;
9313 },
9314
9315 serialize: function(){
9316 var previousSerialization = this.previous.apply(this, arguments) || {};
9317 if (this.options.selectable){
9318 previousSerialization.selectedRows = this.selectedRows.map(function(row){
9319 return Array.indexOf(this.body.rows, row);
9320 }.bind(this));
9321 }
9322 return previousSerialization;
9323 },
9324
9325 restore: function(tableState){
9326 if(this.options.selectable && tableState.selectedRows){
9327 tableState.selectedRows.each(function(index){
9328 this.selectRow(this.body.rows[index]);
9329 }.bind(this));
9330 }
9331 this.previous.apply(this, arguments);
9332 },
9333
9334 deselectRow: function(row, _nocheck){
9335 if (!this.isSelected(row) || (!_nocheck && !this.body.getChildren().contains(row))) return;
9336
9337 this.selectedRows = new Elements(Array.from(this.selectedRows).erase(row));
9338 row.removeClass(this.options.classRowSelected);
9339 this.fireEvent('rowUnfocus', [row, this.selectedRows]);
9340 this.fireEvent('stateChanged');
9341 return this;
9342 },
9343
9344 selectAll: function(selectNone){
9345 if (!selectNone && !this.options.allowMultiSelect) return;
9346 this.selectRange(0, this.body.rows.length, selectNone);
9347 return this;
9348 },
9349
9350 selectNone: function(){
9351 return this.selectAll(true);
9352 },
9353
9354 selectRange: function(startRow, endRow, _deselect){
9355 if (!this.options.allowMultiSelect && !_deselect) return;
9356 var method = _deselect ? 'deselectRow' : 'selectRow',
9357 rows = Array.clone(this.body.rows);
9358
9359 if (typeOf(startRow) == 'element') startRow = rows.indexOf(startRow);
9360 if (typeOf(endRow) == 'element') endRow = rows.indexOf(endRow);
9361 endRow = endRow < rows.length - 1 ? endRow : rows.length - 1;
9362
9363 if (endRow < startRow){
9364 var tmp = startRow;
9365 startRow = endRow;
9366 endRow = tmp;
9367 }
9368
9369 for (var i = startRow; i <= endRow; i++){
9370 if (this.options.selectHiddenRows || rows[i].isDisplayed()) this[method](rows[i], true);
9371 }
9372
9373 return this;
9374 },
9375
9376 deselectRange: function(startRow, endRow){
9377 this.selectRange(startRow, endRow, true);
9378 },
9379
9380 getSelected: function(){
9381 return this.selectedRows;
9382 },
9383
9384
9385
9386
9387
9388 enterRow: function(row){
9389 if (this.hovered) this.hovered = this.leaveRow(this.hovered);
9390 this.hovered = row.addClass(this.options.classRowHovered);
9391 },
9392
9393 leaveRow: function(row){
9394 row.removeClass(this.options.classRowHovered);
9395 },
9396
9397 updateSelects: function(){
9398 Array.each(this.body.rows, function(row){
9399 var binders = row.retrieve('binders');
9400 if (!binders && !this.selectEnabled) return;
9401 if (!binders){
9402 binders = {
9403 mouseenter: this.enterRow.pass([row], this),
9404 mouseleave: this.leaveRow.pass([row], this)
9405 };
9406 row.store('binders', binders);
9407 }
9408 if (this.selectEnabled) row.addEvents(binders);
9409 else row.removeEvents(binders);
9410 }, this);
9411 },
9412
9413 shiftFocus: function(offset, event){
9414 if (!this.focused) return this.selectRow(this.body.rows[0], event);
9415 var to = this.getRowByOffset(offset, this.options.selectHiddenRows);
9416 if (to === null || this.focused == this.body.rows[to]) return this;
9417 this.toggleRow(this.body.rows[to], event);
9418 },
9419
9420 clickRow: function(event, row){
9421 var selecting = (event.shift || event.meta || event.control) && this.options.shiftForMultiSelect;
9422 if (!selecting && !(event.rightClick && this.isSelected(row) && this.options.allowMultiSelect)) this.selectNone();
9423
9424 if (event.rightClick) this.selectRow(row);
9425 else this.toggleRow(row);
9426
9427 if (event.shift){
9428 this.selectRange(this.rangeStart || this.body.rows[0], row, this.rangeStart ? !this.isSelected(row) : true);
9429 this.focused = row;
9430 }
9431 this.rangeStart = row;
9432 },
9433
9434 getRowByOffset: function(offset, includeHiddenRows){
9435 if (!this.focused) return 0;
9436 var index = Array.indexOf(this.body.rows, this.focused);
9437 if ((index == 0 && offset < 0) || (index == this.body.rows.length -1 && offset > 0)) return null;
9438 if (includeHiddenRows){
9439 index += offset;
9440 } else {
9441 var limit = 0,
9442 count = 0;
9443 if (offset > 0){
9444 while (count < offset && index < this.body.rows.length -1){
9445 if (this.body.rows[++index].isDisplayed()) count++;
9446 }
9447 } else {
9448 while (count > offset && index > 0){
9449 if (this.body.rows[--index].isDisplayed()) count--;
9450 }
9451 }
9452 }
9453 return index;
9454 },
9455
9456 attachSelects: function(attach){
9457 attach = attach != null ? attach : true;
9458
9459 var method = attach ? 'addEvents' : 'removeEvents';
9460 this.element[method]({
9461 mouseleave: this.bound.mouseleave,
9462 click: this.bound.activateKeyboard
9463 });
9464
9465 this.body[method]({
9466 'click:relay(tr)': this.bound.clickRow,
9467 'contextmenu:relay(tr)': this.bound.clickRow
9468 });
9469
9470 if (this.options.useKeyboard || this.keyboard){
9471 if (!this.keyboard) this.keyboard = new Keyboard();
9472 if (!this.selectKeysDefined){
9473 this.selectKeysDefined = true;
9474 var timer, held;
9475
9476 var move = function(offset){
9477 var mover = function(e){
9478 clearTimeout(timer);
9479 e.preventDefault();
9480 var to = this.body.rows[this.getRowByOffset(offset, this.options.selectHiddenRows)];
9481 if (e.shift && to && this.isSelected(to)){
9482 this.deselectRow(this.focused);
9483 this.focused = to;
9484 } else {
9485 if (to && (!this.options.allowMultiSelect || !e.shift)){
9486 this.selectNone();
9487 }
9488 this.shiftFocus(offset, e);
9489 }
9490
9491 if (held){
9492 timer = mover.delay(100, this, e);
9493 } else {
9494 timer = (function(){
9495 held = true;
9496 mover(e);
9497 }).delay(400);
9498 }
9499 }.bind(this);
9500 return mover;
9501 }.bind(this);
9502
9503 var clear = function(){
9504 clearTimeout(timer);
9505 held = false;
9506 };
9507
9508 this.keyboard.addEvents({
9509 'keydown:shift+up': move(-1),
9510 'keydown:shift+down': move(1),
9511 'keyup:shift+up': clear,
9512 'keyup:shift+down': clear,
9513 'keyup:up': clear,
9514 'keyup:down': clear
9515 });
9516
9517 var shiftHint = '';
9518 if (this.options.allowMultiSelect && this.options.shiftForMultiSelect && this.options.useKeyboard){
9519 shiftHint = " (Shift multi-selects).";
9520 }
9521
9522 this.keyboard.addShortcuts({
9523 'Select Previous Row': {
9524 keys: 'up',
9525 shortcut: 'up arrow',
9526 handler: move(-1),
9527 description: 'Select the previous row in the table.' + shiftHint
9528 },
9529 'Select Next Row': {
9530 keys: 'down',
9531 shortcut: 'down arrow',
9532 handler: move(1),
9533 description: 'Select the next row in the table.' + shiftHint
9534 }
9535 });
9536
9537 }
9538 this.keyboard[attach ? 'activate' : 'deactivate']();
9539 }
9540 this.updateSelects();
9541 },
9542
9543 mouseleave: function(){
9544 if (this.hovered) this.leaveRow(this.hovered);
9545 }
9546
9547 });
9548
9549
9550
9551
9552
9553
9554
9555
9556
9557
9558
9559
9560
9561
9562
9563
9564
9565
9566
9567
9568
9569
9570
9571
9572
9573
9574
9575
9576 var Scroller = new Class({
9577
9578 Implements: [Events, Options],
9579
9580 options: {
9581 area: 20,
9582 velocity: 1,
9583 onChange: function(x, y){
9584 this.element.scrollTo(x, y);
9585 },
9586 fps: 50
9587 },
9588
9589 initialize: function(element, options){
9590 this.setOptions(options);
9591 this.element = document.id(element);
9592 this.docBody = document.id(this.element.getDocument().body);
9593 this.listener = (typeOf(this.element) != 'element') ? this.docBody : this.element;
9594 this.timer = null;
9595 this.bound = {
9596 attach: this.attach.bind(this),
9597 detach: this.detach.bind(this),
9598 getCoords: this.getCoords.bind(this)
9599 };
9600 },
9601
9602 start: function(){
9603 this.listener.addEvents({
9604 mouseover: this.bound.attach,
9605 mouseleave: this.bound.detach
9606 });
9607 return this;
9608 },
9609
9610 stop: function(){
9611 this.listener.removeEvents({
9612 mouseover: this.bound.attach,
9613 mouseleave: this.bound.detach
9614 });
9615 this.detach();
9616 this.timer = clearInterval(this.timer);
9617 return this;
9618 },
9619
9620 attach: function(){
9621 this.listener.addEvent('mousemove', this.bound.getCoords);
9622 },
9623
9624 detach: function(){
9625 this.listener.removeEvent('mousemove', this.bound.getCoords);
9626 this.timer = clearInterval(this.timer);
9627 },
9628
9629 getCoords: function(event){
9630 this.page = (this.listener.get('tag') == 'body') ? event.client : event.page;
9631 if (!this.timer) this.timer = this.scroll.periodical(Math.round(1000 / this.options.fps), this);
9632 },
9633
9634 scroll: function(){
9635 var size = this.element.getSize(),
9636 scroll = this.element.getScroll(),
9637 pos = this.element != this.docBody ? this.element.getOffsets() : {x: 0, y:0},
9638 scrollSize = this.element.getScrollSize(),
9639 change = {x: 0, y: 0},
9640 top = this.options.area.top || this.options.area,
9641 bottom = this.options.area.bottom || this.options.area;
9642 for (var z in this.page){
9643 if (this.page[z] < (top + pos[z]) && scroll[z] != 0){
9644 change[z] = (this.page[z] - top - pos[z]) * this.options.velocity;
9645 } else if (this.page[z] + bottom > (size[z] + pos[z]) && scroll[z] + size[z] != scrollSize[z]){
9646 change[z] = (this.page[z] - size[z] + bottom - pos[z]) * this.options.velocity;
9647 }
9648 change[z] = change[z].round();