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