source: products/qPloneDropDownMenu/branches/0.2/skins/qPloneDropDownMenu/javascripts/prototype.js @ 1

Last change on this file since 1 was 1, checked in by myroslav, 18 years ago

Building directory structure

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