懒羊羊
2023-08-30 1ac2bc1590406d9babec036e154d8d08f34a6aa1
提交 | 用户 | 时间
1ac2bc 1 /*  Prototype JavaScript framework, version 1.5.1
2  *  (c) 2005-2007 Sam Stephenson
3  *
4  *  Prototype is freely distributable under the terms of an MIT-style license.
5  *  For details, see the Prototype web site: http://www.prototypejs.org/
6  *
7 /*--------------------------------------------------------------------------*/
8
9 var Prototype = {
10   Version: '1.5.1',
11
12   Browser: {
13     IE:     !!(window.attachEvent && !window.opera),
14     Opera:  !!window.opera,
15     WebKit: navigator.userAgent.indexOf('AppleWebKit/') > -1,
16     Gecko:  navigator.userAgent.indexOf('Gecko') > -1 && navigator.userAgent.indexOf('KHTML') == -1
17   },
18
19   BrowserFeatures: {
20     XPath: !!document.evaluate,
21     ElementExtensions: !!window.HTMLElement,
22     SpecificElementExtensions:
23       (document.createElement('div').__proto__ !==
24        document.createElement('form').__proto__)
25   },
26
27   ScriptFragment: '<script[^>]*>([\u0001-\uFFFF]*?)</script>',
28   JSONFilter: /^\/\*-secure-\s*(.*)\s*\*\/\s*$/,
29
30   emptyFunction: function() { },
31   K: function(x) { return x }
32 }
33
34 var Class = {
35   create: function() {
36     return function() {
37       this.initialize.apply(this, arguments);
38     }
39   }
40 }
41
42 var Abstract = new Object();
43
44 Object.extend = function(destination, source) {
45   for (var property in source) {
46     destination[property] = source[property];
47   }
48   return destination;
49 }
50
51 Object.extend(Object, {
52   inspect: function(object) {
53     try {
54       if (object === undefined) return 'undefined';
55       if (object === null) return 'null';
56       return object.inspect ? object.inspect() : object.toString();
57     } catch (e) {
58       if (e instanceof RangeError) return '...';
59       throw e;
60     }
61   },
62
63   toJSON: function(object) {
64     var type = typeof object;
65     switch(type) {
66       case 'undefined':
67       case 'function':
68       case 'unknown': return;
69       case 'boolean': return object.toString();
70     }
71     if (object === null) return 'null';
72     if (object.toJSON) return object.toJSON();
73     if (object.ownerDocument === document) return;
74     var results = [];
75     for (var property in object) {
76       var value = Object.toJSON(object[property]);
77       if (value !== undefined)
78         results.push(property.toJSON() + ': ' + value);
79     }
80     return '{' + results.join(', ') + '}';
81   },
82
83   keys: function(object) {
84     var keys = [];
85     for (var property in object)
86       keys.push(property);
87     return keys;
88   },
89
90   values: function(object) {
91     var values = [];
92     for (var property in object)
93       values.push(object[property]);
94     return values;
95   },
96
97   clone: function(object) {
98     return Object.extend({}, object);
99   }
100 });
101
102 Function.prototype.bind = function() {
103   var __method = this, args = $A(arguments), object = args.shift();
104   return function() {
105     return __method.apply(object, args.concat($A(arguments)));
106   }
107 }
108
109 Function.prototype.bindAsEventListener = function(object) {
110   var __method = this, args = $A(arguments), object = args.shift();
111   return function(event) {
112     return __method.apply(object, [event || window.event].concat(args));
113   }
114 }
115
116 Object.extend(Number.prototype, {
117   toColorPart: function() {
118     return this.toPaddedString(2, 16);
119   },
120
121   succ: function() {
122     return this + 1;
123   },
124
125   times: function(iterator) {
126     $R(0, this, true).each(iterator);
127     return this;
128   },
129
130   toPaddedString: function(length, radix) {
131     var string = this.toString(radix || 10);
132     return '0'.times(length - string.length) + string;
133   },
134
135   toJSON: function() {
136     return isFinite(this) ? this.toString() : 'null';
137   }
138 });
139
140 Date.prototype.toJSON = function() {
141   return '"' + this.getFullYear() + '-' +
142     (this.getMonth() + 1).toPaddedString(2) + '-' +
143     this.getDate().toPaddedString(2) + 'T' +
144     this.getHours().toPaddedString(2) + ':' +
145     this.getMinutes().toPaddedString(2) + ':' +
146     this.getSeconds().toPaddedString(2) + '"';
147 };
148
149 var Try = {
150   these: function() {
151     var returnValue;
152
153     for (var i = 0, length = arguments.length; i < length; i++) {
154       var lambda = arguments[i];
155       try {
156         returnValue = lambda();
157         break;
158       } catch (e) {}
159     }
160
161     return returnValue;
162   }
163 }
164
165 /*--------------------------------------------------------------------------*/
166
167 var PeriodicalExecuter = Class.create();
168 PeriodicalExecuter.prototype = {
169   initialize: function(callback, frequency) {
170     this.callback = callback;
171     this.frequency = frequency;
172     this.currentlyExecuting = false;
173
174     this.registerCallback();
175   },
176
177   registerCallback: function() {
178     this.timer = setInterval(this.onTimerEvent.bind(this), this.frequency * 1000);
179   },
180
181   stop: function() {
182     if (!this.timer) return;
183     clearInterval(this.timer);
184     this.timer = null;
185   },
186
187   onTimerEvent: function() {
188     if (!this.currentlyExecuting) {
189       try {
190         this.currentlyExecuting = true;
191         this.callback(this);
192       } finally {
193         this.currentlyExecuting = false;
194       }
195     }
196   }
197 }
198 Object.extend(String, {
199   interpret: function(value) {
200     return value == null ? '' : String(value);
201   },
202   specialChar: {
203     '\b': '\\b',
204     '\t': '\\t',
205     '\n': '\\n',
206     '\f': '\\f',
207     '\r': '\\r',
208     '\\': '\\\\'
209   }
210 });
211
212 Object.extend(String.prototype, {
213   gsub: function(pattern, replacement) {
214     var result = '', source = this, match;
215     replacement = arguments.callee.prepareReplacement(replacement);
216
217     while (source.length > 0) {
218       if (match = source.match(pattern)) {
219         result += source.slice(0, match.index);
220         result += String.interpret(replacement(match));
221         source  = source.slice(match.index + match[0].length);
222       } else {
223         result += source, source = '';
224       }
225     }
226     return result;
227   },
228
229   sub: function(pattern, replacement, count) {
230     replacement = this.gsub.prepareReplacement(replacement);
231     count = count === undefined ? 1 : count;
232
233     return this.gsub(pattern, function(match) {
234       if (--count < 0) return match[0];
235       return replacement(match);
236     });
237   },
238
239   scan: function(pattern, iterator) {
240     this.gsub(pattern, iterator);
241     return this;
242   },
243
244   truncate: function(length, truncation) {
245     length = length || 30;
246     truncation = truncation === undefined ? '...' : truncation;
247     return this.length > length ?
248       this.slice(0, length - truncation.length) + truncation : this;
249   },
250
251   strip: function() {
252     return this.replace(/^\s+/, '').replace(/\s+$/, '');
253   },
254
255   stripTags: function() {
256     return this.replace(/<\/?[^>]+>/gi, '');
257   },
258
259   stripScripts: function() {
260     return this.replace(new RegExp(Prototype.ScriptFragment, 'img'), '');
261   },
262
263   extractScripts: function() {
264     var matchAll = new RegExp(Prototype.ScriptFragment, 'img');
265     var matchOne = new RegExp(Prototype.ScriptFragment, 'im');
266     return (this.match(matchAll) || []).map(function(scriptTag) {
267       return (scriptTag.match(matchOne) || ['', ''])[1];
268     });
269   },
270
271   evalScripts: function() {
272     return this.extractScripts().map(function(script) { return eval(script) });
273   },
274
275   escapeHTML: function() {
276     var self = arguments.callee;
277     self.text.data = this;
278     return self.div.innerHTML;
279   },
280
281   unescapeHTML: function() {
282     var div = document.createElement('div');
283     div.innerHTML = this.stripTags();
284     return div.childNodes[0] ? (div.childNodes.length > 1 ?
285       $A(div.childNodes).inject('', function(memo, node) { return memo+node.nodeValue }) :
286       div.childNodes[0].nodeValue) : '';
287   },
288
289   toQueryParams: function(separator) {
290     var match = this.strip().match(/([^?#]*)(#.*)?$/);
291     if (!match) return {};
292
293     return match[1].split(separator || '&').inject({}, function(hash, pair) {
294       if ((pair = pair.split('='))[0]) {
295         var key = decodeURIComponent(pair.shift());
296         var value = pair.length > 1 ? pair.join('=') : pair[0];
297         if (value != undefined) value = decodeURIComponent(value);
298
299         if (key in hash) {
300           if (hash[key].constructor != Array) hash[key] = [hash[key]];
301           hash[key].push(value);
302         }
303         else hash[key] = value;
304       }
305       return hash;
306     });
307   },
308
309   toArray: function() {
310     return this.split('');
311   },
312
313   succ: function() {
314     return this.slice(0, this.length - 1) +
315       String.fromCharCode(this.charCodeAt(this.length - 1) + 1);
316   },
317
318   times: function(count) {
319     var result = '';
320     for (var i = 0; i < count; i++) result += this;
321     return result;
322   },
323
324   camelize: function() {
325     var parts = this.split('-'), len = parts.length;
326     if (len == 1) return parts[0];
327
328     var camelized = this.charAt(0) == '-'
329       ? parts[0].charAt(0).toUpperCase() + parts[0].substring(1)
330       : parts[0];
331
332     for (var i = 1; i < len; i++)
333       camelized += parts[i].charAt(0).toUpperCase() + parts[i].substring(1);
334
335     return camelized;
336   },
337
338   capitalize: function() {
339     return this.charAt(0).toUpperCase() + this.substring(1).toLowerCase();
340   },
341
342   underscore: function() {
343     return this.gsub(/::/, '/').gsub(/([A-Z]+)([A-Z][a-z])/,'#{1}_#{2}').gsub(/([a-z\d])([A-Z])/,'#{1}_#{2}').gsub(/-/,'_').toLowerCase();
344   },
345
346   dasherize: function() {
347     return this.gsub(/_/,'-');
348   },
349
350   inspect: function(useDoubleQuotes) {
351     var escapedString = this.gsub(/[\x00-\x1f\\]/, function(match) {
352       var character = String.specialChar[match[0]];
353       return character ? character : '\\u00' + match[0].charCodeAt().toPaddedString(2, 16);
354     });
355     if (useDoubleQuotes) return '"' + escapedString.replace(/"/g, '\\"') + '"';
356     return "'" + escapedString.replace(/'/g, '\\\'') + "'";
357   },
358
359   toJSON: function() {
360     return this.inspect(true);
361   },
362
363   unfilterJSON: function(filter) {
364     return this.sub(filter || Prototype.JSONFilter, '#{1}');
365   },
366
367   evalJSON: function(sanitize) {
368     var json = this.unfilterJSON();
369     try {
370       if (!sanitize || (/^("(\\.|[^"\\\n\r])*?"|[,:{}\[\]0-9.\-+Eaeflnr-u \n\r\t])+?$/.test(json)))
371         return eval('(' + json + ')');
372     } catch (e) { }
373     throw new SyntaxError('Badly formed JSON string: ' + this.inspect());
374   },
375
376   include: function(pattern) {
377     return this.indexOf(pattern) > -1;
378   },
379
380   startsWith: function(pattern) {
381     return this.indexOf(pattern) === 0;
382   },
383
384   endsWith: function(pattern) {
385     var d = this.length - pattern.length;
386     return d >= 0 && this.lastIndexOf(pattern) === d;
387   },
388
389   empty: function() {
390     return this == '';
391   },
392
393   blank: function() {
394     return /^\s*$/.test(this);
395   }
396 });
397
398 if (Prototype.Browser.WebKit || Prototype.Browser.IE) Object.extend(String.prototype, {
399   escapeHTML: function() {
400     return this.replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;');
401   },
402   unescapeHTML: function() {
403     return this.replace(/&amp;/g,'&').replace(/&lt;/g,'<').replace(/&gt;/g,'>');
404   }
405 });
406
407 String.prototype.gsub.prepareReplacement = function(replacement) {
408   if (typeof replacement == 'function') return replacement;
409   var template = new Template(replacement);
410   return function(match) { return template.evaluate(match) };
411 }
412
413 String.prototype.parseQuery = String.prototype.toQueryParams;
414
415 Object.extend(String.prototype.escapeHTML, {
416   div:  document.createElement('div'),
417   text: document.createTextNode('')
418 });
419
420 with (String.prototype.escapeHTML) div.appendChild(text);
421
422 var Template = Class.create();
423 Template.Pattern = /(^|.|\r|\n)(#\{(.*?)\})/;
424 Template.prototype = {
425   initialize: function(template, pattern) {
426     this.template = template.toString();
427     this.pattern  = pattern || Template.Pattern;
428   },
429
430   evaluate: function(object) {
431     return this.template.gsub(this.pattern, function(match) {
432       var before = match[1];
433       if (before == '\\') return match[2];
434       return before + String.interpret(object[match[3]]);
435     });
436   }
437 }
438
439 var $break = {}, $continue = new Error('"throw $continue" is deprecated, use "return" instead');
440
441 var Enumerable = {
442   each: function(iterator) {
443     var index = 0;
444     try {
445       this._each(function(value) {
446         iterator(value, index++);
447       });
448     } catch (e) {
449       if (e != $break) 
450           throw e;
451     }
452     return this;
453   },
454
455   eachSlice: function(number, iterator) {
456     var index = -number, slices = [], array = this.toArray();
457     while ((index += number) < array.length)
458       slices.push(array.slice(index, index+number));
459     return slices.map(iterator);
460   },
461
462   all: function(iterator) {
463     var result = true;
464     this.each(function(value, index) {
465       result = result && !!(iterator || Prototype.K)(value, index);
466       if (!result) throw $break;
467     });
468     return result;
469   },
470
471   any: function(iterator) {
472     var result = false;
473     this.each(function(value, index) {
474       if (result = !!(iterator || Prototype.K)(value, index))
475         throw $break;
476     });
477     return result;
478   },
479
480   collect: function(iterator) {
481     var results = [];
482     this.each(function(value, index) {
483       results.push((iterator || Prototype.K)(value, index));
484     });
485     return results;
486   },
487
488   detect: function(iterator) {
489     var result;
490     this.each(function(value, index) {
491       if (iterator(value, index)) {
492         result = value;
493         throw $break;
494       }
495     });
496     return result;
497   },
498
499   findAll: function(iterator) {
500     var results = [];
501     this.each(function(value, index) {
502       if (iterator(value, index))
503         results.push(value);
504     });
505     return results;
506   },
507
508   grep: function(pattern, iterator) {
509     var results = [];
510     this.each(function(value, index) {
511       var stringValue = value.toString();
512       if (stringValue.match(pattern))
513         results.push((iterator || Prototype.K)(value, index));
514     })
515     return results;
516   },
517
518   include: function(object) {
519     var found = false;
520     this.each(function(value) {
521       if (value == object) {
522         found = true;
523         throw $break;
524       }
525     });
526     return found;
527   },
528
529   inGroupsOf: function(number, fillWith) {
530     fillWith = fillWith === undefined ? null : fillWith;
531     return this.eachSlice(number, function(slice) {
532       while(slice.length < number) slice.push(fillWith);
533       return slice;
534     });
535   },
536
537   inject: function(memo, iterator) {
538     this.each(function(value, index) {
539       memo = iterator(memo, value, index);
540     });
541     return memo;
542   },
543
544   invoke: function(method) {
545     var args = $A(arguments).slice(1);
546     return this.map(function(value) {
547       return value[method].apply(value, args);
548     });
549   },
550
551   max: function(iterator) {
552     var result;
553     this.each(function(value, index) {
554       value = (iterator || Prototype.K)(value, index);
555       if (result == undefined || value >= result)
556         result = value;
557     });
558     return result;
559   },
560
561   min: function(iterator) {
562     var result;
563     this.each(function(value, index) {
564       value = (iterator || Prototype.K)(value, index);
565       if (result == undefined || value < result)
566         result = value;
567     });
568     return result;
569   },
570
571   partition: function(iterator) {
572     var trues = [], falses = [];
573     this.each(function(value, index) {
574       ((iterator || Prototype.K)(value, index) ?
575         trues : falses).push(value);
576     });
577     return [trues, falses];
578   },
579
580   pluck: function(property) {
581     var results = [];
582     this.each(function(value, index) {
583       results.push(value[property]);
584     });
585     return results;
586   },
587
588   reject: function(iterator) {
589     var results = [];
590     this.each(function(value, index) {
591       if (!iterator(value, index))
592         results.push(value);
593     });
594     return results;
595   },
596
597   sortBy: function(iterator) {
598     return this.map(function(value, index) {
599       return {value: value, criteria: iterator(value, index)};
600     }).sort(function(left, right) {
601       var a = left.criteria, b = right.criteria;
602       return a < b ? -1 : a > b ? 1 : 0;
603     }).pluck('value');
604   },
605
606   toArray: function() {
607     return this.map();
608   },
609
610   zip: function() {
611     var iterator = Prototype.K, args = $A(arguments);
612     if (typeof args.last() == 'function')
613       iterator = args.pop();
614
615     var collections = [this].concat(args).map($A);
616     return this.map(function(value, index) {
617       return iterator(collections.pluck(index));
618     });
619   },
620
621   size: function() {
622     return this.toArray().length;
623   },
624
625   inspect: function() {
626     return '#<Enumerable:' + this.toArray().inspect() + '>';
627   }
628 }
629
630 Object.extend(Enumerable, {
631   map:     Enumerable.collect,
632   find:    Enumerable.detect,
633   select:  Enumerable.findAll,
634   member:  Enumerable.include,
635   entries: Enumerable.toArray
636 });
637 var $A = Array.from = function(iterable) {
638   if (!iterable) return [];
639   if (iterable.toArray) {
640     return iterable.toArray();
641   } else {
642     var results = [];
643     for (var i = 0, length = iterable.length; i < length; i++)
644       results.push(iterable[i]);
645     return results;
646   }
647 }
648
649 if (Prototype.Browser.WebKit) {
650   $A = Array.from = function(iterable) {
651     if (!iterable) return [];
652     if (!(typeof iterable == 'function' && iterable == '[object NodeList]') &&
653       iterable.toArray) {
654       return iterable.toArray();
655     } else {
656       var results = [];
657       for (var i = 0, length = iterable.length; i < length; i++)
658         results.push(iterable[i]);
659       return results;
660     }
661   }
662 }
663
664 Object.extend(Array.prototype, Enumerable);
665
666 if (!Array.prototype._reverse)
667   Array.prototype._reverse = Array.prototype.reverse;
668
669 Object.extend(Array.prototype, {
670   _each: function(iterator) {
671     for (var i = 0, length = this.length; i < length; i++)
672       iterator(this[i]);
673   },
674
675   clear: function() {
676     this.length = 0;
677     return this;
678   },
679
680   first: function() {
681     return this[0];
682   },
683
684   last: function() {
685     return this[this.length - 1];
686   },
687
688   compact: function() {
689     return this.select(function(value) {
690       return value != null;
691     });
692   },
693
694   flatten: function() {
695     return this.inject([], function(array, value) {
696       return array.concat(value && value.constructor == Array ?
697         value.flatten() : [value]);
698     });
699   },
700
701   without: function() {
702     var values = $A(arguments);
703     return this.select(function(value) {
704       return !values.include(value);
705     });
706   },
707
708   indexOf: function(object) {
709     for (var i = 0, length = this.length; i < length; i++)
710       if (this[i] == object) return i;
711     return -1;
712   },
713
714   reverse: function(inline) {
715     return (inline !== false ? this : this.toArray())._reverse();
716   },
717
718   reduce: function() {
719     return this.length > 1 ? this : this[0];
720   },
721
722   uniq: function(sorted) {
723     return this.inject([], function(array, value, index) {
724       if (0 == index || (sorted ? array.last() != value : !array.include(value)))
725         array.push(value);
726       return array;
727     });
728   },
729
730   clone: function() {
731     return [].concat(this);
732   },
733
734   size: function() {
735     return this.length;
736   },
737
738   inspect: function() {
739     return '[' + this.map(Object.inspect).join(', ') + ']';
740   },
741
742   toJSON: function() {
743     var results = [];
744     this.each(function(object) {
745       var value = Object.toJSON(object);
746       if (value !== undefined) results.push(value);
747     });
748     return '[' + results.join(', ') + ']';
749   }
750 });
751
752 Array.prototype.toArray = Array.prototype.clone;
753
754 function $w(string) {
755   string = string.strip();
756   return string ? string.split(/\s+/) : [];
757 }
758
759 if (Prototype.Browser.Opera){
760   Array.prototype.concat = function() {
761     var array = [];
762     for (var i = 0, length = this.length; i < length; i++) array.push(this[i]);
763     for (var i = 0, length = arguments.length; i < length; i++) {
764       if (arguments[i].constructor == Array) {
765         for (var j = 0, arrayLength = arguments[i].length; j < arrayLength; j++)
766           array.push(arguments[i][j]);
767       } else {
768         array.push(arguments[i]);
769       }
770     }
771     return array;
772   }
773 }
774 var Hash = function(object) {
775   if (object instanceof Hash) this.merge(object);
776   else Object.extend(this, object || {});
777 };
778
779 Object.extend(Hash, {
780   toQueryString: function(obj) {
781     var parts = [];
782     parts.add = arguments.callee.addPair;
783
784     this.prototype._each.call(obj, function(pair) {
785       if (!pair.key) return;
786       var value = pair.value;
787
788       if (value && typeof value == 'object') {
789         if (value.constructor == Array) value.each(function(value) {
790           parts.add(pair.key, value);
791         });
792         return;
793       }
794       parts.add(pair.key, value);
795     });
796
797     return parts.join('&');
798   },
799
800   toJSON: function(object) {
801     var results = [];
802     this.prototype._each.call(object, function(pair) {
803       var value = Object.toJSON(pair.value);
804       if (value !== undefined) results.push(pair.key.toJSON() + ': ' + value);
805     });
806     return '{' + results.join(', ') + '}';
807   }
808 });
809
810 Hash.toQueryString.addPair = function(key, value, prefix) {
811   key = encodeURIComponent(key);
812   if (value === undefined) this.push(key);
813   else this.push(key + '=' + (value == null ? '' : encodeURIComponent(value)));
814 }
815
816 Object.extend(Hash.prototype, Enumerable);
817 Object.extend(Hash.prototype, {
818   _each: function(iterator) {
819     for (var key in this) {
820       var value = this[key];
821       if (value && value == Hash.prototype[key]) continue;
822
823       var pair = [key, value];
824       pair.key = key;
825       pair.value = value;
826       iterator(pair);
827     }
828   },
829
830   keys: function() {
831     return this.pluck('key');
832   },
833
834   values: function() {
835     return this.pluck('value');
836   },
837
838   merge: function(hash) {
839     return $H(hash).inject(this, function(mergedHash, pair) {
840       mergedHash[pair.key] = pair.value;
841       return mergedHash;
842     });
843   },
844
845   remove: function() {
846     var result;
847     for(var i = 0, length = arguments.length; i < length; i++) {
848       var value = this[arguments[i]];
849       if (value !== undefined){
850         if (result === undefined) result = value;
851         else {
852           if (result.constructor != Array) result = [result];
853           result.push(value)
854         }
855       }
856       delete this[arguments[i]];
857     }
858     return result;
859   },
860
861   toQueryString: function() {
862     return Hash.toQueryString(this);
863   },
864
865   inspect: function() {
866     return '#<Hash:{' + this.map(function(pair) {
867       return pair.map(Object.inspect).join(': ');
868     }).join(', ') + '}>';
869   },
870
871   toJSON: function() {
872     return Hash.toJSON(this);
873   }
874 });
875
876 function $H(object) {
877   if (object instanceof Hash) return object;
878   return new Hash(object);
879 };
880
881 // Safari iterates over shadowed properties
882 if (function() {
883   var i = 0, Test = function(value) { this.key = value };
884   Test.prototype.key = 'foo';
885   for (var property in new Test('bar')) i++;
886   return i > 1;
887 }()) Hash.prototype._each = function(iterator) {
888   var cache = [];
889   for (var key in this) {
890     var value = this[key];
891     if ((value && value == Hash.prototype[key]) || cache.include(key)) continue;
892     cache.push(key);
893     var pair = [key, value];
894     pair.key = key;
895     pair.value = value;
896     iterator(pair);
897   }
898 };
899 ObjectRange = Class.create();
900 Object.extend(ObjectRange.prototype, Enumerable);
901 Object.extend(ObjectRange.prototype, {
902   initialize: function(start, end, exclusive) {
903     this.start = start;
904     this.end = end;
905     this.exclusive = exclusive;
906   },
907
908   _each: function(iterator) {
909     var value = this.start;
910     while (this.include(value)) {
911       iterator(value);
912       value = value.succ();
913     }
914   },
915
916   include: function(value) {
917     if (value < this.start)
918       return false;
919     if (this.exclusive)
920       return value < this.end;
921     return value <= this.end;
922   }
923 });
924
925 var $R = function(start, end, exclusive) {
926   return new ObjectRange(start, end, exclusive);
927 }
928
929 var Ajax = {
930   getTransport: function() {
931     return Try.these(
932       function() {return new XMLHttpRequest()},
933       function() {return new ActiveXObject('Msxml2.XMLHTTP')},
934       function() {return new ActiveXObject('Microsoft.XMLHTTP')}
935     ) || false;
936   },
937
938   activeRequestCount: 0
939 }
940
941 Ajax.Responders = {
942   responders: [],
943
944   _each: function(iterator) {
945     this.responders._each(iterator);
946   },
947
948   register: function(responder) {
949     if (!this.include(responder))
950       this.responders.push(responder);
951   },
952
953   unregister: function(responder) {
954     this.responders = this.responders.without(responder);
955   },
956
957   dispatch: function(callback, request, transport, json) {
958     this.each(function(responder) {
959       if (typeof responder[callback] == 'function') {
960         try {
961           responder[callback].apply(responder, [request, transport, json]);
962         } catch (e) {}
963       }
964     });
965   }
966 };
967
968 Object.extend(Ajax.Responders, Enumerable);
969
970 Ajax.Responders.register({
971   onCreate: function() {
972     Ajax.activeRequestCount++;
973   },
974   onComplete: function() {
975     Ajax.activeRequestCount--;
976   }
977 });
978
979 Ajax.Base = function() {};
980 Ajax.Base.prototype = {
981   setOptions: function(options) {
982     this.options = {
983       method:       'post',
984       asynchronous: true,
985       contentType:  'application/x-www-form-urlencoded',
986       encoding:     'UTF-8',
987       parameters:   ''
988     }
989     Object.extend(this.options, options || {});
990
991     this.options.method = this.options.method.toLowerCase();
992     if (typeof this.options.parameters == 'string')
993       this.options.parameters = this.options.parameters.toQueryParams();
994   }
995 }
996
997 Ajax.Request = Class.create();
998 Ajax.Request.Events =
999   ['Uninitialized', 'Loading', 'Loaded', 'Interactive', 'Complete'];
1000
1001 Ajax.Request.prototype = Object.extend(new Ajax.Base(), {
1002   _complete: false,
1003
1004   initialize: function(url, options) {
1005     this.transport = Ajax.getTransport();
1006     this.setOptions(options);
1007     this.request(url);
1008   },
1009
1010   request: function(url) {
1011     this.url = url;
1012     this.method = this.options.method;
1013     var params = Object.clone(this.options.parameters);
1014
1015     if (!['get', 'post'].include(this.method)) {
1016       // simulate other verbs over post
1017       params['_method'] = this.method;
1018       this.method = 'post';
1019     }
1020
1021     this.parameters = params;
1022
1023     if (params = Hash.toQueryString(params)) {
1024       // when GET, append parameters to URL
1025       if (this.method == 'get' || this.options.postBody)
1026         this.url += (this.url.include('?') ? '&' : '?') + params;
1027       else if (/Konqueror|Safari|KHTML/.test(navigator.userAgent))
1028         params += '&_=';
1029     }
1030
1031     try {
1032       if (this.options.onCreate) this.options.onCreate(this.transport);
1033       Ajax.Responders.dispatch('onCreate', this, this.transport);
1034
1035       this.transport.open(this.method.toUpperCase(), this.url,
1036         this.options.asynchronous);
1037
1038       if (this.options.asynchronous)
1039         setTimeout(function() { this.respondToReadyState(1) }.bind(this), 10);
1040
1041       this.transport.onreadystatechange = this.onStateChange.bind(this);
1042       this.setRequestHeaders();
1043
1044       this.body = this.method == 'post' ? (this.options.postBody || params) : null;
1045       this.transport.send(this.body);
1046
1047       /* Force Firefox to handle ready state 4 for synchronous requests */
1048       if (!this.options.asynchronous && this.transport.overrideMimeType)
1049         this.onStateChange();
1050
1051     }
1052     catch (e) {
1053       this.dispatchException(e);
1054     }
1055   },
1056
1057   onStateChange: function() {
1058     var readyState = this.transport.readyState;
1059     if (readyState > 1 && !((readyState == 4) && this._complete))
1060       this.respondToReadyState(this.transport.readyState);
1061   },
1062
1063   setRequestHeaders: function() {
1064     var headers = {
1065       'X-Requested-With': 'XMLHttpRequest',
1066       'X-Prototype-Version': Prototype.Version,
1067       'Accept': 'text/javascript, text/html, application/xml, text/xml, */*'
1068     };
1069
1070     if (this.method == 'post') {
1071       headers['Content-type'] = this.options.contentType +
1072         (this.options.encoding ? '; charset=' + this.options.encoding : '');
1073
1074       /* Force "Connection: close" for older Mozilla browsers to work
1075        * around a bug where XMLHttpRequest sends an incorrect
1076        * Content-length header. See Mozilla Bugzilla #246651.
1077        */
1078       if (this.transport.overrideMimeType &&
1079           (navigator.userAgent.match(/Gecko\/(\d{4})/) || [0,2005])[1] < 2005)
1080             headers['Connection'] = 'close';
1081     }
1082
1083     // user-defined headers
1084     if (typeof this.options.requestHeaders == 'object') {
1085       var extras = this.options.requestHeaders;
1086
1087       if (typeof extras.push == 'function')
1088         for (var i = 0, length = extras.length; i < length; i += 2)
1089           headers[extras[i]] = extras[i+1];
1090       else
1091         $H(extras).each(function(pair) { headers[pair.key] = pair.value });
1092     }
1093
1094     for (var name in headers)
1095       this.transport.setRequestHeader(name, headers[name]);
1096   },
1097
1098   success: function() {
1099     return !this.transport.status
1100         || (this.transport.status >= 200 && this.transport.status < 300);
1101   },
1102
1103   respondToReadyState: function(readyState) {
1104     var state = Ajax.Request.Events[readyState];
1105     var transport = this.transport, json = this.evalJSON();
1106
1107     if (state == 'Complete') {
1108       try {
1109         this._complete = true;
1110         (this.options['on' + this.transport.status]
1111          || this.options['on' + (this.success() ? 'Success' : 'Failure')]
1112          || Prototype.emptyFunction)(transport, json);
1113       } catch (e) {
1114         this.dispatchException(e);
1115       }
1116
1117       var contentType = this.getHeader('Content-type');
1118       if (contentType && contentType.strip().
1119         match(/^(text|application)\/(x-)?(java|ecma)script(;.*)?$/i))
1120           this.evalResponse();
1121     }
1122
1123     try {
1124       (this.options['on' + state] || Prototype.emptyFunction)(transport, json);
1125       Ajax.Responders.dispatch('on' + state, this, transport, json);
1126     } catch (e) {
1127       this.dispatchException(e);
1128     }
1129
1130     if (state == 'Complete') {
1131       // avoid memory leak in MSIE: clean up
1132       this.transport.onreadystatechange = Prototype.emptyFunction;
1133     }
1134   },
1135
1136   getHeader: function(name) {
1137     try {
1138       return this.transport.getResponseHeader(name);
1139     } catch (e) { return null }
1140   },
1141
1142   evalJSON: function() {
1143     try {
1144       var json = this.getHeader('X-JSON');
1145       return json ? json.evalJSON() : null;
1146     } catch (e) { return null }
1147   },
1148
1149   evalResponse: function() {
1150     try {
1151       return eval((this.transport.responseText || '').unfilterJSON());
1152     } catch (e) {
1153       this.dispatchException(e);
1154     }
1155   },
1156
1157   dispatchException: function(exception) {
1158     (this.options.onException || Prototype.emptyFunction)(this, exception);
1159     Ajax.Responders.dispatch('onException', this, exception);
1160   }
1161 });
1162
1163 Ajax.Updater = Class.create();
1164
1165 Object.extend(Object.extend(Ajax.Updater.prototype, Ajax.Request.prototype), {
1166   initialize: function(container, url, options) {
1167     this.container = {
1168       success: (container.success || container),
1169       failure: (container.failure || (container.success ? null : container))
1170     }
1171
1172     this.transport = Ajax.getTransport();
1173     this.setOptions(options);
1174
1175     var onComplete = this.options.onComplete || Prototype.emptyFunction;
1176     this.options.onComplete = (function(transport, param) {
1177       this.updateContent();
1178       onComplete(transport, param);
1179     }).bind(this);
1180
1181     this.request(url);
1182   },
1183
1184   updateContent: function() {
1185     var receiver = this.container[this.success() ? 'success' : 'failure'];
1186     var response = this.transport.responseText;
1187
1188     if (!this.options.evalScripts) response = response.stripScripts();
1189
1190     if (receiver = $(receiver)) {
1191       if (this.options.insertion)
1192         new this.options.insertion(receiver, response);
1193       else
1194         receiver.update(response);
1195     }
1196
1197     if (this.success()) {
1198       if (this.onComplete)
1199         setTimeout(this.onComplete.bind(this), 10);
1200     }
1201   }
1202 });
1203
1204 Ajax.PeriodicalUpdater = Class.create();
1205 Ajax.PeriodicalUpdater.prototype = Object.extend(new Ajax.Base(), {
1206   initialize: function(container, url, options) {
1207     this.setOptions(options);
1208     this.onComplete = this.options.onComplete;
1209
1210     this.frequency = (this.options.frequency || 2);
1211     this.decay = (this.options.decay || 1);
1212
1213     this.updater = {};
1214     this.container = container;
1215     this.url = url;
1216
1217     this.start();
1218   },
1219
1220   start: function() {
1221     this.options.onComplete = this.updateComplete.bind(this);
1222     this.onTimerEvent();
1223   },
1224
1225   stop: function() {
1226     this.updater.options.onComplete = undefined;
1227     clearTimeout(this.timer);
1228     (this.onComplete || Prototype.emptyFunction).apply(this, arguments);
1229   },
1230
1231   updateComplete: function(request) {
1232     if (this.options.decay) {
1233       this.decay = (request.responseText == this.lastText ?
1234         this.decay * this.options.decay : 1);
1235
1236       this.lastText = request.responseText;
1237     }
1238     this.timer = setTimeout(this.onTimerEvent.bind(this),
1239       this.decay * this.frequency * 1000);
1240   },
1241
1242   onTimerEvent: function() {
1243     this.updater = new Ajax.Updater(this.container, this.url, this.options);
1244   }
1245 });
1246 function $(element) {
1247   if (arguments.length > 1) {
1248     for (var i = 0, elements = [], length = arguments.length; i < length; i++)
1249       elements.push($(arguments[i]));
1250     return elements;
1251   }
1252   if (typeof element == 'string')
1253     element = document.getElementById(element);
1254   return Element.extend(element);
1255 }
1256
1257 if (Prototype.BrowserFeatures.XPath) {
1258   document._getElementsByXPath = function(expression, parentElement) {
1259     var results = [];
1260     var query = document.evaluate(expression, $(parentElement) || document,
1261       null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null);
1262     for (var i = 0, length = query.snapshotLength; i < length; i++)
1263       results.push(query.snapshotItem(i));
1264     return results;
1265   };
1266
1267   document.getElementsByClassName = function(className, parentElement) {
1268     var q = ".//*[contains(concat(' ', @class, ' '), ' " + className + " ')]";
1269     return document._getElementsByXPath(q, parentElement);
1270   }
1271
1272 } else document.getElementsByClassName = function(className, parentElement) {
1273   var children = ($(parentElement) || document.body).getElementsByTagName('*');
1274   var elements = [], child;
1275   for (var i = 0, length = children.length; i < length; i++) {
1276     child = children[i];
1277     if (Element.hasClassName(child, className))
1278       elements.push(Element.extend(child));
1279   }
1280   return elements;
1281 };
1282
1283 /*--------------------------------------------------------------------------*/
1284
1285 if (!window.Element) var Element = {};
1286
1287 Element.extend = function(element) {
1288   var F = Prototype.BrowserFeatures;
1289   if (!element || !element.tagName || element.nodeType == 3 ||
1290    element._extended || F.SpecificElementExtensions || element == window)
1291     return element;
1292
1293   var methods = {}, tagName = element.tagName, cache = Element.extend.cache,
1294    T = Element.Methods.ByTag;
1295
1296   // extend methods for all tags (Safari doesn't need this)
1297   if (!F.ElementExtensions) {
1298     Object.extend(methods, Element.Methods),
1299     Object.extend(methods, Element.Methods.Simulated);
1300   }
1301
1302   // extend methods for specific tags
1303   if (T[tagName]) Object.extend(methods, T[tagName]);
1304
1305   for (var property in methods) {
1306     var value = methods[property];
1307     if (typeof value == 'function' && !(property in element))
1308       element[property] = cache.findOrStore(value);
1309   }
1310
1311   element._extended = Prototype.emptyFunction;
1312   return element;
1313 };
1314
1315 Element.extend.cache = {
1316   findOrStore: function(value) {
1317     return this[value] = this[value] || function() {
1318       return value.apply(null, [this].concat($A(arguments)));
1319     }
1320   }
1321 };
1322
1323 Element.Methods = {
1324   visible: function(element) {
1325     return $(element).style.display != 'none';
1326   },
1327
1328   toggle: function(element) {
1329     element = $(element);
1330     Element[Element.visible(element) ? 'hide' : 'show'](element);
1331     return element;
1332   },
1333
1334   hide: function(element) {
1335     $(element).style.display = 'none';
1336     return element;
1337   },
1338
1339   show: function(element) {
1340     $(element).style.display = '';
1341     return element;
1342   },
1343
1344   remove: function(element) {
1345     element = $(element);
1346     element.parentNode.removeChild(element);
1347     return element;
1348   },
1349
1350   update: function(element, html) {
1351     html = typeof html == 'undefined' ? '' : html.toString();
1352     $(element).innerHTML = html.stripScripts();
1353     setTimeout(function() {html.evalScripts()}, 10);
1354     return element;
1355   },
1356
1357   replace: function(element, html) {
1358     element = $(element);
1359     html = typeof html == 'undefined' ? '' : html.toString();
1360     if (element.outerHTML) {
1361       element.outerHTML = html.stripScripts();
1362     } else {
1363       var range = element.ownerDocument.createRange();
1364       range.selectNodeContents(element);
1365       element.parentNode.replaceChild(
1366         range.createContextualFragment(html.stripScripts()), element);
1367     }
1368     setTimeout(function() {html.evalScripts()}, 10);
1369     return element;
1370   },
1371
1372   inspect: function(element) {
1373     element = $(element);
1374     var result = '<' + element.tagName.toLowerCase();
1375     $H({'id': 'id', 'className': 'class'}).each(function(pair) {
1376       var property = pair.first(), attribute = pair.last();
1377       var value = (element[property] || '').toString();
1378       if (value) result += ' ' + attribute + '=' + value.inspect(true);
1379     });
1380     return result + '>';
1381   },
1382
1383   recursivelyCollect: function(element, property) {
1384     element = $(element);
1385     var elements = [];
1386     while (element = element[property])
1387       if (element.nodeType == 1)
1388         elements.push(Element.extend(element));
1389     return elements;
1390   },
1391
1392   ancestors: function(element) {
1393     return $(element).recursivelyCollect('parentNode');
1394   },
1395
1396   descendants: function(element) {
1397     return $A($(element).getElementsByTagName('*')).each(Element.extend);
1398   },
1399
1400   firstDescendant: function(element) {
1401     element = $(element).firstChild;
1402     while (element && element.nodeType != 1) element = element.nextSibling;
1403     return $(element);
1404   },
1405
1406   immediateDescendants: function(element) {
1407     if (!(element = $(element).firstChild)) return [];
1408     while (element && element.nodeType != 1) element = element.nextSibling;
1409     if (element) return [element].concat($(element).nextSiblings());
1410     return [];
1411   },
1412
1413   previousSiblings: function(element) {
1414     return $(element).recursivelyCollect('previousSibling');
1415   },
1416
1417   nextSiblings: function(element) {
1418     return $(element).recursivelyCollect('nextSibling');
1419   },
1420
1421   siblings: function(element) {
1422     element = $(element);
1423     return element.previousSiblings().reverse().concat(element.nextSiblings());
1424   },
1425
1426   match: function(element, selector) {
1427     if (typeof selector == 'string')
1428       selector = new Selector(selector);
1429     return selector.match($(element));
1430   },
1431
1432   up: function(element, expression, index) {
1433     element = $(element);
1434     if (arguments.length == 1) return $(element.parentNode);
1435     var ancestors = element.ancestors();
1436     return expression ? Selector.findElement(ancestors, expression, index) :
1437       ancestors[index || 0];
1438   },
1439
1440   down: function(element, expression, index) {
1441     element = $(element);
1442     if (arguments.length == 1) return element.firstDescendant();
1443     var descendants = element.descendants();
1444     return expression ? Selector.findElement(descendants, expression, index) :
1445       descendants[index || 0];
1446   },
1447
1448   previous: function(element, expression, index) {
1449     element = $(element);
1450     if (arguments.length == 1) return $(Selector.handlers.previousElementSibling(element));
1451     var previousSiblings = element.previousSiblings();
1452     return expression ? Selector.findElement(previousSiblings, expression, index) :
1453       previousSiblings[index || 0];
1454   },
1455
1456   next: function(element, expression, index) {
1457     element = $(element);
1458     if (arguments.length == 1) return $(Selector.handlers.nextElementSibling(element));
1459     var nextSiblings = element.nextSiblings();
1460     return expression ? Selector.findElement(nextSiblings, expression, index) :
1461       nextSiblings[index || 0];
1462   },
1463
1464   getElementsBySelector: function() {
1465     var args = $A(arguments), element = $(args.shift());
1466     return Selector.findChildElements(element, args);
1467   },
1468
1469   getElementsByClassName: function(element, className) {
1470     return document.getElementsByClassName(className, element);
1471   },
1472
1473   readAttribute: function(element, name) {
1474     element = $(element);
1475     if (Prototype.Browser.IE) {
1476       if (!element.attributes) return null;
1477       var t = Element._attributeTranslations;
1478       if (t.values[name]) return t.values[name](element, name);
1479       if (t.names[name])  name = t.names[name];
1480       var attribute = element.attributes[name];
1481       return attribute ? attribute.nodeValue : null;
1482     }
1483     return element.getAttribute(name);
1484   },
1485
1486   getHeight: function(element) {
1487     return $(element).getDimensions().height;
1488   },
1489
1490   getWidth: function(element) {
1491     return $(element).getDimensions().width;
1492   },
1493
1494   classNames: function(element) {
1495     return new Element.ClassNames(element);
1496   },
1497
1498   hasClassName: function(element, className) {
1499     if (!(element = $(element))) return;
1500     var elementClassName = element.className;
1501     if (elementClassName.length == 0) return false;
1502     if (elementClassName == className ||
1503             (typeof(elementClassName.match) === 'function' && elementClassName.match(new RegExp("(^|\\s)" + className + "(\\s|$)"))))
1504       return true;
1505     return false;
1506   },
1507
1508   addClassName: function(element, className) {
1509     if (!(element = $(element))) return;
1510     Element.classNames(element).add(className);
1511     return element;
1512   },
1513
1514   removeClassName: function(element, className) {
1515     if (!(element = $(element))) return;
1516     Element.classNames(element).remove(className);
1517     return element;
1518   },
1519
1520   toggleClassName: function(element, className) {
1521     if (!(element = $(element))) return;
1522     Element.classNames(element)[element.hasClassName(className) ? 'remove' : 'add'](className);
1523     return element;
1524   },
1525
1526   observe: function() {
1527     Event.observe.apply(Event, arguments);
1528     return $A(arguments).first();
1529   },
1530
1531   stopObserving: function() {
1532     Event.stopObserving.apply(Event, arguments);
1533     return $A(arguments).first();
1534   },
1535
1536   // removes whitespace-only text node children
1537   cleanWhitespace: function(element) {
1538     element = $(element);
1539     var node = element.firstChild;
1540     while (node) {
1541       var nextNode = node.nextSibling;
1542       if (node.nodeType == 3 && !/\S/.test(node.nodeValue))
1543         element.removeChild(node);
1544       node = nextNode;
1545     }
1546     return element;
1547   },
1548
1549   empty: function(element) {
1550     return $(element).innerHTML.blank();
1551   },
1552
1553   descendantOf: function(element, ancestor) {
1554     element = $(element), ancestor = $(ancestor);
1555     while (element = element.parentNode)
1556       if (element == ancestor) return true;
1557     return false;
1558   },
1559
1560   scrollTo: function(element) {
1561     element = $(element);
1562     var pos = Position.cumulativeOffset(element);
1563     window.scrollTo(pos[0], pos[1]);
1564     return element;
1565   },
1566
1567   getStyle: function(element, style) {
1568     element = $(element);
1569     style = style == 'float' ? 'cssFloat' : style.camelize();
1570     var value = element.style[style];
1571     if (!value) {
1572       var css = document.defaultView.getComputedStyle(element, null);
1573       value = css ? css[style] : null;
1574     }
1575     if (style == 'opacity') return value ? parseFloat(value) : 1.0;
1576     return value == 'auto' ? null : value;
1577   },
1578
1579   getOpacity: function(element) {
1580     return $(element).getStyle('opacity');
1581   },
1582
1583   setStyle: function(element, styles, camelized) {
1584     element = $(element);
1585     var elementStyle = element.style;
1586
1587     for (var property in styles)
1588       if (property == 'opacity') element.setOpacity(styles[property])
1589       else
1590         elementStyle[(property == 'float' || property == 'cssFloat') ?
1591           (elementStyle.styleFloat === undefined ? 'cssFloat' : 'styleFloat') :
1592           (camelized ? property : property.camelize())] = styles[property];
1593
1594     return element;
1595   },
1596
1597   setOpacity: function(element, value) {
1598     element = $(element);
1599     element.style.opacity = (value == 1 || value === '') ? '' :
1600       (value < 0.00001) ? 0 : value;
1601     return element;
1602   },
1603
1604   getDimensions: function(element) {
1605     element = $(element);
1606     var display = $(element).getStyle('display');
1607     if (display != 'none' && display != null) // Safari bug
1608       return {width: element.offsetWidth, height: element.offsetHeight};
1609
1610     // All *Width and *Height properties give 0 on elements with display none,
1611     // so enable the element temporarily
1612     var els = element.style;
1613     var originalVisibility = els.visibility;
1614     var originalPosition = els.position;
1615     var originalDisplay = els.display;
1616     els.visibility = 'hidden';
1617     els.position = 'absolute';
1618     els.display = 'block';
1619     var originalWidth = element.clientWidth;
1620     var originalHeight = element.clientHeight;
1621     els.display = originalDisplay;
1622     els.position = originalPosition;
1623     els.visibility = originalVisibility;
1624     return {width: originalWidth, height: originalHeight};
1625   },
1626
1627   makePositioned: function(element) {
1628     element = $(element);
1629     var pos = Element.getStyle(element, 'position');
1630     if (pos == 'static' || !pos) {
1631       element._madePositioned = true;
1632       element.style.position = 'relative';
1633       // Opera returns the offset relative to the positioning context, when an
1634       // element is position relative but top and left have not been defined
1635       if (window.opera) {
1636         element.style.top = 0;
1637         element.style.left = 0;
1638       }
1639     }
1640     return element;
1641   },
1642
1643   undoPositioned: function(element) {
1644     element = $(element);
1645     if (element._madePositioned) {
1646       element._madePositioned = undefined;
1647       element.style.position =
1648         element.style.top =
1649         element.style.left =
1650         element.style.bottom =
1651         element.style.right = '';
1652     }
1653     return element;
1654   },
1655
1656   makeClipping: function(element) {
1657     element = $(element);
1658     if (element._overflow) return element;
1659     element._overflow = element.style.overflow || 'auto';
1660     if ((Element.getStyle(element, 'overflow') || 'visible') != 'hidden')
1661       element.style.overflow = 'hidden';
1662     return element;
1663   },
1664
1665   undoClipping: function(element) {
1666     element = $(element);
1667     if (!element._overflow) return element;
1668     element.style.overflow = element._overflow == 'auto' ? '' : element._overflow;
1669     element._overflow = null;
1670     return element;
1671   }
1672 };
1673
1674 Object.extend(Element.Methods, {
1675   childOf: Element.Methods.descendantOf,
1676   childElements: Element.Methods.immediateDescendants
1677 });
1678
1679 if (Prototype.Browser.Opera) {
1680   Element.Methods._getStyle = Element.Methods.getStyle;
1681   Element.Methods.getStyle = function(element, style) {
1682     switch(style) {
1683       case 'left':
1684       case 'top':
1685       case 'right':
1686       case 'bottom':
1687         if (Element._getStyle(element, 'position') == 'static') return null;
1688       default: return Element._getStyle(element, style);
1689     }
1690   };
1691 }
1692 else if (Prototype.Browser.IE) {
1693   Element.Methods.getStyle = function(element, style) {
1694     element = $(element);
1695     style = (style == 'float' || style == 'cssFloat') ? 'styleFloat' : style.camelize();
1696     var value = element.style[style];
1697     if (!value && element.currentStyle) value = element.currentStyle[style];
1698
1699     if (style == 'opacity') {
1700       if (value = (element.getStyle('filter') || '').match(/alpha\(opacity=(.*)\)/))
1701         if (value[1]) return parseFloat(value[1]) / 100;
1702       return 1.0;
1703     }
1704
1705     if (value == 'auto') {
1706       if ((style == 'width' || style == 'height') && (element.getStyle('display') != 'none'))
1707         return element['offset'+style.capitalize()] + 'px';
1708       return null;
1709     }
1710     return value;
1711   };
1712
1713   Element.Methods.setOpacity = function(element, value) {
1714     element = $(element);
1715     var filter = element.getStyle('filter'), style = element.style;
1716     if (value == 1 || value === '') {
1717       style.filter = filter.replace(/alpha\([^\)]*\)/gi,'');
1718       return element;
1719     } else if (value < 0.00001) value = 0;
1720     style.filter = filter.replace(/alpha\([^\)]*\)/gi, '') +
1721       'alpha(opacity=' + (value * 100) + ')';
1722     return element;
1723   };
1724
1725   // IE is missing .innerHTML support for TABLE-related elements
1726   Element.Methods.update = function(element, html) {
1727     element = $(element);
1728     html = typeof html == 'undefined' ? '' : html.toString();
1729     var tagName = element.tagName.toUpperCase();
1730     if (['THEAD','TBODY','TR','TD'].include(tagName)) {
1731       var div = document.createElement('div');
1732       switch (tagName) {
1733         case 'THEAD':
1734         case 'TBODY':
1735           div.innerHTML = '<table><tbody>' +  html.stripScripts() + '</tbody></table>';
1736           depth = 2;
1737           break;
1738         case 'TR':
1739           div.innerHTML = '<table><tbody><tr>' +  html.stripScripts() + '</tr></tbody></table>';
1740           depth = 3;
1741           break;
1742         case 'TD':
1743           div.innerHTML = '<table><tbody><tr><td>' +  html.stripScripts() + '</td></tr></tbody></table>';
1744           depth = 4;
1745       }
1746       $A(element.childNodes).each(function(node) { element.removeChild(node) });
1747       depth.times(function() { div = div.firstChild });
1748       $A(div.childNodes).each(function(node) { element.appendChild(node) });
1749     } else {
1750       element.innerHTML = html.stripScripts();
1751     }
1752     setTimeout(function() { html.evalScripts() }, 10);
1753     return element;
1754   }
1755 }
1756 else if (Prototype.Browser.Gecko) {
1757   Element.Methods.setOpacity = function(element, value) {
1758     element = $(element);
1759     element.style.opacity = (value == 1) ? 0.999999 :
1760       (value === '') ? '' : (value < 0.00001) ? 0 : value;
1761     return element;
1762   };
1763 }
1764
1765 Element._attributeTranslations = {
1766   names: {
1767     colspan:   "colSpan",
1768     rowspan:   "rowSpan",
1769     valign:    "vAlign",
1770     datetime:  "dateTime",
1771     accesskey: "accessKey",
1772     tabindex:  "tabIndex",
1773     enctype:   "encType",
1774     maxlength: "maxLength",
1775     readonly:  "readOnly",
1776     longdesc:  "longDesc"
1777   },
1778   values: {
1779     _getAttr: function(element, attribute) {
1780       return element.getAttribute(attribute, 2);
1781     },
1782     _flag: function(element, attribute) {
1783       return $(element).hasAttribute(attribute) ? attribute : null;
1784     },
1785     style: function(element) {
1786       return element.style.cssText.toLowerCase();
1787     },
1788     title: function(element) {
1789       var node = element.getAttributeNode('title');
1790       return node.specified ? node.nodeValue : null;
1791     }
1792   }
1793 };
1794
1795 (function() {
1796   Object.extend(this, {
1797     href: this._getAttr,
1798     src:  this._getAttr,
1799     type: this._getAttr,
1800     disabled: this._flag,
1801     checked:  this._flag,
1802     readonly: this._flag,
1803     multiple: this._flag
1804   });
1805 }).call(Element._attributeTranslations.values);
1806
1807 Element.Methods.Simulated = {
1808   hasAttribute: function(element, attribute) {
1809     var t = Element._attributeTranslations, node;
1810     attribute = t.names[attribute] || attribute;
1811     node = $(element).getAttributeNode(attribute);
1812     return node && node.specified;
1813   }
1814 };
1815
1816 Element.Methods.ByTag = {};
1817
1818 Object.extend(Element, Element.Methods);
1819
1820 if (!Prototype.BrowserFeatures.ElementExtensions &&
1821  document.createElement('div').__proto__) {
1822   window.HTMLElement = {};
1823   window.HTMLElement.prototype = document.createElement('div').__proto__;
1824   Prototype.BrowserFeatures.ElementExtensions = true;
1825 }
1826
1827 Element.hasAttribute = function(element, attribute) {
1828   if (element.hasAttribute) return element.hasAttribute(attribute);
1829   return Element.Methods.Simulated.hasAttribute(element, attribute);
1830 };
1831
1832 Element.addMethods = function(methods) {
1833   var F = Prototype.BrowserFeatures, T = Element.Methods.ByTag;
1834
1835   if (!methods) {
1836     Object.extend(Form, Form.Methods);
1837     Object.extend(Form.Element, Form.Element.Methods);
1838     Object.extend(Element.Methods.ByTag, {
1839       "FORM":     Object.clone(Form.Methods),
1840       "INPUT":    Object.clone(Form.Element.Methods),
1841       "SELECT":   Object.clone(Form.Element.Methods),
1842       "TEXTAREA": Object.clone(Form.Element.Methods)
1843     });
1844   }
1845
1846   if (arguments.length == 2) {
1847     var tagName = methods;
1848     methods = arguments[1];
1849   }
1850
1851   if (!tagName) Object.extend(Element.Methods, methods || {});
1852   else {
1853     if (tagName.constructor == Array) tagName.each(extend);
1854     else extend(tagName);
1855   }
1856
1857   function extend(tagName) {
1858     tagName = tagName.toUpperCase();
1859     if (!Element.Methods.ByTag[tagName])
1860       Element.Methods.ByTag[tagName] = {};
1861     Object.extend(Element.Methods.ByTag[tagName], methods);
1862   }
1863
1864   function copy(methods, destination, onlyIfAbsent) {
1865     onlyIfAbsent = onlyIfAbsent || false;
1866     var cache = Element.extend.cache;
1867     for (var property in methods) {
1868       var value = methods[property];
1869       if (!onlyIfAbsent || !(property in destination))
1870         destination[property] = cache.findOrStore(value);
1871     }
1872   }
1873
1874   function findDOMClass(tagName) {
1875     var klass;
1876     var trans = {
1877       "OPTGROUP": "OptGroup", "TEXTAREA": "TextArea", "P": "Paragraph",
1878       "FIELDSET": "FieldSet", "UL": "UList", "OL": "OList", "DL": "DList",
1879       "DIR": "Directory", "H1": "Heading", "H2": "Heading", "H3": "Heading",
1880       "H4": "Heading", "H5": "Heading", "H6": "Heading", "Q": "Quote",
1881       "INS": "Mod", "DEL": "Mod", "A": "Anchor", "IMG": "Image", "CAPTION":
1882       "TableCaption", "COL": "TableCol", "COLGROUP": "TableCol", "THEAD":
1883       "TableSection", "TFOOT": "TableSection", "TBODY": "TableSection", "TR":
1884       "TableRow", "TH": "TableCell", "TD": "TableCell", "FRAMESET":
1885       "FrameSet", "IFRAME": "IFrame"
1886     };
1887     if (trans[tagName]) klass = 'HTML' + trans[tagName] + 'Element';
1888     if (window[klass]) return window[klass];
1889     klass = 'HTML' + tagName + 'Element';
1890     if (window[klass]) return window[klass];
1891     klass = 'HTML' + tagName.capitalize() + 'Element';
1892     if (window[klass]) return window[klass];
1893
1894     window[klass] = {};
1895     window[klass].prototype = document.createElement(tagName).__proto__;
1896     return window[klass];
1897   }
1898
1899   if (F.ElementExtensions) {
1900     copy(Element.Methods, HTMLElement.prototype);
1901     copy(Element.Methods.Simulated, HTMLElement.prototype, true);
1902   }
1903
1904   if (F.SpecificElementExtensions) {
1905     for (var tag in Element.Methods.ByTag) {
1906       var klass = findDOMClass(tag);
1907       if (typeof klass == "undefined") continue;
1908       copy(T[tag], klass.prototype);
1909     }
1910   }
1911
1912   Object.extend(Element, Element.Methods);
1913   delete Element.ByTag;
1914 };
1915
1916 var Toggle = { display: Element.toggle };
1917
1918 /*--------------------------------------------------------------------------*/
1919
1920 Abstract.Insertion = function(adjacency) {
1921   this.adjacency = adjacency;
1922 }
1923
1924 Abstract.Insertion.prototype = {
1925   initialize: function(element, content) {
1926     this.element = $(element);
1927     this.content = content.stripScripts();
1928
1929     if (this.adjacency && this.element.insertAdjacentHTML) {
1930       try {
1931         this.element.insertAdjacentHTML(this.adjacency, this.content);
1932       } catch (e) {
1933         var tagName = this.element.tagName.toUpperCase();
1934         if (['TBODY', 'TR'].include(tagName)) {
1935           this.insertContent(this.contentFromAnonymousTable());
1936         } else {
1937           throw e;
1938         }
1939       }
1940     } else {
1941       this.range = this.element.ownerDocument.createRange();
1942       if (this.initializeRange) this.initializeRange();
1943       this.insertContent([this.range.createContextualFragment(this.content)]);
1944     }
1945
1946     setTimeout(function() {content.evalScripts()}, 10);
1947   },
1948
1949   contentFromAnonymousTable: function() {
1950     var div = document.createElement('div');
1951     div.innerHTML = '<table><tbody>' + this.content + '</tbody></table>';
1952     return $A(div.childNodes[0].childNodes[0].childNodes);
1953   }
1954 }
1955
1956 var Insertion = new Object();
1957
1958 Insertion.Before = Class.create();
1959 Insertion.Before.prototype = Object.extend(new Abstract.Insertion('beforeBegin'), {
1960   initializeRange: function() {
1961     this.range.setStartBefore(this.element);
1962   },
1963
1964   insertContent: function(fragments) {
1965     fragments.each((function(fragment) {
1966       this.element.parentNode.insertBefore(fragment, this.element);
1967     }).bind(this));
1968   }
1969 });
1970
1971 Insertion.Top = Class.create();
1972 Insertion.Top.prototype = Object.extend(new Abstract.Insertion('afterBegin'), {
1973   initializeRange: function() {
1974     this.range.selectNodeContents(this.element);
1975     this.range.collapse(true);
1976   },
1977
1978   insertContent: function(fragments) {
1979     fragments.reverse(false).each((function(fragment) {
1980       this.element.insertBefore(fragment, this.element.firstChild);
1981     }).bind(this));
1982   }
1983 });
1984
1985 Insertion.Bottom = Class.create();
1986 Insertion.Bottom.prototype = Object.extend(new Abstract.Insertion('beforeEnd'), {
1987   initializeRange: function() {
1988     this.range.selectNodeContents(this.element);
1989     this.range.collapse(this.element);
1990   },
1991
1992   insertContent: function(fragments) {
1993     fragments.each((function(fragment) {
1994       this.element.appendChild(fragment);
1995     }).bind(this));
1996   }
1997 });
1998
1999 Insertion.After = Class.create();
2000 Insertion.After.prototype = Object.extend(new Abstract.Insertion('afterEnd'), {
2001   initializeRange: function() {
2002     this.range.setStartAfter(this.element);
2003   },
2004
2005   insertContent: function(fragments) {
2006     fragments.each((function(fragment) {
2007       this.element.parentNode.insertBefore(fragment,
2008         this.element.nextSibling);
2009     }).bind(this));
2010   }
2011 });
2012
2013 /*--------------------------------------------------------------------------*/
2014
2015 Element.ClassNames = Class.create();
2016 Element.ClassNames.prototype = {
2017   initialize: function(element) {
2018     this.element = $(element);
2019   },
2020
2021   _each: function(iterator) {
2022     this.element.className.split(/\s+/).select(function(name) {
2023       return name.length > 0;
2024     })._each(iterator);
2025   },
2026
2027   set: function(className) {
2028     this.element.className = className;
2029   },
2030
2031   add: function(classNameToAdd) {
2032     if (this.include(classNameToAdd)) return;
2033     this.set($A(this).concat(classNameToAdd).join(' '));
2034   },
2035
2036   remove: function(classNameToRemove) {
2037     if (!this.include(classNameToRemove)) return;
2038     this.set($A(this).without(classNameToRemove).join(' '));
2039   },
2040
2041   toString: function() {
2042     return $A(this).join(' ');
2043   }
2044 };
2045
2046 Object.extend(Element.ClassNames.prototype, Enumerable);
2047 /* Portions of the Selector class are derived from Jack Slocum�۪s DomQuery,
2048  * part of YUI-Ext version 0.40, distributed under the terms of an MIT-style
2049  * license.  Please see http://www.yui-ext.com/ for more information. */
2050
2051 var Selector = Class.create();
2052
2053 Selector.prototype = {
2054   initialize: function(expression) {
2055     this.expression = expression.strip();
2056     this.compileMatcher();
2057   },
2058
2059   compileMatcher: function() {
2060     // Selectors with namespaced attributes can't use the XPath version
2061     if (Prototype.BrowserFeatures.XPath && !(/\[[\w-]*?:/).test(this.expression))
2062       return this.compileXPathMatcher();
2063
2064     var e = this.expression, ps = Selector.patterns, h = Selector.handlers,
2065         c = Selector.criteria, le, p, m;
2066
2067     if (Selector._cache[e]) {
2068       this.matcher = Selector._cache[e]; return;
2069     }
2070     this.matcher = ["this.matcher = function(root) {",
2071                     "var r = root, h = Selector.handlers, c = false, n;"];
2072
2073     while (e && le != e && (/\S/).test(e)) {
2074       le = e;
2075       for (var i in ps) {
2076         p = ps[i];
2077         if (m = e.match(p)) {
2078           this.matcher.push(typeof c[i] == 'function' ? c[i](m) :
2079               new Template(c[i]).evaluate(m));
2080           e = e.replace(m[0], '');
2081           break;
2082         }
2083       }
2084     }
2085
2086     this.matcher.push("return h.unique(n);\n}");
2087     eval(this.matcher.join('\n'));
2088     Selector._cache[this.expression] = this.matcher;
2089   },
2090
2091   compileXPathMatcher: function() {
2092     var e = this.expression, ps = Selector.patterns,
2093         x = Selector.xpath, le,  m;
2094
2095     if (Selector._cache[e]) {
2096       this.xpath = Selector._cache[e]; return;
2097     }
2098
2099     this.matcher = ['.//*'];
2100     while (e && le != e && (/\S/).test(e)) {
2101       le = e;
2102       for (var i in ps) {
2103         if (m = e.match(ps[i])) {
2104           this.matcher.push(typeof x[i] == 'function' ? x[i](m) :
2105             new Template(x[i]).evaluate(m));
2106           e = e.replace(m[0], '');
2107           break;
2108         }
2109       }
2110     }
2111
2112     this.xpath = this.matcher.join('');
2113     Selector._cache[this.expression] = this.xpath;
2114   },
2115
2116   findElements: function(root) {
2117     root = root || document;
2118     if (this.xpath) return document._getElementsByXPath(this.xpath, root);
2119     return this.matcher(root);
2120   },
2121
2122   match: function(element) {
2123     return this.findElements(document).include(element);
2124   },
2125
2126   toString: function() {
2127     return this.expression;
2128   },
2129
2130   inspect: function() {
2131     return "#<Selector:" + this.expression.inspect() + ">";
2132   }
2133 };
2134
2135 Object.extend(Selector, {
2136   _cache: {},
2137
2138   xpath: {
2139     descendant:   "//*",
2140     child:        "/*",
2141     adjacent:     "/following-sibling::*[1]",
2142     laterSibling: '/following-sibling::*',
2143     tagName:      function(m) {
2144       if (m[1] == '*') return '';
2145       return "[local-name()='" + m[1].toLowerCase() +
2146              "' or local-name()='" + m[1].toUpperCase() + "']";
2147     },
2148     className:    "[contains(concat(' ', @class, ' '), ' #{1} ')]",
2149     id:           "[@id='#{1}']",
2150     attrPresence: "[@#{1}]",
2151     attr: function(m) {
2152       m[3] = m[5] || m[6];
2153       return new Template(Selector.xpath.operators[m[2]]).evaluate(m);
2154     },
2155     pseudo: function(m) {
2156       var h = Selector.xpath.pseudos[m[1]];
2157       if (!h) return '';
2158       if (typeof h === 'function') return h(m);
2159       return new Template(Selector.xpath.pseudos[m[1]]).evaluate(m);
2160     },
2161     operators: {
2162       '=':  "[@#{1}='#{3}']",
2163       '!=': "[@#{1}!='#{3}']",
2164       '^=': "[starts-with(@#{1}, '#{3}')]",
2165       '$=': "[substring(@#{1}, (string-length(@#{1}) - string-length('#{3}') + 1))='#{3}']",
2166       '*=': "[contains(@#{1}, '#{3}')]",
2167       '~=': "[contains(concat(' ', @#{1}, ' '), ' #{3} ')]",
2168       '|=': "[contains(concat('-', @#{1}, '-'), '-#{3}-')]"
2169     },
2170     pseudos: {
2171       'first-child': '[not(preceding-sibling::*)]',
2172       'last-child':  '[not(following-sibling::*)]',
2173       'only-child':  '[not(preceding-sibling::* or following-sibling::*)]',
2174       'empty':       "[count(*) = 0 and (count(text()) = 0 or translate(text(), ' \t\r\n', '') = '')]",
2175       'checked':     "[@checked]",
2176       'disabled':    "[@disabled]",
2177       'enabled':     "[not(@disabled)]",
2178       'not': function(m) {
2179         var e = m[6], p = Selector.patterns,
2180             x = Selector.xpath, le, m, v;
2181
2182         var exclusion = [];
2183         while (e && le != e && (/\S/).test(e)) {
2184           le = e;
2185           for (var i in p) {
2186             if (m = e.match(p[i])) {
2187               v = typeof x[i] == 'function' ? x[i](m) : new Template(x[i]).evaluate(m);
2188               exclusion.push("(" + v.substring(1, v.length - 1) + ")");
2189               e = e.replace(m[0], '');
2190               break;
2191             }
2192           }
2193         }
2194         return "[not(" + exclusion.join(" and ") + ")]";
2195       },
2196       'nth-child':      function(m) {
2197         return Selector.xpath.pseudos.nth("(count(./preceding-sibling::*) + 1) ", m);
2198       },
2199       'nth-last-child': function(m) {
2200         return Selector.xpath.pseudos.nth("(count(./following-sibling::*) + 1) ", m);
2201       },
2202       'nth-of-type':    function(m) {
2203         return Selector.xpath.pseudos.nth("position() ", m);
2204       },
2205       'nth-last-of-type': function(m) {
2206         return Selector.xpath.pseudos.nth("(last() + 1 - position()) ", m);
2207       },
2208       'first-of-type':  function(m) {
2209         m[6] = "1"; return Selector.xpath.pseudos['nth-of-type'](m);
2210       },
2211       'last-of-type':   function(m) {
2212         m[6] = "1"; return Selector.xpath.pseudos['nth-last-of-type'](m);
2213       },
2214       'only-of-type':   function(m) {
2215         var p = Selector.xpath.pseudos; return p['first-of-type'](m) + p['last-of-type'](m);
2216       },
2217       nth: function(fragment, m) {
2218         var mm, formula = m[6], predicate;
2219         if (formula == 'even') formula = '2n+0';
2220         if (formula == 'odd')  formula = '2n+1';
2221         if (mm = formula.match(/^(\d+)$/)) // digit only
2222           return '[' + fragment + "= " + mm[1] + ']';
2223         if (mm = formula.match(/^(-?\d*)?n(([+-])(\d+))?/)) { // an+b
2224           if (mm[1] == "-") mm[1] = -1;
2225           var a = mm[1] ? Number(mm[1]) : 1;
2226           var b = mm[2] ? Number(mm[2]) : 0;
2227           predicate = "[((#{fragment} - #{b}) mod #{a} = 0) and " +
2228           "((#{fragment} - #{b}) div #{a} >= 0)]";
2229           return new Template(predicate).evaluate({
2230             fragment: fragment, a: a, b: b });
2231         }
2232       }
2233     }
2234   },
2235
2236   criteria: {
2237     tagName:      'n = h.tagName(n, r, "#{1}", c);   c = false;',
2238     className:    'n = h.className(n, r, "#{1}", c); c = false;',
2239     id:           'n = h.id(n, r, "#{1}", c);        c = false;',
2240     attrPresence: 'n = h.attrPresence(n, r, "#{1}"); c = false;',
2241     attr: function(m) {
2242       m[3] = (m[5] || m[6]);
2243       return new Template('n = h.attr(n, r, "#{1}", "#{3}", "#{2}"); c = false;').evaluate(m);
2244     },
2245     pseudo:       function(m) {
2246       if (m[6]) m[6] = m[6].replace(/"/g, '\\"');
2247       return new Template('n = h.pseudo(n, "#{1}", "#{6}", r, c); c = false;').evaluate(m);
2248     },
2249     descendant:   'c = "descendant";',
2250     child:        'c = "child";',
2251     adjacent:     'c = "adjacent";',
2252     laterSibling: 'c = "laterSibling";'
2253   },
2254
2255   patterns: {
2256     // combinators must be listed first
2257     // (and descendant needs to be last combinator)
2258     laterSibling: /^\s*~\s*/,
2259     child:        /^\s*>\s*/,
2260     adjacent:     /^\s*\+\s*/,
2261     descendant:   /^\s/,
2262
2263     // selectors follow
2264     tagName:      /^\s*(\*|[\w\-]+)(\b|$)?/,
2265     id:           /^#([\w\-\*]+)(\b|$)/,
2266     className:    /^\.([\w\-\*]+)(\b|$)/,
2267     pseudo:       /^:((first|last|nth|nth-last|only)(-child|-of-type)|empty|checked|(en|dis)abled|not)(\((.*?)\))?(\b|$|\s|(?=:))/,
2268     attrPresence: /^\[([\w]+)\]/,
2269     attr:         /\[((?:[\w-]*:)?[\w-]+)\s*(?:([!^$*~|]?=)\s*((['"])([^\]]*?)\4|([^'"][^\]]*?)))?\]/
2270   },
2271
2272   handlers: {
2273     // UTILITY FUNCTIONS
2274     // joins two collections
2275     concat: function(a, b) {
2276       for (var i = 0, node; node = b[i]; i++)
2277         a.push(node);
2278       return a;
2279     },
2280
2281     // marks an array of nodes for counting
2282     mark: function(nodes) {
2283       for (var i = 0, node; node = nodes[i]; i++)
2284         node._counted = true;
2285       return nodes;
2286     },
2287
2288     unmark: function(nodes) {
2289       for (var i = 0, node; node = nodes[i]; i++)
2290         node._counted = undefined;
2291       return nodes;
2292     },
2293
2294     // mark each child node with its position (for nth calls)
2295     // "ofType" flag indicates whether we're indexing for nth-of-type
2296     // rather than nth-child
2297     index: function(parentNode, reverse, ofType) {
2298       parentNode._counted = true;
2299       if (reverse) {
2300         for (var nodes = parentNode.childNodes, i = nodes.length - 1, j = 1; i >= 0; i--) {
2301           node = nodes[i];
2302           if (node.nodeType == 1 && (!ofType || node._counted)) node.nodeIndex = j++;
2303         }
2304       } else {
2305         for (var i = 0, j = 1, nodes = parentNode.childNodes; node = nodes[i]; i++)
2306           if (node.nodeType == 1 && (!ofType || node._counted)) node.nodeIndex = j++;
2307       }
2308     },
2309
2310     // filters out duplicates and extends all nodes
2311     unique: function(nodes) {
2312       if (nodes.length == 0) return nodes;
2313       var results = [], n;
2314       for (var i = 0, l = nodes.length; i < l; i++)
2315         if (!(n = nodes[i])._counted) {
2316           n._counted = true;
2317           results.push(Element.extend(n));
2318         }
2319       return Selector.handlers.unmark(results);
2320     },
2321
2322     // COMBINATOR FUNCTIONS
2323     descendant: function(nodes) {
2324       var h = Selector.handlers;
2325       for (var i = 0, results = [], node; node = nodes[i]; i++)
2326         h.concat(results, node.getElementsByTagName('*'));
2327       return results;
2328     },
2329
2330     child: function(nodes) {
2331       var h = Selector.handlers;
2332       for (var i = 0, results = [], node; node = nodes[i]; i++) {
2333         for (var j = 0, children = [], child; child = node.childNodes[j]; j++)
2334           if (child.nodeType == 1 && child.tagName != '!') results.push(child);
2335       }
2336       return results;
2337     },
2338
2339     adjacent: function(nodes) {
2340       for (var i = 0, results = [], node; node = nodes[i]; i++) {
2341         var next = this.nextElementSibling(node);
2342         if (next) results.push(next);
2343       }
2344       return results;
2345     },
2346
2347     laterSibling: function(nodes) {
2348       var h = Selector.handlers;
2349       for (var i = 0, results = [], node; node = nodes[i]; i++)
2350         h.concat(results, Element.nextSiblings(node));
2351       return results;
2352     },
2353
2354     nextElementSibling: function(node) {
2355       while (node = node.nextSibling)
2356           if (node.nodeType == 1) return node;
2357       return null;
2358     },
2359
2360     previousElementSibling: function(node) {
2361       while (node = node.previousSibling)
2362         if (node.nodeType == 1) return node;
2363       return null;
2364     },
2365
2366     // TOKEN FUNCTIONS
2367     tagName: function(nodes, root, tagName, combinator) {
2368       tagName = tagName.toUpperCase();
2369       var results = [], h = Selector.handlers;
2370       if (nodes) {
2371         if (combinator) {
2372           // fastlane for ordinary descendant combinators
2373           if (combinator == "descendant") {
2374             for (var i = 0, node; node = nodes[i]; i++)
2375               h.concat(results, node.getElementsByTagName(tagName));
2376             return results;
2377           } else nodes = this[combinator](nodes);
2378           if (tagName == "*") return nodes;
2379         }
2380         for (var i = 0, node; node = nodes[i]; i++)
2381           if (node.tagName.toUpperCase() == tagName) results.push(node);
2382         return results;
2383       } else return root.getElementsByTagName(tagName);
2384     },
2385
2386     id: function(nodes, root, id, combinator) {
2387       var targetNode = $(id), h = Selector.handlers;
2388       if (!nodes && root == document) return targetNode ? [targetNode] : [];
2389       if (nodes) {
2390         if (combinator) {
2391           if (combinator == 'child') {
2392             for (var i = 0, node; node = nodes[i]; i++)
2393               if (targetNode.parentNode == node) return [targetNode];
2394           } else if (combinator == 'descendant') {
2395             for (var i = 0, node; node = nodes[i]; i++)
2396               if (Element.descendantOf(targetNode, node)) return [targetNode];
2397           } else if (combinator == 'adjacent') {
2398             for (var i = 0, node; node = nodes[i]; i++)
2399               if (Selector.handlers.previousElementSibling(targetNode) == node)
2400                 return [targetNode];
2401           } else nodes = h[combinator](nodes);
2402         }
2403         for (var i = 0, node; node = nodes[i]; i++)
2404           if (node == targetNode) return [targetNode];
2405         return [];
2406       }
2407       return (targetNode && Element.descendantOf(targetNode, root)) ? [targetNode] : [];
2408     },
2409
2410     className: function(nodes, root, className, combinator) {
2411       if (nodes && combinator) nodes = this[combinator](nodes);
2412       return Selector.handlers.byClassName(nodes, root, className);
2413     },
2414
2415     byClassName: function(nodes, root, className) {
2416       if (!nodes) nodes = Selector.handlers.descendant([root]);
2417       var needle = ' ' + className + ' ';
2418       for (var i = 0, results = [], node, nodeClassName; node = nodes[i]; i++) {
2419         nodeClassName = node.className;
2420         if (nodeClassName.length == 0) continue;
2421         if (nodeClassName == className || (' ' + nodeClassName + ' ').include(needle))
2422           results.push(node);
2423       }
2424       return results;
2425     },
2426
2427     attrPresence: function(nodes, root, attr) {
2428       var results = [];
2429       for (var i = 0, node; node = nodes[i]; i++)
2430         if (Element.hasAttribute(node, attr)) results.push(node);
2431       return results;
2432     },
2433
2434     attr: function(nodes, root, attr, value, operator) {
2435       if (!nodes) nodes = root.getElementsByTagName("*");
2436       var handler = Selector.operators[operator], results = [];
2437       for (var i = 0, node; node = nodes[i]; i++) {
2438         var nodeValue = Element.readAttribute(node, attr);
2439         if (nodeValue === null) continue;
2440         if (handler(nodeValue, value)) results.push(node);
2441       }
2442       return results;
2443     },
2444
2445     pseudo: function(nodes, name, value, root, combinator) {
2446       if (nodes && combinator) nodes = this[combinator](nodes);
2447       if (!nodes) nodes = root.getElementsByTagName("*");
2448       return Selector.pseudos[name](nodes, value, root);
2449     }
2450   },
2451
2452   pseudos: {
2453     'first-child': function(nodes, value, root) {
2454       for (var i = 0, results = [], node; node = nodes[i]; i++) {
2455         if (Selector.handlers.previousElementSibling(node)) continue;
2456           results.push(node);
2457       }
2458       return results;
2459     },
2460     'last-child': function(nodes, value, root) {
2461       for (var i = 0, results = [], node; node = nodes[i]; i++) {
2462         if (Selector.handlers.nextElementSibling(node)) continue;
2463           results.push(node);
2464       }
2465       return results;
2466     },
2467     'only-child': function(nodes, value, root) {
2468       var h = Selector.handlers;
2469       for (var i = 0, results = [], node; node = nodes[i]; i++)
2470         if (!h.previousElementSibling(node) && !h.nextElementSibling(node))
2471           results.push(node);
2472       return results;
2473     },
2474     'nth-child':        function(nodes, formula, root) {
2475       return Selector.pseudos.nth(nodes, formula, root);
2476     },
2477     'nth-last-child':   function(nodes, formula, root) {
2478       return Selector.pseudos.nth(nodes, formula, root, true);
2479     },
2480     'nth-of-type':      function(nodes, formula, root) {
2481       return Selector.pseudos.nth(nodes, formula, root, false, true);
2482     },
2483     'nth-last-of-type': function(nodes, formula, root) {
2484       return Selector.pseudos.nth(nodes, formula, root, true, true);
2485     },
2486     'first-of-type':    function(nodes, formula, root) {
2487       return Selector.pseudos.nth(nodes, "1", root, false, true);
2488     },
2489     'last-of-type':     function(nodes, formula, root) {
2490       return Selector.pseudos.nth(nodes, "1", root, true, true);
2491     },
2492     'only-of-type':     function(nodes, formula, root) {
2493       var p = Selector.pseudos;
2494       return p['last-of-type'](p['first-of-type'](nodes, formula, root), formula, root);
2495     },
2496
2497     // handles the an+b logic
2498     getIndices: function(a, b, total) {
2499       if (a == 0) return b > 0 ? [b] : [];
2500       return $R(1, total).inject([], function(memo, i) {
2501         if (0 == (i - b) % a && (i - b) / a >= 0) memo.push(i);
2502         return memo;
2503       });
2504     },
2505
2506     // handles nth(-last)-child, nth(-last)-of-type, and (first|last)-of-type
2507     nth: function(nodes, formula, root, reverse, ofType) {
2508       if (nodes.length == 0) return [];
2509       if (formula == 'even') formula = '2n+0';
2510       if (formula == 'odd')  formula = '2n+1';
2511       var h = Selector.handlers, results = [], indexed = [], m;
2512       h.mark(nodes);
2513       for (var i = 0, node; node = nodes[i]; i++) {
2514         if (!node.parentNode._counted) {
2515           h.index(node.parentNode, reverse, ofType);
2516           indexed.push(node.parentNode);
2517         }
2518       }
2519       if (formula.match(/^\d+$/)) { // just a number
2520         formula = Number(formula);
2521         for (var i = 0, node; node = nodes[i]; i++)
2522           if (node.nodeIndex == formula) results.push(node);
2523       } else if (m = formula.match(/^(-?\d*)?n(([+-])(\d+))?/)) { // an+b
2524         if (m[1] == "-") m[1] = -1;
2525         var a = m[1] ? Number(m[1]) : 1;
2526         var b = m[2] ? Number(m[2]) : 0;
2527         var indices = Selector.pseudos.getIndices(a, b, nodes.length);
2528         for (var i = 0, node, l = indices.length; node = nodes[i]; i++) {
2529           for (var j = 0; j < l; j++)
2530             if (node.nodeIndex == indices[j]) results.push(node);
2531         }
2532       }
2533       h.unmark(nodes);
2534       h.unmark(indexed);
2535       return results;
2536     },
2537
2538     'empty': function(nodes, value, root) {
2539       for (var i = 0, results = [], node; node = nodes[i]; i++) {
2540         // IE treats comments as element nodes
2541         if (node.tagName == '!' || (node.firstChild && !node.innerHTML.match(/^\s*$/))) continue;
2542         results.push(node);
2543       }
2544       return results;
2545     },
2546
2547     'not': function(nodes, selector, root) {
2548       var h = Selector.handlers, selectorType, m;
2549       var exclusions = new Selector(selector).findElements(root);
2550       h.mark(exclusions);
2551       for (var i = 0, results = [], node; node = nodes[i]; i++)
2552         if (!node._counted) results.push(node);
2553       h.unmark(exclusions);
2554       return results;
2555     },
2556
2557     'enabled': function(nodes, value, root) {
2558       for (var i = 0, results = [], node; node = nodes[i]; i++)
2559         if (!node.disabled) results.push(node);
2560       return results;
2561     },
2562
2563     'disabled': function(nodes, value, root) {
2564       for (var i = 0, results = [], node; node = nodes[i]; i++)
2565         if (node.disabled) results.push(node);
2566       return results;
2567     },
2568
2569     'checked': function(nodes, value, root) {
2570       for (var i = 0, results = [], node; node = nodes[i]; i++)
2571         if (node.checked) results.push(node);
2572       return results;
2573     }
2574   },
2575
2576   operators: {
2577     '=':  function(nv, v) { return nv == v; },
2578     '!=': function(nv, v) { return nv != v; },
2579     '^=': function(nv, v) { return nv.startsWith(v); },
2580     '$=': function(nv, v) { return nv.endsWith(v); },
2581     '*=': function(nv, v) { return nv.include(v); },
2582     '~=': function(nv, v) { return (' ' + nv + ' ').include(' ' + v + ' '); },
2583     '|=': function(nv, v) { return ('-' + nv.toUpperCase() + '-').include('-' + v.toUpperCase() + '-'); }
2584   },
2585
2586   matchElements: function(elements, expression) {
2587     var matches = new Selector(expression).findElements(), h = Selector.handlers;
2588     h.mark(matches);
2589     for (var i = 0, results = [], element; element = elements[i]; i++)
2590       if (element._counted) results.push(element);
2591     h.unmark(matches);
2592     return results;
2593   },
2594
2595   findElement: function(elements, expression, index) {
2596     if (typeof expression == 'number') {
2597       index = expression; expression = false;
2598     }
2599     return Selector.matchElements(elements, expression || '*')[index || 0];
2600   },
2601
2602   findChildElements: function(element, expressions) {
2603     var exprs = expressions.join(','), expressions = [];
2604     exprs.scan(/(([\w#:.~>+()\s-]+|\*|\[.*?\])+)\s*(,|$)/, function(m) {
2605       expressions.push(m[1].strip());
2606     });
2607     var results = [], h = Selector.handlers;
2608     for (var i = 0, l = expressions.length, selector; i < l; i++) {
2609       selector = new Selector(expressions[i].strip());
2610       h.concat(results, selector.findElements(element));
2611     }
2612     return (l > 1) ? h.unique(results) : results;
2613   }
2614 });
2615
2616 function $$() {
2617   return Selector.findChildElements(document, $A(arguments));
2618 }
2619 var Form = {
2620   reset: function(form) {
2621     $(form).reset();
2622     return form;
2623   },
2624
2625   serializeElements: function(elements, getHash) {
2626     var data = elements.inject({}, function(result, element) {
2627       if (!element.disabled && element.name) {
2628         var key = element.name, value = $(element).getValue();
2629         if (value != null) {
2630              if (key in result) {
2631             if (result[key].constructor != Array) result[key] = [result[key]];
2632             result[key].push(value);
2633           }
2634           else result[key] = value;
2635         }
2636       }
2637       return result;
2638     });
2639
2640     return getHash ? data : Hash.toQueryString(data);
2641   }
2642 };
2643
2644 Form.Methods = {
2645   serialize: function(form, getHash) {
2646     return Form.serializeElements(Form.getElements(form), getHash);
2647   },
2648
2649   getElements: function(form) {
2650     return $A($(form).getElementsByTagName('*')).inject([],
2651       function(elements, child) {
2652         if (Form.Element.Serializers[child.tagName.toLowerCase()])
2653           elements.push(Element.extend(child));
2654         return elements;
2655       }
2656     );
2657   },
2658
2659   getInputs: function(form, typeName, name) {
2660     form = $(form);
2661     var inputs = form.getElementsByTagName('input');
2662
2663     if (!typeName && !name) return $A(inputs).map(Element.extend);
2664
2665     for (var i = 0, matchingInputs = [], length = inputs.length; i < length; i++) {
2666       var input = inputs[i];
2667       if ((typeName && input.type != typeName) || (name && input.name != name))
2668         continue;
2669       matchingInputs.push(Element.extend(input));
2670     }
2671
2672     return matchingInputs;
2673   },
2674
2675   disable: function(form) {
2676     form = $(form);
2677     Form.getElements(form).invoke('disable');
2678     return form;
2679   },
2680
2681   enable: function(form) {
2682     form = $(form);
2683     Form.getElements(form).invoke('enable');
2684     return form;
2685   },
2686
2687   findFirstElement: function(form) {
2688     return $(form).getElements().find(function(element) {
2689       return element.type != 'hidden' && !element.disabled &&
2690         ['input', 'select', 'textarea'].include(element.tagName.toLowerCase());
2691     });
2692   },
2693
2694   focusFirstElement: function(form) {
2695     form = $(form);
2696     form.findFirstElement().activate();
2697     return form;
2698   },
2699
2700   request: function(form, options) {
2701     form = $(form), options = Object.clone(options || {});
2702
2703     var params = options.parameters;
2704     options.parameters = form.serialize(true);
2705
2706     if (params) {
2707       if (typeof params == 'string') params = params.toQueryParams();
2708       Object.extend(options.parameters, params);
2709     }
2710
2711     if (form.hasAttribute('method') && !options.method)
2712       options.method = form.method;
2713
2714     return new Ajax.Request(form.readAttribute('action'), options);
2715   }
2716 }
2717
2718 /*--------------------------------------------------------------------------*/
2719
2720 Form.Element = {
2721   focus: function(element) {
2722     $(element).focus();
2723     return element;
2724   },
2725
2726   select: function(element) {
2727     $(element).select();
2728     return element;
2729   }
2730 }
2731
2732 Form.Element.Methods = {
2733   serialize: function(element) {
2734     element = $(element);
2735     if (!element.disabled && element.name) {
2736       var value = element.getValue();
2737       if (value != undefined) {
2738         var pair = {};
2739         pair[element.name] = value;
2740         return Hash.toQueryString(pair);
2741       }
2742     }
2743     return '';
2744   },
2745
2746   getValue: function(element) {
2747     element = $(element);
2748     var method = element.tagName.toLowerCase();
2749     return Form.Element.Serializers[method](element);
2750   },
2751
2752   clear: function(element) {
2753     $(element).value = '';
2754     return element;
2755   },
2756
2757   present: function(element) {
2758     return $(element).value != '';
2759   },
2760
2761   activate: function(element) {
2762     element = $(element);
2763     try {
2764       element.focus();
2765       if (element.select && (element.tagName.toLowerCase() != 'input' ||
2766         !['button', 'reset', 'submit'].include(element.type)))
2767         element.select();
2768     } catch (e) {}
2769     return element;
2770   },
2771
2772   disable: function(element) {
2773     element = $(element);
2774     element.blur();
2775     element.disabled = true;
2776     return element;
2777   },
2778
2779   enable: function(element) {
2780     element = $(element);
2781     element.disabled = false;
2782     return element;
2783   }
2784 }
2785
2786 /*--------------------------------------------------------------------------*/
2787
2788 var Field = Form.Element;
2789 var $F = Form.Element.Methods.getValue;
2790
2791 /*--------------------------------------------------------------------------*/
2792
2793 Form.Element.Serializers = {
2794   input: function(element) {
2795     switch (element.type.toLowerCase()) {
2796       case 'checkbox':
2797       case 'radio':
2798         return Form.Element.Serializers.inputSelector(element);
2799       default:
2800         return Form.Element.Serializers.textarea(element);
2801     }
2802   },
2803
2804   inputSelector: function(element) {
2805     return element.checked ? element.value : null;
2806   },
2807
2808   textarea: function(element) {
2809     return element.value;
2810   },
2811
2812   select: function(element) {
2813     return this[element.type == 'select-one' ?
2814       'selectOne' : 'selectMany'](element);
2815   },
2816
2817   selectOne: function(element) {
2818     var index = element.selectedIndex;
2819     return index >= 0 ? this.optionValue(element.options[index]) : null;
2820   },
2821
2822   selectMany: function(element) {
2823     var values, length = element.length;
2824     if (!length) return null;
2825
2826     for (var i = 0, values = []; i < length; i++) {
2827       var opt = element.options[i];
2828       if (opt.selected) values.push(this.optionValue(opt));
2829     }
2830     return values;
2831   },
2832
2833   optionValue: function(opt) {
2834     // extend element because hasAttribute may not be native
2835     return Element.extend(opt).hasAttribute('value') ? opt.value : opt.text;
2836   }
2837 }
2838
2839 /*--------------------------------------------------------------------------*/
2840
2841 Abstract.TimedObserver = function() {}
2842 Abstract.TimedObserver.prototype = {
2843   initialize: function(element, frequency, callback) {
2844     this.frequency = frequency;
2845     this.element   = $(element);
2846     this.callback  = callback;
2847
2848     this.lastValue = this.getValue();
2849     this.registerCallback();
2850   },
2851
2852   registerCallback: function() {
2853     setInterval(this.onTimerEvent.bind(this), this.frequency * 1000);
2854   },
2855
2856   onTimerEvent: function() {
2857     var value = this.getValue();
2858     var changed = ('string' == typeof this.lastValue && 'string' == typeof value
2859       ? this.lastValue != value : String(this.lastValue) != String(value));
2860     if (changed) {
2861       this.callback(this.element, value);
2862       this.lastValue = value;
2863     }
2864   }
2865 }
2866
2867 Form.Element.Observer = Class.create();
2868 Form.Element.Observer.prototype = Object.extend(new Abstract.TimedObserver(), {
2869   getValue: function() {
2870     return Form.Element.getValue(this.element);
2871   }
2872 });
2873
2874 Form.Observer = Class.create();
2875 Form.Observer.prototype = Object.extend(new Abstract.TimedObserver(), {
2876   getValue: function() {
2877     return Form.serialize(this.element);
2878   }
2879 });
2880
2881 /*--------------------------------------------------------------------------*/
2882
2883 Abstract.EventObserver = function() {}
2884 Abstract.EventObserver.prototype = {
2885   initialize: function(element, callback) {
2886     this.element  = $(element);
2887     this.callback = callback;
2888
2889     this.lastValue = this.getValue();
2890     if (this.element.tagName.toLowerCase() == 'form')
2891       this.registerFormCallbacks();
2892     else
2893       this.registerCallback(this.element);
2894   },
2895
2896   onElementEvent: function() {
2897     var value = this.getValue();
2898     if (this.lastValue != value) {
2899       this.callback(this.element, value);
2900       this.lastValue = value;
2901     }
2902   },
2903
2904   registerFormCallbacks: function() {
2905     Form.getElements(this.element).each(this.registerCallback.bind(this));
2906   },
2907
2908   registerCallback: function(element) {
2909     if (element.type) {
2910       switch (element.type.toLowerCase()) {
2911         case 'checkbox':
2912         case 'radio':
2913           Event.observe(element, 'click', this.onElementEvent.bind(this));
2914           break;
2915         default:
2916           Event.observe(element, 'change', this.onElementEvent.bind(this));
2917           break;
2918       }
2919     }
2920   }
2921 }
2922
2923 Form.Element.EventObserver = Class.create();
2924 Form.Element.EventObserver.prototype = Object.extend(new Abstract.EventObserver(), {
2925   getValue: function() {
2926     return Form.Element.getValue(this.element);
2927   }
2928 });
2929
2930 Form.EventObserver = Class.create();
2931 Form.EventObserver.prototype = Object.extend(new Abstract.EventObserver(), {
2932   getValue: function() {
2933     return Form.serialize(this.element);
2934   }
2935 });
2936 if (!window.Event) {
2937   var Event = new Object();
2938 }
2939
2940 Object.extend(Event, {
2941   KEY_BACKSPACE: 8,
2942   KEY_TAB:       9,
2943   KEY_RETURN:   13,
2944   KEY_ESC:      27,
2945   KEY_LEFT:     37,
2946   KEY_UP:       38,
2947   KEY_RIGHT:    39,
2948   KEY_DOWN:     40,
2949   KEY_DELETE:   46,
2950   KEY_HOME:     36,
2951   KEY_END:      35,
2952   KEY_PAGEUP:   33,
2953   KEY_PAGEDOWN: 34,
2954
2955   element: function(event) {
2956     return $(event.target || event.srcElement);
2957   },
2958
2959   isLeftClick: function(event) {
2960     return (((event.which) && (event.which == 1)) ||
2961             ((event.button) && (event.button == 1)));
2962   },
2963
2964   pointerX: function(event) {
2965     return event.pageX || (event.clientX +
2966       (document.documentElement.scrollLeft || document.body.scrollLeft));
2967   },
2968
2969   pointerY: function(event) {
2970     return event.pageY || (event.clientY +
2971       (document.documentElement.scrollTop || document.body.scrollTop));
2972   },
2973
2974   stop: function(event) {
2975     if (event.preventDefault) {
2976       event.preventDefault();
2977       event.stopPropagation();
2978     } else {
2979       event.returnValue = false;
2980       event.cancelBubble = true;
2981     }
2982   },
2983
2984   // find the first node with the given tagName, starting from the
2985   // node the event was triggered on; traverses the DOM upwards
2986   findElement: function(event, tagName) {
2987     var element = Event.element(event);
2988     while (element.parentNode && (!element.tagName ||
2989         (element.tagName.toUpperCase() != tagName.toUpperCase())))
2990       element = element.parentNode;
2991     return element;
2992   },
2993
2994   observers: false,
2995
2996   _observeAndCache: function(element, name, observer, useCapture) {
2997     if (!this.observers) this.observers = [];
2998     if (element.addEventListener) {
2999       this.observers.push([element, name, observer, useCapture]);
3000       element.addEventListener(name, observer, useCapture);
3001     } else if (element.attachEvent) {
3002       this.observers.push([element, name, observer, useCapture]);
3003       element.attachEvent('on' + name, observer);
3004     }
3005   },
3006
3007   unloadCache: function() {
3008     if (!Event.observers) return;
3009     for (var i = 0, length = Event.observers.length; i < length; i++) {
3010       Event.stopObserving.apply(this, Event.observers[i]);
3011       Event.observers[i][0] = null;
3012     }
3013     Event.observers = false;
3014   },
3015
3016   observe: function(element, name, observer, useCapture) {
3017     element = $(element);
3018     useCapture = useCapture || false;
3019
3020     if (name == 'keypress' &&
3021       (Prototype.Browser.WebKit || element.attachEvent))
3022       name = 'keydown';
3023
3024     Event._observeAndCache(element, name, observer, useCapture);
3025   },
3026
3027   stopObserving: function(element, name, observer, useCapture) {
3028     element = $(element);
3029     useCapture = useCapture || false;
3030
3031     if (name == 'keypress' &&
3032         (Prototype.Browser.WebKit || element.attachEvent))
3033       name = 'keydown';
3034
3035     if (element.removeEventListener) {
3036       element.removeEventListener(name, observer, useCapture);
3037     } else if (element.detachEvent) {
3038       try {
3039         element.detachEvent('on' + name, observer);
3040       } catch (e) {}
3041     }
3042   }
3043 });
3044
3045 /* prevent memory leaks in IE */
3046 if (Prototype.Browser.IE)
3047   Event.observe(window, 'unload', Event.unloadCache, false);
3048 var Position = {
3049   // set to true if needed, warning: firefox performance problems
3050   // NOT neeeded for page scrolling, only if draggable contained in
3051   // scrollable elements
3052   includeScrollOffsets: false,
3053
3054   // must be called before calling withinIncludingScrolloffset, every time the
3055   // page is scrolled
3056   prepare: function() {
3057     this.deltaX =  window.pageXOffset
3058                 || document.documentElement.scrollLeft
3059                 || document.body.scrollLeft
3060                 || 0;
3061     this.deltaY =  window.pageYOffset
3062                 || document.documentElement.scrollTop
3063                 || document.body.scrollTop
3064                 || 0;
3065   },
3066
3067   realOffset: function(element) {
3068     var valueT = 0, valueL = 0;
3069     do {
3070       valueT += element.scrollTop  || 0;
3071       valueL += element.scrollLeft || 0;
3072       element = element.parentNode;
3073     } while (element);
3074     return [valueL, valueT];
3075   },
3076
3077   cumulativeOffset: function(element) {
3078     var valueT = 0, valueL = 0;
3079     do {
3080       valueT += element.offsetTop  || 0;
3081       valueL += element.offsetLeft || 0;
3082       element = element.offsetParent;
3083     } while (element);
3084     return [valueL, valueT];
3085   },
3086
3087   positionedOffset: function(element) {
3088     var valueT = 0, valueL = 0;
3089     do {
3090       valueT += element.offsetTop  || 0;
3091       valueL += element.offsetLeft || 0;
3092       element = element.offsetParent;
3093       if (element) {
3094         if(element.tagName=='BODY') break;
3095         var p = Element.getStyle(element, 'position');
3096         if (p == 'relative' || p == 'absolute') break;
3097       }
3098     } while (element);
3099     return [valueL, valueT];
3100   },
3101
3102   offsetParent: function(element) {
3103     if (element.offsetParent) return element.offsetParent;
3104     if (element == document.body) return element;
3105
3106     while ((element = element.parentNode) && element != document.body)
3107       if (Element.getStyle(element, 'position') != 'static')
3108         return element;
3109
3110     return document.body;
3111   },
3112
3113   // caches x/y coordinate pair to use with overlap
3114   within: function(element, x, y) {
3115     if (this.includeScrollOffsets)
3116       return this.withinIncludingScrolloffsets(element, x, y);
3117     this.xcomp = x;
3118     this.ycomp = y;
3119     this.offset = this.cumulativeOffset(element);
3120
3121     return (y >= this.offset[1] &&
3122             y <  this.offset[1] + element.offsetHeight &&
3123             x >= this.offset[0] &&
3124             x <  this.offset[0] + element.offsetWidth);
3125   },
3126
3127   withinIncludingScrolloffsets: function(element, x, y) {
3128     var offsetcache = this.realOffset(element);
3129
3130     this.xcomp = x + offsetcache[0] - this.deltaX;
3131     this.ycomp = y + offsetcache[1] - this.deltaY;
3132     this.offset = this.cumulativeOffset(element);
3133
3134     return (this.ycomp >= this.offset[1] &&
3135             this.ycomp <  this.offset[1] + element.offsetHeight &&
3136             this.xcomp >= this.offset[0] &&
3137             this.xcomp <  this.offset[0] + element.offsetWidth);
3138   },
3139
3140   // within must be called directly before
3141   overlap: function(mode, element) {
3142     if (!mode) return 0;
3143     if (mode == 'vertical')
3144       return ((this.offset[1] + element.offsetHeight) - this.ycomp) /
3145         element.offsetHeight;
3146     if (mode == 'horizontal')
3147       return ((this.offset[0] + element.offsetWidth) - this.xcomp) /
3148         element.offsetWidth;
3149   },
3150
3151   page: function(forElement) {
3152     var valueT = 0, valueL = 0;
3153
3154     var element = forElement;
3155     do {
3156       valueT += element.offsetTop  || 0;
3157       valueL += element.offsetLeft || 0;
3158
3159       // Safari fix
3160       if (element.offsetParent == document.body)
3161         if (Element.getStyle(element,'position')=='absolute') break;
3162
3163     } while (element = element.offsetParent);
3164
3165     element = forElement;
3166     do {
3167       if (!window.opera || element.tagName=='BODY') {
3168         valueT -= element.scrollTop  || 0;
3169         valueL -= element.scrollLeft || 0;
3170       }
3171     } while (element = element.parentNode);
3172
3173     return [valueL, valueT];
3174   },
3175
3176   clone: function(source, target) {
3177     var options = Object.extend({
3178       setLeft:    true,
3179       setTop:     true,
3180       setWidth:   true,
3181       setHeight:  true,
3182       offsetTop:  0,
3183       offsetLeft: 0
3184     }, arguments[2] || {})
3185
3186     // find page position of source
3187     source = $(source);
3188     var p = Position.page(source);
3189
3190     // find coordinate system to use
3191     target = $(target);
3192     var delta = [0, 0];
3193     var parent = null;
3194     // delta [0,0] will do fine with position: fixed elements,
3195     // position:absolute needs offsetParent deltas
3196     if (Element.getStyle(target,'position') == 'absolute') {
3197       parent = Position.offsetParent(target);
3198       delta = Position.page(parent);
3199     }
3200
3201     // correct by body offsets (fixes Safari)
3202     if (parent == document.body) {
3203       delta[0] -= document.body.offsetLeft;
3204       delta[1] -= document.body.offsetTop;
3205     }
3206
3207     // set position
3208     if(options.setLeft)   target.style.left  = (p[0] - delta[0] + options.offsetLeft) + 'px';
3209     if(options.setTop)    target.style.top   = (p[1] - delta[1] + options.offsetTop) + 'px';
3210     if(options.setWidth)  target.style.width = source.offsetWidth + 'px';
3211     if(options.setHeight) target.style.height = source.offsetHeight + 'px';
3212   },
3213
3214   absolutize: function(element) {
3215     element = $(element);
3216     if (element.style.position == 'absolute') return;
3217     Position.prepare();
3218
3219     var offsets = Position.positionedOffset(element);
3220     var top     = offsets[1];
3221     var left    = offsets[0];
3222     var width   = element.clientWidth;
3223     var height  = element.clientHeight;
3224
3225     element._originalLeft   = left - parseFloat(element.style.left  || 0);
3226     element._originalTop    = top  - parseFloat(element.style.top || 0);
3227     element._originalWidth  = element.style.width;
3228     element._originalHeight = element.style.height;
3229
3230     element.style.position = 'absolute';
3231     element.style.top    = top + 'px';
3232     element.style.left   = left + 'px';
3233     element.style.width  = width + 'px';
3234     element.style.height = height + 'px';
3235   },
3236
3237   relativize: function(element) {
3238     element = $(element);
3239     if (element.style.position == 'relative') return;
3240     Position.prepare();
3241
3242     element.style.position = 'relative';
3243     var top  = parseFloat(element.style.top  || 0) - (element._originalTop || 0);
3244     var left = parseFloat(element.style.left || 0) - (element._originalLeft || 0);
3245
3246     element.style.top    = top + 'px';
3247     element.style.left   = left + 'px';
3248     element.style.height = element._originalHeight;
3249     element.style.width  = element._originalWidth;
3250   }
3251 }
3252
3253 // Safari returns margins on body which is incorrect if the child is absolutely
3254 // positioned.  For performance reasons, redefine Position.cumulativeOffset for
3255 // KHTML/WebKit only.
3256 if (Prototype.Browser.WebKit) {
3257   Position.cumulativeOffset = function(element) {
3258     var valueT = 0, valueL = 0;
3259     do {
3260       valueT += element.offsetTop  || 0;
3261       valueL += element.offsetLeft || 0;
3262       if (element.offsetParent == document.body)
3263         if (Element.getStyle(element, 'position') == 'absolute') break;
3264
3265       element = element.offsetParent;
3266     } while (element);
3267
3268     return [valueL, valueT];
3269   }
3270 }
3271
3272 Element.addMethods();