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