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

Benjamin Mako Hill || Want to submit a patch?