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