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