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