PageSourceSearch

https://www.printwand.com/media/js/e9e0e57456c976ac910c19256dffed17.js

js printwand.com collected 2026-10-04 00:18:37 UTC 338,437 bytes, 9,612 lines download raw bytes

1/*  Prototype JavaScript framework, version 1.6.0.3
2 *  (c) 2005-2008 Sam Stephenson
3 *
4 *  Prototype is freely distributable under the terms of an MIT-style license.
5 *  For details, see the Prototype web site: http://www.prototypejs.org/
6 *
7 *--------------------------------------------------------------------------*/
8
9var Prototype = {
10  Version: '1.6.0.3',
11
12  Browser: {
13    IE:     !!(window.attachEvent &&
14      navigator.userAgent.indexOf('Opera') === -1),
15    Opera:  navigator.userAgent.indexOf('Opera') > -1,
16    WebKit: navigator.userAgent.indexOf('AppleWebKit/') > -1,
17    Gecko:  navigator.userAgent.indexOf('Gecko') > -1 &&
18      navigator.userAgent.indexOf('KHTML') === -1,
19    MobileSafari: !!navigator.userAgent.match(/Apple.*Mobile.*Safari/)
20  },
21
22  BrowserFeatures: {
23    XPath: !!document.evaluate,
24    SelectorsAPI: !!document.querySelector,
25    ElementExtensions: !!window.HTMLElement,
26    SpecificElementExtensions:
27      document.createElement('div')['__proto__'] &&
28      document.createElement('div')['__proto__'] !==
29        document.createElement('form')['__proto__']
30  },
31
32  ScriptFragment: '<script[^>]*>([\\S\\s]*?)<\/script>',
33  JSONFilter: /^\/\*-secure-([\s\S]*)\*\/\s*$/,
34
35  emptyFunction: function() { },
36  K: function(x) { return x }
37};
38
39if (Prototype.Browser.MobileSafari)
40  Prototype.BrowserFeatures.SpecificElementExtensions = false;
41
42
43/* Based on Alex Arnell's inheritance implementation. */
44var Class = {
45  create: function() {
46    var parent = null, properties = $A(arguments);
47    if (Object.isFunction(properties[0]))
48      parent = properties.shift();
49
50    function klass() {
51      this.initialize.apply(this, arguments);
52    }
53
54    Object.extend(klass, Class.Methods);
55    klass.superclass = parent;
56    klass.subclasses = [];
57
58    if (parent) {
59      var subclass = function() { };
60      subclass.prototype = parent.prototype;
61      klass.prototype = new subclass;
62      parent.subclasses.push(klass);
63    }
64
65    for (var i = 0; i < properties.length; i++)
66      klass.addMethods(properties[i]);
67
68    if (!klass.prototype.initialize)
69      klass.prototype.initialize = Prototype.emptyFunction;
70
71    klass.prototype.constructor = klass;
72
73    return klass;
74  }
75};
76
77Class.Methods = {
78  addMethods: function(source) {
79    var ancestor   = this.superclass && this.superclass.prototype;
80    var properties = Object.keys(source);
81
82    if (!Object.keys({ toString: true }).length)
83      properties.push("toString", "valueOf");
84
85    for (var i = 0, length = properties.length; i < length; i++) {
86      var property = properties[i], value = source[property];
87      if (ancestor && Object.isFunction(value) &&
88          value.argumentNames().first() == "$super") {
89        var method = value;
90        value = (function(m) {
91          return function() { return ancestor[m].apply(this, arguments) };
92        })(property).wrap(method);
93
94        value.valueOf = method.valueOf.bind(method);
95        value.toString = method.toString.bind(method);
96      }
97      this.prototype[property] = value;
98    }
99
100    return this;
101  }
102};
103
104var Abstract = { };
105
106Object.extend = function(destination, source) {
107  for (var property in source)
108    destination[property] = source[property];
109  return destination;
110};
111
112Object.extend(Object, {
113  inspect: function(object) {
114    try {
115      if (Object.isUndefined(object)) return 'undefined';
116      if (object === null) return 'null';
117      return object.inspect ? object.inspect() : String(object);
118    } catch (e) {
119      if (e instanceof RangeError) return '...';
120      throw e;
121    }
122  },
123
124  toJSON: function(object) {
125    var type = typeof object;
126    switch (type) {
127      case 'undefined':
128      case 'function':
129      case 'unknown': return;
130      case 'boolean': return object.toString();
131    }
132
133    if (object === null) return 'null';
134    if (object.toJSON) return object.toJSON();
135    if (Object.isElement(object)) return;
136
137    var results = [];
138    for (var property in object) {
139      var value = Object.toJSON(object[property]);
140      if (!Object.isUndefined(value))
141        results.push(property.toJSON() + ': ' + value);
142    }
143
144    return '{' + results.join(', ') + '}';
145  },
146
147  toQueryString: function(object) {
148    return $H(object).toQueryString();
149  },
150
151  toHTML: function(object) {
152    return object && object.toHTML ? object.toHTML() : String.interpret(object);
153  },
154
155  keys: function(object) {
156    var keys = [];
157    for (var property in object)
158      keys.push(property);
159    return keys;
160  },
161
162  values: function(object) {
163    var values = [];
164    for (var property in object)
165      values.push(object[property]);
166    return values;
167  },
168
169  clone: function(object) {
170    return Object.extend({ }, object);
171  },
172
173  isElement: function(object) {
174    return !!(object && object.nodeType == 1);
175  },
176
177  isArray: function(object) {
178    return object != null && typeof object == "object" &&
179      'splice' in object && 'join' in object;
180  },
181
182  isHash: function(object) {
183    return object instanceof Hash;
184  },
185
186  isFunction: function(object) {
187    return typeof object == "function";
188  },
189
190  isString: function(object) {
191    return typeof object == "string";
192  },
193
194  isNumber: function(object) {
195    return typeof object == "number";
196  },
197
198  isUndefined: function(object) {
199    return typeof object == "undefined";
200  }
201});
202
203Object.extend(Function.prototype, {
204  argumentNames: function() {
205    var names = this.toString().match(/^[\s\(]*function[^(]*\(([^\)]*)\)/)[1]
206      .replace(/\s+/g, '').split(',');
207    return names.length == 1 && !names[0] ? [] : names;
208  },
209
210  bind: function() {
211    if (arguments.length < 2 && Object.isUndefined(arguments[0])) return this;
212    var __method = this, args = $A(arguments), object = args.shift();
213    return function() {
214      return __method.apply(object, args.concat($A(arguments)));
215    }
216  },
217
218  bindAsEventListener: function() {
219    var __method = this, args = $A(arguments), object = args.shift();
220    return function(event) {
221      return __method.apply(object, [event || window.event].concat(args));
222    }
223  },
224
225  curry: function() {
226    if (!arguments.length) return this;
227    var __method = this, args = $A(arguments);
228    return function() {
229      return __method.apply(this, args.concat($A(arguments)));
230    }
231  },
232
233  delay: function() {
234    var __method = this, args = $A(arguments), timeout = args.shift() * 1000;
235    return window.setTimeout(function() {
236      return __method.apply(__method, args);
237    }, timeout);
238  },
239
240  defer: function() {
241    var args = [0.01].concat($A(arguments));
242    return this.delay.apply(this, args);
243  },
244
245  wrap: function(wrapper) {
246    var __method = this;
247    return function() {
248      return wrapper.apply(this, [__method.bind(this)].concat($A(arguments)));
249    }
250  },
251
252  methodize: function() {
253    if (this._methodized) return this._methodized;
254    var __method = this;
255    return this._methodized = function() {
256      return __method.apply(null, [this].concat($A(arguments)));
257    };
258  }
259});
260
261Date.prototype.toJSON = function() {
262  return '"' + this.getUTCFullYear() + '-' +
263    (this.getUTCMonth() + 1).toPaddedString(2) + '-' +
264    this.getUTCDate().toPaddedString(2) + 'T' +
265    this.getUTCHours().toPaddedString(2) + ':' +
266    this.getUTCMinutes().toPaddedString(2) + ':' +
267    this.getUTCSeconds().toPaddedString(2) + 'Z"';
268};
269
270var Try = {
271  these: function() {
272    var returnValue;
273
274    for (var i = 0, length = arguments.length; i < length; i++) {
275      var lambda = arguments[i];
276      try {
277        returnValue = lambda();
278        break;
279      } catch (e) { }
280    }
281
282    return returnValue;
283  }
284};
285
286RegExp.prototype.match = RegExp.prototype.test;
287
288RegExp.escape = function(str) {
289  return String(str).replace(/([.*+?^=!:${}()|[\]\/\\])/g, '\\$1');
290};
291
292/*--------------------------------------------------------------------------*/
293
294var PeriodicalExecuter = Class.create({
295  initialize: function(callback, frequency) {
296    this.callback = callback;
297    this.frequency = frequency;
298    this.currentlyExecuting = false;
299
300    this.registerCallback();
301  },
302
303  registerCallback: function() {
304    this.timer = setInterval(this.onTimerEvent.bind(this), this.frequency * 1000);
305  },
306
307  execute: function() {
308    this.callback(this);
309  },
310
311  stop: function() {
312    if (!this.timer) return;
313    clearInterval(this.timer);
314    this.timer = null;
315  },
316
317  onTimerEvent: function() {
318    if (!this.currentlyExecuting) {
319      try {
320        this.currentlyExecuting = true;
321        this.execute();
322      } finally {
323        this.currentlyExecuting = false;
324      }
325    }
326  }
327});
328Object.extend(String, {
329  interpret: function(value) {
330    return value == null ? '' : String(value);
331  },
332  specialChar: {
333    '\b': '\\b',
334    '\t': '\\t',
335    '\n': '\\n',
336    '\f': '\\f',
337    '\r': '\\r',
338    '\\': '\\\\'
339  }
340});
341
342Object.extend(String.prototype, {
343  gsub: function(pattern, replacement) {
344    var result = '', source = this, match;
345    replacement = arguments.callee.prepareReplacement(replacement);
346
347    while (source.length > 0) {
348      if (match = source.match(pattern)) {
349        result += source.slice(0, match.index);
350        result += String.interpret(replacement(match));
351        source  = source.slice(match.index + match[0].length);
352      } else {
353        result += source, source = '';
354      }
355    }
356    return result;
357  },
358
359  sub: function(pattern, replacement, count) {
360    replacement = this.gsub.prepareReplacement(replacement);
361    count = Object.isUndefined(count) ? 1 : count;
362
363    return this.gsub(pattern, function(match) {
364      if (--count < 0) return match[0];
365      return replacement(match);
366    });
367  },
368
369  scan: function(pattern, iterator) {
370    this.gsub(pattern, iterator);
371    return String(this);
372  },
373
374  truncate: function(length, truncation) {
375    length = length || 30;
376    truncation = Object.isUndefined(truncation) ? '...' : truncation;
377    return this.length > length ?
378      this.slice(0, length - truncation.length) + truncation : String(this);
379  },
380
381  strip: function() {
382    return this.replace(/^\s+/, '').replace(/\s+$/, '');
383  },
384
385  stripTags: function() {
386    return this.replace(/<\/?[^>]+>/gi, '');
387  },
388
389  stripScripts: function() {
390    return this.replace(new RegExp(Prototype.ScriptFragment, 'img'), '');
391  },
392
393  extractScripts: function() {
394    var matchAll = new RegExp(Prototype.ScriptFragment, 'img');
395    var matchOne = new RegExp(Prototype.ScriptFragment, 'im');
396    return (this.match(matchAll) || []).map(function(scriptTag) {
397      return (scriptTag.match(matchOne) || ['', ''])[1];
398    });
399  },
400
401  evalScripts: function() {
402    return this.extractScripts().map(function(script) { return eval(script) });
403  },
404
405  escapeHTML: function() {
406    var self = arguments.callee;
407    self.text.data = this;
408    return self.div.innerHTML;
409  },
410
411  unescapeHTML: function() {
412    var div = new Element('div');
413    div.innerHTML = this.stripTags();
414    return div.childNodes[0] ? (div.childNodes.length > 1 ?
415      $A(div.childNodes).inject('', function(memo, node) { return memo+node.nodeValue }) :
416      div.childNodes[0].nodeValue) : '';
417  },
418
419  toQueryParams: function(separator) {
420    var match = this.strip().match(/([^?#]*)(#.*)?$/);
421    if (!match) return { };
422
423    return match[1].split(separator || '&').inject({ }, function(hash, pair) {
424      if ((pair = pair.split('='))[0]) {
425        var key = decodeURIComponent(pair.shift());
426        var value = pair.length > 1 ? pair.join('=') : pair[0];
427        if (value != undefined) value = decodeURIComponent(value);
428
429        if (key in hash) {
430          if (!Object.isArray(hash[key])) hash[key] = [hash[key]];
431          hash[key].push(value);
432        }
433        else hash[key] = value;
434      }
435      return hash;
436    });
437  },
438
439  toArray: function() {
440    return this.split('');
441  },
442
443  succ: function() {
444    return this.slice(0, this.length - 1) +
445      String.fromCharCode(this.charCodeAt(this.length - 1) + 1);
446  },
447
448  times: function(count) {
449    return count < 1 ? '' : new Array(count + 1).join(this);
450  },
451
452  camelize: function() {
453    var parts = this.split('-'), len = parts.length;
454    if (len == 1) return parts[0];
455
456    var camelized = this.charAt(0) == '-'
457      ? parts[0].charAt(0).toUpperCase() + parts[0].substring(1)
458      : parts[0];
459
460    for (var i = 1; i < len; i++)
461      camelized += parts[i].charAt(0).toUpperCase() + parts[i].substring(1);
462
463    return camelized;
464  },
465
466  capitalize: function() {
467    return this.charAt(0).toUpperCase() + this.substring(1).toLowerCase();
468  },
469
470  underscore: function() {
471    return this.gsub(/::/, '/').gsub(/([A-Z]+)([A-Z][a-z])/,'#{1}_#{2}').gsub(/([a-z\d])([A-Z])/,'#{1}_#{2}').gsub(/-/,'_').toLowerCase();
472  },
473
474  dasherize: function() {
475    return this.gsub(/_/,'-');
476  },
477
478  inspect: function(useDoubleQuotes) {
479    var escapedString = this.gsub(/[\x00-\x1f\\]/, function(match) {
480      var character = String.specialChar[match[0]];
481      return character ? character : '\\u00' + match[0].charCodeAt().toPaddedString(2, 16);
482    });
483    if (useDoubleQuotes) return '"' + escapedString.replace(/"/g, '\\"') + '"';
484    return "'" + escapedString.replace(/'/g, '\\\'') + "'";
485  },
486
487  toJSON: function() {
488    return this.inspect(true);
489  },
490
491  unfilterJSON: function(filter) {
492    return this.sub(filter || Prototype.JSONFilter, '#{1}');
493  },
494
495  isJSON: function() {
496    var str = this;
497    if (str.blank()) return false;
498    str = this.replace(/\\./g, '@').replace(/"[^"\\\n\r]*"/g, '');
499    return (/^[,:{}\[\]0-9.\-+Eaeflnr-u \n\r\t]*$/).test(str);
500  },
501
502  evalJSON: function(sanitize) {
503    var json = this.unfilterJSON();
504    try {
505      if (!sanitize || json.isJSON()) return eval('(' + json + ')');
506    } catch (e) { }
507    throw new SyntaxError('Badly formed JSON string: ' + this.inspect());
508  },
509
510  include: function(pattern) {
511    return this.indexOf(pattern) > -1;
512  },
513
514  startsWith: function(pattern) {
515    return this.indexOf(pattern) === 0;
516  },
517
518  endsWith: function(pattern) {
519    var d = this.length - pattern.length;
520    return d >= 0 && this.lastIndexOf(pattern) === d;
521  },
522
523  empty: function() {
524    return this == '';
525  },
526
527  blank: function() {
528    return /^\s*$/.test(this);
529  },
530
531  interpolate: function(object, pattern) {
532    return new Template(this, pattern).evaluate(object);
533  }
534});
535
536if (Prototype.Browser.WebKit || Prototype.Browser.IE) Object.extend(String.prototype, {
537  escapeHTML: function() {
538    return this.replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;');
539  },
540  unescapeHTML: function() {
541    return this.stripTags().replace(/&amp;/g,'&').replace(/&lt;/g,'<').replace(/&gt;/g,'>');
542  }
543});
544
545String.prototype.gsub.prepareReplacement = function(replacement) {
546  if (Object.isFunction(replacement)) return replacement;
547  var template = new Template(replacement);
548  return function(match) { return template.evaluate(match) };
549};
550
551String.prototype.parseQuery = String.prototype.toQueryParams;
552
553Object.extend(String.prototype.escapeHTML, {
554  div:  document.createElement('div'),
555  text: document.createTextNode('')
556});
557
558String.prototype.escapeHTML.div.appendChild(String.prototype.escapeHTML.text);
559
560var Template = Class.create({
561  initialize: function(template, pattern) {
562    this.template = template.toString();
563    this.pattern = pattern || Template.Pattern;
564  },
565
566  evaluate: function(object) {
567    if (Object.isFunction(object.toTemplateReplacements))
568      object = object.toTemplateReplacements();
569
570    return this.template.gsub(this.pattern, function(match) {
571      if (object == null) return '';
572
573      var before = match[1] || '';
574      if (before == '\\') return match[2];
575
576      var ctx = object, expr = match[3];
577      var pattern = /^([^.[]+|\[((?:.*?[^\\])?)\])(\.|\[|$)/;
578      match = pattern.exec(expr);
579      if (match == null) return before;
580
581      while (match != null) {
582        var comp = match[1].startsWith('[') ? match[2].gsub('\\\\]', ']') : match[1];
583        ctx = ctx[comp];
584        if (null == ctx || '' == match[3]) break;
585        expr = expr.substring('[' == match[3] ? match[1].length : match[0].length);
586        match = pattern.exec(expr);
587      }
588
589      return before + String.interpret(ctx);
590    });
591  }
592});
593Template.Pattern = /(^|.|\r|\n)(#\{(.*?)\})/;
594
595var $break = { };
596
597var Enumerable = {
598  each: function(iterator, context) {
599    var index = 0;
600    try {
601      this._each(function(value) {
602        iterator.call(context, value, index++);
603      });
604    } catch (e) {
605      if (e != $break) throw e;
606    }
607    return this;
608  },
609
610  eachSlice: function(number, iterator, context) {
611    var index = -number, slices = [], array = this.toArray();
612    if (number < 1) return array;
613    while ((index += number) < array.length)
614      slices.push(array.slice(index, index+number));
615    return slices.collect(iterator, context);
616  },
617
618  all: function(iterator, context) {
619    iterator = iterator || Prototype.K;
620    var result = true;
621    this.each(function(value, index) {
622      result = result && !!iterator.call(context, value, index);
623      if (!result) throw $break;
624    });
625    return result;
626  },
627
628  any: function(iterator, context) {
629    iterator = iterator || Prototype.K;
630    var result = false;
631    this.each(function(value, index) {
632      if (result = !!iterator.call(context, value, index))
633        throw $break;
634    });
635    return result;
636  },
637
638  collect: function(iterator, context) {
639    iterator = iterator || Prototype.K;
640    var results = [];
641    this.each(function(value, index) {
642      results.push(iterator.call(context, value, index));
643    });
644    return results;
645  },
646
647  detect: function(iterator, context) {
648    var result;
649    this.each(function(value, index) {
650      if (iterator.call(context, value, index)) {
651        result = value;
652        throw $break;
653      }
654    });
655    return result;
656  },
657
658  findAll: function(iterator, context) {
659    var results = [];
660    this.each(function(value, index) {
661      if (iterator.call(context, value, index))
662        results.push(value);
663    });
664    return results;
665  },
666
667  grep: function(filter, iterator, context) {
668    iterator = iterator || Prototype.K;
669    var results = [];
670
671    if (Object.isString(filter))
672      filter = new RegExp(filter);
673
674    this.each(function(value, index) {
675      if (filter.match(value))
676        results.push(iterator.call(context, value, index));
677    });
678    return results;
679  },
680
681  include: function(object) {
682    if (Object.isFunction(this.indexOf))
683      if (this.indexOf(object) != -1) return true;
684
685    var found = false;
686    this.each(function(value) {
687      if (value == object) {
688        found = true;
689        throw $break;
690      }
691    });
692    return found;
693  },
694
695  inGroupsOf: function(number, fillWith) {
696    fillWith = Object.isUndefined(fillWith) ? null : fillWith;
697    return this.eachSlice(number, function(slice) {
698      while(slice.length < number) slice.push(fillWith);
699      return slice;
700    });
701  },
702
703  inject: function(memo, iterator, context) {
704    this.each(function(value, index) {
705      memo = iterator.call(context, memo, value, index);
706    });
707    return memo;
708  },
709
710  invoke: function(method) {
711    var args = $A(arguments).slice(1);
712    return this.map(function(value) {
713      return value[method].apply(value, args);
714    });
715  },
716
717  max: function(iterator, context) {
718    iterator = iterator || Prototype.K;
719    var result;
720    this.each(function(value, index) {
721      value = iterator.call(context, value, index);
722      if (result == null || value >= result)
723        result = value;
724    });
725    return result;
726  },
727
728  min: function(iterator, context) {
729    iterator = iterator || Prototype.K;
730    var result;
731    this.each(function(value, index) {
732      value = iterator.call(context, value, index);
733      if (result == null || value < result)
734        result = value;
735    });
736    return result;
737  },
738
739  partition: function(iterator, context) {
740    iterator = iterator || Prototype.K;
741    var trues = [], falses = [];
742    this.each(function(value, index) {
743      (iterator.call(context, value, index) ?
744        trues : falses).push(value);
745    });
746    return [trues, falses];
747  },
748
749  pluck: function(property) {
750    var results = [];
751    this.each(function(value) {
752      results.push(value[property]);
753    });
754    return results;
755  },
756
757  reject: function(iterator, context) {
758    var results = [];
759    this.each(function(value, index) {
760      if (!iterator.call(context, value, index))
761        results.push(value);
762    });
763    return results;
764  },
765
766  sortBy: function(iterator, context) {
767    return this.map(function(value, index) {
768      return {
769        value: value,
770        criteria: iterator.call(context, value, index)
771      };
772    }).sort(function(left, right) {
773      var a = left.criteria, b = right.criteria;
774      return a < b ? -1 : a > b ? 1 : 0;
775    }).pluck('value');
776  },
777
778  toArray: function() {
779    return this.map();
780  },
781
782  zip: function() {
783    var iterator = Prototype.K, args = $A(arguments);
784    if (Object.isFunction(args.last()))
785      iterator = args.pop();
786
787    var collections = [this].concat(args).map($A);
788    return this.map(function(value, index) {
789      return iterator(collections.pluck(index));
790    });
791  },
792
793  size: function() {
794    return this.toArray().length;
795  },
796
797  inspect: function() {
798    return '#<Enumerable:' + this.toArray().inspect() + '>';
799  }
800};
801
802Object.extend(Enumerable, {
803  map:     Enumerable.collect,
804  find:    Enumerable.detect,
805  select:  Enumerable.findAll,
806  filter:  Enumerable.findAll,
807  member:  Enumerable.include,
808  entries: Enumerable.toArray,
809  every:   Enumerable.all,
810  some:    Enumerable.any
811});
812function $A(iterable) {
813  if (!iterable) return [];
814  if (iterable.toArray) return iterable.toArray();
815  var length = iterable.length || 0, results = new Array(length);
816  while (length--) results[length] = iterable[length];
817  return results;
818}
819
820if (Prototype.Browser.WebKit) {
821  $A = function(iterable) {
822    if (!iterable) return [];
823    // In Safari, only use the `toArray` method if it's not a NodeList.
824    // A NodeList is a function, has an function `item` property, and a numeric
825    // `length` property. Adapted from Google Doctype.
826    if (!(typeof iterable === 'function' && typeof iterable.length ===
827        'number' && typeof iterable.item === 'function') && iterable.toArray)
828      return iterable.toArray();
829    var length = iterable.length || 0, results = new Array(length);
830    while (length--) results[length] = iterable[length];
831    return results;
832  };
833}
834
835Array.from = $A;
836
837Object.extend(Array.prototype, Enumerable);
838
839if (!Array.prototype._reverse) Array.prototype._reverse = Array.prototype.reverse;
840
841Object.extend(Array.prototype, {
842  _each: function(iterator) {
843    for (var i = 0, length = this.length; i < length; i++)
844      iterator(this[i]);
845  },
846
847  clear: function() {
848    this.length = 0;
849    return this;
850  },
851
852  first: function() {
853    return this[0];
854  },
855
856  last: function() {
857    return this[this.length - 1];
858  },
859
860  compact: function() {
861    return this.select(function(value) {
862      return value != null;
863    });
864  },
865
866  flatten: function() {
867    return this.inject([], function(array, value) {
868      return array.concat(Object.isArray(value) ?
869        value.flatten() : [value]);
870    });
871  },
872
873  without: function() {
874    var values = $A(arguments);
875    return this.select(function(value) {
876      return !values.include(value);
877    });
878  },
879
880  reverse: function(inline) {
881    return (inline !== false ? this : this.toArray())._reverse();
882  },
883
884  reduce: function() {
885    return this.length >
885 1 ? this : this[0];
886  },
887
888  uniq: function(sorted) {
889    return this.inject([], function(array, value, index) {
890      if (0 == index || (sorted ? array.last() != value : !array.include(value)))
891        array.push(value);
892      return array;
893    });
894  },
895
896  intersect: function(array) {
897    return this.uniq().findAll(function(item) {
898      return array.detect(function(value) { return item === value });
899    });
900  },
901
902  clone: function() {
903    return [].concat(this);
904  },
905
906  size: function() {
907    return this.length;
908  },
909
910  inspect: function() {
911    return '[' + this.map(Object.inspect).join(', ') + ']';
912  },
913
914  toJSON: function() {
915    var results = [];
916    this.each(function(object) {
917      var value = Object.toJSON(object);
918      if (!Object.isUndefined(value)) results.push(value);
919    });
920    return '[' + results.join(', ') + ']';
921  }
922});
923
924// use native browser JS 1.6 implementation if available
925if (Object.isFunction(Array.prototype.forEach))
926  Array.prototype._each = Array.prototype.forEach;
927
928if (!Array.prototype.indexOf) Array.prototype.indexOf = function(item, i) {
929  i || (i = 0);
930  var length = this.length;
931  if (i < 0) i = length + i;
932  for (; i < length; i++)
933    if (this[i] === item) return i;
934  return -1;
935};
936
937if (!Array.prototype.lastIndexOf) Array.prototype.lastIndexOf = function(item, i) {
938  i = isNaN(i) ? this.length : (i < 0 ? this.length + i : i) + 1;
939  var n = this.slice(0, i).reverse().indexOf(item);
940  return (n < 0) ? n : i - n - 1;
941};
942
943Array.prototype.toArray = Array.prototype.clone;
944
945function $w(string) {
946  if (!Object.isString(string)) return [];
947  string = string.strip();
948  return string ? string.split(/\s+/) : [];
949}
950
951if (Prototype.Browser.Opera){
952  Array.prototype.concat = function() {
953    var array = [];
954    for (var i = 0, length = this.length; i < length; i++) array.push(this[i]);
955    for (var i = 0, length = arguments.length; i < length; i++) {
956      if (Object.isArray(arguments[i])) {
957        for (var j = 0, arrayLength = arguments[i].length; j < arrayLength; j++)
958          array.push(arguments[i][j]);
959      } else {
960        array.push(arguments[i]);
961      }
962    }
963    return array;
964  };
965}
966Object.extend(Number.prototype, {
967  toColorPart: function() {
968    return this.toPaddedString(2, 16);
969  },
970
971  succ: function() {
972    return this + 1;
973  },
974
975  times: function(iterator, context) {
976    $R(0, this, true).each(iterator, context);
977    return this;
978  },
979
980  toPaddedString: function(length, radix) {
981    var string = this.toString(radix || 10);
982    return '0'.times(length - string.length) + string;
983  },
984
985  toJSON: function() {
986    return isFinite(this) ? this.toString() : 'null';
987  }
988});
989
990$w('abs round ceil floor').each(function(method){
991  Number.prototype[method] = Math[method].methodize();
992});
993function $H(object) {
994  return new Hash(object);
995};
996
997var Hash = Class.create(Enumerable, (function() {
998
999  function toQueryPair(key, value) {
1000    if (Object.isUndefined(value)) return key;
1001    return key + '=' + encodeURIComponent(String.interpret(value));
1002  }
1003
1004  return {
1005    initialize: function(object) {
1006      this._object = Object.isHash(object) ? object.toObject() : Object.clone(object);
1007    },
1008
1009    _each: function(iterator) {
1010      for (var key in this._object) {
1011        var value = this._object[key], pair = [key, value];
1012        pair.key = key;
1013        pair.value = value;
1014        iterator(pair);
1015      }
1016    },
1017
1018    set: function(key, value) {
1019      return this._object[key] = value;
1020    },
1021
1022    get: function(key) {
1023      // simulating poorly supported hasOwnProperty
1024      if (this._object[key] !== Object.prototype[key])
1025        return this._object[key];
1026    },
1027
1028    unset: function(key) {
1029      var value = this._object[key];
1030      delete this._object[key];
1031      return value;
1032    },
1033
1034    toObject: function() {
1035      return Object.clone(this._object);
1036    },
1037
1038    keys: function() {
1039      return this.pluck('key');
1040    },
1041
1042    values: function() {
1043      return this.pluck('value');
1044    },
1045
1046    index: function(value) {
1047      var match = this.detect(function(pair) {
1048        return pair.value === value;
1049      });
1050      return match && match.key;
1051    },
1052
1053    merge: function(object) {
1054      return this.clone().update(object);
1055    },
1056
1057    update: function(object) {
1058      return new Hash(object).inject(this, function(result, pair) {
1059        result.set(pair.key, pair.value);
1060        return result;
1061      });
1062    },
1063
1064    toQueryString: function() {
1065      return this.inject([], function(results, pair) {
1066        var key = encodeURIComponent(pair.key), values = pair.value;
1067
1068        if (values && typeof values == 'object') {
1069          if (Object.isArray(values))
1070            return results.concat(values.map(toQueryPair.curry(key)));
1071        } else results.push(toQueryPair(key, values));
1072        return results;
1073      }).join('&');
1074    },
1075
1076    inspect: function() {
1077      return '#<Hash:{' + this.map(function(pair) {
1078        return pair.map(Object.inspect).join(': ');
1079      }).join(', ') + '}>';
1080    },
1081
1082    toJSON: function() {
1083      return Object.toJSON(this.toObject());
1084    },
1085
1086    clone: function() {
1087      return new Hash(this);
1088    }
1089  }
1090})());
1091
1092Hash.prototype.toTemplateReplacements = Hash.prototype.toObject;
1093Hash.from = $H;
1094var ObjectRange = Class.create(Enumerable, {
1095  initialize: function(start, end, exclusive) {
1096    this.start = start;
1097    this.end = end;
1098    this.exclusive = exclusive;
1099  },
1100
1101  _each: function(iterator) {
1102    var value = this.start;
1103    while (this.include(value)) {
1104      iterator(value);
1105      value = value.succ();
1106    }
1107  },
1108
1109  include: function(value) {
1110    if (value < this.start)
1111      return false;
1112    if (this.exclusive)
1113      return value < this.end;
1114    return value <= this.end;
1115  }
1116});
1117
1118var $R = function(start, end, exclusive) {
1119  return new ObjectRange(start, end, exclusive);
1120};
1121
1122var Ajax = {
1123  getTransport: function() {
1124    return Try.these(
1125      function() {return new XMLHttpRequest()},
1126      function() {return new ActiveXObject('Msxml2.XMLHTTP')},
1127      function() {return new ActiveXObject('Microsoft.XMLHTTP')}
1128    ) || false;
1129  },
1130
1131  activeRequestCount: 0
1132};
1133
1134Ajax.Responders = {
1135  responders: [],
1136
1137  _each: function(iterator) {
1138    this.responders._each(iterator);
1139  },
1140
1141  register: function(responder) {
1142    if (!this.include(responder))
1143      this.responders.push(responder);
1144  },
1145
1146  unregister: function(responder) {
1147    this.responders = this.responders.without(responder);
1148  },
1149
1150  dispatch: function(callback, request, transport, json) {
1151    this.each(function(responder) {
1152      if (Object.isFunction(responder[callback])) {
1153        try {
1154          responder[callback].apply(responder, [request, transport, json]);
1155        } catch (e) { }
1156      }
1157    });
1158  }
1159};
1160
1161Object.extend(Ajax.Responders, Enumerable);
1162
1163Ajax.Responders.register({
1164  onCreate:   function() { Ajax.activeRequestCount++ },
1165  onComplete: function() { Ajax.activeRequestCount-- }
1166});
1167
1168Ajax.Base = Class.create({
1169  initialize: function(options) {
1170    this.options = {
1171      method:       'post',
1172      asynchronous: true,
1173      contentType:  'application/x-www-form-urlencoded',
1174      encoding:     'UTF-8',
1175      parameters:   '',
1176      evalJSON:     true,
1177      evalJS:       true
1178    };
1179    Object.extend(this.options, options || { });
1180
1181    this.options.method = this.options.method.toLowerCase();
1182
1183    if (Object.isString(this.options.parameters))
1184      this.options.parameters = this.options.parameters.toQueryParams();
1185    else if (Object.isHash(this.options.parameters))
1186      this.options.parameters = this.options.parameters.toObject();
1187  }
1188});
1189
1190Ajax.Request = Class.create(Ajax.Base, {
1191  _complete: false,
1192
1193  initialize: function($super, url, options) {
1194    $super(options);
1195    this.transport = Ajax.getTransport();
1196    this.request(url);
1197  },
1198
1199  request: function(url) {
1200    this.url = url;
1201    this.method = this.options.method;
1202    var params = Object.clone(this.options.parameters);
1203
1204    if (!['get', 'post'].include(this.method)) {
1205      // simulate other verbs over post
1206      params['_method'] = this.method;
1207      this.method = 'post';
1208    }
1209
1210    this.parameters = params;
1211
1212    if (params = Object.toQueryString(params)) {
1213      // when GET, append parameters to URL
1214      if (this.method == 'get')
1215        this.url += (this.url.include('?') ? '&' : '?') + params;
1216      else if (/Konqueror|Safari|KHTML/.test(navigator.userAgent))
1217        params += '&_=';
1218    }
1219
1220    try {
1221      var response = new Ajax.Response(this);
1222      if (this.options.onCreate) this.options.onCreate(response);
1223      Ajax.Responders.dispatch('onCreate', this, response);
1224
1225      this.transport.open(this.method.toUpperCase(), this.url,
1226        this.options.asynchronous);
1227
1228      if (this.options.asynchronous) this.respondToReadyState.bind(this).defer(1);
1229
1230      this.transport.onreadystatechange = this.onStateChange.bind(this);
1231      this.setRequestHeaders();
1232
1233      this.body = this.method == 'post' ? (this.options.postBody || params) : null;
1234      this.transport.send(this.body);
1235
1236      /* Force Firefox to handle ready state 4 for synchronous requests */
1237      if (!this.options.asynchronous && this.transport.overrideMimeType)
1238        this.onStateChange();
1239
1240    }
1241    catch (e) {
1242      this.dispatchException(e);
1243    }
1244  },
1245
1246  onStateChange: function() {
1247    var readyState = this.transport.readyState;
1248    if (readyState > 1 && !((readyState == 4) && this._complete))
1249      this.respondToReadyState(this.transport.readyState);
1250  },
1251
1252  setRequestHeaders: function() {
1253    var headers = {
1254      'X-Requested-With': 'XMLHttpRequest',
1255      'X-Prototype-Version': Prototype.Version,
1256      'Accept': 'text/javascript, text/html, application/xml, text/xml, */*'
1257    };
1258
1259    if (this.method == 'post') {
1260      headers['Content-type'] = this.options.contentType +
1261        (this.options.encoding ? '; charset=' + this.options.encoding : '');
1262
1263      /* Force "Connection: close" for older Mozilla browsers to work
1264       * around a bug where XMLHttpRequest sends an incorrect
1265       * Content-length header. See Mozilla Bugzilla #246651.
1266       */
1267      if (this.transport.overrideMimeType &&
1268          (navigator.userAgent.match(/Gecko\/(\d{4})/) || [0,2005])[1] < 2005)
1269            headers['Connection'] = 'close';
1270    }
1271
1272    // user-defined headers
1273    if (typeof this.options.requestHeaders == 'object') {
1274      var extras = this.options.requestHeaders;
1275
1276      if (Object.isFunction(extras.push))
1277        for (var i = 0, length = extras.length; i < length; i += 2)
1278          headers[extras[i]] = extras[i+1];
1279      else
1280        $H(extras).each(function(pair) { headers[pair.key] = pair.value });
1281    }
1282
1283    for (var name in headers)
1284      this.transport.setRequestHeader(name, headers[name]);
1285  },
1286
1287  success: function() {
1288    var status = this.getStatus();
1289    return !status || (status >= 200 && status < 300);
1290  },
1291
1292  getStatus: function() {
1293    try {
1294      return this.transport.status || 0;
1295    } catch (e) { return 0 }
1296  },
1297
1298  respondToReadyState: function(readyState) {
1299    var state = Ajax.Request.Events[readyState], response = new Ajax.Response(this);
1300
1301    if (state == 'Complete') {
1302      try {
1303        this._complete = true;
1304        (this.options['on' + response.status]
1305         || this.options['on' + (this.success() ? 'Success' : 'Failure')]
1306         || Prototype.emptyFunction)(response, response.headerJSON);
1307      } catch (e) {
1308        this.dispatchException(e);
1309      }
1310
1311      var contentType = response.getHeader('Content-type');
1312      if (this.options.evalJS == 'force'
1313          || (this.options.evalJS && this.isSameOrigin() && contentType
1314          && contentType.match(/^\s*(text|application)\/(x-)?(java|ecma)script(;.*)?\s*$/i)))
1315        this.evalResponse();
1316    }
1317
1318    try {
1319      (this.options['on' + state] || Prototype.emptyFunction)(response, response.headerJSON);
1320      Ajax.Responders.dispatch('on' + state, this, response, response.headerJSON);
1321    } catch (e) {
1322      this.dispatchException(e);
1323    }
1324
1325    if (state == 'Complete') {
1326      // avoid memory leak in MSIE: clean up
1327      this.transport.onreadystatechange = Prototype.emptyFunction;
1328    }
1329  },
1330
1331  isSameOrigin: function() {
1332    var m = this.url.match(/^\s*https?:\/\/[^\/]*/);
1333    return !m || (m[0] == '#{protocol}//#{domain}#{port}'.interpolate({
1334      protocol: location.protocol,
1335      domain: document.domain,
1336      port: location.port ? ':' + location.port : ''
1337    }));
1338  },
1339
1340  getHeader: function(name) {
1341    try {
1342      return this.transport.getResponseHeader(name) || null;
1343    } catch (e) { return null }
1344  },
1345
1346  evalResponse: function() {
1347    try {
1348      return eval((this.transport.responseText || '').unfilterJSON());
1349    } catch (e) {
1350      this.dispatchException(e);
1351    }
1352  },
1353
1354  dispatchException: function(exception) {
1355    (this.options.onException || Prototype.emptyFunction)(this, exception);
1356    Ajax.Responders.dispatch('onException', this, exception);
1357  }
1358});
1359
1360Ajax.Request.Events =
1361  ['Uninitialized', 'Loading', 'Loaded', 'Interactive', 'Complete'];
1362
1363Ajax.Response = Class.create({
1364  initialize: function(request){
1365    this.request = request;
1366    var transport  = this.transport  = request.transport,
1367        readyState = this.readyState = transport.readyState;
1368
1369    if((readyState > 2 && !Prototype.Browser.IE) || readyState == 4) {
1370      this.status       = this.getStatus();
1371      this.statusText   = this.getStatusText();
1372      this.responseText = String.interpret(transport.responseText);
1373      this.headerJSON   = this._getHeaderJSON();
1374    }
1375
1376    if(readyState == 4) {
1377      var xml = transport.responseXML;
1378      this.responseXML  = Object.isUndefined(xml) ? null : xml;
1379      this.responseJSON = this._getResponseJSON();
1380    }
1381  },
1382
1383  status:      0,
1384  statusText: '',
1385
1386  getStatus: Ajax.Request.prototype.getStatus,
1387
1388  getStatusText: function() {
1389    try {
1390      return this.transport.statusText || '';
1391    } catch (e) { return '' }
1392  },
1393
1394  getHeader: Ajax.Request.prototype.getHeader,
1395
1396  getAllHeaders: function() {
1397    try {
1398      return this.getAllResponseHeaders();
1399    } catch (e) { return null }
1400  },
1401
1402  getResponseHeader: function(name) {
1403    return this.transport.getResponseHeader(name);
1404  },
1405
1406  getAllResponseHeaders: function() {
1407    return this.transport.getAllResponseHeaders();
1408  },
1409
1410  _getHeaderJSON: function() {
1411    var json = this.getHeader('X-JSON');
1412    if (!json) return null;
1413    json = decodeURIComponent(escape(json));
1414    try {
1415      return json.evalJSON(this.request.options.sanitizeJSON ||
1416        !this.request.isSameOrigin());
1417    } catch (e) {
1418      this.request.dispatchException(e);
1419    }
1420  },
1421
1422  _getResponseJSON: function() {
1423    var options = this.request.options;
1424    if (!options.evalJSON || (options.evalJSON != 'force' &&
1425      !(this.getHeader('Content-type') || '').include('application/json')) ||
1426        this.responseText.blank())
1427          return null;
1428    try {
1429      return this.responseText.evalJSON(options.sanitizeJSON ||
1430        !this.request.isSameOrigin());
1431    } catch (e) {
1432      this.request.dispatchException(e);
1433    }
1434  }
1435});
1436
1437Ajax.Updater = Class.create(Ajax.Request, {
1438  initialize: function($super, container, url, options) {
1439    this.container = {
1440      success: (container.success || container),
1441      failure: (container.failure || (container.success ? null : container))
1442    };
1443
1444    options = Object.clone(options);
1445    var onComplete = options.onComplete;
1446    options.onComplete = (function(response, json) {
1447      this.updateContent(response.responseText);
1448      if (Object.isFunction(onComplete)) onComplete(response, json);
1449    }).bind(this);
1450
1451    $super(url, options);
1452  },
1453
1454  updateContent: function(responseText) {
1455    var receiver = this.container[this.success() ? 'success' : 'failure'],
1456        options = this.options;
1457
1458    if (!options.evalScripts) responseText = responseText.stripScripts();
1459
1460    if (receiver = $(receiver)) {
1461      if (options.insertion) {
1462        if (Object.isString(options.insertion)) {
1463          var insertion = { }; insertion[options.insertion] = responseText;
1464          receiver.insert(insertion);
1465        }
1466        else options.insertion(receiver, responseText);
1467      }
1468      else receiver.update(responseText);
1469    }
1470  }
1471});
1472
1473Ajax.PeriodicalUpdater = Class.create(Ajax.Base, {
1474  initialize: function($super, container, url, options) {
1475    $super(options);
1476    this.onComplete = this.options.onComplete;
1477
1478    this.frequency = (this.options.frequency || 2);
1479    this.decay = (this.options.decay || 1);
1480
1481    this.updater = { };
1482    this.container = container;
1483    this.url = url;
1484
1485    this.start();
1486  },
1487
1488  start: function() {
1489    this.options.onComplete = this.updateComplete.bind(this);
1490    this.onTimerEvent();
1491  },
1492
1493  stop: function() {
1494    this.updater.options.onComplete = undefined;
1495    clearTimeout(this.timer);
1496    (this.onComplete || Prototype.emptyFunction).apply(this, arguments);
1497  },
1498
1499  updateComplete: function(response) {
1500    if (this.options.decay) {
1501      this.decay = (response.responseText == this.lastText ?
1502        this.decay * this.options.decay : 1);
1503
1504      this.lastText = response.responseText;
1505    }
1506    this.timer = this.onTimerEvent.bind(this).delay(this.decay * this.frequency);
1507  },
1508
1509  onTimerEvent: function() {
1510    this.updater = new Ajax.Updater(this.container, this.url, this.options);
1511  }
1512});
1513function $(element) {
1514  if (arguments.length > 1) {
1515    for (var i = 0, elements = [], length = arguments.length; i < length; i++)
1516      elements.push($(arguments[i]));
1517    return elements;
1518  }
1519  if (Object.isString(element))
1520    element = document.getElementById(element);
1521  return Element.extend(element);
1522}
1523
1524if (Prototype.BrowserFeatures.XPath) {
1525  document._getElementsByXPath = function(expression, parentElement) {
1526    var results = [];
1527    var query = document.evaluate(expression, $(parentElement) || document,
1528      null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null);
1529    for (var i = 0, length = query.snapshotLength; i < length; i++)
1530      results.push(Element.extend(query.snapshotItem(i)));
1531    return results;
1532  };
1533}
1534
1535/*--------------------------------------------------------------------------*/
1536
1537if (!window.Node) var Node = { };
1538
1539if (!Node.ELEMENT_NODE) {
1540  // DOM level 2 ECMAScript Language Binding
1541  Object.extend(Node, {
1542    ELEMENT_NODE: 1,
1543    ATTRIBUTE_NODE: 2,
1544    TEXT_NODE: 3,
1545    CDATA_SECTION_NODE: 4,
1546    ENTITY_REFERENCE_NODE: 5,
1547    ENTITY_NODE: 6,
1548    PROCESSING_INSTRUCTION_NODE: 7,
1549    COMMENT_NODE: 8,
1550    DOCUMENT_NODE: 9,
1551    DOCUMENT_TYPE_NODE: 10,
1552    DOCUMENT_FRAGMENT_NODE: 11,
1553    NOTATION_NODE: 12
1554  });
1555}
1556
1557(function() {
1558  var element = this.Element;
1559  this.Element = function(tagName, attributes) {
1560    attributes = attributes || { };
1561    tagName = tagName.toLowerCase();
1562    var cache = Element.cache;
1563    if (Prototype.Browser.IE && attributes.name) {
1564      tagName = '<' + tagName + ' name="' + attributes.name + '">';
1565      delete attributes.name;
1566      return Element.writeAttribute(document.createElement(tagName), attributes);
1567    }
1568    if (!cache[tagName]) cache[tagName] = Element.extend(document.createElement(tagName));
1569    return Element.writeAttribute(cache[tagName].cloneNode(false), attributes);
1570  };
1571  Object.extend(this.Element, element || { });
1572  if (element) this.Element.prototype = element.prototype;
1573}).call(window);
1574
1575Element.cache = { };
1576
1577Element.Methods = {
1578  visible: function(element) {
1579    return $(element).style.display != 'none';
1580  },
1581
1582  toggle: function(element) {
1583    element = $(element);
1584    Element[Element.visible(element) ? 'hide' : 'show'](element);
1585    return element;
1586  },
1587
1588  hide: function(element) {
1589    element = $(element);
1590    element.style.display = 'none';
1591    return element;
1592  },
1593
1594  show: function(element) {
1595    element = $(element);
1596    element.style.display = '';
1597    return element;
1598  },
1599
1600  remove: function(element) {
1601    element = $(element);
1602    element.parentNode.removeChild(element);
1603    return element;
1604  },
1605
1606  update: function(element, content) {
1607    element = $(element);
1608    if (content && content.toElement) content = content.toElement();
1609    if (Object.isElement(content)) return element.update().insert(content);
1610    content = Object.toHTML(content);
1611    element.innerHTML = content.stripScripts();
1612    content.evalScripts.bind(content).defer();
1613    return element;
1614  },
1615
1616  replace: function(element, content) {
1617    element = $(element);
1618    if (content && content.toElement) content = content.toElement();
1619    else if (!Object.isElement(content)) {
1620      content = Object.toHTML(content);
1621      var range = element.ownerDocument.createRange();
1622      range.selectNode(element);
1623      content.evalScripts.bind(content).defer();
1624      content = range.createContextualFragment(content.stripScripts());
1625    }
1626    element.parentNode.replaceChild(content, element);
1627    return element;
1628  },
1629
1630  insert: function(element, insertions) {
1631    element = $(element);
1632
1633    if (Object.isString(insertions) || Object.isNumber(insertions) ||
1634        Object.isElement(insertions) || (insertions && (insertions.toElement || insertions.toHTML)))
1635          insertions = {bottom:insertions};
1636
1637    var content, insert, tagName, childNodes;
1638
1639    for (var position in insertions) {
1640      content  = insertions[position];
1641      position = position.toLowerCase();
1642      insert = Element._insertionTranslations[position];
1643
1644      if (content && content.toElement) content = content.toElement();
1645      if (Object.isElement(content)) {
1646        insert(element, content);
1647        continue;
1648      }
1649
1650      content = Object.toHTML(content);
1651
1652      tagName = ((position == 'before' || position == 'after')
1653        ? element.parentNode : element).tagName.toUpperCase();
1654
1655      childNodes = Element._getContentFromAnonymousElement(tagName, content.stripScripts());
1656
1657      if (position == 'top' || position == 'after') childNodes.reverse();
1658      childNodes.each(insert.curry(element));
1659
1660      content.evalScripts.bind(content).defer();
1661    }
1662
1663    return element;
1664  },
1665
1666  wrap: function(element, wrapper, attributes) {
1667    element = $(element);
1668    if (Object.isElement(wrapper))
1669      $(wrapper).writeAttribute(attributes || { });
1670    else if (Object.isString(wrapper)) wrapper = new Element(wrapper, attributes);
1671    else wrapper = new Element('div', wrapper);
1672    if (element.parentNode)
1673      element.parentNode.replaceChild(wrapper, element);
1674    wrapper.appendChild(element);
1675    return wrapper;
1676  },
1677
1678  inspect: function(element) {
1679    element = $(element);
1680    var result = '<' + element.tagName.toLowerCase();
1681    $H({'id': 'id', 'className': 'class'}).each(function(pair) {
1682      var property = pair.first(), attribute = pair.last();
1683      var value = (element[property] || '').toString();
1684      if (value) result += ' ' + attribute + '=' + value.inspect(true);
1685    });
1686    return result + '>';
1687  },
1688
1689  recursivelyCollect: function(element, property) {
1690    element = $(element);
1691    var elements = [];
1692    while (element = element[property])
1693      if (element.nodeType == 1)
1694        elements.push(Element.extend(element));
1695    return elements;
1696  },
1697
1698  ancestors: function(element) {
1699    return $(element).recursivelyCollect('parentNode');
1700  },
1701
1702  descendants: function(element) {
1703    return $(element).select("*");
1704  },
1705
1706  firstDescendant: function(element) {
1707    element = $(element).firstChild;
1708    while (element && element.nodeType != 1) element = element.nextSibling;
1709    return $(element);
1710  },
1711
1712  immediateDescendants: function(element) {
1713    if (!(element = $(element).firstChild)) return [];
1714    while (element && element.nodeType != 1) element = element.nextSibling;
1715    if (element) return [element].concat($(element).nextSiblings());
1716    return [];
1717  },
1718
1719  previousSiblings: function(element) {
1720    return $(element).recursivelyCollect('previousSibling');
1721  },
1722
1723  nextSiblings: function(element) {
1724    return $(element).recursivelyCollect('nextSibling');
1725  },
1726
1727  siblings: function(element) {
1728    element = $(element);
1729    return element.previousSiblings().reverse().concat(element.nextSiblings());
1730  },
1731
1732  match: function(element, selector) {
1733    if (Object.isString(selector))
1734      selector = new Selector(selector);
1735    return selector.match($(element));
1736  },
1737
1738  up: function(element, expression, index) {
1739    element = $(element);
1740    if (arguments.length == 1) return $(element.parentNode);
1741    var ancestors = element.ancestors();
1742    return Object.isNumber(expression) ? ancestors[expression] :
1743      Selector.findElement(ancestors, expression, index);
1744  },
1745
1746  down: function(element, expression, index) {
1747    element = $(element);
1748    if (arguments.length == 1) return element.firstDescendant();
1749    return Object.isNumber(expression) ? element.descendants()[expression] :
1750      Element.select(element, expression)[index || 0];
1751  },
1752
1753  previous: function(element, expression, index) {
1754    element = $(element);
1755    if (arguments.length == 1) return $(Selector.handlers.previousElementSibling(element));
1756    var previousSiblings = element.previousSiblings();
1757    return Object.isNumber(expression) ? previousSiblings[expression] :
1758      Selector.findElement(previousSiblings, expression, index);
1759  },
1760
1761  next: function(element, expression, index) {
1762    element = $(element);
1763    if (arguments.length == 1) return $(Selector.handlers.nextElementSibling(element));
1764    var nextSiblings = element.nextSiblings();
1765    return Object.isNumber(expression) ? nextSiblings[expression] :
1766      Selector.findElement(nextSiblings, expression, index);
1767  },
1768
1769  select: function() {
1770    var args = $A(arguments), element = $(args.shift());
1771    return Selector.findChildElements(element, args);
1772  },
1773
1774  adjacent: function() {
1775    var args = $A(arguments), element = $(args.shift());
1776    return Selector.findChildElements(element.parentNode, args).without(element);
1777  },
1778
1779  identify: function(element) {
1780    element = $(element);
1781    var id = element.readAttribute('id'), self = arguments.callee;
1782    if (id) return id;
1783    do { id = 'anonymous_element_' + self.counter++ } while ($(id));
1784    element.writeAttribute('id', id);
1785    return id;
1786  },
1787
1788  readAttribute: function(element, name) {
1789    element = $(element);
1790    if (Prototype.Browser.IE) {
1791      var t = Element._attributeTranslations.read;
1792      if (t.values[name]) return t.values[name](element, name);
1793      if (t.names[name]) name = t.names[name];
1794      if (name.include(':')) {
1795        return (!element.attributes || !element.attributes[name]) ? null :
1796         element.attributes[name].value;
1797      }
1798    }
1799    return element.getAttribute(name);
1800  },
1801
1802  writeAttribute: function(element, name, value) {
1803    element = $(element);
1804    var attributes = { }, t = Element._attributeTranslations.write;
1805
1806    if (typeof name == 'object') attributes = name;
1807    else attributes[name] = Object.isUndefined(value) ? true : value;
1808
1809    for (var attr in attributes) {
1810      name = t.names[attr] || attr;
1811      value = attributes[attr];
1812      if (t.values[attr]) name = t.values[attr](element, value);
1813      if (value === false || value === null)
1814        element.removeAttribute(name);
1815      else if (value === true)
1816        element.setAttribute(name, name);
1817      else element.setAttribute(name, value);
1818    }
1819    return element;
1820  },
1821
1822  getHeight: function(element) {
1823    return $(element).getDimensions().height;
1824  },
1825
1826  getWidth: function(element) {
1827    return $(element).getDimensions().width;
1828  },
1829
1830  classNames: function(element) {
1831    return new Element.ClassNames(element);
1832  },
1833
1834  hasClassName: function(element, className) {
1835    if (!(element = $(element))) return;
1836    var elementClassName = element.className;
1837    return (elementClassName.length > 0 && (elementClassName == className ||
1838      new RegExp("(^|\\s)" + className + "(\\s|$)").test(elementClassName)));
1839  },
1840
1841  addClassName: function(element, className) {
1842    if (!(element = $(element))) return;
1843    if (!element.hasClassName(className))
1844      element.className += (element.className ? ' ' : '') + className;
1845    return element;
1846  },
1847
1848  removeClassName: function(element, className) {
1849    if (!(element = $(element))) return;
1850    element.className = element.className.replace(
1851      new RegExp("(^|\\s+)" + className + "(\\s+|$)"), ' ').strip();
1852    return element;
1853  },
1854
1855  toggleClassName: function(element, className) {
1856    if (!(element = $(element))) return;
1857    return element[element.hasClassName(className) ?
1858      'removeClassName' : 'addClassName'](className);
1859  },
1860
1861  // removes whitespace-only text node children
1862  cleanWhitespace: function(element) {
1863    element = $(element);
1864    var node = element.firstChild;
1865    while (node) {
1866      var nextNode = node.nextSibling;
1867      if (node.nodeType == 3 && !/\S/.test(node.nodeValue))
1868        element.removeChild(node);
1869      node = nextNode;
1870    }
1871    return element;
1872  },
1873
1874  empty: function(element) {
1875    return $(element).innerHTML.blank();
1876  },
1877
1878  descendantOf: function(element, ancestor) {
1879    element = $(element), ancestor = $(ancestor);
1880
1881    if (element.compareDocumentPosition)
1882      return (element.compareDocumentPosition(ancestor) & 8) === 8;
1883
1884    if (ancestor.contains)
1885      return ancestor.contains(element) && ancestor !== element;
1886
1887    while (element = element.parentNode)
1888      if (element == ancestor) return true;
1889
1890    return false;
1891  },
1892
1893  scrollTo: function(element) {
1894    element = $(element);
1895    var pos = element.cumulativeOffset();
1896    window.scrollTo(pos[0], pos[1]);
1897    return element;
1898  },
1899
1900  getStyle: function(element, style) {
1901    element = $(element);
1902    style = style == 'float' ? 'cssFloat' : style.camelize();
1903    var value = element.style[style];
1904    if (!value || value == 'auto') {
1905      var css = document.defaultView.getComputedStyle(element, null);
1906      value = css ? css[style] : null;
1907    }
1908    if (style == 'opacity') return value ? parseFloat(value) : 1.0;
1909    return value == 'auto' ? null : value;
1910  },
1911
1912  getOpacity: function(element) {
1913    return $(element).getStyle('opacity');
1914  },
1915
1916  setStyle: function(element, styles) {
1917    element = $(element);
1918    var elementStyle = element.style, match;
1919    if (Object.isString(styles)) {
1920      element.style.cssText += ';' + styles;
1921      return styles.include('opacity') ?
1922        element.setOpacity(styles.match(/opacity:\s*(\d?\.?\d*)/)[1]) : element;
1923    }
1924    for (var property in styles)
1925      if (property == 'opacity') element.setOpacity(styles[property]);
1926      else
1927        elementStyle[(property == 'float' || property == 'cssFloat') ?
1928          (Object.isUndefined(elementStyle.styleFloat) ? 'cssFloat' : 'styleFloat') :
1929            property] = styles[property];
1930
1931    return element;
1932  },
1933
1934  setOpacity: function(element, value) {
1935    element = $(element);
1936    element.style.opacity = (value == 1 || value === '') ? '' :
1937      (value < 0.00001) ? 0 : value;
1938    return element;
1939  },
1940
1941  getDimensions: function(element) {
1942    element = $(element);
1943    var display = element.getStyle('display');
1944    if (display != 'none' && display != null) // Safari bug
1945      return {width: element.offsetWidth, height: element.offsetHeight};
1946
1947    // All *Width and *Height properties give 0 on elements with display none,
1948    // so enable the element temporarily
1949    var els = element.style;
1950    var originalVisibility = els.visibility;
1951    var originalPosition = els.position;
1952    var originalDisplay = els.display;
1953    els.visibility = 'hidden';
1954    els.position = 'absolute';
1955    els.display = 'block';
1956    var originalWidth = element.clientWidth;
1957    var originalHeight = element.clientHeight;
1958    els.display = originalDisplay;
1959    els.position = originalPosition;
1960    els.visibility = originalVisibility;
1961    return {width: originalWidth, height: originalHeight};
1962  },
1963
1964  makePositioned: function(element) {
1965    element = $(element);
1966    var pos = Element.getStyle(element, 'position');
1967    if (pos == 'static' || !pos) {
1968      element._madePositioned = true;
1969      element.style.position = 'relative';
1970      // Opera returns the offset relative to the positioning context, when an
1971      // element is position relative but top and left have not been defined
1972      if (Prototype.Browser.Opera) {
1973        element.style.top = 0;
1974        element.style.left = 0;
1975      }
1976    }
1977    return element;
1978  },
1979
1980  undoPositioned: function(element) {
1981    element = $(element);
1982    if (element._madePositioned) {
1983      element._madePositioned = undefined;
1984      element.style.position =
1985        element.style.top =
1986        element.style.left =
1987        element.style.bottom =
1988        element.style.right = '';
1989    }
1990    return element;
1991  },
1992
1993  makeClipping: function(element) {
1994    element = $(element);
1995    if (element._overflow) return element;
1996    element._overflow = Element.getStyle(element, 'overflow') || 'auto';
1997    if (element._overflow !== 'hidden')
1998      element.style.overflow = 'hidden';
1999    return element;
2000  },
2001
2002  undoClipping: function(element) {
2003    element = $(element);
2004    if (!element._overflow) return element;
2005    element.style.overflow = element._overflow == 'auto' ? '' : element._overflow;
2006    element._overflow = null;
2007    return element;
2008  },
2009
2010  cumulativeOffset: function(element) {
2011    var valueT = 0, valueL = 0;
2012    do {
2013      valueT += element.offsetTop  || 0;
2014      valueL += element.offsetLeft || 0;
2015      element = element.offsetParent;
2016    } while (element);
2017    return Element._returnOffset(valueL, valueT);
2018  },
2019
2020  positionedOffset: function(element) {
2021    var valueT = 0, valueL = 0;
2022    do {
2023      valueT += element.offsetTop  || 0;
2024      valueL += element.offsetLeft || 0;
2025      element = element.offsetParent;
2026      if (element) {
2027        if (element.tagName.toUpperCase() == 'BODY') break;
2028        var p = Element.getStyle(element, 'position');
2029        if (p !== 'static') break;
2030      }
2031    } while (element);
2032    return Element._returnOffset(valueL, valueT);
2033  },
2034
2035  absolutize: function(element) {
2036    element = $(element);
2037    if (element.getStyle('position') == 'absolute') return element;
2038    // Position.prepare(); // To be done manually by Scripty when it needs it.
2039
2040    var offsets = element.positionedOffset();
2041    var top     = offsets[1];
2042    var left    = offsets[0];
2043    var width   = element.clientWidth;
2044    var height  = element.clientHeight;
2045
2046    element._originalLeft   = left - parseFloat(element.style.left  || 0);
2047    element._originalTop    = top  - parseFloat(element.style.top || 0);
2048    element._originalWidth  = element.style.width;
2049    element._originalHeight = element.style.height;
2050
2051    element.style.position = 'absolute';
2052    element.style.top    = top + 'px';
2053    element.style.left   = left + 'px';
2054    element.style.width  = width + 'px';
2055    element.style.height = height + 'px';
2056    return element;
2057  },
2058
2059  relativize: function(element) {
2060    element = $(element);
2061    if (element.getStyle('position') == 'relative') return element;
2062    // Position.prepare(); // To be done manually by Scripty when it needs it.
2063
2064    element.style.position = 'relative';
2065    var top  = parseFloat(element.style.top  || 0) - (element._originalTop || 0);
2066    var left = parseFloat(element.style.left || 0) - (element._originalLeft || 0);
2067
2068    element.style.top    = top + 'px';
2069    element.style.left   = left + 'px';
2070    element.style.height = element._originalHeight;
2071    element.style.width  = element._originalWidth;
2072    return element;
2073  },
2074
2075  cumulativeScrollOffset: function(element) {
2076    var valueT = 0, valueL = 0;
2077    do {
2078      valueT += element.scrollTop  || 0;
2079      valueL += element.scrollLeft || 0;
2080      element = element.parentNode;
2081    } while (element);
2082    return Element._returnOffset(valueL, valueT);
2083  },
2084
2085  getOffsetParent: function(element) {
2086    if (element.offsetParent) return $(element.offsetParent);
2087    if (element == document.body) return $(element);
2088    if(element.tagName.toUpperCase()=='HTML') //for IE6,7
2089        return $(document.body);                //
2090
2091    while ((element = element.parentNode) && element != document.body)
2092      if (Element.getStyle(element, 'position') != 'static')
2093        return $(element);
2094
2095    return $(document.body);
2096  },
2097
2098  viewportOffset: function(forElement) {
2099    var valueT = 0, valueL = 0;
2100
2101    var element = forElement;
2102    do {
2103      valueT += element.offsetTop  || 0;
2104      valueL += element.offsetLeft || 0;
2105
2106      // Safari fix
2107      if (element.offsetParent == document.body &&
2108        Element.getStyle(element, 'position') == 'absolute') break;
2109
2110    } while (element = element.offsetParent);
2111
2112    element = forElement;
2113    do {
2114      if (!Prototype.Browser.Opera || (element.tagName && (element.tagName.toUpperCase() == 'BODY'))) {
2115        valueT -= element.scrollTop  || 0;
2116        valueL -= element.scrollLeft || 0;
2117      }
2118    } while (element = element.parentNode);
2119
2120    return Element._returnOffset(valueL, valueT);
2121  },
2122
2123  clonePosition: function(element, source) {
2124    var options = Object.extend({
2125      setLeft:    true,
2126      setTop:     true,
2127      setWidth:   true,
2128      setHeight:  true,
2129      offsetTop:  0,
2130      offsetLeft: 0
2131    }, arguments[2] || { });
2132
2133    // find page position of source
2134    source = $(source);
2135    var p = source.viewportOffset();
2136
2137    // find coordinate system to use
2138    element = $(element);
2139    var delta = [0, 0];
2140    var parent = null;
2141    // delta [0,0] will do fine with position: fixed elements,
2142    // position:absolute needs offsetParent deltas
2143    if (Element.getStyle(element, 'position') == 'absolute') {
2144      parent = element.getOffsetParent();
2145      delta = parent.viewportOffset();
2146    }
2147
2148    // correct by body offsets (fixes Safari)
2149    if (parent == document.body) {
2150      delta[0] -= document.body.offsetLeft;
2151      delta[1] -= document.body.offsetTop;
2152    }
2153
2154    // set position
2155    if (options.setLeft)   element.style.left  = (p[0] - delta[0] + options.offsetLeft) + 'px';
2156    if (options.setTop)    element.style.top   = (p[1] - delta[1] + options.offsetTop) + 'px';
2157    if (options.setWidth)  element.style.width = source.offsetWidth + 'px';
2158    if (options.setHeight) element.style.height = source.offsetHeight + 'px';
2159    return element;
2160  }
2161};
2162
2163Element.Methods.identify.counter = 1;
2164
2165Object.extend(Element.Methods, {
2166  getElementsBySelector: Element.Methods.select,
2167  childElements: Element.Methods.immediateDescendants
2168});
2169
2170Element._attributeTranslations = {
2171  write: {
2172    names: {
2173      className: 'class',
2174      htmlFor:   'for'
2175    },
2176    values: { }
2177  }
2178};
2179
2180if (Prototype.Browser.Opera) {
2181  Element.Methods.getStyle = Element.Methods.getStyle.wrap(
2182    function(proceed, element, style) {
2183      switch (style) {
2184        case 'left': case 'top': case 'right': case 'bottom':
2185          if (proceed(element, 'position') === 'static') return null;
2186        case 'height': case 'width':
2187          // returns '0px' for hidden elements; we want it to return null
2188          if (!Element.visible(element)) return null;
2189
2190          // returns the border-box dimensions rather than the content-box
2191          // dimensions, so we subtract padding and borders from the value
2192          var dim = parseInt(proceed(element, style), 10);
2193
2194          if (dim !== element['offset' + style.capitalize()])
2195            return dim + 'px';
2196
2197          var properties;
2198          if (style === 'height') {
2199            properties = ['border-top-width', 'padding-top',
2200             'padding-bottom', 'border-bottom-width'];
2201          }
2202          else {
2203            properties = ['border-left-width', 'padding-left',
2204             'padding-right', 'border-right-width'];
2205          }
2206          return properties.inject(dim, function(memo, property) {
2207            var val = proceed(element, property);
2208            return val === null ? memo : memo - parseInt(val, 10);
2209          }) + 'px';
2210        default: return proceed(element, style);
2211      }
2212    }
2213  );
2214
2215  Element.Methods.readAttribute = Element.Methods.readAttribute.wrap(
2216    function(proceed, element, attribute) {
2217      if (attribute === 'title') return element.title;
2218      return proceed(element, attribute);
2219    }
2220  );
2221}
2222
2223else if (Prototype.Browser.IE) {
2224  // IE doesn't report offsets correctly for static elements, so we change them
2225  // to "relative" to get the values, then change them back.
2226  Element.Methods.getOffsetParent = Element.Methods.getOffsetParent.wrap(
2227    function(proceed, element) {
2228      element = $(element);
2229      // IE throws an error if element is not in document
2230      try { element.offsetParent }
2231      catch(e) { return $(document.body) }
2232      var position = element.getStyle('position');
2233      if (position !== 'static') return proceed(element);
2234      element.setStyle({ position: 'relative' });
2235      var value = proceed(element);
2236      element.setStyle({ position: position });
2237      return value;
2238    }
2239  );
2240
2241  $w('positionedOffset viewportOffset').each(function(method) {
2242    Element.Methods[method] = Element.Methods[method].wrap(
2243      function(proceed, element) {
2244        element = $(element);
2245        try { element.offsetParent }
2246        catch(e) { return Element._returnOffset(0,0) }
2247        var position = element.getStyle('position');
2248        if (position !== 'static') return proceed(element);
2249        // Trigger hasLayout on the offset parent so that IE6 reports
2250        // accurate offsetTop and offsetLeft values for position: fixed.
2251        var offsetParent = element.getOffsetParent();
2252        if (offsetParent && offsetParent.getStyle('position') === 'fixed')
2253          offsetParent.setStyle({ zoom: 1 });
2254        element.setStyle({ position: 'relative' });
2255        var value = proceed(element);
2256        element.setStyle({ position: position });
2257        return value;
2258      }
2259    );
2260  });
2261
2262  Element.Methods.cumulativeOffset = Element.Methods.cumulativeOffset.wrap(
2263    function(proceed, element) {
2264      try { element.offsetParent }
2265      catch(e) { return Element._returnOffset(0,0) }
2266      return proceed(element);
2267    }
2268  );
2269
2270  Element.Methods.getStyle = function(element, style) {
2271    element = $(element);
2272    style = (style == 'float' || style == 'cssFloat') ? 'styleFloat' : style.camelize();
2273    var value = element.style[style];
2274    if (!value && element.currentStyle) value = element.currentStyle[style];
2275
2276    if (style == 'opacity') {
2277      if (value = (element.getStyle('filter') || '').match(/alpha\(opacity=(.*)\)/))
2278        if (value[1]) return parseFloat(value[1]) / 100;
2279      return 1.0;
2280    }
2281
2282    if (value == 'auto') {
2283      if ((style == 'width' || style == 'height') && (element.getStyle('display') != 'none'))
2284        return element['offset' + style.capitalize()] + 'px';
2285      return null;
2286    }
2287    return value;
2288  };
2289
2290  Element.Methods.setOpacity = function(element, value) {
2291    function stripAlpha(filter){
2292      return filter.replace(/alpha\([^\)]*\)/gi,'');
2293    }
2294    element = $(element);
2295    var currentStyle = element.currentStyle;
2296    if ((currentStyle && !currentStyle.hasLayout) ||
2297      (!currentStyle && element.style.zoom == 'normal'))
2298        element.style.zoom = 1;
2299
2300    var filter = element.getStyle('filter'), style = element.style;
2301    if (value == 1 || value === '') {
2302      (filter = stripAlpha(filter)) ?
2303        style.filter = filter : style.removeAttribute('filter');
2304      return element;
2305    } else if (value < 0.00001) value = 0;
2306    style.filter = stripAlpha(filter) +
2307      'alpha(opacity=' + (value * 100) + ')';
2308    return element;
2309  };
2310
2311  Element._attributeTranslations = {
2312    read: {
2313      names: {
2314        'class': 'className',
2315        'for':   'htmlFor'
2316      },
2317      values: {
2318        _getAttr: function(element, attribute) {
2319          return element.getAttribute(attribute, 2);
2320        },
2321        _getAttrNode: function(element, attribute) {
2322          var node = element.getAttributeNode(attribute);
2323          return node ? node.value : "";
2324        },
2325        _getEv: function(element, attribute) {
2326          attribute = element.getAttribute(attribute);
2327          return attribute ? attribute.toString().slice(23, -2) : null;
2328        },
2329        _flag: function(element, attribute) {
2330          return $(element).hasAttribute(attribute) ? attribute : null;
2331        },
2332        style: function(element) {
2333          return element.style.cssText.toLowerCase();
2334        },
2335        title: function(element) {
2336          return element.title;
2337        }
2338      }
2339    }
2340  };
2341
2342  Element._attributeTranslations.write = {
2343    names: Object.extend({
2344      cellpadding: 'cellPadding',
2345      cellspacing: 'cellSpacing'
2346    }, Element._attributeTranslations.read.names),
2347    values: {
2348      checked: function(element, value) {
2349        element.checked = !!value;
2350      },
2351
2352      style: function(element, value) {
2353        element.style.cssText = value ? value : '';
2354      }
2355    }
2356  };
2357
2358  Element._attributeTranslations.has = {};
2359
2360  $w('colSpan rowSpan vAlign dateTime accessKey tabIndex ' +
2361      'encType maxLength readOnly longDesc frameBorder').each(function(attr) {
2362    Element._attributeTranslations.write.names[attr.toLowerCase()] = attr;
2363    Element._attributeTranslations.has[attr.toLowerCase()] = attr;
2364  });
2365
2366  (function(v) {
2367    Object.extend(v, {
2368      href:        v._getAttr,
2369      src:         v._getAttr,
2370      type:        v._getAttr,
2371      action:      v._getAttrNode,
2372      disabled:    v._flag,
2373      checked:     v._flag,
2374      readonly:    v._flag,
2375      multiple:    v._flag,
2376      onload:      v._getEv,
2377      onunload:    v._getEv,
2378      onclick:     v._getEv,
2379      ondblclick:  v._getEv,
2380      onmousedown: v._getEv,
2381      onmouseup:   v._getEv,
2382      onmouseover: v._getEv,
2383      onmousemove: v._getEv,
2384      onmouseout:  v._getEv,
2385      onfocus:     v._getEv,
2386      onblur:      v._getEv,
2387      onkeypress:  v._getEv,
2388      onkeydown:   v._getEv,
2389      onkeyup:     v._getEv,
2390      onsubmit:    v._getEv,
2391      onreset:     v._getEv,
2392      onselect:    v._getEv,
2393      onchange:    v._getEv
2394    });
2395  })(Element._attributeTranslations.read.values);
2396}
2397
2398else if (Prototype.Browser.Gecko && /rv:1\.8\.0/.test(navigator.userAgent)) {
2399  Element.Methods.setOpacity = function(element, value) {
2400    element = $(element);
2401    element.style.opacity = (value == 1) ? 0.999999 :
2402      (value === '') ? '' : (value < 0.00001) ? 0 : value;
2403    return element;
2404  };
2405}
2406
2407else if (Prototype.Browser.WebKit) {
2408  Element.Methods.setOpacity = function(element, value) {
2409    element = $(element);
2410    element.style.opacity = (value == 1 || value === '') ? '' :
2411      (value < 0.00001) ? 0 : value;
2412
2413    if (value == 1)
2414      if(element.tagName.toUpperCase() == 'IMG' && element.width) {
2415        element.width++; element.width--;
2416      } else try {
2417        var n = document.createTextNode(' ');
2418        element.appendChild(n);
2419        element.removeChild(n);
2420      } catch (e) { }
2421
2422    return element;
2423  };
2424
2425  // Safari returns margins on body which is incorrect if the child is absolutely
2426  // positioned.  For performance reasons, redefine Element#cumulativeOffset for
2427  // KHTML/WebKit only.
2428  Element.Methods.cumulativeOffset = function(element) {
2429    var valueT = 0, valueL = 0;
2430    do {
2431      valueT += element.offsetTop  || 0;
2432      valueL += element.offsetLeft || 0;
2433      if (element.offsetParent == document.body)
2434        if (Element.getStyle(element, 'position') == 'absolute') break;
2435
2436      element = element.offsetParent;
2437    } while (element);
2438
2439    return Element._returnOffset(valueL, valueT);
2440  };
2441}
2442
2443if (Prototype.Browser.IE || Prototype.Browser.Opera) {
2444  // IE and Opera are missing .innerHTML support for TABLE-related and SELECT elements
2445  Element.Methods.update = function(element, content) {
2446    element = $(element);
2447
2448    if (content && content.toElement) content = content.toElement();
2449    if (Object.isElement(content)) return element.update().insert(content);
2450
2451    content = Object.toHTML(content);
2452    var tagName = element.tagName.toUpperCase();
2453
2454    if (tagName in Element._insertionTranslations.tags) {
2455      $A(element.childNodes).each(function(node) { element.removeChild(node) });
2456      Element._getContentFromAnonymousElement(tagName, content.stripScripts())
2457        .each(function(node) { element.appendChild(node) });
2458    }
2459    else element.innerHTML = content.stripScripts();
2460
2461    content.evalScripts.bind(content).defer();
2462    return element;
2463  };
2464}
2465
2466if ('outerHTML' in document.createElement('div')) {
2467  Element.Methods.replace = function(element, content) {
2468    element = $(element);
2469
2470    if (content && content.toElement) content = content.toElement();
2471    if (Object.isElement(content)) {
2472      element.parentNode.replaceChild(content, element);
2473      return element;
2474    }
2475
2476    content = Object.toHTML(content);
2477    var parent = element.parentNode, tagName = parent.tagName.toUpperCase();
2478
2479    if (Element._insertionTranslations.tags[tagName]) {
2480      var nextSibling = element.next();
2481      var fragments = Element._getContentFromAnonymousElement(tagName, content.stripScripts());
2482      parent.removeChild(element);
2483      if (nextSibling)
2484        fragments.each(function(node) { parent.insertBefore(node, nextSibling) });
2485      else
2486        fragments.each(function(node) { parent.appendChild(node) });
2487    }
2488    else element.outerHTML = content.stripScripts();
2489
2490    content.evalScripts.bind(content).defer();
2491    return element;
2492  };
2493}
2494
2495Element._returnOffset = function(l, t) {
2496  var result = [l, t];
2497  result.left = l;
2498  result.top = t;
2499  return result;
2500};
2501
2502Element._getContentFromAnonymousElement = function(tagName, html) {
2503  var div = new Element('div'), t = Element._insertionTranslations.tags[tagName];
2504  if (t) {
2505    div.innerHTML = t[0] + html + t[1];
2506    t[2].times(function() { div = div.firstChild });
2507  } else div.innerHTML = html;
2508  return $A(div.childNodes);
2509};
2510
2511Element._insertionTranslations = {
2512  before: function(element, node) {
2513    element.parentNode.insertBefore(node, element);
2514  },
2515  top: function(element, node) {
2516    element.insertBefore(node, element.firstChild);
2517  },
2518  bottom: function(element, node) {
2519    element.appendChild(node);
2520  },
2521  after: function(element, node) {
2522    element.parentNode.insertBefore(node, element.nextSibling);
2523  },
2524  tags: {
2525    TABLE:  ['<table>',                '</table>',                   1],
2526    TBODY:  ['<table><tbody>',         '</tbody></table>',           2],
2527    TR:     ['<table><tbody><tr>',     '</tr></tbody></table>',      3],
2528    TD:     ['<table><tbody><tr><td>', '</td></tr></tbody></table>', 4],
2529    SELECT: ['<select>',               '</select>',                  1]
2530  }
2531};
2532
2533(function() {
2534  Object.extend(this.tags, {
2535    THEAD: this.tags.TBODY,
2536    TFOOT: this.tags.TBODY,
2537    TH:    this.tags.TD
2538  });
2539}).call(Element._insertionTranslations);
2540
2541Element.Methods.Simulated = {
2542  hasAttribute: function(element, attribute) {
2543    attribute = Element._attributeTranslations.has[attribute] || attribute;
2544    var node = $(element).getAttributeNode(attribute);
2545    return !!(node && node.specified);
2546  }
2547};
2548
2549Element.Methods.ByTag = { };
2550
2551Object.extend(Element, Element.Methods);
2552
2553if (!Prototype.BrowserFeatures.ElementExtensions &&
2554    document.createElement('div')['__proto__']) {
2555  window.HTMLElement = { };
2556  window.HTMLElement.prototype = document.createElement('div')['__proto__'];
2557  Prototype.BrowserFeatures.ElementExtensions = true;
2558}
2559
2560Element.extend = (function() {
2561  if (Prototype.BrowserFeatures.SpecificElementExtensions)
2562    return Prototype.K;
2563
2564  var Methods = { }, ByTag = Element.Methods.ByTag;
2565
2566  var extend = Object.extend(function(element) {
2567    if (!element || element._extendedByPrototype ||
2568        element.nodeType != 1 || element == window) return element;
2569
2570    var methods = Object.clone(Methods),
2571      tagName = element.tagName.toUpperCase(), property, value;
2572
2573    // extend methods for specific tags
2574    if (ByTag[tagName]) Object.extend(methods, ByTag[tagName]);
2575
2576    for (property in methods) {
2577      value = methods[property];
2578      if (Object.isFunction(value) && !(property in element))
2579        element[property] = value.methodize();
2580    }
2581
2582    element._extendedByPrototype = Prototype.emptyFunction;
2583    return element;
2584
2585  }, {
2586    refresh: function() {
2587      // extend methods for all tags (Safari doesn't need this)
2588      if (!Prototype.BrowserFeatures.ElementExtensions) {
2589        Object.extend(Methods, Element.Methods);
2590        Object.extend(Methods, Element.Methods.Simulated);
2591      }
2592    }
2593  });
2594
2595  extend.refresh();
2596  return extend;
2597})();
2598
2599Element.hasAttribute = function(element, attribute) {
2600  if (element.hasAttribute) return element.hasAttribute(attribute);
2601  return Element.Methods.Simulated.hasAttribute(element, attribute);
2602};
2603
2604Element.addMethods = function(methods) {
2605  var F = Prototype.BrowserFeatures, T = Element.Methods.ByTag;
2606
2607  if (!methods) {
2608    Object.extend(Form, Form.Methods);
2609    Object.extend(Form.Element, Form.Element.Methods);
2610    Object.extend(Element.Methods.ByTag, {
2611      "FORM":     Object.clone(Form.Methods),
2612      "INPUT":    Object.clone(Form.Element.Methods),
2613      "SELECT":   Object.clone(Form.Element.Methods),
2614      "TEXTAREA": Object.clone(Form.Element.Methods)
2615    });
2616  }
2617
2618  if (arguments.length == 2) {
2619    var tagName = methods;
2620    methods = arguments[1];
2621  }
2622
2623  if (!tagName) Object.extend(Element.Methods, methods || { });
2624  else {
2625    if (Object.isArray(tagName)) tagName.each(extend);
2626    else extend(tagName);
2627  }
2628
2629  function extend(tagName) {
2630    tagName = tagName.toUpperCase();
2631    if (!Element.Methods.ByTag[tagName])
2632      Element.Methods.ByTag[tagName] = { };
2633    Object.extend(Element.Methods.ByTag[tagName], methods);
2634  }
2635
2636  function copy(methods, destination, onlyIfAbsent) {
2637    onlyIfAbsent = onlyIfAbsent || false;
2638    for (var property in methods) {
2639      var value = methods[property];
2640      if (!Object.isFunction(value)) continue;
2641      if (!onlyIfAbsent || !(property in destination))
2642        destination[property] = value.methodize();
2643    }
2644  }
2645
2646  function findDOMClass(tagName) {
2647    var klass;
2648    var trans = {
2649      "OPTGROUP": "OptGroup", "TEXTAREA": "TextArea", "P": "Paragraph",
2650      "FIELDSET": "FieldSet", "UL": "UList", "OL": "OList", "DL": "DList",
2651      "DIR": "Directory", "H1": "Heading", "H2": "Heading", "H3": "Heading",
2652      "H4": "Heading", "H5": "Heading", "H6": "Heading", "Q": "Quote",
2653      "INS": "Mod", "DEL": "Mod", "A": "Anchor", "IMG": "Image", "CAPTION":
2654      "TableCaption", "COL": "TableCol", "COLGROUP": "TableCol", "THEAD":
2655      "TableSection", "TFOOT": "TableSection", "TBODY": "TableSection", "TR":
2656      "TableRow", "TH": "TableCell", "TD": "TableCell", "FRAMESET":
2657      "FrameSet", "IFRAME": "IFrame"
2658    };
2659    if (trans[tagName]) klass = 'HTML' + trans[tagName] + 'Element';
2660    if (window[klass]) return window[klass];
2661    klass = 'HTML' + tagName + 'Element';
2662    if (window[klass]) return window[klass];
2663    klass = 'HTML' + tagName.capitalize() + 'Element';
2664    if (window[klass]) return window[klass];
2665
2666    window[klass] = { };
2667    window[klass].prototype = document.createElement(tagName)['__proto__'];
2668    return window[klass];
2669  }
2670
2671  if (F.ElementExtensions) {
2672    copy(Element.Methods, HTMLElement.prototype);
2673    copy(Element.Methods.Simulated, HTMLElement.prototype, true);
2674  }
2675
2676  if (F.SpecificElementExtensions) {
2677    for (var tag in Element.Methods.ByTag) {
2678      var klass = findDOMClass(tag);
2679      if (Object.isUndefined(klass)) continue;
2680      copy(T[tag], klass.prototype);
2681    }
2682  }
2683
2684  Object.extend(Element, Element.Methods);
2685  delete Element.ByTag;
2686
2687  if (Element.extend.refresh) Element.extend.refresh();
2688  Element.cache = { };
2689};
2690
2691document.viewport = {
2692  getDimensions: function() {
2693    var dimensions = { }, B = Prototype.Browser;
2694    $w('width height').each(function(d) {
2695      var D = d.capitalize();
2696      if (B.WebKit && !document.evaluate) {
2697        // Safari <3.0 needs self.innerWidth/Height
2698        dimensions[d] = self['inner' + D];
2699      } else if (B.Opera && parseFloat(window.opera.version()) < 9.5) {
2700        // Opera <9.5 needs document.body.clientWidth/Height
2701        dimensions[d] = document.body['client' + D]
2702      } else {
2703        dimensions[d] = document.documentElement['client' + D];
2704      }
2705    });
2706    return dimensions;
2707  },
2708
2709  getWidth: function() {
2710    return this.getDimensions().width;
2711  },
2712
2713  getHeight: function() {
2714    return this.getDimensions().height;
2715  },
2716
2717  getScrollOffsets: function() {
2718    return Element._returnOffset(
2719      window.pageXOffset || document.documentElement.scrollLeft || document.body.scrollLeft,
2720      window.pageYOffset || document.documentElement.scrollTop || document.body.scrollTop);
2721  }
2722};
2723/* Portions of the Selector class are derived from Jack Slocum's DomQuery,
2724 * part of YUI-Ext version 0.40, distributed under the terms of an MIT-style
2725 * license.  Please see http://www.yui-ext.com/ for more information. */
2726
2727var Selector = Class.create({
2728  initialize: function(expression) {
2729    this.expression = expression.strip();
2730
2731    if (this.shouldUseSelectorsAPI()) {
2732      this.mode = 'selectorsAPI';
2733    } else if (this.shouldUseXPath()) {
2734      this.mode = 'xpath';
2735      this.compileXPathMatcher();
2736    } else {
2737      this.mode = "normal";
2738      this.compileMatcher();
2739    }
2740
2741  },
2742
2743  shouldUseXPath: function() {
2744    if (!Prototype.BrowserFeatures.XPath) return false;
2745
2746    var e = this.expression;
2747
2748    // Safari 3 chokes on :*-of-type and :empty
2749    if (Prototype.Browser.WebKit &&
2750     (e.include("-of-type") || e.include(":empty")))
2751      return false;
2752
2753    // XPath can't do namespaced attributes, nor can it read
2754    // the "checked" property from DOM nodes
2755    if ((/(\[[\w-]*?:|:checked)/).test(e))
2756      return false;
2757
2758    return true;
2759  },
2760
2761  shouldUseSelectorsAPI: function() {
2762    if (!Prototype.BrowserFeatures.SelectorsAPI) return false;
2763
2764    if (!Selector._div) Selector._div = new Element('div');
2765
2766    // Make sure the browser treats the selector as valid. Test on an
2767    // isolated element to minimize cost of this check.
2768    try {
2769      Selector._div.querySelector(this.expression);
2770    } catch(e) {
2771      return false;
2772    }
2773
2774    return true;
2775  },
2776
2777  compileMatcher: function() {
2778    var e = this.expression, ps = Selector.patterns, h = Selector.handlers,
2779        c = Selector.criteria, le, p, m;
2780
2781    if (Selector._cache[e]) {
2782      this.matcher = Selector._cache[e];
2783      return;
2784    }
2785
2786    this.matcher = ["this.matcher = function(root) {",
2787                    "var r = root, h = Selector.handlers, c = false, n;"];
2788
2789    while (e && le != e && (/\S/).test(e)) {
2790      le = e;
2791      for (var i in ps) {
2792        p = ps[i];
2793        if (m = e.match(p)) {
2794          this.matcher.push(Object.isFunction(c[i]) ? c[i](m) :
2795            new Template(c[i]).evaluate(m));
2796          e = e.replace(m[0], '');
2797          break;
2798        }
2799      }
2800    }
2801
2802    this.matcher.push("return h.unique(n);\n}");
2803    eval(this.matcher.join('\n'));
2804    Selector._cache[this.expression] = this.matcher;
2805  },
2806
2807  compileXPathMatcher: function() {
2808    var e = this.expression, ps = Selector.patterns,
2809        x = Selector.xpath, le, m;
2810
2811    if (Selector._cache[e]) {
2812      this.xpath = Selector._cache[e]; return;
2813    }
2814
2815    this.matcher = ['.//*'];
2816    while (e && le != e && (/\S/).test(e)) {
2817      le = e;
2818      for (var i in ps) {
2819        if (m = e.match(ps[i])) {
2820          this.matcher.push(Object.isFunction(x[i]) ? x[i](m) :
2821            new Template(x[i]).evaluate(m));
2822          e = e.replace(m[0], '');
2823          break;
2824        }
2825      }
2826    }
2827
2828    this.xpath = this.matcher.join('');
2829    Selector._cache[this.expression] = this.xpath;
2830  },
2831
2832  findElements: function(root) {
2833    root = root || document;
2834    var e = this.expression, results;
2835
2836    switch (this.mode) {
2837      case 'selectorsAPI':
2838        // querySelectorAll queries document-wide, then filters to descendants
2839        // of the context element. That's not what we want.
2840        // Add an explicit context to the selector if necessary.
2841        if (root !== document) {
2842          var oldId = root.id, id = $(root).identify();
2843          e = "#" + id + " " + e;
2844        }
2845
2846        results = $A(root.querySelectorAll(e)).map(Element.extend);
2847        root.id = oldId;
2848
2849        return results;
2850      case 'xpath':
2851        return document._getElementsByXPath(this.xpath, root);
2852      default:
2853       return this.matcher(root);
2854    }
2855  },
2856
2857  match: function(element) {
2858    this.tokens = [];
2859
2860    var e = this.expression, ps = Selector.patterns, as = Selector.assertions;
2861    var le, p, m;
2862
2863    while (e && le !== e && (/\S/).test(e)) {
2864      le = e;
2865      for (var i in ps) {
2866        p = ps[i];
2867        if (m = e.match(p)) {
2868          // use the Selector.assertions methods unless the selector
2869          // is too complex.
2870          if (as[i]) {
2871            this.tokens.push([i, Object.clone(m)]);
2872            e = e.replace(m[0], '');
2873          } else {
2874            // reluctantly do a document-wide search
2875            // and look for a match in the array
2876            return this.findElements(document).include(element);
2877          }
2878        }
2879      }
2880    }
2881
2882    var match = true, name, matches;
2883    for (var i = 0, token; token = this.tokens[i]; i++) {
2884      name = token[0], matches = token[1];
2885      if (!Selector.assertions[name](element, matches)) {
2886        match = false; break;
2887      }
2888    }
2889
2890    return match;
2891  },
2892
2893  toString: function() {
2894    return this.expression;
2895  },
2896
2897  inspect: function() {
2898    return "#<Selector:" + this.expression.inspect() + ">";
2899  }
2900});
2901
2902Object.extend(Selector, {
2903  _cache: { },
2904
2905  xpath: {
2906    descendant:   "//*",
2907    child:        "/*",
2908    adjacent:     "/following-sibling::*[1]",
2909    laterSibling: '/following-sibling::*',
2910    tagName:      function(m) {
2911      if (m[1] == '*') return '';
2912      return "[local-name()='" + m[1].toLowerCase() +
2913             "' or local-name()='" + m[1].toUpperCase() + "']";
2914    },
2915    className:    "[contains(concat(' ', @class, ' '), ' #{1} ')]",
2916    id:           "[@id='#{1}']",
2917    attrPresence: function(m) {
2918      m[1] = m[1].toLowerCase();
2919      return new Template("[@#{1}]").evaluate(m);
2920    },
2921    attr: function(m) {
2922      m[1] = m[1].toLowerCase();
2923      m[3] = m[5] || m[6];
2924      return new Template(Selector.xpath.operators[m[2]]).evaluate(m);
2925    },
2926    pseudo: function(m) {
2927      var h = Selector.xpath.pseudos[m[1]];
2928      if (!h) return '';
2929      if (Object.isFunction(h)) return h(m);
2930      return new Template(Selector.xpath.pseudos[m[1]]).evaluate(m);
2931    },
2932    operators: {
2933      '=':  "[@#{1}='#{3}']",
2934      '!=': "[@#{1}!='#{3}']",
2935      '^=': "[starts-with(@#{1}, '#{3}')]",
2936      '$=': "[substring(@#{1}, (string-length(@#{1}) - string-length('#{3}') + 1))='#{3}']",
2937      '*=': "[contains(@#{1}, '#{3}')]",
2938      '~=': "[contains(concat(' ', @#{1}, ' '), ' #{3} ')]",
2939      '|=': "[contains(concat('-', @#{1}, '-'), '-#{3}-')]"
2940    },
2941    pseudos: {
2942      'first-child': '[not(preceding-sibling::*)]',
2943      'last-child':  '[not(following-sibling::*)]',
2944      'only-child':  '[not(preceding-sibling::* or following-sibling::*)]',
2945      'empty':       "[count(*) = 0 and (count(text()) = 0)]",
2946      'checked':     "[@checked]",
2947      'disabled':    "[(@disabled) and (@type!='hidden')]",
2948      'enabled':     "[not(@disabled) and (@type!='hidden')]",
2949      'not': function(m) {
2950        var e = m[6], p = Selector.patterns,
2951            x = Selector.xpath, le, v;
2952
2953        var exclusion = [];
2954        while (e && le != e && (/\S/).test(e)) {
2955          le = e;
2956          for (var i in p) {
2957            if (m = e.match(p[i])) {
2958              v = Object.isFunction(x[i]) ? x[i](m) : new Template(x[i]).evaluate(m);
2959              exclusion.push("(" + v.substring(1, v.length - 1) + ")");
2960              e = e.replace(m[0], '');
2961              break;
2962            }
2963          }
2964        }
2965        return "[not(" + exclusion.join(" and ") + ")]";
2966      },
2967      'nth-child':      function(m) {
2968        return Selector.xpath.pseudos.nth("(count(./preceding-sibling::*) + 1) ", m);
2969      },
2970      'nth-last-child': function(m) {
2971        return Selector.xpath.pseudos.nth("(count(./following-sibling::*) + 1) ", m);
2972      },
2973      'nth-of-type':    function(m) {
2974        return Selector.xpath.pseudos.nth("position() ", m);
2975      },
2976      'nth-last-of-type': function(m) {
2977        return Selector.xpath.pseudos.nth("(last() + 1 - position()) ", m);
2978      },
2979      'first-of-type':  function(m) {
2980        m[6] = "1"; return Selector.xpath.pseudos['nth-of-type'](m);
2981      },
2982      'last-of-type':   function(m) {
2983        m[6] = "1"; return Selector.xpath.pseudos['nth-last-of-type'](m);
2984      },
2985      'only-of-type':   function(m) {
2986        var p = Selector.xpath.pseudos; return p['first-of-type'](m) + p['last-of-type'](m);
2987      },
2988      nth: function(fragment, m) {
2989        var mm, formula = m[6], predicate;
2990        if (formula == 'even') formula = '2n+0';
2991        if (formula == 'odd')  formula = '2n+1';
2992        if (mm = formula.match(/^(\d+)$/)) // digit only
2993          return '[' + fragment + "= " + mm[1] + ']';
2994        if (mm = formula.match(/^(-?\d*)?n(([+-])(\d+))?/)) { // an+b
2995          if (mm[1] == "-") mm[1] = -1;
2996          var a = mm[1] ? Number(mm[1]) : 1;
2997          var b = mm[2] ? Number(mm[2]) : 0;
2998          predicate = "[((#{fragment} - #{b}) mod #{a} = 0) and " +
2999          "((#{fragment} - #{b}) div #{a} >= 0)]";
3000          return new Template(predicate).evaluate({
3001            fragment: fragment, a: a, b: b });
3002        }
3003      }
3004    }
3005  },
3006
3007  criteria: {
3008    tagName:      'n = h.tagName(n, r, "#{1}", c);      c = false;',
3009    className:    'n = h.className(n, r, "#{1}", c);    c = false;',
3010    id:           'n = h.id(n, r, "#{1}", c);           c = false;',
3011    attrPresence: 'n = h.attrPresence(n, r, "#{1}", c); c = false;',
3012    attr: function(m) {
3013      m[3] = (m[5] || m[6]);
3014      return new Template('n = h.attr(n, r, "#{1}", "#{3}", "#{2}", c); c = false;').evaluate(m);
3015    },
3016    pseudo: function(m) {
3017      if (m[6]) m[6] = m[6].replace(/"/g, '\\"');
3018      return new Template('n = h.pseudo(n, "#{1}", "#{6}", r, c); c = false;').evaluate(m);
3019    },
3020    descendant:   'c = "descendant";',
3021    child:        'c = "child";',
3022    adjacent:     'c = "adjacent";',
3023    laterSibling: 'c = "laterSibling";'
3024  },
3025
3026  patterns: {
3027    // combinators must be listed first
3028    // (and descendant needs to be last combinator)
3029    laterSibling: /^\s*~\s*/,
3030    child:        /^\s*>\s*/,
3031    adjacent:     /^\s*\+\s*/,
3032    descendant:   /^\s/,
3033
3034    // selectors follow
3035    tagName:      /^\s*(\*|[\w\-]+)(\b|$)?/,
3036    id:           /^#([\w\-\*]+)(\b|$)/,
3037    className:    /^\.([\w\-\*]+)(\b|$)/,
3038    pseudo:
3039/^:((first|last|nth|nth-last|only)(-child|-of-type)|empty|checked|(en|dis)abled|not)(\((.*?)\))?(\b|$|(?=\s|[:+~>]))/,
3040    attrPresence: /^\[((?:[\w]+:)?[\w]+)\]/,
3041    attr:         /\[((?:[\w-]*:)?[\w-]+)\s*(?:([!^$*~|]?=)\s*((['"])([^\4]*?)\4|([^'"][^\]]*?)))?\]/
3042  },
3043
3044  // for Selector.match and Element#match
3045  assertions: {
3046    tagName: function(element, matches) {
3047      return matches[1].toUpperCase() == element.tagName.toUpperCase();
3048    },
3049
3050    className: function(element, matches) {
3051      return Element.hasClassName(element, matches[1]);
3052    },
3053
3054    id: function(element, matches) {
3055      return element.id === matches[1];
3056    },
3057
3058    attrPresence: function(element, matches) {
3059      return Element.hasAttribute(element, matches[1]);
3060    },
3061
3062    attr: function(element, matches) {
3063      var nodeValue = Element.readAttribute(element, matches[1]);
3064      return nodeValue && Selector.operators[matches[2]](nodeValue, matches[5] || matches[6]);
3065    }
3066  },
3067
3068  handlers: {
3069    // UTILITY FUNCTIONS
3070    // joins two collections
3071    concat: function(a, b) {
3072      for (var i = 0, node; node = b[i]; i++)
3073        a.push(node);
3074      return a;
3075    },
3076
3077    // marks an array of nodes for counting
3078    mark: function(nodes) {
3079      var _true = Prototype.emptyFunction;
3080      for (var i = 0, node; node = nodes[i]; i++)
3081        node._countedByPrototype = _true;
3082      return nodes;
3083    },
3084
3085    unmark: function(nodes) {
3086      for (var i = 0, node; node = nodes[i]; i++)
3087        node._countedByPrototype = undefined;
3088      return nodes;
3089    },
3090
3091    // mark each child node with its position (for nth calls)
3092    // "ofType" flag indicates whether we're indexing for nth-of-type
3093    // rather than nth-child
3094    index: function(parentNode, reverse, ofType) {
3095      parentNode._countedByPrototype = Prototype.emptyFunction;
3096      if (reverse) {
3097        for (var nodes = parentNode.childNodes, i = nodes.length - 1, j = 1; i >= 0; i--) {
3098          var node = nodes[i];
3099          if (node.nodeType == 1 && (!ofType || node._countedByPrototype)) node.nodeIndex = j++;
3100        }
3101      } else {
3102        for (var i = 0, j = 1, nodes = parentNode.childNodes; node = nodes[i]; i++)
3103          if (node.nodeType == 1 && (!ofType || node._countedByPrototype)) node.nodeIndex = j++;
3104      }
3105    },
3106
3107    // filters out duplicates and extends all nodes
3108    unique: function(nodes) {
3109      if (nodes.length == 0) return nodes;
3110      var results = [], n;
3111      for (var i = 0, l = nodes.length; i < l; i++)
3112        if (!(n = nodes[i])._countedByPrototype) {
3113          n._countedByPrototype = Prototype.emptyFunction;
3114          results.push(Element.extend(n));
3115        }
3116      return Selector.handlers.unmark(results);
3117    },
3118
3119    // COMBINATOR FUNCTIONS
3120    descendant: function(nodes) {
3121      var h = Selector.handlers;
3122      for (var i = 0, results = [], node; node = nodes[i]; i++)
3123        h.concat(results, node.getElementsByTagName('*'));
3124      return results;
3125    },
3126
3127    child: function(nodes) {
3128      var h = Selector.handlers;
3129      for (var i = 0, results = [], node; node = nodes[i]; i++) {
3130        for (var j = 0, child; child = node.childNodes[j]; j++)
3131          if (child.nodeType == 1 && child.tagName != '!') results.push(child);
3132      }
3133      return results;
3134    },
3135
3136    adjacent: function(nodes) {
3137      for (var i = 0, results = [], node; node = nodes[i]; i++) {
3138        var next = this.nextElementSibling(node);
3139        if (next) results.push(next);
3140      }
3141      return results;
3142    },
3143
3144    laterSibling: function(nodes) {
3145      var h = Selector.handlers;
3146      for (var i = 0, results = [], node; node = nodes[i]; i++)
3147        h.concat(results, Element.nextSiblings(node));
3148      return results;
3149    },
3150
3151    nextElementSibling: function(node) {
3152      while (node = node.nextSibling)
3153        if (node.nodeType == 1) return node;
3154      return null;
3155    },
3156
3157    previousElementSibling: function(node) {
3158      while (node = node.previousSibling)
3159        if (node.nodeType == 1) return node;
3160      return null;
3161    },
3162
3163    // TOKEN FUNCTIONS
3164    tagName: function(nodes, root, tagName, combinator) {
3165      var uTagName = tagName.toUpperCase();
3166      var results = [], h = Selector.handlers;
3167      if (nodes) {
3168        if (combinator) {
3169          // fastlane for ordinary descendant combinators
3170          if (combinator == "descendant") {
3171            for (var i = 0, node; node = nodes[i]; i++)
3172              h.concat(results, node.getElementsByTagName(tagName));
3173            return results;
3174          } else nodes = this[combinator](nodes);
3175          if (tagName == "*") return nodes;
3176        }
3177        for (var i = 0, node; node = nodes[i]; i++)
3178          if (node.tagName.toUpperCase() === uTagName) results.push(node);
3179        return results;
3180      } else return root.getElementsByTagName(tagName);
3181    },
3182
3183    id: function(nodes, root, id, combinator) {
3184      var targetNode = $(id), h = Selector.handlers;
3185      if (!targetNode) return [];
3186      if (!nodes && root == document) return [targetNode];
3187      if (nodes) {
3188        if (combinator) {
3189          if (combinator == 'child') {
3190            for (var i = 0, node; node = nodes[i]; i++)
3191              if (targetNode.parentNode == node) return [targetNode];
3192          } else if (combinator == 'descendant') {
3193            for (var i = 0, node; node = nodes[i]; i++)
3194              if (Element.descendantOf(targetNode, node)) return [targetNode];
3195          } else if (combinator == 'adjacent') {
3196            for (var i = 0, node; node = nodes[i]; i++)
3197              if (Selector.handlers.previousElementSibling(targetNode) == node)
3198                return [targetNode];
3199          } else nodes = h[combinator](nodes);
3200        }
3201        for (var i = 0, node; node = nodes[i]; i++)
3202          if (node == targetNode) return [targetNode];
3203        return [];
3204      }
3205      return (targetNode && Element.descendantOf(targetNode, root)) ? [targetNode] : [];
3206    },
3207
3208    className: function(nodes, root, className, combinator) {
3209      if (nodes && combinator) nodes = this[combinator](nodes);
3210      return Selector.handlers.byClassName(nodes, root, className);
3211    },
3212
3213    byClassName: function(nodes, root, className) {
3214      if (!nodes) nodes = Selector.handlers.descendant([root]);
3215      var needle = ' ' + className + ' ';
3216      for (var i = 0, results = [], node, nodeClassName; node = nodes[i]; i++) {
3217        nodeClassName = node.className;
3218        if (nodeClassName.length == 0) continue;
3219        if (nodeClassName == className || (' ' + nodeClassName + ' ').include(needle))
3220          results.push(node);
3221      }
3222      return results;
3223    },
3224
3225    attrPresence: function(nodes, root, attr, combinator) {
3226      if (!nodes) nodes = root.getElementsByTagName("*");
3227      if (nodes && combinator) nodes = this[combinator](nodes);
3228      var results = [];
3229      for (var i = 0, node; node = nodes[i]; i++)
3230        if (Element.hasAttribute(node, attr)) results.push(node);
3231      return results;
3232    },
3233
3234    attr: function(nodes, root, attr, value, operator, combinator) {
3235      if (!nodes) nodes = root.getElementsByTagName("*");
3236      if (nodes && combinator) nodes = this[combinator](nodes);
3237      var handler = Selector.operators[operator], results = [];
3238      for (var i = 0, node; node = nodes[i]; i++) {
3239        var nodeValue = Element.readAttribute(node, attr);
3240        if (nodeValue === null) continue;
3241        if (handler(nodeValue, value)) results.push(node);
3242      }
3243      return results;
3244    },
3245
3246    pseudo: function(nodes, name, value, root, combinator) {
3247      if (nodes && combinator) nodes = this[combinator](nodes);
3248      if (!nodes) nodes = root.getElementsByTagName("*");
3249      return Selector.pseudos[name](nodes, value, root);
3250    }
3251  },
3252
3253  pseudos: {
3254    'first-child': function(nodes, value, root) {
3255      for (var i = 0, results = [], node; node = nodes[i]; i++) {
3256        if (Selector.handlers.previousElementSibling(node)) continue;
3257          results.push(node);
3258      }
3259      return results;
3260    },
3261    'last-child': function(nodes, value, root) {
3262      for (var i = 0, results = [], node; node = nodes[i]; i++) {
3263        if (Selector.handlers.nextElementSibling(node)) continue;
3264          results.push(node);
3265      }
3266      return results;
3267    },
3268    'only-child': function(nodes, value, root) {
3269      var h = Selector.handlers;
3270      for (var i = 0, results = [], node; node = nodes[i]; i++)
3271        if (!h.previousElementSibling(node) && !h.nextElementSibling(node))
3272          results.push(node);
3273      return results;
3274    },
3275    'nth-child':        function(nodes, formula, root) {
3276      return Selector.pseudos.nth(nodes, formula, root);
3277    },
3278    'nth-last-child':   function(nodes, formula, root) {
3279      return Selector.pseudos.nth(nodes, formula, root, true);
3280    },
3281    'nth-of-type':      function(nodes, formula, root) {
3282      return Selector.pseudos.nth(nodes, formula, root, false, true);
3283    },
3284    'nth-last-of-type': function(nodes, formula, root) {
3285      return Selector.pseudos.nth(nodes, formula, root, true, true);
3286    },
3287    'first-of-type':    function(nodes, formula, root) {
3288      return Selector.pseudos.nth(nodes, "1", root, false, true);
3289    },
3290    'last-of-type':     function(nodes, formula, root) {
3291      return Selector.pseudos.nth(nodes, "1", root, true, true);
3292    },
3293    'only-of-type':     function(nodes, formula, root) {
3294      var p = Selector.pseudos;
3295      return p['last-of-type'](p['first-of-type'](nodes, formula, root), formula, root);
3296    },
3297
3298    // handles the an+b logic
3299    getIndices: function(a, b, total) {
3300      if (a == 0) return b > 0 ? [b] : [];
3301      return $R(1, total).inject([], function(memo, i) {
3302        if (0 == (i - b) % a && (i - b) / a >= 0) memo.push(i);
3303        return memo;
3304      });
3305    },
3306
3307    // handles nth(-last)-child, nth(-last)-of-type, and (first|last)-of-type
3308    nth: function(nodes, formula, root, reverse, ofType) {
3309      if (nodes.length == 0) return [];
3310      if (formula == 'even') formula = '2n+0';
3311      if (formula == 'odd')  formula = '2n+1';
3312      var h = Selector.handlers, results = [], indexed = [], m;
3313      h.mark(nodes);
3314      for (var i = 0, node; node = nodes[i]; i++) {
3315        if (!node.parentNode._countedByPrototype) {
3316          h.index(node.parentNode, reverse, ofType);
3317          indexed.push(node.parentNode);
3318        }
3319      }
3320      if (formula.match(/^\d+$/)) { // just a number
3321        formula = Number(formula);
3322        for (var i = 0, node; node = nodes[i]; i++)
3323          if (node.nodeIndex == formula) results.push(node);
3324      } else if (m = formula.match(/^(-?\d*)?n(([+-])(\d+))?/)) { // an+b
3325        if (m[1] == "-") m[1] = -1;
3326        var a = m[1] ? Number(m[1]) : 1;
3327        var b = m[2] ? Number(m[2]) : 0;
3328        var indices = Selector.pseudos.getIndices(a, b, nodes.length);
3329        for (var i = 0, node, l = indices.length; node = nodes[i]; i++) {
3330          for (var j = 0; j < l; j++)
3331            if (node.nodeIndex == indices[j]) results.push(node);
3332        }
3333      }
3334      h.unmark(nodes);
3335      h.unmark(indexed);
3336      return results;
3337    },
3338
3339    'empty': function(nodes, value, root) {
3340      for (var i = 0, results = [], node; node = nodes[i]; i++) {
3341        // IE treats comments as element nodes
3342        if (node.tagName == '!' || node.firstChild) continue;
3343        results.push(node);
3344      }
3345      return results;
3346    },
3347
3348    'not': function(nodes, selector, root) {
3349      var h = Selector.handlers, selectorType, m;
3350      var exclusions = new Selector(selector).findElements(root);
3351      h.mark(exclusions);
3352      for (var i = 0, results = [], node; node = nodes[i]; i++)
3353        if (!node._countedByPrototype) results.push(node);
3354      h.unmark(exclusions);
3355      return results;
3356    },
3357
3358    'enabled': function(nodes, value, root) {
3359      for (var i = 0, results = [], node; node = nodes[i]; i++)
3360        if (!node.disabled && (!node.type || node.type !== 'hidden'))
3361          results.push(node);
3362      return results;
3363    },
3364
3365    'disabled': function(nodes, value, root) {
3366      for (var i = 0, results = [], node; node = nodes[i]; i++)
3367        if (node.disabled) results.push(node);
3368      return results;
3369    },
3370
3371    'checked': function(nodes, value, root) {
3372      for (var i = 0, results = [], node; node = nodes[i]; i++)
3373        if (node.checked) results.push(node);
3374      return results;
3375    }
3376  },
3377
3378  operators: {
3379    '=':  function(nv, v) { return nv == v; },
3380    '!=': function(nv, v) { return nv != v; },
3381    '^=': function(nv, v) { return nv == v || nv && nv.startsWith(v); },
3382    '$=': function(nv, v) { return nv == v || nv && nv.endsWith(v); },
3383    '*=': function(nv, v) { return nv == v || nv && nv.include(v); },
3384    '$=': function(nv, v) { return nv.endsWith(v); },
3385    '*=': function(nv, v) { return nv.include(v); },
3386    '~=': function(nv, v) { return (' ' + nv + ' ').include(' ' + v + ' '); },
3387    '|=': function(nv, v) { return ('-' + (nv || "").toUpperCase() +
3388     '-').include('-' + (v || "").toUpperCase() + '-'); }
3389  },
3390
3391  split: function(expression) {
3392    var expressions = [];
3393    expression.scan(/(([\w#:.~>+()\s-]+|\*|\[.*?\])+)\s*(,|$)/, function(m) {
3394      expressions.push(m[1].strip());
3395    });
3396    return expressions;
3397  },
3398
3399  matchElements: function(elements, expression) {
3400    var matches = $$(expression), h = Selector.handlers;
3401    h.mark(matches);
3402    for (var i = 0, results = [], element; element = elements[i]; i++)
3403      if (element._countedByPrototype) results.push(element);
3404    h.unmark(matches);
3405    return results;
3406  },
3407
3408  findElement: function(elements, expression, index) {
3409    if (Object.isNumber(expression)) {
3410      index = expression; expression = false;
3411    }
3412    return Selector.matchElements(elements, expression || '*')[index || 0];
3413  },
3414
3415  findChildElements: function(element, expressions) {
3416    expressions = Selector.split(expressions.join(','));
3417    var results = [], h = Selector.handlers;
3418    for (var i = 0, l = expressions.length, selector; i < l; i++) {
3419      selector = new Selector(expressions[i].strip());
3420      h.concat(results, selector.findElements(element));
3421    }
3422    return (l > 1) ? h.unique(results) : results;
3423  }
3424});
3425
3426if (Prototype.Browser.IE) {
3427  Object.extend(Selector.handlers, {
3428    // IE returns comment nodes on getElementsByTagName("*").
3429    // Filter them out.
3430    concat: function(a, b) {
3431      for (var i = 0, node; node = b[i]; i++)
3432        if (node.tagName !== "!") a.push(node);
3433      return a;
3434    },
3435
3436    // IE improperly serializes _countedByPrototype in (inner|outer)HTML.
3437    unmark: function(nodes) {
3438      for (var i = 0, node; node = nodes[i]; i++)
3439        node.removeAttribute('_countedByPrototype');
3440      return nodes;
3441    }
3442  });
3443}
3444
3445function $$() {
3446  return Selector.findChildElements(document, $A(arguments));
3447}
3448var Form = {
3449  reset: function(form) {
3450    $(form).reset();
3451    return form;
3452  },
3453
3454  serializeElements: function(elements, options) {
3455    if (typeof options != 'object') options = { hash: !!options };
3456    else if (Object.isUndefined(options.hash)) options.hash = true;
3457    var key, value, submitted = false, submit = options.submit;
3458
3459    var data = elements.inject({ }, function(result, element) {
3460      if (!element.disabled && element.name) {
3461        key = element.name; value = $(element).getValue();
3462        if (value != null && element.type != 'file' && (element.type != 'submit' || (!submitted &&
3463            submit !== false && (!submit || key == submit) && (submitted = true)))) {
3464          if (key in result) {
3465            // a key is already present; construct an array of values
3466            if (!Object.isArray(result[key])) result[key] = [result[key]];
3467            result[key].push(value);
3468          }
3469          else result[key] = value;
3470        }
3471      }
3472      return result;
3473    });
3474
3475    return options.hash ? data : Object.toQueryString(data);
3476  }
3477};
3478
3479Form.Methods = {
3480  serialize: function(form, options) {
3481    return Form.serializeElements(Form.getElements(form), options);
3482  },
3483
3484  getElements: function(form) {
3485    return $A($(form).getElementsByTagName('*')).inject([],
3486      function(elements, child) {
3487        if (Form.Element.Serializers[child.tagName.toLowerCase()])
3488          elements.push(Element.extend(child));
3489        return elements;
3490      }
3491    );
3492  },
3493
3494  getInputs: function(form, typeName, name) {
3495    form = $(form);
3496    var inputs = form.getElementsByTagName('input');
3497
3498    if (!typeName && !name) return $A(inputs).map(Element.extend);
3499
3500    for (var i = 0, matchingInputs = [], length = inputs.length; i < length; i++) {
3501      var input = inputs[i];
3502      if ((typeName && input.type != typeName) || (name && input.name != name))
3503        continue;
3504      matchingInputs.push(Element.extend(input));
3505    }
3506
3507    return matchingInputs;
3508  },
3509
3510  disable: function(form) {
3511    form = $(form);
3512    Form.getElements(form).invoke('disable');
3513    return form;
3514  },
3515
3516  enable: function(form) {
3517    form = $(form);
3518    Form.getElements(form).invoke('enable');
3519    return form;
3520  },
3521
3522  findFirstElement: function(form) {
3523    var elements = $(form).getElements().findAll(function(element) {
3524      return 'hidden' != element.type && !element.disabled;
3525    });
3526    var firstByIndex = elements.findAll(function(element) {
3527      return element.hasAttribute('tabIndex') && element.tabIndex >= 0;
3528    }).sortBy(function(element) { return element.tabIndex }).first();
3529
3530    return firstByIndex ? firstByIndex : elements.find(function(element) {
3531      return ['input', 'select', 'textarea'].include(element.tagName.toLowerCase());
3532    });
3533  },
3534
3535  focusFirstElement: function(form) {
3536    form = $(form);
3537    form.findFirstElement().activate();
3538    return form;
3539  },
3540
3541  request: function(form, options) {
3542    form = $(form), options = Object.clone(options || { });
3543
3544    var params = options.parameters, action = form.readAttribute('action') || '';
3545    if (action.blank()) action = window.location.href;
3546    options.parameters = form.serialize(true);
3547
3548    if (params) {
3549      if (Object.isString(params)) params = params.toQueryParams();
3550      Object.extend(options.parameters, params);
3551    }
3552
3553    if (form.hasAttribute('method') && !options.method)
3554      options.method = form.method;
3555
3556    return new Ajax.Request(action, options);
3557  }
3558};
3559
3560/*--------------------------------------------------------------------------*/
3561
3562Form.Element = {
3563  focus: function(element) {
3564    $(element).focus();
3565    return element;
3566  },
3567
3568  select: function(element) {
3569    $(element).select();
3570    return element;
3571  }
3572};
3573
3574Form.Element.Methods = {
3575  serialize: function(element) {
3576    element = $(element);
3577    if (!element.disabled && element.name) {
3578      var value = element.getValue();
3579      if (value != undefined) {
3580        var pair = { };
3581        pair[element.name] = value;
3582        return Object.toQueryString(pair);
3583      }
3584    }
3585    return '';
3586  },
3587
3588  getValue: function(element) {
3589    element = $(element);
3590    var method = element.tagName.toLowerCase();
3591    return Form.Element.Serializers[method](element);
3592  },
3593
3594  setValue: function(element, value) {
3595    element = $(element);
3596    var method = element.tagName.toLowerCase();
3597    Form.Element.Serializers[method](element, value);
3598    return element;
3599  },
3600
3601  clear: function(element) {
3602    $(element).value = '';
3603    return element;
3604  },
3605
3606  present: function(element) {
3607    return $(element).value != '';
3608  },
3609
3610  activate: function(element) {
3611    element = $(element);
3612    try {
3613      element.focus();
3614      if (element.select && (element.tagName.toLowerCase() != 'input' ||
3615          !['button', 'reset', 'submit'].include(element.type)))
3616        element.select();
3617    } catch (e) { }
3618    return element;
3619  },
3620
3621  disable: function(element) {
3622    element = $(element);
3623    element.disabled = true;
3624    return element;
3625  },
3626
3627  enable: function(element) {
3628    element = $(element);
3629    element.disabled = false;
3630    return element;
3631  }
3632};
3633
3634/*--------------------------------------------------------------------------*/
3635
3636var Field = Form.Element;
3637var $F = Form.Element.Methods.getValue;
3638
3639/*--------------------------------------------------------------------------*/
3640
3641Form.Element.Serializers = {
3642  input: function(element, value) {
3643    switch (element.type.toLowerCase()) {
3644      case 'checkbox':
3645      case 'radio':
3646        return Form.Element.Serializers.inputSelector(element, value);
3647      default:
3648        return Form.Element.Serializers.textarea(element, value);
3649    }
3650  },
3651
3652  inputSelector: function(element, value) {
3653    if (Object.isUndefined(value)) return element.checked ? element.value : null;
3654    else element.checked = !!value;
3655  },
3656
3657  textarea: function(element, value) {
3658    if (Object.isUndefined(value)) return element.value;
3659    else element.value = value;
3660  },
3661
3662  select: function(element, value) {
3663    if (Object.isUndefined(value))
3664      return this[element.type == 'select-one' ?
3665        'selectOne' : 'selectMany'](element);
3666    else {
3667      var opt, currentValue, single = !Object.isArray(value);
3668      for (var i = 0, length = element.length; i < length; i++) {
3669        opt = element.options[i];
3670        currentValue = this.optionValue(opt);
3671        if (single) {
3672          if (currentValue == value) {
3673            opt.selected = true;
3674            return;
3675          }
3676        }
3677        else opt.selected = value.include(currentValue);
3678      }
3679    }
3680  },
3681
3682  selectOne: function(element) {
3683    var index = element.selectedIndex;
3684    return index >= 0 ? this.optionValue(element.options[index]) : null;
3685  },
3686
3687  selectMany: function(element) {
3688    var values, length = element.length;
3689    if (!length) return null;
3690
3691    for (var i = 0, values = []; i < length; i++) {
3692      var opt = element.options[i];
3693      if (opt.selected) values.push(this.optionValue(opt));
3694    }
3695    return values;
3696  },
3697
3698  optionValue: function(opt) {
3699    // extend element because hasAttribute may not be native
3700    return Element.extend(opt).hasAttribute('value') ? opt.value : opt.text;
3701  }
3702};
3703
3704/*--------------------------------------------------------------------------*/
3705
3706Abstract.TimedObserver = Class.create(PeriodicalExecuter, {
3707  initialize: function($super, element, frequency, callback) {
3708    $super(callback, frequency);
3709    this.element   = $(element);
3710    this.lastValue = this.getValue();
3711  },
3712
3713  execute: function() {
3714    var value = this.getValue();
3715    if (Object.isString(this.lastValue) && Object.isString(value) ?
3716        this.lastValue != value : String(this.lastValue) != String(value)) {
3717      this.callback(this.element, value);
3718      this.lastValue = value;
3719    }
3720  }
3721});
3722
3723Form.Element.Observer = Class.create(Abstract.TimedObserver, {
3724  getValue: function() {
3725    return Form.Element.getValue(this.element);
3726  }
3727});
3728
3729Form.Observer = Class.create(Abstract.TimedObserver, {
3730  getValue: function() {
3731    return Form.serialize(this.element);
3732  }
3733});
3734
3735/*--------------------------------------------------------------------------*/
3736
3737Abstract.EventObserver = Class.create({
3738  initialize: function(element, callback) {
3739    this.element  = $(element);
3740    this.callback = callback;
3741
3742    this.lastValue = this.getValue();
3743    if (this.element.tagName.toLowerCase() == 'form')
3744      this.registerFormCallbacks();
3745    else
3746      this.registerCallback(this.element);
3747  },
3748
3749  onElementEvent: function() {
3750    var value = this.getValue();
3751    if (this.lastValue != value) {
3752      this.callback(this.element, value);
3753      this.lastValue = value;
3754    }
3755  },
3756
3757  registerFormCallbacks: function() {
3758    Form.getElements(this.element).each(this.registerCallback, this);
3759  },
3760
3761  registerCallback: function(element) {
3762    if (element.type) {
3763      switch (element.type.toLowerCase()) {
3764        case 'checkbox':
3765        case 'radio':
3766          Event.observe(element, 'click', this.onElementEvent.bind(this));
3767          break;
3768        default:
3769          Event.observe(element, 'change', this.onElementEvent.bind(this));
3770          break;
3771      }
3772    }
3773  }
3774});
3775
3776Form.Element.EventObserver = Class.create(Abstract.EventObserver, {
3777  getValue: function() {
3778    return Form.Element.getValue(this.element);
3779  }
3780});
3781
3782Form.EventObserver = Class.create(Abstract.EventObserver, {
3783  getValue: function() {
3784    return Form.serialize(this.element);
3785  }
3786});
3787if (!window.Event) var Event = { };
3788
3789Object.extend(Event, {
3790  KEY_BACKSPACE: 8,
3791  KEY_TAB:       9,
3792  KEY_RETURN:   13,
3793  KEY_ESC:      27,
3794  KEY_LEFT:     37,
3795  KEY_UP:       38,
3796  KEY_RIGHT:    39,
3797  KEY_DOWN:     40,
3798  KEY_DELETE:   46,
3799  KEY_HOME:     36,
3800  KEY_END:      35,
3801  KEY_PAGEUP:   33,
3802  KEY_PAGEDOWN: 34,
3803  KEY_INSERT:   45,
3804
3805  cache: { },
3806
3807  relatedTarget: function(event) {
3808    var element;
3809    switch(event.type) {
3810      case 'mouseover': element = event.fromElement; break;
3811      case 'mouseout':  element = event.toElement;   break;
3812      default: return null;
3813    }
3814    return Element.extend(element);
3815  }
3816});
3817
3818Event.Methods = (function() {
3819  var isButton;
3820
3821  if (Prototype.Browser.IE) {
3822    var buttonMap = { 0: 1, 1: 4, 2: 2 };
3823    isButton = function(event, code) {
3824      return event.button == buttonMap[code];
3825    };
3826
3827  } else if (Prototype.Browser.WebKit) {
3828    isButton = function(event, code) {
3829      switch (code) {
3830        case 0: return event.which == 1 && !event.metaKey;
3831        case 1: return event.which == 1 && event.metaKey;
3832        default: return false;
3833      }
3834    };
3835
3836  } else {
3837    isButton = function(event, code) {
3838      return event.which ? (event.which === code + 1) : (event.button === code);
3839    };
3840  }
3841
3842  return {
3843    isLeftClick:   function(event) { return isButton(event, 0) },
3844    isMiddleClick: function(event) { return isButton(event, 1) },
3845    isRightClick:  function(event) { return isButton(event, 2) },
3846
3847    element: function(event) {
3848      event = Event.extend(event);
3849
3850      var node          = event.target,
3851          type          = event.type,
3852          currentTarget = event.currentTarget;
3853
3854      if (currentTarget && currentTarget.tagName) {
3855        // Firefox screws up the "click" event when moving between radio buttons
3856        // via arrow keys. It also screws up the "load" and "error" events on images,
3857        // reporting the document as the target instead of the original image.
3858        if (type === 'load' || type === 'error' ||
3859          (type === 'click' && currentTarget.tagName.toLowerCase() === 'input'
3860            && currentTarget.type === 'radio'))
3861              node = currentTarget;
3862      }
3863      if (node) {
3864        if (node.nodeType == Node.TEXT_NODE) node = node.parentNode;
3865        return Element.extend(node);
3866      } else return false;
3867    },
3868
3869    findElement: function(event, expression) {
3870      var element = Event.element(event);
3871      if (!expression) return element;
3872      var elements = [element].concat(element.ancestors());
3873      return Selector.findElement(elements, expression, 0);
3874    },
3875
3876    pointer: function(event) {
3877      var docElement = document.documentElement,
3878      body = document.body || { scrollLeft: 0, scrollTop: 0 };
3879      return {
3880        x: event.pageX || (event.clientX +
3881          (docElement.scrollLeft || body.scrollLeft) -
3882          (docElement.clientLeft || 0)),
3883        y: event.pageY || (event.clientY +
3884          (docElement.scrollTop || body.scrollTop) -
3885          (docElement.clientTop || 0))
3886      };
3887    },
3888
3889    pointerX: function(event) { return Event.pointer(event).x },
3890    pointerY: function(event) { return Event.pointer(event).y },
3891
3892    stop: function(event) {
3893      Event.extend(event);
3894      event.preventDefault();
3895      event.stopPropagation();
3896      event.stopped = true;
3897    }
3898  };
3899})();
3900
3901Event.extend = (function() {
3902  var methods = Object.keys(Event.Methods).inject({ }, function(m, name) {
3903    m[name] = Event.Methods[name].methodize();
3904    return m;
3905  });
3906
3907  if (Prototype.Browser.IE) {
3908    Object.extend(methods, {
3909      stopPropagation: function() { this.cancelBubble = true },
3910      preventDefault:  function() { this.returnValue = false },
3911      inspect: function() { return "[object Event]" }
3912    });
3913
3914    return function(event) {
3915      if (!event) return false;
3916      if (event._extendedByPrototype) return event;
3917
3918      event._extendedByPrototype = Prototype.emptyFunction;
3919      var pointer = Event.pointer(event);
3920      Object.extend(event, {
3921        target: event.srcElement,
3922        relatedTarget: Event.relatedTarget(event),
3923        pageX:  pointer.x,
3924        pageY:  pointer.y
3925      });
3926      return Object.extend(event, methods);
3927    };
3928
3929  } else {
3930    Event.prototype = Event.prototype || document.createEvent("HTMLEvents")['__proto__'];
3931    Object.extend(Event.prototype, methods);
3932    return Prototype.K;
3933  }
3934})();
3935
3936Object.extend(Event, (function() {
3937  var cache = Event.cache;
3938
3939  function getEventID(element) {
3940    try {
3941      if (element._prototypeEventID) return element._prototypeEventID[0];
3942      arguments.callee.id = arguments.callee.id || 1;
3943      return element._prototypeEventID = [++arguments.callee.id];
3944    } catch (error) {
3945      return false;
3946    }
3947  }
3948
3949  function getDOMEventName(eventName) {
3950    if (eventName && eventName.include(':')) return "dataavailable";
3951    return eventName;
3952  }
3953
3954  function getCacheForID(id) {
3955    return cache[id] = cache[id] || { };
3956  }
3957
3958  function getWrappersForEventName(id, eventName) {
3959    var c = getCacheForID(id);
3960    return c[eventName] = c[eventName] || [];
3961  }
3962
3963  function createWrapper(element, eventName, handler) {
3964    var id = getEventID(element);
3965    var c = getWrappersForEventName(id, eventName);
3966    if (c.pluck("handler").include(handler)) return false;
3967
3968    var wrapper = function(event) {
3969      if (!Event || !Event.extend ||
3970        (event.eventName && event.eventName != eventName))
3971          return false;
3972
3973      Event.extend(event);
3974      handler.call(element, event);
3975    };
3976
3977    wrapper.handler = handler;
3978    c.push(wrapper);
3979    return wrapper;
3980  }
3981
3982  function findWrapper(id, eventName, handler) {
3983    var c = getWrappersForEventName(id, eventName);
3984    return c.find(function(wrapper) { return wrapper.handler == handler });
3985  }
3986
3987  function destroyWrapper(id, eventName, handler) {
3988    var c = getCacheForID(id);
3989    if (!c[eventName]) return false;
3990    c[eventName] = c[eventName].without(findWrapper(id, eventName, handler));
3991  }
3992
3993  function destroyCache() {
3994    for (var id in cache)
3995      for (var eventName in cache[id])
3996        cache[id][eventName] = null;
3997  }
3998
3999
4000  // Internet Explorer needs to remove event handlers on page unload
4001  // in order to avoid memory leaks.
4002  if (window.attachEvent) {
4003    window.attachEvent("onunload", destroyCache);
4004  }
4005
4006  // Safari has a dummy event handler on page unload so that it won't
4007  // use its bfcache. Safari <= 3.1 has an issue with restoring the "document"
4008  // object when page is returned to via the back button using its bfcache.
4009  if (Prototype.Browser.WebKit) {
4010    window.addEventListener('unload', Prototype.emptyFunction, false);
4011  }
4012
4013  return {
4014    observe: function(element, eventName, handler) {
4015      element = $(element);
4016      var name = getDOMEventName(eventName);
4017
4018      var wrapper = createWrapper(element, eventName, handler);
4019      if (!wrapper) return element;
4020
4021      if (element.addEventListener) {
4022        element.addEventListener(name, wrapper, false);
4023      } else {
4024        element.attachEvent("on" + name, wrapper);
4025      }
4026
4027      return element;
4028    },
4029
4030    stopObserving: function(element, eventName, handler) {
4031      element = $(element);
4032      var id = getEventID(element), name = getDOMEventName(eventName);
4033
4034      if (!handler && eventName) {
4035        getWrappersForEventName(id, eventName).each(function(wrapper) {
4036          element.stopObserving(eventName, wrapper.handler);
4037        });
4038        return element;
4039
4040      } else if (!eventName) {
4041        Object.keys(getCacheForID(id)).each(function(eventName) {
4042          element.stopObserving(eventName);
4043        });
4044        return element;
4045      }
4046
4047      var wrapper = findWrapper(id, eventName, handler);
4048      if (!wrapper) return element;
4049
4050      if (element.removeEventListener) {
4051        element.removeEventListener(name, wrapper, false);
4052      } else {
4053        element.detachEvent("on" + name, wrapper);
4054      }
4055
4056      destroyWrapper(id, eventName, handler);
4057
4058      return element;
4059    },
4060
4061    fire: function(element, eventName, memo) {
4062      element = $(element);
4063      if (element == document && document.createEvent && !element.dispatchEvent)
4064        element = document.documentElement;
4065
4066      var event;
4067      if (document.createEvent) {
4068        event = document.createEvent("HTMLEvents");
4069        event.initEvent("dataavailable", true, true);
4070      } else {
4071        event = document.createEventObject();
4072        event.eventType = "ondataavailable";
4073      }
4074
4075      event.eventName = eventName;
4076      event.memo = memo || { };
4077
4078      if (document.createEvent) {
4079        element.dispatchEvent(event);
4080      } else {
4081        element.fireEvent(event.eventType, event);
4082      }
4083
4084      return Event.extend(event);
4085    }
4086  };
4087})());
4088
4089Object.extend(Event, Event.Methods);
4090
4091Element.addMethods({
4092  fire:          Event.fire,
4093  observe:       Event.observe,
4094  stopObserving: Event.stopObserving
4095});
4096
4097Object.extend(document, {
4098  fire:          Element.Methods.fire.methodize(),
4099  observe:       Element.Methods.observe.methodize(),
4100  stopObserving: Element.Methods.stopObserving.methodize(),
4101  loaded:        false
4102});
4103
4104(function() {
4105  /* Support for the DOMContentLoaded event is based on work by Dan Webb,
4106     Matthias Miller, Dean Edwards and John Resig. */
4107
4108  var timer;
4109
4110  function fireContentLoadedEvent() {
4111    if (document.loaded) return;
4112    if (timer) window.clearInterval(timer);
4113    document.fire("dom:loaded");
4114    document.loaded = true;
4115  }
4116
4117  if (document.addEventListener) {
4118    if (Prototype.Browser.WebKit) {
4119      timer = window.setInterval(function() {
4120        if (/loaded|complete/.test(document.readyState))
4121          fireContentLoadedEvent();
4122      }, 0);
4123
4124      Event.observe(window, "load", fireContentLoadedEvent);
4125
4126    } else {
4127      document.addEventListener("DOMContentLoaded",
4128        fireContentLoadedEvent, false);
4129    }
4130
4131  } else {
4132    document.write("<script id=__onDOMContentLoaded defer src=//:><\/script>");
4133    $("__onDOMContentLoaded").onreadystatechange = function() {
4134      if (this.readyState == "complete") {
4135        this.onreadystatechange = null;
4136        fireContentLoadedEvent();
4137      }
4138    };
4139  }
4140})();
4141/*------------------------------- DEPRECATED -------------------------------*/
4142
4143Hash.toQueryString = Object.toQueryString;
4144
4145var Toggle = { display: Element.toggle };
4146
4147Element.Methods.childOf = Element.Methods.descendantOf;
4148
4149var Insertion = {
4150  Before: function(element, content) {
4151    return Element.insert(element, {before:content});
4152  },
4153
4154  Top: function(element, content) {
4155    return Element.insert(element, {top:content});
4156  },
4157
4158  Bottom: function(element, content) {
4159    return Element.insert(element, {bottom:content});
4160  },
4161
4162  After: function(element, content) {
4163    return Element.insert(element, {after:content});
4164  }
4165};
4166
4167var $continue = new Error('"throw $continue" is deprecated, use "return" instead');
4168
4169// This should be moved to script.aculo.us; notice the deprecated methods
4170// further below, that map to the newer Element methods.
4171var Position = {
4172  // set to true if needed, warning: firefox performance problems
4173  // NOT neeeded for page scrolling, only if draggable contained in
4174  // scrollable elements
4175  includeScrollOffsets: false,
4176
4177  // must be called before calling withinIncludingScrolloffset, every time the
4178  // page is scrolled
4179  prepare: function() {
4180    this.deltaX =  window.pageXOffset
4181                || document.documentElement.scrollLeft
4182                || document.body.scrollLeft
4183                || 0;
4184    this.deltaY =  window.pageYOffset
4185                || document.documentElement.scrollTop
4186                || document.body.scrollTop
4187                || 0;
4188  },
4189
4190  // caches x/y coordinate pair to use with overlap
4191  within: function(element, x, y) {
4192    if (this.includeScrollOffsets)
4193      return this.withinIncludingScrolloffsets(element, x, y);
4194    this.xcomp = x;
4195    this.ycomp = y;
4196    this.offset = Element.cumulativeOffset(element);
4197
4198    return (y >= this.offset[1] &&
4199            y <  this.offset[1] + element.offsetHeight &&
4200            x >= this.offset[0] &&
4201            x <  this.offset[0] + element.offsetWidth);
4202  },
4203
4204  withinIncludingScrolloffsets: function(element, x, y) {
4205    var offsetcache = Element.cumulativeScrollOffset(element);
4206
4207    this.xcomp = x + offsetcache[0] - this.deltaX;
4208    this.ycomp = y + offsetcache[1] - this.deltaY;
4209    this.offset = Element.cumulativeOffset(element);
4210
4211    return (this.ycomp >= this.offset[1] &&
4212            this.ycomp <  this.offset[1] + element.offsetHeight &&
4213            this.xcomp >= this.offset[0] &&
4214            this.xcomp <  this.offset[0] + element.offsetWidth);
4215  },
4216
4217  // within must be called directly before
4218  overlap: function(mode, element) {
4219    if (!mode) return 0;
4220    if (mode == 'vertical')
4221      return ((this.offset[1] + element.offsetHeight) - this.ycomp) /
4222        element.offsetHeight;
4223    if (mode == 'horizontal')
4224      return ((this.offset[0] + element.offsetWidth) - this.xcomp) /
4225        element.offsetWidth;
4226  },
4227
4228  // Deprecation layer -- use newer Element methods now (1.5.2).
4229
4230  cumulativeOffset: Element.Methods.cumulativeOffset,
4231
4232  positionedOffset: Element.Methods.positionedOffset,
4233
4234  absolutize: function(element) {
4235    Position.prepare();
4236    return Element.absolutize(element);
4237  },
4238
4239  relativize: function(element) {
4240    Position.prepare();
4241    return Element.relativize(element);
4242  },
4243
4244  realOffset: Element.Methods.cumulativeScrollOffset,
4245
4246  offsetParent: Element.Methods.getOffsetParent,
4247
4248  page: Element.Methods.viewportOffset,
4249
4250  clone: function(source, target, options) {
4251    options = options || { };
4252    return Element.clonePosition(target, source, options);
4253  }
4254};
4255
4256/*--------------------------------------------------------------------------*/
4257
4258if (!document.getElementsByClassName) document.getElementsByClassName = function(instanceMethods){
4259  function iter(name) {
4260    return name.blank() ? null : "[contains(concat(' ', @class, ' '), ' " + name + " ')]";
4261  }
4262
4263  instanceMethods.getElementsByClassName = Prototype.BrowserFeatures.XPath ?
4264  function(element, className) {
4265    className = className.toString().strip();
4266    var cond = /\s/.test(className) ? $w(className).map(iter).join('') : iter(className);
4267    return cond ? document._getElementsByXPath('.//*' + cond, element) : [];
4268  } : function(element, className) {
4269    className = className.toString().strip();
4270    var elements = [], classNames = (/\s/.test(className) ? $w(className) : null);
4271    if (!classNames && !className) return elements;
4272
4273    var nodes = $(element).getElementsByTagName('*');
4274    className = ' ' + className + ' ';
4275
4276    for (var i = 0, child, cn; child = nodes[i]; i++) {
4277      if (child.className && (cn = ' ' + child.className + ' ') && (cn.include(className) ||
4278          (classNames && classNames.all(function(name) {
4279            return !name.toString().blank() && cn.include(' ' + name + ' ');
4280          }))))
4281        elements.push(Element.extend(child));
4282    }
4283    return elements;
4284  };
4285
4286  return function(className, parentElement) {
4287    return $(parentElement || document.body).getElementsByClassName(className);
4288  };
4289}(Element.Methods);
4290
4291/*--------------------------------------------------------------------------*/
4292
4293Element.ClassNames = Class.create();
4294Element.ClassNames.prototype = {
4295  initialize: function(element) {
4296    this.element = $(element);
4297  },
4298
4299  _each: function(iterator) {
4300    this.element.className.split(/\s+/).select(function(name) {
4301      return name.length > 0;
4302    })._each(iterator);
4303  },
4304
4305  set: function(className) {
4306    this.element.className = className;
4307  },
4308
4309  add: function(classNameToAdd) {
4310    if (this.include(classNameToAdd)) return;
4311    this.set($A(this).concat(classNameToAdd).join(' '));
4312  },
4313
4314  remove: function(classNameToRemove) {
4315    if (!this.include(classNameToRemove)) return;
4316    this.set($A(this).without(classNameToRemove).join(' '));
4317  },
4318
4319  toString: function() {
4320    return $A(this).join(' ');
4321  }
4322};
4323
4324Object.extend(Element.ClassNames.prototype, Enumerable);
4325
4326/*--------------------------------------------------------------------------*/
4327
4328Element.addMethods();
4329/*
4330* Really easy field validation with Prototype
4331* http://tetlaw.id.au/view/javascript/really-easy-field-validation
4332* Andrew Tetlaw
4333* Version 1.5.4.1 (2007-01-05)
4334*
4335* Copyright (c) 2007 Andrew Tetlaw
4336* Permission is hereby granted, free of charge, to any person
4337* obtaining a copy of this software and associated documentation
4338* files (the "Software"), to deal in the Software without
4339* restriction, including without limitation the rights to use, copy,
4340* modify, merge, publish, distribute, sublicense, and/or sell copies
4341* of the Software, and to permit persons to whom the Software is
4342* furnished to do so, subject to the following conditions:
4343*
4344* The above copyright notice and this permission notice shall be
4345* included in all copies or substantial portions of the Software.
4346*
4347* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
4348* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
4349* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
4350* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
4351* BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
4352* ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
4353* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
4354* SOFTWARE.
4355*
4356*/
4357var Validator = Class.create();
4358
4359Validator.prototype = {
4360    initialize : function(className, error, test, options) {
4361        if(typeof test == 'function'){
4362            this.options = $H(options);
4363            this._test = test;
4364        } else {
4365            this.options = $H(test);
4366            this._test = function(){return true};
4367        }
4368        this.error = error || 'Validation failed.';
4369        this.className = className;
4370    },
4371    test : function(v, elm) {
4372        return (this._test(v,elm) && this.options.all(function(p){
4373            return Validator.methods[p.key] ? Validator.methods[p.key](v,elm,p.value) : true;
4374        }));
4375    }
4376}
4377Validator.methods = {
4378    pattern : function(v,elm,opt) {return Validation.get('IsEmpty').test(v) || opt.test(v)},
4379    minLength : function(v,elm,opt) {return v.length >= opt},
4380    maxLength : function(v,elm,opt) {return v.length <= opt},
4381    min : function(v,elm,opt) {return v >= parseFloat(opt)},
4382    max : function(v,elm,opt) {return v <= parseFloat(opt)},
4383    notOneOf : function(v,elm,opt) {return $A(opt).all(function(value) {
4384        return v != value;
4385    })},
4386    oneOf : function(v,elm,opt) {return $A(opt).any(function(value) {
4387        return v == value;
4388    })},
4389    is : function(v,elm,opt) {return v == opt},
4390    isNot : function(v,elm,opt) {return v != opt},
4391    equalToField : function(v,elm,opt) {return v == $F(opt)},
4392    notEqualToField : function(v,elm,opt) {return v != $F(opt)},
4393    include : function(v,elm,opt) {return $A(opt).all(function(value) {
4394        return Validation.get(value).test(v,elm);
4395    })}
4396}
4397
4398var Validation = Class.create();
4399Validation.defaultOptions = {
4400    onSubmit : true,
4401    stopOnFirst : false,
4402    immediate : false,
4403    focusOnError : true,
4404    useTitles : false,
4405    addClassNameToContainer: false,
4406    containerClassName: '.input-box',
4407    onFormValidate : function(result, form) {},
4408    onElementValidate : function(result, elm) {}
4409};
4410
4411Validation.prototype = {
4412    initialize : function(form, options){
4413        this.form = $(form);
4414        if (!this.form) {
4415            return;
4416        }
4417        this.options = Object.extend({
4418            onSubmit : Validation.defaultOptions.onSubmit,
4419            stopOnFirst : Validation.defaultOptions.stopOnFirst,
4420            immediate : Validation.defaultOptions.immediate,
4421            focusOnError : Validation.defaultOptions.focusOnError,
4422            useTitles : Validation.defaultOptions.useTitles,
4423            onFormValidate : Validation.defaultOptions.onFormValidate,
4424            onElementValidate : Validation.defaultOptions.onElementValidate
4425        }, options || {});
4426        if(this.options.onSubmit) Event.observe(this.form,'submit',this.onSubmit.bind(this),false);
4427        if(this.options.immediate) {
4428            Form.getElements(this.form).each(function(input) { // Thanks Mike!
4429                if (input.tagName.toLowerCase() == 'select') {
4430                    Event.observe(input, 'blur', this.onChange.bindAsEventListener(this));
4431                }
4432                if (input.type.toLowerCase() == 'radio' || input.type.toLowerCase() == 'checkbox') {
4433                    Event.observe(input, 'click', this.onChange.bindAsEventListener(this));
4434                } else {
4435                    Event.observe(input, 'change', this.onChange.bindAsEventListener(this));
4436                }
4437            }, this);
4438        }
4439    },
4440    onChange : function (ev) {
4441        Validation.isOnChange = true;
4442        Validation.validate(Event.element(ev),{
4443                useTitle : this.options.useTitles,
4444                onElementValidate : this.options.onElementValidate
4445        });
4446        Validation.isOnChange = false;
4447    },
4448    onSubmit :  function(ev){
4449        if(!this.validate()) Event.stop(ev);
4450    },
4451    validate : function() {
4452        var result = false;
4453        var useTitles = this.options.useTitles;
4454        var callback = this.options.onElementValidate;
4455        try {
4456            if(this.options.stopOnFirst) {
4457                result = Form.getElements(this.form).all(function(elm) {
4458                    if (elm.hasClassName('local-validation') && !this.isElementInForm(elm, this.form)) {
4459                        return true;
4460                    }
4461                    return Validation.validate(elm,{useTitle : useTitles, onElementValidate : callback});
4462                }, this);
4463            } else {
4464                result = Form.getElements(this.form).collect(function(elm) {
4465                    if (elm.hasClassName('local-validation') && !this.isElementInForm(elm, this.form)) {
4466                        return true;
4467                    }
4468                    return Validation.validate(elm,{useTitle : useTitles, onElementValidate : callback});
4469                }, this).all();
4470            }
4471        } catch (e) {
4472
4473        }
4474        if(!result && this.options.focusOnError) {
4475            try{
4476                Form.getElements(this.form).findAll(function(elm){return $(elm).hasClassName('validation-failed')}).first().focus()
4477            }
4478            catch(e){
4479
4480            }
4481        }
4482        this.options.onFormValidate(result, this.form);
4483        return result;
4484    },
4485    reset : function() {
4486        Form.getElements(this.form).each(Validation.reset);
4487    },
4488    isElementInForm : function(elm, form) {
4489        var domForm = elm.up('form');
4490        if (domForm == form) {
4491            return true;
4492        }
4493        return false;
4494    }
4495}
4496
4497Object.extend(Validation, {
4498    validate : function(elm, options){
4499        options = Object.extend({
4500            useTitle : false,
4501            onElementValidate : function(result, elm) {}
4502        }, options || {});
4503        elm = $(elm);
4504
4505        var cn = $w(elm.className);
4506        return result = cn.all(function(value) {
4507            var test = Validation.test(value,elm,options.useTitle);
4508            options.onElementValidate(test, elm);
4509            return test;
4510        });
4511    },
4512    insertAdvice : function(elm, advice){
4513        var container = $(elm).up('.field-row');
4514        if(container){
4515            Element.insert(container, {after: advice});
4516        } else if (elm.up('td.value')) {
4517            elm.up('td.value').insert({bottom: advice});
4518        } else if (elm.advaiceContainer && $(elm.advaiceContainer)) {
4519            $(elm.advaiceContainer).update(advice);
4520        }
4521        else {
4522            switch (elm.type.toLowerCase()) {
4523                case 'checkbox':
4524                case 'radio':
4525                    var p = elm.parentNode;
4526                    if(p) {
4527                        Element.insert(p, {'bottom': advice});
4528                    } else {
4529                        Element.insert(elm, {'after': advice});
4530                    }
4531                    break;
4532                default:
4533                    Element.insert(elm, {'after': advice});
4534            }
4535        }
4536    },
4537    showAdvice : function(elm, advice, adviceName){
4538        if(!elm.advices){
4539            elm.advices = new Hash();
4540        }
4541        else{
4542            elm.advices.each(function(pair){
4543                this.hideAdvice(elm, pair.value);
4544            }.bind(this));
4545        }
4546        elm.advices.set(adviceName, advice);
4547        if(typeof Effect == 'undefined') {
4548            advice.style.display = 'block';
4549        } else {
4550            if(!advice._adviceAbsolutize) {
4551                new Effect.Appear(advice, {duration : 1 });
4552            } else {
4553                Position.absolutize(advice);
4554                advice.show();
4555                advice.setStyle({
4556                    'top':advice._adviceTop,
4557                    'left': advice._adviceLeft,
4558                    'width': advice._adviceWidth,
4559                    'z-index': 1000
4560                });
4561                advice.addClassName('advice-absolute');
4562            }
4563        }
4564    },
4565    hideAdvice : function(elm, advice){
4566        if(advice != null) advice.hide();
4567    },
4568    updateCallback : function(elm, status) {
4569        if (typeof elm.callbackFunction != 'undefined') {
4570            eval(elm.callbackFunction+'(\''+elm.id+'\',\''+status+'\')');
4571        }
4572    },
4573    ajaxError : function(elm, errorMsg) {
4574        var name = 'validate-ajax';
4575        var advice = Validation.getAdvice(name, elm);
4576        if (advice == null) {
4577            advice = this.createAdvice(name, elm, false, errorMsg);
4578        }
4579        this.showAdvice(elm, advice, 'validate-ajax');
4580        this.updateCallback(elm, 'failed');
4581
4582        elm.addClassName('validation-failed');
4583        elm.addClassName('validate-ajax');
4584        if (Validation.defaultOptions.addClassNameToContainer && Validation.defaultOptions.containerClassName != '') {
4585            var container = elm.up(Validation.defaultOptions.containerClassName);
4586            if (container && this.allowContainerClassName(elm)) {
4587                container.removeClassName('validation-passed');
4588                container.addClassName('validation-error');
4589            }
4590        }
4591    },
4592    allowContainerClassName: function (elm) {
4593        if (elm.type == 'radio' || elm.type == 'checkbox') {
4594            return elm.hasClassName('change-container-classname');
4595        }
4596
4597        return true;
4598    },
4599    test : function(name, elm, useTitle) {
4600        var v = Validation.get(name);
4601        var prop = '__advice'+name.camelize();
4602        try {
4603        if(Validation.isVisible(elm) && !v.test($F(elm), elm)) {
4604            //if(!elm[prop]) {
4605                var advice = Validation.getAdvice(name, elm);
4606                if (advice == null) {
4607                    advice = this.createAdvice(name, elm, useTitle);
4608                }
4609                this.showAdvice(elm, advice, name);
4610                this.updateCallback(elm, 'failed');
4611            //}
4612            elm[prop] = 1;
4613            if (!elm.advaiceContainer) {
4614                elm.removeClassName('validation-passed');
4615                elm.addClassName('validation-failed');
4616            }
4617
4618           if (Validation.defaultOptions.addClassNameToContainer && Validation.defaultOptions.containerClassName != '') {
4619                var container = elm.up(Validation.defaultOptions.containerClassName);
4620                if (container && this.allowContainerClassName(elm)) {
4621                    container.removeClassName('validation-passed');
4622                    container.addClassName('validation-error');
4623                }
4624            }
4625            return false;
4626        } else {
4627            var advice = Validation.getAdvice(name, elm);
4628            this.hideAdvice(elm, advice);
4629            this.updateCallback(elm, 'passed');
4630            elm[prop] = '';
4631            elm.removeClassName('validation-failed');
4632            elm.addClassName('validation-passed');
4633            if (Validation.defaultOptions.addClassNameToContainer && Validation.defaultOptions.containerClassName != '') {
4634                var container = elm.up(Validation.defaultOptions.containerClassName);
4635                if (container && !container.down('.validation-failed') && this.allowContainerClassName(elm)) {
4636                    if (!Validation.get('IsEmpty').test(elm.value) || !this.isVisible(elm)) {
4637                        container.addClassName('validation-passed');
4638                    } else {
4639                        container.removeClassName('validation-passed');
4640                    }
4641                    container.removeClassName('validation-error');
4642                }
4643            }
4644            return true;
4645        }
4646        } catch(e) {
4647            throw(e)
4648        }
4649    },
4650    isVisible : function(elm) {
4651        while(elm.tagName != 'BODY') {
4652            if(!$(elm).visible()) return false;
4653            elm = elm.parentNode;
4654        }
4655        return true;
4656    },
4657    getAdvice : function(name, elm) {
4658        return $('advice-' + name + '-' + Validation.getElmID(elm)) || $('advice-' + Validation.getElmID(elm));
4659    },
4660    createAdvice : function(name, elm, useTitle, customError) {
4661        var v = Validation.get(name);
4662        var errorMsg = useTitle ? ((elm && elm.title) ? elm.title : v.error) : v.error;
4663        if (customError) {
4664            errorMsg = customError;
4665        }
4666        try {
4667            if (Translator){
4668                errorMsg = Translator.translate(errorMsg);
4669            }
4670        }
4671        catch(e){}
4672
4673        advice = '<div class="validation-advice" id="advice-' + name + '-' + Validation.getElmID(elm) +'" style="display:none">' + errorMsg + '</div>'
4674
4675
4676        Validation.insertAdvice(elm, advice);
4677        advice = Validation.getAdvice(name, elm);
4678        if($(elm).hasClassName('absolute-advice')) {
4679            var dimensions = $(elm).getDimensions();
4680            var originalPosition = Position.cumulativeOffset(elm);
4681
4682            advice._adviceTop = (originalPosition[1] + dimensions.height) + 'px';
4683            advice._adviceLeft = (originalPosition[0])  + 'px';
4684            advice._adviceWidth = (dimensions.width)  + 'px';
4685            advice._adviceAbsolutize = true;
4686        }
4687        return advice;
4688    },
4689    getElmID : function(elm) {
4690        return elm.id ? elm.id : elm.name;
4691    },
4692    reset : function(elm) {
4693        elm = $(elm);
4694        var cn = $w(elm.className);
4695        cn.each(function(value) {
4696            var prop = '__advice'+value.camelize();
4697            if(elm[prop]) {
4698                var advice = Validation.getAdvice(value, elm);
4699                if (advice) {
4700                    advice.hide();
4701                }
4702                elm[prop] = '';
4703            }
4704            elm.removeClassName('validation-failed');
4705            elm.removeClassName('validation-passed');
4706            if (Validation.defaultOptions.addClassNameToContainer && Validation.defaultOptions.containerClassName != '') {
4707                var container = elm.up(Validation.defaultOptions.containerClassName);
4708                if (container) {
4709                    container.removeClassName('validation-passed');
4710                    container.removeClassName('validation-error');
4711                }
4712            }
4713        });
4714    },
4715    add : function(className, error, test, options) {
4716        var nv = {};
4717        nv[className] = new Validator(className, error, test, options);
4718        Object.extend(Validation.methods, nv);
4719    },
4720    addAllThese : function(validators) {
4721        var nv = {};
4722        $A(validators).each(function(value) {
4723                nv[value[0]] = new Validator(value[0], value[1], value[2], (value.length > 3 ? value[3] : {}));
4724            });
4725        Object.extend(Validation.methods, nv);
4726    },
4727    get : function(name) {
4728        return  Validation.methods[name] ? Validation.methods[name] : Validation.methods['_LikeNoIDIEverSaw_'];
4729    },
4730    methods : {
4731        '_LikeNoIDIEverSaw_' : new Validator('_LikeNoIDIEverSaw_','',{})
4732    }
4733});
4734
4735Validation.add('IsEmpty', '', function(v) {
4736    return  (v == '' || (v == null) || (v.length == 0) || /^\s+$/.test(v)); // || /^\s+$/.test(v));
4737});
4738
4739Validation.addAllThese([
4740    ['validate-select', 'Please select an option.', function(v) {
4741                return ((v != "none") && (v != null) && (v.length != 0));
4742            }],
4743    ['required-entry', 'This is a required field.', function(v) {
4744                return !Validation.get('IsEmpty').test(v);
4745            }],
4746    ['validate-number', 'Please enter a valid number in this field.', function(v) {
4747                return Validation.get('IsEmpty').test(v) || (!isNaN(parseNumber(v)) && !/^\s+$/.test(parseNumber(v)));
4748            }],
4749    ['validate-digits', 'Please use numbers only in this field. please avoid spaces or other characters such as dots or commas.', function(v) {
4750                return Validation.get('IsEmpty').test(v) ||  !/[^\d]/.test(v);
4751            }],
4752    ['validate-alpha', 'Please use letters only (a-z or A-Z) in this field.', function (v) {
4753                return Validation.get('IsEmpty').test(v) ||  /^[a-zA-Z]+$/.test(v)
4754            }],
4755    ['validate-code', 'Please use only letters (a-z), numbers (0-9) or underscore(_) in this field, first character should be a letter.', function (v) {
4756                return Validation.get('IsEmpty').test(v) ||  /^[a-z]+[a-z0-9_]+$/.test(v)
4757            }],
4758    ['validate-alphanum', 'Please use only letters (a-z or A-Z) or numbers (0-9) only in this field. No spaces or other characters are allowed.', function(v) {
4759                return Validation.get('IsEmpty').test(v) ||  /^[a-zA-Z0-9]+$/.test(v) /*!/\W/.test(v)*/
4760            }],
4761    ['validate-street', 'Please use only letters (a-z or A-Z) or numbers (0-9) or spaces and # only in this field.', function(v) {
4762                return Validation.get('IsEmpty').test(v) ||  /^[ \w]{3,}([A-Za-z]\.)?([ \w]*\#\d+)?(\r\n| )[ \w]{3,}/.test(v)
4763            }],
4764    ['validate-phoneStrict', 'Please enter a valid phone number. For example (123) 456-7890 or 123-456-7890.', function(v) {
4765                return Validation.get('IsEmpty').test(v) || /^(\()?\d{3}(\))?(-|\s)?\d{3}(-|\s)\d{4}$/.test(v);
4766            }],
4767    ['validate-phoneLax', 'Please enter a valid phone number. For example (123) 456-7890 or 123-456-7890.', function(v) {
4768                return Validation.get('IsEmpty').test(v) || /^((\d[-. ]?)?((\(\d{3}\))|\d{3}))?[-. ]?\d{3}[-. ]?\d{4}$/.test(v);
4769            }],
4770    ['validate-fax', 'Please enter a valid fax number. For example (123) 456-7890 or 123-456-7890.', function(v) {
4771                return Validation.get('IsEmpty').test(v) || /^(\()?\d{3}(\))?(-|\s)?\d{3}(-|\s)\d{4}$/.test(v);
4772            }],
4773    ['validate-date', 'Please enter a valid date.', function(v) {
4774                var test = new Date(v);
4775                return Validation.get('IsEmpty').test(v) || !isNaN(test);
4776            }],
4777    ['validate-email', 'Please enter a valid email address. For example [email protected].', function (v) {
4778                //return Validation.get('IsEmpty').test(v) || /\w{1,}[@][\w\-]{1,}([.]([\w\-]{1,})){1,3}$/.test(v)
4779                //return Validation.get('IsEmpty').test(v) || /^[\!\#$%\*/?|\^\{\}`~&\'\+\-=_a-z0-9][\!\#$%\*/?|\^\{\}`~&\'\+\-=_a-z0-9\.]{1,30}[\!\#$%\*/?|\^\{\}`~&\'\+\-=_a-z0-9]@([a-z0-9_-]{1,30}\.){1,5}[a-z]{2,4}$/i.test(v)
4780                return Validation.get('IsEmpty').test(v) || /^[a-z0-9,!\#\$%&'\*\+\/=\?\^_`\{\|\}~-]+(\.[a-z0-9,!\#\$%&'\*\+\/=\?\^_`\{\|\}~-]+)*@[a-z0-9-]+(\.[a-z0-9-]+)*\.([a-z]{2,})/i.test(v)
4781            }],
4782    ['validate-emailSender', 'Please use only visible characters and spaces.', function (v) {
4783                return Validation.get('IsEmpty').test(v) ||  /^[\S ]+$/.test(v)
4784                    }],
4785    ['validate-password', 'Please enter 6 or more characters. Leading or trailing space
4785s will be ignored.', function(v) {
4786                var pass=v.strip(); /*strip leading and trailing spaces*/
4787                return !(pass.length>0 && pass.length < 6);
4788            }],
4789    ['validate-admin-password', 'Please enter 7 or more characters. Password should contain both numeric and alphabetic characters.', function(v) {
4790                var pass=v.strip();
4791                if (0 == pass.length) {
4792                    return true;
4793                }
4794                if (!(/[a-z]/i.test(v)) || !(/[0-9]/.test(v))) {
4795                    return false;
4796                }
4797                return !(pass.length < 7);
4798            }],
4799    ['validate-cpassword', 'Please make sure your passwords match.', function(v) {
4800                var conf = $('confirmation') ? $('confirmation') : $$('.validate-cpassword')[0];
4801                var pass = false;
4802                if ($('password')) {
4803                    pass = $('password');
4804                }
4805                var passwordElements = $$('.validate-password');
4806                for (var i = 0; i < passwordElements.size(); i++) {
4807                    var passwordElement = passwordElements[i];
4808                    if (passwordElement.up('form').id == conf.up('form').id) {
4809                        pass = passwordElement;
4810                    }
4811                }
4812                if ($$('.validate-admin-password').size()) {
4813                    pass = $$('.validate-admin-password')[0];
4814                }
4815                return (pass.value == conf.value);
4816            }],
4817    ['validate-url', 'Please enter a valid URL. http:// is required', function (v) {
4818                return Validation.get('IsEmpty').test(v) || /^(http|https|ftp):\/\/(([A-Z0-9][A-Z0-9_-]*)(\.[A-Z0-9][A-Z0-9_-]*)+)(:(\d+))?\/?/i.test(v)
4819            }],
4820    ['validate-clean-url', 'Please enter a valid URL. For example http://www.example.com or www.example.com', function (v) {
4821                return Validation.get('IsEmpty').test(v) || /^(http|https|ftp):\/\/(([A-Z0-9][A-Z0-9_-]*)(\.[A-Z0-9][A-Z0-9_-]*)+.(com|org|net|dk|at|us|tv|info|uk|co.uk|biz|se)$)(:(\d+))?\/?/i.test(v) || /^(www)((\.[A-Z0-9][A-Z0-9_-]*)+.(com|org|net|dk|at|us|tv|info|uk|co.uk|biz|se)$)(:(\d+))?\/?/i.test(v)
4822            }],
4823    ['validate-identifier', 'Please enter a valid URL Key. For example "example-page", "example-page.html" or "anotherlevel/example-page"', function (v) {
4824                return Validation.get('IsEmpty').test(v) || /^[A-Z0-9][A-Z0-9_\/-]+(\.[A-Z0-9_-]+)*$/i.test(v)
4825            }],
4826    ['validate-xml-identifier', 'Please enter a valid XML-identifier. For example something_1, block5, id-4', function (v) {
4827                return Validation.get('IsEmpty').test(v) || /^[A-Z][A-Z0-9_\/-]*$/i.test(v)
4828            }],
4829    ['validate-ssn', 'Please enter a valid social security number. For example 123-45-6789.', function(v) {
4830            return Validation.get('IsEmpty').test(v) || /^\d{3}-?\d{2}-?\d{4}$/.test(v);
4831            }],
4832    ['validate-zip', 'Please enter a valid zip code. For example 90602 or 90602-1234.', function(v) {
4833            return Validation.get('IsEmpty').test(v) || /(^\d{5}$)|(^\d{5}-\d{4}$)/.test(v);
4834            }],
4835    ['validate-zip-international', 'Please enter a valid zip code.', function(v) {
4836            //return Validation.get('IsEmpty').test(v) || /(^[A-z0-9]{2,10}([\s]{0,1}|[\-]{0,1})[A-z0-9]{2,10}$)/.test(v);
4837            return true;
4838            }],
4839    ['validate-date-au', 'Please use this date format: dd/mm/yyyy. For example 17/03/2006 for the 17th of March, 2006.', function(v) {
4840                if(Validation.get('IsEmpty').test(v)) return true;
4841                var regex = /^(\d{2})\/(\d{2})\/(\d{4})$/;
4842                if(!regex.test(v)) return false;
4843                var d = new Date(v.replace(regex, '$2/$1/$3'));
4844                return ( parseInt(RegExp.$2, 10) == (1+d.getMonth()) ) &&
4845                            (parseInt(RegExp.$1, 10) == d.getDate()) &&
4846                            (parseInt(RegExp.$3, 10) == d.getFullYear() );
4847            }],
4848    ['validate-currency-dollar', 'Please enter a valid $ amount. For example $100.00.', function(v) {
4849                // [$]1[##][,###]+[.##]
4850                // [$]1###+[.##]
4851                // [$]0.##
4852                // [$].##
4853                return Validation.get('IsEmpty').test(v) ||  /^\$?\-?([1-9]{1}[0-9]{0,2}(\,[0-9]{3})*(\.[0-9]{0,2})?|[1-9]{1}\d*(\.[0-9]{0,2})?|0(\.[0-9]{0,2})?|(\.[0-9]{1,2})?)$/.test(v)
4854            }],
4855    ['validate-one-required', 'Please select one of the above options.', function (v,elm) {
4856                var p = elm.parentNode;
4857                var options = p.getElementsByTagName('INPUT');
4858                return $A(options).any(function(elm) {
4859                    return $F(elm);
4860                });
4861            }],
4862    ['validate-one-required-by-name', 'Please select one of the options.', function (v,elm) {
4863                var inputs = $$('input[name="' + elm.name.replace(/([\\"])/g, '\\$1') + '"]');
4864
4865                var error = 1;
4866                for(var i=0;i<inputs.length;i++) {
4867                    if((inputs[i].type == 'checkbox' || inputs[i].type == 'radio') && inputs[i].checked == true) {
4868                        error = 0;
4869                    }
4870
4871                    if(Validation.isOnChange && (inputs[i].type == 'checkbox' || inputs[i].type == 'radio')) {
4872                        Validation.reset(inputs[i]);
4873                    }
4874                }
4875
4876                if( error == 0 ) {
4877                    return true;
4878                } else {
4879                    return false;
4880                }
4881            }],
4882    ['validate-not-negative-number', 'Please enter a valid number in this field.', function(v) {
4883                v = parseNumber(v);
4884                return (!isNaN(v) && v>=0);
4885            }],
4886    ['validate-state', 'Please select State/Province.', function(v) {
4887                return (v!=0 || v == '');
4888            }],
4889
4890    ['validate-new-password', 'Please enter 6 or more characters. Leading or trailing spaces will be ignored.', function(v) {
4891                if (!Validation.get('validate-password').test(v)) return false;
4892                if (Validation.get('IsEmpty').test(v) && v != '') return false;
4893                return true;
4894            }],
4895    ['validate-greater-than-zero', 'Please enter a number greater than 0 in this field.', function(v) {
4896                if(v.length)
4897                    return parseFloat(v) > 0;
4898                else
4899                    return true;
4900            }],
4901    ['validate-zero-or-greater', 'Please enter a number 0 or greater in this field.', function(v) {
4902                if(v.length)
4903                    return parseFloat(v) >= 0;
4904                else
4905                    return true;
4906            }],
4907    ['validate-cc-number', 'Please enter a valid credit card number.', function(v, elm) {
4908                // remove non-numerics
4909                var ccTypeContainer = $(elm.id.substr(0,elm.id.indexOf('_cc_number')) + '_cc_type');
4910                if (ccTypeContainer && typeof Validation.creditCartTypes.get(ccTypeContainer.value) != 'undefined'
4911                        && Validation.creditCartTypes.get(ccTypeContainer.value)[2] == false) {
4912                    if (!Validation.get('IsEmpty').test(v) && Validation.get('validate-digits').test(v)) {
4913                        return true;
4914                    } else {
4915                        return false;
4916                    }
4917                }
4918                return validateCreditCard(v);
4919            }],
4920    ['validate-cc-type', 'Credit card number doesn\'t match credit card type', function(v, elm) {
4921                // remove credit card number delimiters such as "-" and space
4922                elm.value = removeDelimiters(elm.value);
4923                v         = removeDelimiters(v);
4924
4925                var ccTypeContainer = $(elm.id.substr(0,elm.id.indexOf('_cc_number')) + '_cc_type');
4926                if (!ccTypeContainer) {
4927                    return true;
4928                }
4929                var ccType = ccTypeContainer.value;
4930
4931                if (typeof Validation.creditCartTypes.get(ccType) == 'undefined') {
4932                    return false;
4933                }
4934
4935                // Other card type or switch or solo card
4936                if (Validation.creditCartTypes.get(ccType)[0]==false) {
4937                    return true;
4938                }
4939
4940                // Matched credit card type
4941                var ccMatchedType = '';
4942
4943                Validation.creditCartTypes.each(function (pair) {
4944                    if (pair.value[0] && v.match(pair.value[0])) {
4945                        ccMatchedType = pair.key;
4946                        throw $break;
4947                    }
4948                });
4949
4950                if(ccMatchedType != ccType) {
4951                    return false;
4952                }
4953
4954                if (ccTypeContainer.hasClassName('validation-failed') && Validation.isOnChange) {
4955                    Validation.validate(ccTypeContainer);
4956                }
4957
4958                return true;
4959            }],
4960     ['validate-cc-type-select', 'Card type doesn\'t match credit card number', function(v, elm) {
4961                var ccNumberContainer = $(elm.id.substr(0,elm.id.indexOf('_cc_type')) + '_cc_number');
4962                if (Validation.isOnChange && Validation.get('IsEmpty').test(ccNumberContainer.value)) {
4963                    return true;
4964                }
4965                if (Validation.get('validate-cc-type').test(ccNumberContainer.value, ccNumberContainer)) {
4966                    Validation.validate(ccNumberContainer);
4967                }
4968                return Validation.get('validate-cc-type').test(ccNumberContainer.value, ccNumberContainer);
4969            }],
4970     ['validate-cc-exp', 'Incorrect credit card expiration date', function(v, elm) {
4971                var ccExpMonth   = v;
4972                var ccExpYear    = $(elm.id.substr(0,elm.id.indexOf('_expiration')) + '_expiration_yr').value;
4973                var currentTime  = new Date();
4974                var currentMonth = currentTime.getMonth() + 1;
4975                var currentYear  = currentTime.getFullYear();
4976                if (ccExpMonth < currentMonth && ccExpYear == currentYear) {
4977                    return false;
4978                }
4979                return true;
4980            }],
4981     ['validate-cc-cvn', 'Please enter a valid credit card verification number.', function(v, elm) {
4982                var ccTypeContainer = $(elm.id.substr(0,elm.id.indexOf('_cc_cid')) + '_cc_type');
4983                if (!ccTypeContainer) {
4984                    return true;
4985                }
4986                var ccType = ccTypeContainer.value;
4987
4988                if (typeof Validation.creditCartTypes.get(ccType) == 'undefined') {
4989                    return false;
4990                }
4991
4992                var re = Validation.creditCartTypes.get(ccType)[1];
4993
4994                if (v.match(re)) {
4995                    return true;
4996                }
4997
4998                return false;
4999            }],
5000     ['validate-ajax', '', function(v, elm) { return true; }],
5001     ['validate-data', 'Please use only letters (a-z or A-Z), numbers (0-9) or underscore(_) in this field, first character should be a letter.', function (v) {
5002                if(v != '' && v) {
5003                    return /^[A-Za-z]+[A-Za-z0-9_]+$/.test(v);
5004                }
5005                return true;
5006            }],
5007     ['validate-css-length', 'Please input a valid CSS-length. For example 100px or 77pt or 20em or .5ex or 50%', function (v) {
5008                if (v != '' && v) {
5009                    return /^[0-9\.]+(px|pt|em|ex|%)?$/.test(v) && (!(/\..*\./.test(v))) && !(/\.$/.test(v));
5010                }
5011                return true;
5012            }],
5013     ['validate-length', 'Maximum length exceeded.', function (v, elm) {
5014                var re = new RegExp(/^maximum-length-[0-9]+$/);
5015                var result = true;
5016                $w(elm.className).each(function(name, index) {
5017                        if (name.match(re) && result) {
5018                           var length = name.split('-')[2];
5019                           result = (v.length <= length);
5020                        }
5021                    });
5022                return result;
5023            }],
5024     ['validate-percents', 'Please enter a number lower than 100', {max:100}],
5025    ['validate-captcha', 'Incorrect solution, please try again.', function (v, elm) {
5026        var res = $$('label[for=captcha]')[0].innerText.match(/Solve the problem: (\d) \+ (\d)/);
5027        return parseInt(v) == parseInt(res[1]) + parseInt(res[2]);
5028    }]
5029]);
5030
5031// Credit Card Validation Javascript
5032// copyright 12th May 2003, by Stephen Chapman, Felgall Pty Ltd
5033
5034// You have permission to copy and use this javascript provided that
5035// the content of the script is not changed in any way.
5036
5037function validateCreditCard(s) {
5038    // remove non-numerics
5039    var v = "0123456789";
5040    var w = "";
5041    for (i=0; i < s.length; i++) {
5042        x = s.charAt(i);
5043        if (v.indexOf(x,0) != -1)
5044        w += x;
5045    }
5046    // validate number
5047    j = w.length / 2;
5048    k = Math.floor(j);
5049    m = Math.ceil(j) - k;
5050    c = 0;
5051    for (i=0; i<k; i++) {
5052        a = w.charAt(i*2+m) * 2;
5053        c += a > 9 ? Math.floor(a/10 + a%10) : a;
5054    }
5055    for (i=0; i<k+m; i++) c += w.charAt(i*2+1-m) * 1;
5056    return (c%10 == 0);
5057}
5058
5059function removeDelimiters (v) {
5060    v = v.replace(/\s/g, '');
5061    v = v.replace(/\-/g, '');
5062    return v;
5063}
5064
5065function parseNumber(v)
5066{
5067    if (typeof v != 'string') {
5068        return parseFloat(v);
5069    }
5070
5071    var isDot  = v.indexOf('.');
5072    var isComa = v.indexOf(',');
5073
5074    if (isDot != -1 && isComa != -1) {
5075        if (isComa > isDot) {
5076            v = v.replace('.', '').replace(',', '.');
5077        }
5078        else {
5079            v = v.replace(',', '');
5080        }
5081    }
5082    else if (isComa != -1) {
5083        v = v.replace(',', '.');
5084    }
5085
5086    return parseFloat(v);
5087}
5088
5089/**
5090 * Hash with credit card types wich can be simply extended in payment modules
5091 * 0 - regexp for card number
5092 * 1 - regexp for cvn
5093 * 2 - check or not credit card number trough Luhn algorithm by
5094 *     function validateCreditCard wich you can find above in this file
5095 */
5096Validation.creditCartTypes = $H({
5097    'VI': [new RegExp('^4[0-9]{12}([0-9]{3})?$'), new RegExp('^[0-9]{3}$'), true],
5098    'MC': [new RegExp('^(5[1-5][0-9]{14}|2(22[1-9][0-9]{12}|2[3-9][0-9]{13}|[3-6][0-9]{14}|7[0-1][0-9]{13}|720[0-9]{12}))$'), new RegExp('^[0-9]{3}$'), true],
5099    'AE': [new RegExp('^3[47][0-9]{13}$'), new RegExp('^[0-9]{4}$'), true],
5100    'DI': [new RegExp('^6011[0-9]{12}$'), new RegExp('^[0-9]{3}$'), true],
5101    'SS': [new RegExp('^((6759[0-9]{12})|(49[013][1356][0-9]{13})|(633[34][0-9]{12})|(633110[0-9]{10})|(564182[0-9]{10}))([0-9]{2,3})?$'), new RegExp('^([0-9]{3}|[0-9]{4})?$'), true],
5102    'OT': [false, new RegExp('^([0-9]{3}|[0-9]{4})?$'), false]
5103});
5104
5105
5106
5107// script.aculo.us builder.js v1.8.2, Tue Nov 18 18:30:58 +0100 2008
5108
5109// Copyright (c) 2005-2008 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us)
5110//
5111// script.aculo.us is freely distributable under the terms of an MIT-style license.
5112// For details, see the script.aculo.us web site: http://script.aculo.us/
5113
5114var Builder = {
5115  NODEMAP: {
5116    AREA: 'map',
5117    CAPTION: 'table',
5118    COL: 'table',
5119    COLGROUP: 'table',
5120    LEGEND: 'fieldset',
5121    OPTGROUP: 'select',
5122    OPTION: 'select',
5123    PARAM: 'object',
5124    TBODY: 'table',
5125    TD: 'table',
5126    TFOOT: 'table',
5127    TH: 'table',
5128    THEAD: 'table',
5129    TR: 'table'
5130  },
5131  // note: For Firefox < 1.5, OPTION and OPTGROUP tags are currently broken,
5132  //       due to a Firefox bug
5133  node: function(elementName) {
5134    elementName = elementName.toUpperCase();
5135
5136    // try innerHTML approach
5137    var parentTag = this.NODEMAP[elementName] || 'div';
5138    var parentElement = document.createElement(parentTag);
5139    try { // prevent IE "feature": http://dev.rubyonrails.org/ticket/2707
5140      parentElement.innerHTML = "<" + elementName + "></" + elementName + ">";
5141    } catch(e) {}
5142    var element = parentElement.firstChild || null;
5143
5144    // see if browser added wrapping tags
5145    if(element && (element.tagName.toUpperCase() != elementName))
5146      element = element.getElementsByTagName(elementName)[0];
5147
5148    // fallback to createElement approach
5149    if(!element) element = document.createElement(elementName);
5150
5151    // abort if nothing could be created
5152    if(!element) return;
5153
5154    // attributes (or text)
5155    if(arguments[1])
5156      if(this._isStringOrNumber(arguments[1]) ||
5157        (arguments[1] instanceof Array) ||
5158        arguments[1].tagName) {
5159          this._children(element, arguments[1]);
5160        } else {
5161          var attrs = this._attributes(arguments[1]);
5162          if(attrs.length) {
5163            try { // prevent IE "feature": http://dev.rubyonrails.org/ticket/2707
5164              parentElement.innerHTML = "<" +elementName + " " +
5165                attrs + "></" + elementName + ">";
5166            } catch(e) {}
5167            element = parentElement.firstChild || null;
5168            // workaround firefox 1.0.X bug
5169            if(!element) {
5170              element = document.createElement(elementName);
5171              for(attr in arguments[1])
5172                element[attr == 'class' ? 'className' : attr] = arguments[1][attr];
5173            }
5174            if(element.tagName.toUpperCase() != elementName)
5175              element = parentElement.getElementsByTagName(elementName)[0];
5176          }
5177        }
5178
5179    // text, or array of children
5180    if(arguments[2])
5181      this._children(element, arguments[2]);
5182
5183     return $(element);
5184  },
5185  _text: function(text) {
5186     return document.createTextNode(text);
5187  },
5188
5189  ATTR_MAP: {
5190    'className': 'class',
5191    'htmlFor': 'for'
5192  },
5193
5194  _attributes: function(attributes) {
5195    var attrs = [];
5196    for(attribute in attributes)
5197      attrs.push((attribute in this.ATTR_MAP ? this.ATTR_MAP[attribute] : attribute) +
5198          '="' + attributes[attribute].toString().escapeHTML().gsub(/"/,'&quot;') + '"');
5199    return attrs.join(" ");
5200  },
5201  _children: function(element, children) {
5202    if(children.tagName) {
5203      element.appendChild(children);
5204      return;
5205    }
5206    if(typeof children=='object') { // array can hold nodes and text
5207      children.flatten().each( function(e) {
5208        if(typeof e=='object')
5209          element.appendChild(e);
5210        else
5211          if(Builder._isStringOrNumber(e))
5212            element.appendChild(Builder._text(e));
5213      });
5214    } else
5215      if(Builder._isStringOrNumber(children))
5216        element.appendChild(Builder._text(children));
5217  },
5218  _isStringOrNumber: function(param) {
5219    return(typeof param=='string' || typeof param=='number');
5220  },
5221  build: function(html) {
5222    var element = this.node('div');
5223    $(element).update(html.strip());
5224    return element.down();
5225  },
5226  dump: function(scope) {
5227    if(typeof scope != 'object' && typeof scope != 'function') scope = window; //global scope
5228
5229    var tags = ("A ABBR ACRONYM ADDRESS APPLET AREA B BASE BASEFONT BDO BIG BLOCKQUOTE BODY " +
5230      "BR BUTTON CAPTION CENTER CITE CODE COL COLGROUP DD DEL DFN DIR DIV DL DT EM FIELDSET " +
5231      "FONT FORM FRAME FRAMESET H1 H2 H3 H4 H5 H6 HEAD HR HTML I IFRAME IMG INPUT INS ISINDEX "+
5232      "KBD LABEL LEGEND LI LINK MAP MENU META NOFRAMES NOSCRIPT OBJECT OL OPTGROUP OPTION P "+
5233      "PARAM PRE Q S SAMP SCRIPT SELECT SMALL SPAN STRIKE STRONG STYLE SUB SUP TABLE TBODY TD "+
5234      "TEXTAREA TFOOT TH THEAD TITLE TR TT U UL VAR").split(/\s+/);
5235
5236    tags.each( function(tag){
5237      scope[tag] = function() {
5238        return Builder.node.apply(Builder, [tag].concat($A(arguments)));
5239      };
5240    });
5241  }
5242};
5243// script.aculo.us effects.js v1.8.2, Tue Nov 18 18:30:58 +0100 2008
5244
5245// Copyright (c) 2005-2008 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us)
5246// Contributors:
5247//  Justin Palmer (http://encytemedia.com/)
5248//  Mark Pilgrim (http://diveintomark.org/)
5249//  Martin Bialasinki
5250//
5251// script.aculo.us is freely distributable under the terms of an MIT-style license.
5252// For details, see the script.aculo.us web site: http://script.aculo.us/
5253
5254// converts rgb() and #xxx to #xxxxxx format,
5255// returns self (or first argument) if not convertable
5256String.prototype.parseColor = function() {
5257  var color = '#';
5258  if (this.slice(0,4) == 'rgb(') {
5259    var cols = this.slice(4,this.length-1).split(',');
5260    var i=0; do { color += parseInt(cols[i]).toColorPart() } while (++i<3);
5261  } else {
5262    if (this.slice(0,1) == '#') {
5263      if (this.length==4) for(var i=1;i<4;i++) color += (this.charAt(i) + this.charAt(i)).toLowerCase();
5264      if (this.length==7) color = this.toLowerCase();
5265    }
5266  }
5267  return (color.length==7 ? color : (arguments[0] || this));
5268};
5269
5270/*--------------------------------------------------------------------------*/
5271
5272Element.collectTextNodes = function(element) {
5273  return $A($(element).childNodes).collect( function(node) {
5274    return (node.nodeType==3 ? node.nodeValue :
5275      (node.hasChildNodes() ? Element.collectTextNodes(node) : ''));
5276  }).flatten().join('');
5277};
5278
5279Element.collectTextNodesIgnoreClass = function(element, className) {
5280  return $A($(element).childNodes).collect( function(node) {
5281    return (node.nodeType==3 ? node.nodeValue :
5282      ((node.hasChildNodes() && !Element.hasClassName(node,className)) ?
5283        Element.collectTextNodesIgnoreClass(node, className) : ''));
5284  }).flatten().join('');
5285};
5286
5287Element.setContentZoom = function(element, percent) {
5288  element = $(element);
5289  element.setStyle({fontSize: (percent/100) + 'em'});
5290  if (Prototype.Browser.WebKit) window.scrollBy(0,0);
5291  return element;
5292};
5293
5294Element.getInlineOpacity = function(element){
5295  return $(element).style.opacity || '';
5296};
5297
5298Element.forceRerendering = function(element) {
5299  try {
5300    element = $(element);
5301    var n = document.createTextNode(' ');
5302    element.appendChild(n);
5303    element.removeChild(n);
5304  } catch(e) { }
5305};
5306
5307/*--------------------------------------------------------------------------*/
5308
5309var Effect = {
5310  _elementDoesNotExistError: {
5311    name: 'ElementDoesNotExistError',
5312    message: 'The specified DOM element does not exist, but is required for this effect to operate'
5313  },
5314  Transitions: {
5315    linear: Prototype.K,
5316    sinoidal: function(pos) {
5317      return (-Math.cos(pos*Math.PI)/2) + .5;
5318    },
5319    reverse: function(pos) {
5320      return 1-pos;
5321    },
5322    flicker: function(pos) {
5323      var pos = ((-Math.cos(pos*Math.PI)/4) + .75) + Math.random()/4;
5324      return pos > 1 ? 1 : pos;
5325    },
5326    wobble: function(pos) {
5327      return (-Math.cos(pos*Math.PI*(9*pos))/2) + .5;
5328    },
5329    pulse: function(pos, pulses) {
5330      return (-Math.cos((pos*((pulses||5)-.5)*2)*Math.PI)/2) + .5;
5331    },
5332    spring: function(pos) {
5333      return 1 - (Math.cos(pos * 4.5 * Math.PI) * Math.exp(-pos * 6));
5334    },
5335    none: function(pos) {
5336      return 0;
5337    },
5338    full: function(pos) {
5339      return 1;
5340    }
5341  },
5342  DefaultOptions: {
5343    duration:   1.0,   // seconds
5344    fps:        100,   // 100= assume 66fps max.
5345    sync:       false, // true for combining
5346    from:       0.0,
5347    to:         1.0,
5348    delay:      0.0,
5349    queue:      'parallel'
5350  },
5351  tagifyText: function(element) {
5352    var tagifyStyle = 'position:relative';
5353    if (Prototype.Browser.IE) tagifyStyle += ';zoom:1';
5354
5355    element = $(element);
5356    $A(element.childNodes).each( function(child) {
5357      if (child.nodeType==3) {
5358        child.nodeValue.toArray().each( function(character) {
5359          element.insertBefore(
5360            new Element('span', {style: tagifyStyle}).update(
5361              character == ' ' ? String.fromCharCode(160) : character),
5362              child);
5363        });
5364        Element.remove(child);
5365      }
5366    });
5367  },
5368  multiple: function(element, effect) {
5369    var elements;
5370    if (((typeof element == 'object') ||
5371        Object.isFunction(element)) &&
5372       (element.length))
5373      elements = element;
5374    else
5375      elements = $(element).childNodes;
5376
5377    var options = Object.extend({
5378      speed: 0.1,
5379      delay: 0.0
5380    }, arguments[2] || { });
5381    var masterDelay = options.delay;
5382
5383    $A(elements).each( function(element, index) {
5384      new effect(element, Object.extend(options, { delay: index * options.speed + masterDelay }));
5385    });
5386  },
5387  PAIRS: {
5388    'slide':  ['SlideDown','SlideUp'],
5389    'blind':  ['BlindDown','BlindUp'],
5390    'appear': ['Appear','Fade']
5391  },
5392  toggle: function(element, effect) {
5393    element = $(element);
5394    effect = (effect || 'appear').toLowerCase();
5395    var options = Object.extend({
5396      queue: { position:'end', scope:(element.id || 'global'), limit: 1 }
5397    }, arguments[2] || { });
5398    Effect[element.visible() ?
5399      Effect.PAIRS[effect][1] : Effect.PAIRS[effect][0]](element, options);
5400  }
5401};
5402
5403Effect.DefaultOptions.transition = Effect.Transitions.sinoidal;
5404
5405/* ------------- core effects ------------- */
5406
5407Effect.ScopedQueue = Class.create(Enumerable, {
5408  initialize: function() {
5409    this.effects  = [];
5410    this.interval = null;
5411  },
5412  _each: function(iterator) {
5413    this.effects._each(iterator);
5414  },
5415  add: function(effect) {
5416    var timestamp = new Date().getTime();
5417
5418    var position = Object.isString(effect.options.queue) ?
5419      effect.options.queue : effect.options.queue.position;
5420
5421    switch(position) {
5422      case 'front':
5423        // move unstarted effects after this effect
5424        this.effects.findAll(function(e){ return e.state=='idle' }).each( function(e) {
5425            e.startOn  += effect.finishOn;
5426            e.finishOn += effect.finishOn;
5427          });
5428        break;
5429      case 'with-last':
5430        timestamp = this.effects.pluck('startOn').max() || timestamp;
5431        break;
5432      case 'end':
5433        // start effect after last queued effect has finished
5434        timestamp = this.effects.pluck('finishOn').max() || timestamp;
5435        break;
5436    }
5437
5438    effect.startOn  += timestamp;
5439    effect.finishOn += timestamp;
5440
5441    if (!effect.options.queue.limit || (this.effects.length < effect.options.queue.limit))
5442      this.effects.push(effect);
5443
5444    if (!this.interval)
5445      this.interval = setInterval(this.loop.bind(this), 15);
5446  },
5447  remove: function(effect) {
5448    this.effects = this.effects.reject(function(e) { return e==effect });
5449    if (this.effects.length == 0) {
5450      clearInterval(this.interval);
5451      this.interval = null;
5452    }
5453  },
5454  loop: function() {
5455    var timePos = new Date().getTime();
5456    for(var i=0, len=this.effects.length;i<len;i++)
5457      this.effects[i] && this.effects[i].loop(timePos);
5458  }
5459});
5460
5461Effect.Queues = {
5462  instances: $H(),
5463  get: function(queueName) {
5464    if (!Object.isString(queueName)) return queueName;
5465
5466    return this.instances.get(queueName) ||
5467      this.instances.set(queueName, new Effect.ScopedQueue());
5468  }
5469};
5470Effect.Queue = Effect.Queues.get('global');
5471
5472Effect.Base = Class.create({
5473  position: null,
5474  start: function(options) {
5475    function codeForEvent(options,eventName){
5476      return (
5477        (options[eventName+'Internal'] ? 'this.options.'+eventName+'Internal(this);' : '') +
5478        (options[eventName] ? 'this.options.'+eventName+'(this);' : '')
5479      );
5480    }
5481    if (options && options.transition === false) options.transition = Effect.Transitions.linear;
5482    this.options      = Object.extend(Object.extend({ },Effect.DefaultOptions), options || { });
5483    this.currentFrame = 0;
5484    this.state        = 'idle';
5485    this.startOn      = this.options.delay*1000;
5486    this.finishOn     = this.startOn+(this.options.duration*1000);
5487    this.fromToDelta  = this.options.to-this.options.from;
5488    this.totalTime    = this.finishOn-this.startOn;
5489    this.totalFrames  = this.options.fps*this.options.duration;
5490
5491    this.render = (function() {
5492      function dispatch(effect, eventName) {
5493        if (effect.options[eventName + 'Internal'])
5494          effect.options[eventName + 'Internal'](effect);
5495        if (effect.options[eventName])
5496          effect.options[eventName](effect);
5497      }
5498
5499      return function(pos) {
5500        if (this.state === "idle") {
5501          this.state = "running";
5502          dispatch(this, 'beforeSetup');
5503          if (this.setup) this.setup();
5504          dispatch(this, 'afterSetup');
5505        }
5506        if (this.state === "running") {
5507          pos = (this.options.transition(pos) * this.fromToDelta) + this.options.from;
5508          this.position = pos;
5509          dispatch(this, 'beforeUpdate');
5510          if (this.update) this.update(pos);
5511          dispatch(this, 'afterUpdate');
5512        }
5513      };
5514    })();
5515
5516    this.event('beforeStart');
5517    if (!this.options.sync)
5518      Effect.Queues.get(Object.isString(this.options.queue) ?
5519        'global' : this.options.queue.scope).add(this);
5520  },
5521  loop: function(timePos) {
5522    if (timePos >= this.startOn) {
5523      if (timePos >= this.finishOn) {
5524        this.render(1.0);
5525        this.cancel();
5526        this.event('beforeFinish');
5527        if (this.finish) this.finish();
5528        this.event('afterFinish');
5529        return;
5530      }
5531      var pos   = (timePos - this.startOn) / this.totalTime,
5532          frame = (pos * this.totalFrames).round();
5533      if (frame > this.currentFrame) {
5534        this.render(pos);
5535        this.currentFrame = frame;
5536      }
5537    }
5538  },
5539  cancel: function() {
5540    if (!this.options.sync)
5541      Effect.Queues.get(Object.isString(this.options.queue) ?
5542        'global' : this.options.queue.scope).remove(this);
5543    this.state = 'finished';
5544  },
5545  event: function(eventName) {
5546    if (this.options[eventName + 'Internal']) this.options[eventName + 'Internal'](this);
5547    if (this.options[eventName]) this.options[eventName](this);
5548  },
5549  inspect: function() {
5550    var data = $H();
5551    for(property in this)
5552      if (!Object.isFunction(this[property])) data.set(property, this[property]);
5553    return '#<Effect:' + data.inspect() + ',options:' + $H(this.options).inspect() + '>';
5554  }
5555});
5556
5557Effect.Parallel = Class.create(Effect.Base, {
5558  initialize: function(effects) {
5559    this.effects = effects || [];
5560    this.start(arguments[1]);
5561  },
5562  update: function(position) {
5563    this.effects.invoke('render', position);
5564  },
5565  finish: function(position) {
5566    this.effects.each( function(effect) {
5567      effect.render(1.0);
5568      effect.cancel();
5569      effect.event('beforeFinish');
5570      if (effect.finish) effect.finish(position);
5571      effect.event('afterFinish');
5572    });
5573  }
5574});
5575
5576Effect.Tween = Class.create(Effect.Base, {
5577  initialize: function(object, from, to) {
5578    object = Object.isString(object) ? $(object) : object;
5579    var args = $A(arguments), method = args.last(),
5580      options = args.length == 5 ? args[3] : null;
5581    this.method = Object.isFunction(method) ? method.bind(object) :
5582      Object.isFunction(object[method]) ? object[method].bind(object) :
5583      function(value) { object[method] = value };
5584    this.start(Object.extend({ from: from, to: to }, options || { }));
5585  },
5586  update: function(position) {
5587    this.method(position);
5588  }
5589});
5590
5591Effect.Event = Class.create(Effect.Base, {
5592  initialize: function() {
5593    this.start(Object.extend({ duration: 0 }, arguments[0] || { }));
5594  },
5595  update: Prototype.emptyFunction
5596});
5597
5598Effect.Opacity = Class.create(Effect.Base, {
5599  initialize: function(element) {
5600    this.element = $(element);
5601    if (!this.element) throw(Effect._elementDoesNotExistError);
5602    // make this work on IE on elements without 'layout'
5603    if (Prototype.Browser.IE && (!this.element.currentStyle.hasLayout))
5604      this.element.setStyle({zoom: 1});
5605    var options = Object.extend({
5606      from: this.element.getOpacity() || 0.0,
5607      to:   1.0
5608    }, arguments[1] || { });
5609    this.start(options);
5610  },
5611  update: function(position) {
5612    this.element.setOpacity(position);
5613  }
5614});
5615
5616Effect.Move = Class.create(Effect.Base, {
5617  initialize: function(element) {
5618    this.element = $(element);
5619    if (!this.element) throw(Effect._elementDoesNotExistError);
5620    var options = Object.extend({
5621      x:    0,
5622      y:    0,
5623      mode: 'relative'
5624    }, arguments[1] || { });
5625    this.start(options);
5626  },
5627  setup: function() {
5628    this.element.makePositioned();
5629    this.originalLeft = parseFloat(this.element.getStyle('left') || '0');
5630    this.originalTop  = parseFloat(this.element.getStyle('top')  || '0');
5631    if (this.options.mode == 'absolute') {
5632      this.options.x = this.options.x - this.originalLeft;
5633      this.options.y = this.options.y - this.originalTop;
5634    }
5635  },
5636  update: function(position) {
5637    this.element.setStyle({
5638      left: (this.options.x  * position + this.originalLeft).round() + 'px',
5639      top:  (this.options.y  * position + this.originalTop).round()  + 'px'
5640    });
5641  }
5642});
5643
5644// for backwards compatibility
5645Effect.MoveBy = function(element, toTop, toLeft) {
5646  return new Effect.Move(element,
5647    Object.extend({ x: toLeft, y: toTop }, arguments[3] || { }));
5648};
5649
5650Effect.Scale = Class.create(Effect.Base, {
5651  initialize: function(element, percent) {
5652    this.element = $(element);
5653    if (!this.element) throw(Effect._elementDoesNotExistError);
5654    var options = Object.extend({
5655      scaleX: true,
5656      scaleY: true,
5657      scaleContent: true,
5658      scaleFromCenter: false,
5659      scaleMode: 'box',        // 'box' or 'contents' or { } with provided values
5660      scaleFrom: 100.0,
5661      scaleTo:   percent
5662    }, arguments[2] || { });
5663    this.start(options);
5664  },
5665  setup: function() {
5666    this.restoreAfterFinish = this.options.restoreAfterFinish || false;
5667    this.elementPositioning = this.element.getStyle('position');
5668
5669    this.originalStyle = { };
5670    ['top','left','width','height','fontSize'].each( function(k) {
5671      this.originalStyle[k] = this.element.style[k];
5672    }.bind(this));
5673
5674    this.originalTop  = this.element.offsetTop;
5675    this.originalLeft = this.element.offsetLeft;
5676
5677    var fontSize = this.element.getStyle('font-size') || '100%';
5678    ['em','px','%','pt'].each( function(fontSizeType) {
5679      if (fontSize.indexOf(fontSizeType)>0) {
5680        this.fontSize     = parseFloat(fontSize);
5681        this.fontSizeType = fontSizeType;
5682      }
5683    }.bind(this));
5684
5685    this.factor = (this.options.scaleTo - this.options.scaleFrom)/100;
5686
5687    this.dims = null;
5688    if (this.options.scaleMode=='box')
5689      this.dims = [this.element.offsetHeight, this.element.offsetWidth];
5690    if (/^content/.test(this.options.scaleMode))
5691      this.dims = [this.element.scrollHeight, this.element.scrollWidth];
5692    if (!this.dims)
5693      this.dims = [this.options.scaleMode.originalHeight,
5694                   this.options.scaleMode.originalWidth];
5695  },
5696  update: function(position) {
5697    var currentScale = (this.options.scaleFrom/100.0) + (this.factor * position);
5698    if (this.options.scaleContent && this.fontSize)
5699      this.element.setStyle({fontSize: this.fontSize * currentScale + this.fontSizeType });
5700    this.setDimensions(this.dims[0] * currentScale, this.dims[1] * currentScale);
5701  },
5702  finish: function(position) {
5703    if (this.restoreAfterFinish) this.element.setStyle(this.originalStyle);
5704  },
5705  setDimensions: function(height, width) {
5706    var d = { };
5707    if (this.options.scaleX) d.width = width.round() + 'px';
5708    if (this.options.scaleY) d.height = height.round() + 'px';
5709    if (this.options.scaleFromCenter) {
5710      var topd  = (height - this.dims[0])/2;
5711      var leftd = (width  - this.dims[1])/2;
5712      if (this.elementPositioning == 'absolute') {
5713        if (this.options.scaleY) d.top = this.originalTop-topd + 'px';
5714        if (this.options.scaleX) d.left = this.originalLeft-leftd + 'px';
5715      } else {
5716        if (this.options.scaleY) d.top = -topd + 'px';
5717        if (this.options.scaleX) d.left = -leftd + 'px';
5718      }
5719    }
5720    this.element.setStyle(d);
5721  }
5722});
5723
5724Effect.Highlight = Class.create(Effect.Base, {
5725  initialize: function(element) {
5726    this.element = $(element);
5727    if (!this.element) throw(Effect._elementDoesNotExistError);
5728    var options = Object.extend({ startcolor: '#ffff99' }, arguments[1] || { });
5729    this.start(options);
5730  },
5731  setup: function() {
5732    // Prevent executing on elements not in the layout flow
5733    if (this.element.getStyle('display')=='none') { this.cancel(); return; }
5734    // Disable background image during the effect
5735    this.oldStyle = { };
5736    if (!this.options.keepBackgroundImage) {
5737      this.oldStyle.backgroundImage = this.element.getStyle('background-image');
5738      this.element.setStyle({backgroundImage: 'none'});
5739    }
5740    if (!this.options.endcolor)
5741      this.options.endcolor = this.element.getStyle('background-color').parseColor('#ffffff');
5742    if (!this.options.restorecolor)
5743      this.options.restorecolor = this.element.getStyle('background-color');
5744    // init color calculations
5745    this._base  = $R(0,2).map(function(i){ return parseInt(this.options.startcolor.slice(i*2+1,i*2+3),16) }.bind(this));
5746    this._delta = $R(0,2).map(function(i){ return parseInt(this.options.endcolor.slice(i*2+1,i*2+3),16)-this._base[i] }.bind(this));
5747  },
5748  update: function(position) {
5749    this.element.setStyle({backgroundColor: $R(0,2).inject('#',function(m,v,i){
5750      return m+((this._base[i]+(this._delta[i]*position)).round().toColorPart()); }.bind(this)) });
5751  },
5752  finish: function() {
5753    this.element.setStyle(Object.extend(this.oldStyle, {
5754      backgroundColor: this.options.restorecolor
5755    }));
5756  }
5757});
5758
5759Effect.ScrollTo = function(element) {
5760  var options = arguments[1] || { },
5761  scrollOffsets = document.viewport.getScrollOffsets(),
5762  elementOffsets = $(element).cumulativeOffset();
5763
5764  if (options.offset) elementOffsets[1] += options.offset;
5765
5766  return new Effect.Tween(null,
5767    scrollOffsets.top,
5768    elementOffsets[1],
5769    options,
5770    function(p){ scrollTo(scrollOffsets.left, p.round()); }
5771  );
5772};
5773
5774/* ------------- combination effects ------------- */
5775
5776Effect.Fade = function(element) {
5777  element = $(element);
5778  var oldOpacity = element.getInlineOpacity();
5779  var options = Object.extend({
5780    from: element.getOpacity() || 1.0,
5781    to:   0.0,
5782    afterFinishInternal: function(effect) {
5783      if (effect.options.to!=0) return;
5784      effect.element.hide().setStyle({opacity: oldOpacity});
5785    }
5786  }, arguments[1] || { });
5787  return new Effect.Opacity(element,options);
5788};
5789
5790Effect.Appear = function(element) {
5791  element = $(element);
5792  var options = Object.extend({
5793  from: (element.getStyle('display') == 'none' ? 0.0 : element.getOpacity() || 0.0),
5794  to:   1.0,
5795  // force Safari to render floated elements properly
5796  afterFinishInternal: function(effect) {
5797    effect.element.forceRerendering();
5798  },
5799  beforeSetup: function(effect) {
5800    effect.element.setOpacity(effect.options.from).show();
5801  }}, arguments[1] || { });
5802  return new Effect.Opacity(element,options);
5803};
5804
5805Effect.Puff = function(element) {
5806  element = $(element);
5807  var oldStyle = {
5808    opacity: element.getInlineOpacity(),
5809    position: element.getStyle('position'),
5810    top:  element.style.top,
5811    left: element.style.left,
5812    width: element.style.width,
5813    height: element.style.height
5814  };
5815  return new Effect.Parallel(
5816   [ new Effect.Scale(element, 200,
5817      { sync: true, scaleFromCenter: true, scaleContent: true, restoreAfterFinish: true }),
5818     new Effect.Opacity(element, { sync: true, to: 0.0 } ) ],
5819     Object.extend({ duration: 1.0,
5820      beforeSetupInternal: function(effect) {
5821        Position.absolutize(effect.effects[0].element);
5822      },
5823      afterFinishInternal: function(effect) {
5824         effect.effects[0].element.hide().setStyle(oldStyle); }
5825     }, arguments[1] || { })
5826   );
5827};
5828
5829Effect.BlindUp = function(element) {
5830  element = $(element);
5831  element.makeClipping();
5832  return new Effect.Scale(element, 0,
5833    Object.extend({ scaleContent: false,
5834      scaleX: false,
5835      restoreAfterFinish: true,
5836      afterFinishInternal: function(effect) {
5837        effect.element.hide().undoClipping();
5838      }
5839    }, arguments[1] || { })
5840  );
5841};
5842
5843Effect.BlindDown = function(element) {
5844  element = $(element);
5845  var elementDimensions = element.getDimensions();
5846  return new Effect.Scale(element, 100, Object.extend({
5847    scaleContent: false,
5848    scaleX: false,
5849    scaleFrom: 0,
5850    scaleMode: {originalHeight: elementDimensions.height, originalWidth: elementDimensions.width},
5851    restoreAfterFinish: true,
5852    afterSetup: function(effect) {
5853      effect.element.makeClipping().setStyle({height: '0px'}).show();
5854    },
5855    afterFinishInternal: function(effect) {
5856      effect.element.undoClipping();
5857    }
5858  }, arguments[1] || { }));
5859};
5860
5861Effect.SwitchOff = function(element) {
5862  element = $(element);
5863  var oldOpacity = element.getInlineOpacity();
5864  return new Effect.Appear(element, Object.extend({
5865    duration: 0.4,
5866    from: 0,
5867    transition: Effect.Transitions.flicker,
5868    afterFinishInternal: function(effect) {
5869      new Effect.Scale(effect.element, 1, {
5870        duration: 0.3, scaleFromCenter: true,
5871        scaleX: false, scaleContent: false, restoreAfterFinish: true,
5872        beforeSetup: function(effect) {
5873          effect.element.makePositioned().makeClipping();
5874        },
5875        afterFinishInternal: function(effect) {
5876          effect.element.hide().undoClipping().undoPositioned().setStyle({opacity: oldOpacity});
5877        }
5878      });
5879    }
5880  }, arguments[1] || { }));
5881};
5882
5883Effect.DropOut = function(element) {
5884  element = $(element);
5885  var oldStyle = {
5886    top: element.getStyle('top'),
5887    left: element.getStyle('left'),
5888    opacity: element.getInlineOpacity() };
5889  return new Effect.Parallel(
5890    [ new Effect.Move(element, {x: 0, y: 100, sync: true }),
5891      new Effect.Opacity(element, { sync: true, to: 0.0 }) ],
5892    Object.extend(
5893      { duration: 0.5,
5894        beforeSetup: function(effect) {
5895          effect.effects[0].element.makePositioned();
5896        },
5897        afterFinishInternal: function(effect) {
5898          effect.effects[0].element.hide().undoPositioned().setStyle(oldStyle);
5899        }
5900      }, arguments[1] || { }));
5901};
5902
5903Effect.Shake = function(element) {
5904  element = $(element);
5905  var options = Object.extend({
5906    distance: 20,
5907    duration: 0.5
5908  }, arguments[1] || {});
5909  var distance = parseFloat(options.distance);
5910  var split = parseFloat(options.duration) / 10.0;
5911  var oldStyle = {
5912    top: element.getStyle('top'),
5913    left: element.getStyle('left') };
5914    return new Effect.Move(element,
5915      { x:  distance, y: 0, duration: split, afterFinishInternal: function(effect) {
5916    new Effect.Move(effect.element,
5917      { x: -distance*2, y: 0, duration: split*2,  afterFinishInternal: function(effect) {
5918    new Effect.Move(effect.element,
5919      { x:  distance*2, y: 0, duration: split*2,  afterFinishInternal: function(effect) {
5920    new Effect.Move(effect.element,
5921      { x: -distance*2, y: 0, duration: split*2,  afterFinishInternal: function(effect) {
5922    new Effect.Move(effect.element,
5923      { x:  distance*2, y: 0, duration: split*2,  afterFinishInternal: function(effect) {
5924    new Effect.Move(effect.element,
5925      { x: -distance, y: 0, duration: split, afterFinishInternal: function(effect) {
5926        effect.element.undoPositioned().setStyle(oldStyle);
5927  }}); }}); }}); }}); }}); }});
5928};
5929
5930Effect.SlideDown = function(element) {
5931  element = $(element).cleanWhitespace();
5932  // SlideDown need to have the content of the element wrapped in a container element with fixed height!
5933  var oldInnerBottom = element.down().getStyle('bottom');
5934  var elementDimensions = element.getDimensions();
5935  return new Effect.Scale(element, 100, Object.extend({
5936    scaleContent: false,
5937    scaleX: false,
5938    scaleFrom: window.opera ? 0 : 1,
5939    scaleMode: {originalHeight: elementDimensions.height, originalWidth: elementDimensions.width},
5940    restoreAfterFinish: true,
5941    afterSetup: function(effect) {
5942      effect.element.makePositioned();
5943      effect.element.down().makePositioned();
5944      if (window.opera) effect.element.setStyle({top: ''});
5945      effect.element.makeClipping().setStyle({height: '0px'}).show();
5946    },
5947    afterUpdateInternal: function(effect) {
5948      effect.element.down().setStyle({bottom:
5949        (effect.dims[0] - effect.element.clientHeight) + 'px' });
5950    },
5951    afterFinishInternal: function(effect) {
5952      effect.element.undoClipping().undoPositioned();
5953      effect.element.down().undoPositioned().setStyle({bottom: oldInnerBottom}); }
5954    }, arguments[1] || { })
5955  );
5956};
5957
5958Effect.SlideUp = function(element) {
5959  element = $(element).cleanWhitespace();
5960  var oldInnerBottom = element.down().getStyle('bottom');
5961  var elementDimensions = element.getDimensions();
5962  return new Effect.Scale(element, window.opera ? 0 : 1,
5963   Object.extend({ scaleContent: false,
5964    scaleX: false,
5965    scaleMode: 'box',
5966    scaleFrom: 100,
5967    scaleMode: {originalHeight: elementDimensions.height, originalWidth: elementDimensions.width},
5968    restoreAfterFinish: true,
5969    afterSetup: function(effect) {
5970      effect.element.makePositioned();
5971      effect.element.down().makePositioned();
5972      if (window.opera) effect.element.setStyle({top: ''});
5973      effect.element.makeClipping().show();
5974    },
5975    afterUpdateInternal: function(effect) {
5976      effect.element.down().setStyle({bottom:
5977        (effect.dims[0] - effect.element.clientHeight) + 'px' });
5978    },
5979    afterFinishInternal: function(effect) {
5980      effect.element.hide().undoClipping().undoPositioned();
5981      effect.element.down().undoPositioned().setStyle({bottom: oldInnerBottom});
5982    }
5983   }, arguments[1] || { })
5984  );
5985};
5986
5987// Bug in opera makes the TD containing this element expand for a instance after finish
5988Effect.Squish = function(element) {
5989  return new Effect.Scale(element, window.opera ? 1 : 0, {
5990    restoreAfterFinish: true,
5991    beforeSetup: function(effect) {
5992      effect.element.makeClipping();
5993    },
5994    afterFinishInternal: function(effect) {
5995      effect.element.hide().undoClipping();
5996    }
5997  });
5998};
5999
6000Effect.Grow = function(element) {
6001  element = $(element);
6002  var options = Object.extend({
6003    direction: 'center',
6004    moveTransition: Effect.Transitions.sinoidal,
6005    scaleTransition: Effect.Transitions.sinoidal,
6006    opacityTransition: Effect.Transitions.full
6007  }, arguments[1] || { });
6008  var oldStyle = {
6009    top: element.style.top,
6010    left: element.style.left,
6011    height: element.style.height,
6012    width: element.style.width,
6013    opacity: element.getInlineOpacity() };
6014
6015  var dims = element.getDimensions();
6016  var initialMoveX, initialMoveY;
6017  var moveX, moveY;
6018
6019  switch (options.direction) {
6020    case 'top-left':
6021      initialMoveX = initialMoveY = moveX = moveY = 0;
6022      break;
6023    case 'top-right':
6024      initialMoveX = dims.width;
6025      initialMoveY = moveY = 0;
6026      moveX = -dims.width;
6027      break;
6028    case 'bottom-left':
6029      initialMoveX = moveX = 0;
6030      initialMoveY = dims.height;
6031      moveY = -dims.height;
6032      break;
6033    case 'bottom-right':
6034      initialMoveX = dims.width;
6035      initialMoveY = dims.height;
6036      moveX = -dims.width;
6037      moveY = -dims.height;
6038      break;
6039    case 'center':
6040      initialMoveX = dims.width / 2;
6041      initialMoveY = dims.height / 2;
6042      moveX = -dims.width / 2;
6043      moveY = -dims.height / 2;
6044      break;
6045  }
6046
6047  return new Effect.Move(element, {
6048    x: initialMoveX,
6049    y: initialMoveY,
6050    duration: 0.01,
6051    beforeSetup: function(effect) {
6052      effect.element.hide().makeClipping().makePositioned();
6053    },
6054    afterFinishInternal: function(effect) {
6055      new Effect.Parallel(
6056        [ new Effect.Opacity(effect.element, { sync: true, to: 1.0, from: 0.0, transition: options.opacityTransition }),
6057          new Effect.Move(effect.element, { x: moveX, y: moveY, sync: true, transition: options.moveTransition }),
6058          new Effect.Scale(effect.element, 100, {
6059            scaleMode: { originalHeight: dims.height, originalWidth: dims.width },
6060            sync: true, scaleFrom: window.opera ? 1 : 0, transition: options.scaleTransition, restoreAfterFinish: true})
6061        ], Object.extend({
6062             beforeSetup: function(effect) {
6063               effect.effects[0].element.setStyle({height: '0px'}).show();
6064             },
6065             afterFinishInternal: function(effect) {
6066               effect.effects[0].element.undoClipping().undoPositioned().setStyle(oldStyle);
6067             }
6068           }, options)
6069      );
6070    }
6071  });
6072};
6073
6074Effect.Shrink = function(element) {
6075  element = $(element);
6076  var options = Object.extend({
6077    direction: 'center',
6078    moveTransition: Effect.Transitions.sinoidal,
6079    scaleTransition: Effect.Transitions.sinoidal,
6080    opacityTransition: Effect.Transitions.none
6081  }, arguments[1] || { });
6082  var oldStyle = {
6083    top: element.style.top,
6084    left: element.style.left,
6085    height: element.style.height,
6086    width: element.style.width,
6087    opacity: element.getInlineOpacity() };
6088
6089  var dims = element.getDimensions();
6090  var moveX, moveY;
6091
6092  switch (options.direction) {
6093    case 'top-left':
6094      moveX = moveY = 0;
6095      break;
6096    case 'top-right':
6097      moveX = dims.width;
6098      moveY = 0;
6099      break;
6100    case 'bottom-left':
6101      moveX = 0;
6102      moveY = dims.height;
6103      break;
6104    case 'bottom-right':
6105      moveX = dims.width;
6106      moveY = dims.height;
6107      break;
6108    case 'center':
6109      moveX = dims.width / 2;
6110      moveY = dims.height / 2;
6111      break;
6112  }
6113
6114  return new Effect.Parallel(
6115    [ new Effect.Opacity(elemen
6115t, { sync: true, to: 0.0, from: 1.0, transition: options.opacityTransition }),
6116      new Effect.Scale(element, window.opera ? 1 : 0, { sync: true, transition: options.scaleTransition, restoreAfterFinish: true}),
6117      new Effect.Move(element, { x: moveX, y: moveY, sync: true, transition: options.moveTransition })
6118    ], Object.extend({
6119         beforeStartInternal: function(effect) {
6120           effect.effects[0].element.makePositioned().makeClipping();
6121         },
6122         afterFinishInternal: function(effect) {
6123           effect.effects[0].element.hide().undoClipping().undoPositioned().setStyle(oldStyle); }
6124       }, options)
6125  );
6126};
6127
6128Effect.Pulsate = function(element) {
6129  element = $(element);
6130  var options    = arguments[1] || { },
6131    oldOpacity = element.getInlineOpacity(),
6132    transition = options.transition || Effect.Transitions.linear,
6133    reverser   = function(pos){
6134      return 1 - transition((-Math.cos((pos*(options.pulses||5)*2)*Math.PI)/2) + .5);
6135    };
6136
6137  return new Effect.Opacity(element,
6138    Object.extend(Object.extend({  duration: 2.0, from: 0,
6139      afterFinishInternal: function(effect) { effect.element.setStyle({opacity: oldOpacity}); }
6140    }, options), {transition: reverser}));
6141};
6142
6143Effect.Fold = function(element) {
6144  element = $(element);
6145  var oldStyle = {
6146    top: element.style.top,
6147    left: element.style.left,
6148    width: element.style.width,
6149    height: element.style.height };
6150  element.makeClipping();
6151  return new Effect.Scale(element, 5, Object.extend({
6152    scaleContent: false,
6153    scaleX: false,
6154    afterFinishInternal: function(effect) {
6155    new Effect.Scale(element, 1, {
6156      scaleContent: false,
6157      scaleY: false,
6158      afterFinishInternal: function(effect) {
6159        effect.element.hide().undoClipping().setStyle(oldStyle);
6160      } });
6161  }}, arguments[1] || { }));
6162};
6163
6164Effect.Morph = Class.create(Effect.Base, {
6165  initialize: function(element) {
6166    this.element = $(element);
6167    if (!this.element) throw(Effect._elementDoesNotExistError);
6168    var options = Object.extend({
6169      style: { }
6170    }, arguments[1] || { });
6171
6172    if (!Object.isString(options.style)) this.style = $H(options.style);
6173    else {
6174      if (options.style.include(':'))
6175        this.style = options.style.parseStyle();
6176      else {
6177        this.element.addClassName(options.style);
6178        this.style = $H(this.element.getStyles());
6179        this.element.removeClassName(options.style);
6180        var css = this.element.getStyles();
6181        this.style = this.style.reject(function(style) {
6182          return style.value == css[style.key];
6183        });
6184        options.afterFinishInternal = function(effect) {
6185          effect.element.addClassName(effect.options.style);
6186          effect.transforms.each(function(transform) {
6187            effect.element.style[transform.style] = '';
6188          });
6189        };
6190      }
6191    }
6192    this.start(options);
6193  },
6194
6195  setup: function(){
6196    function parseColor(color){
6197      if (!color || ['rgba(0, 0, 0, 0)','transparent'].include(color)) color = '#ffffff';
6198      color = color.parseColor();
6199      return $R(0,2).map(function(i){
6200        return parseInt( color.slice(i*2+1,i*2+3), 16 );
6201      });
6202    }
6203    this.transforms = this.style.map(function(pair){
6204      var property = pair[0], value = pair[1], unit = null;
6205
6206      if (value.parseColor('#zzzzzz') != '#zzzzzz') {
6207        value = value.parseColor();
6208        unit  = 'color';
6209      } else if (property == 'opacity') {
6210        value = parseFloat(value);
6211        if (Prototype.Browser.IE && (!this.element.currentStyle.hasLayout))
6212          this.element.setStyle({zoom: 1});
6213      } else if (Element.CSS_LENGTH.test(value)) {
6214          var components = value.match(/^([\+\-]?[0-9\.]+)(.*)$/);
6215          value = parseFloat(components[1]);
6216          unit = (components.length == 3) ? components[2] : null;
6217      }
6218
6219      var originalValue = this.element.getStyle(property);
6220      return {
6221        style: property.camelize(),
6222        originalValue: unit=='color' ? parseColor(originalValue) : parseFloat(originalValue || 0),
6223        targetValue: unit=='color' ? parseColor(value) : value,
6224        unit: unit
6225      };
6226    }.bind(this)).reject(function(transform){
6227      return (
6228        (transform.originalValue == transform.targetValue) ||
6229        (
6230          transform.unit != 'color' &&
6231          (isNaN(transform.originalValue) || isNaN(transform.targetValue))
6232        )
6233      );
6234    });
6235  },
6236  update: function(position) {
6237    var style = { }, transform, i = this.transforms.length;
6238    while(i--)
6239      style[(transform = this.transforms[i]).style] =
6240        transform.unit=='color' ? '#'+
6241          (Math.round(transform.originalValue[0]+
6242            (transform.targetValue[0]-transform.originalValue[0])*position)).toColorPart() +
6243          (Math.round(transform.originalValue[1]+
6244            (transform.targetValue[1]-transform.originalValue[1])*position)).toColorPart() +
6245          (Math.round(transform.originalValue[2]+
6246            (transform.targetValue[2]-transform.originalValue[2])*position)).toColorPart() :
6247        (transform.originalValue +
6248          (transform.targetValue - transform.originalValue) * position).toFixed(3) +
6249            (transform.unit === null ? '' : transform.unit);
6250    this.element.setStyle(style, true);
6251  }
6252});
6253
6254Effect.Transform = Class.create({
6255  initialize: function(tracks){
6256    this.tracks  = [];
6257    this.options = arguments[1] || { };
6258    this.addTracks(tracks);
6259  },
6260  addTracks: function(tracks){
6261    tracks.each(function(track){
6262      track = $H(track);
6263      var data = track.values().first();
6264      this.tracks.push($H({
6265        ids:     track.keys().first(),
6266        effect:  Effect.Morph,
6267        options: { style: data }
6268      }));
6269    }.bind(this));
6270    return this;
6271  },
6272  play: function(){
6273    return new Effect.Parallel(
6274      this.tracks.map(function(track){
6275        var ids = track.get('ids'), effect = track.get('effect'), options = track.get('options');
6276        var elements = [$(ids) || $$(ids)].flatten();
6277        return elements.map(function(e){ return new effect(e, Object.extend({ sync:true }, options)) });
6278      }).flatten(),
6279      this.options
6280    );
6281  }
6282});
6283
6284Element.CSS_PROPERTIES = $w(
6285  'backgroundColor backgroundPosition borderBottomColor borderBottomStyle ' +
6286  'borderBottomWidth borderLeftColor borderLeftStyle borderLeftWidth ' +
6287  'borderRightColor borderRightStyle borderRightWidth borderSpacing ' +
6288  'borderTopColor borderTopStyle borderTopWidth bottom clip color ' +
6289  'fontSize fontWeight height left letterSpacing lineHeight ' +
6290  'marginBottom marginLeft marginRight marginTop markerOffset maxHeight '+
6291  'maxWidth minHeight minWidth opacity outlineColor outlineOffset ' +
6292  'outlineWidth paddingBottom paddingLeft paddingRight paddingTop ' +
6293  'right textIndent top width wordSpacing zIndex');
6294
6295Element.CSS_LENGTH = /^(([\+\-]?[0-9\.]+)(em|ex|px|in|cm|mm|pt|pc|\%))|0$/;
6296
6297String.__parseStyleElement = document.createElement('div');
6298String.prototype.parseStyle = function(){
6299  var style, styleRules = $H();
6300  if (Prototype.Browser.WebKit)
6301    style = new Element('div',{style:this}).style;
6302  else {
6303    String.__parseStyleElement.innerHTML = '<div style="' + this + '"></div>';
6304    style = String.__parseStyleElement.childNodes[0].style;
6305  }
6306
6307  Element.CSS_PROPERTIES.each(function(property){
6308    if (style[property]) styleRules.set(property, style[property]);
6309  });
6310
6311  if (Prototype.Browser.IE && this.include('opacity'))
6312    styleRules.set('opacity', this.match(/opacity:\s*((?:0|1)?(?:\.\d*)?)/)[1]);
6313
6314  return styleRules;
6315};
6316
6317if (document.defaultView && document.defaultView.getComputedStyle) {
6318  Element.getStyles = function(element) {
6319    var css = document.defaultView.getComputedStyle($(element), null);
6320    return Element.CSS_PROPERTIES.inject({ }, function(styles, property) {
6321      styles[property] = css[property];
6322      return styles;
6323    });
6324  };
6325} else {
6326  Element.getStyles = function(element) {
6327    element = $(element);
6328    var css = element.currentStyle, styles;
6329    styles = Element.CSS_PROPERTIES.inject({ }, function(results, property) {
6330      results[property] = css[property];
6331      return results;
6332    });
6333    if (!styles.opacity) styles.opacity = element.getOpacity();
6334    return styles;
6335  };
6336}
6337
6338Effect.Methods = {
6339  morph: function(element, style) {
6340    element = $(element);
6341    new Effect.Morph(element, Object.extend({ style: style }, arguments[2] || { }));
6342    return element;
6343  },
6344  visualEffect: function(element, effect, options) {
6345    element = $(element);
6346    var s = effect.dasherize().camelize(), klass = s.charAt(0).toUpperCase() + s.substring(1);
6347    new Effect[klass](element, options);
6348    return element;
6349  },
6350  highlight: function(element, options) {
6351    element = $(element);
6352    new Effect.Highlight(element, options);
6353    return element;
6354  }
6355};
6356
6357$w('fade appear grow shrink fold blindUp blindDown slideUp slideDown '+
6358  'pulsate shake puff squish switchOff dropOut').each(
6359  function(effect) {
6360    Effect.Methods[effect] = function(element, options){
6361      element = $(element);
6362      Effect[effect.charAt(0).toUpperCase() + effect.substring(1)](element, options);
6363      return element;
6364    };
6365  }
6366);
6367
6368$w('getInlineOpacity forceRerendering setContentZoom collectTextNodes collectTextNodesIgnoreClass getStyles').each(
6369  function(f) { Effect.Methods[f] = Element[f]; }
6370);
6371
6372Element.addMethods(Effect.Methods);
6373// script.aculo.us dragdrop.js v1.8.2, Tue Nov 18 18:30:58 +0100 2008
6374
6375// Copyright (c) 2005-2008 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us)
6376//           (c) 2005-2008 Sammi Williams (http://www.oriontransfer.co.nz, [email protected])
6377//
6378// script.aculo.us is freely distributable under the terms of an MIT-style license.
6379// For details, see the script.aculo.us web site: http://script.aculo.us/
6380
6381if(Object.isUndefined(Effect))
6382  throw("dragdrop.js requires including script.aculo.us' effects.js library");
6383
6384var Droppables = {
6385  drops: [],
6386
6387  remove: function(element) {
6388    this.drops = this.drops.reject(function(d) { return d.element==$(element) });
6389  },
6390
6391  add: function(element) {
6392    element = $(element);
6393    var options = Object.extend({
6394      greedy:     true,
6395      hoverclass: null,
6396      tree:       false
6397    }, arguments[1] || { });
6398
6399    // cache containers
6400    if(options.containment) {
6401      options._containers = [];
6402      var containment = options.containment;
6403      if(Object.isArray(containment)) {
6404        containment.each( function(c) { options._containers.push($(c)) });
6405      } else {
6406        options._containers.push($(containment));
6407      }
6408    }
6409
6410    if(options.accept) options.accept = [options.accept].flatten();
6411
6412    Element.makePositioned(element); // fix IE
6413    options.element = element;
6414
6415    this.drops.push(options);
6416  },
6417
6418  findDeepestChild: function(drops) {
6419    deepest = drops[0];
6420
6421    for (i = 1; i < drops.length; ++i)
6422      if (Element.isParent(drops[i].element, deepest.element))
6423        deepest = drops[i];
6424
6425    return deepest;
6426  },
6427
6428  isContained: function(element, drop) {
6429    var containmentNode;
6430    if(drop.tree) {
6431      containmentNode = element.treeNode;
6432    } else {
6433      containmentNode = element.parentNode;
6434    }
6435    return drop._containers.detect(function(c) { return containmentNode == c });
6436  },
6437
6438  isAffected: function(point, element, drop) {
6439    return (
6440      (drop.element!=element) &&
6441      ((!drop._containers) ||
6442        this.isContained(element, drop)) &&
6443      ((!drop.accept) ||
6444        (Element.classNames(element).detect(
6445          function(v) { return drop.accept.include(v) } ) )) &&
6446      Position.within(drop.element, point[0], point[1]) );
6447  },
6448
6449  deactivate: function(drop) {
6450    if(drop.hoverclass)
6451      Element.removeClassName(drop.element, drop.hoverclass);
6452    this.last_active = null;
6453  },
6454
6455  activate: function(drop) {
6456    if(drop.hoverclass)
6457      Element.addClassName(drop.element, drop.hoverclass);
6458    this.last_active = drop;
6459  },
6460
6461  show: function(point, element) {
6462    if(!this.drops.length) return;
6463    var drop, affected = [];
6464
6465    this.drops.each( function(drop) {
6466      if(Droppables.isAffected(point, element, drop))
6467        affected.push(drop);
6468    });
6469
6470    if(affected.length>0)
6471      drop = Droppables.findDeepestChild(affected);
6472
6473    if(this.last_active && this.last_active != drop) this.deactivate(this.last_active);
6474    if (drop) {
6475      Position.within(drop.element, point[0], point[1]);
6476      if(drop.onHover)
6477        drop.onHover(element, drop.element, Position.overlap(drop.overlap, drop.element));
6478
6479      if (drop != this.last_active) Droppables.activate(drop);
6480    }
6481  },
6482
6483  fire: function(event, element) {
6484    if(!this.last_active) return;
6485    Position.prepare();
6486
6487    if (this.isAffected([Event.pointerX(event), Event.pointerY(event)], element, this.last_active))
6488      if (this.last_active.onDrop) {
6489        this.last_active.onDrop(element, this.last_active.element, event);
6490        return true;
6491      }
6492  },
6493
6494  reset: function() {
6495    if(this.last_active)
6496      this.deactivate(this.last_active);
6497  }
6498};
6499
6500var Draggables = {
6501  drags: [],
6502  observers: [],
6503
6504  register: function(draggable) {
6505    if(this.drags.length == 0) {
6506      this.eventMouseUp   = this.endDrag.bindAsEventListener(this);
6507      this.eventMouseMove = this.updateDrag.bindAsEventListener(this);
6508      this.eventKeypress  = this.keyPress.bindAsEventListener(this);
6509
6510      Event.observe(document, "mouseup", this.eventMouseUp);
6511      Event.observe(draggable.element, "mousemove", this.eventMouseMove);
6512      Event.observe(document, "keypress", this.eventKeypress);
6513    }
6514    this.drags.push(draggable);
6515  },
6516
6517  unregister: function(draggable) {
6518    this.drags = this.drags.reject(function(d) { return d==draggable });
6519    if(this.drags.length == 0) {
6520      Event.stopObserving(document, "mouseup", this.eventMouseUp);
6521      Event.stopObserving(draggable.element, "mousemove", this.eventMouseMove);
6522      Event.stopObserving(document, "keypress", this.eventKeypress);
6523    }
6524  },
6525
6526  activate: function(draggable) {
6527    if(draggable.options.delay) {
6528      this._timeout = setTimeout(function() {
6529        Draggables._timeout = null;
6530        window.focus();
6531        Draggables.activeDraggable = draggable;
6532      }.bind(this), draggable.options.delay);
6533    } else {
6534      window.focus(); // allows keypress events if window isn't currently focused, fails for Safari
6535      this.activeDraggable = draggable;
6536    }
6537  },
6538
6539  deactivate: function() {
6540    this.activeDraggable = null;
6541  },
6542
6543  updateDrag: function(event) {
6544    if(!this.activeDraggable) return;
6545    var pointer = [Event.pointerX(event), Event.pointerY(event)];
6546    // Mozilla-based browsers fire successive mousemove events with
6547    // the same coordinates, prevent needless redrawing (moz bug?)
6548    if(this._lastPointer && (this._lastPointer.inspect() == pointer.inspect())) return;
6549    this._lastPointer = pointer;
6550
6551    this.activeDraggable.updateDrag(event, pointer);
6552  },
6553
6554  endDrag: function(event) {
6555    if(this._timeout) {
6556      clearTimeout(this._timeout);
6557      this._timeout = null;
6558    }
6559    if(!this.activeDraggable) return;
6560    this._lastPointer = null;
6561    this.activeDraggable.endDrag(event);
6562    this.activeDraggable = null;
6563  },
6564
6565  keyPress: function(event) {
6566    if(this.activeDraggable)
6567      this.activeDraggable.keyPress(event);
6568  },
6569
6570  addObserver: function(observer) {
6571    this.observers.push(observer);
6572    this._cacheObserverCallbacks();
6573  },
6574
6575  removeObserver: function(element) {  // element instead of observer fixes mem leaks
6576    this.observers = this.observers.reject( function(o) { return o.element==element });
6577    this._cacheObserverCallbacks();
6578  },
6579
6580  notify: function(eventName, draggable, event) {  // 'onStart', 'onEnd', 'onDrag'
6581    if(this[eventName+'Count'] > 0)
6582      this.observers.each( function(o) {
6583        if(o[eventName]) o[eventName](eventName, draggable, event);
6584      });
6585    if(draggable.options[eventName]) draggable.options[eventName](draggable, event);
6586  },
6587
6588  _cacheObserverCallbacks: function() {
6589    ['onStart','onEnd','onDrag'].each( function(eventName) {
6590      Draggables[eventName+'Count'] = Draggables.observers.select(
6591        function(o) { return o[eventName]; }
6592      ).length;
6593    });
6594  }
6595};
6596
6597/*--------------------------------------------------------------------------*/
6598
6599var Draggable = Class.create({
6600  initialize: function(element) {
6601    var defaults = {
6602      handle: false,
6603      reverteffect: function(element, top_offset, left_offset) {
6604        var dur = Math.sqrt(Math.abs(top_offset^2)+Math.abs(left_offset^2))*0.02;
6605        new Effect.Move(element, { x: -left_offset, y: -top_offset, duration: dur,
6606          queue: {scope:'_draggable', position:'end'}
6607        });
6608      },
6609      endeffect: function(element) {
6610        var toOpacity = Object.isNumber(element._opacity) ? element._opacity : 1.0;
6611        new Effect.Opacity(element, {duration:0.2, from:0.7, to:toOpacity,
6612          queue: {scope:'_draggable', position:'end'},
6613          afterFinish: function(){
6614            Draggable._dragging[element] = false
6615          }
6616        });
6617      },
6618      zindex: 1000,
6619      revert: false,
6620      quiet: false,
6621      scroll: false,
6622      scrollSensitivity: 20,
6623      scrollSpeed: 15,
6624      snap: false,  // false, or xy or [x,y] or function(x,y){ return [x,y] }
6625      delay: 0
6626    };
6627
6628    if(!arguments[1] || Object.isUndefined(arguments[1].endeffect))
6629      Object.extend(defaults, {
6630        starteffect: function(element) {
6631          element._opacity = Element.getOpacity(element);
6632          Draggable._dragging[element] = true;
6633          new Effect.Opacity(element, {duration:0.2, from:element._opacity, to:0.7});
6634        }
6635      });
6636
6637    var options = Object.extend(defaults, arguments[1] || { });
6638
6639    this.element = $(element);
6640
6641    if(options.handle && Object.isString(options.handle))
6642      this.handle = this.element.down('.'+options.handle, 0);
6643
6644    if(!this.handle) this.handle = $(options.handle);
6645    if(!this.handle) this.handle = this.element;
6646
6647    if(options.scroll && !options.scroll.scrollTo && !options.scroll.outerHTML) {
6648      options.scroll = $(options.scroll);
6649      this._isScrollChild = Element.childOf(this.element, options.scroll);
6650    }
6651
6652    Element.makePositioned(this.element); // fix IE
6653
6654    this.options  = options;
6655    this.dragging = false;
6656
6657    this.eventMouseDown = this.initDrag.bindAsEventListener(this);
6658    Event.observe(this.handle, "mousedown", this.eventMouseDown);
6659
6660    Draggables.register(this);
6661  },
6662
6663  destroy: function() {
6664    Event.stopObserving(this.handle, "mousedown", this.eventMouseDown);
6665    Draggables.unregister(this);
6666  },
6667
6668  currentDelta: function() {
6669    return([
6670      parseInt(Element.getStyle(this.element,'left') || '0'),
6671      parseInt(Element.getStyle(this.element,'top') || '0')]);
6672  },
6673
6674  initDrag: function(event) {
6675    if(!Object.isUndefined(Draggable._dragging[this.element]) &&
6676      Draggable._dragging[this.element]) return;
6677    if(Event.isLeftClick(event)) {
6678      // abort on form elements, fixes a Firefox issue
6679      var src = Event.element(event);
6680      if((tag_name = src.tagName.toUpperCase()) && (
6681        tag_name=='INPUT' ||
6682        tag_name=='SELECT' ||
6683        tag_name=='OPTION' ||
6684        tag_name=='BUTTON' ||
6685        tag_name=='TEXTAREA')) return;
6686
6687      var pointer = [Event.pointerX(event), Event.pointerY(event)];
6688      var pos     = Position.cumulativeOffset(this.element);
6689      this.offset = [0,1].map( function(i) { return (pointer[i] - pos[i]) });
6690
6691      Draggables.activate(this);
6692      Event.stop(event);
6693    }
6694  },
6695
6696  startDrag: function(event) {
6697    this.dragging = true;
6698    if(!this.delta)
6699      this.delta = this.currentDelta();
6700
6701    if(this.options.zindex) {
6702      this.originalZ = parseInt(Element.getStyle(this.element,'z-index') || 0);
6703      this.element.style.zIndex = this.options.zindex;
6704    }
6705
6706    if(this.options.ghosting) {
6707      this._clone = this.element.cloneNode(true);
6708      this._originallyAbsolute = (this.element.getStyle('position') == 'absolute');
6709      if (!this._originallyAbsolute)
6710        Position.absolutize(this.element);
6711      this.element.parentNode.insertBefore(this._clone, this.element);
6712    }
6713
6714    if(this.options.scroll) {
6715      if (this.options.scroll == window) {
6716        var where = this._getWindowScroll(this.options.scroll);
6717        this.originalScrollLeft = where.left;
6718        this.originalScrollTop = where.top;
6719      } else {
6720        this.originalScrollLeft = this.options.scroll.scrollLeft;
6721        this.originalScrollTop = this.options.scroll.scrollTop;
6722      }
6723    }
6724
6725    Draggables.notify('onStart', this, event);
6726
6727    if(this.options.starteffect) this.options.starteffect(this.element);
6728  },
6729
6730  updateDrag: function(event, pointer) {
6731    if(!this.dragging) this.startDrag(event);
6732
6733    if(!this.options.quiet){
6734      Position.prepare();
6735      Droppables.show(pointer, this.element);
6736    }
6737
6738    Draggables.notify('onDrag', this, event);
6739
6740    this.draw(pointer);
6741    if(this.options.change) this.options.change(this);
6742
6743    if(this.options.scroll) {
6744      this.stopScrolling();
6745
6746      var p;
6747      if (this.options.scroll == window) {
6748        with(this._getWindowScroll(this.options.scroll)) { p = [ left, top, left+width, top+height ]; }
6749      } else {
6750        p = Position.page(this.options.scroll);
6751        p[0] += this.options.scroll.scrollLeft + Position.deltaX;
6752        p[1] += this.options.scroll.scrollTop + Position.deltaY;
6753        p.push(p[0]+this.options.scroll.offsetWidth);
6754        p.push(p[1]+this.options.scroll.offsetHeight);
6755      }
6756      var speed = [0,0];
6757      if(pointer[0] < (p[0]+this.options.scrollSensitivity)) speed[0] = pointer[0]-(p[0]+this.options.scrollSensitivity);
6758      if(pointer[1] < (p[1]+this.options.scrollSensitivity)) speed[1] = pointer[1]-(p[1]+this.options.scrollSensitivity);
6759      if(pointer[0] > (p[2]-this.options.scrollSensitivity)) speed[0] = pointer[0]-(p[2]-this.options.scrollSensitivity);
6760      if(pointer[1] > (p[3]-this.options.scrollSensitivity)) speed[1] = pointer[1]-(p[3]-this.options.scrollSensitivity);
6761      this.startScrolling(speed);
6762    }
6763
6764    // fix AppleWebKit rendering
6765    if(Prototype.Browser.WebKit) window.scrollBy(0,0);
6766
6767    Event.stop(event);
6768  },
6769
6770  finishDrag: function(event, success) {
6771    this.dragging = false;
6772
6773    if(this.options.quiet){
6774      Position.prepare();
6775      var pointer = [Event.pointerX(event), Event.pointerY(event)];
6776      Droppables.show(pointer, this.element);
6777    }
6778
6779    if(this.options.ghosting) {
6780      if (!this._originallyAbsolute)
6781        Position.relativize(this.element);
6782      delete this._originallyAbsolute;
6783      Element.remove(this._clone);
6784      this._clone = null;
6785    }
6786
6787    var dropped = false;
6788    if(success) {
6789      dropped = Droppables.fire(event, this.element);
6790      if (!dropped) dropped = false;
6791    }
6792    if(dropped && this.options.onDropped) this.options.onDropped(this.element);
6793    Draggables.notify('onEnd', this, event);
6794
6795    var revert = this.options.revert;
6796    if(revert && Object.isFunction(revert)) revert = revert(this.element);
6797
6798    var d = this.currentDelta();
6799    if(revert && this.options.reverteffect) {
6800      if (dropped == 0 || revert != 'failure')
6801        this.options.reverteffect(this.element,
6802          d[1]-this.delta[1], d[0]-this.delta[0]);
6803    } else {
6804      this.delta = d;
6805    }
6806
6807    if(this.options.zindex)
6808      this.element.style.zIndex = this.originalZ;
6809
6810    if(this.options.endeffect)
6811      this.options.endeffect(this.element);
6812
6813    Draggables.deactivate(this);
6814    Droppables.reset();
6815  },
6816
6817  keyPress: function(event) {
6818    if(event.keyCode!=Event.KEY_ESC) return;
6819    this.finishDrag(event, false);
6820    Event.stop(event);
6821  },
6822
6823  endDrag: function(event) {
6824    if(!this.dragging) return;
6825    this.stopScrolling();
6826    this.finishDrag(event, true);
6827    Event.stop(event);
6828  },
6829
6830  draw: function(point) {
6831    var pos = Position.cumulativeOffset(this.element);
6832    if(this.options.ghosting) {
6833      var r   = Position.realOffset(this.element);
6834      pos[0] += r[0] - Position.deltaX; pos[1] += r[1] - Position.deltaY;
6835    }
6836
6837    var d = this.currentDelta();
6838    pos[0] -= d[0]; pos[1] -= d[1];
6839
6840    if(this.options.scroll && (this.options.scroll != window && this._isScrollChild)) {
6841      pos[0] -= this.options.scroll.scrollLeft-this.originalScrollLeft;
6842      pos[1] -= this.options.scroll.scrollTop-this.originalScrollTop;
6843    }
6844
6845    var p = [0,1].map(function(i){
6846      return (point[i]-pos[i]-this.offset[i])
6847    }.bind(this));
6848
6849    if(this.options.snap) {
6850      if(Object.isFunction(this.options.snap)) {
6851        p = this.options.snap(p[0],p[1],this);
6852      } else {
6853      if(Object.isArray(this.options.snap)) {
6854        p = p.map( function(v, i) {
6855          return (v/this.options.snap[i]).round()*this.options.snap[i] }.bind(this));
6856      } else {
6857        p = p.map( function(v) {
6858          return (v/this.options.snap).round()*this.options.snap }.bind(this));
6859      }
6860    }}
6861
6862    var style = this.element.style;
6863    if((!this.options.constraint) || (this.options.constraint=='horizontal'))
6864      style.left = p[0] + "px";
6865    if((!this.options.constraint) || (this.options.constraint=='vertical'))
6866      style.top  = p[1] + "px";
6867
6868    if(style.visibility=="hidden") style.visibility = ""; // fix gecko rendering
6869  },
6870
6871  stopScrolling: function() {
6872    if(this.scrollInterval) {
6873      clearInterval(this.scrollInterval);
6874      this.scrollInterval = null;
6875      Draggables._lastScrollPointer = null;
6876    }
6877  },
6878
6879  startScrolling: function(speed) {
6880    if(!(speed[0] || speed[1])) return;
6881    this.scrollSpeed = [speed[0]*this.options.scrollSpeed,speed[1]*this.options.scrollSpeed];
6882    this.lastScrolled = new Date();
6883    this.scrollInterval = setInterval(this.scroll.bind(this), 10);
6884  },
6885
6886  scroll: function() {
6887    var current = new Date();
6888    var delta = current - this.lastScrolled;
6889    this.lastScrolled = current;
6890    if(this.options.scroll == window) {
6891      with (this._getWindowScroll(this.options.scroll)) {
6892        if (this.scrollSpeed[0] || this.scrollSpeed[1]) {
6893          var d = delta / 1000;
6894          this.options.scroll.scrollTo( left + d*this.scrollSpeed[0], top + d*this.scrollSpeed[1] );
6895        }
6896      }
6897    } else {
6898      this.options.scroll.scrollLeft += this.scrollSpeed[0] * delta / 1000;
6899      this.options.scroll.scrollTop  += this.scrollSpeed[1] * delta / 1000;
6900    }
6901
6902    Position.prepare();
6903    Droppables.show(Draggables._lastPointer, this.element);
6904    Draggables.notify('onDrag', this);
6905    if (this._isScrollChild) {
6906      Draggables._lastScrollPointer = Draggables._lastScrollPointer || $A(Draggables._lastPointer);
6907      Draggables._lastScrollPointer[0] += this.scrollSpeed[0] * delta / 1000;
6908      Draggables._lastScrollPointer[1] += this.scrollSpeed[1] * delta / 1000;
6909      if (Draggables._lastScrollPointer[0] < 0)
6910        Draggables._lastScrollPointer[0] = 0;
6911      if (Draggables._lastScrollPointer[1] < 0)
6912        Draggables._lastScrollPointer[1] = 0;
6913      this.draw(Draggables._lastScrollPointer);
6914    }
6915
6916    if(this.options.change) this.options.change(this);
6917  },
6918
6919  _getWindowScroll: function(w) {
6920    var T, L, W, H;
6921    with (w.document) {
6922      if (w.document.documentElement && documentElement.scrollTop) {
6923        T = documentElement.scrollTop;
6924        L = documentElement.scrollLeft;
6925      } else if (w.document.body) {
6926        T = body.scrollTop;
6927        L = body.scrollLeft;
6928      }
6929      if (w.innerWidth) {
6930        W = w.innerWidth;
6931        H = w.innerHeight;
6932      } else if (w.document.documentElement && documentElement.clientWidth) {
6933        W = documentElement.clientWidth;
6934        H = documentElement.clientHeight;
6935      } else {
6936        W = body.offsetWidth;
6937        H = body.offsetHeight;
6938      }
6939    }
6940    return { top: T, left: L, width: W, height: H };
6941  }
6942});
6943
6944Draggable._dragging = { };
6945
6946/*--------------------------------------------------------------------------*/
6947
6948var SortableObserver = Class.create({
6949  initialize: function(element, observer) {
6950    this.element   = $(element);
6951    this.observer  = observer;
6952    this.lastValue = Sortable.serialize(this.element);
6953  },
6954
6955  onStart: function() {
6956    this.lastValue = Sortable.serialize(this.element);
6957  },
6958
6959  onEnd: function() {
6960    Sortable.unmark();
6961    if(this.lastValue != Sortable.serialize(this.element))
6962      this.observer(this.element)
6963  }
6964});
6965
6966var Sortable = {
6967  SERIALIZE_RULE: /^[^_\-](?:[A-Za-z0-9\-\_]*)[_](.*)$/,
6968
6969  sortables: { },
6970
6971  _findRootElement: function(element) {
6972    while (element.tagName.toUpperCase() != "BODY") {
6973      if(element.id && Sortable.sortables[element.id]) return element;
6974      element = element.parentNode;
6975    }
6976  },
6977
6978  options: function(element) {
6979    element = Sortable._findRootElement($(element));
6980    if(!element) return;
6981    return Sortable.sortables[element.id];
6982  },
6983
6984  destroy: function(element){
6985    element = $(element);
6986    var s = Sortable.sortables[element.id];
6987
6988    if(s) {
6989      Draggables.removeObserver(s.element);
6990      s.droppables.each(function(d){ Droppables.remove(d) });
6991      s.draggables.invoke('destroy');
6992
6993      delete Sortable.sortables[s.element.id];
6994    }
6995  },
6996
6997  create: function(element) {
6998    element = $(element);
6999    var options = Object.extend({
7000      element:     element,
7001      tag:         'li',       // assumes li children, override with tag: 'tagname'
7002      dropOnEmpty: false,
7003      tree:        false,
7004      treeTag:     'ul',
7005      overlap:     'vertical', // one of 'vertical', 'horizontal'
7006      constraint:  'vertical', // one of 'vertical', 'horizontal', false
7007      containment: element,    // also takes array of elements (or id's); or false
7008      handle:      false,      // or a CSS class
7009      only:        false,
7010      delay:       0,
7011      hoverclass:  null,
7012      ghosting:    false,
7013      quiet:       false,
7014      scroll:      false,
7015      scrollSensitivity: 20,
7016      scrollSpeed: 15,
7017      format:      this.SERIALIZE_RULE,
7018
7019      // these take arrays of elements or ids and can be
7020      // used for better initialization performance
7021      elements:    false,
7022      handles:     false,
7023
7024      onChange:    Prototype.emptyFunction,
7025      onUpdate:    Prototype.emptyFunction
7026    }, arguments[1] || { });
7027
7028    // clear any old sortable with same element
7029    this.destroy(element);
7030
7031    // build options for the draggables
7032    var options_for_draggable = {
7033      revert:      true,
7034      quiet:       options.quiet,
7035      scroll:      options.scroll,
7036      scrollSpeed: options.scrollSpeed,
7037      scrollSensitivity: options.scrollSensitivity,
7038      delay:       options.delay,
7039      ghosting:    options.ghosting,
7040      constraint:  options.constraint,
7041      handle:      options.handle };
7042
7043    if(options.starteffect)
7044      options_for_draggable.starteffect = options.starteffect;
7045
7046    if(options.reverteffect)
7047      options_for_draggable.reverteffect = options.reverteffect;
7048    else
7049      if(options.ghosting) options_for_draggable.reverteffect = function(element) {
7050        element.style.top  = 0;
7051        element.style.left = 0;
7052      };
7053
7054    if(options.endeffect)
7055      options_for_draggable.endeffect = options.endeffect;
7056
7057    if(options.zindex)
7058      options_for_draggable.zindex = options.zindex;
7059
7060    // build options for the droppables
7061    var options_for_droppable = {
7062      overlap:     options.overlap,
7063      containment: options.containment,
7064      tree:        options.tree,
7065      hoverclass:  options.hoverclass,
7066      onHover:     Sortable.onHover
7067    };
7068
7069    var options_for_tree = {
7070      onHover:      Sortable.onEmptyHover,
7071      overlap:      options.overlap,
7072      containment:  options.containment,
7073      hoverclass:   options.hoverclass
7074    };
7075
7076    // fix for gecko engine
7077    Element.cleanWhitespace(element);
7078
7079    options.draggables = [];
7080    options.droppables = [];
7081
7082    // drop on empty handling
7083    if(options.dropOnEmpty || options.tree) {
7084      Droppables.add(element, options_for_tree);
7085      options.droppables.push(element);
7086    }
7087
7088    (options.elements || this.findElements(element, options) || []).each( function(e,i) {
7089      var handle = options.handles ? $(options.handles[i]) :
7090        (options.handle ? $(e).select('.' + options.handle)[0] : e);
7091      options.draggables.push(
7092        new Draggable(e, Object.extend(options_for_draggable, { handle: handle })));
7093      Droppables.add(e, options_for_droppable);
7094      if(options.tree) e.treeNode = element;
7095      options.droppables.push(e);
7096    });
7097
7098    if(options.tree) {
7099      (Sortable.findTreeElements(element, options) || []).each( function(e) {
7100        Droppables.add(e, options_for_tree);
7101        e.treeNode = element;
7102        options.droppables.push(e);
7103      });
7104    }
7105
7106    // keep reference
7107    this.sortables[element.id] = options;
7108
7109    // for onupdate
7110    Draggables.addObserver(new SortableObserver(element, options.onUpdate));
7111
7112  },
7113
7114  // return all suitable-for-sortable elements in a guaranteed order
7115  findElements: function(element, options) {
7116    return Element.findChildren(
7117      element, options.only, options.tree ? true : false, options.tag);
7118  },
7119
7120  findTreeElements: function(element, options) {
7121    return Element.findChildren(
7122      element, options.only, options.tree ? true : false, options.treeTag);
7123  },
7124
7125  onHover: function(element, dropon, overlap) {
7126    if(Element.isParent(dropon, element)) return;
7127
7128    if(overlap > .33 && overlap < .66 && Sortable.options(dropon).tree) {
7129      return;
7130    } else if(overlap>0.5) {
7131      Sortable.mark(dropon, 'before');
7132      if(dropon.previousSibling != element) {
7133        var oldParentNode = element.parentNode;
7134        element.style.visibility = "hidden"; // fix gecko rendering
7135        dropon.parentNode.insertBefore(element, dropon);
7136        if(dropon.parentNode!=oldParentNode)
7137          Sortable.options(oldParentNode).onChange(element);
7138        Sortable.options(dropon.parentNode).onChange(element);
7139      }
7140    } else {
7141      Sortable.mark(dropon, 'after');
7142      var nextElement = dropon.nextSibling || null;
7143      if(nextElement != element) {
7144        var oldParentNode = element.parentNode;
7145        element.style.visibility = "hidden"; // fix gecko rendering
7146        dropon.parentNode.insertBefore(element, nextElement);
7147        if(dropon.parentNode!=oldParentNode)
7148          Sortable.options(oldParentNode).onChange(element);
7149        Sortable.options(dropon.parentNode).onChange(element);
7150      }
7151    }
7152  },
7153
7154  onEmptyHover: function(element, dropon, overlap) {
7155    var oldParentNode = element.parentNode;
7156    var droponOptions = Sortable.options(dropon);
7157
7158    if(!Element.isParent(dropon, element)) {
7159      var index;
7160
7161      var children = Sortable.findElements(dropon, {tag: droponOptions.tag, only: droponOptions.only});
7162      var child = null;
7163
7164      if(children) {
7165        var offset = Element.offsetSize(dropon, droponOptions.overlap) * (1.0 - overlap);
7166
7167        for (index = 0; index < children.length; index += 1) {
7168          if (offset - Element.offsetSize (children[index], droponOptions.overlap) >= 0) {
7169            offset -= Element.offsetSize (children[index], droponOptions.overlap);
7170          } else if (offset - (Element.offsetSize (children[index], droponOptions.overlap) / 2) >= 0) {
7171            child = index + 1 < children.length ? children[index + 1] : null;
7172            break;
7173          } else {
7174            child = children[index];
7175            break;
7176          }
7177        }
7178      }
7179
7180      dropon.insertBefore(element, child);
7181
7182      Sortable.options(oldParentNode).onChange(element);
7183      droponOptions.onChange(element);
7184    }
7185  },
7186
7187  unmark: function() {
7188    if(Sortable._marker) Sortable._marker.hide();
7189  },
7190
7191  mark: function(dropon, position) {
7192    // mark on ghosting only
7193    var sortable = Sortable.options(dropon.parentNode);
7194    if(sortable && !sortable.ghosting) return;
7195
7196    if(!Sortable._marker) {
7197      Sortable._marker =
7198        ($('dropmarker') || Element.extend(document.createElement('DIV'))).
7199          hide().addClassName('dropmarker').setStyle({position:'absolute'});
7200      document.getElementsByTagName("body").item(0).appendChild(Sortable._marker);
7201    }
7202    var offsets = Position.cumulativeOffset(dropon);
7203    Sortable._marker.setStyle({left: offsets[0]+'px', top: offsets[1] + 'px'});
7204
7205    if(position=='after')
7206      if(sortable.overlap == 'horizontal')
7207        Sortable._marker.setStyle({left: (offsets[0]+dropon.clientWidth) + 'px'});
7208      else
7209        Sortable._marker.setStyle({top: (offsets[1]+dropon.clientHeight) + 'px'});
7210
7211    Sortable._marker.show();
7212  },
7213
7214  _tree: function(element, options, parent) {
7215    var children = Sortable.findElements(element, options) || [];
7216
7217    for (var i = 0; i < children.length; ++i) {
7218      var match = children[i].id.match(options.format);
7219
7220      if (!match) continue;
7221
7222      var child = {
7223        id: encodeURIComponent(match ? match[1] : null),
7224        element: element,
7225        parent: parent,
7226        children: [],
7227        position: parent.children.length,
7228        container: $(children[i]).down(options.treeTag)
7229      };
7230
7231      /* Get the element containing the children and recurse over it */
7232      if (child.container)
7233        this._tree(child.container, options, child);
7234
7235      parent.children.push (child);
7236    }
7237
7238    return parent;
7239  },
7240
7241  tree: function(element) {
7242    element = $(element);
7243    var sortableOptions = this.options(element);
7244    var options = Object.extend({
7245      tag: sortableOptions.tag,
7246      treeTag: sortableOptions.treeTag,
7247      only: sortableOptions.only,
7248      name: element.id,
7249      format: sortableOptions.format
7250    }, arguments[1] || { });
7251
7252    var root = {
7253      id: null,
7254      parent: null,
7255      children: [],
7256      container: element,
7257      position: 0
7258    };
7259
7260    return Sortable._tree(element, options, root);
7261  },
7262
7263  /* Construct a [i] index for a particular node */
7264  _constructIndex: function(node) {
7265    var index = '';
7266    do {
7267      if (node.id) index = '[' + node.position + ']' + index;
7268    } while ((node = node.parent) != null);
7269    return index;
7270  },
7271
7272  sequence: function(element) {
7273    element = $(element);
7274    var options = Object.extend(this.options(element), arguments[1] || { });
7275
7276    return $(this.findElements(element, options) || []).map( function(item) {
7277      return item.id.match(options.format) ? item.id.match(options.format)[1] : '';
7278    });
7279  },
7280
7281  setSequence: function(element, new_sequence) {
7282    element = $(element);
7283    var options = Object.extend(this.options(element), arguments[2] || { });
7284
7285    var nodeMap = { };
7286    this.findElements(element, options).each( function(n) {
7287        if (n.id.match(options.format))
7288            nodeMap[n.id.match(options.format)[1]] = [n, n.parentNode];
7289        n.parentNode.removeChild(n);
7290    });
7291
7292    new_sequence.each(function(ident) {
7293      var n = nodeMap[ident];
7294      if (n) {
7295        n[1].appendChild(n[0]);
7296        delete nodeMap[ident];
7297      }
7298    });
7299  },
7300
7301  serialize: function(element) {
7302    element = $(element);
7303    var options = Object.extend(Sortable.options(element), arguments[1] || { });
7304    var name = encodeURIComponent(
7305      (arguments[1] && arguments[1].name) ? arguments[1].name : element.id);
7306
7307    if (options.tree) {
7308      return Sortable.tree(element, arguments[1]).children.map( function (item) {
7309        return [name + Sortable._constructIndex(item) + "[id]=" +
7310                encodeURIComponent(item.id)].concat(item.children.map(arguments.callee));
7311      }).flatten().join('&');
7312    } else {
7313      return Sortable.sequence(element, arguments[1]).map( function(item) {
7314        return name + "[]=" + encodeURIComponent(item);
7315      }).join('&');
7316    }
7317  }
7318};
7319
7320// Returns true if child is contained within element
7321Element.isParent = function(child, element) {
7322  if (!child.parentNode || child == element) return false;
7323  if (child.parentNode == element) return true;
7324  return Element.isParent(child.parentNode, element);
7325};
7326
7327Element.findChildren = function(element, only, recursive, tagName) {
7328  if(!element.hasChildNodes()) return null;
7329  tagName = tagName.toUpperCase();
7330  if(only) only = [only].flatten();
7331  var elements = [];
7332  $A(element.childNodes).each( function(e) {
7333    if(e.tagName && e.tagName.toUpperCase()==tagName &&
7334      (!only || (Element.classNames(e).detect(function(v) { return only.include(v) }))))
7335        elements.push(e);
7336    if(recursive) {
7337      var grandchildren = Element.findChildren(e, only, recursive, tagName);
7338      if(grandchildren) elements.push(grandchildren);
7339    }
7340  });
7341
7342  return (elements.length>0 ? elements.flatten() : []);
7343};
7344
7345Element.offsetSize = function (element, type) {
7346  return element['offset' + ((type=='vertical' || type=='height') ? 'Height' : 'Width')];
7347};
7348// script.aculo.us controls.js v1.8.2, Tue Nov 18 18:30:58 +0100 2008
7349
7350// Copyright (c) 2005-2008 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us)
7351//           (c) 2005-2008 Ivan Krstic (http://blogs.law.harvard.edu/ivan)
7352//           (c) 2005-2008 Jon Tirsen (http://www.tirsen.com)
7353// Contributors:
7354//  Richard Livsey
7355//  Rahul Bhargava
7356//  Rob Wills
7357//
7358// script.aculo.us is freely distributable under the terms of an MIT-style license.
7359// For details, see the script.aculo.us web site: http://script.aculo.us/
7360
7361// Autocompleter.Base handles all the autocompletion functionality
7362// that's independent of the data source for autocompletion. This
7363// includes drawing the autocompletion menu, observing keyboard
7364// and mouse events, and similar.
7365//
7366// Specific autocompleters need to provide, at the very least,
7367// a getUpdatedChoices function that will be invoked every time
7368// the text inside the monitored textbox changes. This method
7369// should get the text for which to provide autocompletion by
7370// invoking this.getToken(), NOT by directly accessing
7371// this.element.value. This is to allow incremental tokenized
7372// autocompletion. Specific auto-completion logic (AJAX, etc)
7373// belongs in getUpdatedChoices.
7374//
7375// Tokenized incremental autocompletion is enabled automatically
7376// when an autocompleter is instantiated with the 'tokens' option
7377// in the options parameter, e.g.:
7378// new Ajax.Autocompleter('id','upd', '/url/', { tokens: ',' });
7379// will incrementally autocomplete with a comma as the token.
7380// Additionally, ',' in the above example can be replaced with
7381// a token array, e.g. { tokens: [',', '\n'] } which
7382// enables autocompletion on multiple tokens. This is most
7383// useful when one of the tokens is \n (a newline), as it
7384// allows smart autocompletion after linebreaks.
7385
7386if(typeof Effect == 'undefined')
7387  throw("controls.js requires including script.aculo.us' effects.js library");
7388
7389var Autocompleter = { };
7390Autocompleter.Base = Class.create({
7391  baseInitialize: function(element, update, options) {
7392    element          = $(element);
7393    this.element     = element;
7394    this.update      = $(update);
7395    this.hasFocus    = false;
7396    this.changed     = false;
7397    this.active      = false;
7398    this.index       = 0;
7399    this.entryCount  = 0;
7400    this.oldElementValue = this.element.value;
7401
7402    if(this.setOptions)
7403      this.setOptions(options);
7404    else
7405      this.options = options || { };
7406
7407    this.options.paramName    = this.options.paramName || this.element.name;
7408    this.options.tokens       = this.options.tokens || [];
7409    this.options.frequency    = this.options.frequency || 0.4;
7410    this.options.minChars     = this.options.minChars || 1;
7411    this.options.onShow       = this.options.onShow ||
7412      function(element, update){
7413        if(!update.style.position || update.style.position=='absolute') {
7414          update.style.position = 'absolute';
7415          Position.clone(element, update, {
7416            setHeight: false,
7417            offsetTop: element.offsetHeight
7418          });
7419        }
7420        Effect.Appear(update,{duration:0.15});
7421      };
7422    this.options.onHide = this.options.onHide ||
7423      function(element, update){ new Effect.Fade(update,{duration:0.15}) };
7424
7425    if(typeof(this.options.tokens) == 'string')
7426      this.options.tokens = new Array(this.options.tokens);
7427    // Force carriage returns as token delimiters anyway
7428    if (!this.options.tokens.include('\n'))
7429      this.options.tokens.push('\n');
7430
7431    this.observer = null;
7432
7433    this.element.setAttribute('autocomplete','off');
7434
7435    Element.hide(this.update);
7436
7437    Event.observe(this.element, 'blur', this.onBlur.bindAsEventListener(this));
7438    Event.observe(this.element, 'keydown', this.onKeyPress.bindAsEventListener(this));
7439  },
7440
7441  show: function() {
7442    if(Element.getStyle(this.update, 'display')=='none') this.options.onShow(this.element, this.update);
7443    if(!this.iefix &&
7444      (Prototype.Browser.IE) &&
7445      (Element.getStyle(this.update, 'position')=='absolute')) {
7446      new Insertion.After(this.update,
7447       '<iframe id="' + this.update.id + '_iefix" '+
7448       'style="display:none;position:absolute;filter:progid:DXImageTransform.Microsoft.Alpha(opacity=0);" ' +
7449       'src="javascript:false;" frameborder="0" scrolling="no"></iframe>');
7450      this.iefix = $(this.update.id+'_iefix');
7451    }
7452    if(this.iefix) setTimeout(this.fixIEOverlapping.bind(this), 50);
7453  },
7454
7455  fixIEOverlapping: function() {
7456    Position.clone(this.update, this.iefix, {setTop:(!this.update.style.height)});
7457    this.iefix.style.zIndex = 1;
7458    this.update.style.zIndex = 2;
7459    Element.show(this.iefix);
7460  },
7461
7462  hide: function() {
7463    this.stopIndicator();
7464    if(Element.getStyle(this.update, 'display')!='none') this.options.onHide(this.element, this.update);
7465    if(this.iefix) Element.hide(this.iefix);
7466  },
7467
7468  startIndicator: function() {
7469    if(this.options.indicator) Element.show(this.options.indicator);
7470  },
7471
7472  stopIndicator: function() {
7473    if(this.options.indicator) Element.hide(this.options.indicator);
7474  },
7475
7476  onKeyPress: function(event) {
7477    if(this.active)
7478      switch(event.keyCode) {
7479       case Event.KEY_TAB:
7480       case Event.KEY_RETURN:
7481         this.selectEntry();
7482         Event.stop(event);
7483       case Event.KEY_ESC:
7484         this.hide();
7485         this.active = false;
7486         Event.stop(event);
7487         return;
7488       case Event.KEY_LEFT:
7489       case Event.KEY_RIGHT:
7490         return;
7491       case Event.KEY_UP:
7492         this.markPrevious();
7493         this.render();
7494         Event.stop(event);
7495         return;
7496       case Event.KEY_DOWN:
7497         this.markNext();
7498         this.render();
7499         Event.stop(event);
7500         return;
7501      }
7502     else
7503       if(event.keyCode==Event.KEY_TAB || event.keyCode==Event.KEY_RETURN ||
7504         (Prototype.Browser.WebKit > 0 && event.keyCode == 0)) return;
7505
7506    this.changed = true;
7507    this.hasFocus = true;
7508
7509    if(this.observer) clearTimeout(this.observer);
7510      this.observer =
7511        setTimeout(this.onObserverEvent.bind(this), this.options.frequency*1000);
7512  },
7513
7514  activate: function() {
7515    this.changed = false;
7516    this.hasFocus = true;
7517    this.getUpdatedChoices();
7518  },
7519
7520  onHover: function(event) {
7521    var element = Event.findElement(event, 'LI');
7522    if(this.index != element.autocompleteIndex)
7523    {
7524        this.index = element.autocompleteIndex;
7525        this.render();
7526    }
7527    Event.stop(event);
7528  },
7529
7530  onClick: function(event) {
7531    var element = Event.findElement(event, 'LI');
7532    this.index = element.autocompleteIndex;
7533    this.selectEntry();
7534    this.hide();
7535  },
7536
7537  onBlur: function(event) {
7538    // needed to make click events working
7539    setTimeout(this.hide.bind(this), 250);
7540    this.hasFocus = false;
7541    this.active = false;
7542  },
7543
7544  render: function() {
7545    if(this.entryCount > 0) {
7546      for (var i = 0; i < this.entryCount; i++)
7547        this.index==i ?
7548          Element.addClassName(this.getEntry(i),"selected") :
7549          Element.removeClassName(this.getEntry(i),"selected");
7550      if(this.hasFocus) {
7551        this.show();
7552        this.active = true;
7553      }
7554    } else {
7555      this.active = false;
7556      this.hide();
7557    }
7558  },
7559
7560  markPrevious: function() {
7561    if(this.index > 0) this.index--;
7562      else this.index = this.entryCount-1;
7563    //this.getEntry(this.index).scrollIntoView(true); useless
7564  },
7565
7566  markNext: function() {
7567    if(this.index < this.entryCount-1) this.index++;
7568      else this.index = 0;
7569    this.getEntry(this.index).scrollIntoView(false);
7570  },
7571
7572  getEntry: function(index) {
7573    return this.update.firstChild.childNodes[index];
7574  },
7575
7576  getCurrentEntry: function() {
7577    return this.getEntry(this.index);
7578  },
7579
7580  selectEntry: function() {
7581    this.active = false;
7582    this.updateElement(this.getCurrentEntry());
7583  },
7584
7585  updateElement: function(selectedElement) {
7586    if (this.options.updateElement) {
7587      this.options.updateElement(selectedElement);
7588      return;
7589    }
7590    var value = '';
7591    if (this.options.select) {
7592      var nodes = $(selectedElement).select('.' + this.options.select) || [];
7593      if(nodes.length>0) value = Element.collectTextNodes(nodes[0], this.options.select);
7594    } else
7595      value = Element.collectTextNodesIgnoreClass(selectedElement, 'informal');
7596
7597    var bounds = this.getTokenBounds();
7598    if (bounds[0] != -1) {
7599      var newValue = this.element.value.substr(0, bounds[0]);
7600      var whitespace = this.element.value.substr(bounds[0]).match(/^\s+/);
7601      if (whitespace)
7602        newValue += whitespace[0];
7603      this.element.value = newValue + value + this.element.value.substr(bounds[1]);
7604    } else {
7605      this.element.value = value;
7606    }
7607    this.oldElementValue = this.element.value;
7608    this.element.focus();
7609
7610    if (this.options.afterUpdateElement)
7611      this.options.afterUpdateElement(this.element, selectedElement);
7612  },
7613
7614  updateChoices: function(choices) {
7615    if(!this.changed && this.hasFocus) {
7616      this.update.innerHTML = choices;
7617      Element.cleanWhitespace(this.update);
7618      Element.cleanWhitespace(this.update.down());
7619
7620      if(this.update.firstChild && this.update.down().childNodes) {
7621        this.entryCount =
7622          this.update.down().childNodes.length;
7623        for (var i = 0; i < this.entryCount; i++) {
7624          var entry = this.getEntry(i);
7625          entry.autocompleteIndex = i;
7626          this.addObservers(entry);
7627        }
7628      } else {
7629        this.entryCount = 0;
7630      }
7631
7632      this.stopIndicator();
7633      this.index = 0;
7634
7635      if(this.entryCount==1 && this.options.autoSelect) {
7636        this.selectEntry();
7637        this.hide();
7638      } else {
7639        this.render();
7640      }
7641    }
7642  },
7643
7644  addObservers: function(element) {
7645    Event.observe(element, "mouseover", this.onHover.bindAsEventListener(this));
7646    Event.observe(element, "click", this.onClick.bindAsEventListener(this));
7647  },
7648
7649  onObserverEvent: function() {
7650    this.changed = false;
7651    this.tokenBounds = null;
7652    if(this.getToken().length>=this.options.minChars) {
7653      this.getUpdatedChoices();
7654    } else {
7655      this.active = false;
7656      this.hide();
7657    }
7658    this.oldElementValue = this.element.value;
7659  },
7660
7661  getToken: function() {
7662    var bounds = this.getTokenBounds();
7663    return this.element.value.substring(bounds[0], bounds[1]).strip();
7664  },
7665
7666  getTokenBounds: function() {
7667    if (null != this.tokenBounds) return this.tokenBounds;
7668    var value = this.element.value;
7669    if (value.strip().empty()) return [-1, 0];
7670    var diff = arguments.callee.getFirstDifferencePos(value, this.oldElementValue);
7671    var offset = (diff == this.oldElementValue.length ? 1 : 0);
7672    var prevTokenPos = -1, nextTokenPos = value.length;
7673    var tp;
7674    for (var index = 0, l = this.options.tokens.length; index < l; ++index) {
7675      tp = value.lastIndexOf(this.options.tokens[index], diff + offset - 1);
7676      if (tp > prevTokenPos) prevTokenPos = tp;
7677      tp = value.indexOf(this.options.tokens[index], diff + offset);
7678      if (-1 != tp && tp < nextTokenPos) nextTokenPos = tp;
7679    }
7680    return (this.tokenBounds = [prevTokenPos + 1, nextTokenPos]);
7681  }
7682});
7683
7684Autocompleter.Base.prototype.getTokenBounds.getFirstDifferencePos = function(newS, oldS) {
7685  var boundary = Math.min(newS.length, oldS.length);
7686  for (var index = 0; index < boundary; ++index)
7687    if (newS[index] != oldS[index])
7688      return index;
7689  return boundary;
7690};
7691
7692Ajax.Autocompleter = Class.create(Autocompleter.Base, {
7693  initialize: function(element, update, url, options) {
7694    this.baseInitialize(element, update, options);
7695    this.options.asynchronous  = true;
7696    this.options.onComplete    = this.onComplete.bind(this);
7697    this.options.defaultParams = this.options.parameters || null;
7698    this.url                   = url;
7699  },
7700
7701  getUpdatedChoices: function() {
7702    this.startIndicator();
7703
7704    var entry = encodeURIComponent(this.options.paramName) + '=' +
7705      encodeURIComponent(this.getToken());
7706
7707    this.options.parameters = this.options.callback ?
7708      this.options.callback(this.element, entry) : entry;
7709
7710    if(this.options.defaultParams)
7711      this.options.parameters += '&' + this.options.defaultParams;
7712
7713    new Ajax.Request(this.url, this.options);
7714  },
7715
7716  onComplete: function(request) {
7717    this.updateChoices(request.responseText);
7718  }
7719});
7720
7721// The local array autocompleter. Used when you'd prefer to
7722// inject an array of autocompletion options into the page, rather
7723// than sending out Ajax queries, which can be quite slow sometimes.
7724//
7725// The constructor takes four parameters. The first two are, as usual,
7726// the id of the monitored textbox, and id of the autocompletion menu.
7727// The third is the array you want to autocomplete from, and the fourth
7728// is the options block.
7729//
7730// Extra local autocompletion options:
7731// - choices - How many autocompletion choices to offer
7732//
7733// - partialSearch - If false, the autocompleter will match entered
7734//                    text only at the beginning of strings in the
7735//                    autocomplete array. Defaults to true, which will
7736//                    match text at the beginning of any *word* in the
7737//                    strings in the autocomplete array. If you want to
7738//                    search anywhere in the string, additionally set
7739//                    the option fullSearch to true (default: off).
7740//
7741// - fullSsearch - Search anywhere in autocomplete array strings.
7742//
7743// - partialChars - How many characters to enter before triggering
7744//                   a partial match (unlike minChars, which defines
7745//                   how many characters are required to do any match
7746//                   at all). Defaults to 2.
7747//
7748// - ignoreCase - Whether to ignore case when autocompleting.
7749//                 Defaults to true.
7750//
7751// It's possible to pass in a custom function as the 'selector'
7752// option, if you prefer to write your own autocompletion logic.
7753// In that case, the other options above will not apply unless
7754// you support them.
7755
7756Autocompleter.Local = Class.create(Autocompleter.Base, {
7757  initialize: function(element, update, array, options) {
7758    this.baseInitialize(element, update, options);
7759    this.options.array = array;
7760  },
7761
7762  getUpdatedChoices: function() {
7763    this.updateChoices(this.options.selector(this));
7764  },
7765
7766  setOptions: function(options) {
7767    this.options = Object.extend({
7768      choices: 10,
7769      partialSearch: true,
7770      partialChars: 2,
7771      ignoreCase: true,
7772      fullSearch: false,
7773      selector: function(instance) {
7774        var ret       = []; // Beginning matches
7775        var partial   = []; // Inside matches
7776        var entry     = instance.getToken();
7777        var count     = 0;
7778
7779        for (var i = 0; i < instance.options.array.length &&
7780          ret.length < instance.options.choices ; i++) {
7781
7782          var elem = instance.options.array[i];
7783          var foundPos = instance.options.ignoreCase ?
7784            elem.toLowerCase().indexOf(entry.toLowerCase()) :
7785            elem.indexOf(entry);
7786
7787          while (foundPos != -1) {
7788            if (foundPos == 0 && elem.length != entry.length) {
7789              ret.push("<li><strong>" + elem.substr(0, entry.length) + "</strong>" +
7790                elem.substr(entry.length) + "</li>");
7791              break;
7792            } else if (entry.length >= instance.options.partialChars &&
7793              instance.options.partialSearch && foundPos != -1) {
7794              if (instance.options.fullSearch || /\s/.test(elem.substr(foundPos-1,1))) {
7795                partial.push("<li>" + elem.substr(0, foundPos) + "<strong>" +
7796                  elem.substr(foundPos, entry.length) + "</strong>" + elem.substr(
7797                  foundPos + entry.length) + "</li>");
7798                break;
7799              }
7800            }
7801
7802            foundPos = instance.options.ignoreCase ?
7803              elem.toLowerCase().indexOf(entry.toLowerCase(), foundPos + 1) :
7804              elem.indexOf(entry, foundPos + 1);
7805
7806          }
7807        }
7808        if (partial.length)
7809          ret = ret.concat(partial.slice(0, instance.options.choices - ret.length));
7810        return "<ul>" + ret.join('') + "</ul>";
7811      }
7812    }, options || { });
7813  }
7814});
7815
7816// AJAX in-place editor and collection editor
7817// Full rewrite by Christophe Porteneuve <[email protected]> (April 2007).
7818
7819// Use this if you notice weird scrolling problems on some browsers,
7820// the DOM might be a bit confused when this gets called so do this
7821// waits 1 ms (with setTimeout) until it does the activation
7822Field.scrollFreeActivate = function(field) {
7823  setTimeout(function() {
7824    Field.activate(field);
7825  }, 1);
7826};
7827
7828Ajax.InPlaceEditor = Class.create({
7829  initialize: function(element, url, options) {
7830    this.url = url;
7831    this.element = element = $(element);
7832    this.prepareOptions();
7833    this._controls = { };
7834    arguments.callee.dealWithDeprecatedOptions(options); // DEPRECATION LAYER!!!
7835    Object.extend(this.options, options || { });
7836    if (!this.options.formId && this.element.id) {
7837      this.options.formId = this.element.id + '-inplaceeditor';
7838      if ($(this.options.formId))
7839        this.options.formId = '';
7840    }
7841    if (this.options.externalControl)
7842      this.options.externalControl = $(this.options.externalControl);
7843    if (!this.options.externalControl)
7844      this.options.externalControlOnly = false;
7845    this._originalBackground = this.element.getStyle('background-color') || 'transparent';
7846    this.element.title = this.options.clickToEditText;
7847    this._boundCancelHandler = this.handleFormCancellation.bind(this);
7848    this._boundComplete = (this.options.onComplete || Prototype.emptyFunction).bind(this);
7849    this._boundFailureHandler = this.handleAJAXFailure.bind(this);
7850    this._boundSubmitHandler = this.handleFormSubmission.bind(this);
7851    this._boundWrapperHandler = this.wrapUp.bind(this);
7852    this.registerListeners();
7853  },
7854  checkForEscapeOrReturn: function(e) {
7855    if (!this._editing || e.ctrlKey || e.altKey || e.shiftKey) return;
7856    if (Event.KEY_ESC == e.keyCode)
7857      this.handleFormCancellation(e);
7858    else if (Event.KEY_RETURN == e.keyCode)
7859      this.handleFormSubmission(e);
7860  },
7861  createControl: function(mode, handler, extraClasses) {
7862    var control = this.options[mode + 'Control'];
7863    var text = this.options[mode + 'Text'];
7864    if ('button' == control) {
7865      var btn = document.createElement('input');
7866      btn.type = 'submit';
7867      btn.value = text;
7868      btn.className = 'editor_' + mode + '_button';
7869      if ('cancel' == mode)
7870        btn.onclick = this._boundCancelHandler;
7871      this._form.appendChild(btn);
7872      this._controls[mode] = btn;
7873    } else if ('link' == control) {
7874      var link = document.createElement('a');
7875      link.href = '#';
7876      link.appendChild(document.createTextNode(text));
7877      link.onclick = 'cancel' == mode ? this._boundCancelHandler : this._boundSubmitHandler;
7878      link.className = 'editor_' + mode + '_link';
7879      if (extraClasses)
7880        link.className += ' ' + extraClasses;
7881      this._form.appendChild(link);
7882      this._controls[mode] = link;
7883    }
7884  },
7885  createEditField: function() {
7886    var text = (this.options.loadTextURL ? this.options.loadingText : this.getText());
7887    var fld;
7888    if (1 >= this.options.rows && !/\r|\n/.test(this.getText())) {
7889      fld = document.createElement('input');
7890      fld.type = 'text';
7891      var size = this.options.size || this.options.cols || 0;
7892      if (0 < size) fld.size = size;
7893    } else {
7894      fld = document.createElement('textarea');
7895      fld.rows = (1 >= this.options.rows ? this.options.autoRows : this.options.rows);
7896      fld.cols = this.options.cols || 40;
7897    }
7898    fld.name = this.options.paramName;
7899    fld.value = text; // No HTML breaks conversion anymore
7900    fld.className = 'editor_field';
7901    if (this.options.submitOnBlur)
7902      fld.onblur = this._boundSubmitHandler;
7903    this._controls.editor = fld;
7904    if (this.options.loadTextURL)
7905      this.loadExternalText();
7906    this._form.appendChild(this._controls.editor);
7907  },
7908  createForm: function() {
7909    var ipe = this;
7910    function addText(mode, condition) {
7911      var text = ipe.options['text' + mode + 'Controls'];
7912      if (!text || condition === false) return;
7913      ipe._form.appendChild(document.createTextNode(text));
7914    };
7915    this._form = $(document.createElement('form'));
7916    this._form.id = this.options.formId;
7917    this._form.addClassName(this.options.formClassName);
7918    this._form.onsubmit = this._boundSubmitHandler;
7919    this.createEditField();
7920    if ('textarea' == this._controls.editor.tagName.toLowerCase())
7921      this._form.appendChild(document.createElement('br'));
7922    if (this.options.onFormCustomization)
7923      this.options.onFormCustomization(this, this._form);
7924    addText('Before', this.options.okControl || this.options.cancelControl);
7925    this.createControl('ok', this._boundSubmitHandler);
7926    addText('Between', this.options.okControl && this.options.cancelControl);
7927    this.createControl('cancel', this._boundCancelHandler, 'editor_cancel');
7928    addText('After', this.options.okControl || this.options.cancelControl);
7929  },
7930  destroy: function() {
7931    if (this._oldInnerHTML)
7932      this.element.innerHTML = this._oldInnerHTML;
7933    this.leaveEditMode();
7934    this.unregisterListeners();
7935  },
7936  enterEditMode: function(e) {
7937    if (this._saving || this._editing) return;
7938    this._editing = true;
7939    this.triggerCallback('onEnterEditMode');
7940    if (this.options.externalControl)
7941      this.options.externalControl.hide();
7942    this.element.hide();
7943    this.createForm();
7944    this.element.parentNode.insertBefore(this._form, this.element);
7945    if (!this.options.loadTextURL)
7946      this.postProcessEditField();
7947    if (e) Event.stop(e);
7948  },
7949  enterHover: function(e) {
7950    if (this.options.hoverClassName)
7951      this.element.addClassName(this.options.hoverClassName);
7952    if (this._saving) return;
7953    this.triggerCallback('onEnterHover');
7954  },
7955  getText: function() {
7956    return this.element.innerHTML.unescapeHTML();
7957  },
7958  handleAJAXFailure: function(transport) {
7959    this.triggerCallback('onFailure', transport);
7960    if (this._oldInnerHTML) {
7961      this.element.innerHTML = this._oldInnerHTML;
7962      this._oldInnerHTML = null;
7963    }
7964  },
7965  handleFormCancellation: function(e) {
7966    this.wrapUp();
7967    if (e) Event.stop(e);
7968  },
7969  handleFormSubmission: function(e) {
7970    var form = this._form;
7971    var value = $F(this._controls.editor);
7972    this.prepareSubmission();
7973    var params = this.options.callback(form, value) || '';
7974    if (Object.isString(params))
7975      params = params.toQueryParams();
7976    params.editorId = this.element.id;
7977    if (this.options.htmlResponse) {
7978      var options = Object.extend({ evalScripts: true }, this.options.ajaxOptions);
7979      Object.extend(options, {
7980        parameters: params,
7981        onComplete: this._boundWrapperHandler,
7982        onFailure: this._boundFailureHandler
7983      });
7984      new Ajax.Updater({ success: this.element }, this.url, options);
7985    } else {
7986      var options = Object.extend({ method: 'get' }, this.options.ajaxOptions);
7987      Object.extend(options, {
7988        parameters: params,
7989        onComplete: this._boundWrapperHandler,
7990        onFailure: this._boundFailureHandler
7991      });
7992      new Ajax.Request(this.url, options);
7993    }
7994    if (e) Event.stop(e);
7995  },
7996  leaveEditMode: function() {
7997    this.element.removeClassName(this.options.savingClassName);
7998    this.removeForm();
7999    this.leaveHover();
8000    this.element.style.backgroundColor = this._originalBackground;
8001    this.element.show();
8002    if (this.options.externalControl)
8003      this.options.externalControl.show();
8004    this._saving = false;
8005    this._editing = false;
8006    this._oldInnerHTML = null;
8007    this.triggerCallback('onLeaveEditMode');
8008  },
8009  leaveHover: function(e) {
8010    if (this.options.hoverClassName)
8011      this.element.removeClassName(this.options.hoverClassName);
8012    if (this._saving) return;
8013    this.triggerCallback('onLeaveHover');
8014  },
8015  loadExternalText: function() {
8016    this._form.addClassName(this.options.loadingClassName);
8017    this._controls.editor.disabled = true;
8018    var options = Object.extend({ method: 'get' }, this.options.ajaxOptions);
8019    Object.extend(options, {
8020      parameters: 'editorId=' + encodeURIComponent(this.element.id),
8021      onComplete: Prototype.emptyFunction,
8022      onSuccess: function(transport) {
8023        this._form.removeClassName(this.options.loadingClassName);
8024        var text = transport.responseText;
8025        if (this.options.stripLoadedTextTags)
8026          text = text.stripTags();
8027        this._controls.editor.value = text;
8028        this._controls.editor.disabled = false;
8029        this.postProcessEditField();
8030      }.bind(this),
8031      onFailure: this._boundFailureHandler
8032    });
8033    new Ajax.Request(this.options.loadTextURL, options);
8034  },
8035  postProcessEditField: function() {
8036    var fpc = this.options.fieldPostCreation;
8037    if (fpc)
8038      $(this._controls.editor)['focus' == fpc ? 'focus' : 'activate']();
8039  },
8040  prepareOptions: function() {
8041    this.options = Object.clone(Ajax.InPlaceEditor.DefaultOptions);
8042    Object.extend(this.options, Ajax.InPlaceEditor.DefaultCallbacks);
8043    [this._extraDefaultOptions].flatten().compact().each(function(defs) {
8044      Object.extend(this.options, defs);
8045    }.bind(this));
8046  },
8047  prepareSubmission: function() {
8048    this._saving = true;
8049    this.removeForm();
8050    this.leaveHover();
8051    this.showSaving();
8052  },
8053  registerListeners: function() {
8054    this._listeners = { };
8055    var listener;
8056    $H(Ajax.InPlaceEditor.Listeners).each(function(pair) {
8057      listener = this[pair.value].bind(this);
8058      this._listeners[pair.key] = listener;
8059      if (!this.options.externalControlOnly)
8060        this.element.observe(pair.key, listener);
8061      if (this.options.externalControl)
8062        this.options.externalControl.observe(pair.key, listener);
8063    }.bind(this));
8064  },
8065  removeForm: function() {
8066    if (!this._form) return;
8067    this._form.remove();
8068    this._form = null;
8069    this._controls = { };
8070  },
8071  showSaving: function() {
8072    this._oldInnerHTML = this.element.innerHTML;
8073    this.element.innerHTML = this.options.savingText;
8074    this.element.addClassName(this.options.savingClassName);
8075    this.element.style.backgroundColor = this._originalBackgroun
8075d;
8076    this.element.show();
8077  },
8078  triggerCallback: function(cbName, arg) {
8079    if ('function' == typeof this.options[cbName]) {
8080      this.options[cbName](this, arg);
8081    }
8082  },
8083  unregisterListeners: function() {
8084    $H(this._listeners).each(function(pair) {
8085      if (!this.options.externalControlOnly)
8086        this.element.stopObserving(pair.key, pair.value);
8087      if (this.options.externalControl)
8088        this.options.externalControl.stopObserving(pair.key, pair.value);
8089    }.bind(this));
8090  },
8091  wrapUp: function(transport) {
8092    this.leaveEditMode();
8093    // Can't use triggerCallback due to backward compatibility: requires
8094    // binding + direct element
8095    this._boundComplete(transport, this.element);
8096  }
8097});
8098
8099Object.extend(Ajax.InPlaceEditor.prototype, {
8100  dispose: Ajax.InPlaceEditor.prototype.destroy
8101});
8102
8103Ajax.InPlaceCollectionEditor = Class.create(Ajax.InPlaceEditor, {
8104  initialize: function($super, element, url, options) {
8105    this._extraDefaultOptions = Ajax.InPlaceCollectionEditor.DefaultOptions;
8106    $super(element, url, options);
8107  },
8108
8109  createEditField: function() {
8110    var list = document.createElement('select');
8111    list.name = this.options.paramName;
8112    list.size = 1;
8113    this._controls.editor = list;
8114    this._collection = this.options.collection || [];
8115    if (this.options.loadCollectionURL)
8116      this.loadCollection();
8117    else
8118      this.checkForExternalText();
8119    this._form.appendChild(this._controls.editor);
8120  },
8121
8122  loadCollection: function() {
8123    this._form.addClassName(this.options.loadingClassName);
8124    this.showLoadingText(this.options.loadingCollectionText);
8125    var options = Object.extend({ method: 'get' }, this.options.ajaxOptions);
8126    Object.extend(options, {
8127      parameters: 'editorId=' + encodeURIComponent(this.element.id),
8128      onComplete: Prototype.emptyFunction,
8129      onSuccess: function(transport) {
8130        var js = transport.responseText.strip();
8131        if (!/^\[.*\]$/.test(js)) // TODO: improve sanity check
8132          throw('Server returned an invalid collection representation.');
8133        this._collection = eval(js);
8134        this.checkForExternalText();
8135      }.bind(this),
8136      onFailure: this.onFailure
8137    });
8138    new Ajax.Request(this.options.loadCollectionURL, options);
8139  },
8140
8141  showLoadingText: function(text) {
8142    this._controls.editor.disabled = true;
8143    var tempOption = this._controls.editor.firstChild;
8144    if (!tempOption) {
8145      tempOption = document.createElement('option');
8146      tempOption.value = '';
8147      this._controls.editor.appendChild(tempOption);
8148      tempOption.selected = true;
8149    }
8150    tempOption.update((text || '').stripScripts().stripTags());
8151  },
8152
8153  checkForExternalText: function() {
8154    this._text = this.getText();
8155    if (this.options.loadTextURL)
8156      this.loadExternalText();
8157    else
8158      this.buildOptionList();
8159  },
8160
8161  loadExternalText: function() {
8162    this.showLoadingText(this.options.loadingText);
8163    var options = Object.extend({ method: 'get' }, this.options.ajaxOptions);
8164    Object.extend(options, {
8165      parameters: 'editorId=' + encodeURIComponent(this.element.id),
8166      onComplete: Prototype.emptyFunction,
8167      onSuccess: function(transport) {
8168        this._text = transport.responseText.strip();
8169        this.buildOptionList();
8170      }.bind(this),
8171      onFailure: this.onFailure
8172    });
8173    new Ajax.Request(this.options.loadTextURL, options);
8174  },
8175
8176  buildOptionList: function() {
8177    this._form.removeClassName(this.options.loadingClassName);
8178    this._collection = this._collection.map(function(entry) {
8179      return 2 === entry.length ? entry : [entry, entry].flatten();
8180    });
8181    var marker = ('value' in this.options) ? this.options.value : this._text;
8182    var textFound = this._collection.any(function(entry) {
8183      return entry[0] == marker;
8184    }.bind(this));
8185    this._controls.editor.update('');
8186    var option;
8187    this._collection.each(function(entry, index) {
8188      option = document.createElement('option');
8189      option.value = entry[0];
8190      option.selected = textFound ? entry[0] == marker : 0 == index;
8191      option.appendChild(document.createTextNode(entry[1]));
8192      this._controls.editor.appendChild(option);
8193    }.bind(this));
8194    this._controls.editor.disabled = false;
8195    Field.scrollFreeActivate(this._controls.editor);
8196  }
8197});
8198
8199//**** DEPRECATION LAYER FOR InPlace[Collection]Editor! ****
8200//**** This only  exists for a while,  in order to  let ****
8201//**** users adapt to  the new API.  Read up on the new ****
8202//**** API and convert your code to it ASAP!            ****
8203
8204Ajax.InPlaceEditor.prototype.initialize.dealWithDeprecatedOptions = function(options) {
8205  if (!options) return;
8206  function fallback(name, expr) {
8207    if (name in options || expr === undefined) return;
8208    options[name] = expr;
8209  };
8210  fallback('cancelControl', (options.cancelLink ? 'link' : (options.cancelButton ? 'button' :
8211    options.cancelLink == options.cancelButton == false ? false : undefined)));
8212  fallback('okControl', (options.okLink ? 'link' : (options.okButton ? 'button' :
8213    options.okLink == options.okButton == false ? false : undefined)));
8214  fallback('highlightColor', options.highlightcolor);
8215  fallback('highlightEndColor', options.highlightendcolor);
8216};
8217
8218Object.extend(Ajax.InPlaceEditor, {
8219  DefaultOptions: {
8220    ajaxOptions: { },
8221    autoRows: 3,                                // Use when multi-line w/ rows == 1
8222    cancelControl: 'link',                      // 'link'|'button'|false
8223    cancelText: 'cancel',
8224    clickToEditText: 'Click to edit',
8225    externalControl: null,                      // id|elt
8226    externalControlOnly: false,
8227    fieldPostCreation: 'activate',              // 'activate'|'focus'|false
8228    formClassName: 'inplaceeditor-form',
8229    formId: null,                               // id|elt
8230    highlightColor: '#ffff99',
8231    highlightEndColor: '#ffffff',
8232    hoverClassName: '',
8233    htmlResponse: true,
8234    loadingClassName: 'inplaceeditor-loading',
8235    loadingText: 'Loading...',
8236    okControl: 'button',                        // 'link'|'button'|false
8237    okText: 'ok',
8238    paramName: 'value',
8239    rows: 1,                                    // If 1 and multi-line, uses autoRows
8240    savingClassName: 'inplaceeditor-saving',
8241    savingText: 'Saving...',
8242    size: 0,
8243    stripLoadedTextTags: false,
8244    submitOnBlur: false,
8245    textAfterControls: '',
8246    textBeforeControls: '',
8247    textBetweenControls: ''
8248  },
8249  DefaultCallbacks: {
8250    callback: function(form) {
8251      return Form.serialize(form);
8252    },
8253    onComplete: function(transport, element) {
8254      // For backward compatibility, this one is bound to the IPE, and passes
8255      // the element directly.  It was too often customized, so we don't break it.
8256      new Effect.Highlight(element, {
8257        startcolor: this.options.highlightColor, keepBackgroundImage: true });
8258    },
8259    onEnterEditMode: null,
8260    onEnterHover: function(ipe) {
8261      ipe.element.style.backgroundColor = ipe.options.highlightColor;
8262      if (ipe._effect)
8263        ipe._effect.cancel();
8264    },
8265    onFailure: function(transport, ipe) {
8266      alert('Error communication with the server: ' + transport.responseText.stripTags());
8267    },
8268    onFormCustomization: null, // Takes the IPE and its generated form, after editor, before controls.
8269    onLeaveEditMode: null,
8270    onLeaveHover: function(ipe) {
8271      ipe._effect = new Effect.Highlight(ipe.element, {
8272        startcolor: ipe.options.highlightColor, endcolor: ipe.options.highlightEndColor,
8273        restorecolor: ipe._originalBackground, keepBackgroundImage: true
8274      });
8275    }
8276  },
8277  Listeners: {
8278    click: 'enterEditMode',
8279    keydown: 'checkForEscapeOrReturn',
8280    mouseover: 'enterHover',
8281    mouseout: 'leaveHover'
8282  }
8283});
8284
8285Ajax.InPlaceCollectionEditor.DefaultOptions = {
8286  loadingCollectionText: 'Loading options...'
8287};
8288
8289// Delayed observer, like Form.Element.Observer,
8290// but waits for delay after last key input
8291// Ideal for live-search fields
8292
8293Form.Element.DelayedObserver = Class.create({
8294  initialize: function(element, delay, callback) {
8295    this.delay     = delay || 0.5;
8296    this.element   = $(element);
8297    this.callback  = callback;
8298    this.timer     = null;
8299    this.lastValue = $F(this.element);
8300    Event.observe(this.element,'keyup',this.delayedListener.bindAsEventListener(this));
8301  },
8302  delayedListener: function(event) {
8303    if(this.lastValue == $F(this.element)) return;
8304    if(this.timer) clearTimeout(this.timer);
8305    this.timer = setTimeout(this.onTimerEvent.bind(this), this.delay * 1000);
8306    this.lastValue = $F(this.element);
8307  },
8308  onTimerEvent: function() {
8309    this.timer = null;
8310    this.callback(this.element, $F(this.element));
8311  }
8312});
8313// script.aculo.us slider.js v1.8.2, Tue Nov 18 18:30:58 +0100 2008
8314
8315// Copyright (c) 2005-2008 Marty Haught, Thomas Fuchs
8316//
8317// script.aculo.us is freely distributable under the terms of an MIT-style license.
8318// For details, see the script.aculo.us web site: http://script.aculo.us/
8319
8320if (!Control) var Control = { };
8321
8322// options:
8323//  axis: 'vertical', or 'horizontal' (default)
8324//
8325// callbacks:
8326//  onChange(value)
8327//  onSlide(value)
8328Control.Slider = Class.create({
8329  initialize: function(handle, track, options) {
8330    var slider = this;
8331
8332    if (Object.isArray(handle)) {
8333      this.handles = handle.collect( function(e) { return $(e) });
8334    } else {
8335      this.handles = [$(handle)];
8336    }
8337
8338    this.track   = $(track);
8339    this.options = options || { };
8340
8341    this.axis      = this.options.axis || 'horizontal';
8342    this.increment = this.options.increment || 1;
8343    this.step      = parseInt(this.options.step || '1');
8344    this.range     = this.options.range || $R(0,1);
8345
8346    this.value     = 0; // assure backwards compat
8347    this.values    = this.handles.map( function() { return 0 });
8348    this.spans     = this.options.spans ? this.options.spans.map(function(s){ return $(s) }) : false;
8349    this.options.startSpan = $(this.options.startSpan || null);
8350    this.options.endSpan   = $(this.options.endSpan || null);
8351
8352    this.restricted = this.options.restricted || false;
8353
8354    this.maximum   = this.options.maximum || this.range.end;
8355    this.minimum   = this.options.minimum || this.range.start;
8356
8357    // Will be used to align the handle onto the track, if necessary
8358    this.alignX = parseInt(this.options.alignX || '0');
8359    this.alignY = parseInt(this.options.alignY || '0');
8360
8361    this.trackLength = this.maximumOffset() - this.minimumOffset();
8362
8363    this.handleLength = this.isVertical() ?
8364      (this.handles[0].offsetHeight != 0 ?
8365        this.handles[0].offsetHeight : this.handles[0].style.height.replace(/px$/,"")) :
8366      (this.handles[0].offsetWidth != 0 ? this.handles[0].offsetWidth :
8367        this.handles[0].style.width.replace(/px$/,""));
8368
8369    this.active   = false;
8370    this.dragging = false;
8371    this.disabled = false;
8372
8373    if (this.options.disabled) this.setDisabled();
8374
8375    // Allowed values array
8376    this.allowedValues = this.options.values ? this.options.values.sortBy(Prototype.K) : false;
8377    if (this.allowedValues) {
8378      this.minimum = this.allowedValues.min();
8379      this.maximum = this.allowedValues.max();
8380    }
8381
8382    this.eventMouseDown = this.startDrag.bindAsEventListener(this);
8383    this.eventMouseUp   = this.endDrag.bindAsEventListener(this);
8384    this.eventMouseMove = this.update.bindAsEventListener(this);
8385
8386    // Initialize handles in reverse (make sure first handle is active)
8387    this.handles.each( function(h,i) {
8388      i = slider.handles.length-1-i;
8389      slider.setValue(parseFloat(
8390        (Object.isArray(slider.options.sliderValue) ?
8391          slider.options.sliderValue[i] : slider.options.sliderValue) ||
8392         slider.range.start), i);
8393      h.makePositioned().observe("mousedown", slider.eventMouseDown);
8394    });
8395
8396    this.track.observe("mousedown", this.eventMouseDown);
8397    document.observe("mouseup", this.eventMouseUp);
8398    $(this.track.parentNode.parentNode).observe("mousemove", this.eventMouseMove);
8399
8400
8401    this.initialized = true;
8402  },
8403  dispose: function() {
8404    var slider = this;
8405    Event.stopObserving(this.track, "mousedown", this.eventMouseDown);
8406    Event.stopObserving(document, "mouseup", this.eventMouseUp);
8407    Event.stopObserving(this.track.parentNode.parentNode, "mousemove", this.eventMouseMove);
8408    this.handles.each( function(h) {
8409      Event.stopObserving(h, "mousedown", slider.eventMouseDown);
8410    });
8411  },
8412  setDisabled: function(){
8413    this.disabled = true;
8414    this.track.parentNode.className = this.track.parentNode.className + ' disabled';
8415  },
8416  setEnabled: function(){
8417    this.disabled = false;
8418  },
8419  getNearestValue: function(value){
8420    if (this.allowedValues){
8421      if (value >= this.allowedValues.max()) return(this.allowedValues.max());
8422      if (value <= this.allowedValues.min()) return(this.allowedValues.min());
8423
8424      var offset = Math.abs(this.allowedValues[0] - value);
8425      var newValue = this.allowedValues[0];
8426      this.allowedValues.each( function(v) {
8427        var currentOffset = Math.abs(v - value);
8428        if (currentOffset <= offset){
8429          newValue = v;
8430          offset = currentOffset;
8431        }
8432      });
8433      return newValue;
8434    }
8435    if (value > this.range.end) return this.range.end;
8436    if (value < this.range.start) return this.range.start;
8437    return value;
8438  },
8439  setValue: function(sliderValue, handleIdx){
8440    if (!this.active) {
8441      this.activeHandleIdx = handleIdx || 0;
8442      this.activeHandle    = this.handles[this.activeHandleIdx];
8443      this.updateStyles();
8444    }
8445    handleIdx = handleIdx || this.activeHandleIdx || 0;
8446    if (this.initialized && this.restricted) {
8447      if ((handleIdx>0) && (sliderValue<this.values[handleIdx-1]))
8448        sliderValue = this.values[handleIdx-1];
8449      if ((handleIdx < (this.handles.length-1)) && (sliderValue>this.values[handleIdx+1]))
8450        sliderValue = this.values[handleIdx+1];
8451    }
8452    sliderValue = this.getNearestValue(sliderValue);
8453    this.values[handleIdx] = sliderValue;
8454    this.value = this.values[0]; // assure backwards compat
8455
8456    this.handles[handleIdx].style[this.isVertical() ? 'top' : 'left'] =
8457      this.translateToPx(sliderValue);
8458
8459    this.drawSpans();
8460    if (!this.dragging || !this.event) this.updateFinished();
8461  },
8462  setValueBy: function(delta, handleIdx) {
8463    this.setValue(this.values[handleIdx || this.activeHandleIdx || 0] + delta,
8464      handleIdx || this.activeHandleIdx || 0);
8465  },
8466  translateToPx: function(value) {
8467    return Math.round(
8468      ((this.trackLength-this.handleLength)/(this.range.end-this.range.start)) *
8469      (value - this.range.start)) + "px";
8470  },
8471  translateToValue: function(offset) {
8472    return ((offset/(this.trackLength-this.handleLength) *
8473      (this.range.end-this.range.start)) + this.range.start);
8474  },
8475  getRange: function(range) {
8476    var v = this.values.sortBy(Prototype.K);
8477    range = range || 0;
8478    return $R(v[range],v[range+1]);
8479  },
8480  minimumOffset: function(){
8481    return(this.isVertical() ? this.alignY : this.alignX);
8482  },
8483  maximumOffset: function(){
8484    return(this.isVertical() ?
8485      (this.track.offsetHeight != 0 ? this.track.offsetHeight :
8486        this.track.style.height.replace(/px$/,"")) - this.alignY :
8487      (this.track.offsetWidth != 0 ? this.track.offsetWidth :
8488        this.track.style.width.replace(/px$/,"")) - this.alignX);
8489  },
8490  isVertical:  function(){
8491    return (this.axis == 'vertical');
8492  },
8493  drawSpans: function() {
8494    var slider = this;
8495    if (this.spans)
8496      $R(0, this.spans.length-1).each(function(r) { slider.setSpan(slider.spans[r], slider.getRange(r)) });
8497    if (this.options.startSpan)
8498      this.setSpan(this.options.startSpan,
8499        $R(0, this.values.length>1 ? this.getRange(0).min() : this.value ));
8500    if (this.options.endSpan)
8501      this.setSpan(this.options.endSpan,
8502        $R(this.values.length>1 ? this.getRange(this.spans.length-1).max() : this.value, this.maximum));
8503  },
8504  setSpan: function(span, range) {
8505    if (this.isVertical()) {
8506      span.style.top = this.translateToPx(range.start);
8507      span.style.height = this.translateToPx(range.end - range.start + this.range.start);
8508    } else {
8509      span.style.left = this.translateToPx(range.start);
8510      span.style.width = this.translateToPx(range.end - range.start + this.range.start);
8511    }
8512  },
8513  updateStyles: function() {
8514    this.handles.each( function(h){ Element.removeClassName(h, 'selected') });
8515    Element.addClassName(this.activeHandle, 'selected');
8516  },
8517  startDrag: function(event) {
8518    if (Event.isLeftClick(event)) {
8519      if (!this.disabled){
8520        this.active = true;
8521
8522        var handle = Event.element(event);
8523        var pointer  = [Event.pointerX(event), Event.pointerY(event)];
8524        var track = handle;
8525        if (track==this.track) {
8526          var offsets  = Position.cumulativeOffset(this.track);
8527          this.event = event;
8528          this.setValue(this.translateToValue(
8529           (this.isVertical() ? pointer[1]-offsets[1] : pointer[0]-offsets[0])-(this.handleLength/2)
8530          ));
8531          var offsets  = Position.cumulativeOffset(this.activeHandle);
8532          this.offsetX = (pointer[0] - offsets[0]);
8533          this.offsetY = (pointer[1] - offsets[1]);
8534        } else {
8535          // find the handle (prevents issues with Safari)
8536          while((this.handles.indexOf(handle) == -1) && handle.parentNode)
8537            handle = handle.parentNode;
8538
8539          if (this.handles.indexOf(handle)!=-1) {
8540            this.activeHandle    = handle;
8541            this.activeHandleIdx = this.handles.indexOf(this.activeHandle);
8542            this.updateStyles();
8543
8544            var offsets  = Position.cumulativeOffset(this.activeHandle);
8545            this.offsetX = (pointer[0] - offsets[0]);
8546            this.offsetY = (pointer[1] - offsets[1]);
8547          }
8548        }
8549      }
8550      Event.stop(event);
8551    }
8552  },
8553  update: function(event) {
8554   if (this.active) {
8555      if (!this.dragging) this.dragging = true;
8556      this.draw(event);
8557      if (Prototype.Browser.WebKit) window.scrollBy(0,0);
8558      Event.stop(event);
8559   }
8560  },
8561  draw: function(event) {
8562    var pointer = [Event.pointerX(event), Event.pointerY(event)];
8563    var offsets = Position.cumulativeOffset(this.track);
8564    pointer[0] -= this.offsetX + offsets[0];
8565    pointer[1] -= this.offsetY + offsets[1];
8566    this.event = event;
8567    this.setValue(this.translateToValue( this.isVertical() ? pointer[1] : pointer[0] ));
8568    if (this.initialized && this.options.onSlide)
8569      this.options.onSlide(this.values.length>1 ? this.values : this.value, this);
8570  },
8571  endDrag: function(event) {
8572    if (this.active && this.dragging) {
8573      this.finishDrag(event, true);
8574      Event.stop(event);
8575    }
8576    this.active = false;
8577    this.dragging = false;
8578  },
8579  finishDrag: function(event, success) {
8580    this.active = false;
8581    this.dragging = false;
8582    this.updateFinished();
8583  },
8584  updateFinished: function() {
8585    if (this.initialized && this.options.onChange)
8586      this.options.onChange(this.values.length>1 ? this.values : this.value, this);
8587    this.event = null;
8588  }
8589});
8590/**
8591 * Magento
8592 *
8593 * NOTICE OF LICENSE
8594 *
8595 * This source file is subject to the Academic Free License (AFL 3.0)
8596 * that is bundled with this package in the file LICENSE_AFL.txt.
8597 * It is also available through the world-wide-web at this URL:
8598 * http://opensource.org/licenses/afl-3.0.php
8599 * If you did not receive a copy of the license and are unable to
8600 * obtain it through the world-wide-web, please send an email
8601 * to [email protected] so we can send you a copy immediately.
8602 *
8603 * DISCLAIMER
8604 *
8605 * Do not edit or add to this file if you wish to upgrade Magento to newer
8606 * versions in the future. If you wish to customize Magento for your
8607 * needs please refer to http://www.magentocommerce.com for more information.
8608 *
8609 * @category    Varien
8610 * @package     js
8611 * @copyright   Copyright (c) 2009 Irubin Consulting Inc. DBA Varien (http://www.varien.com)
8612 * @license     http://opensource.org/licenses/afl-3.0.php  Academic Free License (AFL 3.0)
8613 */
8614
8615function openModalBox(panel,titleText) {
8616    $(panel).style.overflow='auto';
8617    Shadowbox.open({
8618        content:    $(panel).innerHTML,
8619        player:     "html",
8620        title:      titleText,
8621        height:     350,
8622        width:      350
8623    });
8624}
8625
8626function popWin(url,win,para) {
8627    var win = window.open(url,win,para);
8628    win.focus();
8629}
8630
8631function setLocation(url){
8632    window.location.href = url;
8633}
8634
8635function setPLocation(url, setFocus){
8636    if( setFocus ) {
8637        window.opener.focus();
8638    }
8639    window.opener.location.href = url;
8640}
8641
8642function setLanguageCode(code, fromCode){
8643    //TODO: javascript cookies have different domain and path than php cookies
8644    var href = window.location.href;
8645    var after = '', dash;
8646    if (dash = href.match(/\#(.*)$/)) {
8647        href = href.replace(/\#(.*)$/, '');
8648        after = dash[0];
8649    }
8650
8651    if (href.match(/[?]/)) {
8652        var re = /([?&]store=)[a-z0-9_]*/;
8653        if (href.match(re)) {
8654            href = href.replace(re, '$1'+code);
8655        } else {
8656            href += '&store='+code;
8657        }
8658
8659        var re = /([?&]from_store=)[a-z0-9_]*/;
8660        if (href.match(re)) {
8661            href = href.replace(re, '');
8662        }
8663    } else {
8664        href += '?store='+code;
8665    }
8666    if (typeof(fromCode) != 'undefined') {
8667        href += '&from_store='+fromCode;
8668    }
8669    href += after;
8670
8671    setLocation(href);
8672}
8673
8674/**
8675 * Add classes to specified elements.
8676 * Supported classes are: 'odd', 'even', 'first', 'last'
8677 *
8678 * @param elements - array of elements to be decorated
8679 * [@param decorateParams] - array of classes to be set. If omitted, all available will be used
8680 */
8681function decorateGeneric(elements, decorateParams)
8682{
8683    var allSupportedParams = ['odd', 'even', 'first', 'last'];
8684    var _decorateParams = {};
8685    var total = elements.length;
8686
8687    if (total) {
8688        // determine params called
8689        if (typeof(decorateParams) == 'undefined') {
8690            decorateParams = allSupportedParams;
8691        }
8692        if (!decorateParams.length) {
8693            return;
8694        }
8695        for (var k in allSupportedParams) {
8696            _decorateParams[allSupportedParams[k]] = false;
8697        }
8698        for (var k in decorateParams) {
8699            _decorateParams[decorateParams[k]] = true;
8700        }
8701
8702        // decorate elements
8703        // elements[0].addClassName('first'); // will cause bug in IE (#5587)
8704        if (_decorateParams.first) {
8705            Element.addClassName(elements[0], 'first');
8706        }
8707        if (_decorateParams.last) {
8708            Element.addClassName(elements[total-1], 'last');
8709        }
8710        for (var i = 0; i < total; i++) {
8711            if ((i + 1) % 2 == 0) {
8712                if (_decorateParams.even) {
8713                    Element.addClassName(elements[i], 'even');
8714                }
8715            }
8716            else {
8717                if (_decorateParams.odd) {
8718                    Element.addClassName(elements[i], 'odd');
8719                }
8720            }
8721        }
8722    }
8723}
8724
8725/**
8726 * Decorate table rows and cells, tbody etc
8727 * @see decorateGeneric()
8728 */
8729function decorateTable(table, options) {
8730    var table = $(table);
8731    if (table) {
8732        // set default options
8733        var _options = {
8734            'tbody'    : false,
8735            'tbody tr' : ['odd', 'even', 'first', 'last'],
8736            'thead tr' : ['first', 'last'],
8737            'tfoot tr' : ['first', 'last'],
8738            'tr td'    : ['last']
8739        };
8740        // overload options
8741        if (typeof(options) != 'undefined') {
8742            for (var k in options) {
8743                _options[k] = options[k];
8744            }
8745        }
8746        // decorate
8747        if (_options['tbody']) {
8748            decorateGeneric(table.select('tbody'), _options['tbody']);
8749        }
8750        if (_options['tbody tr']) {
8751            decorateGeneric(table.select('tbody tr'), _options['tbody tr']);
8752        }
8753        if (_options['thead tr']) {
8754            decorateGeneric(table.select('thead tr'), _options['thead tr']);
8755        }
8756        if (_options['tfoot tr']) {
8757            decorateGeneric(table.select('tfoot tr'), _options['tfoot tr']);
8758        }
8759        if (_options['tr td']) {
8760            var allRows = table.select('tr');
8761            if (allRows.length) {
8762                for (var i = 0; i < allRows.length; i++) {
8763                    decorateGeneric(allRows[i].getElementsByTagName('TD'), _options['tr td']);
8764                }
8765            }
8766        }
8767    }
8768}
8769
8770/**
8771 * Set "odd", "even" and "last" CSS classes for list items
8772 * @see decorateGeneric()
8773 */
8774function decorateList(list, nonRecursive) {
8775    if ($(list)) {
8776        if (typeof(nonRecursive) == 'undefined') {
8777            var items = $(list).select('li')
8778        }
8779        else {
8780            var items = $(list).childElements();
8781        }
8782        decorateGeneric(items, ['odd', 'even', 'last']);
8783    }
8784}
8785
8786/**
8787 * Set "odd", "even" and "last" CSS classes for list items
8788 * @see decorateGeneric()
8789 */
8790function decorateDataList(list) {
8791    list = $(list);
8792    if (list) {
8793        decorateGeneric(list.select('dt'), ['odd', 'even', 'last']);
8794        decorateGeneric(list.select('dd'), ['odd', 'even', 'last']);
8795    }
8796}
8797
8798/**
8799 * Parse SID and produces the correct URL
8800 */
8801function parseSidUrl(baseUrl, urlExt) {
8802    sidPos = baseUrl.indexOf('/?SID=');
8803    sid = '';
8804    urlExt = (urlExt != undefined) ? urlExt : '';
8805
8806    if(sidPos > -1) {
8807        sid = '?' + baseUrl.substring(sidPos + 2);
8808        baseUrl = baseUrl.substring(0, sidPos + 1);
8809    }
8810
8811    return baseUrl+urlExt+sid;
8812}
8813
8814/**
8815 * Formats currency using patern
8816 * format - JSON (pattern, decimal, decimalsDelimeter, groupsDelimeter)
8817 * showPlus - true (always show '+'or '-'),
8818 *      false (never show '-' even if number is negative)
8819 *      null (show '-' if number is negative)
8820 */
8821
8822function formatCurrency(price, format, showPlus){
8823    precision = isNaN(format.precision = Math.abs(format.precision)) ? 2 : format.precision;
8824    requiredPrecision = isNaN(format.requiredPrecision = Math.abs(format.requiredPrecision)) ? 2 : format.requiredPrecision;
8825
8826    //precision = (precision > requiredPrecision) ? precision : requiredPrecision;
8827    //for now we don't need this difference so precision is requiredPrecision
8828    precision = requiredPrecision;
8829
8830    integerRequired = isNaN(format.integerRequired = Math.abs(format.integerRequired)) ? 1 : format.integerRequired;
8831
8832    decimalSymbol = format.decimalSymbol == undefined ? "," : format.decimalSymbol;
8833    groupSymbol = format.groupSymbol == undefined ? "." : format.groupSymbol;
8834    groupLength = format.groupLength == undefined ? 3 : format.groupLength;
8835
8836    if (showPlus == undefined || showPlus == true) {
8837        s = price < 0 ? "-" : ( showPlus ? "+" : "");
8838    } else if (showPlus == false) {
8839        s = '';
8840    }
8841
8842    i = parseInt(price = Math.abs(+price || 0).toFixed(precision)) + "";
8843    pad = (i.length < integerRequired) ? (integerRequired - i.length) : 0;
8844    while (pad) { i = '0' + i; pad--; }
8845
8846    j = (j = i.length) > groupLength ? j % groupLength : 0;
8847    re = new RegExp("(\\d{" + groupLength + "})(?=\\d)", "g");
8848
8849    /**
8850     * replace(/-/, 0) is only for fixing Safari bug which appears
8851     * when Math.abs(0).toFixed() executed on "0" number.
8852     * Result is "0.-0" :(
8853     */
8854    r = (j ? i.substr(0, j) + groupSymbol : "") + i.substr(j).replace(re, "$1" + groupSymbol) + (precision ? decimalSymbol + Math.abs(price - i).toFixed(precision).replace(/-/, 0).slice(2) : "")
8855
8856    if (format.pattern.indexOf('{sign}') == -1) {
8857        pattern = s + format.pattern;
8858    } else {
8859        pattern = format.pattern.replace('{sign}', s);
8860    }
8861
8862    return pattern.replace('%s', r).replace(/^\s\s*/, '').replace(/\s\s*$/, '');
8863};
8864
8865function expandDetails(el, childClass) {
8866    if (Element.hasClassName(el,'show-details')) {
8867        $$(childClass).each(function(item){item.hide()});
8868        Element.removeClassName(el,'show-details');
8869    }
8870    else {
8871        $$(childClass).each(function(item){item.show()});
8872        Element.addClassName(el,'show-details');
8873    }
8874}
8875
8876// Version 1.0
8877var isIE = navigator.appVersion.match(/MSIE/) == "MSIE";
8878
8879if (!window.Varien)
8880    var Varien = new Object();
8881
8882Varien.showLoading = function(){
8883    Element.show('loading-process');
8884}
8885Varien.hideLoading = function(){
8886    Element.hide('loading-process');
8887}
8888Varien.GlobalHandlers = {
8889    onCreate: function() {
8890        Varien.showLoading();
8891    },
8892
8893    onComplete: function() {
8894        if(Ajax.activeRequestCount == 0) {
8895            Varien.hideLoading();
8896        }
8897    }
8898};
8899
8900Ajax.Responders.register(Varien.GlobalHandlers);
8901
8902/**
8903 * Quick Search form client model
8904 */
8905Varien.searchForm = Class.create();
8906Varien.searchForm.prototype = {
8907    initialize : function(form, field, emptyText){
8908        this.form   = $(form);
8909        this.field  = $(field);
8910        this.emptyText = emptyText;
8911
8912        Event.observe(this.form,  'submit', this.submit.bind(this));
8913        Event.observe(this.field, 'focus', this.focus.bind(this));
8914        Event.observe(this.field, 'blur', this.blur.bind(this));
8915        this.blur();
8916    },
8917
8918    submit : function(event){
8919        if (this.field.value == this.emptyText || this.field.value == ''){
8920            Event.stop(event);
8921            return false;
8922        }
8923        return true;
8924    },
8925
8926    focus : function(event){
8927        if(this.field.value==this.emptyText){
8928            this.field.value='';
8929        }
8930
8931    },
8932
8933    blur : function(event){
8934        if(this.field.value==''){
8935            this.field.value=this.emptyText;
8936        }
8937    },
8938
8939    initAutocomplete : function(url, destinationElement){
8940        new Ajax.Autocompleter(
8941            this.field,
8942            destinationElement,
8943            url,
8944            {
8945                paramName: this.field.name,
8946                method: 'get',
8947                minChars: 2,
8948                updateElement: this._selectAutocompleteItem.bind(this),
8949                onShow : function(element, update) {
8950                    if(!update.style.position || update.style.position=='absolute') {
8951                        update.style.position = 'absolute';
8952                        Position.clone(element, update, {
8953                            setHeight: false,
8954                            offsetTop: element.offsetHeight
8955                        });
8956                    }
8957                    Effect.Appear(update,{duration:0});
8958                }
8959
8960            }
8961        );
8962    },
8963
8964    _selectAutocompleteItem : function(element){
8965        if(element.title){
8966            this.field.value = element.title;
8967        }
8968        this.form.submit();
8969    }
8970}
8971
8972Varien.Tabs = Class.create();
8973Varien.Tabs.prototype = {
8974  initialize: function(selector) {
8975    var self=this;
8976    $$(selector+' a').each(this.initTab.bind(this));
8977  },
8978
8979  initTab: function(el) {
8980      el.href = 'javascript:void(0)';
8981      if ($(el.parentNode).hasClassName('active')) {
8982        this.showContent(el);
8983      }
8984      el.observe('click', this.showContent.bind(this, el));
8985  },
8986
8987  showContent: function(a) {
8988    var li = $(a.parentNode), ul = $(li.parentNode);
8989    ul.getElementsBySelector('li', 'ol').each(function(el){
8990      var contents = $(el.id+'_contents');
8991      if (el==li) {
8992        el.addClassName('active');
8993        contents.show();
8994      } else {
8995        el.removeClassName('active');
8996        contents.hide();
8997      }
8998    });
8999  }
9000}
9001
9002Varien.DOB = Class.create();
9003Varien.DOB.prototype = {
9004    initialize: function(selector, required, format) {
9005        var el        = $$(selector)[0];
9006        this.day      = Element.select($(el), '.dob-day input')[0];
9007        this.month    = Element.select($(el), '.dob-month input')[0];
9008        this.year     = Element.select($(el), '.dob-year input')[0];
9009        this.dob      = Element.select($(el), '.dob-full input')[0];
9010        this.advice   = Element.select($(el), '.validation-advice')[0];
9011        this.required = required;
9012        this.format   = format;
9013
9014        this.day.validate = this.validate.bind(this);
9015        this.month.validate = this.validate.bind(this);
9016        this.year.validate = this.validate.bind(this);
9017
9018        this.advice.hide();
9019    },
9020
9021    validate: function() {
9022        var error = false;
9023
9024        if (this.day.value=='' && this.month.value=='' && this.year.value=='') {
9025            if (this.required) {
9026                error = 'This date is a required value.';
9027            } else {
9028                this.dob.value = '';
9029            }
9030        } else if (this.day.value=='' || this.month.value=='' || this.year.value=='') {
9031            error = 'Please enter a valid full date.';
9032        } else {
9033            var date = new Date();
9034            if (this.day.value<1 || this.day.value>31) {
9035                error = 'Please enter a valid day (1-31).';
9036            } else if (this.month.value<1 || this.month.value>12) {
9037                error = 'Please enter a valid month (1-12).';
9038            } else if (this.year.value<1900 || this.year.value>date.getFullYear()) {
9039                error = 'Please enter a valid year (1900-'+date.getFullYear()+').';
9040            } else {
9041                this.dob.value = this.format.replace(/(%m|%b)/i, this.month.value).replace(/(%d|%e)/i, this.day.value).replace(/%y/i, this.year.value);
9042                var testDOB = this.month.value + '/' + this.day.value + '/'+ this.year.value;
9043                var test = new Date(testDOB);
9044                if (isNaN(test)) {
9045                    error = 'Please enter a valid date.';
9046                }
9047            }
9048        }
9049
9050        if (error !== false) {
9051            try {
9052                this.advice.innerHTML = Translator.translate(error);
9053            }
9054            catch (e) {
9055                this.advice.innerHTML = error;
9056            }
9057            this.advice.show();
9058            return false;
9059        }
9060
9061        this.advice.hide();
9062        return true;
9063    }
9064}
9065
9066Validation.addAllThese([
9067    ['validate-custom', ' ', function(v,elm) {
9068        return elm.validate();
9069    }]
9070]);
9071
9072function truncateOptions() {
9073    $$('.truncated').each(function(element){
9074        Event.observe(element, 'mouseover', function(){
9075            if (element.down('div.truncated_full_value')) {
9076                element.down('div.truncated_full_value').addClassName('show')
9077            }
9078        });
9079        Event.observe(element, 'mouseout', function(){
9080            if (element.down('div.truncated_full_value')) {
9081                element.down('div.truncated_full_value').removeClassName('show')
9082            }
9083        });
9084
9085    });
9086}
9087Event.observe(window, 'load', function(){
9088   truncateOptions();
9089});
9090
9091Element.addMethods({
9092    getInnerText: function(element)
9093    {
9094        element = $(element);
9095        if(element.innerText && !Prototype.Browser.Opera) {
9096            return element.innerText
9097        }
9098        return element.innerHTML.stripScripts().unescapeHTML().replace(/[\n\r\s]+/g, ' ');
9099    }
9100});
9101/*
9102if (!("console" in window) || !("firebug" in console))
9103{
9104    var names = ["log", "debug", "info", "warn", "error", "assert", "dir", "dirxml",
9105    "group", "groupEnd", "time", "timeEnd", "count", "trace", "profile", "profileEnd"];
9106
9107    window.console = {};
9108    for (var i = 0; i < names.length; ++i)
9109        window.console[names[i]] = function() {}
9110}
9111*/
9112/**
9113 * Magento
9114 *
9115 * NOTICE OF LICENSE
9116 *
9117 * This source file is subject to the Academic Free License (AFL 3.0)
9118 * that is bundled with this package in the file LICENSE_AFL.txt.
9119 * It is also available through the world-wide-web at this URL:
9120 * http://opensource.org/licenses/afl-3.0.php
9121 * If you did not receive a copy of the license and are unable to
9122 * obtain it through the world-wide-web, please send an email
9123 * to [email protected] so we can send you a copy immediately.
9124 *
9125 * DISCLAIMER
9126 *
9127 * Do not edit or add to this file if you wish to upgrade Magento to newer
9128 * versions in the future. If you wish to customize Magento for your
9129 * needs please refer to http://www.magentocommerce.com for more information.
9130 *
9131 * @category    Varien
9132 * @package     js
9133 * @copyright   Copyright (c) 2009 Irubin Consulting Inc. DBA Varien (http://www.varien.com)
9134 * @license     http://opensource.org/licenses/afl-3.0.php  Academic Free License (AFL 3.0)
9135 */
9136VarienForm = Class.create();
9137VarienForm.prototype = {
9138    initialize: function(formId, firstFieldFocus){
9139        this.form       = $(formId);
9140        if (!this.form) {
9141            return;
9142        }
9143        this.cache      = $A();
9144        this.currLoader = false;
9145        this.currDataIndex = false;
9146        this.validator  = new Validation(this.form);
9147        this.elementFocus   = this.elementOnFocus.bindAsEventListener(this);
9148        this.elementBlur    = this.elementOnBlur.bindAsEventListener(this);
9149        this.childLoader    = this.onChangeChildLoad.bindAsEventListener(this);
9150        this.highlightClass = 'highlight';
9151        this.extraChildParams = '';
9152        this.firstFieldFocus= firstFieldFocus || false;
9153        this.bindElements();
9154        if(this.firstFieldFocus){
9155            try{
9156                Form.Element.focus(Form.findFirstElement(this.form))
9157            }
9158            catch(e){}
9159        }
9160    },
9161
9162    submit : function(url){
9163        if(this.validator && this.validator.validate()){
9164             this.form.submit();
9165        }
9166        return false;
9167    },
9168
9169    bindElements:function (){
9170        var elements = Form.getElements(this.form);
9171        for (var row in elements) {
9172            if (elements[row].id) {
9173                Event.observe(elements[row],'focus',this.elementFocus);
9174                Event.observe(elements[row],'blur',this.elementBlur);
9175            }
9176        }
9177    },
9178
9179    elementOnFocus: function(event){
9180        var element = Event.findElement(event, 'fieldset');
9181        if(element){
9182            Element.addClassName(element, this.highlightClass);
9183        }
9184    },
9185
9186    elementOnBlur: function(event){
9187        var element = Event.findElement(event, 'fieldset');
9188        if(element){
9189            Element.removeClassName(element, this.highlightClass);
9190        }
9191    },
9192
9193    setElementsRelation: function(parent, child, dataUrl, first){
9194        if (parent=$(parent)) {
9195            // TODO: array of relation and caching
9196            if (!this.cache[parent.id]){
9197                this.cache[parent.id] = $A();
9198                this.cache[parent.id]['child']     = child;
9199                this.cache[parent.id]['dataUrl']   = dataUrl;
9200                this.cache[parent.id]['data']      = $A();
9201                this.cache[parent.id]['first']      = first || false;
9202            }
9203            Event.observe(parent,'change',this.childLoader);
9204        }
9205    },
9206
9207    onChangeChildLoad: function(event){
9208        element = Event.element(event);
9209        this.elementChildLoad(element);
9210    },
9211
9212    elementChildLoad: function(element, callback){
9213        this.callback = callback || false;
9214        if (element.value) {
9215            this.currLoader = element.id;
9216            this.currDataIndex = element.value;
9217            if (this.cache[element.id]['data'][element.value]) {
9218                this.setDataToChild(this.cache[element.id]['data'][element.value]);
9219            }
9220            else{
9221                new Ajax.Request(this.cache[this.currLoader]['dataUrl'],{
9222                        method: 'post',
9223                        parameters: {"parent":element.value},
9224                        onComplete: this.reloadChildren.bind(this)
9225                });
9226            }
9227        }
9228    },
9229
9230    reloadChildren: function(transport){
9231        var data = eval('(' + transport.responseText + ')');
9232        this.cache[this.currLoader]['data'][this.currDataIndex] = data;
9233        this.setDataToChild(data);
9234    },
9235
9236    setDataToChild: function(data){
9237        if (data.length) {
9238            var child = $(this.cache[this.currLoader]['child']);
9239            if (child){
9240                var html = '<select name="'+child.name+'" id="'+child.id+'" class="'+child.className+'" title="'+child.title+'" '+this.extraChildParams+'>';
9241                if(this.cache[this.currLoader]['first']){
9242                    html+= '<option value="">'+this.cache[this.currLoader]['first']+'</option>';
9243                }
9244                for (var i in data){
9245                    if(data[i].value) {
9246                        html+= '<option value="'+data[i].value+'"';
9247                        if(child.value && (child.value == data[i].value || child.value == data[i].label)){
9248                            html+= ' selected';
9249                        }
9250                        html+='>'+data[i].label+'</option>';
9251                    }
9252                }
9253                html+= '</select>';
9254                Element.insert(child, {before: html});
9255                Element.remove(child);
9256            }
9257        }
9258        else{
9259            var child = $(this.cache[this.currLoader]['child']);
9260            if (child){
9261                var html = '<input type="text" name="'+child.name+'" id="'+child.id+'" class="'+child.className+'" title="'+child.title+'" '+this.extraChildParams+'>';
9262                Element.insert(child, {before: html});
9263                Element.remove(child);
9264            }
9265        }
9266
9267        this.bindElements();
9268        if (this.callback) {
9269            this.callback();
9270        }
9271    }
9272}
9273
9274RegionUpdater = Class.create();
9275RegionUpdater.prototype = {
9276    initialize: function (countryEl, regionTextEl, regionSelectEl, regions, disableAction, zipEl)
9277    {
9278        this.countryEl = $(countryEl);
9279        this.regionTextEl = $(regionTextEl);
9280        this.regionSelectEl = $(regionSelectEl);
9281        this.zipEl = $(zipEl);
9282        this.regions = regions;
9283
9284        this.disableAction = (typeof disableAction=='undefined') ? 'hide' : disableAction;
9285        this.zipOptions = (typeof zipOptions=='undefined') ? false : zipOptions;
9286
9287        if (this.regionSelectEl.options.length<=1) {
9288            this.update();
9289        }
9290
9291        Event.observe(this.countryEl, 'change', this.update.bind(this));
9292    },
9293
9294    update: function()
9295    {
9296        if (this.regions[this.countryEl.value]) {
9297            var i, option, region, def;
9298
9299            if (this.regionTextEl) {
9300                def = this.regionTextEl.value.toLowerCase();
9301                this.regionTextEl.value = '';
9302            }
9303            if (!def) {
9304                def = this.regionSelectEl.getAttribute('defaultValue');
9305            }
9306
9307            this.regionSelectEl.options.length = 1;
9308            for (regionId in this.regions[this.countryEl.value]) {
9309                region = this.regions[this.countryEl.value][regionId];
9310
9311                option = document.createElement('OPTION');
9312                option.value = regionId;
9313                option.text = region.name;
9314
9315                if (this.regionSelectEl.options.add) {
9316                    this.regionSelectEl.options.add(option);
9317                } else {
9318                    this.regionSelectEl.appendChild(option);
9319                }
9320
9321                if (regionId==def || region.name.toLowerCase()==def || region.code.toLowerCase()==def) {
9322                    this.regionSelectEl.value = regionId;
9323                }
9324            }
9325
9326            if (this.disableAction=='hide') {
9327                if (this.regionTextEl) {
9328                    this.regionTextEl.style.display = 'none';
9329                }
9330
9331                this.regionSelectEl.style.display = '';
9332            } else if (this.disableAction=='disable') {
9333                if (this.regionTextEl) {
9334                    this.regionTextEl.disabled = true;
9335                }
9336                this.regionSelectEl.disabled = false;
9337            }
9338            this.setMarkDisplay(this.regionSelectEl, true);
9339        } else {
9340            if (this.disableAction=='hide') {
9341                if (this.regionTextEl) {
9342                    this.regionTextEl.style.display = '';
9343                }
9344                this.regionSelectEl.style.display = 'none';
9345                Validation.reset(this.regionSelectEl);
9346            } else if (this.disableAction=='disable') {
9347                if (this.regionTextEl) {
9348                    this.regionTextEl.disabled = false;
9349                }
9350                this.regionSelectEl.disabled = true;
9351            } else if (this.disableAction=='nullify') {
9352                this.regionSelectEl.options.length = 1;
9353                this.regionSelectEl.value = '';
9354                this.regionSelectEl.selectedIndex = 0;
9355                this.lastCountryId = '';
9356            }
9357            this.setMarkDisplay(this.regionSelectEl, false);
9358        }
9359
9360        // Make Zip and its label required/optional
9361        var zipUpdater = new ZipUpdater(this.countryEl.value, this.zipEl);
9362        zipUpdater.update();
9363    },
9364
9365    setMarkDisplay: function(elem, display){
9366        elem = $(elem);
9367        var labelElement = elem.up(0).down('label > span.required') ||
9368                           elem.up(1).down('label > span.required') ||
9369                           elem.up(0).down('label.required > em') ||
9370                           elem.up(1).down('label.required > em');
9371        if(labelElement) {
9372            inputElement = labelElement.up().next('input');
9373            if (display) {
9374                labelElement.show();
9375                if (inputElement) {
9376                    inputElement.addClassName('required-entry');
9377                }
9378            } else {
9379                labelElement.hide();
9380                if (inputElement) {
9381                    inputElement.removeClassName('required-entry');
9382                }
9383            }
9384        }
9385    }
9386}
9387
9388ZipUpdater = Class.create();
9389ZipUpdater.prototype = {
9390    initialize: function(country, zipElement)
9391    {
9392        this.country = country;
9393        this.zipElement = $(zipElement);
9394    },
9395
9396    update: function()
9397    {
9398        // Country ISO 2-letter codes must be pre-defined
9399        if (typeof optionalZipCountries == 'undefined') {
9400            return false;
9401        }
9402
9403        // Ajax-request and normal content load compatibility
9404        if (this.zipElement != undefined) {
9405            this._setPostcodeOptional();
9406        } else {
9407            Event.observe(window, "load", this._setPostcodeOptional.bind(this));
9408        }
9409    },
9410
9411    _setPostcodeOptional: function()
9412    {
9413        this.zipElement = $(this.zipElement);
9414        if (this.zipElement == undefined) {
9415            return false;
9416        }
9417
9418        // find label
9419        var label = $$('label[for="' + this.zipElement.id + '"]')[0];
9420        if (label != undefined) {
9421            var wildCard = label.down('em') || label.down('span.required');
9422        }
9423
9424        // Make Zip and its label required/optional
9425        if (optionalZipCountries.indexOf(this.country) != -1) {
9426            while (this.zipElement.hasClassName('required-entry')) {
9427                this.zipElement.removeClassName('required-entry');
9428            }
9429            if (wildCard != undefined) {
9430                wildCard.hide();
9431            }
9432        } else {
9433            this.zipElement.addClassName('required-entry');
9434            if (wildCard != undefined) {
9435                wildCard.show();
9436            }
9437        }
9438    }
9439}
9440
9441/**
9442 * Magento
9443 *
9444 * NOTICE OF LICENSE
9445 *
9446 * This source file is subject to the Academic Free License (AFL 3.0)
9447 * that is bundled with this package in the file LICENSE_AFL.txt.
9448 * It is also available through the world-wide-web at this URL:
9449 * http://opensource.org/licenses/afl-3.0.php
9450 * If you did not receive a copy of the license and are unable to
9451 * obtain it through the world-wide-web, please send an email
9452 * to [email protected] so we can send you a copy immediately.
9453 *
9454 * DISCLAIMER
9455 *
9456 * Do not edit or add to this file if you wish to upgrade Magento to newer
9457 * versions in the future. If you wish to customize Magento for your
9458 * needs please refer to http://www.magentocommerce.com for more information.
9459 *
9460 * @category    Varien
9461 * @package     js
9462 * @copyright   Copyright (c) 2009 Irubin Consulting Inc. DBA Varien (http://www.varien.com)
9463 * @license     http://opensource.org/licenses/afl-3.0.php  Academic Free License (AFL 3.0)
9464 */
9465function toggleMenu(el, over)
9466{
9467    if (over) {
9468        Element.addClassName(el, 'over');
9469    }
9470    else {
9471        Element.removeClassName(el, 'over');
9472    }
9473}
9474
9475/**
9476 * Magento
9477 *
9478 * NOTICE OF LICENSE
9479 *
9480 * This source file is subject to the Academic Free License (AFL 3.0)
9481 * that is bundled with this package in the file LICENSE_AFL.txt.
9482 * It is also available through the world-wide-web at this URL:
9483 * http://opensource.org/licenses/afl-3.0.php
9484 * If you did not receive a copy of the license and are unable to
9485 * obtain it through the world-wide-web, please send an email
9486 * to [email protected] so we can send you a copy immediately.
9487 *
9488 * DISCLAIMER
9489 *
9490 * Do not edit or add to this file if you wish to upgrade Magento to newer
9491 * versions in the future. If you wish to customize Magento for your
9492 * needs please refer to http://www.magentocommerce.com for more information.
9493 *
9494 * @category    Mage
9495 * @package     js
9496 * @copyright   Copyright (c) 2009 Irubin Consulting Inc. DBA Varien (http://www.varien.com)
9497 * @license     http://opensource.org/licenses/afl-3.0.php  Academic Free License (AFL 3.0)
9498 */
9499
9500var Translate = Class.create();
9501Translate.prototype = {
9502    initialize: function(data){
9503        this.data = $H(data);
9504    },
9505
9506    translate : function(){
9507        var args = arguments;
9508        var text = arguments[0];
9509
9510        if(this.data.get(text)){
9511            return this.data.get(text);
9512        }
9513        return text;
9514    },
9515    add : function() {
9516        if (arguments.length > 1) {
9517            this.data.set(arguments[0], arguments[1]);
9518        } else if (typeof arguments[0] =='object') {
9519            $H(arguments[0]).each(function (pair){
9520                this.data.set(pair.key, pair.value);
9521            }.bind(this));
9522        }
9523    }
9524}
9525
9526/**
9527 * Magento
9528 *
9529 * NOTICE OF LICENSE
9530 *
9531 * This source file is subject to the Academic Free License (AFL 3.0)
9532 * that is bundled with this package in the file LICENSE_AFL.txt.
9533 * It is also available through the world-wide-web at this URL:
9534 * http://opensource.org/licenses/afl-3.0.php
9535 * If you did not receive a copy of the license and are unable to
9536 * obtain it through the world-wide-web, please send an email
9537 * to [email protected] so we can send you a copy immediately.
9538 *
9539 * DISCLAIMER
9540 *
9541 * Do not edit or add to this file if you wish to upgrade Magento to newer
9542 * versions in the future. If you wish to customize Magento for your
9543 * needs please refer to http://www.magentocommerce.com for more information.
9544 *
9545 * @category    Mage
9546 * @package     js
9547 * @copyright   Copyright (c) 2009 Irubin Consulting Inc. DBA Varien (http://www.varien.com)
9548 * @license     http://opensource.org/licenses/afl-3.0.php  Academic Free License (AFL 3.0)
9549 */
9550// old school cookie functions grabbed off the web
9551
9552if (!window.Mage) var Mage = {};
9553
9554Mage.Cookies = {};
9555Mage.Cookies.expires  = null;
9556Mage.Cookies.path     = '/';
9557Mage.Cookies.domain   = null;
9558Mage.Cookies.secure   = false;
9559Mage.Cookies.set = function(name, value){
9560     var argv = arguments;
9561     var argc = arguments.length;
9562     var expires = (argc > 2) ? argv[2] : Mage.Cookies.expires;
9563     var path = (argc > 3) ? argv[3] : Mage.Cookies.path;
9564     var domain = (argc > 4) ? argv[4] : Mage.Cookies.domain;
9565     var secure = (argc > 5) ? argv[5] : Mage.Cookies.secure;
9566     document.cookie = name + "=" + escape (value) +
9567       ((expires == null) ? "" : ("; expires=" + expires.toGMTString())) +
9568       ((path == null) ? "" : ("; path=" + path)) +
9569       ((domain == null) ? "" : ("; domain=" + domain)) +
9570       ((secure == true) ? "; secure" : "");
9571};
9572
9573Mage.Cookies.get = function(name){
9574    var arg = name + "=";
9575    var alen = arg.length;
9576    var clen = document.cookie.length;
9577    var i = 0;
9578    var j = 0;
9579    while(i < clen){
9580        j = i + alen;
9581        if (document.cookie.substring(i, j) == arg)
9582            return Mage.Cookies.getCookieVal(j);
9583        i = document.cookie.indexOf(" ", i) + 1;
9584        if(i == 0)
9585            break;
9586    }
9587    return null;
9588};
9589
9590Mage.Cookies.clear = function(name) {
9591  if(Mage.Cookies.get(name)){
9592    document.cookie = name + "=" +
9593    "; expires=Thu, 01-Jan-70 00:00:01 GMT";
9594  }
9595};
9596
9597Mage.Cookies.getCookieVal = function(offset){
9598   var endstr = document.cookie.indexOf(";", offset);
9599   if(endstr == -1){
9600       endstr = document.cookie.length;
9601   }
9602   return unescape(document.cookie.substring(offset, endstr));
9603};
9604
9605/*
9606 * Shadowbox.js, version 3.0.3
9607 * http://shadowbox-js.com/
9608 *
9609 * Copyright 2007-2010, Michael J. I. Jackson
9610 * Date: 2011-04-08 01:28:11 +0000
9611 */
9612(function(T,p){var g={version:"3.0.3"};var Y=navigator.userAgent.toLowerCase();if(Y.indexOf("windows")>-1||Y.indexOf("win32")>-1){g.isWindows=true}else{if(Y.indexOf("macintosh")>-1||Y.indexOf("mac os x")>-1){g.isMac=true}else{if(Y.indexOf("linux")>-1){g.isLinux=true}}}g.isIE=Y.indexOf("msie")>-1;g.isIE6=Y.indexOf("msie 6")>-1;g.isIE7=Y.indexOf("msie 7")>-1;g.isGecko=Y.indexOf("gecko")>-1&&Y.indexOf("safari")==-1;g.isWebKit=Y.indexOf("applewebkit/")>-1;var e=/#(.+)$/,M=/^(light|shadow)box\[(.*?)\]/i,o=/\s*([a-z_]*?)\s*=\s*(.+)\s*/,ar=/[0-9a-z]+$/i,ap=/(.+\/)shadowbox\.js/i;var w=false,m=false,V={},ai=0,O,aa;g.current=-1;g.dimensions=null;g.ease=function(K){return 1+Math.pow(K-1,3)};g.errorInfo={fla:{name:"Flash",url:"http://www.adobe.com/products/flashplayer/"},qt:{name:"QuickTime",url:"http://www.apple.com/quicktime/download/"},wmp:{name:"Windows Media Player",url:"http://www.microsoft.com/windows/windowsmedia/"},f4m:{name:"Flip4Mac",url:"http://www.flip4mac.com/wmv_download.htm"}};g.gallery=[];g.onReady=aj;g.path=null;g.player=null;g.playerId="sb-player";g.options={animate:true,animateFade:true,autoplayMovies:true,continuous:false,enableKeys:true,flashParams:{bgcolor:"#000000",allowfullscreen:true},flashVars:{},flashVersion:"9.0.115",handleOversize:"resize",handleUnsupported:"link",onChange:aj,onClose:aj,onFinish:aj,onOpen:aj,showMovieControls:true,skipSetup:false,slideshowDelay:0,viewportPadding:20};g.getCurrent=function(){return g.current>-1?g.gallery[g.current]:null};g.hasNext=function(){return g.gallery.length>1&&(g.current!=g.gallery.length-1||g.options.continuous)};g.isOpen=function(){return w};g.isPaused=function(){return aa=="pause"};g.applyOptions=function(K){V=ao({},g.options);ao(g.options,K)};g.revertOptions=function(){ao(g.options,V)};
9612g.init=function(au,ax){if(m){return}m=true;if(g.skin.options){ao(g.options,g.skin.options)}if(au){ao(g.options,au)}if(!g.path){var aw,S=document.getElementsByTagName("script");for(var av=0,K=S.length;av<K;++av){aw=ap.exec(S[av].src);if(aw){g.path=aw[1];break}}}if(ax){g.onReady=ax}aq()};g.open=function(S){if(w){return}var K=g.makeGallery(S);g.gallery=K[0];g.current=K[1];S=g.getCurrent();if(S==null){return}g.applyOptions(S.options||{});f();if(g.gallery.length){S=g.getCurrent();if(g.options.onOpen(S)===false){return}w=true;g.skin.onOpen(S,U)}};g.close=function(){if(!w){return}w=false;if(g.player){g.player.remove();g.player=null}if(typeof aa=="number"){clearTimeout(aa);aa=null}ai=0;af(false);g.options.onClose(g.getCurrent());g.skin.onClose();g.revertOptions()};g.play=function(){if(!g.hasNext()){return}if(!ai){ai=g.options.slideshowDelay*1000}if(ai){O=X();aa=setTimeout(function(){ai=O=0;g.next()},ai);if(g.skin.onPlay){g.skin.onPlay()}}};g.pause=function(){if(typeof aa!="number"){return}ai=Math.max(0,ai-(X()-O));if(ai){clearTimeout(aa);aa="pause";if(g.skin.onPause){g.skin.onPause()}}};g.change=function(K){if(!(K in g.gallery)){if(g.options.continuous){K=(K<0?g.gallery.length+K:0);if(!(K in g.gallery)){return}}else{return}}g.current=K;if(typeof aa=="number"){clearTimeout(aa);aa=null;ai=O=0}g.options.onChange(g.getCurrent());U(true)};g.next=function(){g.change(g.current+1)};g.previous=function(){g.change(g.current-1)};g.setDimensions=function(aG,ax,aE,aF,aw,K,aC,az){var aB=aG,av=ax;var aA=2*aC+aw;if(aG+aA>aE){aG=aE-aA}var au=2*aC+K;if(ax+au>aF){ax=aF-au}var S=(aB-aG)/aB,aD=(av-ax)/av,ay=(S>0||aD>0);if(az&&ay){if(S>aD){ax=Math.round((av/aB)*aG)}else{if(aD>S){aG=Math.round((aB/av)*ax)}}}g.dimensions={height:aG+aw,width:ax+K,innerHeight:aG,innerWidth:ax,top:Math.floor((aE-(aG+aA))/2+aC),left:Math.floor((aF-(ax+au))/2+aC),oversized:ay};return g.dimensions};g.makeGallery=function(aw){var K=[],av=-1;if(typeof aw=="string"){aw=[aw]}if(typeof aw.length=="number"){ac(aw,function(ay,az){if(az.content){K[ay]=az}else{K[ay]={content:az}}});av=0}else{if(aw.tagName){var S=g.getCache(aw);aw=S?S:g.makeObject(aw)}if(aw.gallery){K=[];var ax;for(var au in g.cache){ax=g.cache[au];if(ax.gallery&&ax.gallery==aw.gallery){if(av==-1&&ax.content==aw.content){av=K.length}K.push(ax)}}if(av==-1){K.unshift(aw);av=0}}else{K=[aw];av=0}}ac(K,function(ay,az){K[ay]=ao({},az)});return[K,av]};g.makeObject=function(av,au){var aw={content:av.href,title:av.getAttribute("title")||"",link:av};if(au){au=ao({},au);ac(["player","title","height","width","gallery"],function(ax,ay){if(typeof au[ay]!="undefined"){aw[ay]=au[ay];delete au[ay]}});aw.options=au}else{aw.options={}}if(!aw.player){aw.player=g.getPlayer(aw.content)}var K=av.getAttribute("rel");if(K){var S=K.match(M);if(S){aw.gallery=escape(S[2])}ac(K.split(";"),function(ax,ay){S=ay.match(o);if(S){aw[S[1]]=S[2]}})}return aw};g.getPlayer=function(au){if(au.indexOf("#")>-1&&au.indexOf(document.location.href)==0){return"inline"}var av=au.indexOf("?");if(av>-1){au=au.substring(0,av)}var S,K=au.match(ar);if(K){S=K[0].toLowerCase()}if(S){if(g.img&&g.img.ext.indexOf(S)>-1){return"img"}if(g.swf&&g.swf.ext.indexOf(S)>-1){return"swf"}if(g.flv&&g.flv.ext.indexOf(S)>-1){return"flv"}if(g.qt&&g.qt.ext.indexOf(S)>-1){if(g.wmp&&g.wmp.ext.indexOf(S)>-1){return"qtwmp"}else{return"qt"}}if(g.wmp&&g.wmp.ext.indexOf(S)>-1){return"wmp"}}return"iframe"};function f(){var av=g.errorInfo,aw=g.plugins,ay,az,aC,au,aB,S,aA,K;for(var ax=0;ax<g.gallery.length;++ax){ay=g.gallery[ax];az=false;aC=null;switch(ay.player){case"flv":case"swf":if(!aw.fla){aC="fla"}break;case"qt":if(!aw.qt){aC="qt"}break;case"wmp":if(g.isMac){if(aw.qt&&aw.f4m){ay.player="qt"}else{aC="qtf4m"}}else{if(!aw.wmp){aC="wmp"}}break;case"qtwmp":if(aw.qt){ay.player="qt"}else{if(aw.wmp){ay.player="wmp"}else{aC="qtwmp"}}break}if(aC){if(g.options.handleUnsupported=="link"){switch(aC){case"qtf4m":aB="shared";S=[av.qt.url,av.qt.name,av.f4m.url,av.f4m.name];break;case"qtwmp":aB="either";S=[av.qt.url,av.qt.name,av.wmp.url,av.wmp.name];break;default:aB="single";S=[av[aC].url,av[aC].name]}ay.player="html";ay.content='<div class="sb-message">'+s(g.lang.errors[aB],S)+"</div>"}else{az=true}}else{if(ay.player=="inline"){au=e.exec(ay.content);if(au){aA=ag(au[1]);if(aA){ay.content=aA.innerHTML}else{az=true}}else{az=true}}else{if(ay.player=="swf"||ay.player=="flv"){K=(ay.options&&ay.options.flashVersion)||g.options.flashVersion;if(g.flash&&!g.flash.hasFlashPlayerVersion(K)){ay.width=310;ay.height=177}}}}if(az){g.gallery.splice(ax,1);if(ax<g.current){--g.current}else{if(ax==g.current){g.current=ax>0?ax-1:ax}}--ax}}}
9612function af(K){if(!g.options.enableKeys){return}(K?j:a)(document,"keydown",W)}function W(au){if(au.metaKey||au.shiftKey||au.altKey||au.ctrlKey){return}var S=l(au),K;switch(S){case 81:case 88:case 27:K=g.close;break;case 37:K=g.previous;break;case 39:K=g.next;break;case 32:K=typeof aa=="number"?g.pause:g.play;break}if(K){G(au);K()}}function U(ay){af(false);var ax=g.getCurrent();var au=(ax.player=="inline"?"html":ax.player);if(typeof g[au]!="function"){throw"unknown player "+au}if(ay){g.player.remove();g.revertOptions();g.applyOptions(ax.options||{})}g.player=new g[au](ax,g.playerId);if(g.gallery.length>1){var av=g.gallery[g.current+1]||g.gallery[0];if(av.player=="img"){var S=new Image();S.src=av.content}var aw=g.gallery[g.current-1]||g.gallery[g.gallery.length-1];if(aw.player=="img"){var K=new Image();K.src=aw.content}}g.skin.onLoad(ay,r)}function r(){if(!w){return}if(typeof g.player.ready!="undefined"){var K=setInterval(function(){if(w){if(g.player.ready){clearInterval(K);K=null;g.skin.onReady(J)}}else{clearInterval(K);K=null}},10)}else{g.skin.onReady(J)}}function J(){if(!w){return}g.player.append(g.skin.body,g.dimensions);g.skin.onShow(q)}function q(){if(!w){return}if(g.player.onLoad){g.player.onLoad()}g.options.onFinish(g.getCurrent());if(!g.isPaused()){g.play()}af(true)}if(!Array.prototype.indexOf){Array.prototype.indexOf=function(S,au){var K=this.length>>>0;au=au||0;if(au<0){au+=K}for(;au<K;++au){if(au in this&&this[au]===S){return au}}return -1}}function X(){return(new Date).getTime()}function ao(K,au){for(var S in au){K[S]=au[S]}return K}function ac(av,aw){var S=0,K=av.length;for(var au=av[0];S<K&&aw.call(au,S,au)!==false;au=av[++S]){}}function s(S,K){return S.replace(/\{(\w+?)\}/g,function(au,av){return K[av]})}function aj(){}function ag(K){return document.getElementById(K)}function z(K){K.parentNode.removeChild(K)}var ak=true,L=true;function an(){var K=document.body,S=document.createElement("div");ak=typeof S.style.opacity==="string";S.style.position="fixed";S.style.margin=0;S.style.top="20px";K.appendChild(S,K.firstChild);L=S.offsetTop==20;K.removeChild(S)}g.getStyle=(function(){var K=/opacity=([^)]*)/,S=document.defaultView&&document.defaultView.getComputedStyle;return function(ax,aw){var av;if(!ak&&aw=="opacity"&&ax.currentStyle){av=K.test(ax.currentStyle.filter||"")?(parseFloat(RegExp.$1)/100)+"":"";return av===""?"1":av}if(S){var au=S(ax,null);if(au){av=au[aw]}if(aw=="opacity"&&av==""){av="1"}}else{av=ax.currentStyle[aw]}return av}})();g.appendHTML=function(au,S){if(au.insertAdjacentHTML){au.insertAdjacentHTML("BeforeEnd",S)}else{if(au.lastChild){var K=au.ownerDocument.createRange();K.setStartAfter(au.lastChild);var av=K.createContextualFragment(S);au.appendChild(av)}else{au.innerHTML=S}}};g.getWindowSize=function(K){if(document.compatMode==="CSS1Compat"){return document.documentElement["client"+K]}return document.body["client"+K]};g.setOpacity=function(au,K){var S=au.style;if(ak){S.opacity=(K==1?"":K)}else{S.zoom=1;if(K==1){if(typeof S.filter=="string"&&(/alpha/i).test(S.filter)){S.filter=S.filter.replace(/\s*[\w\.]*alpha\([^\)]*\);?/gi,"")}}else{S.filter=(S.filter||"").replace(/\s*[\w\.]*alpha\([^\)]*\)/gi,"")+" alpha(opacity="+(K*100)+")"}}};g.clearOpacity=function(K){g.setOpacity(K,1)};function C(S){var K=S.target?S.target:S.srcElement;return K.nodeType==3?K.parentNode:K}function Q(S){var K=S.pageX||(S.clientX+(document.documentElement.scrollLeft||document.body.scrollLeft)),au=S.pageY||(S.clientY+(document.documentElement.scrollTop||document.body.scrollTop));return[K,au]}function G(K){K.preventDefault()}function l(K){return K.which?K.which:K.keyCode}function j(av,au,S){if(av.addEventListener){av.addEventListener(au,S,false)}else{if(av.nodeType===3||av.nodeType===8){return}if(av.setInterval&&(av!==T&&!av.frameElement)){av=T}if(!S.__guid){S.__guid=j.guid++}if(!av.events){av.events={}}var K=av.events[au];if(!K){K=av.events[au]={};if(av["on"+au]){K[0]=av["on"+au]}}K[S.__guid]=S;av["on"+au]=j.handleEvent}}j.guid=1;j.handleEvent=function(av){var K=true;av=av||j.fixEvent(((this.ownerDocument||this.document||this).parentWindow||T).event);var S=this.events[av.type];for(var au in S){this.__handleEvent=S[au];if(this.__handleEvent(av)===false){K=false}}return K};j.preventDefault=function(){this.returnValue=false};j.stopPropagation=function(){this.cancelBubble=true};j.fixEvent=function(K){K.preventDefault=j.preventDefault;K.stopPropagation=j.stopPropagation;return K};function a(au,S,K){if(au.removeEventListener){au.removeEventListener(S,K,false)}else{if(au.events&&au.events[S]){delete au.events[S][K.__guid]}}}var D=false,N;if(document.addEventListener){N=function(){document.removeEventListener("DOMContentLoaded",N,false);g.load()}}else{if(document.attachEvent){N=function(){if(document.readyState==="complete"){document.detachEvent("onreadystatechange",N);g.load()}}}}function i(){if(D){return}try{document.documentElement.doScroll("left")}catch(K){setTimeout(i,1);return}g.load()}
9612function aq(){if(document.readyState==="complete"){return g.load()}if(document.addEventListener){document.addEventListener("DOMContentLoaded",N,false);T.addEventListener("load",g.load,false)}else{if(document.attachEvent){document.attachEvent("onreadystatechange",N);T.attachEvent("onload",g.load);var K=false;try{K=T.frameElement===null}catch(S){}if(document.documentElement.doScroll&&K){i()}}}}g.load=function(){if(D){return}if(!document.body){return setTimeout(g.load,13)}D=true;an();g.onReady();if(!g.options.skipSetup){g.setup()}g.skin.init()};g.plugins={};if(navigator.plugins&&navigator.plugins.length){var am=[];ac(navigator.plugins,function(K,S){am.push(S.name)});am=am.join(",");var d=am.indexOf("Flip4Mac")>-1;g.plugins={fla:am.indexOf("Shockwave Flash")>-1,qt:am.indexOf("QuickTime")>-1,wmp:!d&&am.indexOf("Windows Media")>-1,f4m:d}}else{var B=function(K){var S;try{S=new ActiveXObject(K)}catch(au){}return !!S};g.plugins={fla:B("ShockwaveFlash.ShockwaveFlash"),qt:B("QuickTime.QuickTime"),wmp:B("wmplayer.ocx"),f4m:false}}var c=/^(light|shadow)box/i,Z="shadowboxCacheKey",h=1;g.cache={};g.select=function(S){var au=[];if(!S){var K;ac(document.getElementsByTagName("a"),function(ax,ay){K=ay.getAttribute("rel");if(K&&c.test(K)){au.push(ay)}})}else{var aw=S.length;if(aw){if(typeof S=="string"){if(g.find){au=g.find(S)}}else{if(aw==2&&typeof S[0]=="string"&&S[1].nodeType){if(g.find){au=g.find(S[0],S[1])}}else{for(var av=0;av<aw;++av){au[av]=S[av]}}}}else{au.push(S)}}return au};g.setup=function(K,S){ac(g.select(K),function(au,av){g.addCache(av,S)})};g.teardown=function(K){ac(g.select(K),function(S,au){g.removeCache(au)})};g.addCache=function(au,K){var S=au[Z];if(S==p){S=h++;au[Z]=S;j(au,"click",b)}g.cache[S]=g.makeObject(au,K)};g.removeCache=function(K){a(K,"click",b);delete g.cache[K[Z]];K[Z]=null};g.getCache=function(S){var K=S[Z];return(K in g.cache&&g.cache[K])};g.clearCache=function(){for(var K in g.cache){g.removeCache(g.cache[K].link)}g.cache={}};function b(K){g.open(this);if(g.gallery.length){G(K)}}g.lang={code:"en",of:"of",loading:"loading",cancel:"Cancel",next:"Next",previous:"Previous",play:"Play",pause:"Pause",close:"Close",errors:{single:'You must install the <a href="{0}">{1}</a> browser plugin to view this content.',shared:'You must install both the <a href="{0}">{1}</a> and <a href="{2}">{3}</a> browser plugins to view this content.',either:'You must install either the <a href="{0}">{1}</a> or the <a href="{2}">{3}</a> browser plugin to view this content.'}};g.html=function(K,S){this.obj=K;this.id=S;this.height=K.height?parseInt(K.height,10):300;this.width=K.width?parseInt(K.width,10):500};g.html.prototype={append:function(K,S){var au=document.createElement("div");au.id=this.id;au.className="html";au.innerHTML=this.obj.content;K.appendChild(au)},remove:function(){var K=ag(this.id);if(K){z(K)}}};var al=false,A=[],H=["sb-nav-close","sb-nav-next","sb-nav-play","sb-nav-pause","sb-nav-previous"],F,ah,v,P=true;function ae(au,aE,aB,az,aF){var K=(aE=="opacity"),aA=K?g.setOpacity:function(aG,aH){aG.style[aE]=""+aH+"px"};if(az==0||(!K&&!g.options.animate)||(K&&!g.options.animateFade)){aA(au,aB);if(aF){aF()}return}var aC=parseFloat(g.getStyle(au,aE))||0;var aD=aB-aC;if(aD==0){if(aF){aF()}return}az*=1000;var av=X(),ay=g.ease,ax=av+az,aw;var S=setInterval(function(){aw=X();if(aw>=ax){clearInterval(S);S=null;aA(au,aB);if(aF){aF()}}else{aA(au,aC+ay((aw-av)/az)*aD)}},10)}function I(){F.style.height=g.getWindowSize("Height")+"px";F.style.width=g.getWindowSize("Width")+"px"}function ad(){F.style.top=document.documentElement.scrollTop+"px";F.style.left=document.documentElement.scrollLeft+"px"}function y(K){if(K){ac(A,function(S,au){au[0].style.visibility=au[1]||""})}else{A=[];ac(g.options.troubleElements,function(au,S){ac(document.getElementsByTagName(S),function(av,aw){A.push([aw,aw.style.visibility]);aw.style.visibility="hidden"})})}}function x(au,K){var S=ag("sb-nav-"+au);if(S){S.style.display=K?"":"none"}}function n(K,ax){var aw=ag("sb-loading"),au=g.getCurrent().player,av=(au=="img"||au=="html");if(K){g.setOpacity(aw,0);aw.style.display="block";var S=function(){g.clearOpacity(aw);
9612if(ax){ax()}};if(av){ae(aw,"opacity",1,g.options.fadeDuration,S)}else{S()}}else{var S=function(){aw.style.display="none";g.clearOpacity(aw);if(ax){ax()}};if(av){ae(aw,"opacity",0,g.options.fadeDuration,S)}else{S()}}}function at(aC){var ax=g.getCurrent();ag("sb-title-inner").innerHTML=ax.title||"";var aD,az,S,aE,aA;if(g.options.displayNav){aD=true;var aB=g.gallery.length;if(aB>1){if(g.options.continuous){az=aA=true}else{az=(aB-1)>g.current;aA=g.current>0}}if(g.options.slideshowDelay>0&&g.hasNext()){aE=!g.isPaused();S=!aE}}else{aD=az=S=aE=aA=false}x("close",aD);x("next",az);x("play",S);x("pause",aE);x("previous",aA);var K="";if(g.options.displayCounter&&g.gallery.length>1){var aB=g.gallery.length;if(g.options.counterType=="skip"){var aw=0,av=aB,au=parseInt(g.options.counterLimit)||0;if(au<aB&&au>2){var ay=Math.floor(au/2);aw=g.current-ay;if(aw<0){aw+=aB}av=g.current+(au-ay);if(av>aB){av-=aB}}while(aw!=av){if(aw==aB){aw=0}K+='<a onclick="Shadowbox.change('+aw+');"';if(aw==g.current){K+=' class="sb-counter-current"'}K+=">"+(++aw)+"</a>"}}else{K=[g.current+1,g.lang.of,aB].join(" ")}}ag("sb-counter").innerHTML=K;aC()}function u(av){var K=ag("sb-title-inner"),au=ag("sb-info-inner"),S=0.35;K.style.visibility=au.style.visibility="";if(K.innerHTML!=""){ae(K,"marginTop",0,S)}ae(au,"marginTop",0,S,av)}function ab(au,aA){var ay=ag("sb-title"),K=ag("sb-info"),av=ay.offsetHeight,aw=K.offsetHeight,ax=ag("sb-title-inner"),az=ag("sb-info-inner"),S=(au?0.35:0);ae(ax,"marginTop",av,S);ae(az,"marginTop",aw*-1,S,function(){ax.style.visibility=az.style.visibility="hidden";aA()})}function E(K,av,S,ax){var aw=ag("sb-wrapper-inner"),au=(S?g.options.resizeDuration:0);ae(v,"top",av,au);ae(aw,"height",K,au,ax)}function t(K,av,S,aw){var au=(S?g.options.resizeDuration:0);ae(v,"left",av,au);ae(v,"width",K,au,aw)}function R(aA,au){var aw=ag("sb-body-inner"),aA=parseInt(aA),au=parseInt(au),S=v.offsetHeight-aw.offsetHeight,K=v.offsetWidth-aw.offsetWidth,ay=ah.offsetHeight,az=ah.offsetWidth,ax=parseInt(g.options.viewportPadding)||20,av=(g.player&&g.options.handleOversize!="drag");return g.setDimensions(aA,au,ay,az,S,K,ax,av)}var k={};k.markup='<div id="sb-container"><div id="sb-overlay"></div><div id="sb-wrapper"><div id="sb-title"><div id="sb-title-inner"></div></div><div id="sb-wrapper-inner"><div id="sb-body"><div id="sb-body-inner"></div><div id="sb-loading"><div id="sb-loading-inner"><span>{loading}</span></div></div></div></div><div id="sb-info"><div id="sb-info-inner"><div id="sb-counter"></div><div id="sb-nav"><a id="sb-nav-close" title="{close}" onclick="Shadowbox.close()"></a><a id="sb-nav-next" title="{next}" onclick="Shadowbox.next()"></a><a id="sb-nav-play" title="{play}" onclick="Shadowbox.play()"></a><a id="sb-nav-pause" title="{pause}" onclick="Shadowbox.pause()"></a><a id="sb-nav-previous" title="{previous}" onclick="Shadowbox.previous()"></a></div></div></div></div></div>';k.options={animSequence:"sync",counterLimit:10,counterType:"default",displayCounter:true,displayNav:true,fadeDuration:0.35,initialHeight:160,initialWidth:320,modal:false,overlayColor:"#000",overlayOpacity:0.5,resizeDuration:0.35,showOverlay:true,troubleElements:["select","object","embed","canvas"]};k.init=function(){g.appendHTML(document.body,s(k.markup,g.lang));k.body=ag("sb-body-inner");F=ag("sb-container");ah=ag("sb-overlay");v=ag("sb-wrapper");if(!L){F.style.position="absolute"}if(!ak){var au,K,S=/url\("(.*\.png)"\)/;ac(H,function(aw,ax){au=ag(ax);if(au){K=g.getStyle(au,"backgroundImage").match(S);if(K){au.style.backgroundImage="none";au.style.filter="progid:DXImageTransform.Microsoft.AlphaImageLoader(enabled=true,src="+K[1]+",sizingMethod=scale);"}}})}var av;j(T,"resize",function(){if(av){clearTimeout(av);av=null}if(w){av=setTimeout(k.onWindowResize,10)}})};k.onOpen=function(K,au){P=false;F.style.display="block";I();var S=R(g.options.initialHeight,g.options.initialWidth);E(S.innerHeight,S.top);t(S.width,S.left);if(g.options.showOverlay){ah.style.backgroundColor=g.options.overlayColor;g.setOpacity(ah,0);if(!g.options.modal){j(ah,"click",g.close)}al=true}if(!L){ad();j(T,"scroll",ad)}y();F.style.visibility="visible";if(al){ae(ah,"opacity",g.options.overlayOpacity,g.options.fadeDuration,au)}else{au()}};k.onLoad=function(S,K){n(true);while(k.body.firstChild){z(k.body.firstChild)}ab(S,function(){if(!w){return}if(!S){v.style.visibility="visible"}at(K)})};k.onReady=function(av){if(!w){return}var S=g.player,au=R(S.height,S.width);var K=function(){u(av)};
9612switch(g.options.animSequence){case"hw":E(au.innerHeight,au.top,true,function(){t(au.width,au.left,true,K)});break;case"wh":t(au.width,au.left,true,function(){E(au.innerHeight,au.top,true,K)});break;default:t(au.width,au.left,true);E(au.innerHeight,au.top,true,K)}};k.onShow=function(K){n(false,K);P=true};k.onClose=function(){if(!L){a(T,"scroll",ad)}a(ah,"click",g.close);v.style.visibility="hidden";var K=function(){F.style.visibility="hidden";F.style.display="none";y(true)};if(al){ae(ah,"opacity",0,g.options.fadeDuration,K)}else{K()}};k.onPlay=function(){x("play",false);x("pause",true)};k.onPause=function(){x("pause",false);x("play",true)};k.onWindowResize=function(){if(!P){return}I();var K=g.player,S=R(K.height,K.width);t(S.width,S.left);E(S.innerHeight,S.top);if(K.onWindowResize){K.onWindowResize()}};g.skin=k;T.Shadowbox=g})(window);

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.