/*  Prototype JavaScript framework, version 1.6.0_rc0
 *  (c) 2005-2007 Sam Stephenson
 *
 *  Prototype is freely distributable under the terms of an MIT-style license.
 *  For details, see the Prototype web site: http://www.prototypejs.org/
 *
 *--------------------------------------------------------------------------*/

var Prototype = {
  Version: '1.6.0_rc0',

  Browser: {
    IE:     !!(window.attachEvent && !window.opera),
    Opera:  !!window.opera,
    WebKit: navigator.userAgent.indexOf('AppleWebKit/') > -1,
    Gecko:  navigator.userAgent.indexOf('Gecko') > -1 && navigator.userAgent.indexOf('KHTML') == -1,
    MobileSafari: !!navigator.userAgent.match(/iPhone.*Mobile.*Safari/)
  },

  BrowserFeatures: {
    XPath: !!document.evaluate,
    ElementExtensions: !!window.HTMLElement,
    SpecificElementExtensions:
      document.createElement('div').__proto__ !==
       document.createElement('form').__proto__
  },

  ScriptFragment: '<script[^>]*>([\\S\\s]*?)<\/script>',
  JSONFilter: /^\/\*-secure-([\s\S]*)\*\/\s*$/,

  emptyFunction: function() { },
  K: function(x) { return x }
};

if (Prototype.Browser.MobileSafari)
  Prototype.BrowserFeatures.SpecificElementExtensions = false;

/* Based on Alex Arnell's inheritance implementation. */
var Class = {
  create: (function() {
    var extending = { };

    return function(parent, properties) {
      if (arguments.length == 1 && !Object.isFunction(parent))
        properties = parent, parent = null;

      function klass() {
        if (arguments[0] !== extending)
          this.initialize.apply(this, arguments);
      }

      klass.superclass = parent;
      klass.subclasses = [];

      if (parent) {
        klass.prototype = new parent(extending);
        parent.subclasses.push(klass);
      }

      if (properties) Class.extend(klass, properties);
      klass.prototype.constructor = klass;

      return klass;
    };
  })(),

  extend: function(destination, source) {
    var ancestor = destination.superclass && destination.superclass.prototype;

    for (var property in source) {
      var value = source[property];
      if (ancestor && Object.isFunction(value) &&
          value.argumentNames().first() == "$super") {
        var method = value, value = Object.extend((function(m) {
          return function() { return ancestor[m].apply(this, arguments) };
        })(property).wrap(method), {
          valueOf:  function() { return method },
          toString: function() { return method.toString() }
        });
      }
      destination.prototype[property] = value;
    }

    return destination;
  },

  mixin: function(destination, source) {
    return Object.extend(destination.prototype, source);
  }
};

var Abstract = { };

Object.extend = function(destination, source) {
  for (var property in source)
    destination[property] = source[property];
  return destination;
};

Object.extend(Object, {
  inspect: function(object) {
    try {
      if (object === undefined) return 'undefined';
      if (object === null) return 'null';
      return object.inspect ? object.inspect() : object.toString();
    } catch (e) {
      if (e instanceof RangeError) return '...';
      throw e;
    }
  },

  toJSON: function(object) {
    var type = typeof object;
    switch (type) {
      case 'undefined':
      case 'function':
      case 'unknown': return;
      case 'boolean': return object.toString();
    }

    if (object === null) return 'null';
    if (object.toJSON) return object.toJSON();
    if (Object.isElement(object)) return;

    var results = [];
    for (var property in object) {
      var value = Object.toJSON(object[property]);
      if (value !== undefined)
        results.push(property.toJSON() + ': ' + value);
    }

    return '{' + results.join(', ') + '}';
  },

  toHTML: function(object) {
    return object && object.toHTML ? object.toHTML() : String.interpret(object);
  },

  keys: function(object) {
    var keys = [];
    for (var property in object)
      keys.push(property);
    return keys;
  },

  values: function(object) {
    var values = [];
    for (var property in object)
      values.push(object[property]);
    return values;
  },

  clone: function(object) {
    return Object.extend({ }, object);
  },

  isElement: function(object) {
    return object && object.nodeType == 1;
  },

  isArray: function(object) {
    return object && object.constructor === Array;
  },

  isFunction: function(object) {
    return typeof object == "function";
  },

  isString: function(object) {
    return typeof object == "string";
  },

  isNumber: function(object) {
    return typeof object == "number";
  },

  isUndefined: function(object) {
    return typeof object == "undefined";
  }
});

