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