]> scripts.mit.edu Git - autoinstalls/wordpress.git/blob - wp-includes/js/scriptaculous/prototype.js
Wordpress 2.5.1
[autoinstalls/wordpress.git] / wp-includes / js / scriptaculous / prototype.js
1 /*  Prototype JavaScript framework, version 1.6.0
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.6.0',
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     MobileSafari: !!navigator.userAgent.match(/Apple.*Mobile.*Safari/)
18   },
19
20   BrowserFeatures: {
21     XPath: !!document.evaluate,
22     ElementExtensions: !!window.HTMLElement,
23     SpecificElementExtensions:
24       document.createElement('div').__proto__ &&
25       document.createElement('div').__proto__ !==
26         document.createElement('form').__proto__
27   },
28
29   ScriptFragment: '<script[^>]*>([\\S\\s]*?)<\/script>',
30   JSONFilter: /^\/\*-secure-([\s\S]*)\*\/\s*$/,
31
32   emptyFunction: function() { },
33   K: function(x) { return x }
34 };
35
36 if (Prototype.Browser.MobileSafari)
37   Prototype.BrowserFeatures.SpecificElementExtensions = false;
38
39 if (Prototype.Browser.WebKit)
40   Prototype.BrowserFeatures.XPath = false;
41
42 /* Based on Alex Arnell's inheritance implementation. */
43 var Class = {
44   create: function() {
45     var parent = null, properties = $A(arguments);
46     if (Object.isFunction(properties[0]))
47       parent = properties.shift();
48
49     function klass() {
50       this.initialize.apply(this, arguments);
51     }
52
53     Object.extend(klass, Class.Methods);
54     klass.superclass = parent;
55     klass.subclasses = [];
56
57     if (parent) {
58       var subclass = function() { };
59       subclass.prototype = parent.prototype;
60       klass.prototype = new subclass;
61       parent.subclasses.push(klass);
62     }
63
64     for (var i = 0; i < properties.length; i++)
65       klass.addMethods(properties[i]);
66
67     if (!klass.prototype.initialize)
68       klass.prototype.initialize = Prototype.emptyFunction;
69
70     klass.prototype.constructor = klass;
71
72     return klass;
73   }
74 };
75
76 Class.Methods = {
77   addMethods: function(source) {
78     var ancestor   = this.superclass && this.superclass.prototype;
79     var properties = Object.keys(source);
80
81     if (!Object.keys({ toString: true }).length)
82       properties.push("toString", "valueOf");
83
84     for (var i = 0, length = properties.length; i < length; i++) {
85       var property = properties[i], value = source[property];
86       if (ancestor && Object.isFunction(value) &&
87           value.argumentNames().first() == "$super") {
88         var method = value, value = Object.extend((function(m) {
89           return function() { return ancestor[m].apply(this, arguments) };
90         })(property).wrap(method), {
91           valueOf:  function() { return method },
92           toString: function() { return method.toString() }
93         });
94       }
95       this.prototype[property] = value;
96     }
97
98     return this;
99   }
100 };
101
102 var Abstract = { };
103
104 Object.extend = function(destination, source) {
105   for (var property in source)
106     destination[property] = source[property];
107   return destination;
108 };
109
110 Object.extend(Object, {
111   inspect: function(object) {
112     try {
113       if (object === undefined) return 'undefined';
114       if (object === null) return 'null';
115       return object.inspect ? object.inspect() : object.toString();
116     } catch (e) {
117       if (e instanceof RangeError) return '...';
118       throw e;
119     }
120   },
121
122   toJSON: function(object) {
123     var type = typeof object;
124     switch (type) {
125       case 'undefined':
126       case 'function':
127       case 'unknown': return;
128       case 'boolean': return object.toString();
129     }
130
131     if (object === null) return 'null';
132     if (object.toJSON) return object.toJSON();
133     if (Object.isElement(object)) return;
134
135     var results = [];
136     for (var property in object) {
137       var value = Object.toJSON(object[property]);
138       if (value !== undefined)
139         results.push(property.toJSON() + ': ' + value);
140     }
141
142     return '{' + results.join(', ') + '}';
143   },
144
145   toQueryString: function(object) {
146     return $H(object).toQueryString();
147   },
148
149   toHTML: function(object) {
150     return object && object.toHTML ? object.toHTML() : String.interpret(object);
151   },
152
153   keys: function(object) {
154     var keys = [];
155     for (var property in object)
156       keys.push(property);
157     return keys;
158   },
159
160   values: function(object) {
161     var values = [];
162     for (var property in object)
163       values.push(object[property]);
164     return values;
165   },
166
167   clone: function(object) {
168     return Object.extend({ }, object);
169   },
170
171   isElement: function(object) {
172     return object && object.nodeType == 1;
173   },
174
175   isArray: function(object) {
176     return object && object.constructor === Array;
177   },
178
179   isHash: function(object) {
180     return object instanceof Hash;
181   },
182
183   isFunction: function(object) {
184     return typeof object == "function";
185   },
186
187   isString: function(object) {
188     return typeof object == "string";
189   },
190
191   isNumber: function(object) {
192     return typeof object == "number";
193   },
194
195   isUndefined: function(object) {
196     return typeof object == "undefined";
197   }
198 });
199
200 Object.extend(Function.prototype, {
201   argumentNames: function() {
202     var names = this.toString().match(/^[\s\(]*function[^(]*\((.*?)\)/)[1].split(",").invoke("strip");
203     return names.length == 1 && !names[0] ? [] : names;
204   },
205
206   bind: function() {
207     if (arguments.length < 2 && arguments[0] === undefined) return this;
208     var __method = this, args = $A(arguments), object = args.shift();
209     return function() {
210       return __method.apply(object, args.concat($A(arguments)));
211     }
212   },
213
214   bindAsEventListener: function() {
215     var __method = this, args = $A(arguments), object = args.shift();
216     return function(event) {
217       return __method.apply(object, [event || window.event].concat(args));
218     }
219   },
220
221   curry: function() {
222     if (!arguments.length) return this;
223     var __method = this, args = $A(arguments);
224     return function() {
225       return __method.apply(this, args.concat($A(arguments)));
226     }
227   },
228
229   delay: function() {
230     var __method = this, args = $A(arguments), timeout = args.shift() * 1000;
231     return window.setTimeout(function() {
232       return __method.apply(__method, args);
233     }, timeout);
234   },
235
236   wrap: function(wrapper) {
237     var __method = this;
238     return function() {
239       return wrapper.apply(this, [__method.bind(this)].concat($A(arguments)));
240     }
241   },
242
243   methodize: function() {
244     if (this._methodized) return this._methodized;
245     var __method = this;
246     return this._methodized = function() {
247       return __method.apply(null, [this].concat($A(arguments)));
248     };
249   }
250 });
251
252 Function.prototype.defer = Function.prototype.delay.curry(0.01);
253
254 Date.prototype.toJSON = function() {
255   return '"' + this.getUTCFullYear() + '-' +
256     (this.getUTCMonth() + 1).toPaddedString(2) + '-' +
257     this.getUTCDate().toPaddedString(2) + 'T' +
258     this.getUTCHours().toPaddedString(2) + ':' +
259     this.getUTCMinutes().toPaddedString(2) + ':' +
260     this.getUTCSeconds().toPaddedString(2) + 'Z"';
261 };
262
263 var Try = {
264   these: function() {
265     var returnValue;
266
267     for (var i = 0, length = arguments.length; i < length; i++) {
268       var lambda = arguments[i];
269       try {
270         returnValue = lambda();
271         break;
272       } catch (e) { }
273     }
274
275     return returnValue;
276   }
277 };
278
279 RegExp.prototype.match = RegExp.prototype.test;
280
281 RegExp.escape = function(str) {
282   return String(str).replace(/([.*+?^=!:${}()|[\]\/\\])/g, '\\$1');
283 };
284
285 /*--------------------------------------------------------------------------*/
286
287 var PeriodicalExecuter = Class.create({
288   initialize: function(callback, frequency) {
289     this.callback = callback;
290     this.frequency = frequency;
291     this.currentlyExecuting = false;
292
293     this.registerCallback();
294   },
295
296   registerCallback: function() {
297     this.timer = setInterval(this.onTimerEvent.bind(this), this.frequency * 1000);
298   },
299
300   execute: function() {
301     this.callback(this);
302   },
303
304   stop: function() {
305     if (!this.timer) return;
306     clearInterval(this.timer);
307     this.timer = null;
308   },
309
310   onTimerEvent: function() {
311     if (!this.currentlyExecuting) {
312       try {
313         this.currentlyExecuting = true;
314         this.execute();
315       } finally {
316         this.currentlyExecuting = false;
317       }
318     }
319   }
320 });
321 Object.extend(String, {
322   interpret: function(value) {
323     return value == null ? '' : String(value);
324   },
325   specialChar: {
326     '\b': '\\b',
327     '\t': '\\t',
328     '\n': '\\n',
329     '\f': '\\f',
330     '\r': '\\r',
331     '\\': '\\\\'
332   }
333 });
334
335 Object.extend(String.prototype, {
336   gsub: function(pattern, replacement) {
337     var result = '', source = this, match;
338     replacement = arguments.callee.prepareReplacement(replacement);
339
340     while (source.length > 0) {
341       if (match = source.match(pattern)) {
342         result += source.slice(0, match.index);
343         result += String.interpret(replacement(match));
344         source  = source.slice(match.index + match[0].length);
345       } else {
346         result += source, source = '';
347       }
348     }
349     return result;
350   },
351
352   sub: function(pattern, replacement, count) {
353     replacement = this.gsub.prepareReplacement(replacement);
354     count = count === undefined ? 1 : count;
355
356     return this.gsub(pattern, function(match) {
357       if (--count < 0) return match[0];
358       return replacement(match);
359     });
360   },
361
362   scan: function(pattern, iterator) {
363     this.gsub(pattern, iterator);
364     return String(this);
365   },
366
367   truncate: function(length, truncation) {
368     length = length || 30;
369     truncation = truncation === undefined ? '...' : truncation;
370     return this.length > length ?
371       this.slice(0, length - truncation.length) + truncation : String(this);
372   },
373
374   strip: function() {
375     return this.replace(/^\s+/, '').replace(/\s+$/, '');
376   },
377
378   stripTags: function() {
379     return this.replace(/<\/?[^>]+>/gi, '');
380   },
381
382   stripScripts: function() {
383     return this.replace(new RegExp(Prototype.ScriptFragment, 'img'), '');
384   },
385
386   extractScripts: function() {
387     var matchAll = new RegExp(Prototype.ScriptFragment, 'img');
388     var matchOne = new RegExp(Prototype.ScriptFragment, 'im');
389     return (this.match(matchAll) || []).map(function(scriptTag) {
390       return (scriptTag.match(matchOne) || ['', ''])[1];
391     });
392   },
393
394   evalScripts: function() {
395     return this.extractScripts().map(function(script) { return eval(script) });
396   },
397
398   escapeHTML: function() {
399     var self = arguments.callee;
400     self.text.data = this;
401     return self.div.innerHTML;
402   },
403
404   unescapeHTML: function() {
405     var div = new Element('div');
406     div.innerHTML = this.stripTags();
407     return div.childNodes[0] ? (div.childNodes.length > 1 ?
408       $A(div.childNodes).inject('', function(memo, node) { return memo+node.nodeValue }) :
409       div.childNodes[0].nodeValue) : '';
410   },
411
412   toQueryParams: function(separator) {
413     var match = this.strip().match(/([^?#]*)(#.*)?$/);
414     if (!match) return { };
415
416     return match[1].split(separator || '&').inject({ }, function(hash, pair) {
417       if ((pair = pair.split('='))[0]) {
418         var key = decodeURIComponent(pair.shift());
419         var value = pair.length > 1 ? pair.join('=') : pair[0];
420         if (value != undefined) value = decodeURIComponent(value);
421
422         if (key in hash) {
423           if (!Object.isArray(hash[key])) hash[key] = [hash[key]];
424           hash[key].push(value);
425         }
426         else hash[key] = value;
427       }
428       return hash;
429     });
430   },
431
432   toArray: function() {
433     return this.split('');
434   },
435
436   succ: function() {
437     return this.slice(0, this.length - 1) +
438       String.fromCharCode(this.charCodeAt(this.length - 1) + 1);
439   },
440
441   times: function(count) {
442     return count < 1 ? '' : new Array(count + 1).join(this);
443   },
444
445   camelize: function() {
446     var parts = this.split('-'), len = parts.length;
447     if (len == 1) return parts[0];
448
449     var camelized = this.charAt(0) == '-'
450       ? parts[0].charAt(0).toUpperCase() + parts[0].substring(1)
451       : parts[0];
452
453     for (var i = 1; i < len; i++)
454       camelized += parts[i].charAt(0).toUpperCase() + parts[i].substring(1);
455
456     return camelized;
457   },
458
459   capitalize: function() {
460     return this.charAt(0).toUpperCase() + this.substring(1).toLowerCase();
461   },
462
463   underscore: function() {
464     return this.gsub(/::/, '/').gsub(/([A-Z]+)([A-Z][a-z])/,'#{1}_#{2}').gsub(/([a-z\d])([A-Z])/,'#{1}_#{2}').gsub(/-/,'_').toLowerCase();
465   },
466
467   dasherize: function() {
468     return this.gsub(/_/,'-');
469   },
470
471   inspect: function(useDoubleQuotes) {
472     var escapedString = this.gsub(/[\x00-\x1f\\]/, function(match) {
473       var character = String.specialChar[match[0]];
474       return character ? character : '\\u00' + match[0].charCodeAt().toPaddedString(2, 16);
475     });
476     if (useDoubleQuotes) return '"' + escapedString.replace(/"/g, '\\"') + '"';
477     return "'" + escapedString.replace(/'/g, '\\\'') + "'";
478   },
479
480   toJSON: function() {
481     return this.inspect(true);
482   },
483
484   unfilterJSON: function(filter) {
485     return this.sub(filter || Prototype.JSONFilter, '#{1}');
486   },
487
488   isJSON: function() {
489     var str = this.replace(/\\./g, '@').replace(/"[^"\\\n\r]*"/g, '');
490     return (/^[,:{}\[\]0-9.\-+Eaeflnr-u \n\r\t]*$/).test(str);
491   },
492
493   evalJSON: function(sanitize) {
494     var json = this.unfilterJSON();
495     try {
496       if (!sanitize || json.isJSON()) return eval('(' + json + ')');
497     } catch (e) { }
498     throw new SyntaxError('Badly formed JSON string: ' + this.inspect());
499   },
500
501   include: function(pattern) {
502     return this.indexOf(pattern) > -1;
503   },
504
505   startsWith: function(pattern) {
506     return this.indexOf(pattern) === 0;
507   },
508
509   endsWith: function(pattern) {
510     var d = this.length - pattern.length;
511     return d >= 0 && this.lastIndexOf(pattern) === d;
512   },
513
514   empty: function() {
515     return this == '';
516   },
517
518   blank: function() {
519     return /^\s*$/.test(this);
520   },
521
522   interpolate: function(object, pattern) {
523     return new Template(this, pattern).evaluate(object);
524   }
525 });
526
527 if (Prototype.Browser.WebKit || Prototype.Browser.IE) Object.extend(String.prototype, {
528   escapeHTML: function() {
529     return this.replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;');
530   },
531   unescapeHTML: function() {
532     return this.replace(/&amp;/g,'&').replace(/&lt;/g,'<').replace(/&gt;/g,'>');
533   }
534 });
535
536 String.prototype.gsub.prepareReplacement = function(replacement) {
537   if (Object.isFunction(replacement)) return replacement;
538   var template = new Template(replacement);
539   return function(match) { return template.evaluate(match) };
540 };
541
542 String.prototype.parseQuery = String.prototype.toQueryParams;
543
544 Object.extend(String.prototype.escapeHTML, {
545   div:  document.createElement('div'),
546   text: document.createTextNode('')
547 });
548
549 with (String.prototype.escapeHTML) div.appendChild(text);
550
551 var Template = Class.create({
552   initialize: function(template, pattern) {
553     this.template = template.toString();
554     this.pattern = pattern || Template.Pattern;
555   },
556
557   evaluate: function(object) {
558     if (Object.isFunction(object.toTemplateReplacements))
559       object = object.toTemplateReplacements();
560
561     return this.template.gsub(this.pattern, function(match) {
562       if (object == null) return '';
563
564       var before = match[1] || '';
565       if (before == '\\') return match[2];
566
567       var ctx = object, expr = match[3];
568       var pattern = /^([^.[]+|\[((?:.*?[^\\])?)\])(\.|\[|$)/, match = pattern.exec(expr);
569       if (match == null) return before;
570
571       while (match != null) {
572         var comp = match[1].startsWith('[') ? match[2].gsub('\\\\]', ']') : match[1];
573         ctx = ctx[comp];
574         if (null == ctx || '' == match[3]) break;
575         expr = expr.substring('[' == match[3] ? match[1].length : match[0].length);
576         match = pattern.exec(expr);
577       }
578
579       return before + String.interpret(ctx);
580     }.bind(this));
581   }
582 });
583 Template.Pattern = /(^|.|\r|\n)(#\{(.*?)\})/;
584
585 var $break = { };
586
587 var Enumerable = {
588   each: function(iterator, context) {
589     var index = 0;
590     iterator = iterator.bind(context);
591     try {
592       this._each(function(value) {
593         iterator(value, index++);
594       });
595     } catch (e) {
596       if (e != $break) throw e;
597     }
598     return this;
599   },
600
601   eachSlice: function(number, iterator, context) {
602     iterator = iterator ? iterator.bind(context) : Prototype.K;
603     var index = -number, slices = [], array = this.toArray();
604     while ((index += number) < array.length)
605       slices.push(array.slice(index, index+number));
606     return slices.collect(iterator, context);
607   },
608
609   all: function(iterator, context) {
610     iterator = iterator ? iterator.bind(context) : Prototype.K;
611     var result = true;
612     this.each(function(value, index) {
613       result = result && !!iterator(value, index);
614       if (!result) throw $break;
615     });
616     return result;
617   },
618
619   any: function(iterator, context) {
620     iterator = iterator ? iterator.bind(context) : Prototype.K;
621     var result = false;
622     this.each(function(value, index) {
623       if (result = !!iterator(value, index))
624         throw $break;
625     });
626     return result;
627   },
628
629   collect: function(iterator, context) {
630     iterator = iterator ? iterator.bind(context) : Prototype.K;
631     var results = [];
632     this.each(function(value, index) {
633       results.push(iterator(value, index));
634     });
635     return results;
636   },
637
638   detect: function(iterator, context) {
639     iterator = iterator.bind(context);
640     var result;
641     this.each(function(value, index) {
642       if (iterator(value, index)) {
643         result = value;
644         throw $break;
645       }
646     });
647     return result;
648   },
649
650   findAll: function(iterator, context) {
651     iterator = iterator.bind(context);
652     var results = [];
653     this.each(function(value, index) {
654       if (iterator(value, index))
655         results.push(value);
656     });
657     return results;
658   },
659
660   grep: function(filter, iterator, context) {
661     iterator = iterator ? iterator.bind(context) : Prototype.K;
662     var results = [];
663
664     if (Object.isString(filter))
665       filter = new RegExp(filter);
666
667     this.each(function(value, index) {
668       if (filter.match(value))
669         results.push(iterator(value, index));
670     });
671     return results;
672   },
673
674   include: function(object) {
675     if (Object.isFunction(this.indexOf))
676       if (this.indexOf(object) != -1) return true;
677
678     var found = false;
679     this.each(function(value) {
680       if (value == object) {
681         found = true;
682         throw $break;
683       }
684     });
685     return found;
686   },
687
688   inGroupsOf: function(number, fillWith) {
689     fillWith = fillWith === undefined ? null : fillWith;
690     return this.eachSlice(number, function(slice) {
691       while(slice.length < number) slice.push(fillWith);
692       return slice;
693     });
694   },
695
696   inject: function(memo, iterator, context) {
697     iterator = iterator.bind(context);
698     this.each(function(value, index) {
699       memo = iterator(memo, value, index);
700     });
701     return memo;
702   },
703
704   invoke: function(method) {
705     var args = $A(arguments).slice(1);
706     return this.map(function(value) {
707       return value[method].apply(value, args);
708     });
709   },
710
711   max: function(iterator, context) {
712     iterator = iterator ? iterator.bind(context) : Prototype.K;
713     var result;
714     this.each(function(value, index) {
715       value = iterator(value, index);
716       if (result == undefined || value >= result)
717         result = value;
718     });
719     return result;
720   },
721
722   min: function(iterator, context) {
723     iterator = iterator ? iterator.bind(context) : Prototype.K;
724     var result;
725     this.each(function(value, index) {
726       value = iterator(value, index);
727       if (result == undefined || value < result)
728         result = value;
729     });
730     return result;
731   },
732
733   partition: function(iterator, context) {
734     iterator = iterator ? iterator.bind(context) : Prototype.K;
735     var trues = [], falses = [];
736     this.each(function(value, index) {
737       (iterator(value, index) ?
738         trues : falses).push(value);
739     });
740     return [trues, falses];
741   },
742
743   pluck: function(property) {
744     var results = [];
745     this.each(function(value) {
746       results.push(value[property]);
747     });
748     return results;
749   },
750
751   reject: function(iterator, context) {
752     iterator = iterator.bind(context);
753     var results = [];
754     this.each(function(value, index) {
755       if (!iterator(value, index))
756         results.push(value);
757     });
758     return results;
759   },
760
761   sortBy: function(iterator, context) {
762     iterator = iterator.bind(context);
763     return this.map(function(value, index) {
764       return {value: value, criteria: iterator(value, index)};
765     }).sort(function(left, right) {
766       var a = left.criteria, b = right.criteria;
767       return a < b ? -1 : a > b ? 1 : 0;
768     }).pluck('value');
769   },
770
771   toArray: function() {
772     return this.map();
773   },
774
775   zip: function() {
776     var iterator = Prototype.K, args = $A(arguments);
777     if (Object.isFunction(args.last()))
778       iterator = args.pop();
779
780     var collections = [this].concat(args).map($A);
781     return this.map(function(value, index) {
782       return iterator(collections.pluck(index));
783     });
784   },
785
786   size: function() {
787     return this.toArray().length;
788   },
789
790   inspect: function() {
791     return '#<Enumerable:' + this.toArray().inspect() + '>';
792   }
793 };
794
795 Object.extend(Enumerable, {
796   map:     Enumerable.collect,
797   find:    Enumerable.detect,
798   select:  Enumerable.findAll,
799   filter:  Enumerable.findAll,
800   member:  Enumerable.include,
801   entries: Enumerable.toArray,
802   every:   Enumerable.all,
803   some:    Enumerable.any
804 });
805 function $A(iterable) {
806   if (!iterable) return [];
807   if (iterable.toArray) return iterable.toArray();
808   var length = iterable.length, results = new Array(length);
809   while (length--) results[length] = iterable[length];
810   return results;
811 }
812
813 if (Prototype.Browser.WebKit) {
814   function $A(iterable) {
815     if (!iterable) return [];
816     if (!(Object.isFunction(iterable) && iterable == '[object NodeList]') &&
817         iterable.toArray) return iterable.toArray();
818     var length = iterable.length, results = new Array(length);
819     while (length--) results[length] = iterable[length];
820     return results;
821   }
822 }
823
824 Array.from = $A;
825
826 Object.extend(Array.prototype, Enumerable);
827
828 if (!Array.prototype._reverse) Array.prototype._reverse = Array.prototype.reverse;
829
830 Object.extend(Array.prototype, {
831   _each: function(iterator) {
832     for (var i = 0, length = this.length; i < length; i++)
833       iterator(this[i]);
834   },
835
836   clear: function() {
837     this.length = 0;
838     return this;
839   },
840
841   first: function() {
842     return this[0];
843   },
844
845   last: function() {
846     return this[this.length - 1];
847   },
848
849   compact: function() {
850     return this.select(function(value) {
851       return value != null;
852     });
853   },
854
855   flatten: function() {
856     return this.inject([], function(array, value) {
857       return array.concat(Object.isArray(value) ?
858         value.flatten() : [value]);
859     });
860   },
861
862   without: function() {
863     var values = $A(arguments);
864     return this.select(function(value) {
865       return !values.include(value);
866     });
867   },
868
869   reverse: function(inline) {
870     return (inline !== false ? this : this.toArray())._reverse();
871   },
872
873   reduce: function() {
874     return this.length > 1 ? this : this[0];
875   },
876
877   uniq: function(sorted) {
878     return this.inject([], function(array, value, index) {
879       if (0 == index || (sorted ? array.last() != value : !array.include(value)))
880         array.push(value);
881       return array;
882     });
883   },
884
885   intersect: function(array) {
886     return this.uniq().findAll(function(item) {
887       return array.detect(function(value) { return item === value });
888     });
889   },
890
891   clone: function() {
892     return [].concat(this);
893   },
894
895   size: function() {
896     return this.length;
897   },
898
899   inspect: function() {
900     return '[' + this.map(Object.inspect).join(', ') + ']';
901   },
902
903   toJSON: function() {
904     var results = [];
905     this.each(function(object) {
906       var value = Object.toJSON(object);
907       if (value !== undefined) results.push(value);
908     });
909     return '[' + results.join(', ') + ']';
910   }
911 });
912
913 // use native browser JS 1.6 implementation if available
914 if (Object.isFunction(Array.prototype.forEach))
915   Array.prototype._each = Array.prototype.forEach;
916
917 if (!Array.prototype.indexOf) Array.prototype.indexOf = function(item, i) {
918   i || (i = 0);
919   var length = this.length;
920   if (i < 0) i = length + i;
921   for (; i < length; i++)
922     if (this[i] === item) return i;
923   return -1;
924 };
925
926 if (!Array.prototype.lastIndexOf) Array.prototype.lastIndexOf = function(item, i) {
927   i = isNaN(i) ? this.length : (i < 0 ? this.length + i : i) + 1;
928   var n = this.slice(0, i).reverse().indexOf(item);
929   return (n < 0) ? n : i - n - 1;
930 };
931
932 Array.prototype.toArray = Array.prototype.clone;
933
934 function $w(string) {
935   if (!Object.isString(string)) return [];
936   string = string.strip();
937   return string ? string.split(/\s+/) : [];
938 }
939
940 if (Prototype.Browser.Opera){
941   Array.prototype.concat = function() {
942     var array = [];
943     for (var i = 0, length = this.length; i < length; i++) array.push(this[i]);
944     for (var i = 0, length = arguments.length; i < length; i++) {
945       if (Object.isArray(arguments[i])) {
946         for (var j = 0, arrayLength = arguments[i].length; j < arrayLength; j++)
947           array.push(arguments[i][j]);
948       } else {
949         array.push(arguments[i]);
950       }
951     }
952     return array;
953   };
954 }
955 Object.extend(Number.prototype, {
956   toColorPart: function() {
957     return this.toPaddedString(2, 16);
958   },
959
960   succ: function() {
961     return this + 1;
962   },
963
964   times: function(iterator) {
965     $R(0, this, true).each(iterator);
966     return this;
967   },
968
969   toPaddedString: function(length, radix) {
970     var string = this.toString(radix || 10);
971     return '0'.times(length - string.length) + string;
972   },
973
974   toJSON: function() {
975     return isFinite(this) ? this.toString() : 'null';
976   }
977 });
978
979 $w('abs round ceil floor').each(function(method){
980   Number.prototype[method] = Math[method].methodize();
981 });
982 function $H(object) {
983   return new Hash(object);
984 };
985
986 var Hash = Class.create(Enumerable, (function() {
987   if (function() {
988     var i = 0, Test = function(value) { this.key = value };
989     Test.prototype.key = 'foo';
990     for (var property in new Test('bar')) i++;
991     return i > 1;
992   }()) {
993     function each(iterator) {
994       var cache = [];
995       for (var key in this._object) {
996         var value = this._object[key];
997         if (cache.include(key)) continue;
998         cache.push(key);
999         var pair = [key, value];
1000         pair.key = key;
1001         pair.value = value;
1002         iterator(pair);
1003       }
1004     }
1005   } else {
1006     function each(iterator) {
1007       for (var key in this._object) {
1008         var value = this._object[key], pair = [key, value];
1009         pair.key = key;
1010         pair.value = value;
1011         iterator(pair);
1012       }
1013     }
1014   }
1015
1016   function toQueryPair(key, value) {
1017     if (Object.isUndefined(value)) return key;
1018     return key + '=' + encodeURIComponent(String.interpret(value));
1019   }
1020
1021   return {
1022     initialize: function(object) {
1023       this._object = Object.isHash(object) ? object.toObject() : Object.clone(object);
1024     },
1025
1026     _each: each,
1027
1028     set: function(key, value) {
1029       return this._object[key] = value;
1030     },
1031
1032     get: function(key) {
1033       return this._object[key];
1034     },
1035
1036     unset: function(key) {
1037       var value = this._object[key];
1038       delete this._object[key];
1039       return value;
1040     },
1041
1042     toObject: function() {
1043       return Object.clone(this._object);
1044     },
1045
1046     keys: function() {
1047       return this.pluck('key');
1048     },
1049
1050     values: function() {
1051       return this.pluck('value');
1052     },
1053
1054     index: function(value) {
1055       var match = this.detect(function(pair) {
1056         return pair.value === value;
1057       });
1058       return match && match.key;
1059     },
1060
1061     merge: function(object) {
1062       return this.clone().update(object);
1063     },
1064
1065     update: function(object) {
1066       return new Hash(object).inject(this, function(result, pair) {
1067         result.set(pair.key, pair.value);
1068         return result;
1069       });
1070     },
1071
1072     toQueryString: function() {
1073       return this.map(function(pair) {
1074         var key = encodeURIComponent(pair.key), values = pair.value;
1075
1076         if (values && typeof values == 'object') {
1077           if (Object.isArray(values))
1078             return values.map(toQueryPair.curry(key)).join('&');
1079         }
1080         return toQueryPair(key, values);
1081       }).join('&');
1082     },
1083
1084     inspect: function() {
1085       return '#<Hash:{' + this.map(function(pair) {
1086         return pair.map(Object.inspect).join(': ');
1087       }).join(', ') + '}>';
1088     },
1089
1090     toJSON: function() {
1091       return Object.toJSON(this.toObject());
1092     },
1093
1094     clone: function() {
1095       return new Hash(this);
1096     }
1097   }
1098 })());
1099
1100 Hash.prototype.toTemplateReplacements = Hash.prototype.toObject;
1101 Hash.from = $H;
1102 var ObjectRange = Class.create(Enumerable, {
1103   initialize: function(start, end, exclusive) {
1104     this.start = start;
1105     this.end = end;
1106     this.exclusive = exclusive;
1107   },
1108
1109   _each: function(iterator) {
1110     var value = this.start;
1111     while (this.include(value)) {
1112       iterator(value);
1113       value = value.succ();
1114     }
1115   },
1116
1117   include: function(value) {
1118     if (value < this.start)
1119       return false;
1120     if (this.exclusive)
1121       return value < this.end;
1122     return value <= this.end;
1123   }
1124 });
1125
1126 var $R = function(start, end, exclusive) {
1127   return new ObjectRange(start, end, exclusive);
1128 };
1129
1130 var Ajax = {
1131   getTransport: function() {
1132     return Try.these(
1133       function() {return new XMLHttpRequest()},
1134       function() {return new ActiveXObject('Msxml2.XMLHTTP')},
1135       function() {return new ActiveXObject('Microsoft.XMLHTTP')}
1136     ) || false;
1137   },
1138
1139   activeRequestCount: 0
1140 };
1141
1142 Ajax.Responders = {
1143   responders: [],
1144
1145   _each: function(iterator) {
1146     this.responders._each(iterator);
1147   },
1148
1149   register: function(responder) {
1150     if (!this.include(responder))
1151       this.responders.push(responder);
1152   },
1153
1154   unregister: function(responder) {
1155     this.responders = this.responders.without(responder);
1156   },
1157
1158   dispatch: function(callback, request, transport, json) {
1159     this.each(function(responder) {
1160       if (Object.isFunction(responder[callback])) {
1161         try {
1162           responder[callback].apply(responder, [request, transport, json]);
1163         } catch (e) { }
1164       }
1165     });
1166   }
1167 };
1168
1169 Object.extend(Ajax.Responders, Enumerable);
1170
1171 Ajax.Responders.register({
1172   onCreate:   function() { Ajax.activeRequestCount++ },
1173   onComplete: function() { Ajax.activeRequestCount-- }
1174 });
1175
1176 Ajax.Base = Class.create({
1177   initialize: function(options) {
1178     this.options = {
1179       method:       'post',
1180       asynchronous: true,
1181       contentType:  'application/x-www-form-urlencoded',
1182       encoding:     'UTF-8',
1183       parameters:   '',
1184       evalJSON:     true,
1185       evalJS:       true
1186     };
1187     Object.extend(this.options, options || { });
1188
1189     this.options.method = this.options.method.toLowerCase();
1190     if (Object.isString(this.options.parameters))
1191       this.options.parameters = this.options.parameters.toQueryParams();
1192   }
1193 });
1194
1195 Ajax.Request = Class.create(Ajax.Base, {
1196   _complete: false,
1197
1198   initialize: function($super, url, options) {
1199     $super(options);
1200     this.transport = Ajax.getTransport();
1201     this.request(url);
1202   },
1203
1204   request: function(url) {
1205     this.url = url;
1206     this.method = this.options.method;
1207     var params = Object.clone(this.options.parameters);
1208
1209     if (!['get', 'post'].include(this.method)) {
1210       // simulate other verbs over post
1211       params['_method'] = this.method;
1212       this.method = 'post';
1213     }
1214
1215     this.parameters = params;
1216
1217     if (params = Object.toQueryString(params)) {
1218       // when GET, append parameters to URL
1219       if (this.method == 'get')
1220         this.url += (this.url.include('?') ? '&' : '?') + params;
1221       else if (/Konqueror|Safari|KHTML/.test(navigator.userAgent))
1222         params += '&_=';
1223     }
1224
1225     try {
1226       var response = new Ajax.Response(this);
1227       if (this.options.onCreate) this.options.onCreate(response);
1228       Ajax.Responders.dispatch('onCreate', this, response);
1229
1230       this.transport.open(this.method.toUpperCase(), this.url,
1231         this.options.asynchronous);
1232
1233       if (this.options.asynchronous) this.respondToReadyState.bind(this).defer(1);
1234
1235       this.transport.onreadystatechange = this.onStateChange.bind(this);
1236       this.setRequestHeaders();
1237
1238       this.body = this.method == 'post' ? (this.options.postBody || params) : null;
1239       this.transport.send(this.body);
1240
1241       /* Force Firefox to handle ready state 4 for synchronous requests */
1242       if (!this.options.asynchronous && this.transport.overrideMimeType)
1243         this.onStateChange();
1244
1245     }
1246     catch (e) {
1247       this.dispatchException(e);
1248     }
1249   },
1250
1251   onStateChange: function() {
1252     var readyState = this.transport.readyState;
1253     if (readyState > 1 && !((readyState == 4) && this._complete))
1254       this.respondToReadyState(this.transport.readyState);
1255   },
1256
1257   setRequestHeaders: function() {
1258     var headers = {
1259       'X-Requested-With': 'XMLHttpRequest',
1260       'X-Prototype-Version': Prototype.Version,
1261       'Accept': 'text/javascript, text/html, application/xml, text/xml, */*'
1262     };
1263
1264     if (this.method == 'post') {
1265       headers['Content-type'] = this.options.contentType +
1266         (this.options.encoding ? '; charset=' + this.options.encoding : '');
1267
1268       /* Force "Connection: close" for older Mozilla browsers to work
1269        * around a bug where XMLHttpRequest sends an incorrect
1270        * Content-length header. See Mozilla Bugzilla #246651.
1271        */
1272       if (this.transport.overrideMimeType &&
1273           (navigator.userAgent.match(/Gecko\/(\d{4})/) || [0,2005])[1] < 2005)
1274             headers['Connection'] = 'close';
1275     }
1276
1277     // user-defined headers
1278     if (typeof this.options.requestHeaders == 'object') {
1279       var extras = this.options.requestHeaders;
1280
1281       if (Object.isFunction(extras.push))
1282         for (var i = 0, length = extras.length; i < length; i += 2)
1283           headers[extras[i]] = extras[i+1];
1284       else
1285         $H(extras).each(function(pair) { headers[pair.key] = pair.value });
1286     }
1287
1288     for (var name in headers)
1289       this.transport.setRequestHeader(name, headers[name]);
1290   },
1291
1292   success: function() {
1293     var status = this.getStatus();
1294     return !status || (status >= 200 && status < 300);
1295   },
1296
1297   getStatus: function() {
1298     try {
1299       return this.transport.status || 0;
1300     } catch (e) { return 0 }
1301   },
1302
1303   respondToReadyState: function(readyState) {
1304     var state = Ajax.Request.Events[readyState], response = new Ajax.Response(this);
1305
1306     if (state == 'Complete') {
1307       try {
1308         this._complete = true;
1309         (this.options['on' + response.status]
1310          || this.options['on' + (this.success() ? 'Success' : 'Failure')]
1311          || Prototype.emptyFunction)(response, response.headerJSON);
1312       } catch (e) {
1313         this.dispatchException(e);
1314       }
1315
1316       var contentType = response.getHeader('Content-type');
1317       if (this.options.evalJS == 'force'
1318           || (this.options.evalJS && contentType
1319           && contentType.match(/^\s*(text|application)\/(x-)?(java|ecma)script(;.*)?\s*$/i)))
1320         this.evalResponse();
1321     }
1322
1323     try {
1324       (this.options['on' + state] || Prototype.emptyFunction)(response, response.headerJSON);
1325       Ajax.Responders.dispatch('on' + state, this, response, response.headerJSON);
1326     } catch (e) {
1327       this.dispatchException(e);
1328     }
1329
1330     if (state == 'Complete') {
1331       // avoid memory leak in MSIE: clean up
1332       this.transport.onreadystatechange = Prototype.emptyFunction;
1333     }
1334   },
1335
1336   getHeader: function(name) {
1337     try {
1338       return this.transport.getResponseHeader(name);
1339     } catch (e) { return null }
1340   },
1341
1342   evalResponse: function() {
1343     try {
1344       return eval((this.transport.responseText || '').unfilterJSON());
1345     } catch (e) {
1346       this.dispatchException(e);
1347     }
1348   },
1349
1350   dispatchException: function(exception) {
1351     (this.options.onException || Prototype.emptyFunction)(this, exception);
1352     Ajax.Responders.dispatch('onException', this, exception);
1353   }
1354 });
1355
1356 Ajax.Request.Events =
1357   ['Uninitialized', 'Loading', 'Loaded', 'Interactive', 'Complete'];
1358
1359 Ajax.Response = Class.create({
1360   initialize: function(request){
1361     this.request = request;
1362     var transport  = this.transport  = request.transport,
1363         readyState = this.readyState = transport.readyState;
1364
1365     if((readyState > 2 && !Prototype.Browser.IE) || readyState == 4) {
1366       this.status       = this.getStatus();
1367       this.statusText   = this.getStatusText();
1368       this.responseText = String.interpret(transport.responseText);
1369       this.headerJSON   = this._getHeaderJSON();
1370     }
1371
1372     if(readyState == 4) {
1373       var xml = transport.responseXML;
1374       this.responseXML  = xml === undefined ? null : xml;
1375       this.responseJSON = this._getResponseJSON();
1376     }
1377   },
1378
1379   status:      0,
1380   statusText: '',
1381
1382   getStatus: Ajax.Request.prototype.getStatus,
1383
1384   getStatusText: function() {
1385     try {
1386       return this.transport.statusText || '';
1387     } catch (e) { return '' }
1388   },
1389
1390   getHeader: Ajax.Request.prototype.getHeader,
1391
1392   getAllHeaders: function() {
1393     try {
1394       return this.getAllResponseHeaders();
1395     } catch (e) { return null }
1396   },
1397
1398   getResponseHeader: function(name) {
1399     return this.transport.getResponseHeader(name);
1400   },
1401
1402   getAllResponseHeaders: function() {
1403     return this.transport.getAllResponseHeaders();
1404   },
1405
1406   _getHeaderJSON: function() {
1407     var json = this.getHeader('X-JSON');
1408     if (!json) return null;
1409     json = decodeURIComponent(escape(json));
1410     try {
1411       return json.evalJSON(this.request.options.sanitizeJSON);
1412     } catch (e) {
1413       this.request.dispatchException(e);
1414     }
1415   },
1416
1417   _getResponseJSON: function() {
1418     var options = this.request.options;
1419     if (!options.evalJSON || (options.evalJSON != 'force' &&
1420       !(this.getHeader('Content-type') || '').include('application/json')))
1421         return null;
1422     try {
1423       return this.transport.responseText.evalJSON(options.sanitizeJSON);
1424     } catch (e) {
1425       this.request.dispatchException(e);
1426     }
1427   }
1428 });
1429
1430 Ajax.Updater = Class.create(Ajax.Request, {
1431   initialize: function($super, container, url, options) {
1432     this.container = {
1433       success: (container.success || container),
1434       failure: (container.failure || (container.success ? null : container))
1435     };
1436
1437     options = options || { };
1438     var onComplete = options.onComplete;
1439     options.onComplete = (function(response, param) {
1440       this.updateContent(response.responseText);
1441       if (Object.isFunction(onComplete)) onComplete(response, param);
1442     }).bind(this);
1443
1444     $super(url, options);
1445   },
1446
1447   updateContent: function(responseText) {
1448     var receiver = this.container[this.success() ? 'success' : 'failure'],
1449         options = this.options;
1450
1451     if (!options.evalScripts) responseText = responseText.stripScripts();
1452
1453     if (receiver = $(receiver)) {
1454       if (options.insertion) {
1455         if (Object.isString(options.insertion)) {
1456           var insertion = { }; insertion[options.insertion] = responseText;
1457           receiver.insert(insertion);
1458         }
1459         else options.insertion(receiver, responseText);
1460       }
1461       else receiver.update(responseText);
1462     }
1463
1464     if (this.success()) {
1465       if (this.onComplete) this.onComplete.bind(this).defer();
1466     }
1467   }
1468 });
1469
1470 Ajax.PeriodicalUpdater = Class.create(Ajax.Base, {
1471   initialize: function($super, container, url, options) {
1472     $super(options);
1473     this.onComplete = this.options.onComplete;
1474
1475     this.frequency = (this.options.frequency || 2);
1476     this.decay = (this.options.decay || 1);
1477
1478     this.updater = { };
1479     this.container = container;
1480     this.url = url;
1481
1482     this.start();
1483   },
1484
1485   start: function() {
1486     this.options.onComplete = this.updateComplete.bind(this);
1487     this.onTimerEvent();
1488   },
1489
1490   stop: function() {
1491     this.updater.options.onComplete = undefined;
1492     clearTimeout(this.timer);
1493     (this.onComplete || Prototype.emptyFunction).apply(this, arguments);
1494   },
1495
1496   updateComplete: function(response) {
1497     if (this.options.decay) {
1498       this.decay = (response.responseText == this.lastText ?
1499         this.decay * this.options.decay : 1);
1500
1501       this.lastText = response.responseText;
1502     }
1503     this.timer = this.onTimerEvent.bind(this).delay(this.decay * this.frequency);
1504   },
1505
1506   onTimerEvent: function() {
1507     this.updater = new Ajax.Updater(this.container, this.url, this.options);
1508   }
1509 });
1510 function $(element) {
1511   if (arguments.length > 1) {
1512     for (var i = 0, elements = [], length = arguments.length; i < length; i++)
1513       elements.push($(arguments[i]));
1514     return elements;
1515   }
1516   if (Object.isString(element))
1517     element = document.getElementById(element);
1518   return Element.extend(element);
1519 }
1520
1521 if (Prototype.BrowserFeatures.XPath) {
1522   document._getElementsByXPath = function(expression, parentElement) {
1523     var results = [];
1524     var query = document.evaluate(expression, $(parentElement) || document,
1525       null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null);
1526     for (var i = 0, length = query.snapshotLength; i < length; i++)
1527       results.push(Element.extend(query.snapshotItem(i)));
1528     return results;
1529   };
1530 }
1531
1532 /*--------------------------------------------------------------------------*/
1533
1534 if (!window.Node) var Node = { };
1535
1536 if (!Node.ELEMENT_NODE) {
1537   // DOM level 2 ECMAScript Language Binding
1538   Object.extend(Node, {
1539     ELEMENT_NODE: 1,
1540     ATTRIBUTE_NODE: 2,
1541     TEXT_NODE: 3,
1542     CDATA_SECTION_NODE: 4,
1543     ENTITY_REFERENCE_NODE: 5,
1544     ENTITY_NODE: 6,
1545     PROCESSING_INSTRUCTION_NODE: 7,
1546     COMMENT_NODE: 8,
1547     DOCUMENT_NODE: 9,
1548     DOCUMENT_TYPE_NODE: 10,
1549     DOCUMENT_FRAGMENT_NODE: 11,
1550     NOTATION_NODE: 12
1551   });
1552 }
1553
1554 (function() {
1555   var element = this.Element;
1556   this.Element = function(tagName, attributes) {
1557     attributes = attributes || { };
1558     tagName = tagName.toLowerCase();
1559     var cache = Element.cache;
1560     if (Prototype.Browser.IE && attributes.name) {
1561       tagName = '<' + tagName + ' name="' + attributes.name + '">';
1562       delete attributes.name;
1563       return Element.writeAttribute(document.createElement(tagName), attributes);
1564     }
1565     if (!cache[tagName]) cache[tagName] = Element.extend(document.createElement(tagName));
1566     return Element.writeAttribute(cache[tagName].cloneNode(false), attributes);
1567   };
1568   Object.extend(this.Element, element || { });
1569 }).call(window);
1570
1571 Element.cache = { };
1572
1573 Element.Methods = {
1574   visible: function(element) {
1575     return $(element).style.display != 'none';
1576   },
1577
1578   toggle: function(element) {
1579     element = $(element);
1580     Element[Element.visible(element) ? 'hide' : 'show'](element);
1581     return element;
1582   },
1583
1584   hide: function(element) {
1585     $(element).style.display = 'none';
1586     return element;
1587   },
1588
1589   show: function(element) {
1590     $(element).style.display = '';
1591     return element;
1592   },
1593
1594   remove: function(element) {
1595     element = $(element);
1596     element.parentNode.removeChild(element);
1597     return element;
1598   },
1599
1600   update: function(element, content) {
1601     element = $(element);
1602     if (content && content.toElement) content = content.toElement();
1603     if (Object.isElement(content)) return element.update().insert(content);
1604     content = Object.toHTML(content);
1605     element.innerHTML = content.stripScripts();
1606     content.evalScripts.bind(content).defer();
1607     return element;
1608   },
1609
1610   replace: function(element, content) {
1611     element = $(element);
1612     if (content && content.toElement) content = content.toElement();
1613     else if (!Object.isElement(content)) {
1614       content = Object.toHTML(content);
1615       var range = element.ownerDocument.createRange();
1616       range.selectNode(element);
1617       content.evalScripts.bind(content).defer();
1618       content = range.createContextualFragment(content.stripScripts());
1619     }
1620     element.parentNode.replaceChild(content, element);
1621     return element;
1622   },
1623
1624   insert: function(element, insertions) {
1625     element = $(element);
1626
1627     if (Object.isString(insertions) || Object.isNumber(insertions) ||
1628         Object.isElement(insertions) || (insertions && (insertions.toElement || insertions.toHTML)))
1629           insertions = {bottom:insertions};
1630
1631     var content, t, range;
1632
1633     for (position in insertions) {
1634       content  = insertions[position];
1635       position = position.toLowerCase();
1636       t = Element._insertionTranslations[position];
1637
1638       if (content && content.toElement) content = content.toElement();
1639       if (Object.isElement(content)) {
1640         t.insert(element, content);
1641         continue;
1642       }
1643
1644       content = Object.toHTML(content);
1645
1646       range = element.ownerDocument.createRange();
1647       t.initializeRange(element, range);
1648       t.insert(element, range.createContextualFragment(content.stripScripts()));
1649
1650       content.evalScripts.bind(content).defer();
1651     }
1652
1653     return element;
1654   },
1655
1656   wrap: function(element, wrapper, attributes) {
1657     element = $(element);
1658     if (Object.isElement(wrapper))
1659       $(wrapper).writeAttribute(attributes || { });
1660     else if (Object.isString(wrapper)) wrapper = new Element(wrapper, attributes);
1661     else wrapper = new Element('div', wrapper);
1662     if (element.parentNode)
1663       element.parentNode.replaceChild(wrapper, element);
1664     wrapper.appendChild(element);
1665     return wrapper;
1666   },
1667
1668   inspect: function(element) {
1669     element = $(element);
1670     var result = '<' + element.tagName.toLowerCase();
1671     $H({'id': 'id', 'className': 'class'}).each(function(pair) {
1672       var property = pair.first(), attribute = pair.last();
1673       var value = (element[property] || '').toString();
1674       if (value) result += ' ' + attribute + '=' + value.inspect(true);
1675     });
1676     return result + '>';
1677   },
1678
1679   recursivelyCollect: function(element, property) {
1680     element = $(element);
1681     var elements = [];
1682     while (element = element[property])
1683       if (element.nodeType == 1)
1684         elements.push(Element.extend(element));
1685     return elements;
1686   },
1687
1688   ancestors: function(element) {
1689     return $(element).recursivelyCollect('parentNode');
1690   },
1691
1692   descendants: function(element) {
1693     return $A($(element).getElementsByTagName('*')).each(Element.extend);
1694   },
1695
1696   firstDescendant: function(element) {
1697     element = $(element).firstChild;
1698     while (element && element.nodeType != 1) element = element.nextSibling;
1699     return $(element);
1700   },
1701
1702   immediateDescendants: function(element) {
1703     if (!(element = $(element).firstChild)) return [];
1704     while (element && element.nodeType != 1) element = element.nextSibling;
1705     if (element) return [element].concat($(element).nextSiblings());
1706     return [];
1707   },
1708
1709   previousSiblings: function(element) {
1710     return $(element).recursivelyCollect('previousSibling');
1711   },
1712
1713   nextSiblings: function(element) {
1714     return $(element).recursivelyCollect('nextSibling');
1715   },
1716
1717   siblings: function(element) {
1718     element = $(element);
1719     return element.previousSiblings().reverse().concat(element.nextSiblings());
1720   },
1721
1722   match: function(element, selector) {
1723     if (Object.isString(selector))
1724       selector = new Selector(selector);
1725     return selector.match($(element));
1726   },
1727
1728   up: function(element, expression, index) {
1729     element = $(element);
1730     if (arguments.length == 1) return $(element.parentNode);
1731     var ancestors = element.ancestors();
1732     return expression ? Selector.findElement(ancestors, expression, index) :
1733       ancestors[index || 0];
1734   },
1735
1736   down: function(element, expression, index) {
1737     element = $(element);
1738     if (arguments.length == 1) return element.firstDescendant();
1739     var descendants = element.descendants();
1740     return expression ? Selector.findElement(descendants, expression, index) :
1741       descendants[index || 0];
1742   },
1743
1744   previous: function(element, expression, index) {
1745     element = $(element);
1746     if (arguments.length == 1) return $(Selector.handlers.previousElementSibling(element));
1747     var previousSiblings = element.previousSiblings();
1748     return expression ? Selector.findElement(previousSiblings, expression, index) :
1749       previousSiblings[index || 0];
1750   },
1751
1752   next: function(element, expression, index) {
1753     element = $(element);
1754     if (arguments.length == 1) return $(Selector.handlers.nextElementSibling(element));
1755     var nextSiblings = element.nextSiblings();
1756     return expression ? Selector.findElement(nextSiblings, expression, index) :
1757       nextSiblings[index || 0];
1758   },
1759
1760   select: function() {
1761     var args = $A(arguments), element = $(args.shift());
1762     return Selector.findChildElements(element, args);
1763   },
1764
1765   adjacent: function() {
1766     var args = $A(arguments), element = $(args.shift());
1767     return Selector.findChildElements(element.parentNode, args).without(element);
1768   },
1769
1770   identify: function(element) {
1771     element = $(element);
1772     var id = element.readAttribute('id'), self = arguments.callee;
1773     if (id) return id;
1774     do { id = 'anonymous_element_' + self.counter++ } while ($(id));
1775     element.writeAttribute('id', id);
1776     return id;
1777   },
1778
1779   readAttribute: function(element, name) {
1780     element = $(element);
1781     if (Prototype.Browser.IE) {
1782       var t = Element._attributeTranslations.read;
1783       if (t.values[name]) return t.values[name](element, name);
1784       if (t.names[name]) name = t.names[name];
1785       if (name.include(':')) {
1786         return (!element.attributes || !element.attributes[name]) ? null :
1787          element.attributes[name].value;
1788       }
1789     }
1790     return element.getAttribute(name);
1791   },
1792
1793   writeAttribute: function(element, name, value) {
1794     element = $(element);
1795     var attributes = { }, t = Element._attributeTranslations.write;
1796
1797     if (typeof name == 'object') attributes = name;
1798     else attributes[name] = value === undefined ? true : value;
1799
1800     for (var attr in attributes) {
1801       var name = t.names[attr] || attr, value = attributes[attr];
1802       if (t.values[attr]) name = t.values[attr](element, value);
1803       if (value === false || value === null)
1804         element.removeAttribute(name);
1805       else if (value === true)
1806         element.setAttribute(name, name);
1807       else element.setAttribute(name, value);
1808     }
1809     return element;
1810   },
1811
1812   getHeight: function(element) {
1813     return $(element).getDimensions().height;
1814   },
1815
1816   getWidth: function(element) {
1817     return $(element).getDimensions().width;
1818   },
1819
1820   classNames: function(element) {
1821     return new Element.ClassNames(element);
1822   },
1823
1824   hasClassName: function(element, className) {
1825     if (!(element = $(element))) return;
1826     var elementClassName = element.className;
1827     return (elementClassName.length > 0 && (elementClassName == className ||
1828       new RegExp("(^|\\s)" + className + "(\\s|$)").test(elementClassName)));
1829   },
1830
1831   addClassName: function(element, className) {
1832     if (!(element = $(element))) return;
1833     if (!element.hasClassName(className))
1834       element.className += (element.className ? ' ' : '') + className;
1835     return element;
1836   },
1837
1838   removeClassName: function(element, className) {
1839     if (!(element = $(element))) return;
1840     element.className = element.className.replace(
1841       new RegExp("(^|\\s+)" + className + "(\\s+|$)"), ' ').strip();
1842     return element;
1843   },
1844
1845   toggleClassName: function(element, className) {
1846     if (!(element = $(element))) return;
1847     return element[element.hasClassName(className) ?
1848       'removeClassName' : 'addClassName'](className);
1849   },
1850
1851   // removes whitespace-only text node children
1852   cleanWhitespace: function(element) {
1853     element = $(element);
1854     var node = element.firstChild;
1855     while (node) {
1856       var nextNode = node.nextSibling;
1857       if (node.nodeType == 3 && !/\S/.test(node.nodeValue))
1858         element.removeChild(node);
1859       node = nextNode;
1860     }
1861     return element;
1862   },
1863
1864   empty: function(element) {
1865     return $(element).innerHTML.blank();
1866   },
1867
1868   descendantOf: function(element, ancestor) {
1869     element = $(element), ancestor = $(ancestor);
1870
1871     if (element.compareDocumentPosition)
1872       return (element.compareDocumentPosition(ancestor) & 8) === 8;
1873
1874     if (element.sourceIndex && !Prototype.Browser.Opera) {
1875       var e = element.sourceIndex, a = ancestor.sourceIndex,
1876        nextAncestor = ancestor.nextSibling;
1877       if (!nextAncestor) {
1878         do { ancestor = ancestor.parentNode; }
1879         while (!(nextAncestor = ancestor.nextSibling) && ancestor.parentNode);
1880       }
1881       if (nextAncestor) return (e > a && e < nextAncestor.sourceIndex);
1882     }
1883
1884     while (element = element.parentNode)
1885       if (element == ancestor) return true;
1886     return false;
1887   },
1888
1889   scrollTo: function(element) {
1890     element = $(element);
1891     var pos = element.cumulativeOffset();
1892     window.scrollTo(pos[0], pos[1]);
1893     return element;
1894   },
1895
1896   getStyle: function(element, style) {
1897     element = $(element);
1898     style = style == 'float' ? 'cssFloat' : style.camelize();
1899     var value = element.style[style];
1900     if (!value) {
1901       var css = document.defaultView.getComputedStyle(element, null);
1902       value = css ? css[style] : null;
1903     }
1904     if (style == 'opacity') return value ? parseFloat(value) : 1.0;
1905     return value == 'auto' ? null : value;
1906   },
1907
1908   getOpacity: function(element) {
1909     return $(element).getStyle('opacity');
1910   },
1911
1912   setStyle: function(element, styles) {
1913     element = $(element);
1914     var elementStyle = element.style, match;
1915     if (Object.isString(styles)) {
1916       element.style.cssText += ';' + styles;
1917       return styles.include('opacity') ?
1918         element.setOpacity(styles.match(/opacity:\s*(\d?\.?\d*)/)[1]) : element;
1919     }
1920     for (var property in styles)
1921       if (property == 'opacity') element.setOpacity(styles[property]);
1922       else
1923         elementStyle[(property == 'float' || property == 'cssFloat') ?
1924           (elementStyle.styleFloat === undefined ? 'cssFloat' : 'styleFloat') :
1925             property] = styles[property];
1926
1927     return element;
1928   },
1929
1930   setOpacity: function(element, value) {
1931     element = $(element);
1932     element.style.opacity = (value == 1 || value === '') ? '' :
1933       (value < 0.00001) ? 0 : value;
1934     return element;
1935   },
1936
1937   getDimensions: function(element) {
1938     element = $(element);
1939     var display = $(element).getStyle('display');
1940     if (display != 'none' && display != null) // Safari bug
1941       return {width: element.offsetWidth, height: element.offsetHeight};
1942
1943     // All *Width and *Height properties give 0 on elements with display none,
1944     // so enable the element temporarily
1945     var els = element.style;
1946     var originalVisibility = els.visibility;
1947     var originalPosition = els.position;
1948     var originalDisplay = els.display;
1949     els.visibility = 'hidden';
1950     els.position = 'absolute';
1951     els.display = 'block';
1952     var originalWidth = element.clientWidth;
1953     var originalHeight = element.clientHeight;
1954     els.display = originalDisplay;
1955     els.position = originalPosition;
1956     els.visibility = originalVisibility;
1957     return {width: originalWidth, height: originalHeight};
1958   },
1959
1960   makePositioned: function(element) {
1961     element = $(element);
1962     var pos = Element.getStyle(element, 'position');
1963     if (pos == 'static' || !pos) {
1964       element._madePositioned = true;
1965       element.style.position = 'relative';
1966       // Opera returns the offset relative to the positioning context, when an
1967       // element is position relative but top and left have not been defined
1968       if (window.opera) {
1969         element.style.top = 0;
1970         element.style.left = 0;
1971       }
1972     }
1973     return element;
1974   },
1975
1976   undoPositioned: function(element) {
1977     element = $(element);
1978     if (element._madePositioned) {
1979       element._madePositioned = undefined;
1980       element.style.position =
1981         element.style.top =
1982         element.style.left =
1983         element.style.bottom =
1984         element.style.right = '';
1985     }
1986     return element;
1987   },
1988
1989   makeClipping: function(element) {
1990     element = $(element);
1991     if (element._overflow) return element;
1992     element._overflow = Element.getStyle(element, 'overflow') || 'auto';
1993     if (element._overflow !== 'hidden')
1994       element.style.overflow = 'hidden';
1995     return element;
1996   },
1997
1998   undoClipping: function(element) {
1999     element = $(element);
2000     if (!element._overflow) return element;
2001     element.style.overflow = element._overflow == 'auto' ? '' : element._overflow;
2002     element._overflow = null;
2003     return element;
2004   },
2005
2006   cumulativeOffset: function(element) {
2007     var valueT = 0, valueL = 0;
2008     do {
2009       valueT += element.offsetTop  || 0;
2010       valueL += element.offsetLeft || 0;
2011       element = element.offsetParent;
2012     } while (element);
2013     return Element._returnOffset(valueL, valueT);
2014   },
2015
2016   positionedOffset: function(element) {
2017     var valueT = 0, valueL = 0;
2018     do {
2019       valueT += element.offsetTop  || 0;
2020       valueL += element.offsetLeft || 0;
2021       element = element.offsetParent;
2022       if (element) {
2023         if (element.tagName == 'BODY') break;
2024         var p = Element.getStyle(element, 'position');
2025         if (p == 'relative' || p == 'absolute') break;
2026       }
2027     } while (element);
2028     return Element._returnOffset(valueL, valueT);
2029   },
2030
2031   absolutize: function(element) {
2032     element = $(element);
2033     if (element.getStyle('position') == 'absolute') return;
2034     // Position.prepare(); // To be done manually by Scripty when it needs it.
2035
2036     var offsets = element.positionedOffset();
2037     var top     = offsets[1];
2038     var left    = offsets[0];
2039     var width   = element.clientWidth;
2040     var height  = element.clientHeight;
2041
2042     element._originalLeft   = left - parseFloat(element.style.left  || 0);
2043     element._originalTop    = top  - parseFloat(element.style.top || 0);
2044     element._originalWidth  = element.style.width;
2045     element._originalHeight = element.style.height;
2046
2047     element.style.position = 'absolute';
2048     element.style.top    = top + 'px';
2049     element.style.left   = left + 'px';
2050     element.style.width  = width + 'px';
2051     element.style.height = height + 'px';
2052     return element;
2053   },
2054
2055   relativize: function(element) {
2056     element = $(element);
2057     if (element.getStyle('position') == 'relative') return;
2058     // Position.prepare(); // To be done manually by Scripty when it needs it.
2059
2060     element.style.position = 'relative';
2061     var top  = parseFloat(element.style.top  || 0) - (element._originalTop || 0);
2062     var left = parseFloat(element.style.left || 0) - (element._originalLeft || 0);
2063
2064     element.style.top    = top + 'px';
2065     element.style.left   = left + 'px';
2066     element.style.height = element._originalHeight;
2067     element.style.width  = element._originalWidth;
2068     return element;
2069   },
2070
2071   cumulativeScrollOffset: function(element) {
2072     var valueT = 0, valueL = 0;
2073     do {
2074       valueT += element.scrollTop  || 0;
2075       valueL += element.scrollLeft || 0;
2076       element = element.parentNode;
2077     } while (element);
2078     return Element._returnOffset(valueL, valueT);
2079   },
2080
2081   getOffsetParent: function(element) {
2082     if (element.offsetParent) return $(element.offsetParent);
2083     if (element == document.body) return $(element);
2084
2085     while ((element = element.parentNode) && element != document.body)
2086       if (Element.getStyle(element, 'position') != 'static')
2087         return $(element);
2088
2089     return $(document.body);
2090   },
2091
2092   viewportOffset: function(forElement) {
2093     var valueT = 0, valueL = 0;
2094
2095     var element = forElement;
2096     do {
2097       valueT += element.offsetTop  || 0;
2098       valueL += element.offsetLeft || 0;
2099
2100       // Safari fix
2101       if (element.offsetParent == document.body &&
2102         Element.getStyle(element, 'position') == 'absolute') break;
2103
2104     } while (element = element.offsetParent);
2105
2106     element = forElement;
2107     do {
2108       if (!Prototype.Browser.Opera || element.tagName == 'BODY') {
2109         valueT -= element.scrollTop  || 0;
2110         valueL -= element.scrollLeft || 0;
2111       }
2112     } while (element = element.parentNode);
2113
2114     return Element._returnOffset(valueL, valueT);
2115   },
2116
2117   clonePosition: function(element, source) {
2118     var options = Object.extend({
2119       setLeft:    true,
2120       setTop:     true,
2121       setWidth:   true,
2122       setHeight:  true,
2123       offsetTop:  0,
2124       offsetLeft: 0
2125     }, arguments[2] || { });
2126
2127     // find page position of source
2128     source = $(source);
2129     var p = source.viewportOffset();
2130
2131     // find coordinate system to use
2132     element = $(element);
2133     var delta = [0, 0];
2134     var parent = null;
2135     // delta [0,0] will do fine with position: fixed elements,
2136     // position:absolute needs offsetParent deltas
2137     if (Element.getStyle(element, 'position') == 'absolute') {
2138       parent = element.getOffsetParent();
2139       delta = parent.viewportOffset();
2140     }
2141
2142     // correct by body offsets (fixes Safari)
2143     if (parent == document.body) {
2144       delta[0] -= document.body.offsetLeft;
2145       delta[1] -= document.body.offsetTop;
2146     }
2147
2148     // set position
2149     if (options.setLeft)   element.style.left  = (p[0] - delta[0] + options.offsetLeft) + 'px';
2150     if (options.setTop)    element.style.top   = (p[1] - delta[1] + options.offsetTop) + 'px';
2151     if (options.setWidth)  element.style.width = source.offsetWidth + 'px';
2152     if (options.setHeight) element.style.height = source.offsetHeight + 'px';
2153     return element;
2154   }
2155 };
2156
2157 Element.Methods.identify.counter = 1;
2158
2159 Object.extend(Element.Methods, {
2160   getElementsBySelector: Element.Methods.select,
2161   childElements: Element.Methods.immediateDescendants
2162 });
2163
2164 Element._attributeTranslations = {
2165   write: {
2166     names: {
2167       className: 'class',
2168       htmlFor:   'for'
2169     },
2170     values: { }
2171   }
2172 };
2173
2174
2175 if (!document.createRange || Prototype.Browser.Opera) {
2176   Element.Methods.insert = function(element, insertions) {
2177     element = $(element);
2178
2179     if (Object.isString(insertions) || Object.isNumber(insertions) ||
2180         Object.isElement(insertions) || (insertions && (insertions.toElement || insertions.toHTML)))
2181           insertions = { bottom: insertions };
2182
2183     var t = Element._insertionTranslations, content, position, pos, tagName;
2184
2185     for (position in insertions) {
2186       content  = insertions[position];
2187       position = position.toLowerCase();
2188       pos      = t[position];
2189
2190       if (content && content.toElement) content = content.toElement();
2191       if (Object.isElement(content)) {
2192         pos.insert(element, content);
2193         continue;
2194       }
2195
2196       content = Object.toHTML(content);
2197       tagName = ((position == 'before' || position == 'after')
2198         ? element.parentNode : element).tagName.toUpperCase();
2199
2200       if (t.tags[tagName]) {
2201         var fragments = Element._getContentFromAnonymousElement(tagName, content.stripScripts());
2202         if (position == 'top' || position == 'after') fragments.reverse();
2203         fragments.each(pos.insert.curry(element));
2204       }
2205       else element.insertAdjacentHTML(pos.adjacency, content.stripScripts());
2206
2207       content.evalScripts.bind(content).defer();
2208     }
2209
2210     return element;
2211   };
2212 }
2213
2214 if (Prototype.Browser.Opera) {
2215   Element.Methods._getStyle = Element.Methods.getStyle;
2216   Element.Methods.getStyle = function(element, style) {
2217     switch(style) {
2218       case 'left':
2219       case 'top':
2220       case 'right':
2221       case 'bottom':
2222         if (Element._getStyle(element, 'position') == 'static') return null;
2223       default: return Element._getStyle(element, style);
2224     }
2225   };
2226   Element.Methods._readAttribute = Element.Methods.readAttribute;
2227   Element.Methods.readAttribute = function(element, attribute) {
2228     if (attribute == 'title') return element.title;
2229     return Element._readAttribute(element, attribute);
2230   };
2231 }
2232
2233 else if (Prototype.Browser.IE) {
2234   $w('positionedOffset getOffsetParent viewportOffset').each(function(method) {
2235     Element.Methods[method] = Element.Methods[method].wrap(
2236       function(proceed, element) {
2237         element = $(element);
2238         var position = element.getStyle('position');
2239         if (position != 'static') return proceed(element);
2240         element.setStyle({ position: 'relative' });
2241         var value = proceed(element);
2242         element.setStyle({ position: position });
2243         return value;
2244       }
2245     );
2246   });
2247
2248   Element.Methods.getStyle = function(element, style) {
2249     element = $(element);
2250     style = (style == 'float' || style == 'cssFloat') ? 'styleFloat' : style.camelize();
2251     var value = element.style[style];
2252     if (!value && element.currentStyle) value = element.currentStyle[style];
2253
2254     if (style == 'opacity') {
2255       if (value = (element.getStyle('filter') || '').match(/alpha\(opacity=(.*)\)/))
2256         if (value[1]) return parseFloat(value[1]) / 100;
2257       return 1.0;
2258     }
2259
2260     if (value == 'auto') {
2261       if ((style == 'width' || style == 'height') && (element.getStyle('display') != 'none'))
2262         return element['offset' + style.capitalize()] + 'px';
2263       return null;
2264     }
2265     return value;
2266   };
2267
2268   Element.Methods.setOpacity = function(element, value) {
2269     function stripAlpha(filter){
2270       return filter.replace(/alpha\([^\)]*\)/gi,'');
2271     }
2272     element = $(element);
2273     var currentStyle = element.currentStyle;
2274     if ((currentStyle && !currentStyle.hasLayout) ||
2275       (!currentStyle && element.style.zoom == 'normal'))
2276         element.style.zoom = 1;
2277
2278     var filter = element.getStyle('filter'), style = element.style;
2279     if (value == 1 || value === '') {
2280       (filter = stripAlpha(filter)) ?
2281         style.filter = filter : style.removeAttribute('filter');
2282       return element;
2283     } else if (value < 0.00001) value = 0;
2284     style.filter = stripAlpha(filter) +
2285       'alpha(opacity=' + (value * 100) + ')';
2286     return element;
2287   };
2288
2289   Element._attributeTranslations = {
2290     read: {
2291       names: {
2292         'class': 'className',
2293         'for':   'htmlFor'
2294       },
2295       values: {
2296         _getAttr: function(element, attribute) {
2297           return element.getAttribute(attribute, 2);
2298         },
2299         _getAttrNode: function(element, attribute) {
2300           var node = element.getAttributeNode(attribute);
2301           return node ? node.value : "";
2302         },
2303         _getEv: function(element, attribute) {
2304           var attribute = element.getAttribute(attribute);
2305           return attribute ? attribute.toString().slice(23, -2) : null;
2306         },
2307         _flag: function(element, attribute) {
2308           return $(element).hasAttribute(attribute) ? attribute : null;
2309         },
2310         style: function(element) {
2311           return element.style.cssText.toLowerCase();
2312         },
2313         title: function(element) {
2314           return element.title;
2315         }
2316       }
2317     }
2318   };
2319
2320   Element._attributeTranslations.write = {
2321     names: Object.clone(Element._attributeTranslations.read.names),
2322     values: {
2323       checked: function(element, value) {
2324         element.checked = !!value;
2325       },
2326
2327       style: function(element, value) {
2328         element.style.cssText = value ? value : '';
2329       }
2330     }
2331   };
2332
2333   Element._attributeTranslations.has = {};
2334
2335   $w('colSpan rowSpan vAlign dateTime accessKey tabIndex ' +
2336       'encType maxLength readOnly longDesc').each(function(attr) {
2337     Element._attributeTranslations.write.names[attr.toLowerCase()] = attr;
2338     Element._attributeTranslations.has[attr.toLowerCase()] = attr;
2339   });
2340
2341   (function(v) {
2342     Object.extend(v, {
2343       href:        v._getAttr,
2344       src:         v._getAttr,
2345       type:        v._getAttr,
2346       action:      v._getAttrNode,
2347       disabled:    v._flag,
2348       checked:     v._flag,
2349       readonly:    v._flag,
2350       multiple:    v._flag,
2351       onload:      v._getEv,
2352       onunload:    v._getEv,
2353       onclick:     v._getEv,
2354       ondblclick:  v._getEv,
2355       onmousedown: v._getEv,
2356       onmouseup:   v._getEv,
2357       onmouseover: v._getEv,
2358       onmousemove: v._getEv,
2359       onmouseout:  v._getEv,
2360       onfocus:     v._getEv,
2361       onblur:      v._getEv,
2362       onkeypress:  v._getEv,
2363       onkeydown:   v._getEv,
2364       onkeyup:     v._getEv,
2365       onsubmit:    v._getEv,
2366       onreset:     v._getEv,
2367       onselect:    v._getEv,
2368       onchange:    v._getEv
2369     });
2370   })(Element._attributeTranslations.read.values);
2371 }
2372
2373 else if (Prototype.Browser.Gecko && /rv:1\.8\.0/.test(navigator.userAgent)) {
2374   Element.Methods.setOpacity = function(element, value) {
2375     element = $(element);
2376     element.style.opacity = (value == 1) ? 0.999999 :
2377       (value === '') ? '' : (value < 0.00001) ? 0 : value;
2378     return element;
2379   };
2380 }
2381
2382 else if (Prototype.Browser.WebKit) {
2383   Element.Methods.setOpacity = function(element, value) {
2384     element = $(element);
2385     element.style.opacity = (value == 1 || value === '') ? '' :
2386       (value < 0.00001) ? 0 : value;
2387
2388     if (value == 1)
2389       if(element.tagName == 'IMG' && element.width) {
2390         element.width++; element.width--;
2391       } else try {
2392         var n = document.createTextNode(' ');
2393         element.appendChild(n);
2394         element.removeChild(n);
2395       } catch (e) { }
2396
2397     return element;
2398   };
2399
2400   // Safari returns margins on body which is incorrect if the child is absolutely
2401   // positioned.  For performance reasons, redefine Position.cumulativeOffset for
2402   // KHTML/WebKit only.
2403   Element.Methods.cumulativeOffset = function(element) {
2404     var valueT = 0, valueL = 0;
2405     do {
2406       valueT += element.offsetTop  || 0;
2407       valueL += element.offsetLeft || 0;
2408       if (element.offsetParent == document.body)
2409         if (Element.getStyle(element, 'position') == 'absolute') break;
2410
2411       element = element.offsetParent;
2412     } while (element);
2413
2414     return Element._returnOffset(valueL, valueT);
2415   };
2416 }
2417
2418 if (Prototype.Browser.IE || Prototype.Browser.Opera) {
2419   // IE and Opera are missing .innerHTML support for TABLE-related and SELECT elements
2420   Element.Methods.update = function(element, content) {
2421     element = $(element);
2422
2423     if (content && content.toElement) content = content.toElement();
2424     if (Object.isElement(content)) return element.update().insert(content);
2425
2426     content = Object.toHTML(content);
2427     var tagName = element.tagName.toUpperCase();
2428
2429     if (tagName in Element._insertionTranslations.tags) {
2430       $A(element.childNodes).each(function(node) { element.removeChild(node) });
2431       Element._getContentFromAnonymousElement(tagName, content.stripScripts())
2432         .each(function(node) { element.appendChild(node) });
2433     }
2434     else element.innerHTML = content.stripScripts();
2435
2436     content.evalScripts.bind(content).defer();
2437     return element;
2438   };
2439 }
2440
2441 if (document.createElement('div').outerHTML) {
2442   Element.Methods.replace = function(element, content) {
2443     element = $(element);
2444
2445     if (content && content.toElement) content = content.toElement();
2446     if (Object.isElement(content)) {
2447       element.parentNode.replaceChild(content, element);
2448       return element;
2449     }
2450
2451     content = Object.toHTML(content);
2452     var parent = element.parentNode, tagName = parent.tagName.toUpperCase();
2453
2454     if (Element._insertionTranslations.tags[tagName]) {
2455       var nextSibling = element.next();
2456       var fragments = Element._getContentFromAnonymousElement(tagName, content.stripScripts());
2457       parent.removeChild(element);
2458       if (nextSibling)
2459         fragments.each(function(node) { parent.insertBefore(node, nextSibling) });
2460       else
2461         fragments.each(function(node) { parent.appendChild(node) });
2462     }
2463     else element.outerHTML = content.stripScripts();
2464
2465     content.evalScripts.bind(content).defer();
2466     return element;
2467   };
2468 }
2469
2470 Element._returnOffset = function(l, t) {
2471   var result = [l, t];
2472   result.left = l;
2473   result.top = t;
2474   return result;
2475 };
2476
2477 Element._getContentFromAnonymousElement = function(tagName, html) {
2478   var div = new Element('div'), t = Element._insertionTranslations.tags[tagName];
2479   div.innerHTML = t[0] + html + t[1];
2480   t[2].times(function() { div = div.firstChild });
2481   return $A(div.childNodes);
2482 };
2483
2484 Element._insertionTranslations = {
2485   before: {
2486     adjacency: 'beforeBegin',
2487     insert: function(element, node) {
2488       element.parentNode.insertBefore(node, element);
2489     },
2490     initializeRange: function(element, range) {
2491       range.setStartBefore(element);
2492     }
2493   },
2494   top: {
2495     adjacency: 'afterBegin',
2496     insert: function(element, node) {
2497       element.insertBefore(node, element.firstChild);
2498     },
2499     initializeRange: function(element, range) {
2500       range.selectNodeContents(element);
2501       range.collapse(true);
2502     }
2503   },
2504   bottom: {
2505     adjacency: 'beforeEnd',
2506     insert: function(element, node) {
2507       element.appendChild(node);
2508     }
2509   },
2510   after: {
2511     adjacency: 'afterEnd',
2512     insert: function(element, node) {
2513       element.parentNode.insertBefore(node, element.nextSibling);
2514     },
2515     initializeRange: function(element, range) {
2516       range.setStartAfter(element);
2517     }
2518   },
2519   tags: {
2520     TABLE:  ['<table>',                '</table>',                   1],
2521     TBODY:  ['<table><tbody>',         '</tbody></table>',           2],
2522     TR:     ['<table><tbody><tr>',     '</tr></tbody></table>',      3],
2523     TD:     ['<table><tbody><tr><td>', '</td></tr></tbody></table>', 4],
2524     SELECT: ['<select>',               '</select>',                  1]
2525   }
2526 };
2527
2528 (function() {
2529   this.bottom.initializeRange = this.top.initializeRange;
2530   Object.extend(this.tags, {
2531     THEAD: this.tags.TBODY,
2532     TFOOT: this.tags.TBODY,
2533     TH:    this.tags.TD
2534   });
2535 }).call(Element._insertionTranslations);
2536
2537 Element.Methods.Simulated = {
2538   hasAttribute: function(element, attribute) {
2539     attribute = Element._attributeTranslations.has[attribute] || attribute;
2540     var node = $(element).getAttributeNode(attribute);
2541     return node && node.specified;
2542   }
2543 };
2544
2545 Element.Methods.ByTag = { };
2546
2547 Object.extend(Element, Element.Methods);
2548
2549 if (!Prototype.BrowserFeatures.ElementExtensions &&
2550     document.createElement('div').__proto__) {
2551   window.HTMLElement = { };
2552   window.HTMLElement.prototype = document.createElement('div').__proto__;
2553   Prototype.BrowserFeatures.ElementExtensions = true;
2554 }
2555
2556 Element.extend = (function() {
2557   if (Prototype.BrowserFeatures.SpecificElementExtensions)
2558     return Prototype.K;
2559
2560   var Methods = { }, ByTag = Element.Methods.ByTag;
2561
2562   var extend = Object.extend(function(element) {
2563     if (!element || element._extendedByPrototype ||
2564         element.nodeType != 1 || element == window) return element;
2565
2566     var methods = Object.clone(Methods),
2567       tagName = element.tagName, property, value;
2568
2569     // extend methods for specific tags
2570     if (ByTag[tagName]) Object.extend(methods, ByTag[tagName]);
2571
2572     for (property in methods) {
2573       value = methods[property];
2574       if (Object.isFunction(value) && !(property in element))
2575         element[property] = value.methodize();
2576     }
2577
2578     element._extendedByPrototype = Prototype.emptyFunction;
2579     return element;
2580
2581   }, {
2582     refresh: function() {
2583       // extend methods for all tags (Safari doesn't need this)
2584       if (!Prototype.BrowserFeatures.ElementExtensions) {
2585         Object.extend(Methods, Element.Methods);
2586         Object.extend(Methods, Element.Methods.Simulated);
2587       }
2588     }
2589   });
2590
2591   extend.refresh();
2592   return extend;
2593 })();
2594
2595 Element.hasAttribute = function(element, attribute) {
2596   if (element.hasAttribute) return element.hasAttribute(attribute);
2597   return Element.Methods.Simulated.hasAttribute(element, attribute);
2598 };
2599
2600 Element.addMethods = function(methods) {
2601   var F = Prototype.BrowserFeatures, T = Element.Methods.ByTag;
2602
2603   if (!methods) {
2604     Object.extend(Form, Form.Methods);
2605     Object.extend(Form.Element, Form.Element.Methods);
2606     Object.extend(Element.Methods.ByTag, {
2607       "FORM":     Object.clone(Form.Methods),
2608       "INPUT":    Object.clone(Form.Element.Methods),
2609       "SELECT":   Object.clone(Form.Element.Methods),
2610       "TEXTAREA": Object.clone(Form.Element.Methods)
2611     });
2612   }
2613
2614   if (arguments.length == 2) {
2615     var tagName = methods;
2616     methods = arguments[1];
2617   }
2618
2619   if (!tagName) Object.extend(Element.Methods, methods || { });
2620   else {
2621     if (Object.isArray(tagName)) tagName.each(extend);
2622     else extend(tagName);
2623   }
2624
2625   function extend(tagName) {
2626     tagName = tagName.toUpperCase();
2627     if (!Element.Methods.ByTag[tagName])
2628       Element.Methods.ByTag[tagName] = { };
2629     Object.extend(Element.Methods.ByTag[tagName], methods);
2630   }
2631
2632   function copy(methods, destination, onlyIfAbsent) {
2633     onlyIfAbsent = onlyIfAbsent || false;
2634     for (var property in methods) {
2635       var value = methods[property];
2636       if (!Object.isFunction(value)) continue;
2637       if (!onlyIfAbsent || !(property in destination))
2638         destination[property] = value.methodize();
2639     }
2640   }
2641
2642   function findDOMClass(tagName) {
2643     var klass;
2644     var trans = {
2645       "OPTGROUP": "OptGroup", "TEXTAREA": "TextArea", "P": "Paragraph",
2646       "FIELDSET": "FieldSet", "UL": "UList", "OL": "OList", "DL": "DList",
2647       "DIR": "Directory", "H1": "Heading", "H2": "Heading", "H3": "Heading",
2648       "H4": "Heading", "H5": "Heading", "H6": "Heading", "Q": "Quote",
2649       "INS": "Mod", "DEL": "Mod", "A": "Anchor", "IMG": "Image", "CAPTION":
2650       "TableCaption", "COL": "TableCol", "COLGROUP": "TableCol", "THEAD":
2651       "TableSection", "TFOOT": "TableSection", "TBODY": "TableSection", "TR":
2652       "TableRow", "TH": "TableCell", "TD": "TableCell", "FRAMESET":
2653       "FrameSet", "IFRAME": "IFrame"
2654     };
2655     if (trans[tagName]) klass = 'HTML' + trans[tagName] + 'Element';
2656     if (window[klass]) return window[klass];
2657     klass = 'HTML' + tagName + 'Element';
2658     if (window[klass]) return window[klass];
2659     klass = 'HTML' + tagName.capitalize() + 'Element';
2660     if (window[klass]) return window[klass];
2661
2662     window[klass] = { };
2663     window[klass].prototype = document.createElement(tagName).__proto__;
2664     return window[klass];
2665   }
2666
2667   if (F.ElementExtensions) {
2668     copy(Element.Methods, HTMLElement.prototype);
2669     copy(Element.Methods.Simulated, HTMLElement.prototype, true);
2670   }
2671
2672   if (F.SpecificElementExtensions) {
2673     for (var tag in Element.Methods.ByTag) {
2674       var klass = findDOMClass(tag);
2675       if (Object.isUndefined(klass)) continue;
2676       copy(T[tag], klass.prototype);
2677     }
2678   }
2679
2680   Object.extend(Element, Element.Methods);
2681   delete Element.ByTag;
2682
2683   if (Element.extend.refresh) Element.extend.refresh();
2684   Element.cache = { };
2685 };
2686
2687 document.viewport = {
2688   getDimensions: function() {
2689     var dimensions = { };
2690     $w('width height').each(function(d) {
2691       var D = d.capitalize();
2692       dimensions[d] = self['inner' + D] ||
2693        (document.documentElement['client' + D] || document.body['client' + D]);
2694     });
2695     return dimensions;
2696   },
2697
2698   getWidth: function() {
2699     return this.getDimensions().width;
2700   },
2701
2702   getHeight: function() {
2703     return this.getDimensions().height;
2704   },
2705
2706   getScrollOffsets: function() {
2707     return Element._returnOffset(
2708       window.pageXOffset || document.documentElement.scrollLeft || document.body.scrollLeft,
2709       window.pageYOffset || document.documentElement.scrollTop || document.body.scrollTop);
2710   }
2711 };
2712 /* Portions of the Selector class are derived from Jack Slocum’s DomQuery,
2713  * part of YUI-Ext version 0.40, distributed under the terms of an MIT-style
2714  * license.  Please see http://www.yui-ext.com/ for more information. */
2715
2716 var Selector = Class.create({
2717   initialize: function(expression) {
2718     this.expression = expression.strip();
2719     this.compileMatcher();
2720   },
2721
2722   compileMatcher: function() {
2723     // Selectors with namespaced attributes can't use the XPath version
2724     if (Prototype.BrowserFeatures.XPath && !(/(\[[\w-]*?:|:checked)/).test(this.expression))
2725       return this.compileXPathMatcher();
2726
2727     var e = this.expression, ps = Selector.patterns, h = Selector.handlers,
2728         c = Selector.criteria, le, p, m;
2729
2730     if (Selector._cache[e]) {
2731       this.matcher = Selector._cache[e];
2732       return;
2733     }
2734
2735     this.matcher = ["this.matcher = function(root) {",
2736                     "var r = root, h = Selector.handlers, c = false, n;"];
2737
2738     while (e && le != e && (/\S/).test(e)) {
2739       le = e;
2740       for (var i in ps) {
2741         p = ps[i];
2742         if (m = e.match(p)) {
2743           this.matcher.push(Object.isFunction(c[i]) ? c[i](m) :
2744               new Template(c[i]).evaluate(m));
2745           e = e.replace(m[0], '');
2746           break;
2747         }
2748       }
2749     }
2750
2751     this.matcher.push("return h.unique(n);\n}");
2752     eval(this.matcher.join('\n'));
2753     Selector._cache[this.expression] = this.matcher;
2754   },
2755
2756   compileXPathMatcher: function() {
2757     var e = this.expression, ps = Selector.patterns,
2758         x = Selector.xpath, le, m;
2759
2760     if (Selector._cache[e]) {
2761       this.xpath = Selector._cache[e]; return;
2762     }
2763
2764     this.matcher = ['.//*'];
2765     while (e && le != e && (/\S/).test(e)) {
2766       le = e;
2767       for (var i in ps) {
2768         if (m = e.match(ps[i])) {
2769           this.matcher.push(Object.isFunction(x[i]) ? x[i](m) :
2770             new Template(x[i]).evaluate(m));
2771           e = e.replace(m[0], '');
2772           break;
2773         }
2774       }
2775     }
2776
2777     this.xpath = this.matcher.join('');
2778     Selector._cache[this.expression] = this.xpath;
2779   },
2780
2781   findElements: function(root) {
2782     root = root || document;
2783     if (this.xpath) return document._getElementsByXPath(this.xpath, root);
2784     return this.matcher(root);
2785   },
2786
2787   match: function(element) {
2788     this.tokens = [];
2789
2790     var e = this.expression, ps = Selector.patterns, as = Selector.assertions;
2791     var le, p, m;
2792
2793     while (e && le !== e && (/\S/).test(e)) {
2794       le = e;
2795       for (var i in ps) {
2796         p = ps[i];
2797         if (m = e.match(p)) {
2798           // use the Selector.assertions methods unless the selector
2799           // is too complex.
2800           if (as[i]) {
2801             this.tokens.push([i, Object.clone(m)]);
2802             e = e.replace(m[0], '');
2803           } else {
2804             // reluctantly do a document-wide search
2805             // and look for a match in the array
2806             return this.findElements(document).include(element);
2807           }
2808         }
2809       }
2810     }
2811
2812     var match = true, name, matches;
2813     for (var i = 0, token; token = this.tokens[i]; i++) {
2814       name = token[0], matches = token[1];
2815       if (!Selector.assertions[name](element, matches)) {
2816         match = false; break;
2817       }
2818     }
2819
2820     return match;
2821   },
2822
2823   toString: function() {
2824     return this.expression;
2825   },
2826
2827   inspect: function() {
2828     return "#<Selector:" + this.expression.inspect() + ">";
2829   }
2830 });
2831
2832 Object.extend(Selector, {
2833   _cache: { },
2834
2835   xpath: {
2836     descendant:   "//*",
2837     child:        "/*",
2838     adjacent:     "/following-sibling::*[1]",
2839     laterSibling: '/following-sibling::*',
2840     tagName:      function(m) {
2841       if (m[1] == '*') return '';
2842       return "[local-name()='" + m[1].toLowerCase() +
2843              "' or local-name()='" + m[1].toUpperCase() + "']";
2844     },
2845     className:    "[contains(concat(' ', @class, ' '), ' #{1} ')]",
2846     id:           "[@id='#{1}']",
2847     attrPresence: "[@#{1}]",
2848     attr: function(m) {
2849       m[3] = m[5] || m[6];
2850       return new Template(Selector.xpath.operators[m[2]]).evaluate(m);
2851     },
2852     pseudo: function(m) {
2853       var h = Selector.xpath.pseudos[m[1]];
2854       if (!h) return '';
2855       if (Object.isFunction(h)) return h(m);
2856       return new Template(Selector.xpath.pseudos[m[1]]).evaluate(m);
2857     },
2858     operators: {
2859       '=':  "[@#{1}='#{3}']",
2860       '!=': "[@#{1}!='#{3}']",
2861       '^=': "[starts-with(@#{1}, '#{3}')]",
2862       '$=': "[substring(@#{1}, (string-length(@#{1}) - string-length('#{3}') + 1))='#{3}']",
2863       '*=': "[contains(@#{1}, '#{3}')]",
2864       '~=': "[contains(concat(' ', @#{1}, ' '), ' #{3} ')]",
2865       '|=': "[contains(concat('-', @#{1}, '-'), '-#{3}-')]"
2866     },
2867     pseudos: {
2868       'first-child': '[not(preceding-sibling::*)]',
2869       'last-child':  '[not(following-sibling::*)]',
2870       'only-child':  '[not(preceding-sibling::* or following-sibling::*)]',
2871       'empty':       "[count(*) = 0 and (count(text()) = 0 or translate(text(), ' \t\r\n', '') = '')]",
2872       'checked':     "[@checked]",
2873       'disabled':    "[@disabled]",
2874       'enabled':     "[not(@disabled)]",
2875       'not': function(m) {
2876         var e = m[6], p = Selector.patterns,
2877             x = Selector.xpath, le, m, v;
2878
2879         var exclusion = [];
2880         while (e && le != e && (/\S/).test(e)) {
2881           le = e;
2882           for (var i in p) {
2883             if (m = e.match(p[i])) {
2884               v = Object.isFunction(x[i]) ? x[i](m) : new Template(x[i]).evaluate(m);
2885               exclusion.push("(" + v.substring(1, v.length - 1) + ")");
2886               e = e.replace(m[0], '');
2887               break;
2888             }
2889           }
2890         }
2891         return "[not(" + exclusion.join(" and ") + ")]";
2892       },
2893       'nth-child':      function(m) {
2894         return Selector.xpath.pseudos.nth("(count(./preceding-sibling::*) + 1) ", m);
2895       },
2896       'nth-last-child': function(m) {
2897         return Selector.xpath.pseudos.nth("(count(./following-sibling::*) + 1) ", m);
2898       },
2899       'nth-of-type':    function(m) {
2900         return Selector.xpath.pseudos.nth("position() ", m);
2901       },
2902       'nth-last-of-type': function(m) {
2903         return Selector.xpath.pseudos.nth("(last() + 1 - position()) ", m);
2904       },
2905       'first-of-type':  function(m) {
2906         m[6] = "1"; return Selector.xpath.pseudos['nth-of-type'](m);
2907       },
2908       'last-of-type':   function(m) {
2909         m[6] = "1"; return Selector.xpath.pseudos['nth-last-of-type'](m);
2910       },
2911       'only-of-type':   function(m) {
2912         var p = Selector.xpath.pseudos; return p['first-of-type'](m) + p['last-of-type'](m);
2913       },
2914       nth: function(fragment, m) {
2915         var mm, formula = m[6], predicate;
2916         if (formula == 'even') formula = '2n+0';
2917         if (formula == 'odd')  formula = '2n+1';
2918         if (mm = formula.match(/^(\d+)$/)) // digit only
2919           return '[' + fragment + "= " + mm[1] + ']';
2920         if (mm = formula.match(/^(-?\d*)?n(([+-])(\d+))?/)) { // an+b
2921           if (mm[1] == "-") mm[1] = -1;
2922           var a = mm[1] ? Number(mm[1]) : 1;
2923           var b = mm[2] ? Number(mm[2]) : 0;
2924           predicate = "[((#{fragment} - #{b}) mod #{a} = 0) and " +
2925           "((#{fragment} - #{b}) div #{a} >= 0)]";
2926           return new Template(predicate).evaluate({
2927             fragment: fragment, a: a, b: b });
2928         }
2929       }
2930     }
2931   },
2932
2933   criteria: {
2934     tagName:      'n = h.tagName(n, r, "#{1}", c);   c = false;',
2935     className:    'n = h.className(n, r, "#{1}", c); c = false;',
2936     id:           'n = h.id(n, r, "#{1}", c);        c = false;',
2937     attrPresence: 'n = h.attrPresence(n, r, "#{1}"); c = false;',
2938     attr: function(m) {
2939       m[3] = (m[5] || m[6]);
2940       return new Template('n = h.attr(n, r, "#{1}", "#{3}", "#{2}"); c = false;').evaluate(m);
2941     },
2942     pseudo: function(m) {
2943       if (m[6]) m[6] = m[6].replace(/"/g, '\\"');
2944       return new Template('n = h.pseudo(n, "#{1}", "#{6}", r, c); c = false;').evaluate(m);
2945     },
2946     descendant:   'c = "descendant";',
2947     child:        'c = "child";',
2948     adjacent:     'c = "adjacent";',
2949     laterSibling: 'c = "laterSibling";'
2950   },
2951
2952   patterns: {
2953     // combinators must be listed first
2954     // (and descendant needs to be last combinator)
2955     laterSibling: /^\s*~\s*/,
2956     child:        /^\s*>\s*/,
2957     adjacent:     /^\s*\+\s*/,
2958     descendant:   /^\s/,
2959
2960     // selectors follow
2961     tagName:      /^\s*(\*|[\w\-]+)(\b|$)?/,
2962     id:           /^#([\w\-\*]+)(\b|$)/,
2963     className:    /^\.([\w\-\*]+)(\b|$)/,
2964     pseudo:       /^:((first|last|nth|nth-last|only)(-child|-of-type)|empty|checked|(en|dis)abled|not)(\((.*?)\))?(\b|$|(?=\s)|(?=:))/,
2965     attrPresence: /^\[([\w]+)\]/,
2966     attr:         /\[((?:[\w-]*:)?[\w-]+)\s*(?:([!^$*~|]?=)\s*((['"])([^\4]*?)\4|([^'"][^\]]*?)))?\]/
2967   },
2968
2969   // for Selector.match and Element#match
2970   assertions: {
2971     tagName: function(element, matches) {
2972       return matches[1].toUpperCase() == element.tagName.toUpperCase();
2973     },
2974
2975     className: function(element, matches) {
2976       return Element.hasClassName(element, matches[1]);
2977     },
2978
2979     id: function(element, matches) {
2980       return element.id === matches[1];
2981     },
2982
2983     attrPresence: function(element, matches) {
2984       return Element.hasAttribute(element, matches[1]);
2985     },
2986
2987     attr: function(element, matches) {
2988       var nodeValue = Element.readAttribute(element, matches[1]);
2989       return Selector.operators[matches[2]](nodeValue, matches[3]);
2990     }
2991   },
2992
2993   handlers: {
2994     // UTILITY FUNCTIONS
2995     // joins two collections
2996     concat: function(a, b) {
2997       for (var i = 0, node; node = b[i]; i++)
2998         a.push(node);
2999       return a;
3000     },
3001
3002     // marks an array of nodes for counting
3003     mark: function(nodes) {
3004       for (var i = 0, node; node = nodes[i]; i++)
3005         node._counted = true;
3006       return nodes;
3007     },
3008
3009     unmark: function(nodes) {
3010       for (var i = 0, node; node = nodes[i]; i++)
3011         node._counted = undefined;
3012       return nodes;
3013     },
3014
3015     // mark each child node with its position (for nth calls)
3016     // "ofType" flag indicates whether we're indexing for nth-of-type
3017     // rather than nth-child
3018     index: function(parentNode, reverse, ofType) {
3019       parentNode._counted = true;
3020       if (reverse) {
3021         for (var nodes = parentNode.childNodes, i = nodes.length - 1, j = 1; i >= 0; i--) {
3022           var node = nodes[i];
3023           if (node.nodeType == 1 && (!ofType || node._counted)) node.nodeIndex = j++;
3024         }
3025       } else {
3026         for (var i = 0, j = 1, nodes = parentNode.childNodes; node = nodes[i]; i++)
3027           if (node.nodeType == 1 && (!ofType || node._counted)) node.nodeIndex = j++;
3028       }
3029     },
3030
3031     // filters out duplicates and extends all nodes
3032     unique: function(nodes) {
3033       if (nodes.length == 0) return nodes;
3034       var results = [], n;
3035       for (var i = 0, l = nodes.length; i < l; i++)
3036         if (!(n = nodes[i])._counted) {
3037           n._counted = true;
3038           results.push(Element.extend(n));
3039         }
3040       return Selector.handlers.unmark(results);
3041     },
3042
3043     // COMBINATOR FUNCTIONS
3044     descendant: function(nodes) {
3045       var h = Selector.handlers;
3046       for (var i = 0, results = [], node; node = nodes[i]; i++)
3047         h.concat(results, node.getElementsByTagName('*'));
3048       return results;
3049     },
3050
3051     child: function(nodes) {
3052       var h = Selector.handlers;
3053       for (var i = 0, results = [], node; node = nodes[i]; i++) {
3054         for (var j = 0, children = [], child; child = node.childNodes[j]; j++)
3055           if (child.nodeType == 1 && child.tagName != '!') results.push(child);
3056       }
3057       return results;
3058     },
3059
3060     adjacent: function(nodes) {
3061       for (var i = 0, results = [], node; node = nodes[i]; i++) {
3062         var next = this.nextElementSibling(node);
3063         if (next) results.push(next);
3064       }
3065       return results;
3066     },
3067
3068     laterSibling: function(nodes) {
3069       var h = Selector.handlers;
3070       for (var i = 0, results = [], node; node = nodes[i]; i++)
3071         h.concat(results, Element.nextSiblings(node));
3072       return results;
3073     },
3074
3075     nextElementSibling: function(node) {
3076       while (node = node.nextSibling)
3077               if (node.nodeType == 1) return node;
3078       return null;
3079     },
3080
3081     previousElementSibling: function(node) {
3082       while (node = node.previousSibling)
3083         if (node.nodeType == 1) return node;
3084       return null;
3085     },
3086
3087     // TOKEN FUNCTIONS
3088     tagName: function(nodes, root, tagName, combinator) {
3089       tagName = tagName.toUpperCase();
3090       var results = [], h = Selector.handlers;
3091       if (nodes) {
3092         if (combinator) {
3093           // fastlane for ordinary descendant combinators
3094           if (combinator == "descendant") {
3095             for (var i = 0, node; node = nodes[i]; i++)
3096               h.concat(results, node.getElementsByTagName(tagName));
3097             return results;
3098           } else nodes = this[combinator](nodes);
3099           if (tagName == "*") return nodes;
3100         }
3101         for (var i = 0, node; node = nodes[i]; i++)
3102           if (node.tagName.toUpperCase() == tagName) results.push(node);
3103         return results;
3104       } else return root.getElementsByTagName(tagName);
3105     },
3106
3107     id: function(nodes, root, id, combinator) {
3108       var targetNode = $(id), h = Selector.handlers;
3109       if (!targetNode) return [];
3110       if (!nodes && root == document) return [targetNode];
3111       if (nodes) {
3112         if (combinator) {
3113           if (combinator == 'child') {
3114             for (var i = 0, node; node = nodes[i]; i++)
3115               if (targetNode.parentNode == node) return [targetNode];
3116           } else if (combinator == 'descendant') {
3117             for (var i = 0, node; node = nodes[i]; i++)
3118               if (Element.descendantOf(targetNode, node)) return [targetNode];
3119           } else if (combinator == 'adjacent') {
3120             for (var i = 0, node; node = nodes[i]; i++)
3121               if (Selector.handlers.previousElementSibling(targetNode) == node)
3122                 return [targetNode];
3123           } else nodes = h[combinator](nodes);
3124         }
3125         for (var i = 0, node; node = nodes[i]; i++)
3126           if (node == targetNode) return [targetNode];
3127         return [];
3128       }
3129       return (targetNode && Element.descendantOf(targetNode, root)) ? [targetNode] : [];
3130     },
3131
3132     className: function(nodes, root, className, combinator) {
3133       if (nodes && combinator) nodes = this[combinator](nodes);
3134       return Selector.handlers.byClassName(nodes, root, className);
3135     },
3136
3137     byClassName: function(nodes, root, className) {
3138       if (!nodes) nodes = Selector.handlers.descendant([root]);
3139       var needle = ' ' + className + ' ';
3140       for (var i = 0, results = [], node, nodeClassName; node = nodes[i]; i++) {
3141         nodeClassName = node.className;
3142         if (nodeClassName.length == 0) continue;
3143         if (nodeClassName == className || (' ' + nodeClassName + ' ').include(needle))
3144           results.push(node);
3145       }
3146       return results;
3147     },
3148
3149     attrPresence: function(nodes, root, attr) {
3150       if (!nodes) nodes = root.getElementsByTagName("*");
3151       var results = [];
3152       for (var i = 0, node; node = nodes[i]; i++)
3153         if (Element.hasAttribute(node, attr)) results.push(node);
3154       return results;
3155     },
3156
3157     attr: function(nodes, root, attr, value, operator) {
3158       if (!nodes) nodes = root.getElementsByTagName("*");
3159       var handler = Selector.operators[operator], results = [];
3160       for (var i = 0, node; node = nodes[i]; i++) {
3161         var nodeValue = Element.readAttribute(node, attr);
3162         if (nodeValue === null) continue;
3163         if (handler(nodeValue, value)) results.push(node);
3164       }
3165       return results;
3166     },
3167
3168     pseudo: function(nodes, name, value, root, combinator) {
3169       if (nodes && combinator) nodes = this[combinator](nodes);
3170       if (!nodes) nodes = root.getElementsByTagName("*");
3171       return Selector.pseudos[name](nodes, value, root);
3172     }
3173   },
3174
3175   pseudos: {
3176     'first-child': function(nodes, value, root) {
3177       for (var i = 0, results = [], node; node = nodes[i]; i++) {
3178         if (Selector.handlers.previousElementSibling(node)) continue;
3179           results.push(node);
3180       }
3181       return results;
3182     },
3183     'last-child': function(nodes, value, root) {
3184       for (var i = 0, results = [], node; node = nodes[i]; i++) {
3185         if (Selector.handlers.nextElementSibling(node)) continue;
3186           results.push(node);
3187       }
3188       return results;
3189     },
3190     'only-child': function(nodes, value, root) {
3191       var h = Selector.handlers;
3192       for (var i = 0, results = [], node; node = nodes[i]; i++)
3193         if (!h.previousElementSibling(node) && !h.nextElementSibling(node))
3194           results.push(node);
3195       return results;
3196     },
3197     'nth-child':        function(nodes, formula, root) {
3198       return Selector.pseudos.nth(nodes, formula, root);
3199     },
3200     'nth-last-child':   function(nodes, formula, root) {
3201       return Selector.pseudos.nth(nodes, formula, root, true);
3202     },
3203     'nth-of-type':      function(nodes, formula, root) {
3204       return Selector.pseudos.nth(nodes, formula, root, false, true);
3205     },
3206     'nth-last-of-type': function(nodes, formula, root) {
3207       return Selector.pseudos.nth(nodes, formula, root, true, true);
3208     },
3209     'first-of-type':    function(nodes, formula, root) {
3210       return Selector.pseudos.nth(nodes, "1", root, false, true);
3211     },
3212     'last-of-type':     function(nodes, formula, root) {
3213       return Selector.pseudos.nth(nodes, "1", root, true, true);
3214     },
3215     'only-of-type':     function(nodes, formula, root) {
3216       var p = Selector.pseudos;
3217       return p['last-of-type'](p['first-of-type'](nodes, formula, root), formula, root);
3218     },
3219
3220     // handles the an+b logic
3221     getIndices: function(a, b, total) {
3222       if (a == 0) return b > 0 ? [b] : [];
3223       return $R(1, total).inject([], function(memo, i) {
3224         if (0 == (i - b) % a && (i - b) / a >= 0) memo.push(i);
3225         return memo;
3226       });
3227     },
3228
3229     // handles nth(-last)-child, nth(-last)-of-type, and (first|last)-of-type
3230     nth: function(nodes, formula, root, reverse, ofType) {
3231       if (nodes.length == 0) return [];
3232       if (formula == 'even') formula = '2n+0';
3233       if (formula == 'odd')  formula = '2n+1';
3234       var h = Selector.handlers, results = [], indexed = [], m;
3235       h.mark(nodes);
3236       for (var i = 0, node; node = nodes[i]; i++) {
3237         if (!node.parentNode._counted) {
3238           h.index(node.parentNode, reverse, ofType);
3239           indexed.push(node.parentNode);
3240         }
3241       }
3242       if (formula.match(/^\d+$/)) { // just a number
3243         formula = Number(formula);
3244         for (var i = 0, node; node = nodes[i]; i++)
3245           if (node.nodeIndex == formula) results.push(node);
3246       } else if (m = formula.match(/^(-?\d*)?n(([+-])(\d+))?/)) { // an+b
3247         if (m[1] == "-") m[1] = -1;
3248         var a = m[1] ? Number(m[1]) : 1;
3249         var b = m[2] ? Number(m[2]) : 0;
3250         var indices = Selector.pseudos.getIndices(a, b, nodes.length);
3251         for (var i = 0, node, l = indices.length; node = nodes[i]; i++) {
3252           for (var j = 0; j < l; j++)
3253             if (node.nodeIndex == indices[j]) results.push(node);
3254         }
3255       }
3256       h.unmark(nodes);
3257       h.unmark(indexed);
3258       return results;
3259     },
3260
3261     'empty': function(nodes, value, root) {
3262       for (var i = 0, results = [], node; node = nodes[i]; i++) {
3263         // IE treats comments as element nodes
3264         if (node.tagName == '!' || (node.firstChild && !node.innerHTML.match(/^\s*$/))) continue;
3265         results.push(node);
3266       }
3267       return results;
3268     },
3269
3270     'not': function(nodes, selector, root) {
3271       var h = Selector.handlers, selectorType, m;
3272       var exclusions = new Selector(selector).findElements(root);
3273       h.mark(exclusions);
3274       for (var i = 0, results = [], node; node = nodes[i]; i++)
3275         if (!node._counted) results.push(node);
3276       h.unmark(exclusions);
3277       return results;
3278     },
3279
3280     'enabled': function(nodes, value, root) {
3281       for (var i = 0, results = [], node; node = nodes[i]; i++)
3282         if (!node.disabled) results.push(node);
3283       return results;
3284     },
3285
3286     'disabled': function(nodes, value, root) {
3287       for (var i = 0, results = [], node; node = nodes[i]; i++)
3288         if (node.disabled) results.push(node);
3289       return results;
3290     },
3291
3292     'checked': function(nodes, value, root) {
3293       for (var i = 0, results = [], node; node = nodes[i]; i++)
3294         if (node.checked) results.push(node);
3295       return results;
3296     }
3297   },
3298
3299   operators: {
3300     '=':  function(nv, v) { return nv == v; },
3301     '!=': function(nv, v) { return nv != v; },
3302     '^=': function(nv, v) { return nv.startsWith(v); },
3303     '$=': function(nv, v) { return nv.endsWith(v); },
3304     '*=': function(nv, v) { return nv.include(v); },
3305     '~=': function(nv, v) { return (' ' + nv + ' ').include(' ' + v + ' '); },
3306     '|=': function(nv, v) { return ('-' + nv.toUpperCase() + '-').include('-' + v.toUpperCase() + '-'); }
3307   },
3308
3309   matchElements: function(elements, expression) {
3310     var matches = new Selector(expression).findElements(), h = Selector.handlers;
3311     h.mark(matches);
3312     for (var i = 0, results = [], element; element = elements[i]; i++)
3313       if (element._counted) results.push(element);
3314     h.unmark(matches);
3315     return results;
3316   },
3317
3318   findElement: function(elements, expression, index) {
3319     if (Object.isNumber(expression)) {
3320       index = expression; expression = false;
3321     }
3322     return Selector.matchElements(elements, expression || '*')[index || 0];
3323   },
3324
3325   findChildElements: function(element, expressions) {
3326     var exprs = expressions.join(','), expressions = [];
3327     exprs.scan(/(([\w#:.~>+()\s-]+|\*|\[.*?\])+)\s*(,|$)/, function(m) {
3328       expressions.push(m[1].strip());
3329     });
3330     var results = [], h = Selector.handlers;
3331     for (var i = 0, l = expressions.length, selector; i < l; i++) {
3332       selector = new Selector(expressions[i].strip());
3333       h.concat(results, selector.findElements(element));
3334     }
3335     return (l > 1) ? h.unique(results) : results;
3336   }
3337 });
3338
3339 function $$() {
3340   return Selector.findChildElements(document, $A(arguments));
3341 }
3342 var Form = {
3343   reset: function(form) {
3344     $(form).reset();
3345     return form;
3346   },
3347
3348   serializeElements: function(elements, options) {
3349     if (typeof options != 'object') options = { hash: !!options };
3350     else if (options.hash === undefined) options.hash = true;
3351     var key, value, submitted = false, submit = options.submit;
3352
3353     var data = elements.inject({ }, function(result, element) {
3354       if (!element.disabled && element.name) {
3355         key = element.name; value = $(element).getValue();
3356         if (value != null && (element.type != 'submit' || (!submitted &&
3357             submit !== false && (!submit || key == submit) && (submitted = true)))) {
3358           if (key in result) {
3359             // a key is already present; construct an array of values
3360             if (!Object.isArray(result[key])) result[key] = [result[key]];
3361             result[key].push(value);
3362           }
3363           else result[key] = value;
3364         }
3365       }
3366       return result;
3367     });
3368
3369     return options.hash ? data : Object.toQueryString(data);
3370   }
3371 };
3372
3373 Form.Methods = {
3374   serialize: function(form, options) {
3375     return Form.serializeElements(Form.getElements(form), options);
3376   },
3377
3378   getElements: function(form) {
3379     return $A($(form).getElementsByTagName('*')).inject([],
3380       function(elements, child) {
3381         if (Form.Element.Serializers[child.tagName.toLowerCase()])
3382           elements.push(Element.extend(child));
3383         return elements;
3384       }
3385     );
3386   },
3387
3388   getInputs: function(form, typeName, name) {
3389     form = $(form);
3390     var inputs = form.getElementsByTagName('input');
3391
3392     if (!typeName && !name) return $A(inputs).map(Element.extend);
3393
3394     for (var i = 0, matchingInputs = [], length = inputs.length; i < length; i++) {
3395       var input = inputs[i];
3396       if ((typeName && input.type != typeName) || (name && input.name != name))
3397         continue;
3398       matchingInputs.push(Element.extend(input));
3399     }
3400
3401     return matchingInputs;
3402   },
3403
3404   disable: function(form) {
3405     form = $(form);
3406     Form.getElements(form).invoke('disable');
3407     return form;
3408   },
3409
3410   enable: function(form) {
3411     form = $(form);
3412     Form.getElements(form).invoke('enable');
3413     return form;
3414   },
3415
3416   findFirstElement: function(form) {
3417     var elements = $(form).getElements().findAll(function(element) {
3418       return 'hidden' != element.type && !element.disabled;
3419     });
3420     var firstByIndex = elements.findAll(function(element) {
3421       return element.hasAttribute('tabIndex') && element.tabIndex >= 0;
3422     }).sortBy(function(element) { return element.tabIndex }).first();
3423
3424     return firstByIndex ? firstByIndex : elements.find(function(element) {
3425       return ['input', 'select', 'textarea'].include(element.tagName.toLowerCase());
3426     });
3427   },
3428
3429   focusFirstElement: function(form) {
3430     form = $(form);
3431     form.findFirstElement().activate();
3432     return form;
3433   },
3434
3435   request: function(form, options) {
3436     form = $(form), options = Object.clone(options || { });
3437
3438     var params = options.parameters, action = form.readAttribute('action') || '';
3439     if (action.blank()) action = window.location.href;
3440     options.parameters = form.serialize(true);
3441
3442     if (params) {
3443       if (Object.isString(params)) params = params.toQueryParams();
3444       Object.extend(options.parameters, params);
3445     }
3446
3447     if (form.hasAttribute('method') && !options.method)
3448       options.method = form.method;
3449
3450     return new Ajax.Request(action, options);
3451   }
3452 };
3453
3454 /*--------------------------------------------------------------------------*/
3455
3456 Form.Element = {
3457   focus: function(element) {
3458     $(element).focus();
3459     return element;
3460   },
3461
3462   select: function(element) {
3463     $(element).select();
3464     return element;
3465   }
3466 };
3467
3468 Form.Element.Methods = {
3469   serialize: function(element) {
3470     element = $(element);
3471     if (!element.disabled && element.name) {
3472       var value = element.getValue();
3473       if (value != undefined) {
3474         var pair = { };
3475         pair[element.name] = value;
3476         return Object.toQueryString(pair);
3477       }
3478     }
3479     return '';
3480   },
3481
3482   getValue: function(element) {
3483     element = $(element);
3484     var method = element.tagName.toLowerCase();
3485     return Form.Element.Serializers[method](element);
3486   },
3487
3488   setValue: function(element, value) {
3489     element = $(element);
3490     var method = element.tagName.toLowerCase();
3491     Form.Element.Serializers[method](element, value);
3492     return element;
3493   },
3494
3495   clear: function(element) {
3496     $(element).value = '';
3497     return element;
3498   },
3499
3500   present: function(element) {
3501     return $(element).value != '';
3502   },
3503
3504   activate: function(element) {
3505     element = $(element);
3506     try {
3507       element.focus();
3508       if (element.select && (element.tagName.toLowerCase() != 'input' ||
3509           !['button', 'reset', 'submit'].include(element.type)))
3510         element.select();
3511     } catch (e) { }
3512     return element;
3513   },
3514
3515   disable: function(element) {
3516     element = $(element);
3517     element.blur();
3518     element.disabled = true;
3519     return element;
3520   },
3521
3522   enable: function(element) {
3523     element = $(element);
3524     element.disabled = false;
3525     return element;
3526   }
3527 };
3528
3529 /*--------------------------------------------------------------------------*/
3530
3531 var Field = Form.Element;
3532 var $F = Form.Element.Methods.getValue;
3533
3534 /*--------------------------------------------------------------------------*/
3535
3536 Form.Element.Serializers = {
3537   input: function(element, value) {
3538     switch (element.type.toLowerCase()) {
3539       case 'checkbox':
3540       case 'radio':
3541         return Form.Element.Serializers.inputSelector(element, value);
3542       default:
3543         return Form.Element.Serializers.textarea(element, value);
3544     }
3545   },
3546
3547   inputSelector: function(element, value) {
3548     if (value === undefined) return element.checked ? element.value : null;
3549     else element.checked = !!value;
3550   },
3551
3552   textarea: function(element, value) {
3553     if (value === undefined) return element.value;
3554     else element.value = value;
3555   },
3556
3557   select: function(element, index) {
3558     if (index === undefined)
3559       return this[element.type == 'select-one' ?
3560         'selectOne' : 'selectMany'](element);
3561     else {
3562       var opt, value, single = !Object.isArray(index);
3563       for (var i = 0, length = element.length; i < length; i++) {
3564         opt = element.options[i];
3565         value = this.optionValue(opt);
3566         if (single) {
3567           if (value == index) {
3568             opt.selected = true;
3569             return;
3570           }
3571         }
3572         else opt.selected = index.include(value);
3573       }
3574     }
3575   },
3576
3577   selectOne: function(element) {
3578     var index = element.selectedIndex;
3579     return index >= 0 ? this.optionValue(element.options[index]) : null;
3580   },
3581
3582   selectMany: function(element) {
3583     var values, length = element.length;
3584     if (!length) return null;
3585
3586     for (var i = 0, values = []; i < length; i++) {
3587       var opt = element.options[i];
3588       if (opt.selected) values.push(this.optionValue(opt));
3589     }
3590     return values;
3591   },
3592
3593   optionValue: function(opt) {
3594     // extend element because hasAttribute may not be native
3595     return Element.extend(opt).hasAttribute('value') ? opt.value : opt.text;
3596   }
3597 };
3598
3599 /*--------------------------------------------------------------------------*/
3600
3601 Abstract.TimedObserver = Class.create(PeriodicalExecuter, {
3602   initialize: function($super, element, frequency, callback) {
3603     $super(callback, frequency);
3604     this.element   = $(element);
3605     this.lastValue = this.getValue();
3606   },
3607
3608   execute: function() {
3609     var value = this.getValue();
3610     if (Object.isString(this.lastValue) && Object.isString(value) ?
3611         this.lastValue != value : String(this.lastValue) != String(value)) {
3612       this.callback(this.element, value);
3613       this.lastValue = value;
3614     }
3615   }
3616 });
3617
3618 Form.Element.Observer = Class.create(Abstract.TimedObserver, {
3619   getValue: function() {
3620     return Form.Element.getValue(this.element);
3621   }
3622 });
3623
3624 Form.Observer = Class.create(Abstract.TimedObserver, {
3625   getValue: function() {
3626     return Form.serialize(this.element);
3627   }
3628 });
3629
3630 /*--------------------------------------------------------------------------*/
3631
3632 Abstract.EventObserver = Class.create({
3633   initialize: function(element, callback) {
3634     this.element  = $(element);
3635     this.callback = callback;
3636
3637     this.lastValue = this.getValue();
3638     if (this.element.tagName.toLowerCase() == 'form')
3639       this.registerFormCallbacks();
3640     else
3641       this.registerCallback(this.element);
3642   },
3643
3644   onElementEvent: function() {
3645     var value = this.getValue();
3646     if (this.lastValue != value) {
3647       this.callback(this.element, value);
3648       this.lastValue = value;
3649     }
3650   },
3651
3652   registerFormCallbacks: function() {
3653     Form.getElements(this.element).each(this.registerCallback, this);
3654   },
3655
3656   registerCallback: function(element) {
3657     if (element.type) {
3658       switch (element.type.toLowerCase()) {
3659         case 'checkbox':
3660         case 'radio':
3661           Event.observe(element, 'click', this.onElementEvent.bind(this));
3662           break;
3663         default:
3664           Event.observe(element, 'change', this.onElementEvent.bind(this));
3665           break;
3666       }
3667     }
3668   }
3669 });
3670
3671 Form.Element.EventObserver = Class.create(Abstract.EventObserver, {
3672   getValue: function() {
3673     return Form.Element.getValue(this.element);
3674   }
3675 });
3676
3677 Form.EventObserver = Class.create(Abstract.EventObserver, {
3678   getValue: function() {
3679     return Form.serialize(this.element);
3680   }
3681 });
3682 if (!window.Event) var Event = { };
3683
3684 Object.extend(Event, {
3685   KEY_BACKSPACE: 8,
3686   KEY_TAB:       9,
3687   KEY_RETURN:   13,
3688   KEY_ESC:      27,
3689   KEY_LEFT:     37,
3690   KEY_UP:       38,
3691   KEY_RIGHT:    39,
3692   KEY_DOWN:     40,
3693   KEY_DELETE:   46,
3694   KEY_HOME:     36,
3695   KEY_END:      35,
3696   KEY_PAGEUP:   33,
3697   KEY_PAGEDOWN: 34,
3698   KEY_INSERT:   45,
3699
3700   cache: { },
3701
3702   relatedTarget: function(event) {
3703     var element;
3704     switch(event.type) {
3705       case 'mouseover': element = event.fromElement; break;
3706       case 'mouseout':  element = event.toElement;   break;
3707       default: return null;
3708     }
3709     return Element.extend(element);
3710   }
3711 });
3712
3713 Event.Methods = (function() {
3714   var isButton;
3715
3716   if (Prototype.Browser.IE) {
3717     var buttonMap = { 0: 1, 1: 4, 2: 2 };
3718     isButton = function(event, code) {
3719       return event.button == buttonMap[code];
3720     };
3721
3722   } else if (Prototype.Browser.WebKit) {
3723     isButton = function(event, code) {
3724       switch (code) {
3725         case 0: return event.which == 1 && !event.metaKey;
3726         case 1: return event.which == 1 && event.metaKey;
3727         default: return false;
3728       }
3729     };
3730
3731   } else {
3732     isButton = function(event, code) {
3733       return event.which ? (event.which === code + 1) : (event.button === code);
3734     };
3735   }
3736
3737   return {
3738     isLeftClick:   function(event) { return isButton(event, 0) },
3739     isMiddleClick: function(event) { return isButton(event, 1) },
3740     isRightClick:  function(event) { return isButton(event, 2) },
3741
3742     element: function(event) {
3743       var node = Event.extend(event).target;
3744       return Element.extend(node.nodeType == Node.TEXT_NODE ? node.parentNode : node);
3745     },
3746
3747     findElement: function(event, expression) {
3748       var element = Event.element(event);
3749       return element.match(expression) ? element : element.up(expression);
3750     },
3751
3752     pointer: function(event) {
3753       return {
3754         x: event.pageX || (event.clientX +
3755           (document.documentElement.scrollLeft || document.body.scrollLeft)),
3756         y: event.pageY || (event.clientY +
3757           (document.documentElement.scrollTop || document.body.scrollTop))
3758       };
3759     },
3760
3761     pointerX: function(event) { return Event.pointer(event).x },
3762     pointerY: function(event) { return Event.pointer(event).y },
3763
3764     stop: function(event) {
3765       Event.extend(event);
3766       event.preventDefault();
3767       event.stopPropagation();
3768       event.stopped = true;
3769     }
3770   };
3771 })();
3772
3773 Event.extend = (function() {
3774   var methods = Object.keys(Event.Methods).inject({ }, function(m, name) {
3775     m[name] = Event.Methods[name].methodize();
3776     return m;
3777   });
3778
3779   if (Prototype.Browser.IE) {
3780     Object.extend(methods, {
3781       stopPropagation: function() { this.cancelBubble = true },
3782       preventDefault:  function() { this.returnValue = false },
3783       inspect: function() { return "[object Event]" }
3784     });
3785
3786     return function(event) {
3787       if (!event) return false;
3788       if (event._extendedByPrototype) return event;
3789
3790       event._extendedByPrototype = Prototype.emptyFunction;
3791       var pointer = Event.pointer(event);
3792       Object.extend(event, {
3793         target: event.srcElement,
3794         relatedTarget: Event.relatedTarget(event),
3795         pageX:  pointer.x,
3796         pageY:  pointer.y
3797       });
3798       return Object.extend(event, methods);
3799     };
3800
3801   } else {
3802     Event.prototype = Event.prototype || document.createEvent("HTMLEvents").__proto__;
3803     Object.extend(Event.prototype, methods);
3804     return Prototype.K;
3805   }
3806 })();
3807
3808 Object.extend(Event, (function() {
3809   var cache = Event.cache;
3810
3811   function getEventID(element) {
3812     if (element._eventID) return element._eventID;
3813     arguments.callee.id = arguments.callee.id || 1;
3814     return element._eventID = ++arguments.callee.id;
3815   }
3816
3817   function getDOMEventName(eventName) {
3818     if (eventName && eventName.include(':')) return "dataavailable";
3819     return eventName;
3820   }
3821
3822   function getCacheForID(id) {
3823     return cache[id] = cache[id] || { };
3824   }
3825
3826   function getWrappersForEventName(id, eventName) {
3827     var c = getCacheForID(id);
3828     return c[eventName] = c[eventName] || [];
3829   }
3830
3831   function createWrapper(element, eventName, handler) {
3832     var id = getEventID(element);
3833     var c = getWrappersForEventName(id, eventName);
3834     if (c.pluck("handler").include(handler)) return false;
3835
3836     var wrapper = function(event) {
3837       if (!Event || !Event.extend ||
3838         (event.eventName && event.eventName != eventName))
3839           return false;
3840
3841       Event.extend(event);
3842       handler.call(element, event)
3843     };
3844
3845     wrapper.handler = handler;
3846     c.push(wrapper);
3847     return wrapper;
3848   }
3849
3850   function findWrapper(id, eventName, handler) {
3851     var c = getWrappersForEventName(id, eventName);
3852     return c.find(function(wrapper) { return wrapper.handler == handler });
3853   }
3854
3855   function destroyWrapper(id, eventName, handler) {
3856     var c = getCacheForID(id);
3857     if (!c[eventName]) return false;
3858     c[eventName] = c[eventName].without(findWrapper(id, eventName, handler));
3859   }
3860
3861   function destroyCache() {
3862     for (var id in cache)
3863       for (var eventName in cache[id])
3864         cache[id][eventName] = null;
3865   }
3866
3867   if (window.attachEvent) {
3868     window.attachEvent("onunload", destroyCache);
3869   }
3870
3871   return {
3872     observe: function(element, eventName, handler) {
3873       element = $(element);
3874       var name = getDOMEventName(eventName);
3875
3876       var wrapper = createWrapper(element, eventName, handler);
3877       if (!wrapper) return element;
3878
3879       if (element.addEventListener) {
3880         element.addEventListener(name, wrapper, false);
3881       } else {
3882         element.attachEvent("on" + name, wrapper);
3883       }
3884
3885       return element;
3886     },
3887
3888     stopObserving: function(element, eventName, handler) {
3889       element = $(element);
3890       var id = getEventID(element), name = getDOMEventName(eventName);
3891
3892       if (!handler && eventName) {
3893         getWrappersForEventName(id, eventName).each(function(wrapper) {
3894           element.stopObserving(eventName, wrapper.handler);
3895         });
3896         return element;
3897
3898       } else if (!eventName) {
3899         Object.keys(getCacheForID(id)).each(function(eventName) {
3900           element.stopObserving(eventName);
3901         });
3902         return element;
3903       }
3904
3905       var wrapper = findWrapper(id, eventName, handler);
3906       if (!wrapper) return element;
3907
3908       if (element.removeEventListener) {
3909         element.removeEventListener(name, wrapper, false);
3910       } else {
3911         element.detachEvent("on" + name, wrapper);
3912       }
3913
3914       destroyWrapper(id, eventName, handler);
3915
3916       return element;
3917     },
3918
3919     fire: function(element, eventName, memo) {
3920       element = $(element);
3921       if (element == document && document.createEvent && !element.dispatchEvent)
3922         element = document.documentElement;
3923
3924       if (document.createEvent) {
3925         var event = document.createEvent("HTMLEvents");
3926         event.initEvent("dataavailable", true, true);
3927       } else {
3928         var event = document.createEventObject();
3929         event.eventType = "ondataavailable";
3930       }
3931
3932       event.eventName = eventName;
3933       event.memo = memo || { };
3934
3935       if (document.createEvent) {
3936         element.dispatchEvent(event);
3937       } else {
3938         element.fireEvent(event.eventType, event);
3939       }
3940
3941       return event;
3942     }
3943   };
3944 })());
3945
3946 Object.extend(Event, Event.Methods);
3947
3948 Element.addMethods({
3949   fire:          Event.fire,
3950   observe:       Event.observe,
3951   stopObserving: Event.stopObserving
3952 });
3953
3954 Object.extend(document, {
3955   fire:          Element.Methods.fire.methodize(),
3956   observe:       Element.Methods.observe.methodize(),
3957   stopObserving: Element.Methods.stopObserving.methodize()
3958 });
3959
3960 (function() {
3961   /* Support for the DOMContentLoaded event is based on work by Dan Webb,
3962      Matthias Miller, Dean Edwards and John Resig. */
3963
3964   var timer, fired = false;
3965
3966   function fireContentLoadedEvent() {
3967     if (fired) return;
3968     if (timer) window.clearInterval(timer);
3969     document.fire("dom:loaded");
3970     fired = true;
3971   }
3972
3973   if (document.addEventListener) {
3974     if (Prototype.Browser.WebKit) {
3975       timer = window.setInterval(function() {
3976         if (/loaded|complete/.test(document.readyState))
3977           fireContentLoadedEvent();
3978       }, 0);
3979
3980       Event.observe(window, "load", fireContentLoadedEvent);
3981
3982     } else {
3983       document.addEventListener("DOMContentLoaded",
3984         fireContentLoadedEvent, false);
3985     }
3986
3987   } else {
3988     document.write("<script id=__onDOMContentLoaded defer src=//:><\/script>");
3989     $("__onDOMContentLoaded").onreadystatechange = function() {
3990       if (this.readyState == "complete") {
3991         this.onreadystatechange = null;
3992         fireContentLoadedEvent();
3993       }
3994     };
3995   }
3996 })();
3997 /*------------------------------- DEPRECATED -------------------------------*/
3998
3999 Hash.toQueryString = Object.toQueryString;
4000
4001 var Toggle = { display: Element.toggle };
4002
4003 Element.Methods.childOf = Element.Methods.descendantOf;
4004
4005 var Insertion = {
4006   Before: function(element, content) {
4007     return Element.insert(element, {before:content});
4008   },
4009
4010   Top: function(element, content) {
4011     return Element.insert(element, {top:content});
4012   },
4013
4014   Bottom: function(element, content) {
4015     return Element.insert(element, {bottom:content});
4016   },
4017
4018   After: function(element, content) {
4019     return Element.insert(element, {after:content});
4020   }
4021 };
4022
4023 var $continue = new Error('"throw $continue" is deprecated, use "return" instead');
4024
4025 // This should be moved to script.aculo.us; notice the deprecated methods
4026 // further below, that map to the newer Element methods.
4027 var Position = {
4028   // set to true if needed, warning: firefox performance problems
4029   // NOT neeeded for page scrolling, only if draggable contained in
4030   // scrollable elements
4031   includeScrollOffsets: false,
4032
4033   // must be called before calling withinIncludingScrolloffset, every time the
4034   // page is scrolled
4035   prepare: function() {
4036     this.deltaX =  window.pageXOffset
4037                 || document.documentElement.scrollLeft
4038                 || document.body.scrollLeft
4039                 || 0;
4040     this.deltaY =  window.pageYOffset
4041                 || document.documentElement.scrollTop
4042                 || document.body.scrollTop
4043                 || 0;
4044   },
4045
4046   // caches x/y coordinate pair to use with overlap
4047   within: function(element, x, y) {
4048     if (this.includeScrollOffsets)
4049       return this.withinIncludingScrolloffsets(element, x, y);
4050     this.xcomp = x;
4051     this.ycomp = y;
4052     this.offset = Element.cumulativeOffset(element);
4053
4054     return (y >= this.offset[1] &&
4055             y <  this.offset[1] + element.offsetHeight &&
4056             x >= this.offset[0] &&
4057             x <  this.offset[0] + element.offsetWidth);
4058   },
4059
4060   withinIncludingScrolloffsets: function(element, x, y) {
4061     var offsetcache = Element.cumulativeScrollOffset(element);
4062
4063     this.xcomp = x + offsetcache[0] - this.deltaX;
4064     this.ycomp = y + offsetcache[1] - this.deltaY;
4065     this.offset = Element.cumulativeOffset(element);
4066
4067     return (this.ycomp >= this.offset[1] &&
4068             this.ycomp <  this.offset[1] + element.offsetHeight &&
4069             this.xcomp >= this.offset[0] &&
4070             this.xcomp <  this.offset[0] + element.offsetWidth);
4071   },
4072
4073   // within must be called directly before
4074   overlap: function(mode, element) {
4075     if (!mode) return 0;
4076     if (mode == 'vertical')
4077       return ((this.offset[1] + element.offsetHeight) - this.ycomp) /
4078         element.offsetHeight;
4079     if (mode == 'horizontal')
4080       return ((this.offset[0] + element.offsetWidth) - this.xcomp) /
4081         element.offsetWidth;
4082   },
4083
4084   // Deprecation layer -- use newer Element methods now (1.5.2).
4085
4086   cumulativeOffset: Element.Methods.cumulativeOffset,
4087
4088   positionedOffset: Element.Methods.positionedOffset,
4089
4090   absolutize: function(element) {
4091     Position.prepare();
4092     return Element.absolutize(element);
4093   },
4094
4095   relativize: function(element) {
4096     Position.prepare();
4097     return Element.relativize(element);
4098   },
4099
4100   realOffset: Element.Methods.cumulativeScrollOffset,
4101
4102   offsetParent: Element.Methods.getOffsetParent,
4103
4104   page: Element.Methods.viewportOffset,
4105
4106   clone: function(source, target, options) {
4107     options = options || { };
4108     return Element.clonePosition(target, source, options);
4109   }
4110 };
4111
4112 /*--------------------------------------------------------------------------*/
4113
4114 if (!document.getElementsByClassName) document.getElementsByClassName = function(instanceMethods){
4115   function iter(name) {
4116     return name.blank() ? null : "[contains(concat(' ', @class, ' '), ' " + name + " ')]";
4117   }
4118
4119   instanceMethods.getElementsByClassName = Prototype.BrowserFeatures.XPath ?
4120   function(element, className) {
4121     className = className.toString().strip();
4122     var cond = /\s/.test(className) ? $w(className).map(iter).join('') : iter(className);
4123     return cond ? document._getElementsByXPath('.//*' + cond, element) : [];
4124   } : function(element, className) {
4125     className = className.toString().strip();
4126     var elements = [], classNames = (/\s/.test(className) ? $w(className) : null);
4127     if (!classNames && !className) return elements;
4128
4129     var nodes = $(element).getElementsByTagName('*');
4130     className = ' ' + className + ' ';
4131
4132     for (var i = 0, child, cn; child = nodes[i]; i++) {
4133       if (child.className && (cn = ' ' + child.className + ' ') && (cn.include(className) ||
4134           (classNames && classNames.all(function(name) {
4135             return !name.toString().blank() && cn.include(' ' + name + ' ');
4136           }))))
4137         elements.push(Element.extend(child));
4138     }
4139     return elements;
4140   };
4141
4142   return function(className, parentElement) {
4143     return $(parentElement || document.body).getElementsByClassName(className);
4144   };
4145 }(Element.Methods);
4146
4147 /*--------------------------------------------------------------------------*/
4148
4149 Element.ClassNames = Class.create();
4150 Element.ClassNames.prototype = {
4151   initialize: function(element) {
4152     this.element = $(element);
4153   },
4154
4155   _each: function(iterator) {
4156     this.element.className.split(/\s+/).select(function(name) {
4157       return name.length > 0;
4158     })._each(iterator);
4159   },
4160
4161   set: function(className) {
4162     this.element.className = className;
4163   },
4164
4165   add: function(classNameToAdd) {
4166     if (this.include(classNameToAdd)) return;
4167     this.set($A(this).concat(classNameToAdd).join(' '));
4168   },
4169
4170   remove: function(classNameToRemove) {
4171     if (!this.include(classNameToRemove)) return;
4172     this.set($A(this).without(classNameToRemove).join(' '));
4173   },
4174
4175   toString: function() {
4176     return $A(this).join(' ');
4177   }
4178 };
4179
4180 Object.extend(Element.ClassNames.prototype, Enumerable);
4181
4182 /*--------------------------------------------------------------------------*/
4183
4184 Element.addMethods();