Object.extend(Function.prototype, {
  argumentNames: function() {
    var names = this.toString().match(/^[\s\(]*function\s*\((.*?)\)/)[1].split(",").invoke("strip");
    return names.length == 1 && !names[0] ? [] : names;
  },

  bind: function() {
    if (arguments.length < 2 && arguments[0] === undefined) return this;
    var __method = this, args = $A(arguments), object = args.shift();
    return function() {
      return __method.apply(object, args.concat($A(arguments)));
    }
  },

  bindAsEventListener: function() {
    var __method = this, args = $A(arguments), object = args.shift();
    return function(event) {
      return __method.apply(object, [event || window.event].concat(args));
    }
  },

  curry: function() {
    if (!arguments.length) return this;
    var __method = this, args = $A(arguments);
    return function() {
      return __method.apply(this, args.concat($A(arguments)));
    }
  },

  delay: function() {
    var __method = this, args = $A(arguments), timeout = args.shift() * 1000;
    return window.setTimeout(function() {
      return __method.apply(__method, args);
    }, timeout);
  },

  wrap: function(wrapper) {
    var __method = this;
    return function() {
      return wrapper.apply(this, [__method.bind(this)].concat($A(arguments)));
    }
  },

  methodize: function() {
    if (this._methodized) return this._methodized;
    var __method = this;
    return this._methodized = function() {
      return __method.apply(null, [this].concat($A(arguments)));
    };
  }
});

Function.prototype.defer = Function.prototype.delay.curry(0.01);

Date.prototype.toJSON = function() {
  return '"' + this.getFullYear() + '-' +
    (this.getMonth() + 1).toPaddedString(2) + '-' +
    this.getDate().toPaddedString(2) + 'T' +
    this.getHours().toPaddedString(2) + ':' +
    this.getMinutes().toPaddedString(2) + ':' +
    this.getSeconds().toPaddedString(2) + '"';
};

var Try = {
  these: function() {
    var returnValue;

    for (var i = 0, length = arguments.length; i < length; i++) {
      var lambda = arguments[i];
      try {
        returnValue = lambda();
        break;
      } catch (e) { }
    }

    return returnValue;
  }
};

RegExp.prototype.match = RegExp.prototype.test;

RegExp.escape = function(str) {
  return String(str).replace(/([.*+?^=!:${}()|[\]\/\\])/g, '\\$1');
};

/*--------------------------------------------------------------------------*/

var PeriodicalExecuter = Class.create({
  initialize: function(callback, frequency) {
    this.callback = callback;
    this.frequency = frequency;
    this.currentlyExecuting = false;

    this.registerCallback();
  },

  registerCallback: function() {
    this.timer = setInterval(this.onTimerEvent.bind(this), this.frequency * 1000);
  },

  execute: function() {
    this.callback(this);
  },

  stop: function() {
    if (!this.timer) return;
    clearInterval(this.timer);
    this.timer = null;
  },

  onTimerEvent: function() {
    if (!this.currentlyExecuting) {
      try {
        this.currentlyExecuting = true;
        this.execute();
      } finally {
        this.currentlyExecuting = false;
      }
    }
  }
});
Object.extend(String, {
  interpret: function(value) {
    return value == null ? '' : String(value);
  },
  specialChar: {
    '\b': '\\b',
    '\t': '\\t',
    '\n': '\\n',
    '\f': '\\f',
    '\r': '\\r',
    '\\': '\\\\'
  }
});

Object.extend(String.prototype, {
  gsub: function(pattern, replacement) {
    var result = '', source = this, match;
    replacement = arguments.callee.prepareReplacement(replacement);

    while (source.length > 0) {
      if (match = source.match(pattern)) {
        result += source.slice(0, match.index);
        result += String.interpret(replacement(match));
        source  = source.slice(match.index + match[0].length);
      } else {
        result += source, source = '';
      }
    }
    return result;
  },

  sub: function(pattern, replacement, count) {
    replacement = this.gsub.prepareReplacement(replacement);
    count = count === undefined ? 1 : count;

    return this.gsub(pattern, function(match) {
      if (--count < 0) return match[0];
      return replacement(match);
    });
  },

  scan: function(pattern, iterator) {
    this.gsub(pattern, iterator);
    return String(this);
  },

  truncate: function(length, truncation) {
    length = length || 30;
    truncation = truncation === undefined ? '...' : truncation;
    return this.length > length ?
      this.slice(0, length - truncation.length) + truncation : String(this);
  },

  strip: function() {
    return this.replace(/^\s+/, '').replace(/\s+$/, '');
  },

  stripTags: function() {
    return this.replace(/<\/?[^>]+>/gi, '');
  },

  stripScripts: function() {
    return this.replace(new RegExp(Prototype.ScriptFragment, 'img'), '');
  },

  extractScripts: function() {
    var matchAll = new RegExp(Prototype.ScriptFragment, 'img');
    var matchOne = new RegExp(Prototype.ScriptFragment, 'im');
    return (this.match(matchAll) || []).map(function(scriptTag) {
      return (scriptTag.match(matchOne) || ['', ''])[1];
    });
  },

  evalScripts: function() {
    return this.extractScripts().map(function(script) { return eval(script) });
  },

  escapeHTML: function() {
    var self = arguments.callee;
    self.text.data = this;
    return self.div.innerHTML;
  },

  unescapeHTML: function() {
    var div = new Element('div');
    div.innerHTML = this.stripTags();
    return div.childNodes[0] ? (div.childNodes.length > 1 ?
      $A(div.childNodes).inject('', function(memo, node) { return memo+node.nodeValue }) :
      div.childNodes[0].nodeValue) : '';
  },

  toQueryParams: function(separator) {
    var match = this.strip().match(/([^?#]*)(#.*)?$/);
    if (!match) return { };

    return match[1].split(separator || '&').inject({ }, function(hash, pair) {
      if ((pair = pair.split('='))[0]) {
        var key = decodeURIComponent(pair.shift());
        var value = pair.length > 1 ? pair.join('=') : pair[0];
        if (value != undefined) value = decodeURIComponent(value);

        if (key in hash) {
          if (!Object.isArray(hash[key])) hash[key] = [hash[key]];
          hash[key].push(value);
        }
        else hash[key] = value;
      }
      return hash;
    });
  },

  toArray: function() {
    return this.split('');
  },

  succ: function() {
    return this.slice(0, this.length - 1) +
      String.fromCharCode(this.charCodeAt(this.length - 1) + 1);
  },

  times: function(count) {
    var result = '';
    for (var i = 0; i < count; i++) result += this;
    return result;
  },

  camelize: function() {
    var parts = this.split('-'), len = parts.length;
    if (len == 1) return parts[0];

    var camelized = this.charAt(0) == '-'
      ? parts[0].charAt(0).toUpperCase() + parts[0].substring(1)
      : parts[0];

    for (var i = 1; i < len; i++)
      camelized += parts[i].charAt(0).toUpperCase() + parts[i].substring(1);

    return camelized;
  },

  capitalize: function() {
    return this.charAt(0).toUpperCase() + this.substring(1).toLowerCase();
  },

  underscore: function() {
    return this.gsub(/::/, '/').gsub(/([A-Z]+)([A-Z][a-z])/,'#{1}_#{2}').gsub(/([a-z\d])([A-Z])/,'#{1}_#{2}').gsub(/-/,'_').toLowerCase();
  },

  dasherize: function() {
    return this.gsub(/_/,'-');
  },

  inspect: function(useDoubleQuotes) {
    var escapedString = this.gsub(/[\x00-\x1f\\]/, function(match) {
      var character = String.specialChar[match[0]];
      return character ? character : '\\u00' + match[0].charCodeAt().toPaddedString(2, 16);
    });
    if (useDoubleQuotes) return '"' + escapedString.replace(/"/g, '\\"') + '"';
    return "'" + escapedString.replace(/'/g, '\\\'') + "'";
  },

  toJSON: function() {
    return this.inspect(true);
  },

  unfilterJSON: function(filter) {
    return this.sub(filter || Prototype.JSONFilter, '#{1}');
  },

  isJSON: function() {
    var str = this.replace(/\\./g, '@').replace(/"[^"\\\n\r]*"/g, '');
    return (/^[,:{}\[\]0-9.\-+Eaeflnr-u \n\r\t]*$/).test(str);
  },

  evalJSON: function(sanitize) {
    var json = this.unfilterJSON();
    try {
      if (!sanitize || json.isJSON()) return eval('(' + json + ')');
    } catch (e) { }
    throw new SyntaxError('Badly formed JSON string: ' + this.inspect());
  },

  include: function(pattern) {
    return this.indexOf(pattern) > -1;
  },

  startsWith: function(pattern) {
    return this.indexOf(pattern) === 0;
  },

  endsWith: function(pattern) {
    var d = this.length - pattern.length;
    return d >= 0 && this.lastIndexOf(pattern) === d;
  },

  empty: function() {
    return this == '';
  },

  blank: function() {
    return /^\s*$/.test(this);
  },

  interpolate: function(object, pattern) {
    return new Template(this, pattern).evaluate(object);
  }
});

if (Prototype.Browser.WebKit || Prototype.Browser.IE) Object.extend(String.prototype, {
  escapeHTML: function() {
    return this.replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;');
  },
  unescapeHTML: function() {
    return this.replace(/&amp;/g,'&').replace(/&lt;/g,'<').replace(/&gt;/g,'>');
  }
});

String.prototype.gsub.prepareReplacement = function(replacement) {
  if (Object.isFunction(replacement)) return replacement;
  var template = new Template(replacement);
  return function(match) { return template.evaluate(match) };
};

String.prototype.parseQuery = String.prototype.toQueryParams;

Object.extend(String.prototype.escapeHTML, {
  div:  document.createElement('div'),
  text: document.createTextNode('')
});

with (String.prototype.escapeHTML) div.appendChild(text);

var Template = Class.create({
  initialize: function(template, pattern) {
    this.template = template.toString();
    this.pattern = pattern || Template.Pattern;
  },

  evaluate: function(object) {
    if (Object.isFunction(object.toTemplateReplacements))
      object = object.toTemplateReplacements();

    return this.template.gsub(this.pattern, function(match) {
      if (object == null) return '';

      var before = match[1] || '';
      if (before == '\\') return match[2];

      var ctx = object, expr = match[3];
      var pattern = /^([^.[]+|\[((?:.*?[^\\])?)\])(\.|\[|$)/, match = pattern.exec(expr);
      if (match == null) return '';

      while (match != null) {
        var comp = match[1].startsWith('[') ? match[2].gsub('\\\\]', ']') : match[1];
        ctx = ctx[comp];
        if (null == ctx || '' == match[3]) break;
        expr = expr.substring('[' == match[3] ? match[1].length : match[0].length);
        match = pattern.exec(expr);
      }

      return before + String.interpret(ctx);
    }.bind(this));
  }
});
Template.Pattern = /(^|.|\r|\n)(#\{(.*?)\})/;

var $break = { };

var Enumerable = {
  each: function(iterator, context) {
    var index = 0;
    iterator = iterator.bind(context);
    try {
      this._each(function(value) {
        iterator(value, index++);
      });
    } catch (e) {
      if (e != $break) throw e;
    }
    return this;
  },

  eachSlice: function(number, iterator, context) {
    iterator = iterator ? iterator.bind(context) : Prototype.K;
    var index = -number, slices = [], array = this.toArray();
    while ((index += number) < array.length)
      slices.push(array.slice(index, index+number));
    return slices.collect(iterator, context);
  },

  all: function(iterator, context) {
    iterator = iterator ? iterator.bind(context) : Prototype.K;
    var result = true;
    this.each(function(value, index) {
      result = result && !!iterator(value, index);
      if (!result) throw $break;
    });
    return result;
  },

  any: function(iterator, context) {
    iterator = iterator ? iterator.bind(context) : Prototype.K;
    var result = false;
    this.each(function(value, index) {
      if (result = !!iterator(value, index))
        throw $break;
    });
    return result;
  },

  collect: function(iterator, context) {
    iterator = iterator ? iterator.bind(context) : Prototype.K;
    var results = [];
    this.each(function(value, index) {
      results.push(iterator(value, index));
    });
    return results;
  },

  detect: function(iterator, context) {
    iterator = iterator.bind(context);
    var result;
    this.each(function(value, index) {
      if (iterator(value, index)) {
        result = value;
        throw $break;
      }
    });
    return result;
  },

  findAll: function(iterator, context) {
    iterator = iterator.bind(context);
    var results = [];
    this.each(function(value, index) {
      if (iterator(value, index))
        results.push(value);
    });
    return results;
  },

  grep: function(filter, iterator, context) {
    iterator = iterator ? iterator.bind(context) : Prototype.K;
    var results = [];

    if (Object.isString(filter))
      filter = new RegExp(filter);

    this.each(function(value, index) {
      if (filter.match(value))
        results.push(iterator(value, index));
    });
    return results;
  },

  include: function(object) {
    if (Object.isFunction(this.indexOf))
      if (this.indexOf(object) != -1) return true;

    var found = false;
    this.each(function(value) {
      if (value == object) {
        found = true;
        throw $break;
      }
    });
    return found;
  },

  inGroupsOf: function(number, fillWith) {
    fillWith = fillWith === undefined ? null : fillWith;
    return this.eachSlice(number, function(slice) {
      while(slice.length < number) slice.push(fillWith);
      return slice;
    });
  },

  inject: function(memo, iterator, context) {
    iterator = iterator.bind(context);
    this.each(function(value, index) {
      memo = iterator(memo, value, index);
    });
    return memo;
  },

  invoke: function(method) {
    var args = $A(arguments).slice(1);
    return this.map(function(value) {
      return value[method].apply(value, args);
    });
  },

  max: function(iterator, context) {
    iterator = iterator ? iterator.bind(context) : Prototype.K;
    var result;
    this.each(function(value, index) {
      value = iterator(value, index);
      if (result == undefined || value >= result)
        result = value;
    });
    return result;
  },

  min: function(iterator, context) {
    iterator = iterator ? iterator.bind(context) : Prototype.K;
    var result;
    this.each(function(value, index) {
      value = iterator(value, index);
      if (result == undefined || value < result)
        result = value;
    });
    return result;
  },

  partition: function(iterator, context) {
    iterator = iterator ? iterator.bind(context) : Prototype.K;
    var trues = [], falses = [];
    this.each(function(value, index) {
      (iterator(value, index) ?
        trues : falses).push(value);
    });
    return [trues, falses];
  },

  pluck: function(property) {
    var results = [];
    this.each(function(value) {
      results.push(value[property]);
    });
    return results;
  },

  reject: function(iterator, context) {
    iterator = iterator.bind(context);
    var results = [];
    this.each(function(value, index) {
      if (!iterator(value, index))
        results.push(value);
    });
    return results;
  },

  sortBy: function(iterator, context) {
    iterator = iterator.bind(context);
    return this.map(function(value, index) {
      return {value: value, criteria: iterator(value, index)};
    }).sort(function(left, right) {
      var a = left.criteria, b = right.criteria;
      return a < b ? -1 : a > b ? 1 : 0;
    }).pluck('value');
  },

  toArray: function() {
    return this.map();
  },

  zip: function() {
    var iterator = Prototype.K, args = $A(arguments);
    if (Object.isFunction(args.last()))
      iterator = args.pop();

    var collections = [this].concat(args).map($A);
    return this.map(function(value, index) {
      return iterator(collections.pluck(index));
    });
  },

  size: function() {
    return this.toArray().length;
  },

  inspect: function() {
    return '#<Enumerable:' + this.toArray().inspect() + '>';
  }
};

Object.extend(Enumerable, {
  map:     Enumerable.collect,
  find:    Enumerable.detect,
  select:  Enumerable.findAll,
  filter:  Enumerable.findAll,
  member:  Enumerable.include,
  entries: Enumerable.toArray,
  every:   Enumerable.all,
  some:    Enumerable.any
});
function $A(iterable) {
  if (!iterable) return [];
  if (iterable.toArray) return iterable.toArray();
  else {
    var results = [];
    for (var i = 0, length = iterable.length; i < length; i++)
      results.push(iterable[i]);
    return results;
  }
}

if (Prototype.Browser.WebKit) {
  function $A(iterable) {
    if (!iterable) return [];
    if (!(Object.isFunction(iterable) && iterable == '[object NodeList]') &&
        iterable.toArray) {
      return iterable.toArray();
    } else {
      var results = [];
      for (var i = 0, length = iterable.length; i < length; i++)
        results.push(iterable[i]);
      return results;
    }
  }
}

Array.from = $A;

Object.extend(Array.prototype, Enumerable);

if (!Array.prototype._reverse) Array.prototype._reverse = Array.prototype.reverse;

Object.extend(Array.prototype, {
  _each: function(iterator) {
    for (var i = 0, length = this.length; i < length; i++)
      iterator(this[i]);
  },

  clear: function() {
    this.length = 0;
    return this;
  },

  first: function() {
    return this[0];
  },

  last: function() {
    return this[this.length - 1];
  },

  compact: function() {
    return this.select(function(value) {
      return value != null;
    });
  },

  flatten: function() {
    return this.inject([], function(array, value) {
      return array.concat(Object.isArray(value) ?
        value.flatten() : [value]);
    });
  },

  without: function() {
    var values = $A(arguments);
    return this.select(function(value) {
      return !values.include(value);
    });
  },

  reverse: function(inline) {
    return (inline !== false ? this : this.toArray())._reverse();
  },

  reduce: function() {
    return this.length > 1 ? this : this[0];
  },

  uniq: function(sorted) {
    return this.inject([], function(array, value, index) {
      if (0 == index || (sorted ? array.last() != value : !array.include(value)))
        array.push(value);
      return array;
    });
  },

  intersect: function(array) {
    return this.uniq().findAll(function(item) {
      return array.detect(function(value) { return item === value });
    });
  },

  clone: function() {
    return [].concat(this);
  },

  size: function() {
    return this.length;
  },

  inspect: function() {
    return '[' + this.map(Object.inspect).join(', ') + ']';
  },

  toJSON: function() {
    var results = [];
    this.each(function(object) {
      var value = Object.toJSON(object);
      if (value !== undefined) results.push(value);
    });
    return '[' + results.join(', ') + ']';
  }
});

// use native browser JS 1.6 implementation if available
if (Object.isFunction(Array.prototype.forEach))
  Array.prototype._each = Array.prototype.forEach;

if (!Array.prototype.indexOf) Array.prototype.indexOf = function(item, i) {
  i || (i = 0);
  var length = this.length;
  if (i < 0) i = length + i;
  for (; i < length; i++)
    if (this[i] === item) return i;
  return -1;
};

if (!Array.prototype.lastIndexOf) Array.prototype.lastIndexOf = function(item, i) {
  i = isNaN(i) ? this.length : (i < 0 ? this.length + i : i) + 1;
  var n = this.slice(0, i).reverse().indexOf(item);
  return (n < 0) ? n : i - n - 1;
};

Array.prototype.toArray = Array.prototype.clone;

function $w(string) {
  string = string.strip();
  return string ? string.split(/\s+/) : [];
}

if (Prototype.Browser.Opera){
  Array.prototype.concat = function() {
    var array = [];
    for (var i = 0, length = this.length; i < length; i++) array.push(this[i]);
    for (var i = 0, length = arguments.length; i < length; i++) {
      if (Object.isArray(arguments[i])) {
        for (var j = 0, arrayLength = arguments[i].length; j < arrayLength; j++)
          array.push(arguments[i][j]);
      } else {
        array.push(arguments[i]);
      }
    }
    return array;
  };
}
Object.extend(Number.prototype, {
  toColorPart: function() {
    return this.toPaddedString(2, 16);
  },

  succ: function() {
    return this + 1;
  },

  times: function(iterator) {
    $R(0, this, true).each(iterator);
    return this;
  },

  toPaddedString: function(length, radix) {
    var string = this.toString(radix || 10);
    return '0'.times(length - string.length) + string;
  },

  toJSON: function() {
    return isFinite(this) ? this.toString() : 'null';
  }
});

$w('abs round ceil floor').each(function(method){
  Number.prototype[method] = Math[method].methodize();
});
var Hash = function(object) {
  if (object instanceof Hash) this.merge(object);
  else Object.extend(this, object || { });
};

Object.extend(Hash, {
  toQueryString: function(obj) {
    var parts = [];
    parts.add = arguments.callee.addPair;

    this.prototype._each.call(obj, function(pair) {
      if (!pair.key) return;
      var value = pair.value;

      if (value && typeof value == 'object') {
        if (Object.isArray(value)) value.each(function(value) {
          parts.add(pair.key, value);
        });
        return;
      }
      parts.add(pair.key, value);
    });

    return parts.join('&');
  },

  toJSON: function(object) {
    var results = [];
    this.prototype._each.call(object, function(pair) {
      var value = Object.toJSON(pair.value);
      if (value !== undefined) results.push(pair.key.toJSON() + ': ' + value);
    });
    return '{' + results.join(', ') + '}';
  }
});

Hash.toQueryString.addPair = function(key, value, prefix) {
  key = encodeURIComponent(key);
  if (value === undefined) this.push(key);
  else this.push(key + '=' + (value == null ? '' : encodeURIComponent(value)));
};

Object.extend(Hash.prototype, Enumerable);
Object.extend(Hash.prototype, {
  _each: function(iterator) {
    for (var key in this) {
      var value = this[key];
      if (value && value == Hash.prototype[key]) continue;

      var pair = [key, value];
      pair.key = key;
      pair.value = value;
      iterator(pair);
    }
  },

  keys: function() {
    return this.pluck('key');
  },

  values: function() {
    return this.pluck('value');
  },

  index: function(value) {
    var match = this.detect(function(pair) {
      return pair.value === value;
    });
    return match && match.key;
  },

  merge: function(hash) {
    return $H(hash).inject(this, function(mergedHash, pair) {
      mergedHash[pair.key] = pair.value;
      return mergedHash;
    });
  },

  remove: function() {
    var result;
    for(var i = 0, length = arguments.length; i < length; i++) {
      var value = this[arguments[i]];
      if (value !== undefined){
        if (result === undefined) result = value;
        else {
          if (!Object.isArray(result)) result = [result];
          result.push(value);
        }
      }
      delete this[arguments[i]];
    }
    return result;
  },

  toQueryString: function() {
    return Hash.toQueryString(this);
  },

  inspect: function() {
    return '#<Hash:{' + this.map(function(pair) {
      return pair.map(Object.inspect).join(': ');
    }).join(', ') + '}>';
  },

  toJSON: function() {
    return Hash.toJSON(this);
  }
});

function $H(object) {
  if (object instanceof Hash) return object;
  return new Hash(object);
};

// Safari iterates over shadowed properties
if (function() {
  var i = 0, Test = function(value) { this.key = value };
  Test.prototype.key = 'foo';
  for (var property in new Test('bar')) i++;
  return i > 1;
}()) Hash.prototype._each = function(iterator) {
  var cache = [];
  for (var key in this) {
    var value = this[key];
    if ((value && value == Hash.prototype[key]) || cache.include(key)) continue;
    cache.push(key);
    var pair = [key, value];
    pair.key = key;
    pair.value = value;
    iterator(pair);
  }
};
ObjectRange = Class.create({
  initialize: function(start, end, exclusive) {
    this.start = start;
    this.end = end;
    this.exclusive = exclusive;
  },

  _each: function(iterator) {
    var value = this.start;
    while (this.include(value)) {
      iterator(value);
      value = value.succ();
    }
  }
});

Object.extend(ObjectRange.prototype, Enumerable);

ObjectRange.prototype.include = function(value) {
  if (value < this.start)
    return false;
  if (this.exclusive)
    return value < this.end;
  return value <= this.end;
};

var $R = function(start, end, exclusive) {
  return new ObjectRange(start, end, exclusive);
};

var Ajax = {
  getTransport: function() {
    return Try.these(
      function() {return new XMLHttpRequest()},
      function() {return new ActiveXObject('Msxml2.XMLHTTP')},
      function() {return new ActiveXObject('Microsoft.XMLHTTP')}
    ) || false;
  },

  activeRequestCount: 0
};

Ajax.Responders = {
  responders: [],

  _each: function(iterator) {
    this.responders._each(iterator);
  },

  register: function(responder) {
    if (!this.include(responder))
      this.responders.push(responder);
  },

  unregister: function(responder) {
    this.responders = this.responders.without(responder);
  },

  dispatch: function(callback, request, transport, json) {
    this.each(function(responder) {
      if (Object.isFunction(responder[callback])) {
        try {
          responder[callback].apply(responder, [request, transport, json]);
        } catch (e) { }
      }
    });
  }
};

Object.extend(Ajax.Responders, Enumerable);

Ajax.Responders.register({
  onCreate:   function() { Ajax.activeRequestCount++ },
  onComplete: function() { Ajax.activeRequestCount-- }
});

Ajax.Base = Class.create({
  initialize: function(options) {
    this.options = {
      method:       'post',
      asynchronous: true,
      contentType:  'application/x-www-form-urlencoded',
      encoding:     'UTF-8',
      parameters:   '',
      evalJSON:     true,
      evalJS:       true
    };
    Object.extend(this.options, options || { });

    this.options.method = this.options.method.toLowerCase();
    if (Object.isString(this.options.parameters))
      this.options.parameters = this.options.parameters.toQueryParams();
  }
});

Ajax.Request = Class.create(Ajax.Base, {
  _complete: false,

  initialize: function($super, url, options) {
    $super(options);
    this.transport = Ajax.getTransport();
    this.request(url);
  },

  request: function(url) {
    this.url = url;
    this.method = this.options.method;
    var params = Object.clone(this.options.parameters);

    if (!['get', 'post'].include(this.method)) {
      // simulate other verbs over post
      params['_method'] = this.method;
      this.method = 'post';
    }

    this.parameters = params;

    if (params = Hash.toQueryString(params)) {
      // when GET, append parameters to URL
      if (this.method == 'get')
        this.url += (this.url.include('?') ? '&' : '?') + params;
      else if (/Konqueror|Safari|KHTML/.test(navigator.userAgent))
        params += '&_=';
    }

    try {
      var response = new Ajax.Response(this);
      if (this.options.onCreate) this.options.onCreate(response);
      Ajax.Responders.dispatch('onCreate', this, response);

      this.transport.open(this.method.toUpperCase(), this.url,
        this.options.asynchronous);

      if (this.options.asynchronous) this.respondToReadyState.bind(this).defer(1);

      this.transport.onreadystatechange = this.onStateChange.bind(this);
      this.setRequestHeaders();

      this.body = this.method == 'post' ? (this.options.postBody || params) : null;
      this.transport.send(this.body);

      /* Force Firefox to handle ready state 4 for synchronous requests */
      if (!this.options.asynchronous && this.transport.overrideMimeType)
        this.onStateChange();

    }
    catch (e) {
      this.dispatchException(e);
    }
  },

  onStateChange: function() {
    var readyState = this.transport.readyState;
    if (readyState > 1 && !((readyState == 4) && this._complete))
      this.respondToReadyState(this.transport.readyState);
  },

  setRequestHeaders: function() {
    var headers = {
      'X-Requested-With': 'XMLHttpRequest',
      'X-Prototype-Version': Prototype.Version,
      'Accept': 'text/javascript, text/html, application/xml, text/xml, */*'
    };

    if (this.method == 'post') {
      headers['Content-type'] = this.options.contentType +
        (this.options.encoding ? '; charset=' + this.options.encoding : '');

      /* Force "Connection: close" for older Mozilla browsers to work
       * around a bug where XMLHttpRequest sends an incorrect
       * Content-length header. See Mozilla Bugzilla #246651.
       */
      if (this.transport.overrideMimeType &&
          (navigator.userAgent.match(/Gecko\/(\d{4})/) || [0,2005])[1] < 2005)
            headers['Connection'] = 'close';
    }

    // user-defined headers
    if (typeof this.options.requestHeaders == 'object') {
      var extras = this.options.requestHeaders;

      if (Object.isFunction(extras.push))
        for (var i = 0, length = extras.length; i < length; i += 2)
          headers[extras[i]] = extras[i+1];
      else
        $H(extras).each(function(pair) { headers[pair.key] = pair.value });
    }

    for (var name in headers)
      this.transport.setRequestHeader(name, headers[name]);
  },

  success: function() {
    var status = this.getStatus();
    return !status || (status >= 200 && status < 300);
  },

  getStatus: function() {
    try {
      return this.transport.status || 0;
    } catch (e) { return 0 }
  },

  respondToReadyState: function(readyState) {
    var state = Ajax.Request.Events[readyState], response = new Ajax.Response(this);

    if (state == 'Complete') {
      try {
        this._complete = true;
        (this.options['on' + response.status]
         || this.options['on' + (this.success() ? 'Success' : 'Failure')]
         || Prototype.emptyFunction)(response, response.headerJSON);
      } catch (e) {
        this.dispatchException(e);
      }

      var contentType = response.getHeader('Content-type');
      if (this.options.evalJS == 'force'
          || (this.options.evalJS && contentType
          && contentType.match(/^\s*(text|application)\/(x-)?(java|ecma)script(;.*)?\s*$/i)))
        this.evalResponse();
    }

    try {
      (this.options['on' + state] || Prototype.emptyFunction)(response, response.headerJSON);
      Ajax.Responders.dispatch('on' + state, this, response, response.headerJSON);
    } catch (e) {
      this.dispatchException(e);
    }

    if (state == 'Complete') {
      // avoid memory leak in MSIE: clean up
      this.transport.onreadystatechange = Prototype.emptyFunction;
    }
  },

  getHeader: function(name) {
    try {
      return this.transport.getResponseHeader(name);
    } catch (e) { return null }
  },

  evalResponse: function() {
    try {
      return eval((this.transport.responseText || '').unfilterJSON());
    } catch (e) {
      this.dispatchException(e);
    }
  },

  dispatchException: function(exception) {
    (this.options.onException || Prototype.emptyFunction)(this, exception);
    Ajax.Responders.dispatch('onException', this, exception);
  }
});

Ajax.Request.Events =
  ['Uninitialized', 'Loading', 'Loaded', 'Interactive', 'Complete'];

Ajax.Response = Class.create({
  initialize: function(request){
    this.request = request;
    var transport  = this.transport  = request.transport,
        readyState = this.readyState = transport.readyState;

    if((readyState > 2 && !Prototype.Browser.IE) || readyState == 4) {
      this.status       = this.getStatus();
      this.statusText   = this.getStatusText();
      this.responseText = String.interpret(transport.responseText);
      this.headerJSON   = this.getHeaderJSON();
    }

    if(readyState == 4) {
      var xml = transport.responseXML;
      this.responseXML  = xml === undefined ? null : xml;
      this.responseJSON = this.getResponseJSON();
    }
  },

  status:      0,
  statusText: '',

  getStatus: Ajax.Request.prototype.getStatus,

  getStatusText: function() {
    try {
      return this.transport.statusText || '';
    } catch (e) { return '' }
  },

  getHeader: Ajax.Request.prototype.getHeader,

  getAllHeaders: function() {
    try {
      return this.getAllResponseHeaders();
    } catch (e) { return null }
  },

  getResponseHeader: function(name) {
    return this.transport.getResponseHeader(name);
  },

  getAllResponseHeaders: function() {
    return this.transport.getAllResponseHeaders();
  },

  getHeaderJSON: function() {
    var json = this.getHeader('X-JSON');
    try {
      return json ? json.evalJSON(this.request.options.sanitizeJSON) : null;
    } catch (e) {
      this.request.dispatchException(e);
    }
  },

  getResponseJSON: function() {
    var options = this.request.options;
    try {
      if (options.evalJSON == 'force' || (options.evalJSON &&
          (this.getHeader('Content-type') || '').include('application/json')))
        return this.transport.responseText.evalJSON(options.sanitizeJSON);
      return null;
    } catch (e) {
      this.request.dispatchException(e);
    }
  }
});

Ajax.Updater = Class.create(Ajax.Request, {
  initialize: function($super, container, url, options) {
    this.container = {
      success: (container.success || container),
      failure: (container.failure || (container.success ? null : container))
    };

    options = options || { };
    var onComplete = options.onComplete;
    options.onComplete = (function(response, param) {
      this.updateContent(response.responseText);
      if (Object.isFunction(onComplete)) onComplete(response, param);
    }).bind(this);

    $super(url, options);
  },

  updateContent: function(responseText) {
    var receiver = this.container[this.success() ? 'success' : 'failure'],
        options = this.options;

    if (!options.evalScripts) responseText = responseText.stripScripts();

    if (receiver = $(receiver)) {
      if (options.insertion) {
        if (Object.isString(options.insertion)) {
          var insertion = { }; insertion[options.insertion] = responseText;
          receiver.insert(insertion);
        }
        else options.insertion(receiver, responseText);
      }
      else receiver.update(responseText);
    }

    if (this.success()) {
      if (this.onComplete) this.onComplete.bind(this).defer();
    }
  }
});

Ajax.PeriodicalUpdater = Class.create(Ajax.Base, {
  initialize: function($super, container, url, options) {
    $super(options);
    this.onComplete = this.options.onComplete;

    this.frequency = (this.options.frequency || 2);
    this.decay = (this.options.decay || 1);

    this.updater = { };
    this.container = container;
    this.url = url;

    this.start();
  },

  start: function() {
    this.options.onComplete = this.updateComplete.bind(this);
    this.onTimerEvent();
  },

  stop: function() {
    this.updater.options.onComplete = undefined;
    clearTimeout(this.timer);
    (this.onComplete || Prototype.emptyFunction).apply(this, arguments);
  },

  updateComplete: function(responseText) {
    if (this.options.decay) {
      this.decay = (responseText == this.lastText ?
        this.decay * this.options.decay : 1);

      this.lastText = responseText;
    }
    this.timer = this.onTimerEvent.bind(this).delay(this.decay * this.frequency);
  },

  onTimerEvent: function() {
    this.updater = new Ajax.Updater(this.container, this.url, this.options);
  }
});
function $(element) {
  if (arguments.length > 1) {
    for (var i = 0, elements = [], length = arguments.length; i < length; i++)
      elements.push($(arguments[i]));
    return elements;
  }
  if (Object.isString(element))
    element = document.getElementById(element);
  return Element.extend(element);
}

if (Prototype.BrowserFeatures.XPath) {
  document._getElementsByXPath = function(expression, parentElement) {
    var results = [];
    var query = document.evaluate(expression, $(parentElement) || document,
      null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null);
    for (var i = 0, length = query.snapshotLength; i < length; i++)
      results.push(query.snapshotItem(i));
    return results;
  };
}

/*--------------------------------------------------------------------------*/

if (!window.Node) var Node = { };

if (!Node.ELEMENT_NODE) {
  // DOM level 2 ECMAScript Language Binding
  Object.extend(Node, {
    ELEMENT_NODE: 1,
    ATTRIBUTE_NODE: 2,
    TEXT_NODE: 3,
    CDATA_SECTION_NODE: 4,
    ENTITY_REFERENCE_NODE: 5,
    ENTITY_NODE: 6,
    PROCESSING_INSTRUCTION_NODE: 7,
    COMMENT_NODE: 8,
    DOCUMENT_NODE: 9,
    DOCUMENT_TYPE_NODE: 10,
    DOCUMENT_FRAGMENT_NODE: 11,
    NOTATION_NODE: 12
  });
}

(function() {
  var element = this.Element;
  this.Element = function(tagName, attributes) {
    attributes = attributes || { };
    tagName = tagName.toLowerCase();
    var cache = Element.cache;
    if (Prototype.Browser.IE && attributes.name) {
      tagName = '<' + tagName + ' name="' + attributes.name + '">';
      delete attributes.name;
      return Element.writeAttribute(document.createElement(tagName), attributes);
    }
    if (!cache[tagName]) cache[tagName] = Element.extend(document.createElement(tagName));
    return Element.writeAttribute(cache[tagName].cloneNode(false), attributes);
  };
  Object.extend(this.Element, element || { });
}).call(window);

Element.cache = { };

Element.Methods = {
  visible: function(element) {
    return $(element).style.display != 'none';
  },

  toggle: function(element) {
    element = $(element);
    Element[Element.visible(element) ? 'hide' : 'show'](element);
    return element;
  },

  hide: function(element) {
    $(element).style.display = 'none';
    return element;
  },

  show: function(element) {
    $(element).style.display = '';
    return element;
  },

  remove: function(element) {
    element = $(element);
    element.parentNode.removeChild(element);
    return element;
  },

  update: function(element, content) {
    element = $(element);
    if (content && content.toElement) content = content.toElement();
    if (Object.isElement(content)) return element.update().insert(content);
    content = Object.toHTML(content);
    element.innerHTML = content.stripScripts();
    content.evalScripts.bind(content).defer();
    return element;
  },

  replace: function(element, content) {
    element = $(element);
    if (content && content.toElement) content = content.toElement();
    else if (!Object.isElement(content)) {
      content = Object.toHTML(content);
      var range = element.ownerDocument.createRange();
      range.selectNode(element);
      content.evalScripts.bind(content).defer();
      content = range.createContextualFragment(content.stripScripts());
    }
    element.parentNode.replaceChild(content, element);
    return element;
  },

  insert: function(element, insertions) {
    element = $(element);

    if (Object.isString(insertions) || Object.isNumber(insertions) ||
        Object.isElement(insertions) || (insertions && (insertions.toElement || insertions.toHTML)))
          insertions = {bottom:insertions};

    var content, t, range;

    for (position in insertions) {
      content  = insertions[position];
      position = position.toLowerCase();
      t = Element._insertionTranslations[position];

      if (content && content.toElement) content = content.toElement();
      if (Object.isElement(content)) {
        t.insert(element, content);
        continue;
      }

      content = Object.toHTML(content);

      range = element.ownerDocument.createRange();
      t.initializeRange(element, range);
      t.insert(element, range.createContextualFragment(content.stripScripts()));

      content.evalScripts.bind(content).defer();
    }

    return element;
  },

  wrap: function(element, wrapper, attributes) {
    element = $(element);
    if (Object.isElement(wrapper))
      $(wrapper).writeAttribute(attributes || { });
    else if (Object.isString(wrapper)) wrapper = new Element(wrapper, attributes);
    else wrapper = new Element('div', wrapper);
    if (element.parentNode)
      element.parentNode.replaceChild(wrapper, element);
    wrapper.appendChild(element);
    return element;
  },

  inspect: function(element) {
    element = $(element);
    var result = '<' + element.tagName.toLowerCase();
    $H({'id': 'id', 'className': 'class'}).each(function(pair) {
      var property = pair.first(), attribute = pair.last();
      var value = (element[property] || '').toString();
      if (value) result += ' ' + attribute + '=' + value.inspect(true);
    });
    return result + '>';
  },

  recursivelyCollect: function(element, property) {
    element = $(element);
    var elements = [];
    while (element = element[property])
      if (element.nodeType == 1)
        elements.push(Element.extend(element));
    return elements;
  },

  ancestors: function(element) {
    return $(element).recursivelyCollect('parentNode');
  },

  descendants: function(element) {
    return $A($(element).getElementsByTagName('*')).each(Element.extend);
  },

  firstDescendant: function(element) {
    element = $(element).firstChild;
    while (element && element.nodeType != 1) element = element.nextSibling;
    return $(element);
  },

  immediateDescendants: function(element) {
    if (!(element = $(element).firstChild)) return [];
    while (element && element.nodeType != 1) element = element.nextSibling;
    if (element) return [element].concat($(element).nextSiblings());
    return [];
  },

  previousSiblings: function(element) {
    return $(element).recursivelyCollect('previousSibling');
  },

  nextSiblings: function(element) {
    return $(element).recursivelyCollect('nextSibling');
  },

  siblings: function(element) {
    element = $(element);
    return element.previousSiblings().reverse().concat(element.nextSiblings());
  },

  match: function(element, selector) {
    if (Object.isString(selector))
      selector = new Selector(selector);
    return selector.match($(element));
  },

  up: function(element, expression, index) {
    element = $(element);
    if (arguments.length == 1) return $(element.parentNode);
    var ancestors = element.ancestors();
    return expression ? Selector.findElement(ancestors, expression, index) :
      ancestors[index || 0];
  },

  down: function(element, expression, index) {
    element = $(element);
    if (arguments.length == 1) return element.firstDescendant();
    var descendants = element.descendants();
    return expression ? Selector.findElement(descendants, expression, index) :
      descendants[index || 0];
  },

  previous: function(element, expression, index) {
    element = $(element);
    if (arguments.length == 1) return $(Selector.handlers.previousElementSibling(element));
    var previousSiblings = element.previousSiblings();
    return expression ? Selector.findElement(previousSiblings, expression, index) :
      previousSiblings[index || 0];
  },

  next: function(element, expression, index) {
    element = $(element);
    if (arguments.length == 1) return $(Selector.handlers.nextElementSibling(element));
    var nextSiblings = element.nextSiblings();
    return expression ? Selector.findElement(nextSiblings, expression, index) :
      nextSiblings[index || 0];
  },

  select: function() {
    var args = $A(arguments), element = $(args.shift());
    return Selector.findChildElements(element, args);
  },

  adjacent: function() {
    var args = $A(arguments), element = $(args.shift());
    return Selector.findChildElements(element.parentNode, args).without(element);
  },

  identify: function(element) {
    element = $(element);
    var id = element.readAttribute('id'), self = arguments.callee;
    if (id) return id;
    do { id = 'anonymous_element_' + self.counter++ } while ($(id));
    element.writeAttribute('id', id);
    return id;
  },

  readAttribute: function(element, name) {
    element = $(element);
    if (Prototype.Browser.IE) {
      var t = Element._attributeTranslations.read;
      if (t.values[name]) return t.values[name](element, name);
      if (t.names[name]) name = t.names[name];
      if (name.include(':')) {
        return (!element.attributes || !element.attributes[name]) ? null :
         element.attributes[name].value;
      }
    }
    return element.getAttribute(name);
  },

  writeAttribute: function(element, name, value) {
    element = $(element);
    var attributes = { }, t = Element._attributeTranslations.write;

    if (typeof name == 'object') attributes = name;
    else attributes[name] = value === undefined ? true : value;

    for (var attr in attributes) {
      var name = t.names[attr] || attr, value = attributes[attr];
      if (t.values[attr]) name = t.values[attr](element, value);
      if (value === false || value === null)
        element.removeAttribute(name);
      else if (value === true)
        element.setAttribute(name, name);
      else element.setAttribute(name, value);
    }
    return element;
  },

  getHeight: function(element) {
    return $(element).getDimensions().height;
  },

  getWidth: function(element) {
    return $(element).getDimensions().width;
  },

  classNames: function(element) {
    return new Element.ClassNames(element);
  },

  hasClassName: function(element, className) {
    if (!(element = $(element))) return;
    var elementClassName = element.className;
    return (elementClassName.length > 0 && (elementClassName == className ||
      elementClassName.match(new RegExp("(^|\\s)" + className + "(\\s|$)"))));
  },

  addClassName: function(element, className) {
    if (!(element = $(element))) return;
    if (!element.hasClassName(className))
      element.className += (element.className ? ' ' : '') + className;
    return element;
  },

  removeClassName: function(element, className) {
    if (!(element = $(element))) return;
    element.className = element.className.replace(
      new RegExp("(^|\\s+)" + className + "(\\s+|$)"), ' ').strip();
    return element;
  },

  toggleClassName: function(element, className) {
    if (!(element = $(element))) return;
    return element[element.hasClassName(className) ?
      'removeClassName' : 'addClassName'](className);
  },

  // removes whitespace-only text node children
  cleanWhitespace: function(element) {
    element = $(element);
    var node = element.firstChild;
    while (node) {
      var nextNode = node.nextSibling;
      if (node.nodeType == 3 && !/\S/.test(node.nodeValue))
        element.removeChild(node);
      node = nextNode;
    }
    return element;
  },

  empty: function(element) {
    return $(element).innerHTML.blank();
  },

  descendantOf: function(element, ancestor) {
    element = $(element), ancestor = $(ancestor);
    while (element = element.parentNode)
      if (element == ancestor) return true;
    return false;
  },

  scrollTo: function(element) {
    element = $(element);
    var pos = element.cumulativeOffset();
    window.scrollTo(pos[0], pos[1]);
    return element;
  },

  getStyle: function(element, style) {
    element = $(element);
    style = style == 'float' ? 'cssFloat' : style.camelize();
    var value = element.style[style];
    if (!value) {
      var css = document.defaultView.getComputedStyle(element, null);
      value = css ? css[style] : null;
    }
    if (style == 'opacity') return value ? parseFloat(value) : 1.0;
    return value == 'auto' ? null : value;
  },

  getOpacity: function(element) {
    return $(element).getStyle('opacity');
  },

  setStyle: function(element, styles) {
    element = $(element);
    var elementStyle = element.style, match;
    if (Object.isString(styles)) {
      element.style.cssText += ';' + styles;
      return styles.include('opacity') ?
        element.setOpacity(styles.match(/opacity:\s*(\d?\.?\d*)/)[1]) : element;
    }
    for (var property in styles)
      if (property == 'opacity') element.setOpacity(styles[property]);
      else
        elementStyle[(property == 'float' || property == 'cssFloat') ?
          (elementStyle.styleFloat === undefined ? 'cssFloat' : 'styleFloat') :
            property] = styles[property];

    return element;
  },

  setOpacity: function(element, value) {
    element = $(element);
    element.style.opacity = (value == 1 || value === '') ? '' :
      (value < 0.00001) ? 0 : value;
    return element;
  },

  getDimensions: function(element) {
    element = $(element);
    var display = $(element).getStyle('display');
    if (display != 'none' && display != null) // Safari bug
      return {width: element.offsetWidth, height: element.offsetHeight};

    // All *Width and *Height properties give 0 on elements with display none,
    // so enable the element temporarily
    var els = element.style;
    var originalVisibility = els.visibility;
    var originalPosition = els.position;
    var originalDisplay = els.display;
    els.visibility = 'hidden';
    els.position = 'absolute';
    els.display = 'block';
    var originalWidth = element.clientWidth;
    var originalHeight = element.clientHeight;
    els.display = originalDisplay;
    els.position = originalPosition;
    els.visibility = originalVisibility;
    return {width: originalWidth, height: originalHeight};
  },

  makePositioned: function(element) {
    element = $(element);
    var pos = Element.getStyle(element, 'position');
    if (pos == 'static' || !pos) {
      element._madePositioned = true;
      element.style.position = 'relative';
      // Opera returns the offset relative to the positioning context, when an
      // element is position relative but top and left have not been defined
      if (window.opera) {
        element.style.top = 0;
        element.style.left = 0;
      }
    }
    return element;
  },

  undoPositioned: function(element) {
    element = $(element);
    if (element._madePositioned) {
      element._madePositioned = undefined;
      element.style.position =
        element.style.top =
        element.style.left =
        element.style.bottom =
        element.style.right = '';
    }
    return element;
  },

  makeClipping: function(element) {
    element = $(element);
    if (element._overflow) return element;
    element._overflow = element.style.overflow || 'auto';
    if ((Element.getStyle(element, 'overflow') || 'visible') != 'hidden')
      element.style.overflow = 'hidden';
    return element;
  },

  undoClipping: function(element) {
    element = $(element);
    if (!element._overflow) return element;
    element.style.overflow = element._overflow == 'auto' ? '' : element._overflow;
    element._overflow = null;
    return element;
  },

  cumulativeOffset: function(element) {
    var valueT = 0, valueL = 0;
    do {
      valueT += element.offsetTop  || 0;
      valueL += element.offsetLeft || 0;
      element = element.offsetParent;
    } while (element);
    return Element._returnOffset(valueL, valueT);
  },

  positionedOffset: function(element) {
    var valueT = 0, valueL = 0;
    do {
      valueT += element.offsetTop  || 0;
      valueL += element.offsetLeft || 0;
      element = element.offsetParent;
      if (element) {
        if (element.tagName == 'BODY') break;
        var p = Element.getStyle(element, 'position');
        if (p == 'relative' || p == 'absolute') break;
      }
    } while (element);
    return Element._returnOffset(valueL, valueT);
  },

  absolutize: function(element) {
    element = $(element);
    if (element.getStyle('position') == 'absolute') return;
    // Position.prepare(); // To be done manually by Scripty when it needs it.

    var offsets = element.positionedOffset();
    var top     = offsets[1];
    var left    = offsets[0];
    var width   = element.clientWidth;
    var height  = element.clientHeight;

    element._originalLeft   = left - parseFloat(element.style.left  || 0);
    element._originalTop    = top  - parseFloat(element.style.top || 0);
    element._originalWidth  = element.style.width;
    element._originalHeight = element.style.height;

    element.style.position = 'absolute';
    element.style.top    = top + 'px';
    element.style.left   = left + 'px';
    element.style.width  = width + 'px';
    element.style.height = height + 'px';
    return element;
  },

  relativize: function(element) {
    element = $(element);
    if (element.getStyle('position') == 'relative') return;
    // Position.prepare(); // To be done manually by Scripty when it needs it.

    element.style.position = 'relative';
    var top  = parseFloat(element.style.top  || 0) - (element._originalTop || 0);
    var left = parseFloat(element.style.left || 0) - (element._originalLeft || 0);

    element.style.top    = top + 'px';
    element.style.left   = left + 'px';
    element.style.height = element._originalHeight;
    element.style.width  = element._originalWidth;
    return element;
  },

  cumulativeScrollOffset: function(element) {
    var valueT = 0, valueL = 0;
    do {
      valueT += element.scrollTop  || 0;
      valueL += element.scrollLeft || 0;
      element = element.parentNode;
    } while (element);
    return Element._returnOffset(valueL, valueT);
  },

  getOffsetParent: function(element) {
    if (element.offsetParent) return $(element.offsetParent);
    if (element == document.body) return $(element);

    while ((element = element.parentNode) && element != document.body)
      if (Element.getStyle(element, 'position') != 'static')
        return $(element);

    return $(document.body);
  },

  viewportOffset: function(forElement) {
    var valueT = 0, valueL = 0;

    var element = forElement;
    do {
      valueT += element.offsetTop  || 0;
      valueL += element.offsetLeft || 0;

      // Safari fix
      if (element.offsetParent == document.body &&
        Element.getStyle(element, 'position') == 'absolute') break;

    } while (element = element.offsetParent);

    element = forElement;
    do {
      if (!Prototype.Browser.Opera || element.tagName == 'BODY') {
        valueT -= element.scrollTop  || 0;
        valueL -= element.scrollLeft || 0;
      }
    } while (element = element.parentNode);

    return Element._returnOffset(valueL, valueT);
  },

  clonePosition: function(element, source) {
    var options = Object.extend({
      setLeft:    true,
      setTop:     true,
      setWidth:   true,
      setHeight:  true,
      offsetTop:  0,
      offsetLeft: 0
    }, arguments[2] || { });

    // find page position of source
    source = $(source);
    var p = source.viewportOffset();

    // find coordinate system to use
    element = $(element);
    var delta = [0, 0];
    var parent = null;
    // delta [0,0] will do fine with position: fixed elements,
    // position:absolute needs offsetParent deltas
    if (Element.getStyle(element, 'position') == 'absolute') {
      parent = element.getOffsetParent();
      delta = parent.viewportOffset();
    }

    // correct by body offsets (fixes Safari)
    if (parent == document.body) {
      delta[0] -= document.body.offsetLeft;
      delta[1] -= document.body.offsetTop;
    }

    // set position
    if (options.setLeft)   element.style.left  = (p[0] - delta[0] + options.offsetLeft) + 'px';
    if (options.setTop)    element.style.top   = (p[1] - delta[1] + options.offsetTop) + 'px';
    if (options.setWidth)  element.style.width = source.offsetWidth + 'px';
    if (options.setHeight) element.style.height = source.offsetHeight + 'px';
    return element;
  }
};

Element.Methods.identify.counter = 1;

if (!document.getElementsByClassName) document.getElementsByClassName = function(instanceMethods){
  function iter(name) {
    return name.blank() ? null : "[contains(concat(' ', @class, ' '), ' " + name + " ')]";
  }

  instanceMethods.getElementsByClassName = Prototype.BrowserFeatures.XPath ?
  function(element, className) {
    className = className.toString().strip();
    var cond = /\s/.test(className) ? $w(className).map(iter).join('') : iter(className);
    return cond ? document._getElementsByXPath('.//*' + cond, element) : [];
  } : function(element, className) {
    className = className.toString().strip();
    var elements = [], classNames = (/\s/.test(className) ? $w(className) : null);
    if (!classNames && !className) return elements;

    var nodes = $(element).getElementsByTagName('*');
    className = ' ' + className + ' ';

    for (var i = 0, child, cn; child = nodes[i]; i++) {
      if (child.className && (cn = ' ' + child.className + ' ') && (cn.include(className) ||
          (classNames && classNames.all(function(name) {
            return !name.toString().blank() && cn.include(' ' + name + ' ');
          }))))
        elements.push(Element.extend(child));
    }
    return elements;
  };

  return function(className, parentElement) {
    return $(parentElement || document.body).getElementsByClassName(className);
  };
}(Element.Methods);

Object.extend(Element.Methods, {
  getElementsBySelector: Element.Methods.select,
  childElements: Element.Methods.immediateDescendants
});

Element._attributeTranslations = {
  write: {
    names: {
      className: 'class',
      htmlFor:   'for'
    },
    values: { }
  }
};


if (!document.createRange || Prototype.Browser.Opera) {
  Element.Methods.insert = function(element, insertions) {
    element = $(element);

    if (Object.isString(insertions) || Object.isNumber(insertions) ||
        Object.isElement(insertions) || (insertions && (insertions.toElement || insertions.toHTML)))
          insertions = { bottom: insertions };

    var t = Element._insertionTranslations, content, position, pos, tagName;

    for (position in insertions) {
      content  = insertions[position];
      position = position.toLowerCase();
      pos      = t[position];

      if (content && content.toElement) content = content.toElement();
      if (Object.isElement(content)) {
        pos.insert(element, content);
        continue;
      }

      content = Object.toHTML(content);
      tagName = ((position == 'before' || position == 'after')
        ? element.parentNode : element).tagName.toUpperCase();

      if (t.tags[tagName]) {
        var fragments = Element._getContentFromAnonymousElement(tagName, content.stripScripts());
        if (position == 'top' || position == 'after') fragments.reverse();
        fragments.each(pos.insert.curry(element));
      }
      else element.insertAdjacentHTML(pos.adjacency, content.stripScripts());

      content.evalScripts.bind(content).defer();
    }

    return element;
  };
}

if (Prototype.Browser.Opera) {
  Element.Methods._getStyle = Element.Methods.getStyle;
  Element.Methods.getStyle = function(element, style) {
    switch(style) {
      case 'left':
      case 'top':
      case 'right':
      case 'bottom':
        if (Element._getStyle(element, 'position') == 'static') return null;
      default: return Element._getStyle(element, style);
    }
  };
  Element.Methods._readAttribute = Element.Methods.readAttribute;
  Element.Methods.readAttribute = function(element, attribute) {
    if (attribute == 'title') return element.title;
    return Element._readAttribute(element, attribute);
  };
}

else if (Prototype.Browser.IE) {
  $w('positionedOffset getOffsetParent viewportOffset').each(function(method) {
    Element.Methods[method] = Element.Methods[method].wrap(
      function(proceed, element) {
        element = $(element);
        var position = element.getStyle('position');
        if (position != 'static') return proceed(element);
        element.setStyle({ position: 'relative' });
        var value = proceed(element);
        element.setStyle({ position: position });
        return value;
      }
    );
  });

  Element.Methods.getStyle = function(element, style) {
    element = $(element);
    style = (style == 'float' || style == 'cssFloat') ? 'styleFloat' : style.camelize();
    var value = element.style[style];
    if (!value && element.currentStyle) value = element.currentStyle[style];

    if (style == 'opacity') {
      if (value = (element.getStyle('filter') || '').match(/alpha\(opacity=(.*)\)/))
        if (value[1]) return parseFloat(value[1]) / 100;
      return 1.0;
    }

    if (value == 'auto') {
      if ((style == 'width' || style == 'height') && (element.getStyle('display') != 'none'))
        return element['offset' + style.capitalize()] + 'px';
      return null;
    }
    return value;
  };

  Element.Methods.setOpacity = function(element, value) {
    function stripAlpha(filter){
      return filter.replace(/alpha\([^\)]*\)/gi,'');
    }
    element = $(element);
    var filter = element.getStyle('filter'), style = element.style;
    if (value == 1 || value === '') {
      (filter = stripAlpha(filter)) ?
        style.filter = filter : style.removeAttribute('filter');
      return element;
    } else if (value < 0.00001) value = 0;
    style.filter = stripAlpha(filter) +
      'alpha(opacity=' + (value * 100) + ')';
    return element;
  };

  Element._attributeTranslations = {
    read: {
      names: {
        'class': 'className',
        'for':   'htmlFor'
      },
      values: {
        _getAttr: function(element, attribute) {
          return element.getAttribute(attribute, 2);
        },
        _getAttrNode: function(element, attribute) {
          var node = element.getAttributeNode(attribute);
          return node ? node.value : "";
        },
        _getEv: function(element, attribute) {
          var attribute = element.getAttribute(attribute);
          return attribute ? attribute.toString().slice(23, -2) : null;
        },
        _flag: function(element, attribute) {
          return $(element).hasAttribute(attribute) ? attribute : null;
        },
        style: function(element) {
          return element.style.cssText.toLowerCase();
        },
        title: function(element) {
          return element.title;
        }
      }
    }
  };

  Element._attributeTranslations.write = {
    names: Object.clone(Element._attributeTranslations.read.names),
    values: {
      checked: function(element, value) {
        element.checked = !!value;
      },

      style: function(element, value) {
        element.style.cssText = value ? value : '';
      }
    }
  };

  Element._attributeTranslations.has = {};

  $w('colSpan rowSpan vAlign dateTime accessKey tabIndex ' +
      'encType maxLength readOnly longDesc').each(function(attr) {
    Element._attributeTranslations.write.names[attr.toLowerCase()] = attr;
    Element._attributeTranslations.has[attr.toLowerCase()] = attr;
  });

  (function(v) {
    Object.extend(v, {
      href:        v._getAttr,
      src:         v._getAttr,
      type:        v._getAttr,
      action:      v._getAttrNode,
      disabled:    v._flag,
      checked:     v._flag,
      readonly:    v._flag,
      multiple:    v._flag,
      onload:      v._getEv,
      onunload:    v._getEv,
      onclick:     v._getEv,
      ondblclick:  v._getEv,
      onmousedown: v._getEv,
      onmouseup:   v._getEv,
      onmouseover: v._getEv,
      onmousemove: v._getEv,
      onmouseout:  v._getEv,
      onfocus:     v._getEv,
      onblur:      v._getEv,
      onkeypress:  v._getEv,
      onkeydown:   v._getEv,
      onkeyup:     v._getEv,
      onsubmit:    v._getEv,
      onreset:     v._getEv,
      onselect:    v._getEv,
      onchange:    v._getEv
    });
  })(Element._attributeTranslations.read.values);
}

else if (Prototype.Browser.Gecko) {
  Element.Methods.setOpacity = function(element, value) {
    element = $(element);
    element.style.opacity = (value == 1) ? 0.999999 :
      (value === '') ? '' : (value < 0.00001) ? 0 : value;
    return element;
  };
}

else if (Prototype.Browser.WebKit) {
  Element.Methods.setOpacity = function(element, value) {
    element = $(element);
    element.style.opacity = (value == 1 || value === '') ? '' :
      (value < 0.00001) ? 0 : value;

    if (value == 1)
      if(element.tagName == 'IMG' && element.width) {
        element.width++; element.width--;
      } else try {
        var n = document.createTextNode(' ');
        element.appendChild(n);
        element.removeChild(n);
      } catch (e) { }

    return element;
  };

  // Safari returns margins on body which is incorrect if the child is absolutely
  // positioned.  For performance reasons, redefine Position.cumulativeOffset for
  // KHTML/WebKit only.
  Element.Methods.cumulativeOffset = function(element) {
    var valueT = 0, valueL = 0;
    do {
      valueT += element.offsetTop  || 0;
      valueL += element.offsetLeft || 0;
      if (element.offsetParent == document.body)
        if (Element.getStyle(element, 'position') == 'absolute') break;

      element = element.offsetParent;
    } while (element);

    return Element._returnOffset(valueL, valueT);
  };
}

if (Prototype.Browser.IE || Prototype.Browser.Opera) {
  // IE and Opera are missing .innerHTML support for TABLE-related and SELECT elements
  Element.Methods.update = function(element, content) {
    element = $(element);

    if (content && content.toElement) content = content.toElement();
    if (Object.isElement(content)) return element.update().insert(content);

    content = Object.toHTML(content);
    var tagName = element.tagName.toUpperCase();

    if (tagName in Element._insertionTranslations.tags) {
      $A(element.childNodes).each(function(node) { element.removeChild(node) });
      Element._getContentFromAnonymousElement(tagName, content.stripScripts())
        .each(function(node) { element.appendChild(node) });
    }
    else element.innerHTML = content.stripScripts();

    content.evalScripts.bind(content).defer();
    return element;
  };
}

if (document.createElement('div').outerHTML) {
  Element.Methods.replace = function(element, content) {
    element = $(element);

    if (content && content.toElement) content = content.toElement();
    if (Object.isElement(content)) {
      element.parentNode.replaceChild(content, element);
      return element;
    }

    content = Object.toHTML(content);
    var parent = element.parentNode, tagName = parent.tagName.toUpperCase();

    if (Element._insertionTranslations.tags[tagName]) {
      var nextSibling = element.next();
      var fragments = Element._getContentFromAnonymousElement(tagName, content.stripScripts());
      parent.removeChild(element);
      if (nextSibling)
        fragments.each(function(node) { parent.insertBefore(node, nextSibling) });
      else
        fragments.each(function(node) { parent.appendChild(node) });
    }
    else element.outerHTML = content.stripScripts();

    content.evalScripts.bind(content).defer();
    return element;
  };
}

Element._returnOffset = function(l, t) {
  var result = [l, t];
  result.left = l;
  result.top = t;
  return result;
};

Element._getContentFromAnonymousElement = function(tagName, html) {
  var div = new Element('div'), t = Element._insertionTranslations.tags[tagName];
  div.innerHTML = t[0] + html + t[1];
  t[2].times(function() { div = div.firstChild });
  return $A(div.childNodes);
};

Element._insertionTranslations = {
  before: {
    adjacency: 'beforeBegin',
    insert: function(element, node) {
      element.parentNode.insertBefore(node, element);
    },
    initializeRange: function(element, range) {
      range.setStartBefore(element);
    }
  },
  top: {
    adjacency: 'afterBegin',
    insert: function(element, node) {
      element.insertBefore(node, element.firstChild);
    },
    initializeRange: function(element, range) {
      range.selectNodeContents(element);
      range.collapse(true);
    }
  },
  bottom: {
    adjacency: 'beforeEnd',
    insert: function(element, node) {
      element.appendChild(node);
    }
  },
  after: {
    adjacency: 'afterEnd',
    insert: function(element, node) {
      element.parentNode.insertBefore(node, element.nextSibling);
    },
    initializeRange: function(element, range) {
      range.setStartAfter(element);
    }
  },
  tags: {
    TABLE:  ['<table>',                '</table>',                   1],
    TBODY:  ['<table><tbody>',         '</tbody></table>',           2],
    TR:     ['<table><tbody><tr>',     '</tr></tbody></table>',      3],
    TD:     ['<table><tbody><tr><td>', '</td></tr></tbody></table>', 4],
    SELECT: ['<select>',               '</select>',                  1]
  }
};

(function() {
  this.bottom.initializeRange = this.top.initializeRange;
  Object.extend(this.tags, {
    THEAD: this.tags.TBODY,
    TFOOT: this.tags.TBODY,
    TH:    this.tags.TD
  });
}).call(Element._insertionTranslations);

Element.Methods.Simulated = {
  hasAttribute: function(element, attribute) {
    attribute = Element._attributeTranslations.has[attribute] || attribute;
    var node = $(element).getAttributeNode(attribute);
    return node && node.specified;
  }
};

Element.Methods.ByTag = { };

Object.extend(Element, Element.Methods);

if (!Prototype.BrowserFeatures.ElementExtensions &&
    document.createElement('div').__proto__) {
  window.HTMLElement = { };
  window.HTMLElement.prototype = document.createElement('div').__proto__;
  Prototype.BrowserFeatures.ElementExtensions = true;
}

Element.extend = (function() {
  if (Prototype.BrowserFeatures.SpecificElementExtensions)
    return Prototype.K;

  var Methods = { }, ByTag = Element.Methods.ByTag;

  var extend = Object.extend(function(element) {
    if (!element || element._extendedByPrototype ||
        element.nodeType != 1 || element == window) return element;

    var methods = Object.clone(Methods),
      tagName = element.tagName, property, value;

    // extend methods for specific tags
    if (ByTag[tagName]) Object.extend(methods, ByTag[tagName]);

    for (property in methods) {
      value = methods[property];
      if (Object.isFunction(value) && !(property in element))
        element[property] = value.methodize();
    }

    element._extendedByPrototype = Prototype.emptyFunction;
    return element;

  }, {
    refresh: function() {
      // extend methods for all tags (Safari doesn't need this)
      if (!Prototype.BrowserFeatures.ElementExtensions) {
        Object.extend(Methods, Element.Methods);
        Object.extend(Methods, Element.Methods.Simulated);
      }
    }
  });

  extend.refresh();
  return extend;
})();

Element.hasAttribute = function(element, attribute) {
  if (element.hasAttribute) return element.hasAttribute(attribute);
  return Element.Methods.Simulated.hasAttribute(element, attribute);
};

Element.addMethods = function(methods) {
  var F = Prototype.BrowserFeatures, T = Element.Methods.ByTag;

  if (!methods) {
    Object.extend(Form, Form.Methods);
    Object.extend(Form.Element, Form.Element.Methods);
    Object.extend(Element.Methods.ByTag, {
      "FORM":     Object.clone(Form.Methods),
      "INPUT":    Object.clone(Form.Element.Methods),
      "SELECT":   Object.clone(Form.Element.Methods),
      "TEXTAREA": Object.clone(Form.Element.Methods)
    });
  }

  if (arguments.length == 2) {
    var tagName = methods;
    methods = arguments[1];
  }

  if (!tagName) Object.extend(Element.Methods, methods || { });
  else {
    if (Object.isArray(tagName)) tagName.each(extend);
    else extend(tagName);
  }

  function extend(tagName) {
    tagName = tagName.toUpperCase();
    if (!Element.Methods.ByTag[tagName])
      Element.Methods.ByTag[tagName] = { };
    Object.extend(Element.Methods.ByTag[tagName], methods);
  }

  function copy(methods, destination, onlyIfAbsent) {
    onlyIfAbsent = onlyIfAbsent || false;
    for (var property in methods) {
      var value = methods[property];
      if (!Object.isFunction(value)) continue;
      if (!onlyIfAbsent || !(property in destination))
        destination[property] = value.methodize();
    }
  }

  function findDOMClass(tagName) {
    var klass;
    var trans = {
      "OPTGROUP": "OptGroup", "TEXTAREA": "TextArea", "P": "Paragraph",
      "FIELDSET": "FieldSet", "UL": "UList", "OL": "OList", "DL": "DList",
      "DIR": "Directory", "H1": "Heading", "H2": "Heading", "H3": "Heading",
      "H4": "Heading", "H5": "Heading", "H6": "Heading", "Q": "Quote",
      "INS": "Mod", "DEL": "Mod", "A": "Anchor", "IMG": "Image", "CAPTION":
      "TableCaption", "COL": "TableCol", "COLGROUP": "TableCol", "THEAD":
      "TableSection", "TFOOT": "TableSection", "TBODY": "TableSection", "TR":
      "TableRow", "TH": "TableCell", "TD": "TableCell", "FRAMESET":
      "FrameSet", "IFRAME": "IFrame"
    };
    if (trans[tagName]) klass = 'HTML' + trans[tagName] + 'Element';
    if (window[klass]) return window[klass];
    klass = 'HTML' + tagName + 'Element';
    if (window[klass]) return window[klass];
    klass = 'HTML' + tagName.capitalize() + 'Element';
    if (window[klass]) return window[klass];

    window[klass] = { };
    window[klass].prototype = document.createElement(tagName).__proto__;
    return window[klass];
  }

  if (F.ElementExtensions) {
    copy(Element.Methods, HTMLElement.prototype);
    copy(Element.Methods.Simulated, HTMLElement.prototype, true);
  }

  if (F.SpecificElementExtensions) {
    for (var tag in Element.Methods.ByTag) {
      var klass = findDOMClass(tag);
      if (Object.isUndefined(klass)) continue;
      copy(T[tag], klass.prototype);
    }
  }

  Object.extend(Element, Element.Methods);
  delete Element.ByTag;

  if (Element.extend.refresh) Element.extend.refresh();
  Element.cache = { };
};

document.viewport = {
  getDimensions: function() {
    var dimensions = { };
    $w('width height').each(function(d) {
      var D = d.capitalize();
      dimensions[d] = self['inner' + D] ||
       (document.documentElement['client' + D] || document.body['client' + D]);
    });
    return dimensions;
  },

  getWidth: function() {
    return this.getDimensions().width;
  },

  getHeight: function() {
    return this.getDimensions().height;
  },

  getScrollOffsets: function() {
    return Element._returnOffset(
      window.pageXOffset || document.documentElement.scrollLeft || document.body.scrollLeft,
      window.pageYOffset || document.documentElement.scrollTop || document.body.scrollTop);
  }
};
/* Portions of the Selector class are derived from Jack Slocum’s DomQuery,
 * part of YUI-Ext version 0.40, distributed under the terms of an MIT-style
 * license.  Please see http://www.yui-ext.com/ for more information. */

var Selector = Class.create({
  initialize: function(expression) {
    this.expression = expression.strip();
    this.compileMatcher();
  },

  compileMatcher: function() {
    // Selectors with namespaced attributes can't use the XPath version
    if (Prototype.BrowserFeatures.XPath && !(/\[[\w-]*?:/).test(this.expression))
      return this.compileXPathMatcher();

    var e = this.expression, ps = Selector.patterns, h = Selector.handlers,
        c = Selector.criteria, le, p, m;

    if (Selector._cache[e]) {
      this.matcher = Selector._cache[e];
      return;
    }

    this.matcher = ["this.matcher = function(root) {",
                    "var r = root, h = Selector.handlers, c = false, n;"];

    while (e && le != e && (/\S/).test(e)) {
      le = e;
      for (var i in ps) {
        p = ps[i];
        if (m = e.match(p)) {
          this.matcher.push(Object.isFunction(c[i]) ? c[i](m) :
    	      new Template(c[i]).evaluate(m));
          e = e.replace(m[0], '');
          break;
        }
      }
    }

    this.matcher.push("return h.unique(n);\n}");
    eval(this.matcher.join('\n'));
    Selector._cache[this.expression] = this.matcher;
  },

  compileXPathMatcher: function() {
    var e = this.expression, ps = Selector.patterns,
        x = Selector.xpath, le, m;

    if (Selector._cache[e]) {
      this.xpath = Selector._cache[e]; return;
    }

    this.matcher = ['.//*'];
    while (e && le != e && (/\S/).test(e)) {
      le = e;
      for (var i in ps) {
        if (m = e.match(ps[i])) {
          this.matcher.push(Object.isFunction(x[i]) ? x[i](m) :
            new Template(x[i]).evaluate(m));
          e = e.replace(m[0], '');
          break;
        }
      }
    }

    this.xpath = this.matcher.join('');
    Selector._cache[this.expression] = this.xpath;
  },

  findElements: function(root) {
    root = root || document;
    if (this.xpath) return document._getElementsByXPath(this.xpath, root);
    return this.matcher(root);
  },

  match: function(element) {
    this.tokens = [];

    var e = this.expression, ps = Selector.patterns, as = Selector.assertions;
    var le, p, m;

    while (e && le !== e && (/\S/).test(e)) {
      le = e;
      for (var i in ps) {
        p = ps[i];
        if (m = e.match(p)) {
          // use the Selector.assertions methods unless the selector
          // is too complex.
          if (as[i]) {
            this.tokens.push([i, Object.clone(m)]);
            e = e.replace(m[0], '');
          } else {
            // reluctantly do a document-wide search
            // and look for a match in the array
            return this.findElements(document).include(element);
          }
        }
      }
    }

    var match = true, name, matches;
    for (var i = 0, token; token = this.tokens[i]; i++) {
      name = token[0], matches = token[1];
      if (!Selector.assertions[name](element, matches)) {
        match = false; break;
      }
    }

    return match;
  },

  toString: function() {
    return this.expression;
  },

  inspect: function() {
    return "#<Selector:" + this.expression.inspect() + ">";
  }
});

Object.extend(Selector, {
  _cache: { },

  xpath: {
    descendant:   "//*",
    child:        "/*",
    adjacent:     "/following-sibling::*[1]",
    laterSibling: '/following-sibling::*',
    tagName:      function(m) {
      if (m[1] == '*') return '';
      return "[local-name()='" + m[1].toLowerCase() +
             "' or local-name()='" + m[1].toUpperCase() + "']";
    },
    className:    "[contains(concat(' ', @class, ' '), ' #{1} ')]",
    id:           "[@id='#{1}']",
    attrPresence: "[@#{1}]",
    attr: function(m) {
      m[3] = m[5] || m[6];
      return new Template(Selector.xpath.operators[m[2]]).evaluate(m);
    },
    pseudo: function(m) {
      var h = Selector.xpath.pseudos[m[1]];
      if (!h) return '';
      if (Object.isFunction(h)) return h(m);
      return new Template(Selector.xpath.pseudos[m[1]]).evaluate(m);
    },
    operators: {
      '=':  "[@#{1}='#{3}']",
      '!=': "[@#{1}!='#{3}']",
      '^=': "[starts-with(@#{1}, '#{3}')]",
      '$=': "[substring(@#{1}, (string-length(@#{1}) - string-length('#{3}') + 1))='#{3}']",
      '*=': "[contains(@#{1}, '#{3}')]",
      '~=': "[contains(concat(' ', @#{1}, ' '), ' #{3} ')]",
      '|=': "[contains(concat('-', @#{1}, '-'), '-#{3}-')]"
    },
    pseudos: {
      'first-child': '[not(preceding-sibling::*)]',
      'last-child':  '[not(following-sibling::*)]',
      'only-child':  '[not(preceding-sibling::* or following-sibling::*)]',
      'empty':       "[count(*) = 0 and (count(text()) = 0 or translate(text(), ' \t\r\n', '') = '')]",
      'checked':     "[@checked]",
      'disabled':    "[@disabled]",
      'enabled':     "[not(@disabled)]",
      'not': function(m) {
        var e = m[6], p = Selector.patterns,
            x = Selector.xpath, le, m, v;

        var exclusion = [];
        while (e && le != e && (/\S/).test(e)) {
          le = e;
          for (var i in p) {
            if (m = e.match(p[i])) {
              v = Object.isFunction(x[i]) ? x[i](m) : new Template(x[i]).evaluate(m);
              exclusion.push("(" + v.substring(1, v.length - 1) + ")");
              e = e.replace(m[0], '');
              break;
            }
          }
        }
        return "[not(" + exclusion.join(" and ") + ")]";
      },
      'nth-child':      function(m) {
        return Selector.xpath.pseudos.nth("(count(./preceding-sibling::*) + 1) ", m);
      },
      'nth-last-child': function(m) {
        return Selector.xpath.pseudos.nth("(count(./following-sibling::*) + 1) ", m);
      },
      'nth-of-type':    function(m) {
        return Selector.xpath.pseudos.nth("position() ", m);
      },
      'nth-last-of-type': function(m) {
        return Selector.xpath.pseudos.nth("(last() + 1 - position()) ", m);
      },
      'first-of-type':  function(m) {
        m[6] = "1"; return Selector.xpath.pseudos['nth-of-type'](m);
      },
      'last-of-type':   function(m) {
        m[6] = "1"; return Selector.xpath.pseudos['nth-last-of-type'](m);
      },
      'only-of-type':   function(m) {
        var p = Selector.xpath.pseudos; return p['first-of-type'](m) + p['last-of-type'](m);
      },
      nth: function(fragment, m) {
        var mm, formula = m[6], predicate;
        if (formula == 'even') formula = '2n+0';
        if (formula == 'odd')  formula = '2n+1';
        if (mm = formula.match(/^(\d+)$/)) // digit only
          return '[' + fragment + "= " + mm[1] + ']';
        if (mm = formula.match(/^(-?\d*)?n(([+-])(\d+))?/)) { // an+b
          if (mm[1] == "-") mm[1] = -1;
          var a = mm[1] ? Number(mm[1]) : 1;
          var b = mm[2] ? Number(mm[2]) : 0;
          predicate = "[((#{fragment} - #{b}) mod #{a} = 0) and " +
          "((#{fragment} - #{b}) div #{a} >= 0)]";
          return new Template(predicate).evaluate({
            fragment: fragment, a: a, b: b });
        }
      }
    }
  },

  criteria: {
    tagName:      'n = h.tagName(n, r, "#{1}", c);   c = false;',
    className:    'n = h.className(n, r, "#{1}", c); c = false;',
    id:           'n = h.id(n, r, "#{1}", c);        c = false;',
    attrPresence: 'n = h.attrPresence(n, r, "#{1}"); c = false;',
    attr: function(m) {
      m[3] = (m[5] || m[6]);
      return new Template('n = h.attr(n, r, "#{1}", "#{3}", "#{2}"); c = false;').evaluate(m);
    },
    pseudo: function(m) {
      if (m[6]) m[6] = m[6].replace(/"/g, '\\"');
      return new Template('n = h.pseudo(n, "#{1}", "#{6}", r, c); c = false;').evaluate(m);
    },
    descendant:   'c = "descendant";',
    child:        'c = "child";',
    adjacent:     'c = "adjacent";',
    laterSibling: 'c = "laterSibling";'
  },

  patterns: {
    // combinators must be listed first
    // (and descendant needs to be last combinator)
    laterSibling: /^\s*~\s*/,
    child:        /^\s*>\s*/,
    adjacent:     /^\s*\+\s*/,
    descendant:   /^\s/,

    // selectors follow
    tagName:      /^\s*(\*|[\w\-]+)(\b|$)?/,
    id:           /^#([\w\-\*]+)(\b|$)/,
    className:    /^\.([\w\-\*]+)(\b|$)/,
    pseudo:       /^:((first|last|nth|nth-last|only)(-child|-of-type)|empty|checked|(en|dis)abled|not)(\((.*?)\))?(\b|$|\s|(?=:))/,
    attrPresence: /^\[([\w]+)\]/,
    attr:         /\[((?:[\w-]*:)?[\w-]+)\s*(?:([!^$*~|]?=)\s*((['"])([^\4]*?)\4|([^'"][^\]]*?)))?\]/
  },

  // for Selector.match and Element#match
  assertions: {
    tagName: function(element, matches) {
      return matches[1].toUpperCase() == element.tagName.toUpperCase();
    },

    className: function(element, matches) {
      return Element.hasClassName(element, matches[1]);
    },

    id: function(element, matches) {
      return element.id === matches[1];
    },

    attrPresence: function(element, matches) {
      return Element.hasAttribute(element, matches[1]);
    },

    attr: function(element, matches) {
      var nodeValue = Element.readAttribute(element, matches[1]);
      return Selector.operators[matches[2]](nodeValue, matches[3]);
    }
  },

  handlers: {
    // UTILITY FUNCTIONS
    // joins two collections
    concat: function(a, b) {
      for (var i = 0, node; node = b[i]; i++)
        a.push(node);
      return a;
    },

    // marks an array of nodes for counting
    mark: function(nodes) {
      for (var i = 0, node; node = nodes[i]; i++)
        node._counted = true;
      return nodes;
    },

    unmark: function(nodes) {
      for (var i = 0, node; node = nodes[i]; i++)
        node._counted = undefined;
      return nodes;
    },

    // mark each child node with its position (for nth calls)
    // "ofType" flag indicates whether we're indexing for nth-of-type
    // rather than nth-child
    index: function(parentNode, reverse, ofType) {
      parentNode._counted = true;
      if (reverse) {
        for (var nodes = parentNode.childNodes, i = nodes.length - 1, j = 1; i >= 0; i--) {
          node = nodes[i];
          if (node.nodeType == 1 && (!ofType || node._counted)) node.nodeIndex = j++;
        }
      } else {
        for (var i = 0, j = 1, nodes = parentNode.childNodes; node = nodes[i]; i++)
          if (node.nodeType == 1 && (!ofType || node._counted)) node.nodeIndex = j++;
      }
    },

    // filters out duplicates and extends all nodes
    unique: function(nodes) {
      if (nodes.length == 0) return nodes;
      var results = [], n;
      for (var i = 0, l = nodes.length; i < l; i++)
        if (!(n = nodes[i])._counted) {
          n._counted = true;
          results.push(Element.extend(n));
        }
      return Selector.handlers.unmark(results);
    },

    // COMBINATOR FUNCTIONS
    descendant: function(nodes) {
      var h = Selector.handlers;
      for (var i = 0, results = [], node; node = nodes[i]; i++)
        h.concat(results, node.getElementsByTagName('*'));
      return results;
    },

    child: function(nodes) {
      var h = Selector.handlers;
      for (var i = 0, results = [], node; node = nodes[i]; i++) {
        for (var j = 0, children = [], child; child = node.childNodes[j]; j++)
          if (child.nodeType == 1 && child.tagName != '!') results.push(child);
      }
      return results;
    },

    adjacent: function(nodes) {
      for (var i = 0, results = [], node; node = nodes[i]; i++) {
        var next = this.nextElementSibling(node);
        if (next) results.push(next);
      }
      return results;
    },

    laterSibling: function(nodes) {
      var h = Selector.handlers;
      for (var i = 0, results = [], node; node = nodes[i]; i++)
        h.concat(results, Element.nextSiblings(node));
      return results;
    },

    nextElementSibling: function(node) {
      while (node = node.nextSibling)
	      if (node.nodeType == 1) return node;
      return null;
    },

    previousElementSibling: function(node) {
      while (node = node.previousSibling)
        if (node.nodeType == 1) return node;
      return null;
    },

    // TOKEN FUNCTIONS
    tagName: function(nodes, root, tagName, combinator) {
      tagName = tagName.toUpperCase();
      var results = [], h = Selector.handlers;
      if (nodes) {
        if (combinator) {
          // fastlane for ordinary descendant combinators
          if (combinator == "descendant") {
            for (var i = 0, node; node = nodes[i]; i++)
              h.concat(results, node.getElementsByTagName(tagName));
            return results;
          } else nodes = this[combinator](nodes);
          if (tagName == "*") return nodes;
        }
        for (var i = 0, node; node = nodes[i]; i++)
          if (node.tagName.toUpperCase() == tagName) results.push(node);
        return results;
      } else return root.getElementsByTagName(tagName);
    },

    id: function(nodes, root, id, combinator) {
      var targetNode = $(id), h = Selector.handlers;
      if (!targetNode) return [];
      if (!nodes && root == document) return [targetNode];
      if (nodes) {
        if (combinator) {
          if (combinator == 'child') {
            for (var i = 0, node; node = nodes[i]; i++)
              if (targetNode.parentNode == node) return [targetNode];
          } else if (combinator == 'descendant') {
            for (var i = 0, node; node = nodes[i]; i++)
              if (Element.descendantOf(targetNode, node)) return [targetNode];
          } else if (combinator == 'adjacent') {
            for (var i = 0, node; node = nodes[i]; i++)
              if (Selector.handlers.previousElementSibling(targetNode) == node)
                return [targetNode];
          } else nodes = h[combinator](nodes);
        }
        for (var i = 0, node; node = nodes[i]; i++)
          if (node == targetNode) return [targetNode];
        return [];
      }
      return (targetNode && Element.descendantOf(targetNode, root)) ? [targetNode] : [];
    },

    className: function(nodes, root, className, combinator) {
      if (nodes && combinator) nodes = this[combinator](nodes);
      return Selector.handlers.byClassName(nodes, root, className);
    },

    byClassName: function(nodes, root, className) {
      if (!nodes) nodes = Selector.handlers.descendant([root]);
      var needle = ' ' + className + ' ';
      for (var i = 0, results = [], node, nodeClassName; node = nodes[i]; i++) {
        nodeClassName = node.className;
        if (nodeClassName.length == 0) continue;
        if (nodeClassName == className || (' ' + nodeClassName + ' ').include(needle))
          results.push(node);
      }
      return results;
    },

    attrPresence: function(nodes, root, attr) {
      var results = [];
      for (var i = 0, node; node = nodes[i]; i++)
        if (Element.hasAttribute(node, attr)) results.push(node);
      return results;
    },

    attr: function(nodes, root, attr, value, operator) {
      if (!nodes) nodes = root.getElementsByTagName("*");
      var handler = Selector.operators[operator], results = [];
      for (var i = 0, node; node = nodes[i]; i++) {
        var nodeValue = Element.readAttribute(node, attr);
        if (nodeValue === null) continue;
        if (handler(nodeValue, value)) results.push(node);
      }
      return results;
    },

    pseudo: function(nodes, name, value, root, combinator) {
      if (nodes && combinator) nodes = this[combinator](nodes);
      if (!nodes) nodes = root.getElementsByTagName("*");
      return Selector.pseudos[name](nodes, value, root);
    }
  },

  pseudos: {
    'first-child': function(nodes, value, root) {
      for (var i = 0, results = [], node; node = nodes[i]; i++) {
        if (Selector.handlers.previousElementSibling(node)) continue;
          results.push(node);
      }
      return results;
    },
    'last-child': function(nodes, value, root) {
      for (var i = 0, results = [], node; node = nodes[i]; i++) {
        if (Selector.handlers.nextElementSibling(node)) continue;
          results.push(node);
      }
      return results;
    },
    'only-child': function(nodes, value, root) {
      var h = Selector.handlers;
      for (var i = 0, results = [], node; node = nodes[i]; i++)
        if (!h.previousElementSibling(node) && !h.nextElementSibling(node))
          results.push(node);
      return results;
    },
    'nth-child':        function(nodes, formula, root) {
      return Selector.pseudos.nth(nodes, formula, root);
    },
    'nth-last-child':   function(nodes, formula, root) {
      return Selector.pseudos.nth(nodes, formula, root, true);
    },
    'nth-of-type':      function(nodes, formula, root) {
      return Selector.pseudos.nth(nodes, formula, root, false, true);
    },
    'nth-last-of-type': function(nodes, formula, root) {
      return Selector.pseudos.nth(nodes, formula, root, true, true);
    },
    'first-of-type':    function(nodes, formula, root) {
      return Selector.pseudos.nth(nodes, "1", root, false, true);
    },
    'last-of-type':     function(nodes, formula, root) {
      return Selector.pseudos.nth(nodes, "1", root, true, true);
    },
    'only-of-type':     function(nodes, formula, root) {
      var p = Selector.pseudos;
      return p['last-of-type'](p['first-of-type'](nodes, formula, root), formula, root);
    },

    // handles the an+b logic
    getIndices: function(a, b, total) {
      if (a == 0) return b > 0 ? [b] : [];
      return $R(1, total).inject([], function(memo, i) {
        if (0 == (i - b) % a && (i - b) / a >= 0) memo.push(i);
        return memo;
      });
    },

    // handles nth(-last)-child, nth(-last)-of-type, and (first|last)-of-type
    nth: function(nodes, formula, root, reverse, ofType) {
      if (nodes.length == 0) return [];
      if (formula == 'even') formula = '2n+0';
      if (formula == 'odd')  formula = '2n+1';
      var h = Selector.handlers, results = [], indexed = [], m;
      h.mark(nodes);
      for (var i = 0, node; node = nodes[i]; i++) {
        if (!node.parentNode._counted) {
          h.index(node.parentNode, reverse, ofType);
          indexed.push(node.parentNode);
        }
      }
      if (formula.match(/^\d+$/)) { // just a number
        formula = Number(formula);
        for (var i = 0, node; node = nodes[i]; i++)
          if (node.nodeIndex == formula) results.push(node);
      } else if (m = formula.match(/^(-?\d*)?n(([+-])(\d+))?/)) { // an+b
        if (m[1] == "-") m[1] = -1;
        var a = m[1] ? Number(m[1]) : 1;
        var b = m[2] ? Number(m[2]) : 0;
        var indices = Selector.pseudos.getIndices(a, b, nodes.length);
        for (var i = 0, node, l = indices.length; node = nodes[i]; i++) {
          for (var j = 0; j < l; j++)
            if (node.nodeIndex == indices[j]) results.push(node);
        }
      }
      h.unmark(nodes);
      h.unmark(indexed);
      return results;
    },

    'empty': function(nodes, value, root) {
      for (var i = 0, results = [], node; node = nodes[i]; i++) {
        // IE treats comments as element nodes
        if (node.tagName == '!' || (node.firstChild && !node.innerHTML.match(/^\s*$/))) continue;
        results.push(node);
      }
      return results;
    },

    'not': function(nodes, selector, root) {
      var h = Selector.handlers, selectorType, m;
      var exclusions = new Selector(selector).findElements(root);
      h.mark(exclusions);
      for (var i = 0, results = [], node; node = nodes[i]; i++)
        if (!node._counted) results.push(node);
      h.unmark(exclusions);
      return results;
    },

    'enabled': function(nodes, value, root) {
      for (var i = 0, results = [], node; node = nodes[i]; i++)
        if (!node.disabled) results.push(node);
      return results;
    },

    'disabled': function(nodes, value, root) {
      for (var i = 0, results = [], node; node = nodes[i]; i++)
        if (node.disabled) results.push(node);
      return results;
    },

    'checked': function(nodes, value, root) {
      for (var i = 0, results = [], node; node = nodes[i]; i++)
        if (node.checked) results.push(node);
      return results;
    }
  },

  operators: {
    '=':  function(nv, v) { return nv == v; },
    '!=': function(nv, v) { return nv != v; },
    '^=': function(nv, v) { return nv.startsWith(v); },
    '$=': function(nv, v) { return nv.endsWith(v); },
    '*=': function(nv, v) { return nv.include(v); },
    '~=': function(nv, v) { return (' ' + nv + ' ').include(' ' + v + ' '); },
    '|=': function(nv, v) { return ('-' + nv.toUpperCase() + '-').include('-' + v.toUpperCase() + '-'); }
  },

  matchElements: function(elements, expression) {
    var matches = new Selector(expression).findElements(), h = Selector.handlers;
    h.mark(matches);
    for (var i = 0, results = [], element; element = elements[i]; i++)
      if (element._counted) results.push(element);
    h.unmark(matches);
    return results;
  },

  findElement: function(elements, expression, index) {
    if (Object.isNumber(expression)) {
      index = expression; expression = false;
    }
    return Selector.matchElements(elements, expression || '*')[index || 0];
  },

  findChildElements: function(element, expressions) {
    var exprs = expressions.join(','), expressions = [];
    exprs.scan(/(([\w#:.~>+()\s-]+|\*|\[.*?\])+)\s*(,|$)/, function(m) {
      expressions.push(m[1].strip());
    });
    var results = [], h = Selector.handlers;
    for (var i = 0, l = expressions.length, selector; i < l; i++) {
      selector = new Selector(expressions[i].strip());
      h.concat(results, selector.findElements(element));
    }
    return (l > 1) ? h.unique(results) : results;
  }
});

function $$() {
  return Selector.findChildElements(document, $A(arguments));
}
var Form = {
  reset: function(form) {
    $(form).reset();
    return form;
  },

  serializeElements: function(elements, options) {
    if (typeof options != 'object') options = { hash: !!options };
    else if (options.hash === undefined) options.hash = true;
    var key, value, submitted = false, submit = options.submit;

    var data = elements.inject({ }, function(result, element) {
      if (!element.disabled && element.name) {
        key = element.name; value = $(element).getValue();
        if (value != null && (element.type != 'submit' || (!submitted &&
            submit !== false && (!submit || key == submit) && (submitted = true)))) {
          if (key in result) {
            // a key is already present; construct an array of values
            if (!Object.isArray(result[key])) result[key] = [result[key]];
            result[key].push(value);
          }
          else result[key] = value;
        }
      }
      return result;
    });

    return options.hash ? data : Hash.toQueryString(data);
  }
};

Form.Methods = {
  serialize: function(form, options) {
    return Form.serializeElements(Form.getElements(form), options);
  },

  getElements: function(form) {
    return $A($(form).getElementsByTagName('*')).inject([],
      function(elements, child) {
        if (Form.Element.Serializers[child.tagName.toLowerCase()])
          elements.push(Element.extend(child));
        return elements;
      }
    );
  },

  getInputs: function(form, typeName, name) {
    form = $(form);
    var inputs = form.getElementsByTagName('input');

    if (!typeName && !name) return $A(inputs).map(Element.extend);

    for (var i = 0, matchingInputs = [], length = inputs.length; i < length; i++) {
      var input = inputs[i];
      if ((typeName && input.type != typeName) || (name && input.name != name))
        continue;
      matchingInputs.push(Element.extend(input));
    }

    return matchingInputs;
  },

  disable: function(form) {
    form = $(form);
    Form.getElements(form).invoke('disable');
    return form;
  },

  enable: function(form) {
    form = $(form);
    Form.getElements(form).invoke('enable');
    return form;
  },

  findFirstElement: function(form) {
    var elements = $(form).getElements().findAll(function(element) {
      return 'hidden' != element.type && !element.disabled;
    });
    var firstByIndex = elements.findAll(function(element) {
      return element.hasAttribute('tabIndex') && element.tabIndex >= 0;
    }).sortBy(function(element) { return element.tabIndex }).first();

    return firstByIndex ? firstByIndex : elements.find(function(element) {
      return ['input', 'select', 'textarea'].include(element.tagName.toLowerCase());
    });
  },

  focusFirstElement: function(form) {
    form = $(form);
    form.findFirstElement().activate();
    return form;
  },

  request: function(form, options) {
    form = $(form), options = Object.clone(options || { });

    var params = options.parameters, action = form.readAttribute('action') || '';
    if (action.blank()) action = window.location.href;
    options.parameters = form.serialize(true);

    if (params) {
      if (Object.isString(params)) params = params.toQueryParams();
      Object.extend(options.parameters, params);
    }

    if (form.hasAttribute('method') && !options.method)
      options.method = form.method;

    return new Ajax.Request(action, options);
  }
};

/*--------------------------------------------------------------------------*/

Form.Element = {
  focus: function(element) {
    $(element).focus();
    return element;
  },

  select: function(element) {
    $(element).select();
    return element;
  }
};

Form.Element.Methods = {
  serialize: function(element) {
    element = $(element);
    if (!element.disabled && element.name) {
      var value = element.getValue();
      if (value != undefined) {
        var pair = { };
        pair[element.name] = value;
        return Hash.toQueryString(pair);
      }
    }
    return '';
  },

  getValue: function(element) {
    element = $(element);
    var method = element.tagName.toLowerCase();
    return Form.Element.Serializers[method](element);
  },

  setValue: function(element, value) {
    element = $(element);
    var method = element.tagName.toLowerCase();
    Form.Element.Serializers[method](element, value);
    return element;
  },

  clear: function(element) {
    $(element).value = '';
    return element;
  },

  present: function(element) {
    return $(element).value != '';
  },

  activate: function(element) {
    element = $(element);
    try {
      element.focus();
      if (element.select && (element.tagName.toLowerCase() != 'input' ||
          !['button', 'reset', 'submit'].include(element.type)))
        element.select();
    } catch (e) { }
    return element;
  },

  disable: function(element) {
    element = $(element);
    element.blur();
    element.disabled = true;
    return element;
  },

  enable: function(element) {
    element = $(element);
    element.disabled = false;
    return element;
  }
};

/*--------------------------------------------------------------------------*/

var Field = Form.Element;
var $F = Form.Element.Methods.getValue;

/*--------------------------------------------------------------------------*/

Form.Element.Serializers = {
  input: function(element, value) {
    switch (element.type.toLowerCase()) {
      case 'checkbox':
      case 'radio':
        return Form.Element.Serializers.inputSelector(element, value);
      default:
        return Form.Element.Serializers.textarea(element, value);
    }
  },

  inputSelector: function(element, value) {
    if (value === undefined) return element.checked ? element.value : null;
    else element.checked = !!value;
  },

  textarea: function(element, value) {
    if (value === undefined) return element.value;
    else element.value = value;
  },

  select: function(element, index) {
    if (index === undefined)
      return this[element.type == 'select-one' ?
        'selectOne' : 'selectMany'](element);
    else {
      var opt, value, single = !Object.isArray(index);
      for (var i = 0, length = element.length; i < length; i++) {
        opt = element.options[i];
        value = this.optionValue(opt);
        if (single) {
          if (value == index) {
            opt.selected = true;
            return;
          }
        }
        else opt.selected = index.include(value);
      }
    }
  },

  selectOne: function(element) {
    var index = element.selectedIndex;
    return index >= 0 ? this.optionValue(element.options[index]) : null;
  },

  selectMany: function(element) {
    var values, length = element.length;
    if (!length) return null;

    for (var i = 0, values = []; i < length; i++) {
      var opt = element.options[i];
      if (opt.selected) values.push(this.optionValue(opt));
    }
    return values;
  },

  optionValue: function(opt) {
    // extend element because hasAttribute may not be native
    return Element.extend(opt).hasAttribute('value') ? opt.value : opt.text;
  }
};

/*--------------------------------------------------------------------------*/

Abstract.TimedObserver = Class.create(PeriodicalExecuter, {
  initialize: function($super, element, frequency, callback) {
    $super(callback, frequency);
    this.element   = $(element);
    this.lastValue = this.getValue();
  },

  execute: function() {
    var value = this.getValue();
    if (Object.isString(this.lastValue) && Object.isString(value) ?
        this.lastValue != value : String(this.lastValue) != String(value)) {
      this.callback(this.element, value);
      this.lastValue = value;
    }
  }
});

Form.Element.Observer = Class.create(Abstract.TimedObserver, {
  getValue: function() {
    return Form.Element.getValue(this.element);
  }
});

Form.Observer = Class.create(Abstract.TimedObserver, {
  getValue: function() {
    return Form.serialize(this.element);
  }
});

/*--------------------------------------------------------------------------*/

Abstract.EventObserver = Class.create({
  initialize: function(element, callback) {
    this.element  = $(element);
    this.callback = callback;

    this.lastValue = this.getValue();
    if (this.element.tagName.toLowerCase() == 'form')
      this.registerFormCallbacks();
    else
      this.registerCallback(this.element);
  },

  onElementEvent: function() {
    var value = this.getValue();
    if (this.lastValue != value) {
      this.callback(this.element, value);
      this.lastValue = value;
    }
  },

  registerFormCallbacks: function() {
    Form.getElements(this.element).each(this.registerCallback, this);
  },

  registerCallback: function(element) {
    if (element.type) {
      switch (element.type.toLowerCase()) {
        case 'checkbox':
        case 'radio':
          Event.observe(element, 'click', this.onElementEvent.bind(this));
          break;
        default:
          Event.observe(element, 'change', this.onElementEvent.bind(this));
          break;
      }
    }
  }
});

Form.Element.EventObserver = Class.create(Abstract.EventObserver, {
  getValue: function() {
    return Form.Element.getValue(this.element);
  }
});

Form.EventObserver = Class.create(Abstract.EventObserver, {
  getValue: function() {
    return Form.serialize(this.element);
  }
});
if (!window.Event) var Event = { };

Object.extend(Event, {
  KEY_BACKSPACE: 8,
  KEY_TAB:       9,
  KEY_RETURN:   13,
  KEY_ESC:      27,
  KEY_LEFT:     37,
  KEY_UP:       38,
  KEY_RIGHT:    39,
  KEY_DOWN:     40,
  KEY_DELETE:   46,
  KEY_HOME:     36,
  KEY_END:      35,
  KEY_PAGEUP:   33,
  KEY_PAGEDOWN: 34,
  KEY_INSERT:   45,

  DOMEvents: ['click', 'dblclick', 'mousedown', 'mouseup', 'mouseover',
              'mousemove', 'mouseout', 'keypress', 'keydown', 'keyup',
              'load', 'unload', 'abort', 'error', 'resize', 'scroll',
              'select', 'change', 'submit', 'reset', 'focus', 'blur',
              'contextmenu'],

  cache: { },

  relatedTarget: function(event) {
    var element;
    switch(event.type) {
      case 'mouseover': element = event.fromElement; break;
      case 'mouseout':  element = event.toElement;   break;
      default: return null;
    }
    return Element.extend(element);
  }
});

Event.Methods = {
  element: function(event) {
    var node = event.target;
    return Element.extend(node.nodeType == Node.TEXT_NODE ? node.parentNode : node);
  },

  findElement: function(event, expression) {
    var element = Event.element(event);
    return element.match(expression) ? element : element.up(expression);
  },

  isLeftClick: function(event) {
    return (((event.which) && (event.which == 1)) ||
            ((event.button) && (event.button == 1)));
  },

  pointer: function(event) {
    return {
      x: event.pageX || (event.clientX +
        (document.documentElement.scrollLeft || document.body.scrollLeft)),
      y: event.pageY || (event.clientY +
        (document.documentElement.scrollTop || document.body.scrollTop))
    };
  },

  pointerX: function(event) { return Event.pointer(event).x },
  pointerY: function(event) { return Event.pointer(event).y },

  stop: function(event) {
    event.preventDefault();
    event.stopPropagation();
  }
};

Event.extend = (function() {
  var methods = Object.keys(Event.Methods).inject({ }, function(m, name) {
    m[name] = Event.Methods[name].methodize();
    return m;
  });

  if (Prototype.Browser.IE) {
    Object.extend(methods, {
      stopPropagation: function() { this.cancelBubble = true },
      preventDefault:  function() { this.returnValue = false },
      inspect: function() { return "[object Event]" }
    });

    return function(event) {
      if (!event) return false;
      if (event._extendedByPrototype) return event;

      event._extendedByPrototype = Prototype.emptyFunction;
      var pointer = Event.pointer(event);
      Object.extend(event, {
        target: event.srcElement,
        relatedTarget: Event.relatedTarget(event),
        pageX:  pointer.x,
        pageY:  pointer.y
      });
      return Object.extend(event, methods);
    };

  } else {
    Event.prototype = Event.prototype || document.createEvent("HTMLEvents").__proto__;
    Object.extend(Event.prototype, methods);
    return Prototype.K;
  }
})();

Object.extend(Event, (function() {
  var cache = Event.cache;

  function getEventID(element) {
    if (element._eventID) return element._eventID;
    arguments.callee.id = arguments.callee.id || 1;
    return element._eventID = ++arguments.callee.id;
  }

  function getDOMEventName(eventName) {
    if (!Event.DOMEvents.include(eventName)) return "dataavailable";
    return { keypress: "keydown" }[eventName] || eventName;
  }

  function getCacheForID(id) {
    return cache[id] = cache[id] || { };
  }

  function getWrappersForEventName(id, eventName) {
    var c = getCacheForID(id);
    return c[eventName] = c[eventName] || [];
  }

  function createWrapper(element, eventName, handler) {
    var id = getEventID(element);
    var c = getWrappersForEventName(id, eventName);
    if (c.pluck("handler").include(handler)) return false;

    var wrapper = function(event) {
      if (event.eventName && event.eventName != eventName)
        return false;

      Event.extend(event);
      handler.call(element, event)
    };

    wrapper.handler = handler;
    c.push(wrapper);
    return wrapper;
  }

  function findWrapper(id, eventName, handler) {
    var c = getWrappersForEventName(id, eventName);
    return c.find(function(wrapper) { return wrapper.handler == handler });
  }

  function destroyWrapper(id, eventName, handler) {
    var c = getCacheForID(id);
    if (!c[eventName]) return false;
    c[eventName] = c[eventName].without(findWrapper(id, eventName, handler));
  }

  function destroyCache() {
    for (var id in cache)
      for (var eventName in cache[id])
        cache[id][eventName] = null;
  }

  if (window.attachEvent) {
    window.attachEvent("onunload", destroyCache);
  }

  return {
    observe: function(element, eventName, handler) {
      element = $(element);
      var name = getDOMEventName(eventName);

      var wrapper = createWrapper(element, eventName, handler);
      if (!wrapper) return element;

      if (element.addEventListener) {
        element.addEventListener(name, wrapper, false);
      } else {
        element.attachEvent("on" + name, wrapper);
      }

      return element;
    },

    stopObserving: function(element, eventName, handler) {
      element = $(element);
      var id = getEventID(element), name = getDOMEventName(eventName);

      if (!handler && eventName) {
        getWrappersForEventName(id, eventName).each(function(wrapper) {
          element.stopObserving(eventName, wrapper.handler);
        });
        return element;

      } else if (!eventName) {
        Object.keys(getCacheForID(id)).each(function(eventName) {
          element.stopObserving(eventName);
        });
        return element;
      }

      var wrapper = findWrapper(id, eventName, handler);
      if (!wrapper) return element;

      if (element.removeEventListener) {
        element.removeEventListener(name, wrapper, false);
      } else {
        element.detachEvent("on" + name, wrapper);
      }

      destroyWrapper(id, eventName, handler);

      return element;
    },

    fire: function(element, eventName, memo) {
      element = $(element);
      if (element == document && document.createEvent && !element.dispatchEvent)
        element = document.documentElement;

      if (document.createEvent) {
        var event = document.createEvent("HTMLEvents");
        event.initEvent("dataavailable", true, true);
      } else {
        var event = document.createEventObject();
        event.eventType = "ondataavailable";
      }

      event.eventName = eventName;
      event.memo = memo || { };

      if (document.createEvent) {
        element.dispatchEvent(event);
      } else {
        element.fireEvent(event.eventType, event);
      }

      return event;
    }
  };
})());

Object.extend(Event, Event.Methods);

Element.addMethods({
  fire:          Event.fire,
  observe:       Event.observe,
  stopObserving: Event.stopObserving
});

Object.extend(document, {
  fire:          Element.Methods.fire.methodize(),
  observe:       Element.Methods.observe.methodize(),
  stopObserving: Element.Methods.stopObserving.methodize()
});

(function() {
  /* Support for the DOMContentLoaded event is based on work by Dan Webb,
     Matthias Miller, Dean Edwards and John Resig. */

  var timer, fired = false;

  function fireContentLoadedEvent() {
    if (fired) return;
    if (timer) window.clearInterval(timer);
    document.fire("contentloaded");
    fired = true;
  }

  if (document.addEventListener) {
    if (Prototype.Browser.WebKit) {
      timer = window.setInterval(function() {
        if (/loaded|complete/.test(document.readyState))
          fireContentLoadedEvent();
      }, 0);

      Event.observe(window, "load", fireContentLoadedEvent);

    } else {
      document.addEventListener("DOMContentLoaded",
        fireContentLoadedEvent, false);
    }

  } else {
    document.write("<script id=__onDOMContentLoaded defer " +
      "src='//:javascript:void(0)'><\/script>");

    $("__onDOMContentLoaded").onreadystatechange = function() {
      if (this.readyState == "complete") {
        this.onreadystatechange = null;
        fireContentLoadedEvent();
      }
    };
  }
})();
/*------------------------------- DEPRECATED -------------------------------*/

var Toggle = { display: Element.toggle };

Element.Methods.childOf = Element.Methods.descendantOf;

var Insertion = {
  Before: function(element, content) {
    return Element.insert(element, {before:content});
  },

  Top: function(element, content) {
    return Element.insert(element, {top:content});
  },

  Bottom: function(element, content) {
    return Element.insert(element, {bottom:content});
  },

  After: function(element, content) {
    return Element.insert(element, {after:content});
  }
};

var $continue = new Error('"throw $continue" is deprecated, use "return" instead');

// This should be moved to script.aculo.us; notice the deprecated methods
// further below, that map to the newer Element methods.
var Position = {
  // set to true if needed, warning: firefox performance problems
  // NOT neeeded for page scrolling, only if draggable contained in
  // scrollable elements
  includeScrollOffsets: false,

  // must be called before calling withinIncludingScrolloffset, every time the
  // page is scrolled
  prepare: function() {
    this.deltaX =  window.pageXOffset
                || document.documentElement.scrollLeft
                || document.body.scrollLeft
                || 0;
    this.deltaY =  window.pageYOffset
                || document.documentElement.scrollTop
                || document.body.scrollTop
                || 0;
  },

  // caches x/y coordinate pair to use with overlap
  within: function(element, x, y) {
    if (this.includeScrollOffsets)
      return this.withinIncludingScrolloffsets(element, x, y);
    this.xcomp = x;
    this.ycomp = y;
    this.offset = Element.cumulativeOffset(element);

    return (y >= this.offset[1] &&
            y <  this.offset[1] + element.offsetHeight &&
            x >= this.offset[0] &&
            x <  this.offset[0] + element.offsetWidth);
  },

  withinIncludingScrolloffsets: function(element, x, y) {
    var offsetcache = Element.cumulativeScrollOffset(element);

    this.xcomp = x + offsetcache[0] - this.deltaX;
    this.ycomp = y + offsetcache[1] - this.deltaY;
    this.offset = Element.cumulativeOffset(element);

    return (this.ycomp >= this.offset[1] &&
            this.ycomp <  this.offset[1] + element.offsetHeight &&
            this.xcomp >= this.offset[0] &&
            this.xcomp <  this.offset[0] + element.offsetWidth);
  },

  // within must be called directly before
  overlap: function(mode, element) {
    if (!mode) return 0;
    if (mode == 'vertical')
      return ((this.offset[1] + element.offsetHeight) - this.ycomp) /
        element.offsetHeight;
    if (mode == 'horizontal')
      return ((this.offset[0] + element.offsetWidth) - this.xcomp) /
        element.offsetWidth;
  },

  // Deprecation layer -- use newer Element methods now (1.5.2).

  cumulativeOffset: Element.Methods.cumulativeOffset,

  positionedOffset: Element.Methods.positionedOffset,

  absolutize: function(element) {
    Position.prepare();
    return Element.absolutize(element);
  },

  relativize: function(element) {
    Position.prepare();
    return Element.relativize(element);
  },

  realOffset: Element.Methods.cumulativeScrollOffset,

  offsetParent: Element.Methods.getOffsetParent,

  page: Element.Methods.viewportOffset,

  clone: function(source, target, options) {
    options = options || { };
    return Element.clonePosition(target, source, options);
  }
};

/*--------------------------------------------------------------------------*/

Element.ClassNames = Class.create();
Element.ClassNames.prototype = {
  initialize: function(element) {
    this.element = $(element);
  },

  _each: function(iterator) {
    this.element.className.split(/\s+/).select(function(name) {
      return name.length > 0;
    })._each(iterator);
  },

  set: function(className) {
    this.element.className = className;
  },

  add: function(classNameToAdd) {
    if (this.include(classNameToAdd)) return;
    this.set($A(this).concat(classNameToAdd).join(' '));
  },

  remove: function(classNameToRemove) {
    if (!this.include(classNameToRemove)) return;
    this.set($A(this).without(classNameToRemove).join(' '));
  },

  toString: function() {
    return $A(this).join(' ');
  }
};

Object.extend(Element.ClassNames.prototype, Enumerable);

/*--------------------------------------------------------------------------*/

Element.addMethods();/**
 * dhtmlHistory - An object that provides DHTML history, history data, and
 * bookmarking for AJAX applications.
 *
 * $Horde: dimp/js/src/dhtmlHistory.js,v 1.17 2007/06/20 16:08:42 slusarz Exp $
 *
 * Original code Copyright 2005, Brad Neuberg, bkn3@columbia.edu
 *  http://codinginparadise.org
 *
 * Permission is hereby granted, free of charge, to any person obtaining
 * a copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, including without limitation
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
 * and/or sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be
 * included in all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
 * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
 * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
 * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT
 * OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR
 * THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 *
 * This file has been altered from the original dhtmlHistory (v0.05) to
 * remove functionality not needed for DIMP and to provide bug fixes and
 * enhancements.
 *
 * This file requires the Prototype Javscript Library v1.5.1+.
 *
 * Additions Copyright 2005-2007 The Horde Project (http://www.horde.org/)
 */

var dhtmlHistory = {
    /* Our current hash location, without the "#" symbol. */
    // currentLocation: null,

    /* Our history change listener. */
    // listener: null,

    /* A hidden IFrame we use in Internet Explorer to detect history
       changes. */
    // iframe: null,

    /* Indicates to the browser whether to ignore location changes. */
    // ignoreLocationChange: null,

    /* The amount of time in milliseconds that we should wait between add
       requests. Firefox is okay with 200 ms, but Internet Explorer needs
       400. */
    WAIT_TIME: 200,

    /* The amount of time in milliseconds an add request has to wait in line
       before being run on a setTimeout(). */
    currentWaitTime: 0,

    /* A flag that indicates that we should fire a history change event when
       we are ready, i.e. after we are initialized and we have a history
       change listener. This is needed due to an edge case in browsers other
       than Internet Explorer; if you leave a page entirely then return, we
       must fire this as a history change event. Unfortunately, we have lost
       all references to listeners from earlier, because JavaScript
       clears out. */
    // fireOnNewListener: null,

    /* A variable that indicates whether this is the first time this page has
       been loaded. If you go to a web page, leave it for another one, and
       then return, the page's onload listener fires again. We need a way to
       differentiate between the first page load and subsequent ones.  This
       variable works hand in hand with the pageLoaded variable we store into
       historyStorage. */
    // firstLoad: false,

    /* A variable to handle an important edge case in Internet Explorer. In
       IE, if a user manually types an address into their browser's location
       bar, we must intercept this by continiously checking the location bar
       with a timer interval. However, if we manually change the location
       bar ourselves programmatically, when using our hidden iframe, we need
       to ignore these changes. Unfortunately, these changes are not atomic,
       so we surround them with the variable 'ieAtomicLocationChange', that if
       true means we are programmatically setting the location and should
       ignore this atomic chunked change. */
    // ieAtomicLocationChange: null,

    /* Initializes our DHTML history. You should call this after the page is
       finished loading. Returns true on success, false on failure. */
    initialize: function()
    {
        // dhtmlHistory does not work with WebKit.
        // See http://codinginparadise.org/weblog/2005/09/safari-no-dhtml-history-possible.html
        if (Prototype.Browser.WebKit) {
            return false;
        }

        historyStorage.init();

        // Get our initial location
        this.currentLocation = this.getCurrentLocation();

        // Write out a hidden iframe for IE and set the amount of time to
        // wait between add() requests.
        if (Prototype.Browser.IE) {
            this.iframe = $(document.createElement('IFRAME')).hide();
            this.iframe.name = this.iframe.id = 'DhtmlHistoryFrame';
            this.iframe.src = 'javascript:false;';
            document.body.appendChild(this.iframe);
            this.writeIframe(this.currentLocation);

            // Wait 400 milliseconds between history updates on IE
            this.WAIT_TIME = 400;

            this.ignoreLocationChange = true;
        }

        /* Add an unload listener for the page; this is needed for FF 1.5+
           because this browser caches all dynamic updates to the page, which
           can break some of our logic related to testing whether this is the
           first instance a page has loaded or whether it is being pulled from
           the cache. */
        Event.observe(window, 'unload', function() { this.firstLoad = false; }.bind(this));

        this.isFirstLoad();

        /* Other browsers can use a location handler that checks at regular
           intervals as their primary mechanism; we use it for IE as well to
           handle an important edge case; see checkLocation() for details. */
        setInterval(this.checkLocation.bind(this), 100);

        return true;
    },

    /* Adds a history change listener. Note that only one listener is
       supported at this time. */
    addListener: function(callback)
    {
        this.listener = callback;

        /* If the page was just loaded and we should not ignore it, fire an
           event to our new listener now. */
        if (this.fireOnNewListener) {
            this.fireHistoryEvent(this.currentLocation);
            this.fireOnNewListener = false;
        }
    },

    add: function(newLoc, historyData)
    {
        if (Prototype.Browser.WebKit) {
            return;
        }

        /* Most browsers require that we wait a certain amount of time before
           changing the location, such as 200 milliseconds; rather than
           forcing external callers to use setTimeout() to account for
           this to prevent bugs, we internally handle this detail by using a
           'currentWaitTime' variable and have requests wait in line. */
        setTimeout(this.addImpl.bind(this, newLoc, historyData), this.currentWaitTime);

        // Indicate that the next request will have to wait for awhile
        this.currentWaitTime += this.WAIT_TIME;
    },

    /* Gets the current hash value that is in the browser's location bar,
       removing leading # symbols if they are present. */
    getCurrentLocation: function()
    {
        return this.removeHash(unescape(location.hash));
    },

    addImpl: function(newLoc, historyData)
    {
        // Indicate that the current wait time is now less
        if (this.currentWaitTime) {
            this.currentWaitTime -= this.WAIT_TIME;
        }

        /* IE has a strange bug; if the newLoc is the same as _any_
           preexisting id in the document, then the history action gets
           recorded twice; throw a programmer exception if there is an
           element with this ID. */
        if ($('newLoc')) {
            return;
        }

        // Remove any leading hash symbols on newLoc
        var newLoc = this.removeHash(newLoc);

        // Store the history data into history storage
        historyStorage.put(newLoc, historyData);

        // Indicate to the browser to ignore this upcoming location change.
        // Indicate to IE that this is an atomic location change block.
        this.ignoreLocationChange = this.ieAtomicLocationChange = true;

        // Save this as our current location and change the browser location
        this.currentLocation = newLoc;
        location.hash = escape(newLoc);

        // Change the hidden iframe's location if on IE
        if (Prototype.Browser.IE) {
            this.writeIframe(newLoc);
        }

        // End of atomic location change block for IE
        this.ieAtomicLocationChange = false;
    },

    isFirstLoad: function()
    {
        if (historyStorage.hasKey("DhtmlHistory_pageLoaded") == false) {
            if (Prototype.Browser.IE) {
                this.fireOnNewListener = false;
            } else {
                this.ignoreLocationChange = true;
            }
            this.firstLoad = true;
            historyStorage.put("DhtmlHistory_pageLoaded", true);
        } else {
            if (Prototype.Browser.IE) {
                this.firstLoad = false;
            } else {
                /* Indicate that we want to pay attention to this location
                   change. */
                this.ignoreLocationChange = false;
            }

            /* For browser's other than IE, fire a history change event;
               on IE, the event will be thrown automatically when it's
               hidden iframe reloads on page load. Unfortunately, we don't
               have any listeners yet; indicate that we want to fire an
               event when a listener is added. */
            this.fireOnNewListener = true;
        }
    },

    /* Notify the listener of new history changes. */
    fireHistoryEvent: function(newHash)
    {
        // Extract the value from our history storage for this hash and call
        // our listener.
        if (this.listener) {
        	this.listener.call(null, newHash, historyStorage.get(newHash));
        }
    },

    /* See if the browser has changed location.  This is the primary history
       mechanism for FF. For IE, we use this to handle an important edge case:
       if a user manually types in a new hash value into their IE location
       bar and press enter, we want to intercept this and notify any history
       listener. */
    checkLocation: function()
    {
        /* Ignore any location changes that we made ourselves for browsers
           other than IE. */
        if (!Prototype.Browser.IE) {
            if (this.ignoreLocationChange) {
                this.ignoreLocationChange = false;
                return;
            }
        } else if (this.ieAtomicLocationChange) {
            /* If we are dealing with IE and we are in the middle of making a
               location change from an iframe, ignore it. */
            return;
        }

        // Get hash location
        var hash = this.getCurrentLocation();

        // See if there has been a change
        if (hash == this.currentLocation) {
            return;
        }

        /* On IE, we need to intercept users manually entering locations into
           the browser; we do this by comparing the browsers location against
           the iframes location; if they differ, we are dealing with a manual
           event and need to place it inside our history, otherwise we can
           return. */
        this.ieAtomicLocationChange = true;

        if (Prototype.Browser.IE) {
            if (this.iframe.contentWindow.l == hash) {
                // The iframe is unchanged
                return;
            }
            this.writeIframe(hash);
        }

        // Save this new location
        this.currentLocation = hash;

        this.ieAtomicLocationChange = false;

        // Notify listeners of the change
        this.fireHistoryEvent(hash);
    },

    /* Removes any leading hash that might be on a location. */
    removeHash: function(h)
    {
        if (h == null || h == undefined) {
            return null;
        } else if (h.length && h.charAt(0) == '#') {
            if (h.length == 1) {
                return "";
            } else {
                return h.substring(1);
            }
        }
        return h;
    },

    /* For IE, says when the hidden iframe has finished loading. */
    iframeLoaded: function(newLoc)
    {
        // Ignore any location changes that we made ourselves
        if (this.ignoreLocationChange) {
            this.ignoreLocationChange = false;
            return;
        }

        // Get the new location
        location.hash = escape(newLoc);

        // Notify listeners of the change
        this.fireHistoryEvent(newLoc);
    },

    writeIframe: function(l)
    {
        var d = this.iframe.contentWindow.document;
        d.open();
        d.write('<html><script type="text/javascript">var l="' + l + '";function pageLoaded(){window.parent.dhtmlHistory.iframeLoaded(l);}</script><body onload="pageLoaded()"></body></html>');
        d.close();
    }
};

/* An object that uses a hidden form to store history state across page loads.
   The chief mechanism for doing so is using the fact that browsers save the
   text in form data for the life of the browser and cache, which means the
   text is still there when the user navigates back to the page. See
   http://codinginparadise.org/weblog/2005/08/ajax-tutorial-saving-session-across.html
   for full details. */
var historyStorage = {
    /* Our hash of key name/values. */
    // storageHash: null,

    /* A reference to our textarea field. */
    // storageField: null,

    put: function(key, value)
    {
        this.loadHashTable();

        // Store this new key
        this.storageHash[key] = value;

        // Save and serialize the hashtable into the form
        this.saveHashTable();
    },

    get: function(key)
    {
        // Make sure the hash table has been loaded from the form
        this.loadHashTable();

        var value = this.storageHash[key];
        return (value == undefined) ? null : value;
    },

    remove: function(key)
    {
        // Make sure the hash table has been loaded from the form
        this.loadHashTable();

        // Delete the value
        delete this.storageHash[key];

        // Serialize and save the hash table into the form
        this.saveHashTable();
    },

    /* Clears out all saved data. */
    reset: function()
    {
        this.storageField.value = "";
        this.storageHash = $H();
    },

    hasKey: function(key)
    {
        // Make sure the hash table has been loaded from the form
        this.loadHashTable();

        return !(typeof this.storageHash[key] == undefined);
    },

    init: function()
    {
        // Write a hidden form into the page
        var form = $(document.createElement('FORM')).hide();
        document.body.appendChild(form);

        var ta = $(document.createElement('TEXTAREA'));
        ta.id = 'historyStorageField';
        form.appendChild(ta);

        this.storageField = $("historyStorageField");
    },

    /* Loads the hash table up from the form. */
    loadHashTable: function()
    {
        if (!this.storageHash) {
            // Destringify the content back into a real JS object
            this.storageHash = (this.storageField.value) ? this.storageField.value.evalJSON() : $H();
        }
    },

    /* Saves the hash table into the form. */
    saveHashTable: function()
    {
        this.loadHashTable();
        this.storageField.value = Object.toJSON(this.storageHash);
    }

};
/**
* Funkcje JavaScript obslugujace nokaut.pl
*
* @copyright Nokaut Sp. z o.o. (chyba ze zaznaczono inaczej)
*/

// metoda trim dla obiektu String
String.prototype.trim = function() { var _ret = this.replace(/^\s+|\s+$/g, ''); return _ret.replace(/^(\&nbsp\;)+|(\&nbsp\;)+$/g, ''); }

// kodowanie / dekodowanie UTF8 
var Utf8 = {

     // public method for url encoding
     encode : function (string) {
         string = string.replace(/\r\n/g,"\n");
         var utftext = "";

         for (var n = 0; n < string.length; n++) {

             var c = string.charCodeAt(n);

             if (c < 128) {
                 utftext += String.fromCharCode(c);
             }
             else if((c > 127) && (c < 2048)) {
                 utftext += String.fromCharCode((c >> 6) | 192);
                 utftext += String.fromCharCode((c & 63) | 128);
             }
             else {
                 utftext += String.fromCharCode((c >> 12) | 224);
                 utftext += String.fromCharCode(((c >> 6) & 63) | 128);
                 utftext += String.fromCharCode((c & 63) | 128);
             }
         }

         return utftext;
     },

     // public method for url decoding
     decode : function (utftext) {
         var string = "";
         var i = 0;
         var c = c1 = c2 = 0;

         while ( i < utftext.length ) {

             c = utftext.charCodeAt(i);

             if (c < 128) {
                 string += String.fromCharCode(c);
                 i++;
             }
             else if((c > 191) && (c < 224)) {
                 c2 = utftext.charCodeAt(i+1);
                 string += String.fromCharCode(((c & 31) << 6) | (c2 & 63));
                 i += 2;
             }
             else {
                 c2 = utftext.charCodeAt(i+1);
                 c3 = utftext.charCodeAt(i+2);
                 string += String.fromCharCode(((c & 15) << 12) | ((c2 & 63) << 6) | (c3 & 63));
                 i += 3;
             }
         }

         return string;
     }
 }

// wrapper do Event.observe dla starszych skryptów
function addEvent(element, type, handler) {
	Event.observe(element, type, handler);
}

// czy x to integer?
function isInt(x) {
   var y=parseInt(x);
   if (isNaN(y)) return false;
   return x==y && x.toString()==y.toString();
}

// obsługa odmiany zależnej od liczby
function printnum(n, words) {
	if (n == 0) {
		return words[2];
	}

	if (n == 1) {
		return words[0];
	}

	if (n > 0 && n<5) {
		return words[1];
	}
	/*
	TODO: kod do poprawienia!
	if (n > 20 && n.substr(n,-1,1) > 1 && n.substr(n,-1,1) < 5){
	return words[1];
	}
	*/

	return words[2];
}

// wyciągnięcie podstawowego adresu URL
function getBaseUrl() {
	return document.location.href.replace('http://','').replace(document.location.host,'').replace('.html','/');
}

// wyciągnięcie cookie
function getCookie(searchName){

	var cookies = document.cookie.split(";");

	for (var i = 0; i < cookies.length; i++){

		var cookieCrumbs = cookies[i].split("=");
		var cookieName = cookieCrumbs[0].replace(" ","");
		var cookieValue = cookieCrumbs[1];


		if(cookieName == searchName){
			return cookieValue;
		}

	}

	return false;
}

// czy element istnieje?
function isNode(id) {
	if (document.getElementById(id) == null) {
		return false;
	} else {
		return true;
	}
}

// firebug
function consoleLog(msg)
{
	// console.log(msg);
}

// zmienne globalne z listą linków na stronie
var Nokaut_Links;
var Nokaut_LinkCount;

// zwróć parametr z URL-a po hashu
function getParam(name) {
	var hash = document.location.hash.substring(1);
	if (hash.indexOf("&") != -1) {
		var params = hash.split("&");
		for (var i = 0; i < params.count; i++) {
			var param = params[i].split("=");
			if (param[0] == name) {
				return param[1];
			}
		}
	} else {
		var param = hash.split("=");
		if (param[0] == name) {
			return param[1];
		}		
	}
	return false;
}

/**
* główna klasa obsługująca nokaut.pl
*/
var Nokaut = Class.create();

Nokaut.prototype = {

	// uruchomienie mechanizmów obecnych na różnych stronach w całym serwisie
	initialize: function() {
		// cache linków
		Nokaut_Links = document.getElementsByTagName('a');
		Nokaut_LinkCount = Nokaut_Links.length;

		this.prepareLinkHandling();

		// common
		if (isNode('NavStore')) new NokautNavStore; // common
		if (isNode('DescriptionCut')) new NokautDescriptionCut; // common
		if (isNode('timePeriodSelector')) new NokautPeriodSelector; // common
		if (isNode('ShopAdminMenu')) new NokautShopAdminMenu; // TODO: do sprawdzenia

		// page/component specific
		if (isNode('PressroomRegister')) new NokautPressroomRegister; // Page_PressroomRegister
		if (isNode('contactForm')) new NokautContactForm; // Page_Contact
		if (isNode('PriceListTable')) new NokautPriceList; // Component_PriceList
		if (isNode('ShopCategoryRelation')) new NokautShopCategoryRelation; // Page_ShopPanelShopCategoryRelations
		if (isNode('ShopDelivery')) new NokautShopDeliveryRangeBis; // Page_ShopPanelShopCost
		if (isNode('ShopDelivery')) new NokautShopDeliveryFields; // Page_ShopPanelShopCost
		if (isNode('deliveryDetailsTable')) new NokautOfferDeliveryDetails; // Page_OfferDeliveryDetails
		if (isNode('ShopPanelShopList')) new NokautShopPanelListAction; // Page_ShopPanelShopList
		if (isNode('BillingPayment')) new NokautBillingPayment; // Page_ShopPanelBillingPay
		if (isNode('AddToStore')) new NokautAddToStore; // Page_Product
		if (isNode('NokautLightbox')) new NokautLightbox; // Page_Product
		if (isNode('OfferTableContent')) new NokautMobilePromoFrame;

		// tabs
		if (isNode('productTabs')) new NokautTabs('productTabs');
		if (isNode('shopTabs')) new NokautTabs('shopTabs');
		if (isNode('indexTabs')) new NokautTabs('indexTabs');
		if (isNode('searchTabs')) new NokautTabs('searchTabs');
		if (isNode('commentTabs')) new NokautTabs('commentTabs');
		if (isNode('userTabs')) new NokautTabs('userTabs');

			nokautFilterTabs = new NokautTabs('filterTabs');

		if (isNode('wishlistTabs')) new NokautTabs('wishlistTabs');

		// grid
		if (isNode('CategoryProducts') && isNode('AjaxDataGrid')) new NokautStartProductGrid;
		if (isNode('ProducerProducts') && isNode('AjaxDataGrid')) new NokautStartProductGrid;
		if (isNode('ShopProducts') && isNode('AjaxDataGrid')) new NokautStartProductGrid;

		// maps
		if (isNode('ShopMap') || isNode('ProductMap')) new NokautShopMapShowCity;
		if (isNode('shopmapContent')) new NokautShopMapContentLoad;

		//if (isNode('setHomepage')) new NokautSetHomepage; // wylaczone na jakis czas
		//if (isNode('addBookmark')) new NokautAddBookmark; // wylaczone na jakis czas

		if (isNode('CategoryRanking') || isNode('ProducerRanking') || isNode('WishListProductTable')) new NokautCompareProductClass;

		this.fixTabindexProblem();

		new NokautSetClickCookie;
		new NokautUser;

		if(isNode('WishListLink')) new NokautProductWishList;
		if(isNode('WishListContent')) new NokautWishList;
		if(isNode('wishlistTabs')) new NokautPageWishList;

		if(isNode('PriceAlertLink')) new NokautProductPriceAlert;
		if(isNode('PriceAlert')) new NokautPriceAlert;
		if(isNode('ProductStoreAlertContent')) new NokautProductStoreAlert;

		if(isNode('UserPage')) new NokautUserPageAddFriend;
		if(isNode('UserPageInvite')) new NokautUserPageSendInvite;
		if(isNode('x3def')) new NokautX3defender;

		 //if(isNode('productTabs')) new NokautAddToViewHistory;

		if(isNode('CategoryForumThread')) new NokautCategoryForum;
		if(isNode('CategoryForumThread')) new NokautAddThread;

		//if(isNode('CategoryGuideItem')) new NokautGuideItem;

		// TODO: rezygnacja ze zmiennej globalnej
		storeObject = new NokautAddToStoreFromPage();

		// TODO: poniższe są do zastąpienia przez obiekty
		hideRegisterDiv();
		hideCountryDiv();
		if (self.showStatTrackerImage) {
			showStatTrackerImage();
		}

  			
	},

	prepareLinkHandling: function() {
		// TODO: do optymalizacji
		if (!document.getElementsByTagName) return;
		//var anchors = Nokaut_Links;
		//var anchorsCount = Nokaut_LinkCount;
		for (var i=0; i < Nokaut_LinkCount; i++) {
			if (Nokaut_Links[i].className == 'helperLink') {
				Event.observe(Nokaut_Links[i],'mouseover', this.showHelper.bind(this));
				Event.observe(Nokaut_Links[i],'mouseout', this.hideHelper.bind(this));
			}
		}
	},


	showHelper: function(e) {
		var helperLink = Event.element(e);

		if(helperLink.tagName == 'IMG'){
			helperLink = helperLink.parentNode;
		}

		var helperId = helperLink.id.replace('Link','Content');
		if ($(helperId).style.display != 'block') {
			if ($(helperId).parentNode.tagName != 'body') {
				var body = document.getElementsByTagName('body')[0];
				body.appendChild($(helperId));

			}
			// TODO: co jezeli link nie bedzie w naglowku?
			var helperLinkPos = Position.cumulativeOffset(helperLink.parentNode.parentNode);
			$(helperId).style.left = (helperLinkPos[0]) + 'px';
			$(helperId).style.display = 'block';
			$(helperId).style.top = (helperLinkPos[1] - Element.getHeight(helperId)) + 'px';
		}
	},

	hideHelper: function(e) {
		var helperLink = Event.element(e);

		if(helperLink.tagName == 'IMG'){
			helperLink = helperLink.parentNode;
		}

		var helperId = helperLink.id.replace('Link','Content');
		if ($(helperId).style.display != 'none') {
			$(helperId).style.display = 'none';
		}
	},

	fixTabindexProblem: function() {
		if (!document.getElementsByTagName) return;
		var formList = document.getElementsByTagName("form");
		var pList = [];
		for (var i=0; i<formList.length; i++) {
			pList = formList[i].getElementsByTagName("p");
			for (var j=0; j<pList.length; j++) {
				pList[j].setAttribute('tabindex',-1);
			}
		}
	}
}

document.observe("contentloaded", function() {
	new Nokaut;
});


var NokautSetClickCookie = Class.create();

NokautSetClickCookie.prototype = {

	initialize: function() {

		this.handleClickCookie();

	},

	handleClickCookie: function(){

		var clidCookie = "";
		if (getCookie('clid') === false){

			var timeExpire = new Date();
			timeExpire.setFullYear(timeExpire.getFullYear() +1);

			var ourTimeStamp = new Date().getTime();
			clid = ourTimeStamp+Math.round((99977562 - 10016486) * Math.random() + 1) + 22423;
			// clid to zmienna globalna na wszelki wypadek, do zamiany na obiekt :)
			clidCookie = "clid=" + clid;
			clidCookie += ";path=/";
			clidCookie += ";expires="+timeExpire.toGMTString();
			document.cookie = clidCookie;
		}

		var clrefCookie = "";
		if (getCookie('clref') === false) {
			var timeExpire = new Date();
			timeExpire.setFullYear(timeExpire.getFullYear() +1);
			var clref = escape(document.referrer);
			clrefCookie = "clref=" + clref;
			clrefCookie += ";path=/";
			clrefCookie += ";expires="+timeExpire.toGMTString();
			document.cookie = clrefCookie + ';' + document.cookie;
		}

		if (getCookie('pid') === false) {
			var timeExpire = new Date();
			timeExpire.setFullYear(timeExpire.getFullYear() +1);

			var partnerId = 0;
			var params = document.location.hash.slice(1).split("&");
			for(var i = 0; i < params.length; i++) {
				if(params[i].indexOf("pid=") == 0) {
					partnerId = params[i].slice(params[i].indexOf("=") + 1);
					pidCookie = "pid=" + partnerId;
					pidCookie += ";path=/";
					pidCookie += ";expires="+timeExpire.toGMTString();
					document.cookie = pidCookie + ";" + document.cookie;

					break;
				}
			}
		}

	}
}

var NokautPeriodSelector = Class.create();
NokautPeriodSelector.prototype = {

	initialize: function() {
		this.enableFormSubmit();
	},

	enableFormSubmit: function() {
		Event.observe($('timePeriodSelector'),'change', function() {
			$('timePeriodForm').submit();
		});
	}

}

/*
var NokautAddBookmark = Class.create();

NokautAddBookmark.prototype = {

	initialize: function() {
		if (window.sidebar || window.external){
			var bookmarkLink = new Element('a', { href: '#', className: 'icon bookmark'});
			bookmarkLink.update('dodaj do ulubionych');
			Event.observe(bookmarkLink, 'click', this.handleAddBookmark.bind(this));
			$('addBookmark').appendChild(bookmarkLink);
		}
	},

	handleAddBookmark: function(e) {
		if (window.sidebar) {
        	window.sidebar.addPanel(document.title, document.location.href, "");
    	} else if (window.external){
         	window.external.AddFavorite(document.location.href, document.title);
    	}

		Event.stop(e);
	}
}

var NokautSetHomepage = Class.create();

NokautSetHomepage.prototype = {

	initialize: function() {

		var ie7 = (document.all && !window.opera && window.XMLHttpRequest);

		if ((typeof window.sidebar == "object") && (typeof
		window.sidebar.addSearchEngine == "function")) {
			Element.update($('addSearchEngine'),' <a href="" id="addSearchEngine">dodaj do Firefoksa</a> | ');
			Event.observe($('addSearchEngine'), 'click', this.handleSherlockSearchEngine);
		}
		else if (ie7) {
			Element.update($('addSearchEngine'),' <a href="" id="addSearchEngine">dodaj do IE7</a> | ');
			Event.observe($('addSearchEngine'), 'click', this.handleOpenSearchEngine.bind(this));
		} else {
			if (document.all && !window.opera){
				Element.update($('setHomepage'),' <a href="" id="setHomepageLink">ustaw jako startową</a> | ');
				Event.observe($('setHomepageLink'), 'click', this.handleSetHomepage.bind(this));
			}
		}


	},

	handleOpenSearchEngine: function() {
		window.external.AddSearchProvider('http://www.nokaut.pl/dl/opensearch.xml');

	},

	handleSherlockSearchEngine: function() {
		window.sidebar.addSearchEngine(
		"http://www.nokaut.pl/dl/firefox.src",
		"http://www.nokaut.pl/img/favicon.gif",
		"Nokaut.pl",
		""
		);
	},

	handleSetHomepage: function(e) {

		if (document.all){
			document.body.style.behavior='url(#default#homepage)';
			document.body.setHomePage('http://www.nokaut.pl');
		}
		Event.stop(e);
	}
}
*/

/**
 * skracanie opisów
 */
var NokautDescriptionCut = Class.create();

NokautDescriptionCut.prototype = {

	// przygotowywuje okienko do pracy
	initialize: function() {

		this.description = $('DescriptionCut').innerHTML;

		if($('DescriptionCut').getHeight() > 13){

			this.setDescription();
			this.handleSetDescription();
		}
	},

	handleSetDescription: function() {

		Event.observe($('DescriptionCutOn'), 'click', this.setShortDescription.bind(this));
		Event.observe($('DescriptionCutOff'), 'click', this.setFullDescription.bind(this));
	},


	setDescription: function(){

		var description_short = this.description.substring(0, this.description.substring(0,80).lastIndexOf(' '));
		var description_tmp = '<span id="descriptionShort">'+description_short+'... <a href="" id="DescriptionCutOff" title="Rozwiń cały tekst"><img src="http://img.nokaut.pl/ikona/plus.gif" alt="+"/></a></span><span id="descriptionFull">'+this.description+' <a href="" id="DescriptionCutOn" title="Pozostaw tylko fragment tekstu"><img src="http://img.nokaut.pl/ikona/minus.gif" alt="-" /></a></span>';
		Element.update($('DescriptionCut'),description_tmp);
		$('descriptionFull').hide();

	},

	setShortDescription: function(e) {
		$('descriptionShort').show();
		$('descriptionFull').hide();
		Event.stop(e);
	},

	setFullDescription: function(e) {
		$('topDescription').style.height = 'auto';
		//$('topDescription').style.overflow = 'none';
		$('descriptionShort').hide();
		$('descriptionFull').show();
		Event.stop(e);
	}
}

/**
* Schowek w nawigacji
*/

var NokautUser = Class.create();

NokautUser.prototype = {

	initialize: function(){
		if (isNode('NavLocation')) {
			this.initializeLocation();
		}
		if (isNode('LoginInfo')) {
			this.initializeLogin();
		}
	},

	initializeLocation: function() {
		var location_cookie = getCookie('location');
		var location_city = '';

		if (location_cookie) {
			var tmp_location = unescape(location_cookie).split('|');
			if (tmp_location[2]) {
				location_city = tmp_location[2];
			}
		}


		if (location_city != '') {

			var string_location = 'Twoja lokalizacja: <strong>'+Utf8.decode(location_city)+'</strong> <a href="/lokalizacja.html">(zmień)</a>';
		} else {
			var string_location = 'Twoja lokalizacja: <strong>nieznana</strong> <a href="/lokalizacja.html">(zmień)</a>';
		}

		Element.update($('NavLocation'),string_location);
	},

	initializeLogin: function() {
		var auth = unescape(getCookie('auth'));
		var authLogin = '';
		if (auth.search(/\;/) > 0) {
			auth = auth.split(';');
			auth = auth[1].split(':');
			authLogin = auth[2].replace('"','').replace('"','');
		}
		if (authLogin != '') {
			$('LoginInfo').update('<strong class="icon user_red">'+authLogin+'</strong> &nbsp; &nbsp; <a href="wylogowanie.html" class="icon logout">wyloguj się</a>');
		} else {
			$('LoginInfo').update('');
			// $('LoginInfo').update('<span class="icon register"><a href="rejestracja.html">załóż konto</a>, twórz listy, oceniaj i komentuj!</span> &nbsp; <a href="logowanie.html" class="icon login">zaloguj się</a>');
		}

	}

}

var NokautNavStore = Class.create();

NokautNavStore.prototype = {

	initialize: function(){

		this.getCookieStoreArray();
		this.countStoreProduct();

	},

	getCookieStoreArray: function(){

		this.productIdList = getCookie('ProductStore');

		if(!this.productIdList){

			this.productIdList = '';
		}
	},

	countStoreProduct: function(){
		if (!isNode('NavStore')) {
			return false;
		}
		if (!isNode('maincontent')) {
			return false;
		}


		if(this.productIdList.length > 0){

			$('NavStore').style.visibility = 'visible';
			//$('maincontent').className = 'store';

			var ProductIdArray = this.productIdList.split("-");

			for(var i = 1;i <= ProductIdArray.length; i++){

				this.countProduct = i;
			}
		}else{
			this.countProduct = 0;
			$('NavStore').style.visibility = 'hidden';
			//$('maincontent').className = '';
		}

		var word_product_text = new Array('ofertę','oferty','ofert');
		var word_is_text = new Array('jest','są','jest');
		var product_text = printnum(this.countProduct, word_product_text);
		var is_text = printnum(this.countProduct, word_is_text);

		Element.update($('NavStore'),'<a href="/schowek.html" class="icon folder_table" title="zobacz zapamiętane oferty">zapamiętano <strong>'+this.countProduct+'</strong> '+product_text+ '</a> ');

	}
}

/**
* Dodaj do schowka ze strony
*/


function addToStorePage(id){

	if(id > 0){

		var store_id = 'store_'+id;
		storeObject.handleAddToStoreFromPage(store_id);

	}
	if (!e) var e = window.event;
	if (e) {
		Event.stop(e);
	}

}

function handleCompareCheckbox(id) {

	if (id > 0) {

		grid.handleCompareCheckbox(id);
	}
}

var NokautAddToStoreFromPage = Class.create();

NokautAddToStoreFromPage.prototype = {

	initialize: function(){

	},

	handleAddToStoreFromPage: function(product_id) {
		this.handleActionStoreFromPageGrid(product_id);
	},

	showConfirmation: function() {
		if (isNode('transparentAlert')) {
			Element.remove($('transparentAlert'));
		}

		var theAlertDiv = document.createElement("div");
		var theBody = document.getElementsByTagName("body")[0];

		theAlertDiv.setAttribute('id','transparentAlert');
		Event.observe(theAlertDiv, 'mouseover', function() {
			clearTimeout(this.alertId);
		}.bind(this));
		Event.observe(theAlertDiv, 'mouseout', function() {
			this.alertId = setTimeout((function() {
				Element.remove($('transparentAlert'));
			}), 1000);
		}.bind(this));


		theAlertDiv.innerHTML = 'oferta została zapamiętana<br /><a href="/schowek.html" class="icon folder_go">zobacz zapamiętane oferty</a>';
		// theAlertDiv.style.display = 'none';

		this.alertId = setTimeout((function() {
			Element.remove($('transparentAlert'));
		}), 4000);

		theBody.appendChild(theAlertDiv);

	},

	handleActionStoreFromPageGrid: function(product_id){

		var ddd = product_id.split('_');

		var productIdList = getCookie('ProductStore');

		if(productIdList.length > 0){

			var ProductIdArray = productIdList.split("-");

		}else{
			var ProductIdArray = new Array();
		}

		if(this.is_in_array(ddd[1],ProductIdArray)){
			this.deleteStoreFromPage(ddd[1],ProductIdArray);
		}else{
			this.addStoreFromPage(ddd[1],ProductIdArray);
		}

		if (isNode('NavStore')) new NokautNavStore;

		this.showConfirmation();

	},

	handleActionStoreFromPage: function(e){

		var anchor = Event.element(e);
		var ddd = anchor.id.split('_');

		var productIdList = getCookie('ProductStore');

		if(productIdList.length > 0){

			var ProductIdArray = productIdList.split("-");

		}else{
			var ProductIdArray = new Array();
		}

		if(this.is_in_array(ddd[1],ProductIdArray)){
			this.deleteStoreFromPage(ddd[1],ProductIdArray);
		}else{
			this.addStoreFromPage(ddd[1],ProductIdArray);
		}

		if (isNode('NavStore')) new NokautNavStore;
		Event.stop(e);
	},

	addStoreFromPage: function(id,array){

		array.push(id);

		if(array.length > 0){

			var productIdList = array.join("-");
		}else{
			var productIdList = array[0];
		}
		this.addToStore(productIdList);
	},

	deleteStoreFromPage: function(id,array){

		for(var i = 0;i < array.length; i++){

			if(array[i] == id){
				array.splice(i,1);
			}
		}

		var productIdList = array.join("-");
		this.addToStore(productIdList);
	},

	addToStore: function(productIdList){

		var timeExpire = new Date();
		timeExpire.setFullYear(timeExpire.getFullYear() +1);

		var theCookie = "ProductStore="+productIdList;
		theCookie += ";path=/";
		theCookie += ";expires="+timeExpire.toGMTString();

		document.cookie = theCookie;

		var analyticsHref = getBaseUrl() + 'link_store';
		urchinTracker(analyticsHref);

	},

	is_in_array: function(element,array){

		for(i = 0;i < array.length;i++){

			if(array[i] == element){

				return true;
			}
		}

		return false;
	}
}

isHistoryEnabled = false;
function initHistory() {
	if (!isHistoryEnabled) {
		dhtmlHistory.initialize();	
		isHistoryEnabled = true;
	}
}

/**
* Uniwersalna obsługa zakładek
*
* TODO: optymalizacja pętli i nie tylko
*/
var NokautTabs = Class.create();

NokautTabs.prototype = {

	// zakładki pogrupowane według listy
	list: [],

	// zaznaczone zakładki według listy
	selected: [],
	documentStop: 1,
	documentHash: '',
	// id-ki list zakładek
	id: '',
	
	defaultTab: [],
	
	hashParam: 'zakladka',

	// czy dla zakładki był uruchamiany loader obrazków?
	isImageLoaderStarted: [],

	gridEnabled: false,
	priceSliderEnabled: false,
	sliders: [],

	// przygotowanie zakładek
	initialize: function(id) {


			this.hashParam = 'zakladka2';

 		initHistory();	
				
		this.id = id;
		this.list[this.id] = [];
		this.selected[this.id] = [];
		this.findAvailableTabs();
		this.showSelectedTabContent();
		this.attachToAnchorLinks();  // TODO: optymalizacja
		dhtmlHistory.addListener(this.handleHistoryChange.bind(this));	
	},
	
	handleHistoryChange: function(newLocation, historyData) {
		consoleLog('obsluga zmiany pozycji w historii, nowa lokacja to '+newLocation);
		this.selected[this.id] = getParam(this.hashParam);
		if (false == this.selected[this.id]) {
			this.selected[this.id] = this.defaultTab[this.id];
		}
		this.showSelectedTabContent();
	},

	setHiddenSelectedTab: function(){

		if($('hiddenSelectedTab')){

			return $F('hiddenSelectedTab');

		}else{

			return false;
		}

	},

	// znajdujemy dostępne zakładki
	findAvailableTabs: function() {

		// wyciągamy element na bazie id
		var tabGroup = document.getElementById(this.id);

		// sprawdzamy linki w tych listach i wyciągamy to co jest po #
		// TODO: zmiana sposobu wyboru elementu by nie chodzić po wszystkich linkach

		var tabLinks = tabGroup.getElementsByTagName('a');
		for (var j=0; j < tabLinks.length; j++) {

			var tabName = tabLinks[j].href.substring(tabLinks[j].href.indexOf("#") + 1).replace(this.hashParam+'=','');
			tabLinks[j].id = tabName + 'Tab';

			// dodajemy zakładkę do listy zakładek
			this.list[this.id].push(tabName);

			// pierwsza zakładka zaznaczana automagicznie
			if (j == 0) {
				this.selected[this.id] = tabName;
				this.defaultTab[this.id] = tabName;
			}

			// chyba że jakaś jest już w urlu...
			if (getParam(this.hashParam)) {
				this.selected[this.id] = getParam(this.hashParam).replace('@','');

			} else if (this.setHiddenSelectedTab() == tabName) {
				this.selected[this.id] = tabName;
				this.defaultTab[this.id] = tabName;

			}
			consoleLog('zaznaczona zakladka to '+this.selected[this.id]+' ('+this.id+')');

		}


	},

	// pokazujemy elementy o ID takiej jak
	showSelectedTabContent: function() {

		var currentContentId = '';

		for (var i=0; i < this.list[this.id].length; i++) {
			var contentId = this.list[this.id][i] + 'Content';

			if ($(contentId)) {
				if (this.list[this.id][i] == this.selected[this.id]) {
					$(contentId).style.display = 'block';

					// włączenie grida na zakładce
					if ((contentId == 'produktyContent') && (this.gridEnabled == false)) {
						if (isNode('AjaxDataGrid')) {
							this.enableProductGrid();
						}
					}
					// włączenie obrazków na zakładce
					if ($(contentId).hasClassName('imageloader') && this.isImageLoaderStarted.indexOf(contentId) == -1) {
						consoleLog('włączam imageloadera dla zakładki '+contentId);
						var tabImages = $(contentId).getElementsByTagName('img');
						for (var j=0; j < tabImages.length; j++) {
							if (tabImages[j].hasAttribute('longdesc')) {
								tabImages[j].setAttribute('src', tabImages[j].getAttribute('longdesc'));
							}
						}
						this.isImageLoaderStarted.push(contentId);
					}

					consoleLog('pokazuje zakladke '+contentId + ' bo zaznaczona jest '+this.selected[this.id]+' ('+this.id+')');
					currentContentId = contentId;
				} else {
					consoleLog('ukrywam zakladke '+contentId + ' bo zaznaczona jest '+this.selected[this.id]+' ('+this.id+')');
					$(contentId).style.display = 'none';
				}
			}
		}

		// TODO: zmiana sposobu wyboru elementu by nie chodzić po wszystkich linkach
		var links = document.getElementById(this.id).getElementsByTagName('a');
		for (var k=0; k < links.length; k++) {
			var anchor = links[k].href.substr(links[k].href.lastIndexOf('#')+1).replace(this.hashParam+'=','');
			if (anchor != '') {
				if (anchor == this.selected[this.id]) {
					links[k].parentNode.className = 'selected';
				} else {
					links[k].parentNode.className = '';


				}
			}
		}


			this.enablePriceSlider();

		var currentPropertyId = currentContentId.replace('filtr_','').replace('Content','');
		if (isInt(currentPropertyId)) {
			if (!this.sliders[currentPropertyId]) {
				this.enableFilterSlider(currentPropertyId);
			}
		}

	},

	enablePriceSlider: function() {
		if (isNode('price-handle-1')) {
			this.priceslider = new Control.Slider(['price-handle-1','price-handle-2'],'price-track',{
				sliderValue:[parseInt($('initial_cache_price_min_from').value),parseInt($('initial_cache_price_min_to').value)],
				range:$R(parseInt($('initial_cache_price_min_from').value), parseInt($('initial_cache_price_min_to').value)),
				restricted:true,
				spans:['price-track-selected'],
				onSlide:function(v) {
					var position = v.toString().split(",");
					$('pfrom').value = Math.round(position[0]/10)*10;
					$('pto').value = Math.round(position[1]/10)*10;
					filterInfo.handleInput('pfrom');
					filterInfo.handleInput('pto');
					grid.handleFilterProxy();
				}
			});
			//$('pfrom').value = $('initial_cache_price_min_from').value;
			//$('pto').value = $('initial_cache_price_min_to').value;

			Event.observe($('pfrom'),'keyup',function() {
				this.priceslider.setValue($('pfrom').value,0);
			}.bind(this));

			Event.observe($('pto'),'keyup',function() {
				this.priceslider.setValue($('pto').value,1);
			}.bind(this));

			filterInfo.addInput({
				element: 'pfrom',
				value_default: parseInt($('initial_cache_price_min_from').value),
				suffix: 'PLN',
				prefix: 'cena od',
				slider: 'price'
			});

			filterInfo.addInput({
				element: 'pto',
				value_default: parseInt($('initial_cache_price_min_to').value),
				suffix: 'PLN',
				prefix: 'cena do',
				slider: 'price'
			});


		}
		this.priceSliderEnabled = true;


	},

	enableFilterSlider: function(propertyId) {

		var property_id = propertyId;
		var property_id_prefix = 'property_' + property_id;

		if (isNode(property_id_prefix + '-handle-1')) {
			this.sliders[property_id] = new Control.Slider([property_id_prefix + '-handle-1', property_id_prefix + '-handle-2'], property_id_prefix + '-track',{
				sliderValue:[parseInt($('initial_'+property_id_prefix+'_from').value),parseInt($('initial_'+property_id_prefix+'_to').value)],
				range:$R(parseInt($('initial_'+property_id_prefix+'_from').value), parseInt($('initial_'+property_id_prefix+'_to').value)),
				restricted:true,
		
				spans:[property_id_prefix+'-track-selected'],
				propertyIdPrefix: property_id_prefix,
				onSlide:function(v) {
					// tutaj powinno byc wykrywane property_id_prefix, bo wewnatrz funkcji to nie jest widoczne
					var multiplier = 1;
					if ($('filter_type_'+this.propertyIdPrefix).value == 'decimal') {
						multiplier = 10;
					}
					
					var position = v.toString().split(",");
					$('filter_'+this.propertyIdPrefix+'_from').value = Math.round(position[0]*multiplier)/multiplier;
					$('filter_'+this.propertyIdPrefix+'_to').value = Math.round(position[1]*multiplier)/multiplier;
					filterInfo.handleInput('filter_'+this.propertyIdPrefix+'_from');
					filterInfo.handleInput('filter_'+this.propertyIdPrefix+'_to');
					grid.handleFilterProxy();
				}
			});
			//$('pfrom').value = $('initial_cache_price_min_from').value;
			//$('pto').value = $('initial_cache_price_min_to').value;

			Event.observe($('filter_'+property_id_prefix+'_from'),'keyup',function() {
				// tutaj powinno byc wykrywane property_id_prefix, bo wewnatrz funkcji to nie jest widoczne
				// this.setValue($('filter_'+this.propertyIdPrefix+'_from').value,0);
			}.bind(this.sliders[property_id]));

			Event.observe($('filter_'+property_id_prefix+'_to'),'keyup',function() {
				// tutaj powinno byc wykrywane property_id_prefix, bo wewnatrz funkcji to nie jest widoczne
				// this.setValue($('filter_'+this.propertyIdPrefix+'_to').value,1);
			}.bind(this.sliders[property_id]));

			var propertyName = $($('filter_' + property_id_prefix+'_from').parentNode.id.replace('Content','Tab')).innerHTML;

			filterInfo.addInput({
				element: 'filter_'+property_id_prefix+'_from',
				value_default: parseInt($('initial_'+property_id_prefix+'_from').value),
				suffix: $('filter_'+property_id_prefix+'_unit').innerHTML,
				prefix: propertyName+' od',
				slider: property_id
			});

			filterInfo.addInput({
				element: 'filter_'+property_id_prefix+'_to',
				value_default: parseInt($('initial_'+property_id_prefix+'_to').value),
				suffix: $('filter_'+property_id_prefix+'_unit').innerHTML,
				prefix: propertyName+' do',
				slider: property_id
			});

		}

	},

	enableProductGrid: function() {

		new NokautStartProductGrid;
		this.gridEnabled = true;

	},

	attachToAnchorLinks: function() {
		// TODO: zmiana sposobu wyboru elementu by nie chodzić po wszystkich linkach
		for (var k=0; k < Nokaut_LinkCount; k++) {
			var link = Nokaut_Links[k];
			if (link.href) {
				if (link.href.lastIndexOf('#') > 0) {
					var anchor = link.href.substr(link.href.lastIndexOf('#')+1).replace(this.hashParam+'=','');
					if (this.list[this.id].indexOf(anchor) != -1) {
						Event.observe(link, 'click', this.handleAnchorLink.bindAsEventListener(this));
					}
				}
			}
		}
	},

	handleAnchorLink: function(e) {
		var link = Event.element(e);

		if(link.tagName == 'IMG'){
			link = link.parentNode;
		}

		if(link.href.indexOf('@') > -1){
			var linkHref = link.href.substring(0,link.href.length-1);
			this.documentStop = 0;
		}else{
			var linkHref = link.href;
		}

		this.selected[this.id] = linkHref.substr(linkHref.lastIndexOf('#')+1).replace(this.hashParam+'=','');

		consoleLog('zarejestrowano klik w zakladke '+this.selected[this.id]+' ('+this.id+')');

		this.showSelectedTabContent();

		var analyticsHref = getBaseUrl() + 'tab_' + this.selected[this.id];
		urchinTracker(analyticsHref);

		dhtmlHistory.add(this.hashParam+'='+this.selected[this.id],'');


		if(this.documentStop == 1){
			Event.stop(e);
		}



	}
}

var NokautFilterInfo = Class.create();

NokautFilterInfo.prototype = {

	fields: [],

	initialize: function(){

	},

	addInput: function(data) {
		this.fields[data.element] = data;

		Event.observe($(data.element), 'keyup', function(e) {
			this.handleInput(Event.element(e).id);
		}.bind(this));

	},

	addCheckbox: function(data) {

		this.fields[data.element] = data;

		Event.observe($(data.element), 'click', function(e) {
			this.handleCheckbox(Event.element(e).id);
		}.bind(this));
	},

	handleInput: function(element_id) {
		var data = filterInfo.fields[element_id];

		if (false == isNode('filterInfo_' + data.element)) {
			if (data.value_default != $(data.element).value) {
				var filterLink = new Element('a', { href: '#', id: 'filterInfo_' + data.element });
				filterLink.update(data.prefix + ' ' + $(data.element).value + ' ' + data.suffix);
				Event.observe(filterLink, 'click', function(e) {
					var elementId = Event.element(e).id.replace('filterInfo_','');
					$(elementId).value = filterInfo.fields[elementId].value_default;

					if (filterInfo.fields[elementId].slider) {
						var sliderId = 0;
						if (data.element.indexOf('_to') > 0) {
							sliderId = 1;
						}

						if (filterInfo.fields[elementId].slider == 'price') {
							nokautFilterTabs.priceslider.setValue(filterInfo.fields[elementId].value_default, sliderId);

						} else {
							nokautFilterTabs.sliders[filterInfo.fields[elementId].slider].setValue(filterInfo.fields[elementId].value_default, sliderId);
						}
					}

					grid.handleFilterProxy();
					Event.element(e).remove();
					Event.stop(e);
				});
				$('filterContainer').appendChild(filterLink);
			}
		} else {
			if (data.value_default != $(data.element).value) {
				$('filterInfo_' + data.element).update(data.prefix + ' ' + $(data.element).value + ' ' + data.suffix);
			} else {
				$('filterInfo_' + data.element).remove();
			}
		}

	},


	handleCheckbox: function(element_id) {
		var data = filterInfo.fields[element_id];

		if (false == isNode('filterInfo_' + data.element)) {
			if ($(data.element).checked == true) {
				var filterLink = new Element('a', { href: '#', id: 'filterInfo_' + data.element });
				filterLink.update(data.prefix + ' ' + $('label_'+data.element).innerHTML + ' ' + data.suffix);
				Event.observe(filterLink, 'click', function(e) {
					var elementId = Event.element(e).id.replace('filterInfo_','');
					$(elementId).checked = false;

					grid.handleFilterProxy();
					Event.element(e).remove();
					Event.stop(e);
				});
				$('filterContainer').appendChild(filterLink);
			}
		} else {
			if ($(data.element).checked == true) {
				$('filterInfo_' + data.element).update(data.prefix + ' ' + $('label_'+data.element).innerHTML + ' ' + data.suffix);
			} else {
				$('filterInfo_' + data.element).remove();
			}
		}

	}


}
filterInfo = new NokautFilterInfo;



/**
* Zwroc anchora z linku
*/
function getAnchorByUrl(url) {
	return url.substring(url.lastIndexOf('#') + 1);
}

/**
* Uruchom edytor dla wskazanego pola
*/
function initTinyMce(area){

	tinyMCE.init({
		mode : "exact",
		elements : area,
		theme : "simple"
	});
}

// TODO: zamienić na metodę klasy NokautShopRegisterForm
function handleHideRegisterDiv(e){

	if($F('companyAddress2Checkbox')=='y'){

		$('company_address2').style.display = "none";

	}else{

		$('company_address2').style.display = "block";

	}

}

// TODO: zamienić na metodę klasy NokautShopRegisterForm
function hideRegisterDiv(){

	if($('companyAddress2Checkbox')){


		addEvent('companyAddress2Checkbox', 'click', handleHideRegisterDiv);
		$('companyAddress2Checkbox').click();
		$('companyAddress2Checkbox').click();

	}


}

// TODO: zamienić na metodę klasy NokautShopRegisterForm
function handleHideCountryDiv(e){

	if(this.id == 'company_country2' || this.id == 'country_list2'){

		var item = 2;

	}else{

		var item = '';
	}

	if($F('company_country')=='Polska'){
		$('nip_section').style.display = "block";
		$('regon_section').style.display = "block";
	}else{
		$('nip_section').style.display = "none";
		$('regon_section').style.display = "none";
	}

	if($F('company_country'+item)!='inne'){
		//$('country_select'+item).style.display = "block";
		$('country_other'+item).style.display = "none";
		//$('company_country_other'+item) .value = '';
		//$('company_country'+item) .value = 'Polska';
	}else{
		$('country_other'+item).style.display = "block";
	}

}

// TODO: zamienić na metodę klasy NokautShopRegisterForm
function hideCountryDiv(){

	if($('company_country'))
	{

		if($F('company_country')!='inne'){

			$('country_other').style.display = "none";
		}

		addEvent($('company_country'), 'change', handleHideCountryDiv);
		//addEvent($('country_list'), 'change', handleHideCountryDiv);

		//$('country_list').click();
	}

	if($('company_country2')){

		if($F('company_country2')!='inne'){

			$('country_other2').style.display = "none";
		}

		addEvent($('company_country2'), 'change', handleHideCountryDiv);
		//addEvent($('country_list2'), 'change', handleHideCountryDiv);
		//$('country_list2').click();
	}
}

var NokautShopAdminMenu = Class.create();

NokautShopAdminMenu.prototype = {

	initialize: function(){

		this.setHiddenTreeElement();
		//this.toggleTreeElement();

	},

	toggleTreeElement: function(){

		var anchorList = document.getElementById('ShopAdminMenu').getElementsByTagName('a');
		for (var i=0; i<anchorList.length; i++) {

			if (anchorList[i].id.substr(0,anchorList[i].id.indexOf('_')) == 'linkShopSubMenu') {

				Event.observe(anchorList[i], 'click', this.handleShopAdminMenu.bindAsEventListener(this));
			}
		}
	},

	handleShopAdminMenu: function(e){

		var anchor = Event.element(e);
		var ddd = anchor.id.split('_');

		//alert(ddd[1]);

		$('ShopSubMenu_'+ddd[1]).toggle();
		Event.stop(e);
	},

	setHiddenTreeElement: function(){


		var elementList = document.getElementsByClassName('shopSubMenu','ShopAdminMenu');
		for (var i=0; i<elementList.length; i++) {

			if(elementList[i].id.length > 0){

				$(elementList[i].id).toggle();
			}
		}

		var elementList = document.getElementsByClassName('subselected','ShopAdminMenu');
		for (var i=0; i<elementList.length; i++) {

			var selectedElement = elementList[i].id.substr(4);
			//alert(selectedElement);

			if(selectedElement.length > 0){

				$(selectedElement).toggle();
			}

		}

	}
}

var NokautCompareProductClass = Class.create();

NokautCompareProductClass.prototype = {

'selectedProducts': [],

	initialize: function(){
		this.prepareCompareButtons();

		var inputList = document.getElementById('ProductRanking').getElementsByTagName('input');

		for (var i=0; i<inputList.length; i++) {

			Event.observe(inputList[i], 'click', this.handleCompareCheckbox.bindAsEventListener(this));
		};

	},

	handleCompareCheckbox: function(e) {

		var input = Event.element(e);
		var id = input.id.substr(input.id.indexOf('_')+1);

		if (isNode('compare_'+id) && $('compare_'+id).checked == true) {
			if (this.selectedProducts.indexOf(id) == -1) {
				this.selectedProducts.push(id);
			}
		} else {
			this.selectedProducts = this.selectedProducts.without(id);
		}

		this.handleCompareButtons();
	},

	markSelectedProducts: function() {
		this.selectedProducts.each(this.markSelectedProduct);
	},

	markSelectedProduct: function(value, index) {
		if (isNode('compare_'+value)) {
			$('compare_'+value).checked = true;
		}

	},

	prepareCompareButtons: function() {
		if (isNode('gridCompareTop')) {
			Event.observe('gridCompareTop','click', this.handleCompareButtonClick.bindAsEventListener(this));
		}
		if (isNode('gridCompareBottom')) {
			Event.observe('gridCompareBottom','click', this.handleCompareButtonClick.bind(this));
		}

	},

	handleCompareButtonClick: function() {
		if (this.selectedProducts.length < 2) {
			alert('Aby porównać produkty, zaznacz co najmniej dwa z nich.');
		} else {
			window.location = '/porownaj/produkty/'+this.selectedProducts.join('-')+'.html';
		}
	},


	handleCompareButtons: function() {
		if (this.selectedProducts.length < 2) {
			if (isNode('gridCompareTop')) {
				$('gridCompareTop').src="img/layout/btnCompareDisabled.png";
			}
			if (isNode('gridCompareBottom')) {
				$('gridCompareBottom').src="img/layout/btnCompareReverseDisabled.png";
			}

		} else {
			if (isNode('gridCompareTop')) {
				$('gridCompareTop').src="img/layout/btnCompare.png";
			}
			if (isNode('gridCompareBottom')) {
				$('gridCompareBottom').src="img/layout/btnCompareReverse.png";
			}
		}
	}
}

NokautControl = Class.create();

NokautControl.prototype = {

	id:'',
	loaded: false,
	overlayStyles: {
		position: 'absolute',
		top: 0,
		left: 0,
		zIndex: 9998,
		opacity: 0.5
	},

//position: fixed; top: 0pt; left: 0pt; width: 100%; height: 100%; z-index: 9998;

	contentStyles: {
		position: 'absolute',
		top: '50%',
		left: '50%',
		zIndex:9999,
		padding:'16px',
		width:'300px',
		height:'150px',
		background:'white',
		border:'1px solid #B8B8B8'

	},

	closeXStyles: {
		position: 'absolute',
		right:0,
		top:0,
		width: '25px',
		height: '25px',
		display: 'block',
		backgroundPosition:'0 0',
		backgroundRepeat:'no-repeat',
		backgroundImage: 'url(/img/layout/closeWindow.png)',
		cursor: 'pointer'

	},

	initialize: function(id) {
		this.id = id+'Content';
		this.loadContent();
		$(id+'Content').hide();
		Event.observe($(id), 'click', this.showContent.bindAsEventListener(this));

	},

	getScrollBarWidth: function(){
		var scroller_div = document.createElement('div');
		Object.extend(scroller_div.style,{
			position: 'absolute',
			top:'-1000px',
			left: '-1000px',
			width: '100px',
			height: '50px',
			overflow: 'hidden'
		});
		var inner_div = document.createElement('div');
		inner_div.style.width = '100%';
		inner_div.style.height = '200px';
		scroller_div.appendChild(inner_div);
		document.body.appendChild(scroller_div);
		var withNoScroll = inner_div.offsetWidth;
		scroller_div.style.overflow = 'auto';
		var withScroll = inner_div.offsetWidth;
		document.body.removeChild(document.body.lastChild);
		return (withNoScroll - withScroll);
	},

	getWindowWidth: function(){
		return (self.innerWidth || document.documentElement.clientWidth || document.body.clientWidth || 0);
	},
	getWindowHeight: function(){
		return (self.innerHeight ||  document.documentElement.clientHeight || document.body.clientHeight || 0);
	},
	getDocumentWidth: function(){

		//alert('bodyScrollWidth: '+document.body.scrollWidth);

		return Math.min(document.body.scrollWidth,this.getWindowWidth());
	},
	getDocumentHeight: function(){

		//alert('body.scrollHeight: '+document.body.scrollHeight);

		return Math.max(document.body.scrollHeight,this.getWindowHeight());
	},

	showContent: function(e){

		if(this.loaded === true){

				var localDocH = this.getDocumentHeight() + 'px';
				var localDocW = this.getDocumentWidth() + 'px';

				alert(localDocW+' / '+localDocH);

				this.overlay.setStyle({
					height: localDocH,
					width: localDocW,
					marginRight: '-300px'
				});

			this.overlay.show();
		}
		$(this.id).show();
		this.close_x.observe('click',this.close.bindAsEventListener(this));
		this.overlay.observe('click',this.close.bindAsEventListener(this));
		Event.stop(e);
	},

	loadContent: function(){

		if(this.loaded === false){
			this.loaded = true;
			this.ie = !!(window.attachEvent && !window.opera);
			this.overlay = $(document.createElement('div'));
			this.overlay.id = 'modal_overlay';
			Object.extend(this.overlay.style,this.overlayStyles);
			this.overlay.hide();
			var body_tag = document.getElementsByTagName('body')[0];
			body_tag.appendChild(this.overlay);

			this.close_x = $(document.createElement('div'));
			this.close_x.id = 'close_x';
			$(this.id).appendChild(this.close_x);
			Object.extend(this.close_x.style,this.closeXStyles);
			this.setCenter();
		}
	},

	close: function(e){
		this.overlay.hide();
		$(this.id).hide();
		Event.stop(e);
	},

	setCenter: function(){

		Object.extend($(this.id).style,this.contentStyles);

		$(this.id).setStyle({
				position: 'absolute'
			});

		var dimensions = $(this.id).getDimensions();
		Position.prepare();

		var offset_left = (Position.deltaX + Math.floor((this.getWindowWidth() - dimensions.width) / 2));
		var offset_top = (Position.deltaY + Math.floor((this.getWindowHeight() - dimensions.height) / 2));
		$(this.id).setStyle({
			top: ((dimensions.height <= this.getWindowHeight()) ? ((offset_top != null && offset_top > 0) ? offset_top : '0') + 'px' : 0),
			left: ((dimensions.width <= this.getWindowWidth()) ? ((offset_left != null && offset_left > 0) ? offset_left : '0') + 'px' : 0)
		});
	}
}

// show popup
function display_popup(url, target, params) {

	if(target == '') {
		target = '_blank';
	}
	if(params == '') {
		params = 'location=0,statusbar=0,menubar=0,width=400,height=300';
	}

	var theWindow = window.open(url, target, params);
  theWindow.focus();
  return false;
}

// confirmation box
function confirmation(url, question) {

	if(question == '') {
		question = 'Czy jesteś pewien ?';
	}

  if (confirm(question)) {
    document.location = url;
  }
  else {
  	return false;
  }
}

// enabled / disabled field
function toggle_enable(id) {
	if(document.getElementById(id).disabled == true) {
		document.getElementById(id).disabled = false;
	}
	else {
		document.getElementById(id).disabled = true;
	}
}

NokautX3defender = Class.create();

NokautX3defender.prototype = {

	initialize: function(){

		$('x3def').setStyle({
			height: this.getDocumentHeight() + 'px'
		});


		var text = 'Strony erotyczne zawierają treści przeznaczone tylko dla dorosłych. Abyś mógł na nie wejść musisz mieć ukończone 18 lat<br/><a href="" id="x3End">anuluj</a> &nbsp; &nbsp; &nbsp; <a href="" id="x3Enter">wchodzę</a> ';
		new Insertion.Top('x3def', text);

		Event.observe('x3End', 'click', this.endX3div.bindAsEventListener(this));
		Event.observe('x3Enter', 'click', this.enterX3div.bindAsEventListener(this));
	},

	endX3div: function(e){

		location.href='http://www.nokaut.pl';
		Event.stop(e);
	},

	enterX3div: function(e){
		$('x3def').hide();

		if (getCookie('x3Defender') === false) {

			var x3defCookie = "";

			var timeExpire = new Date();
			timeExpire.setFullYear(timeExpire.getFullYear() +1);

			x3defCookie = "x3Defender=y";
			x3defCookie += ";path=/";
			x3defCookie += ";expires="+timeExpire.toGMTString();
			document.cookie = x3defCookie + ';' + document.cookie;
		}

		Event.stop(e);
	},

	getWindowWidth: function(){
		return (self.innerWidth || document.documentElement.clientWidth || document.body.clientWidth || 0) - (navigator.userAgent.indexOf('Gecko') > -1 && navigator.userAgent.indexOf('KHTML') == -1 ? 0 : this.getScrollBarWidth());
	},
	getWindowHeight: function(){
		return (self.innerHeight ||  document.documentElement.clientHeight || document.body.clientHeight || 0);
	},
	getDocumentWidth: function(){
		return Math.min(document.body.scrollWidth,this.getWindowWidth());
	},
	getDocumentHeight: function(){
		return Math.max(document.body.scrollHeight,this.getWindowHeight());
	},

	getScrollBarWidth: function(){
		var scroller_div = document.createElement('div');
		Object.extend(scroller_div.style,{
			position: 'absolute',
			top:'-1000px',
			left: '-1000px',
			width: '100px',
			height: '50px',
			overflow: 'hidden'
		});
		var inner_div = document.createElement('div');
		inner_div.style.width = '100%';
		inner_div.style.height = '200px';
		scroller_div.appendChild(inner_div);
		document.body.appendChild(scroller_div);
		var withNoScroll = inner_div.offsetWidth;
		scroller_div.style.overflow = 'auto';
		var withScroll = inner_div.offsetWidth;
		document.body.removeChild(document.body.lastChild);
		return (withNoScroll - withScroll);
	}

}

