]> git.openstreetmap.org Git - rails.git/blob - public/lib/Prototype.js
Create a branch to play around with some ideas for integrating OpenStreetBugs into...
[rails.git] / public / lib / Prototype.js
1 /*  Prototype JavaScript framework, version 1.4.0
2  *  (c) 2005 Sam Stephenson <sam@conio.net>
3  *
4  *  Prototype is freely distributable under the terms of an MIT-style license.
5  *  For details, see the Prototype web site: http://prototype.conio.net/
6  *
7 /*--------------------------------------------------------------------------*/
8
9 var Prototype = {
10   Version: '1.4.0',
11   ScriptFragment: '(?:<script.*?>)((\n|\r|.)*?)(?:<\/script>)',
12
13   emptyFunction: function() {},
14   K: function(x) {return x}
15 }
16
17 var Class = {
18   create: function() {
19     return function() {
20       this.initialize.apply(this, arguments);
21     }
22   }
23 }
24
25 var Abstract = new Object();
26
27 Object.extend = function(destination, source) {
28   for (property in source) {
29     destination[property] = source[property];
30   }
31   return destination;
32 }
33
34 Object.inspect = function(object) {
35   try {
36     if (object == undefined) return 'undefined';
37     if (object == null) return 'null';
38     return object.inspect ? object.inspect() : object.toString();
39   } catch (e) {
40     if (e instanceof RangeError) return '...';
41     throw e;
42   }
43 }
44
45 Function.prototype.bind = function() {
46   var __method = this, args = $A(arguments), object = args.shift();
47   return function() {
48     return __method.apply(object, args.concat($A(arguments)));
49   }
50 }
51
52 Function.prototype.bindAsEventListener = function(object) {
53   var __method = this;
54   return function(event) {
55     return __method.call(object, event || window.event);
56   }
57 }
58
59 Object.extend(Number.prototype, {
60   toColorPart: function() {
61     var digits = this.toString(16);
62     if (this < 16) return '0' + digits;
63     return digits;
64   },
65
66   succ: function() {
67     return this + 1;
68   },
69
70   times: function(iterator) {
71     $R(0, this, true).each(iterator);
72     return this;
73   }
74 });
75
76 var Try = {
77   these: function() {
78     var returnValue;
79
80     for (var i = 0; i < arguments.length; i++) {
81       var lambda = arguments[i];
82       try {
83         returnValue = lambda();
84         break;
85       } catch (e) {}
86     }
87
88     return returnValue;
89   }
90 }
91
92 /*--------------------------------------------------------------------------*/
93
94 var PeriodicalExecuter = Class.create();
95 PeriodicalExecuter.prototype = {
96   initialize: function(callback, frequency) {
97     this.callback = callback;
98     this.frequency = frequency;
99     this.currentlyExecuting = false;
100
101     this.registerCallback();
102   },
103
104   registerCallback: function() {
105     setInterval(this.onTimerEvent.bind(this), this.frequency * 1000);
106   },
107
108   onTimerEvent: function() {
109     if (!this.currentlyExecuting) {
110       try {
111         this.currentlyExecuting = true;
112         this.callback();
113       } finally {
114         this.currentlyExecuting = false;
115       }
116     }
117   }
118 }
119
120 /*--------------------------------------------------------------------------*/
121
122 function $() {
123   var elements = new Array();
124
125   for (var i = 0; i < arguments.length; i++) {
126     var element = arguments[i];
127     if (typeof element == 'string')
128       element = document.getElementById(element);
129
130     if (arguments.length == 1)
131       return element;
132
133     elements.push(element);
134   }
135
136   return elements;
137 }
138 Object.extend(String.prototype, {
139   stripTags: function() {
140     return this.replace(/<\/?[^>]+>/gi, '');
141   },
142
143   stripScripts: function() {
144     return this.replace(new RegExp(Prototype.ScriptFragment, 'img'), '');
145   },
146
147   extractScripts: function() {
148     var matchAll = new RegExp(Prototype.ScriptFragment, 'img');
149     var matchOne = new RegExp(Prototype.ScriptFragment, 'im');
150     return (this.match(matchAll) || []).map(function(scriptTag) {
151       return (scriptTag.match(matchOne) || ['', ''])[1];
152     });
153   },
154
155   evalScripts: function() {
156     return this.extractScripts().map(eval);
157   },
158
159   escapeHTML: function() {
160     var div = document.createElement('div');
161     var text = document.createTextNode(this);
162     div.appendChild(text);
163     return div.innerHTML;
164   },
165
166   unescapeHTML: function() {
167     var div = document.createElement('div');
168     div.innerHTML = this.stripTags();
169     return div.childNodes[0] ? div.childNodes[0].nodeValue : '';
170   },
171
172   toQueryParams: function() {
173     var pairs = this.match(/^\??(.*)$/)[1].split('&');
174     return pairs.inject({}, function(params, pairString) {
175       var pair = pairString.split('=');
176       params[pair[0]] = pair[1];
177       return params;
178     });
179   },
180
181   toArray: function() {
182     return this.split('');
183   },
184
185   camelize: function() {
186     var oStringList = this.split('-');
187     if (oStringList.length == 1) return oStringList[0];
188
189     var camelizedString = this.indexOf('-') == 0
190       ? oStringList[0].charAt(0).toUpperCase() + oStringList[0].substring(1)
191       : oStringList[0];
192
193     for (var i = 1, len = oStringList.length; i < len; i++) {
194       var s = oStringList[i];
195       camelizedString += s.charAt(0).toUpperCase() + s.substring(1);
196     }
197
198     return camelizedString;
199   },
200
201   inspect: function() {
202     return "'" + this.replace('\\', '\\\\').replace("'", '\\\'') + "'";
203   }
204 });
205
206 String.prototype.parseQuery = String.prototype.toQueryParams;
207
208 var $break    = new Object();
209 var $continue = new Object();
210
211 var Enumerable = {
212   each: function(iterator) {
213     var index = 0;
214     try {
215       this._each(function(value) {
216         try {
217           iterator(value, index++);
218         } catch (e) {
219           if (e != $continue) throw e;
220         }
221       });
222     } catch (e) {
223       if (e != $break) throw e;
224     }
225   },
226
227   all: function(iterator) {
228     var result = true;
229     this.each(function(value, index) {
230       result = result && !!(iterator || Prototype.K)(value, index);
231       if (!result) throw $break;
232     });
233     return result;
234   },
235
236   any: function(iterator) {
237     var result = true;
238     this.each(function(value, index) {
239       if (result = !!(iterator || Prototype.K)(value, index))
240         throw $break;
241     });
242     return result;
243   },
244
245   collect: function(iterator) {
246     var results = [];
247     this.each(function(value, index) {
248       results.push(iterator(value, index));
249     });
250     return results;
251   },
252
253   detect: function (iterator) {
254     var result;
255     this.each(function(value, index) {
256       if (iterator(value, index)) {
257         result = value;
258         throw $break;
259       }
260     });
261     return result;
262   },
263
264   findAll: function(iterator) {
265     var results = [];
266     this.each(function(value, index) {
267       if (iterator(value, index))
268         results.push(value);
269     });
270     return results;
271   },
272
273   grep: function(pattern, iterator) {
274     var results = [];
275     this.each(function(value, index) {
276       var stringValue = value.toString();
277       if (stringValue.match(pattern))
278         results.push((iterator || Prototype.K)(value, index));
279     })
280     return results;
281   },
282
283   include: function(object) {
284     var found = false;
285     this.each(function(value) {
286       if (value == object) {
287         found = true;
288         throw $break;
289       }
290     });
291     return found;
292   },
293
294   inject: function(memo, iterator) {
295     this.each(function(value, index) {
296       memo = iterator(memo, value, index);
297     });
298     return memo;
299   },
300
301   invoke: function(method) {
302     var args = $A(arguments).slice(1);
303     return this.collect(function(value) {
304       return value[method].apply(value, args);
305     });
306   },
307
308   max: function(iterator) {
309     var result;
310     this.each(function(value, index) {
311       value = (iterator || Prototype.K)(value, index);
312       if (value >= (result || value))
313         result = value;
314     });
315     return result;
316   },
317
318   min: function(iterator) {
319     var result;
320     this.each(function(value, index) {
321       value = (iterator || Prototype.K)(value, index);
322       if (value <= (result || value))
323         result = value;
324     });
325     return result;
326   },
327
328   partition: function(iterator) {
329     var trues = [], falses = [];
330     this.each(function(value, index) {
331       ((iterator || Prototype.K)(value, index) ?
332         trues : falses).push(value);
333     });
334     return [trues, falses];
335   },
336
337   pluck: function(property) {
338     var results = [];
339     this.each(function(value, index) {
340       results.push(value[property]);
341     });
342     return results;
343   },
344
345   reject: function(iterator) {
346     var results = [];
347     this.each(function(value, index) {
348       if (!iterator(value, index))
349         results.push(value);
350     });
351     return results;
352   },
353
354   sortBy: function(iterator) {
355     return this.collect(function(value, index) {
356       return {value: value, criteria: iterator(value, index)};
357     }).sort(function(left, right) {
358       var a = left.criteria, b = right.criteria;
359       return a < b ? -1 : a > b ? 1 : 0;
360     }).pluck('value');
361   },
362
363   toArray: function() {
364     return this.collect(Prototype.K);
365   },
366
367   zip: function() {
368     var iterator = Prototype.K, args = $A(arguments);
369     if (typeof args.last() == 'function')
370       iterator = args.pop();
371
372     var collections = [this].concat(args).map($A);
373     return this.map(function(value, index) {
374       iterator(value = collections.pluck(index));
375       return value;
376     });
377   },
378
379   inspect: function() {
380     return '#<Enumerable:' + this.toArray().inspect() + '>';
381   }
382 }
383
384 Object.extend(Enumerable, {
385   map:     Enumerable.collect,
386   find:    Enumerable.detect,
387   select:  Enumerable.findAll,
388   member:  Enumerable.include,
389   entries: Enumerable.toArray
390 });
391 var $A = Array.from = function(iterable) {
392   if (!iterable) return [];
393   if (iterable.toArray) {
394     return iterable.toArray();
395   } else {
396     var results = [];
397     for (var i = 0; i < iterable.length; i++)
398       results.push(iterable[i]);
399     return results;
400   }
401 }
402
403 Object.extend(Array.prototype, Enumerable);
404
405 Array.prototype._reverse = Array.prototype.reverse;
406
407 Object.extend(Array.prototype, {
408   _each: function(iterator) {
409     for (var i = 0; i < this.length; i++)
410       iterator(this[i]);
411   },
412
413   clear: function() {
414     this.length = 0;
415     return this;
416   },
417
418   first: function() {
419     return this[0];
420   },
421
422   last: function() {
423     return this[this.length - 1];
424   },
425
426   compact: function() {
427     return this.select(function(value) {
428       return value != undefined || value != null;
429     });
430   },
431
432   flatten: function() {
433     return this.inject([], function(array, value) {
434       return array.concat(value.constructor == Array ?
435         value.flatten() : [value]);
436     });
437   },
438
439   without: function() {
440     var values = $A(arguments);
441     return this.select(function(value) {
442       return !values.include(value);
443     });
444   },
445
446   indexOf: function(object) {
447     for (var i = 0; i < this.length; i++)
448       if (this[i] == object) return i;
449     return -1;
450   },
451
452   reverse: function(inline) {
453     return (inline !== false ? this : this.toArray())._reverse();
454   },
455
456   shift: function() {
457     var result = this[0];
458     for (var i = 0; i < this.length - 1; i++)
459       this[i] = this[i + 1];
460     this.length--;
461     return result;
462   },
463
464   inspect: function() {
465     return '[' + this.map(Object.inspect).join(', ') + ']';
466   }
467 });
468 var Hash = {
469   _each: function(iterator) {
470     for (key in this) {
471       var value = this[key];
472       if (typeof value == 'function') continue;
473
474       var pair = [key, value];
475       pair.key = key;
476       pair.value = value;
477       iterator(pair);
478     }
479   },
480
481   keys: function() {
482     return this.pluck('key');
483   },
484
485   values: function() {
486     return this.pluck('value');
487   },
488
489   merge: function(hash) {
490     return $H(hash).inject($H(this), function(mergedHash, pair) {
491       mergedHash[pair.key] = pair.value;
492       return mergedHash;
493     });
494   },
495
496   toQueryString: function() {
497     return this.map(function(pair) {
498       return pair.map(encodeURIComponent).join('=');
499     }).join('&');
500   },
501
502   inspect: function() {
503     return '#<Hash:{' + this.map(function(pair) {
504       return pair.map(Object.inspect).join(': ');
505     }).join(', ') + '}>';
506   }
507 }
508
509 function $H(object) {
510   var hash = Object.extend({}, object || {});
511   Object.extend(hash, Enumerable);
512   Object.extend(hash, Hash);
513   return hash;
514 }
515 ObjectRange = Class.create();
516 Object.extend(ObjectRange.prototype, Enumerable);
517 Object.extend(ObjectRange.prototype, {
518   initialize: function(start, end, exclusive) {
519     this.start = start;
520     this.end = end;
521     this.exclusive = exclusive;
522   },
523
524   _each: function(iterator) {
525     var value = this.start;
526     do {
527       iterator(value);
528       value = value.succ();
529     } while (this.include(value));
530   },
531
532   include: function(value) {
533     if (value < this.start)
534       return false;
535     if (this.exclusive)
536       return value < this.end;
537     return value <= this.end;
538   }
539 });
540
541 var $R = function(start, end, exclusive) {
542   return new ObjectRange(start, end, exclusive);
543 }
544
545 var Ajax = {
546   getTransport: function() {
547     return Try.these(
548       function() {return new ActiveXObject('Msxml2.XMLHTTP')},
549       function() {return new ActiveXObject('Microsoft.XMLHTTP')},
550       function() {return new XMLHttpRequest()}
551     ) || false;
552   },
553
554   activeRequestCount: 0
555 }
556
557 Ajax.Responders = {
558   responders: [],
559
560   _each: function(iterator) {
561     this.responders._each(iterator);
562   },
563
564   register: function(responderToAdd) {
565     if (!this.include(responderToAdd))
566       this.responders.push(responderToAdd);
567   },
568
569   unregister: function(responderToRemove) {
570     this.responders = this.responders.without(responderToRemove);
571   },
572
573   dispatch: function(callback, request, transport, json) {
574     this.each(function(responder) {
575       if (responder[callback] && typeof responder[callback] == 'function') {
576         try {
577           responder[callback].apply(responder, [request, transport, json]);
578         } catch (e) {}
579       }
580     });
581   }
582 };
583
584 Object.extend(Ajax.Responders, Enumerable);
585
586 Ajax.Responders.register({
587   onCreate: function() {
588     Ajax.activeRequestCount++;
589   },
590
591   onComplete: function() {
592     Ajax.activeRequestCount--;
593   }
594 });
595
596 Ajax.Base = function() {};
597 Ajax.Base.prototype = {
598   setOptions: function(options) {
599     this.options = {
600       method:       'post',
601       asynchronous: true,
602       parameters:   ''
603     }
604     Object.extend(this.options, options || {});
605   },
606
607   responseIsSuccess: function() {
608     return this.transport.status == undefined
609         || this.transport.status == 0
610         || (this.transport.status >= 200 && this.transport.status < 300);
611   },
612
613   responseIsFailure: function() {
614     return !this.responseIsSuccess();
615   }
616 }
617
618 Ajax.Request = Class.create();
619 Ajax.Request.Events =
620   ['Uninitialized', 'Loading', 'Loaded', 'Interactive', 'Complete'];
621
622 Ajax.Request.prototype = Object.extend(new Ajax.Base(), {
623   initialize: function(url, options) {
624     this.transport = Ajax.getTransport();
625     this.setOptions(options);
626     this.request(url);
627   },
628
629   request: function(url) {
630     var parameters = this.options.parameters || '';
631     if (parameters.length > 0) parameters += '&_=';
632
633     try {
634       this.url = url;
635       if (this.options.method == 'get' && parameters.length > 0)
636         this.url += (this.url.match(/\?/) ? '&' : '?') + parameters;
637
638       Ajax.Responders.dispatch('onCreate', this, this.transport);
639
640       this.transport.open(this.options.method, this.url,
641         this.options.asynchronous);
642
643       if (this.options.asynchronous) {
644         this.transport.onreadystatechange = this.onStateChange.bind(this);
645         setTimeout((function() {this.respondToReadyState(1)}).bind(this), 10);
646       }
647
648       this.setRequestHeaders();
649
650       var body = this.options.postBody ? this.options.postBody : parameters;
651       this.transport.send(this.options.method == 'post' ? body : null);
652
653     } catch (e) {
654       this.dispatchException(e);
655     }
656   },
657
658   setRequestHeaders: function() {
659     var requestHeaders =
660       ['X-Requested-With', 'XMLHttpRequest',
661        'X-Prototype-Version', Prototype.Version];
662
663     if (this.options.method == 'post') {
664       requestHeaders.push('Content-type',
665         'application/x-www-form-urlencoded');
666
667       /* Force "Connection: close" for Mozilla browsers to work around
668        * a bug where XMLHttpReqeuest sends an incorrect Content-length
669        * header. See Mozilla Bugzilla #246651.
670        */
671       if (this.transport.overrideMimeType)
672         requestHeaders.push('Connection', 'close');
673     }
674
675     if (this.options.requestHeaders)
676       requestHeaders.push.apply(requestHeaders, this.options.requestHeaders);
677
678     for (var i = 0; i < requestHeaders.length; i += 2)
679       this.transport.setRequestHeader(requestHeaders[i], requestHeaders[i+1]);
680   },
681
682   onStateChange: function() {
683     var readyState = this.transport.readyState;
684     if (readyState != 1)
685       this.respondToReadyState(this.transport.readyState);
686   },
687
688   header: function(name) {
689     try {
690       return this.transport.getResponseHeader(name);
691     } catch (e) {}
692   },
693
694   evalJSON: function() {
695     try {
696       return eval(this.header('X-JSON'));
697     } catch (e) {}
698   },
699
700   evalResponse: function() {
701     try {
702       return eval(this.transport.responseText);
703     } catch (e) {
704       this.dispatchException(e);
705     }
706   },
707
708   respondToReadyState: function(readyState) {
709     var event = Ajax.Request.Events[readyState];
710     var transport = this.transport, json = this.evalJSON();
711
712     if (event == 'Complete') {
713       try {
714         (this.options['on' + this.transport.status]
715          || this.options['on' + (this.responseIsSuccess() ? 'Success' : 'Failure')]
716          || Prototype.emptyFunction)(transport, json);
717       } catch (e) {
718         this.dispatchException(e);
719       }
720
721       if ((this.header('Content-type') || '').match(/^text\/javascript/i))
722         this.evalResponse();
723     }
724
725     try {
726       (this.options['on' + event] || Prototype.emptyFunction)(transport, json);
727       Ajax.Responders.dispatch('on' + event, this, transport, json);
728     } catch (e) {
729       this.dispatchException(e);
730     }
731
732     /* Avoid memory leak in MSIE: clean up the oncomplete event handler */
733     if (event == 'Complete')
734       this.transport.onreadystatechange = Prototype.emptyFunction;
735   },
736
737   dispatchException: function(exception) {
738     (this.options.onException || Prototype.emptyFunction)(this, exception);
739     Ajax.Responders.dispatch('onException', this, exception);
740   }
741 });
742
743 Ajax.Updater = Class.create();
744
745 Object.extend(Object.extend(Ajax.Updater.prototype, Ajax.Request.prototype), {
746   initialize: function(container, url, options) {
747     this.containers = {
748       success: container.success ? $(container.success) : $(container),
749       failure: container.failure ? $(container.failure) :
750         (container.success ? null : $(container))
751     }
752
753     this.transport = Ajax.getTransport();
754     this.setOptions(options);
755
756     var onComplete = this.options.onComplete || Prototype.emptyFunction;
757     this.options.onComplete = (function(transport, object) {
758       this.updateContent();
759       onComplete(transport, object);
760     }).bind(this);
761
762     this.request(url);
763   },
764
765   updateContent: function() {
766     var receiver = this.responseIsSuccess() ?
767       this.containers.success : this.containers.failure;
768     var response = this.transport.responseText;
769
770     if (!this.options.evalScripts)
771       response = response.stripScripts();
772
773     if (receiver) {
774       if (this.options.insertion) {
775         new this.options.insertion(receiver, response);
776       } else {
777         Element.update(receiver, response);
778       }
779     }
780
781     if (this.responseIsSuccess()) {
782       if (this.onComplete)
783         setTimeout(this.onComplete.bind(this), 10);
784     }
785   }
786 });
787
788 Ajax.PeriodicalUpdater = Class.create();
789 Ajax.PeriodicalUpdater.prototype = Object.extend(new Ajax.Base(), {
790   initialize: function(container, url, options) {
791     this.setOptions(options);
792     this.onComplete = this.options.onComplete;
793
794     this.frequency = (this.options.frequency || 2);
795     this.decay = (this.options.decay || 1);
796
797     this.updater = {};
798     this.container = container;
799     this.url = url;
800
801     this.start();
802   },
803
804   start: function() {
805     this.options.onComplete = this.updateComplete.bind(this);
806     this.onTimerEvent();
807   },
808
809   stop: function() {
810     this.updater.onComplete = undefined;
811     clearTimeout(this.timer);
812     (this.onComplete || Prototype.emptyFunction).apply(this, arguments);
813   },
814
815   updateComplete: function(request) {
816     if (this.options.decay) {
817       this.decay = (request.responseText == this.lastText ?
818         this.decay * this.options.decay : 1);
819
820       this.lastText = request.responseText;
821     }
822     this.timer = setTimeout(this.onTimerEvent.bind(this),
823       this.decay * this.frequency * 1000);
824   },
825
826   onTimerEvent: function() {
827     this.updater = new Ajax.Updater(this.container, this.url, this.options);
828   }
829 });
830 document.getElementsByClassName = function(className, parentElement) {
831   var children = ($(parentElement) || document.body).getElementsByTagName('*');
832   return $A(children).inject([], function(elements, child) {
833     if (child.className.match(new RegExp("(^|\\s)" + className + "(\\s|$)")))
834       elements.push(child);
835     return elements;
836   });
837 }
838
839 /*--------------------------------------------------------------------------*/
840
841 if (!window.Element) {
842   var Element = new Object();
843 }
844
845 Object.extend(Element, {
846   visible: function(element) {
847     return $(element).style.display != 'none';
848   },
849
850   toggle: function() {
851     for (var i = 0; i < arguments.length; i++) {
852       var element = $(arguments[i]);
853       Element[Element.visible(element) ? 'hide' : 'show'](element);
854     }
855   },
856
857   hide: function() {
858     for (var i = 0; i < arguments.length; i++) {
859       var element = $(arguments[i]);
860       element.style.display = 'none';
861     }
862   },
863
864   show: function() {
865     for (var i = 0; i < arguments.length; i++) {
866       var element = $(arguments[i]);
867       element.style.display = '';
868     }
869   },
870
871   remove: function(element) {
872     element = $(element);
873     element.parentNode.removeChild(element);
874   },
875
876   update: function(element, html) {
877     $(element).innerHTML = html.stripScripts();
878     setTimeout(function() {html.evalScripts()}, 10);
879   },
880
881   getHeight: function(element) {
882     element = $(element);
883     return element.offsetHeight;
884   },
885
886   classNames: function(element) {
887     return new Element.ClassNames(element);
888   },
889
890   hasClassName: function(element, className) {
891     if (!(element = $(element))) return;
892     return Element.classNames(element).include(className);
893   },
894
895   addClassName: function(element, className) {
896     if (!(element = $(element))) return;
897     return Element.classNames(element).add(className);
898   },
899
900   removeClassName: function(element, className) {
901     if (!(element = $(element))) return;
902     return Element.classNames(element).remove(className);
903   },
904
905   // removes whitespace-only text node children
906   cleanWhitespace: function(element) {
907     element = $(element);
908     for (var i = 0; i < element.childNodes.length; i++) {
909       var node = element.childNodes[i];
910       if (node.nodeType == 3 && !/\S/.test(node.nodeValue))
911         Element.remove(node);
912     }
913   },
914
915   empty: function(element) {
916     return $(element).innerHTML.match(/^\s*$/);
917   },
918
919   scrollTo: function(element) {
920     element = $(element);
921     var x = element.x ? element.x : element.offsetLeft,
922         y = element.y ? element.y : element.offsetTop;
923     window.scrollTo(x, y);
924   },
925
926   getStyle: function(element, style) {
927     element = $(element);
928     var value = element.style[style.camelize()];
929     if (!value) {
930       if (document.defaultView && document.defaultView.getComputedStyle) {
931         var css = document.defaultView.getComputedStyle(element, null);
932         value = css ? css.getPropertyValue(style) : null;
933       } else if (element.currentStyle) {
934         value = element.currentStyle[style.camelize()];
935       }
936     }
937
938     if (window.opera && ['left', 'top', 'right', 'bottom'].include(style))
939       if (Element.getStyle(element, 'position') == 'static') value = 'auto';
940
941     return value == 'auto' ? null : value;
942   },
943
944   setStyle: function(element, style) {
945     element = $(element);
946     for (name in style)
947       element.style[name.camelize()] = style[name];
948   },
949
950   getDimensions: function(element) {
951     element = $(element);
952     if (Element.getStyle(element, 'display') != 'none')
953       return {width: element.offsetWidth, height: element.offsetHeight};
954
955     // All *Width and *Height properties give 0 on elements with display none,
956     // so enable the element temporarily
957     var els = element.style;
958     var originalVisibility = els.visibility;
959     var originalPosition = els.position;
960     els.visibility = 'hidden';
961     els.position = 'absolute';
962     els.display = '';
963     var originalWidth = element.clientWidth;
964     var originalHeight = element.clientHeight;
965     els.display = 'none';
966     els.position = originalPosition;
967     els.visibility = originalVisibility;
968     return {width: originalWidth, height: originalHeight};
969   },
970
971   makePositioned: function(element) {
972     element = $(element);
973     var pos = Element.getStyle(element, 'position');
974     if (pos == 'static' || !pos) {
975       element._madePositioned = true;
976       element.style.position = 'relative';
977       // Opera returns the offset relative to the positioning context, when an
978       // element is position relative but top and left have not been defined
979       if (window.opera) {
980         element.style.top = 0;
981         element.style.left = 0;
982       }
983     }
984   },
985
986   undoPositioned: function(element) {
987     element = $(element);
988     if (element._madePositioned) {
989       element._madePositioned = undefined;
990       element.style.position =
991         element.style.top =
992         element.style.left =
993         element.style.bottom =
994         element.style.right = '';
995     }
996   },
997
998   makeClipping: function(element) {
999     element = $(element);
1000     if (element._overflow) return;
1001     element._overflow = element.style.overflow;
1002     if ((Element.getStyle(element, 'overflow') || 'visible') != 'hidden')
1003       element.style.overflow = 'hidden';
1004   },
1005
1006   undoClipping: function(element) {
1007     element = $(element);
1008     if (element._overflow) return;
1009     element.style.overflow = element._overflow;
1010     element._overflow = undefined;
1011   }
1012 });
1013
1014 var Toggle = new Object();
1015 Toggle.display = Element.toggle;
1016
1017 /*--------------------------------------------------------------------------*/
1018
1019 Abstract.Insertion = function(adjacency) {
1020   this.adjacency = adjacency;
1021 }
1022
1023 Abstract.Insertion.prototype = {
1024   initialize: function(element, content) {
1025     this.element = $(element);
1026     this.content = content.stripScripts();
1027
1028     if (this.adjacency && this.element.insertAdjacentHTML) {
1029       try {
1030         this.element.insertAdjacentHTML(this.adjacency, this.content);
1031       } catch (e) {
1032         if (this.element.tagName.toLowerCase() == 'tbody') {
1033           this.insertContent(this.contentFromAnonymousTable());
1034         } else {
1035           throw e;
1036         }
1037       }
1038     } else {
1039       this.range = this.element.ownerDocument.createRange();
1040       if (this.initializeRange) this.initializeRange();
1041       this.insertContent([this.range.createContextualFragment(this.content)]);
1042     }
1043
1044     setTimeout(function() {content.evalScripts()}, 10);
1045   },
1046
1047   contentFromAnonymousTable: function() {
1048     var div = document.createElement('div');
1049     div.innerHTML = '<table><tbody>' + this.content + '</tbody></table>';
1050     return $A(div.childNodes[0].childNodes[0].childNodes);
1051   }
1052 }
1053
1054 var Insertion = new Object();
1055
1056 Insertion.Before = Class.create();
1057 Insertion.Before.prototype = Object.extend(new Abstract.Insertion('beforeBegin'), {
1058   initializeRange: function() {
1059     this.range.setStartBefore(this.element);
1060   },
1061
1062   insertContent: function(fragments) {
1063     fragments.each((function(fragment) {
1064       this.element.parentNode.insertBefore(fragment, this.element);
1065     }).bind(this));
1066   }
1067 });
1068
1069 Insertion.Top = Class.create();
1070 Insertion.Top.prototype = Object.extend(new Abstract.Insertion('afterBegin'), {
1071   initializeRange: function() {
1072     this.range.selectNodeContents(this.element);
1073     this.range.collapse(true);
1074   },
1075
1076   insertContent: function(fragments) {
1077     fragments.reverse(false).each((function(fragment) {
1078       this.element.insertBefore(fragment, this.element.firstChild);
1079     }).bind(this));
1080   }
1081 });
1082
1083 Insertion.Bottom = Class.create();
1084 Insertion.Bottom.prototype = Object.extend(new Abstract.Insertion('beforeEnd'), {
1085   initializeRange: function() {
1086     this.range.selectNodeContents(this.element);
1087     this.range.collapse(this.element);
1088   },
1089
1090   insertContent: function(fragments) {
1091     fragments.each((function(fragment) {
1092       this.element.appendChild(fragment);
1093     }).bind(this));
1094   }
1095 });
1096
1097 Insertion.After = Class.create();
1098 Insertion.After.prototype = Object.extend(new Abstract.Insertion('afterEnd'), {
1099   initializeRange: function() {
1100     this.range.setStartAfter(this.element);
1101   },
1102
1103   insertContent: function(fragments) {
1104     fragments.each((function(fragment) {
1105       this.element.parentNode.insertBefore(fragment,
1106         this.element.nextSibling);
1107     }).bind(this));
1108   }
1109 });
1110
1111 /*--------------------------------------------------------------------------*/
1112
1113 Element.ClassNames = Class.create();
1114 Element.ClassNames.prototype = {
1115   initialize: function(element) {
1116     this.element = $(element);
1117   },
1118
1119   _each: function(iterator) {
1120     this.element.className.split(/\s+/).select(function(name) {
1121       return name.length > 0;
1122     })._each(iterator);
1123   },
1124
1125   set: function(className) {
1126     this.element.className = className;
1127   },
1128
1129   add: function(classNameToAdd) {
1130     if (this.include(classNameToAdd)) return;
1131     this.set(this.toArray().concat(classNameToAdd).join(' '));
1132   },
1133
1134   remove: function(classNameToRemove) {
1135     if (!this.include(classNameToRemove)) return;
1136     this.set(this.select(function(className) {
1137       return className != classNameToRemove;
1138     }).join(' '));
1139   },
1140
1141   toString: function() {
1142     return this.toArray().join(' ');
1143   }
1144 }
1145
1146 Object.extend(Element.ClassNames.prototype, Enumerable);
1147 var Field = {
1148   clear: function() {
1149     for (var i = 0; i < arguments.length; i++)
1150       $(arguments[i]).value = '';
1151   },
1152
1153   focus: function(element) {
1154     $(element).focus();
1155   },
1156
1157   present: function() {
1158     for (var i = 0; i < arguments.length; i++)
1159       if ($(arguments[i]).value == '') return false;
1160     return true;
1161   },
1162
1163   select: function(element) {
1164     $(element).select();
1165   },
1166
1167   activate: function(element) {
1168     element = $(element);
1169     element.focus();
1170     if (element.select)
1171       element.select();
1172   }
1173 }
1174
1175 /*--------------------------------------------------------------------------*/
1176
1177 var Form = {
1178   serialize: function(form) {
1179     var elements = Form.getElements($(form));
1180     var queryComponents = new Array();
1181
1182     for (var i = 0; i < elements.length; i++) {
1183       var queryComponent = Form.Element.serialize(elements[i]);
1184       if (queryComponent)
1185         queryComponents.push(queryComponent);
1186     }
1187
1188     return queryComponents.join('&');
1189   },
1190
1191   getElements: function(form) {
1192     form = $(form);
1193     var elements = new Array();
1194
1195     for (tagName in Form.Element.Serializers) {
1196       var tagElements = form.getElementsByTagName(tagName);
1197       for (var j = 0; j < tagElements.length; j++)
1198         elements.push(tagElements[j]);
1199     }
1200     return elements;
1201   },
1202
1203   getInputs: function(form, typeName, name) {
1204     form = $(form);
1205     var inputs = form.getElementsByTagName('input');
1206
1207     if (!typeName && !name)
1208       return inputs;
1209
1210     var matchingInputs = new Array();
1211     for (var i = 0; i < inputs.length; i++) {
1212       var input = inputs[i];
1213       if ((typeName && input.type != typeName) ||
1214           (name && input.name != name))
1215         continue;
1216       matchingInputs.push(input);
1217     }
1218
1219     return matchingInputs;
1220   },
1221
1222   disable: function(form) {
1223     var elements = Form.getElements(form);
1224     for (var i = 0; i < elements.length; i++) {
1225       var element = elements[i];
1226       element.blur();
1227       element.disabled = 'true';
1228     }
1229   },
1230
1231   enable: function(form) {
1232     var elements = Form.getElements(form);
1233     for (var i = 0; i < elements.length; i++) {
1234       var element = elements[i];
1235       element.disabled = '';
1236     }
1237   },
1238
1239   findFirstElement: function(form) {
1240     return Form.getElements(form).find(function(element) {
1241       return element.type != 'hidden' && !element.disabled &&
1242         ['input', 'select', 'textarea'].include(element.tagName.toLowerCase());
1243     });
1244   },
1245
1246   focusFirstElement: function(form) {
1247     Field.activate(Form.findFirstElement(form));
1248   },
1249
1250   reset: function(form) {
1251     $(form).reset();
1252   }
1253 }
1254
1255 Form.Element = {
1256   serialize: function(element) {
1257     element = $(element);
1258     var method = element.tagName.toLowerCase();
1259     var parameter = Form.Element.Serializers[method](element);
1260
1261     if (parameter) {
1262       var key = encodeURIComponent(parameter[0]);
1263       if (key.length == 0) return;
1264
1265       if (parameter[1].constructor != Array)
1266         parameter[1] = [parameter[1]];
1267
1268       return parameter[1].map(function(value) {
1269         return key + '=' + encodeURIComponent(value);
1270       }).join('&');
1271     }
1272   },
1273
1274   getValue: function(element) {
1275     element = $(element);
1276     var method = element.tagName.toLowerCase();
1277     var parameter = Form.Element.Serializers[method](element);
1278
1279     if (parameter)
1280       return parameter[1];
1281   }
1282 }
1283
1284 Form.Element.Serializers = {
1285   input: function(element) {
1286     switch (element.type.toLowerCase()) {
1287       case 'submit':
1288       case 'hidden':
1289       case 'password':
1290       case 'text':
1291         return Form.Element.Serializers.textarea(element);
1292       case 'checkbox':
1293       case 'radio':
1294         return Form.Element.Serializers.inputSelector(element);
1295     }
1296     return false;
1297   },
1298
1299   inputSelector: function(element) {
1300     if (element.checked)
1301       return [element.name, element.value];
1302   },
1303
1304   textarea: function(element) {
1305     return [element.name, element.value];
1306   },
1307
1308   select: function(element) {
1309     return Form.Element.Serializers[element.type == 'select-one' ?
1310       'selectOne' : 'selectMany'](element);
1311   },
1312
1313   selectOne: function(element) {
1314     var value = '', opt, index = element.selectedIndex;
1315     if (index >= 0) {
1316       opt = element.options[index];
1317       value = opt.value;
1318       if (!value && !('value' in opt))
1319         value = opt.text;
1320     }
1321     return [element.name, value];
1322   },
1323
1324   selectMany: function(element) {
1325     var value = new Array();
1326     for (var i = 0; i < element.length; i++) {
1327       var opt = element.options[i];
1328       if (opt.selected) {
1329         var optValue = opt.value;
1330         if (!optValue && !('value' in opt))
1331           optValue = opt.text;
1332         value.push(optValue);
1333       }
1334     }
1335     return [element.name, value];
1336   }
1337 }
1338
1339 /*--------------------------------------------------------------------------*/
1340
1341 var $F = Form.Element.getValue;
1342
1343 /*--------------------------------------------------------------------------*/
1344
1345 Abstract.TimedObserver = function() {}
1346 Abstract.TimedObserver.prototype = {
1347   initialize: function(element, frequency, callback) {
1348     this.frequency = frequency;
1349     this.element   = $(element);
1350     this.callback  = callback;
1351
1352     this.lastValue = this.getValue();
1353     this.registerCallback();
1354   },
1355
1356   registerCallback: function() {
1357     setInterval(this.onTimerEvent.bind(this), this.frequency * 1000);
1358   },
1359
1360   onTimerEvent: function() {
1361     var value = this.getValue();
1362     if (this.lastValue != value) {
1363       this.callback(this.element, value);
1364       this.lastValue = value;
1365     }
1366   }
1367 }
1368
1369 Form.Element.Observer = Class.create();
1370 Form.Element.Observer.prototype = Object.extend(new Abstract.TimedObserver(), {
1371   getValue: function() {
1372     return Form.Element.getValue(this.element);
1373   }
1374 });
1375
1376 Form.Observer = Class.create();
1377 Form.Observer.prototype = Object.extend(new Abstract.TimedObserver(), {
1378   getValue: function() {
1379     return Form.serialize(this.element);
1380   }
1381 });
1382
1383 /*--------------------------------------------------------------------------*/
1384
1385 Abstract.EventObserver = function() {}
1386 Abstract.EventObserver.prototype = {
1387   initialize: function(element, callback) {
1388     this.element  = $(element);
1389     this.callback = callback;
1390
1391     this.lastValue = this.getValue();
1392     if (this.element.tagName.toLowerCase() == 'form')
1393       this.registerFormCallbacks();
1394     else
1395       this.registerCallback(this.element);
1396   },
1397
1398   onElementEvent: function() {
1399     var value = this.getValue();
1400     if (this.lastValue != value) {
1401       this.callback(this.element, value);
1402       this.lastValue = value;
1403     }
1404   },
1405
1406   registerFormCallbacks: function() {
1407     var elements = Form.getElements(this.element);
1408     for (var i = 0; i < elements.length; i++)
1409       this.registerCallback(elements[i]);
1410   },
1411
1412   registerCallback: function(element) {
1413     if (element.type) {
1414       switch (element.type.toLowerCase()) {
1415         case 'checkbox':
1416         case 'radio':
1417           Event.observe(element, 'click', this.onElementEvent.bind(this));
1418           break;
1419         case 'password':
1420         case 'text':
1421         case 'textarea':
1422         case 'select-one':
1423         case 'select-multiple':
1424           Event.observe(element, 'change', this.onElementEvent.bind(this));
1425           break;
1426       }
1427     }
1428   }
1429 }
1430
1431 Form.Element.EventObserver = Class.create();
1432 Form.Element.EventObserver.prototype = Object.extend(new Abstract.EventObserver(), {
1433   getValue: function() {
1434     return Form.Element.getValue(this.element);
1435   }
1436 });
1437
1438 Form.EventObserver = Class.create();
1439 Form.EventObserver.prototype = Object.extend(new Abstract.EventObserver(), {
1440   getValue: function() {
1441     return Form.serialize(this.element);
1442   }
1443 });
1444 if (!window.Event) {
1445   var Event = new Object();
1446 }
1447
1448 Object.extend(Event, {
1449   KEY_BACKSPACE: 8,
1450   KEY_TAB:       9,
1451   KEY_RETURN:   13,
1452   KEY_ESC:      27,
1453   KEY_LEFT:     37,
1454   KEY_UP:       38,
1455   KEY_RIGHT:    39,
1456   KEY_DOWN:     40,
1457   KEY_DELETE:   46,
1458
1459   element: function(event) {
1460     return event.target || event.srcElement;
1461   },
1462
1463   isLeftClick: function(event) {
1464     return (((event.which) && (event.which == 1)) ||
1465             ((event.button) && (event.button == 1)));
1466   },
1467
1468   pointerX: function(event) {
1469     return event.pageX || (event.clientX +
1470       (document.documentElement.scrollLeft || document.body.scrollLeft));
1471   },
1472
1473   pointerY: function(event) {
1474     return event.pageY || (event.clientY +
1475       (document.documentElement.scrollTop || document.body.scrollTop));
1476   },
1477
1478   stop: function(event) {
1479     if (event.preventDefault) {
1480       event.preventDefault();
1481       event.stopPropagation();
1482     } else {
1483       event.returnValue = false;
1484       event.cancelBubble = true;
1485     }
1486   },
1487
1488   // find the first node with the given tagName, starting from the
1489   // node the event was triggered on; traverses the DOM upwards
1490   findElement: function(event, tagName) {
1491     var element = Event.element(event);
1492     while (element.parentNode && (!element.tagName ||
1493         (element.tagName.toUpperCase() != tagName.toUpperCase())))
1494       element = element.parentNode;
1495     return element;
1496   },
1497
1498   observers: false,
1499
1500   _observeAndCache: function(element, name, observer, useCapture) {
1501     if (!this.observers) this.observers = [];
1502     if (element.addEventListener) {
1503       this.observers.push([element, name, observer, useCapture]);
1504       element.addEventListener(name, observer, useCapture);
1505     } else if (element.attachEvent) {
1506       this.observers.push([element, name, observer, useCapture]);
1507       element.attachEvent('on' + name, observer);
1508     }
1509   },
1510
1511   unloadCache: function() {
1512     if (!Event.observers) return;
1513     for (var i = 0; i < Event.observers.length; i++) {
1514       Event.stopObserving.apply(this, Event.observers[i]);
1515       Event.observers[i][0] = null;
1516     }
1517     Event.observers = false;
1518   },
1519
1520   observe: function(elementParam, name, observer, useCapture) {
1521     var element = $(elementParam);
1522     useCapture = useCapture || false;
1523
1524     if (name == 'keypress' &&
1525         (navigator.appVersion.match(/Konqueror|Safari|KHTML/)
1526         || element.attachEvent))
1527       name = 'keydown';
1528
1529     this._observeAndCache(element, name, observer, useCapture);
1530   },
1531
1532   stopObserving: function(elementParam, name, observer, useCapture) {
1533     var element = $(elementParam);
1534     useCapture = useCapture || false;
1535
1536     if (name == 'keypress' &&
1537         (navigator.appVersion.match(/Konqueror|Safari|KHTML/)
1538         || element.detachEvent))
1539       name = 'keydown';
1540
1541     if (element.removeEventListener) {
1542       element.removeEventListener(name, observer, useCapture);
1543     } else if (element.detachEvent) {
1544       element.detachEvent('on' + name, observer);
1545     }
1546   }
1547 });
1548
1549 /* prevent memory leaks in IE */
1550 Event.observe(window, 'unload', Event.unloadCache, false);
1551 var Position = {
1552   // set to true if needed, warning: firefox performance problems
1553   // NOT neeeded for page scrolling, only if draggable contained in
1554   // scrollable elements
1555   includeScrollOffsets: false,
1556
1557   // must be called before calling withinIncludingScrolloffset, every time the
1558   // page is scrolled
1559   prepare: function() {
1560     this.deltaX =  window.pageXOffset
1561                 || document.documentElement.scrollLeft
1562                 || document.body.scrollLeft
1563                 || 0;
1564     this.deltaY =  window.pageYOffset
1565                 || document.documentElement.scrollTop
1566                 || document.body.scrollTop
1567                 || 0;
1568   },
1569
1570   realOffset: function(element) {
1571     var valueT = 0, valueL = 0;
1572     do {
1573       valueT += element.scrollTop  || 0;
1574       valueL += element.scrollLeft || 0;
1575       element = element.parentNode;
1576     } while (element);
1577     return [valueL, valueT];
1578   },
1579
1580   cumulativeOffset: function(element) {
1581     var valueT = 0, valueL = 0;
1582     do {
1583       valueT += element.offsetTop  || 0;
1584       valueL += element.offsetLeft || 0;
1585       element = element.offsetParent;
1586     } while (element);
1587     return [valueL, valueT];
1588   },
1589
1590   positionedOffset: function(element) {
1591     var valueT = 0, valueL = 0;
1592     do {
1593       valueT += element.offsetTop  || 0;
1594       valueL += element.offsetLeft || 0;
1595       element = element.offsetParent;
1596       if (element) {
1597         p = Element.getStyle(element, 'position');
1598         if (p == 'relative' || p == 'absolute') break;
1599       }
1600     } while (element);
1601     return [valueL, valueT];
1602   },
1603
1604   offsetParent: function(element) {
1605     if (element.offsetParent) return element.offsetParent;
1606     if (element == document.body) return element;
1607
1608     while ((element = element.parentNode) && element != document.body)
1609       if (Element.getStyle(element, 'position') != 'static')
1610         return element;
1611
1612     return document.body;
1613   },
1614
1615   // caches x/y coordinate pair to use with overlap
1616   within: function(element, x, y) {
1617     if (this.includeScrollOffsets)
1618       return this.withinIncludingScrolloffsets(element, x, y);
1619     this.xcomp = x;
1620     this.ycomp = y;
1621     this.offset = this.cumulativeOffset(element);
1622
1623     return (y >= this.offset[1] &&
1624             y <  this.offset[1] + element.offsetHeight &&
1625             x >= this.offset[0] &&
1626             x <  this.offset[0] + element.offsetWidth);
1627   },
1628
1629   withinIncludingScrolloffsets: function(element, x, y) {
1630     var offsetcache = this.realOffset(element);
1631
1632     this.xcomp = x + offsetcache[0] - this.deltaX;
1633     this.ycomp = y + offsetcache[1] - this.deltaY;
1634     this.offset = this.cumulativeOffset(element);
1635
1636     return (this.ycomp >= this.offset[1] &&
1637             this.ycomp <  this.offset[1] + element.offsetHeight &&
1638             this.xcomp >= this.offset[0] &&
1639             this.xcomp <  this.offset[0] + element.offsetWidth);
1640   },
1641
1642   // within must be called directly before
1643   overlap: function(mode, element) {
1644     if (!mode) return 0;
1645     if (mode == 'vertical')
1646       return ((this.offset[1] + element.offsetHeight) - this.ycomp) /
1647         element.offsetHeight;
1648     if (mode == 'horizontal')
1649       return ((this.offset[0] + element.offsetWidth) - this.xcomp) /
1650         element.offsetWidth;
1651   },
1652
1653   clone: function(source, target) {
1654     source = $(source);
1655     target = $(target);
1656     target.style.position = 'absolute';
1657     var offsets = this.cumulativeOffset(source);
1658     target.style.top    = offsets[1] + 'px';
1659     target.style.left   = offsets[0] + 'px';
1660     target.style.width  = source.offsetWidth + 'px';
1661     target.style.height = source.offsetHeight + 'px';
1662   },
1663
1664   page: function(forElement) {
1665     var valueT = 0, valueL = 0;
1666
1667     var element = forElement;
1668     do {
1669       valueT += element.offsetTop  || 0;
1670       valueL += element.offsetLeft || 0;
1671
1672       // Safari fix
1673       if (element.offsetParent==document.body)
1674         if (Element.getStyle(element,'position')=='absolute') break;
1675
1676     } while (element = element.offsetParent);
1677
1678     element = forElement;
1679     do {
1680       valueT -= element.scrollTop  || 0;
1681       valueL -= element.scrollLeft || 0;
1682     } while (element = element.parentNode);
1683
1684     return [valueL, valueT];
1685   },
1686
1687   clone: function(source, target) {
1688     var options = Object.extend({
1689       setLeft:    true,
1690       setTop:     true,
1691       setWidth:   true,
1692       setHeight:  true,
1693       offsetTop:  0,
1694       offsetLeft: 0
1695     }, arguments[2] || {})
1696
1697     // find page position of source
1698     source = $(source);
1699     var p = Position.page(source);
1700
1701     // find coordinate system to use
1702     target = $(target);
1703     var delta = [0, 0];
1704     var parent = null;
1705     // delta [0,0] will do fine with position: fixed elements,
1706     // position:absolute needs offsetParent deltas
1707     if (Element.getStyle(target,'position') == 'absolute') {
1708       parent = Position.offsetParent(target);
1709       delta = Position.page(parent);
1710     }
1711
1712     // correct by body offsets (fixes Safari)
1713     if (parent == document.body) {
1714       delta[0] -= document.body.offsetLeft;
1715       delta[1] -= document.body.offsetTop;
1716     }
1717
1718     // set position
1719     if(options.setLeft)   target.style.left  = (p[0] - delta[0] + options.offsetLeft) + 'px';
1720     if(options.setTop)    target.style.top   = (p[1] - delta[1] + options.offsetTop) + 'px';
1721     if(options.setWidth)  target.style.width = source.offsetWidth + 'px';
1722     if(options.setHeight) target.style.height = source.offsetHeight + 'px';
1723   },
1724
1725   absolutize: function(element) {
1726     element = $(element);
1727     if (element.style.position == 'absolute') return;
1728     Position.prepare();
1729
1730     var offsets = Position.positionedOffset(element);
1731     var top     = offsets[1];
1732     var left    = offsets[0];
1733     var width   = element.clientWidth;
1734     var height  = element.clientHeight;
1735
1736     element._originalLeft   = left - parseFloat(element.style.left  || 0);
1737     element._originalTop    = top  - parseFloat(element.style.top || 0);
1738     element._originalWidth  = element.style.width;
1739     element._originalHeight = element.style.height;
1740
1741     element.style.position = 'absolute';
1742     element.style.top    = top + 'px';;
1743     element.style.left   = left + 'px';;
1744     element.style.width  = width + 'px';;
1745     element.style.height = height + 'px';;
1746   },
1747
1748   relativize: function(element) {
1749     element = $(element);
1750     if (element.style.position == 'relative') return;
1751     Position.prepare();
1752
1753     element.style.position = 'relative';
1754     var top  = parseFloat(element.style.top  || 0) - (element._originalTop || 0);
1755     var left = parseFloat(element.style.left || 0) - (element._originalLeft || 0);
1756
1757     element.style.top    = top + 'px';
1758     element.style.left   = left + 'px';
1759     element.style.height = element._originalHeight;
1760     element.style.width  = element._originalWidth;
1761   }
1762 }
1763
1764 // Safari returns margins on body which is incorrect if the child is absolutely
1765 // positioned.  For performance reasons, redefine Position.cumulativeOffset for
1766 // KHTML/WebKit only.
1767 if (/Konqueror|Safari|KHTML/.test(navigator.userAgent)) {
1768   Position.cumulativeOffset = function(element) {
1769     var valueT = 0, valueL = 0;
1770     do {
1771       valueT += element.offsetTop  || 0;
1772       valueL += element.offsetLeft || 0;
1773       if (element.offsetParent == document.body)
1774         if (Element.getStyle(element, 'position') == 'absolute') break;
1775
1776       element = element.offsetParent;
1777     } while (element);
1778
1779     return [valueL, valueT];
1780   }
1781 }