/*  Prototype JavaScript framework, version 1.6.0.2
 *  (c) 2005-2008 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.2',

  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(/Apple.*Mobile.*Safari/)
  },

  BrowserFeatures: {
    XPath: !!document.evaluate,
    ElementExtensions: !!window.HTMLElement,
    SpecificElementExtensions:
      document.createElement('div').__proto__ &&
      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 parent = null, properties = $A(arguments);
    if (Object.isFunction(properties[0]))
      parent = properties.shift();

    function klass() {
      this.initialize.apply(this, arguments);
    }

    Object.extend(klass, Class.Methods);
    klass.superclass = parent;
    klass.subclasses = [];

    if (parent) {
      var subclass = function() { };
      subclass.prototype = parent.prototype;
      klass.prototype = new subclass;
      parent.subclasses.push(klass);
    }

    for (var i = 0; i < properties.length; i++)
      klass.addMethods(properties[i]);

    if (!klass.prototype.initialize)
      klass.prototype.initialize = Prototype.emptyFunction;

    klass.prototype.constructor = klass;

    return klass;
  }
};

Class.Methods = {
  addMethods: function(source) {
    var ancestor   = this.superclass && this.superclass.prototype;
    var properties = Object.keys(source);

    if (!Object.keys({ toString: true }).length)
      properties.push("toString", "valueOf");

    for (var i = 0, length = properties.length; i < length; i++) {
      var property = properties[i], 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() }
        });
      }
      this.prototype[property] = value;
    }

    return this;
  }
};

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.isUndefined(object)) return 'undefined';
      if (object === null) return 'null';
      return object.inspect ? object.inspect() : String(object);
    } 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 (!Object.isUndefined(value))
        results.push(property.toJSON() + ': ' + value);
    }

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

  toQueryString: function(object) {
    return $H(object).toQueryString();
  },

  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 != null && typeof object == "object" &&
      'splice' in object && 'join' in object;
  },

  isHash: function(object) {
    return object instanceof Hash;
  },

  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[^(]*\((.*?)\)/)[1].split(",").invoke("strip");
    return names.length == 1 && !names[0] ? [] : names;
  },

  bind: function() {
    if (arguments.length < 2 && Object.isUndefined(arguments[0])) 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.getUTCFullYear() + '-' +
    (this.getUTCMonth() + 1).toPaddedString(2) + '-' +
    this.getUTCDate().toPaddedString(2) + 'T' +
    this.getUTCHours().toPaddedString(2) + ':' +
    this.getUTCMinutes().toPaddedString(2) + ':' +
    this.getUTCSeconds().toPaddedString(2) + 'Z"';
};

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 = Object.isUndefined(count) ? 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 = Object.isUndefined(truncation) ? '...' : 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) {
    return count < 1 ? '' : new Array(count + 1).join(this);
  },

  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;
    if (str.blank()) return false;
    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 before;

      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);
    });
  }
});
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 = Object.isUndefined(fillWith) ? 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 == null || 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 == null || 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();
  var length = iterable.length || 0, results = new Array(length);
  while (length--) results[length] = iterable[length];
  return results;
}

if (Prototype.Browser.WebKit) {
  $A = function(iterable) {
    if (!iterable) return [];
    if (!(Object.isFunction(iterable) && iterable == '[object NodeList]') &&
        iterable.toArray) return iterable.toArray();
    var length = iterable.length || 0, results = new Array(length);
    while (length--) results[length] = iterable[length];
    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 (!Object.isUndefined(value)) 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) {
  if (!Object.isString(string)) return [];
  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();
});
function $H(object) {
  return new Hash(object);
};

var Hash = Class.create(Enumerable, (function() {

  function toQueryPair(key, value) {
    if (Object.isUndefined(value)) return key;
    return key + '=' + encodeURIComponent(String.interpret(value));
  }

  return {
    initialize: function(object) {
      this._object = Object.isHash(object) ? object.toObject() : Object.clone(object);
    },

    _each: function(iterator) {
      for (var key in this._object) {
        var value = this._object[key], pair = [key, value];
        pair.key = key;
        pair.value = value;
        iterator(pair);
      }
    },

    set: function(key, value) {
      return this._object[key] = value;
    },

    get: function(key) {
      return this._object[key];
    },

    unset: function(key) {
      var value = this._object[key];
      delete this._object[key];
      return value;
    },

    toObject: function() {
      return Object.clone(this._object);
    },

    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(object) {
      return this.clone().update(object);
    },

    update: function(object) {
      return new Hash(object).inject(this, function(result, pair) {
        result.set(pair.key, pair.value);
        return result;
      });
    },

    toQueryString: function() {
      return this.map(function(pair) {
        var key = encodeURIComponent(pair.key), values = pair.value;

        if (values && typeof values == 'object') {
          if (Object.isArray(values))
            return values.map(toQueryPair.curry(key)).join('&');
        }
        return toQueryPair(key, values);
      }).join('&');
    },

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

    toJSON: function() {
      return Object.toJSON(this.toObject());
    },

    clone: function() {
      return new Hash(this);
    }
  }
})());

Hash.prototype.toTemplateReplacements = Hash.prototype.toObject;
Hash.from = $H;
var ObjectRange = Class.create(Enumerable, {
  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();
    }
  },

  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();
    else if (Object.isHash(this.options.parameters))
      this.options.parameters = this.options.parameters.toObject();
  }
});

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 = Object.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 && this.isSameOrigin() && 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;
    }
  },

  isSameOrigin: function() {
    var m = this.url.match(/^\s*https?:\/\/[^\/]*/);
    return !m || (m[0] == '#{protocol}//#{domain}#{port}'.interpolate({
      protocol: location.protocol,
      domain: document.domain,
      port: location.port ? ':' + location.port : ''
    }));
  },

  getHeader: function(name) {
    try {
      return this.transport.getResponseHeader(name) || null;
    } 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  = Object.isUndefined(xml) ? 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');
    if (!json) return null;
    json = decodeURIComponent(escape(json));
    try {
      return json.evalJSON(this.request.options.sanitizeJSON ||
        !this.request.isSameOrigin());
    } catch (e) {
      this.request.dispatchException(e);
    }
  },

  _getResponseJSON: function() {
    var options = this.request.options;
    if (!options.evalJSON || (options.evalJSON != 'force' &&
      !(this.getHeader('Content-type') || '').include('application/json')) ||
        this.responseText.blank())
          return null;
    try {
      return this.responseText.evalJSON(options.sanitizeJSON ||
        !this.request.isSameOrigin());
    } 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 = Object.clone(options);
    var onComplete = options.onComplete;
    options.onComplete = (function(response, json) {
      this.updateContent(response.responseText);
      if (Object.isFunction(onComplete)) onComplete(response, json);
    }).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);
    }
  }
});

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(response) {
    if (this.options.decay) {
      this.decay = (response.responseText == this.lastText ?
        this.decay * this.options.decay : 1);

      this.lastText = response.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(Element.extend(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, insert, tagName, childNodes;

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

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

      content = Object.toHTML(content);

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

      childNodes = Element._getContentFromAnonymousElement(tagName, content.stripScripts());

      if (position == 'top' || position == 'after') childNodes.reverse();
      childNodes.each(insert.curry(element));

      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 wrapper;
  },

  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 $(element).select("*");
  },

  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 Object.isNumber(expression) ? ancestors[expression] :
      Selector.findElement(ancestors, expression, index);
  },

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

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

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

  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] = Object.isUndefined(value) ? true : value;

    for (var attr in attributes) {
      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 ||
      new RegExp("(^|\\s)" + className + "(\\s|$)").test(elementClassName)));
  },

  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);
    var originalAncestor = ancestor;

    if (element.compareDocumentPosition)
      return (element.compareDocumentPosition(ancestor) & 8) === 8;

    if (element.sourceIndex && !Prototype.Browser.Opera) {
      var e = element.sourceIndex, a = ancestor.sourceIndex,
       nextAncestor = ancestor.nextSibling;
      if (!nextAncestor) {
        do { ancestor = ancestor.parentNode; }
        while (!(nextAncestor = ancestor.nextSibling) && ancestor.parentNode);
      }
      if (nextAncestor && nextAncestor.sourceIndex)
       return (e > a && e < nextAncestor.sourceIndex);
    }

    while (element = element.parentNode)
      if (element == originalAncestor) 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') ?
          (Object.isUndefined(elementStyle.styleFloat) ? '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.getStyle(element, 'overflow') || 'auto';
    if (element._overflow !== '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 !== 'static') 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;

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

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

if (Prototype.Browser.Opera) {
  Element.Methods.getStyle = Element.Methods.getStyle.wrap(
    function(proceed, element, style) {
      switch (style) {
        case 'left': case 'top': case 'right': case 'bottom':
          if (proceed(element, 'position') === 'static') return null;
        case 'height': case 'width':
          // returns '0px' for hidden elements; we want it to return null
          if (!Element.visible(element)) return null;

          // returns the border-box dimensions rather than the content-box
          // dimensions, so we subtract padding and borders from the value
          var dim = parseInt(proceed(element, style), 10);

          if (dim !== element['offset' + style.capitalize()])
            return dim + 'px';

          var properties;
          if (style === 'height') {
            properties = ['border-top-width', 'padding-top',
             'padding-bottom', 'border-bottom-width'];
          }
          else {
            properties = ['border-left-width', 'padding-left',
             'padding-right', 'border-right-width'];
          }
          return properties.inject(dim, function(memo, property) {
            var val = proceed(element, property);
            return val === null ? memo : memo - parseInt(val, 10);
          }) + 'px';
        default: return proceed(element, style);
      }
    }
  );

  Element.Methods.readAttribute = Element.Methods.readAttribute.wrap(
    function(proceed, element, attribute) {
      if (attribute === 'title') return element.title;
      return proceed(element, attribute);
    }
  );
}

else if (Prototype.Browser.IE) {
  // IE doesn't report offsets correctly for static elements, so we change them
  // to "relative" to get the values, then change them back.
  Element.Methods.getOffsetParent = Element.Methods.getOffsetParent.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;
    }
  );

  $w('positionedOffset 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);
        // Trigger hasLayout on the offset parent so that IE6 reports
        // accurate offsetTop and offsetLeft values for position: fixed.
        var offsetParent = element.getOffsetParent();
        if (offsetParent && offsetParent.getStyle('position') === 'fixed')
          offsetParent.setStyle({ zoom: 1 });
        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 currentStyle = element.currentStyle;
    if ((currentStyle && !currentStyle.hasLayout) ||
      (!currentStyle && element.style.zoom == 'normal'))
        element.style.zoom = 1;

    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) {
          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.extend({
      cellpadding: 'cellPadding',
      cellspacing: 'cellSpacing'
    }, 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 && /rv:1\.8\.0/.test(navigator.userAgent)) {
  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 Element#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 ('outerHTML' in document.createElement('div')) {
  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];
  if (t) {
    div.innerHTML = t[0] + html + t[1];
    t[2].times(function() { div = div.firstChild });
  } else div.innerHTML = html;
  return $A(div.childNodes);
};

Element._insertionTranslations = {
  before: function(element, node) {
    element.parentNode.insertBefore(node, element);
  },
  top: function(element, node) {
    element.insertBefore(node, element.firstChild);
  },
  bottom: function(element, node) {
    element.appendChild(node);
  },
  after: function(element, node) {
    element.parentNode.insertBefore(node, element.nextSibling);
  },
  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() {
  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 = { };
    var B = Prototype.Browser;
    $w('width height').each(function(d) {
      var D = d.capitalize();
      dimensions[d] = (B.WebKit && !document.evaluate) ? self['inner' + D] :
        (B.Opera) ? document.body['client' + D] : document.documentElement['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();
  },

  shouldUseXPath: function() {
    if (!Prototype.BrowserFeatures.XPath) return false;

    var e = this.expression;

    // Safari 3 chokes on :*-of-type and :empty
    if (Prototype.Browser.WebKit &&
     (e.include("-of-type") || e.include(":empty")))
      return false;

    // XPath can't do namespaced attributes, nor can it read
    // the "checked" property from DOM nodes
    if ((/(\[[\w-]*?:|:checked)/).test(this.expression))
      return false;

    return true;
  },

  compileMatcher: function() {
    if (this.shouldUseXPath())
      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: function(m) {
      m[1] = m[1].toLowerCase();
      return new Template("[@#{1}]").evaluate(m);
    },
    attr: function(m) {
      m[1] = m[1].toLowerCase();
      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, 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); c = false;',
    attr: function(m) {
      m[3] = (m[5] || m[6]);
      return new Template('n = h.attr(n, r, "#{1}", "#{3}", "#{2}", c); 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 nodeValue && Selector.operators[matches[2]](nodeValue, matches[5] || matches[6]);
    }
  },

  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) {
      var _true = Prototype.emptyFunction;
      for (var i = 0, node; node = nodes[i]; i++)
        node._countedByPrototype = _true;
      return nodes;
    },

    unmark: function(nodes) {
      for (var i = 0, node; node = nodes[i]; i++)
        node._countedByPrototype = 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._countedByPrototype = Prototype.emptyFunction;
      if (reverse) {
        for (var nodes = parentNode.childNodes, i = nodes.length - 1, j = 1; i >= 0; i--) {
          var node = nodes[i];
          if (node.nodeType == 1 && (!ofType || node._countedByPrototype)) node.nodeIndex = j++;
        }
      } else {
        for (var i = 0, j = 1, nodes = parentNode.childNodes; node = nodes[i]; i++)
          if (node.nodeType == 1 && (!ofType || node._countedByPrototype)) 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])._countedByPrototype) {
          n._countedByPrototype = Prototype.emptyFunction;
          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, 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) {
      var uTagName = 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() === uTagName) 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, combinator) {
      if (!nodes) nodes = root.getElementsByTagName("*");
      if (nodes && combinator) nodes = this[combinator](nodes);
      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, combinator) {
      if (!nodes) nodes = root.getElementsByTagName("*");
      if (nodes && combinator) nodes = this[combinator](nodes);
      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._countedByPrototype) {
          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._countedByPrototype) 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() + '-'); }
  },

  split: function(expression) {
    var expressions = [];
    expression.scan(/(([\w#:.~>+()\s-]+|\*|\[.*?\])+)\s*(,|$)/, function(m) {
      expressions.push(m[1].strip());
    });
    return expressions;
  },

  matchElements: function(elements, expression) {
    var matches = $$(expression), h = Selector.handlers;
    h.mark(matches);
    for (var i = 0, results = [], element; element = elements[i]; i++)
      if (element._countedByPrototype) 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) {
    expressions = Selector.split(expressions.join(','));
    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;
  }
});

if (Prototype.Browser.IE) {
  Object.extend(Selector.handlers, {
    // IE returns comment nodes on getElementsByTagName("*").
    // Filter them out.
    concat: function(a, b) {
      for (var i = 0, node; node = b[i]; i++)
        if (node.tagName !== "!") a.push(node);
      return a;
    },

    // IE improperly serializes _countedByPrototype in (inner|outer)HTML.
    unmark: function(nodes) {
      for (var i = 0, node; node = nodes[i]; i++)
        node.removeAttribute('_countedByPrototype');
      return nodes;
    }
  });
}

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 (Object.isUndefined(options.hash)) 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 : Object.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 Object.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 (Object.isUndefined(value)) return element.checked ? element.value : null;
    else element.checked = !!value;
  },

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

  select: function(element, index) {
    if (Object.isUndefined(index))
      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,

  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 = (function() {
  var isButton;

  if (Prototype.Browser.IE) {
    var buttonMap = { 0: 1, 1: 4, 2: 2 };
    isButton = function(event, code) {
      return event.button == buttonMap[code];
    };

  } else if (Prototype.Browser.WebKit) {
    isButton = function(event, code) {
      switch (code) {
        case 0: return event.which == 1 && !event.metaKey;
        case 1: return event.which == 1 && event.metaKey;
        default: return false;
      }
    };

  } else {
    isButton = function(event, code) {
      return event.which ? (event.which === code + 1) : (event.button === code);
    };
  }

  return {
    isLeftClick:   function(event) { return isButton(event, 0) },
    isMiddleClick: function(event) { return isButton(event, 1) },
    isRightClick:  function(event) { return isButton(event, 2) },

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

    findElement: function(event, expression) {
      var element = Event.element(event);
      if (!expression) return element;
      var elements = [element].concat(element.ancestors());
      return Selector.findElement(elements, expression, 0);
    },

    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.extend(event);
      event.preventDefault();
      event.stopPropagation();
      event.stopped = true;
    }
  };
})();

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._prototypeEventID) return element._prototypeEventID[0];
    arguments.callee.id = arguments.callee.id || 1;
    return element._prototypeEventID = [++arguments.callee.id];
  }

  function getDOMEventName(eventName) {
    if (eventName && eventName.include(':')) return "dataavailable";
    return 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 || !Event.extend ||
        (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;

      var event;
      if (document.createEvent) {
        event = document.createEvent("HTMLEvents");
        event.initEvent("dataavailable", true, true);
      } else {
        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.extend(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(),
  loaded:        false
});

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

  var timer;

  function fireContentLoadedEvent() {
    if (document.loaded) return;
    if (timer) window.clearInterval(timer);
    document.fire("dom:loaded");
    document.loaded = 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=//:><\/script>");
    $("__onDOMContentLoaded").onreadystatechange = function() {
      if (this.readyState == "complete") {
        this.onreadystatechange = null;
        fireContentLoadedEvent();
      }
    };
  }
})();
/*------------------------------- DEPRECATED -------------------------------*/

Hash.toQueryString = Object.toQueryString;

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);
  }
};

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

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);

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

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();// script.aculo.us effects.js v1.8.2, Tue Nov 18 18:30:58 +0100 2008

// Copyright (c) 2005-2008 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us)
// Contributors:
//  Justin Palmer (http://encytemedia.com/)
//  Mark Pilgrim (http://diveintomark.org/)
//  Martin Bialasinki
//
// script.aculo.us is freely distributable under the terms of an MIT-style license.
// For details, see the script.aculo.us web site: http://script.aculo.us/

// converts rgb() and #xxx to #xxxxxx format,
// returns self (or first argument) if not convertable
String.prototype.parseColor = function() {
  var color = '#';
  if (this.slice(0,4) == 'rgb(') {
    var cols = this.slice(4,this.length-1).split(',');
    var i=0; do { color += parseInt(cols[i]).toColorPart() } while (++i<3);
  } else {
    if (this.slice(0,1) == '#') {
      if (this.length==4) for(var i=1;i<4;i++) color += (this.charAt(i) + this.charAt(i)).toLowerCase();
      if (this.length==7) color = this.toLowerCase();
    }
  }
  return (color.length==7 ? color : (arguments[0] || this));
};

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

Element.collectTextNodes = function(element) {
  return $A($(element).childNodes).collect( function(node) {
    return (node.nodeType==3 ? node.nodeValue :
      (node.hasChildNodes() ? Element.collectTextNodes(node) : ''));
  }).flatten().join('');
};

Element.collectTextNodesIgnoreClass = function(element, className) {
  return $A($(element).childNodes).collect( function(node) {
    return (node.nodeType==3 ? node.nodeValue :
      ((node.hasChildNodes() && !Element.hasClassName(node,className)) ?
        Element.collectTextNodesIgnoreClass(node, className) : ''));
  }).flatten().join('');
};

Element.setContentZoom = function(element, percent) {
  element = $(element);
  element.setStyle({fontSize: (percent/100) + 'em'});
  if (Prototype.Browser.WebKit) window.scrollBy(0,0);
  return element;
};

Element.getInlineOpacity = function(element){
  return $(element).style.opacity || '';
};

Element.forceRerendering = function(element) {
  try {
    element = $(element);
    var n = document.createTextNode(' ');
    element.appendChild(n);
    element.removeChild(n);
  } catch(e) { }
};

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

var Effect = {
  _elementDoesNotExistError: {
    name: 'ElementDoesNotExistError',
    message: 'The specified DOM element does not exist, but is required for this effect to operate'
  },
  Transitions: {
    linear: Prototype.K,
    sinoidal: function(pos) {
      return (-Math.cos(pos*Math.PI)/2) + .5;
    },
    reverse: function(pos) {
      return 1-pos;
    },
    flicker: function(pos) {
      var pos = ((-Math.cos(pos*Math.PI)/4) + .75) + Math.random()/4;
      return pos > 1 ? 1 : pos;
    },
    wobble: function(pos) {
      return (-Math.cos(pos*Math.PI*(9*pos))/2) + .5;
    },
    pulse: function(pos, pulses) {
      return (-Math.cos((pos*((pulses||5)-.5)*2)*Math.PI)/2) + .5;
    },
    spring: function(pos) {
      return 1 - (Math.cos(pos * 4.5 * Math.PI) * Math.exp(-pos * 6));
    },
    none: function(pos) {
      return 0;
    },
    full: function(pos) {
      return 1;
    }
  },
  DefaultOptions: {
    duration:   1.0,   // seconds
    fps:        100,   // 100= assume 66fps max.
    sync:       false, // true for combining
    from:       0.0,
    to:         1.0,
    delay:      0.0,
    queue:      'parallel'
  },
  tagifyText: function(element) {
    var tagifyStyle = 'position:relative';
    if (Prototype.Browser.IE) tagifyStyle += ';zoom:1';

    element = $(element);
    $A(element.childNodes).each( function(child) {
      if (child.nodeType==3) {
        child.nodeValue.toArray().each( function(character) {
          element.insertBefore(
            new Element('span', {style: tagifyStyle}).update(
              character == ' ' ? String.fromCharCode(160) : character),
              child);
        });
        Element.remove(child);
      }
    });
  },
  multiple: function(element, effect) {
    var elements;
    if (((typeof element == 'object') ||
        Object.isFunction(element)) &&
       (element.length))
      elements = element;
    else
      elements = $(element).childNodes;

    var options = Object.extend({
      speed: 0.1,
      delay: 0.0
    }, arguments[2] || { });
    var masterDelay = options.delay;

    $A(elements).each( function(element, index) {
      new effect(element, Object.extend(options, { delay: index * options.speed + masterDelay }));
    });
  },
  PAIRS: {
    'slide':  ['SlideDown','SlideUp'],
    'blind':  ['BlindDown','BlindUp'],
    'appear': ['Appear','Fade']
  },
  toggle: function(element, effect) {
    element = $(element);
    effect = (effect || 'appear').toLowerCase();
    var options = Object.extend({
      queue: { position:'end', scope:(element.id || 'global'), limit: 1 }
    }, arguments[2] || { });
    Effect[element.visible() ?
      Effect.PAIRS[effect][1] : Effect.PAIRS[effect][0]](element, options);
  }
};

Effect.DefaultOptions.transition = Effect.Transitions.sinoidal;

/* ------------- core effects ------------- */

Effect.ScopedQueue = Class.create(Enumerable, {
  initialize: function() {
    this.effects  = [];
    this.interval = null;
  },
  _each: function(iterator) {
    this.effects._each(iterator);
  },
  add: function(effect) {
    var timestamp = new Date().getTime();

    var position = Object.isString(effect.options.queue) ?
      effect.options.queue : effect.options.queue.position;

    switch(position) {
      case 'front':
        // move unstarted effects after this effect
        this.effects.findAll(function(e){ return e.state=='idle' }).each( function(e) {
            e.startOn  += effect.finishOn;
            e.finishOn += effect.finishOn;
          });
        break;
      case 'with-last':
        timestamp = this.effects.pluck('startOn').max() || timestamp;
        break;
      case 'end':
        // start effect after last queued effect has finished
        timestamp = this.effects.pluck('finishOn').max() || timestamp;
        break;
    }

    effect.startOn  += timestamp;
    effect.finishOn += timestamp;

    if (!effect.options.queue.limit || (this.effects.length < effect.options.queue.limit))
      this.effects.push(effect);

    if (!this.interval)
      this.interval = setInterval(this.loop.bind(this), 15);
  },
  remove: function(effect) {
    this.effects = this.effects.reject(function(e) { return e==effect });
    if (this.effects.length == 0) {
      clearInterval(this.interval);
      this.interval = null;
    }
  },
  loop: function() {
    var timePos = new Date().getTime();
    for(var i=0, len=this.effects.length;i<len;i++)
      this.effects[i] && this.effects[i].loop(timePos);
  }
});

Effect.Queues = {
  instances: $H(),
  get: function(queueName) {
    if (!Object.isString(queueName)) return queueName;

    return this.instances.get(queueName) ||
      this.instances.set(queueName, new Effect.ScopedQueue());
  }
};
Effect.Queue = Effect.Queues.get('global');

Effect.Base = Class.create({
  position: null,
  start: function(options) {
    function codeForEvent(options,eventName){
      return (
        (options[eventName+'Internal'] ? 'this.options.'+eventName+'Internal(this);' : '') +
        (options[eventName] ? 'this.options.'+eventName+'(this);' : '')
      );
    }
    if (options && options.transition === false) options.transition = Effect.Transitions.linear;
    this.options      = Object.extend(Object.extend({ },Effect.DefaultOptions), options || { });
    this.currentFrame = 0;
    this.state        = 'idle';
    this.startOn      = this.options.delay*1000;
    this.finishOn     = this.startOn+(this.options.duration*1000);
    this.fromToDelta  = this.options.to-this.options.from;
    this.totalTime    = this.finishOn-this.startOn;
    this.totalFrames  = this.options.fps*this.options.duration;

    this.render = (function() {
      function dispatch(effect, eventName) {
        if (effect.options[eventName + 'Internal'])
          effect.options[eventName + 'Internal'](effect);
        if (effect.options[eventName])
          effect.options[eventName](effect);
      }

      return function(pos) {
        if (this.state === "idle") {
          this.state = "running";
          dispatch(this, 'beforeSetup');
          if (this.setup) this.setup();
          dispatch(this, 'afterSetup');
        }
        if (this.state === "running") {
          pos = (this.options.transition(pos) * this.fromToDelta) + this.options.from;
          this.position = pos;
          dispatch(this, 'beforeUpdate');
          if (this.update) this.update(pos);
          dispatch(this, 'afterUpdate');
        }
      };
    })();

    this.event('beforeStart');
    if (!this.options.sync)
      Effect.Queues.get(Object.isString(this.options.queue) ?
        'global' : this.options.queue.scope).add(this);
  },
  loop: function(timePos) {
    if (timePos >= this.startOn) {
      if (timePos >= this.finishOn) {
        this.render(1.0);
        this.cancel();
        this.event('beforeFinish');
        if (this.finish) this.finish();
        this.event('afterFinish');
        return;
      }
      var pos   = (timePos - this.startOn) / this.totalTime,
          frame = (pos * this.totalFrames).round();
      if (frame > this.currentFrame) {
        this.render(pos);
        this.currentFrame = frame;
      }
    }
  },
  cancel: function() {
    if (!this.options.sync)
      Effect.Queues.get(Object.isString(this.options.queue) ?
        'global' : this.options.queue.scope).remove(this);
    this.state = 'finished';
  },
  event: function(eventName) {
    if (this.options[eventName + 'Internal']) this.options[eventName + 'Internal'](this);
    if (this.options[eventName]) this.options[eventName](this);
  },
  inspect: function() {
    var data = $H();
    for(property in this)
      if (!Object.isFunction(this[property])) data.set(property, this[property]);
    return '#<Effect:' + data.inspect() + ',options:' + $H(this.options).inspect() + '>';
  }
});

Effect.Parallel = Class.create(Effect.Base, {
  initialize: function(effects) {
    this.effects = effects || [];
    this.start(arguments[1]);
  },
  update: function(position) {
    this.effects.invoke('render', position);
  },
  finish: function(position) {
    this.effects.each( function(effect) {
      effect.render(1.0);
      effect.cancel();
      effect.event('beforeFinish');
      if (effect.finish) effect.finish(position);
      effect.event('afterFinish');
    });
  }
});

Effect.Tween = Class.create(Effect.Base, {
  initialize: function(object, from, to) {
    object = Object.isString(object) ? $(object) : object;
    var args = $A(arguments), method = args.last(),
      options = args.length == 5 ? args[3] : null;
    this.method = Object.isFunction(method) ? method.bind(object) :
      Object.isFunction(object[method]) ? object[method].bind(object) :
      function(value) { object[method] = value };
    this.start(Object.extend({ from: from, to: to }, options || { }));
  },
  update: function(position) {
    this.method(position);
  }
});

Effect.Event = Class.create(Effect.Base, {
  initialize: function() {
    this.start(Object.extend({ duration: 0 }, arguments[0] || { }));
  },
  update: Prototype.emptyFunction
});

Effect.Opacity = Class.create(Effect.Base, {
  initialize: function(element) {
    this.element = $(element);
    if (!this.element) throw(Effect._elementDoesNotExistError);
    // make this work on IE on elements without 'layout'
    if (Prototype.Browser.IE && (!this.element.currentStyle.hasLayout))
      this.element.setStyle({zoom: 1});
    var options = Object.extend({
      from: this.element.getOpacity() || 0.0,
      to:   1.0
    }, arguments[1] || { });
    this.start(options);
  },
  update: function(position) {
    this.element.setOpacity(position);
  }
});

Effect.Move = Class.create(Effect.Base, {
  initialize: function(element) {
    this.element = $(element);
    if (!this.element) throw(Effect._elementDoesNotExistError);
    var options = Object.extend({
      x:    0,
      y:    0,
      mode: 'relative'
    }, arguments[1] || { });
    this.start(options);
  },
  setup: function() {
    this.element.makePositioned();
    this.originalLeft = parseFloat(this.element.getStyle('left') || '0');
    this.originalTop  = parseFloat(this.element.getStyle('top')  || '0');
    if (this.options.mode == 'absolute') {
      this.options.x = this.options.x - this.originalLeft;
      this.options.y = this.options.y - this.originalTop;
    }
  },
  update: function(position) {
    this.element.setStyle({
      left: (this.options.x  * position + this.originalLeft).round() + 'px',
      top:  (this.options.y  * position + this.originalTop).round()  + 'px'
    });
  }
});

// for backwards compatibility
Effect.MoveBy = function(element, toTop, toLeft) {
  return new Effect.Move(element,
    Object.extend({ x: toLeft, y: toTop }, arguments[3] || { }));
};

Effect.Scale = Class.create(Effect.Base, {
  initialize: function(element, percent) {
    this.element = $(element);
    if (!this.element) throw(Effect._elementDoesNotExistError);
    var options = Object.extend({
      scaleX: true,
      scaleY: true,
      scaleContent: true,
      scaleFromCenter: false,
      scaleMode: 'box',        // 'box' or 'contents' or { } with provided values
      scaleFrom: 100.0,
      scaleTo:   percent
    }, arguments[2] || { });
    this.start(options);
  },
  setup: function() {
    this.restoreAfterFinish = this.options.restoreAfterFinish || false;
    this.elementPositioning = this.element.getStyle('position');

    this.originalStyle = { };
    ['top','left','width','height','fontSize'].each( function(k) {
      this.originalStyle[k] = this.element.style[k];
    }.bind(this));

    this.originalTop  = this.element.offsetTop;
    this.originalLeft = this.element.offsetLeft;

    var fontSize = this.element.getStyle('font-size') || '100%';
    ['em','px','%','pt'].each( function(fontSizeType) {
      if (fontSize.indexOf(fontSizeType)>0) {
        this.fontSize     = parseFloat(fontSize);
        this.fontSizeType = fontSizeType;
      }
    }.bind(this));

    this.factor = (this.options.scaleTo - this.options.scaleFrom)/100;

    this.dims = null;
    if (this.options.scaleMode=='box')
      this.dims = [this.element.offsetHeight, this.element.offsetWidth];
    if (/^content/.test(this.options.scaleMode))
      this.dims = [this.element.scrollHeight, this.element.scrollWidth];
    if (!this.dims)
      this.dims = [this.options.scaleMode.originalHeight,
                   this.options.scaleMode.originalWidth];
  },
  update: function(position) {
    var currentScale = (this.options.scaleFrom/100.0) + (this.factor * position);
    if (this.options.scaleContent && this.fontSize)
      this.element.setStyle({fontSize: this.fontSize * currentScale + this.fontSizeType });
    this.setDimensions(this.dims[0] * currentScale, this.dims[1] * currentScale);
  },
  finish: function(position) {
    if (this.restoreAfterFinish) this.element.setStyle(this.originalStyle);
  },
  setDimensions: function(height, width) {
    var d = { };
    if (this.options.scaleX) d.width = width.round() + 'px';
    if (this.options.scaleY) d.height = height.round() + 'px';
    if (this.options.scaleFromCenter) {
      var topd  = (height - this.dims[0])/2;
      var leftd = (width  - this.dims[1])/2;
      if (this.elementPositioning == 'absolute') {
        if (this.options.scaleY) d.top = this.originalTop-topd + 'px';
        if (this.options.scaleX) d.left = this.originalLeft-leftd + 'px';
      } else {
        if (this.options.scaleY) d.top = -topd + 'px';
        if (this.options.scaleX) d.left = -leftd + 'px';
      }
    }
    this.element.setStyle(d);
  }
});

Effect.Highlight = Class.create(Effect.Base, {
  initialize: function(element) {
    this.element = $(element);
    if (!this.element) throw(Effect._elementDoesNotExistError);
    var options = Object.extend({ startcolor: '#ffff99' }, arguments[1] || { });
    this.start(options);
  },
  setup: function() {
    // Prevent executing on elements not in the layout flow
    if (this.element.getStyle('display')=='none') { this.cancel(); return; }
    // Disable background image during the effect
    this.oldStyle = { };
    if (!this.options.keepBackgroundImage) {
      this.oldStyle.backgroundImage = this.element.getStyle('background-image');
      this.element.setStyle({backgroundImage: 'none'});
    }
    if (!this.options.endcolor)
      this.options.endcolor = this.element.getStyle('background-color').parseColor('#ffffff');
    if (!this.options.restorecolor)
      this.options.restorecolor = this.element.getStyle('background-color');
    // init color calculations
    this._base  = $R(0,2).map(function(i){ return parseInt(this.options.startcolor.slice(i*2+1,i*2+3),16) }.bind(this));
    this._delta = $R(0,2).map(function(i){ return parseInt(this.options.endcolor.slice(i*2+1,i*2+3),16)-this._base[i] }.bind(this));
  },
  update: function(position) {
    this.element.setStyle({backgroundColor: $R(0,2).inject('#',function(m,v,i){
      return m+((this._base[i]+(this._delta[i]*position)).round().toColorPart()); }.bind(this)) });
  },
  finish: function() {
    this.element.setStyle(Object.extend(this.oldStyle, {
      backgroundColor: this.options.restorecolor
    }));
  }
});

Effect.ScrollTo = function(element) {
  var options = arguments[1] || { },
  scrollOffsets = document.viewport.getScrollOffsets(),
  elementOffsets = $(element).cumulativeOffset();

  if (options.offset) elementOffsets[1] += options.offset;

  return new Effect.Tween(null,
    scrollOffsets.top,
    elementOffsets[1],
    options,
    function(p){ scrollTo(scrollOffsets.left, p.round()); }
  );
};

/* ------------- combination effects ------------- */

Effect.Fade = function(element) {
  element = $(element);
  var oldOpacity = element.getInlineOpacity();
  var options = Object.extend({
    from: element.getOpacity() || 1.0,
    to:   0.0,
    afterFinishInternal: function(effect) {
      if (effect.options.to!=0) return;
      effect.element.hide().setStyle({opacity: oldOpacity});
    }
  }, arguments[1] || { });
  return new Effect.Opacity(element,options);
};

Effect.Appear = function(element) {
  element = $(element);
  var options = Object.extend({
  from: (element.getStyle('display') == 'none' ? 0.0 : element.getOpacity() || 0.0),
  to:   1.0,
  // force Safari to render floated elements properly
  afterFinishInternal: function(effect) {
    effect.element.forceRerendering();
  },
  beforeSetup: function(effect) {
    effect.element.setOpacity(effect.options.from).show();
  }}, arguments[1] || { });
  return new Effect.Opacity(element,options);
};

Effect.Puff = function(element) {
  element = $(element);
  var oldStyle = {
    opacity: element.getInlineOpacity(),
    position: element.getStyle('position'),
    top:  element.style.top,
    left: element.style.left,
    width: element.style.width,
    height: element.style.height
  };
  return new Effect.Parallel(
   [ new Effect.Scale(element, 200,
      { sync: true, scaleFromCenter: true, scaleContent: true, restoreAfterFinish: true }),
     new Effect.Opacity(element, { sync: true, to: 0.0 } ) ],
     Object.extend({ duration: 1.0,
      beforeSetupInternal: function(effect) {
        Position.absolutize(effect.effects[0].element);
      },
      afterFinishInternal: function(effect) {
         effect.effects[0].element.hide().setStyle(oldStyle); }
     }, arguments[1] || { })
   );
};

Effect.BlindUp = function(element) {
  element = $(element);
  element.makeClipping();
  return new Effect.Scale(element, 0,
    Object.extend({ scaleContent: false,
      scaleX: false,
      restoreAfterFinish: true,
      afterFinishInternal: function(effect) {
        effect.element.hide().undoClipping();
      }
    }, arguments[1] || { })
  );
};

Effect.BlindDown = function(element) {
  element = $(element);
  var elementDimensions = element.getDimensions();
  return new Effect.Scale(element, 100, Object.extend({
    scaleContent: false,
    scaleX: false,
    scaleFrom: 0,
    scaleMode: {originalHeight: elementDimensions.height, originalWidth: elementDimensions.width},
    restoreAfterFinish: true,
    afterSetup: function(effect) {
      effect.element.makeClipping().setStyle({height: '0px'}).show();
    },
    afterFinishInternal: function(effect) {
      effect.element.undoClipping();
    }
  }, arguments[1] || { }));
};

Effect.SwitchOff = function(element) {
  element = $(element);
  var oldOpacity = element.getInlineOpacity();
  return new Effect.Appear(element, Object.extend({
    duration: 0.4,
    from: 0,
    transition: Effect.Transitions.flicker,
    afterFinishInternal: function(effect) {
      new Effect.Scale(effect.element, 1, {
        duration: 0.3, scaleFromCenter: true,
        scaleX: false, scaleContent: false, restoreAfterFinish: true,
        beforeSetup: function(effect) {
          effect.element.makePositioned().makeClipping();
        },
        afterFinishInternal: function(effect) {
          effect.element.hide().undoClipping().undoPositioned().setStyle({opacity: oldOpacity});
        }
      });
    }
  }, arguments[1] || { }));
};

Effect.DropOut = function(element) {
  element = $(element);
  var oldStyle = {
    top: element.getStyle('top'),
    left: element.getStyle('left'),
    opacity: element.getInlineOpacity() };
  return new Effect.Parallel(
    [ new Effect.Move(element, {x: 0, y: 100, sync: true }),
      new Effect.Opacity(element, { sync: true, to: 0.0 }) ],
    Object.extend(
      { duration: 0.5,
        beforeSetup: function(effect) {
          effect.effects[0].element.makePositioned();
        },
        afterFinishInternal: function(effect) {
          effect.effects[0].element.hide().undoPositioned().setStyle(oldStyle);
        }
      }, arguments[1] || { }));
};

Effect.Shake = function(element) {
  element = $(element);
  var options = Object.extend({
    distance: 20,
    duration: 0.5
  }, arguments[1] || {});
  var distance = parseFloat(options.distance);
  var split = parseFloat(options.duration) / 10.0;
  var oldStyle = {
    top: element.getStyle('top'),
    left: element.getStyle('left') };
    return new Effect.Move(element,
      { x:  distance, y: 0, duration: split, afterFinishInternal: function(effect) {
    new Effect.Move(effect.element,
      { x: -distance*2, y: 0, duration: split*2,  afterFinishInternal: function(effect) {
    new Effect.Move(effect.element,
      { x:  distance*2, y: 0, duration: split*2,  afterFinishInternal: function(effect) {
    new Effect.Move(effect.element,
      { x: -distance*2, y: 0, duration: split*2,  afterFinishInternal: function(effect) {
    new Effect.Move(effect.element,
      { x:  distance*2, y: 0, duration: split*2,  afterFinishInternal: function(effect) {
    new Effect.Move(effect.element,
      { x: -distance, y: 0, duration: split, afterFinishInternal: function(effect) {
        effect.element.undoPositioned().setStyle(oldStyle);
  }}); }}); }}); }}); }}); }});
};

Effect.SlideDown = function(element) {
  element = $(element).cleanWhitespace();
  // SlideDown need to have the content of the element wrapped in a container element with fixed height!
  var oldInnerBottom = element.down().getStyle('bottom');
  var elementDimensions = element.getDimensions();
  return new Effect.Scale(element, 100, Object.extend({
    scaleContent: false,
    scaleX: false,
    scaleFrom: window.opera ? 0 : 1,
    scaleMode: {originalHeight: elementDimensions.height, originalWidth: elementDimensions.width},
    restoreAfterFinish: true,
    afterSetup: function(effect) {
      effect.element.makePositioned();
      effect.element.down().makePositioned();
      if (window.opera) effect.element.setStyle({top: ''});
      effect.element.makeClipping().setStyle({height: '0px'}).show();
    },
    afterUpdateInternal: function(effect) {
      effect.element.down().setStyle({bottom:
        (effect.dims[0] - effect.element.clientHeight) + 'px' });
    },
    afterFinishInternal: function(effect) {
      effect.element.undoClipping().undoPositioned();
      effect.element.down().undoPositioned().setStyle({bottom: oldInnerBottom}); }
    }, arguments[1] || { })
  );
};

Effect.SlideUp = function(element) {
  element = $(element).cleanWhitespace();
  var oldInnerBottom = element.down().getStyle('bottom');
  var elementDimensions = element.getDimensions();
  return new Effect.Scale(element, window.opera ? 0 : 1,
   Object.extend({ scaleContent: false,
    scaleX: false,
    scaleMode: 'box',
    scaleFrom: 100,
    scaleMode: {originalHeight: elementDimensions.height, originalWidth: elementDimensions.width},
    restoreAfterFinish: true,
    afterSetup: function(effect) {
      effect.element.makePositioned();
      effect.element.down().makePositioned();
      if (window.opera) effect.element.setStyle({top: ''});
      effect.element.makeClipping().show();
    },
    afterUpdateInternal: function(effect) {
      effect.element.down().setStyle({bottom:
        (effect.dims[0] - effect.element.clientHeight) + 'px' });
    },
    afterFinishInternal: function(effect) {
      effect.element.hide().undoClipping().undoPositioned();
      effect.element.down().undoPositioned().setStyle({bottom: oldInnerBottom});
    }
   }, arguments[1] || { })
  );
};

// Bug in opera makes the TD containing this element expand for a instance after finish
Effect.Squish = function(element) {
  return new Effect.Scale(element, window.opera ? 1 : 0, {
    restoreAfterFinish: true,
    beforeSetup: function(effect) {
      effect.element.makeClipping();
    },
    afterFinishInternal: function(effect) {
      effect.element.hide().undoClipping();
    }
  });
};

Effect.Grow = function(element) {
  element = $(element);
  var options = Object.extend({
    direction: 'center',
    moveTransition: Effect.Transitions.sinoidal,
    scaleTransition: Effect.Transitions.sinoidal,
    opacityTransition: Effect.Transitions.full
  }, arguments[1] || { });
  var oldStyle = {
    top: element.style.top,
    left: element.style.left,
    height: element.style.height,
    width: element.style.width,
    opacity: element.getInlineOpacity() };

  var dims = element.getDimensions();
  var initialMoveX, initialMoveY;
  var moveX, moveY;

  switch (options.direction) {
    case 'top-left':
      initialMoveX = initialMoveY = moveX = moveY = 0;
      break;
    case 'top-right':
      initialMoveX = dims.width;
      initialMoveY = moveY = 0;
      moveX = -dims.width;
      break;
    case 'bottom-left':
      initialMoveX = moveX = 0;
      initialMoveY = dims.height;
      moveY = -dims.height;
      break;
    case 'bottom-right':
      initialMoveX = dims.width;
      initialMoveY = dims.height;
      moveX = -dims.width;
      moveY = -dims.height;
      break;
    case 'center':
      initialMoveX = dims.width / 2;
      initialMoveY = dims.height / 2;
      moveX = -dims.width / 2;
      moveY = -dims.height / 2;
      break;
  }

  return new Effect.Move(element, {
    x: initialMoveX,
    y: initialMoveY,
    duration: 0.01,
    beforeSetup: function(effect) {
      effect.element.hide().makeClipping().makePositioned();
    },
    afterFinishInternal: function(effect) {
      new Effect.Parallel(
        [ new Effect.Opacity(effect.element, { sync: true, to: 1.0, from: 0.0, transition: options.opacityTransition }),
          new Effect.Move(effect.element, { x: moveX, y: moveY, sync: true, transition: options.moveTransition }),
          new Effect.Scale(effect.element, 100, {
            scaleMode: { originalHeight: dims.height, originalWidth: dims.width },
            sync: true, scaleFrom: window.opera ? 1 : 0, transition: options.scaleTransition, restoreAfterFinish: true})
        ], Object.extend({
             beforeSetup: function(effect) {
               effect.effects[0].element.setStyle({height: '0px'}).show();
             },
             afterFinishInternal: function(effect) {
               effect.effects[0].element.undoClipping().undoPositioned().setStyle(oldStyle);
             }
           }, options)
      );
    }
  });
};

Effect.Shrink = function(element) {
  element = $(element);
  var options = Object.extend({
    direction: 'center',
    moveTransition: Effect.Transitions.sinoidal,
    scaleTransition: Effect.Transitions.sinoidal,
    opacityTransition: Effect.Transitions.none
  }, arguments[1] || { });
  var oldStyle = {
    top: element.style.top,
    left: element.style.left,
    height: element.style.height,
    width: element.style.width,
    opacity: element.getInlineOpacity() };

  var dims = element.getDimensions();
  var moveX, moveY;

  switch (options.direction) {
    case 'top-left':
      moveX = moveY = 0;
      break;
    case 'top-right':
      moveX = dims.width;
      moveY = 0;
      break;
    case 'bottom-left':
      moveX = 0;
      moveY = dims.height;
      break;
    case 'bottom-right':
      moveX = dims.width;
      moveY = dims.height;
      break;
    case 'center':
      moveX = dims.width / 2;
      moveY = dims.height / 2;
      break;
  }

  return new Effect.Parallel(
    [ new Effect.Opacity(element, { sync: true, to: 0.0, from: 1.0, transition: options.opacityTransition }),
      new Effect.Scale(element, window.opera ? 1 : 0, { sync: true, transition: options.scaleTransition, restoreAfterFinish: true}),
      new Effect.Move(element, { x: moveX, y: moveY, sync: true, transition: options.moveTransition })
    ], Object.extend({
         beforeStartInternal: function(effect) {
           effect.effects[0].element.makePositioned().makeClipping();
         },
         afterFinishInternal: function(effect) {
           effect.effects[0].element.hide().undoClipping().undoPositioned().setStyle(oldStyle); }
       }, options)
  );
};

Effect.Pulsate = function(element) {
  element = $(element);
  var options    = arguments[1] || { },
    oldOpacity = element.getInlineOpacity(),
    transition = options.transition || Effect.Transitions.linear,
    reverser   = function(pos){
      return 1 - transition((-Math.cos((pos*(options.pulses||5)*2)*Math.PI)/2) + .5);
    };

  return new Effect.Opacity(element,
    Object.extend(Object.extend({  duration: 2.0, from: 0,
      afterFinishInternal: function(effect) { effect.element.setStyle({opacity: oldOpacity}); }
    }, options), {transition: reverser}));
};

Effect.Fold = function(element) {
  element = $(element);
  var oldStyle = {
    top: element.style.top,
    left: element.style.left,
    width: element.style.width,
    height: element.style.height };
  element.makeClipping();
  return new Effect.Scale(element, 5, Object.extend({
    scaleContent: false,
    scaleX: false,
    afterFinishInternal: function(effect) {
    new Effect.Scale(element, 1, {
      scaleContent: false,
      scaleY: false,
      afterFinishInternal: function(effect) {
        effect.element.hide().undoClipping().setStyle(oldStyle);
      } });
  }}, arguments[1] || { }));
};

Effect.Morph = Class.create(Effect.Base, {
  initialize: function(element) {
    this.element = $(element);
    if (!this.element) throw(Effect._elementDoesNotExistError);
    var options = Object.extend({
      style: { }
    }, arguments[1] || { });

    if (!Object.isString(options.style)) this.style = $H(options.style);
    else {
      if (options.style.include(':'))
        this.style = options.style.parseStyle();
      else {
        this.element.addClassName(options.style);
        this.style = $H(this.element.getStyles());
        this.element.removeClassName(options.style);
        var css = this.element.getStyles();
        this.style = this.style.reject(function(style) {
          return style.value == css[style.key];
        });
        options.afterFinishInternal = function(effect) {
          effect.element.addClassName(effect.options.style);
          effect.transforms.each(function(transform) {
            effect.element.style[transform.style] = '';
          });
        };
      }
    }
    this.start(options);
  },

  setup: function(){
    function parseColor(color){
      if (!color || ['rgba(0, 0, 0, 0)','transparent'].include(color)) color = '#ffffff';
      color = color.parseColor();
      return $R(0,2).map(function(i){
        return parseInt( color.slice(i*2+1,i*2+3), 16 );
      });
    }
    this.transforms = this.style.map(function(pair){
      var property = pair[0], value = pair[1], unit = null;

      if (value.parseColor('#zzzzzz') != '#zzzzzz') {
        value = value.parseColor();
        unit  = 'color';
      } else if (property == 'opacity') {
        value = parseFloat(value);
        if (Prototype.Browser.IE && (!this.element.currentStyle.hasLayout))
          this.element.setStyle({zoom: 1});
      } else if (Element.CSS_LENGTH.test(value)) {
          var components = value.match(/^([\+\-]?[0-9\.]+)(.*)$/);
          value = parseFloat(components[1]);
          unit = (components.length == 3) ? components[2] : null;
      }

      var originalValue = this.element.getStyle(property);
      return {
        style: property.camelize(),
        originalValue: unit=='color' ? parseColor(originalValue) : parseFloat(originalValue || 0),
        targetValue: unit=='color' ? parseColor(value) : value,
        unit: unit
      };
    }.bind(this)).reject(function(transform){
      return (
        (transform.originalValue == transform.targetValue) ||
        (
          transform.unit != 'color' &&
          (isNaN(transform.originalValue) || isNaN(transform.targetValue))
        )
      );
    });
  },
  update: function(position) {
    var style = { }, transform, i = this.transforms.length;
    while(i--)
      style[(transform = this.transforms[i]).style] =
        transform.unit=='color' ? '#'+
          (Math.round(transform.originalValue[0]+
            (transform.targetValue[0]-transform.originalValue[0])*position)).toColorPart() +
          (Math.round(transform.originalValue[1]+
            (transform.targetValue[1]-transform.originalValue[1])*position)).toColorPart() +
          (Math.round(transform.originalValue[2]+
            (transform.targetValue[2]-transform.originalValue[2])*position)).toColorPart() :
        (transform.originalValue +
          (transform.targetValue - transform.originalValue) * position).toFixed(3) +
            (transform.unit === null ? '' : transform.unit);
    this.element.setStyle(style, true);
  }
});

Effect.Transform = Class.create({
  initialize: function(tracks){
    this.tracks  = [];
    this.options = arguments[1] || { };
    this.addTracks(tracks);
  },
  addTracks: function(tracks){
    tracks.each(function(track){
      track = $H(track);
      var data = track.values().first();
      this.tracks.push($H({
        ids:     track.keys().first(),
        effect:  Effect.Morph,
        options: { style: data }
      }));
    }.bind(this));
    return this;
  },
  play: function(){
    return new Effect.Parallel(
      this.tracks.map(function(track){
        var ids = track.get('ids'), effect = track.get('effect'), options = track.get('options');
        var elements = [$(ids) || $$(ids)].flatten();
        return elements.map(function(e){ return new effect(e, Object.extend({ sync:true }, options)) });
      }).flatten(),
      this.options
    );
  }
});

Element.CSS_PROPERTIES = $w(
  'backgroundColor backgroundPosition borderBottomColor borderBottomStyle ' +
  'borderBottomWidth borderLeftColor borderLeftStyle borderLeftWidth ' +
  'borderRightColor borderRightStyle borderRightWidth borderSpacing ' +
  'borderTopColor borderTopStyle borderTopWidth bottom clip color ' +
  'fontSize fontWeight height left letterSpacing lineHeight ' +
  'marginBottom marginLeft marginRight marginTop markerOffset maxHeight '+
  'maxWidth minHeight minWidth opacity outlineColor outlineOffset ' +
  'outlineWidth paddingBottom paddingLeft paddingRight paddingTop ' +
  'right textIndent top width wordSpacing zIndex');

Element.CSS_LENGTH = /^(([\+\-]?[0-9\.]+)(em|ex|px|in|cm|mm|pt|pc|\%))|0$/;

String.__parseStyleElement = document.createElement('div');
String.prototype.parseStyle = function(){
  var style, styleRules = $H();
  if (Prototype.Browser.WebKit)
    style = new Element('div',{style:this}).style;
  else {
    String.__parseStyleElement.innerHTML = '<div style="' + this + '"></div>';
    style = String.__parseStyleElement.childNodes[0].style;
  }

  Element.CSS_PROPERTIES.each(function(property){
    if (style[property]) styleRules.set(property, style[property]);
  });

  if (Prototype.Browser.IE && this.include('opacity'))
    styleRules.set('opacity', this.match(/opacity:\s*((?:0|1)?(?:\.\d*)?)/)[1]);

  return styleRules;
};

if (document.defaultView && document.defaultView.getComputedStyle) {
  Element.getStyles = function(element) {
    var css = document.defaultView.getComputedStyle($(element), null);
    return Element.CSS_PROPERTIES.inject({ }, function(styles, property) {
      styles[property] = css[property];
      return styles;
    });
  };
} else {
  Element.getStyles = function(element) {
    element = $(element);
    var css = element.currentStyle, styles;
    styles = Element.CSS_PROPERTIES.inject({ }, function(results, property) {
      results[property] = css[property];
      return results;
    });
    if (!styles.opacity) styles.opacity = element.getOpacity();
    return styles;
  };
}

Effect.Methods = {
  morph: function(element, style) {
    element = $(element);
    new Effect.Morph(element, Object.extend({ style: style }, arguments[2] || { }));
    return element;
  },
  visualEffect: function(element, effect, options) {
    element = $(element);
    var s = effect.dasherize().camelize(), klass = s.charAt(0).toUpperCase() + s.substring(1);
    new Effect[klass](element, options);
    return element;
  },
  highlight: function(element, options) {
    element = $(element);
    new Effect.Highlight(element, options);
    return element;
  }
};

$w('fade appear grow shrink fold blindUp blindDown slideUp slideDown '+
  'pulsate shake puff squish switchOff dropOut').each(
  function(effect) {
    Effect.Methods[effect] = function(element, options){
      element = $(element);
      Effect[effect.charAt(0).toUpperCase() + effect.substring(1)](element, options);
      return element;
    };
  }
);

$w('getInlineOpacity forceRerendering setContentZoom collectTextNodes collectTextNodesIgnoreClass getStyles').each(
  function(f) { Effect.Methods[f] = Element[f]; }
);

Element.addMethods(Effect.Methods);function init(){}//delete me

function getCookie(Name){
    return AltrecGlobals.Functions.Cookies.get(Name);
}


function setCookie(name, value, days){
	 AltrecGlobals.Functions.Cookies.set({
		name : name,
		days : days,
		value : value
	 });
}

function queryStringReferrer(key){
	try {var params = document.referrer.split('?')[1].split('&'); 
	for (var i=0; i<args.length; i++) if (params[i].split('=')[0] == key) return params[i].split('=')[1];
	} catch(e){} return false;
}


function gup(name)
{
    name = name.replace(/[\[]/, "\\\[").replace(/[\]]/, "\\\]");
    var regexS = "[\\?&]" + name + "=([^&#]*)";
    var regex = new RegExp(regexS);
    var results = regex.exec(window.location.href);
    return (results == null) ? "" : results[1];
}

function launchURL(url, w, h, scroll, resize, title, status){
    var features = new Array();
    features = "height=" + h + ",width=" + w + ",scrollbars=" + scroll + ",resizable=" + resize + ",toolbar=0,location=0";
    window.open(url, title, features);
    window.name = "main";
}


function checkSignup(myform){
    myemail = myform.email.value;
    if (myemail.indexOf('@') < 0 || myemail.indexOf('.') < 0) {
        alert("Please enter a valid e-mail address to sign up.");
        return (false);
    }
    else {
        return (true)
    }
}

String.prototype.trim = function() {return this.replace(/^\s+|\s+$/g, "");};
String.prototype.ltrim = function(){return this.replace(/^\s+/, "");};
String.prototype.rtrim = function(){return this.replace(/\s+$/, "");};
String.prototype.contains = function(str){return this.indexOf(str) >= 0;};

function FormatCurrency(AmountNumber, withDollarSign)
{
	var num = Number(AmountNumber);
    var amount = Math.round(num * 100) / 100;
    amount = amount.toFixed(2);
    
    var delimiter = ","; // replace comma if desired
    var a = amount.split('.', 2);
    var d = a[1];
    var i = parseInt(a[0]);
    if (isNaN(i)) 
    { return ''; }
    var minus = '';
    if (i < 0) 
    {
        minus = '-';
    }
    i = Math.abs(i);
    var n = new String(i);
    var a = [];
    while (n.length > 3) 
    {
        var nn = n.substr(n.length - 3);
        a.unshift(nn);
        n = n.substr(0, n.length - 3);
    }
    if (n.length > 0) 
    {
        a.unshift(n);
    }
    n = a.join(delimiter);
    if (d.length < 1) 
    {
        amount = n;
    }
    else 
    {
        amount = n + '.' + d;
    }
    amount = minus + amount;
    return ((withDollarSign) ? "$" : "") + amount;
}

function pausescroller(content, divId, divClass, delay){
    this.content = content; 
    this.tickerid = divId; 
    this.delay = delay; 
    this.mouseoverBol = 0; 
    this.hiddendivpointer = 1; 
    document.write('<div id="' + divId + '" class="' + divClass + '" style="overflow: hidden"><div class="innerDiv" style="position: absolute; width: 100%" id="' + divId + '1">' + content[0] + '</div><div class="innerDiv" style="position: absolute; width: 100%; visibility: hidden" id="' + divId + '2">' + content[1] + '</div></div>');
    var scrollerinstance = this;
    if (window.addEventListener) 
        window.addEventListener("load", function(){
            scrollerinstance.initialize()
        }, false);
    else 
        if (window.attachEvent) 
            window.attachEvent("onload", function(){
                scrollerinstance.initialize()
            });
        else 
            if (document.getElementById) 
                setTimeout(function(){
                    scrollerinstance.initialize()
                }, 700);
}


pausescroller.prototype.initialize = function(){
    this.tickerdiv = document.getElementById(this.tickerid);
    this.visiblediv = document.getElementById(this.tickerid + "1");
    this.hiddendiv = document.getElementById(this.tickerid + "2");
    this.visibledivtop = parseInt(pausescroller.getCSSpadding(this.tickerdiv));
    this.visiblediv.style.width = this.hiddendiv.style.width = this.tickerdiv.offsetWidth - (this.visibledivtop * 2) + "px";
    this.getinline(this.visiblediv, this.hiddendiv);
    this.hiddendiv.style.visibility = "visible";
    var scrollerinstance = this;
    document.getElementById(this.tickerid).onmouseover = function(){
        scrollerinstance.mouseoverBol = 1
    };
    document.getElementById(this.tickerid).onmouseout = function(){
        scrollerinstance.mouseoverBol = 0
    };
    if (window.attachEvent) 
        window.attachEvent("onunload", function(){
            scrollerinstance.tickerdiv.onmouseover = scrollerinstance.tickerdiv.onmouseout = null
        });
    setTimeout(function(){
        scrollerinstance.animateup()
    }, this.delay);
}


pausescroller.prototype.animateup = function(){
    var scrollerinstance = this;
    if (parseInt(this.hiddendiv.style.top) > (this.visibledivtop + 5)) {
        this.visiblediv.style.top = parseInt(this.visiblediv.style.top) - 5 + "px";
        this.hiddendiv.style.top = parseInt(this.hiddendiv.style.top) - 5 + "px";
        setTimeout(function(){
            scrollerinstance.animateup()
        }, 50);
    }
    else {
        this.getinline(this.hiddendiv, this.visiblediv);
        this.swapdivs();
        setTimeout(function(){
            scrollerinstance.setmessage()
        }, this.delay);
    }
}


pausescroller.prototype.swapdivs = function(){
    var tempcontainer = this.visiblediv;
    this.visiblediv = this.hiddendiv;
    this.hiddendiv = tempcontainer;
}

pausescroller.prototype.getinline = function(div1, div2){
    div1.style.top = this.visibledivtop + "px";
    div2.style.top = Math.max(div1.parentNode.offsetHeight, div1.offsetHeight) + "px";
}


pausescroller.prototype.setmessage = function(){
    var scrollerinstance = this;
    if (this.mouseoverBol == 1) 
        setTimeout(function(){
            scrollerinstance.setmessage()
        }, 100);
    else {
        var i = this.hiddendivpointer;
        var ceiling = this.content.length;
        this.hiddendivpointer = (i + 1 > ceiling - 1) ? 0 : i + 1;
        this.hiddendiv.innerHTML = this.content[this.hiddendivpointer];
        this.animateup();
    }
}

pausescroller.getCSSpadding = function(tickerobj){ 
    if (tickerobj.currentStyle) 
        return tickerobj.currentStyle["paddingTop"];
    else 
        if (window.getComputedStyle) 
            return window.getComputedStyle(tickerobj, "").getPropertyValue("padding-top");
        else 
            return 0;
}


var url = window.location.href;
if (![].forEach) 
  Array.prototype.forEach = Array.prototype.each;
/*
 EventEmitter v1.1.3
 Copyright 2011, Oliver Caldwell (flowdev.co.uk)
 Dual licensed under the MIT or GPL Version 2 licenses.
 https://github.com/Wolfy87/EventEmitter
 */
var EventEmitter = null;
(function() {
  function b() {
    this._events = {};
    this._listeners = [];
    this._maxListeners = 10
  }
  b.prototype._convertNameToRegExp = function(a) {
    return a.replace(/\./g, "\\.").replace(/\*\\\./g, "[\\w\\-]+\\.").replace(/\*$/gi, ".+")
  };
  b.prototype.addListener = function(a, e, c) {
    var d = this._listeners.length;
    this.emit("newListener", a, e);
    this._listeners.push({
      listener: e,
      once: c ? !0 : !1
    });
    a = this._convertNameToRegExp(a);
    typeof this._events[a] === "undefined" && (this._events[a] = []);
    this._events[a].push(d);
    this._events[a].length === this._maxListeners &&
    console.log("Maximum number of listeners (" + this._maxListeners + ') reached for the "' + a + '" event!');
    return this
  };
  b.prototype.on = b.prototype.addListener;
  b.prototype.once = function(a, e) {
    return this.addListener(a, e, !0)
  };
  b.prototype.removeListener = function(a, e) {
    var c = null, d = null;
    a = this._convertNameToRegExp(a);
    if (this._events[a] instanceof Array) {
      d = this._events[a];
      for (c = 0; c < d.length; c++) 
        this._listeners[d[c]].listener === e && d.splice(c, 1)
    }
    return this
  };
  b.prototype.removeAllListeners = function(a) {
    a = this._convertNameToRegExp(a);
    this._events[a] = [];
    return this
  };
  b.prototype.setMaxListeners = function(a) {
    this._maxListeners = a;
    return this
  };
  b.prototype.listeners = function(a, e) {
    var c = null, d = null, b = [], f = null;
    for (c in this._events) 
      if (a.match(RegExp("^" + c + "$")) && this._events[c] instanceof Array) {
        indexes = this._events[c];
        for (d = 0; d < indexes.length; d++) 
          f = this._listeners[indexes[d]], e ? f.once ? (b.push(f.listener), this._events[c].splice(d, 1)) : b.push(f.listener) : b.push(f.listener)
      }
    return b
  };
  b.prototype.emit = function(a) {
    var b = null, c = Array.prototype.slice.call(arguments), d = this.listeners(a, !0);
    if (d.length === 0) 
      if (a === "error") 
        throw "unspecifiedErrorEvent";
      else 
        return !1;
    c.splice(0, 1);
    for (b = 0; b < d.length; b++) 
      d[b].apply(null, c);
    return !0
  };
  EventEmitter = b
})();

var instanceObjects = {};
var InstanceContainer = {
  Init: function() {
    this.hasBeenInit = true;
  },
  GetInstance: function(id) {
    return instanceObjects[id];
  },
  RegisterInstance: function(obj, idProp) {
    if (!this.hasBeenInit) {
      this.Init();
    }
    if (!idProp) 
      idProp = 'id';
    instanceObjects[obj[idProp]] = obj;
  },
  FireEvent: function(id, event) {
    if (arguments.length > 2) 
      this.GetInstance(id)[event](arguments[2]);
    else 
      this.GetInstance(id)[event]();
  },
  FireEventAsync: function(id, event, delay) {
    var args = '';
    if (arguments.length > 3) {
      args = ", '" + arguments[3] + "'";
    }
    
    return setTimeout("try{InstanceContainer.FireEvent('" + id + "', '" + event + "'" + args + ");}catch(e){}", delay);
  },
  destroy: function() {
    var objList = [];
    for (var obj in instanceObjects) {
      objList.push(obj);
    }
    
    for (var i = 0; i < objList.length; i++) {
      instanceObjects[objList[i]] = null;
    }
    instanceObjects[objList[i]] = null;
    
  }
};


var AltrecGlobals = {
  Variables: {
    freeShippingTreashold: 45,
    Messages: {
      ServerBusy: "We're sorry, we're having technical difficulties. Please call our Customer Service team to complete your order: <b>(800) 369-3949</b>."
    }
  },
  Site: {
    LoggedInUser: null,
    DomainPrefix: "",
    isUserLoggedIn: function() {
      if (this.LoggedInUser) 
        return true;
      else 
        return false;
    },
    Search: {
      autocompleteEnabled: true,
      minChars: 3,
      msDelay: 0,
      GetUrl: function() {
        return AltrecGlobals.Site.AjaxPrefix + '/servlets/autocomplete';
      },
      Init: function() {
        PayloadMediator.renderPayload('SearchPayload', {
          defaultText: 'Search our Site'
        });
      }
    },
    getHiddenKeysHandler: function() {
      if (!this.hiddenKeysHandlerVar) {
        this.hiddenKeysHandlerVar = new this.hiddenKeysHandler();
      }
      return this.hiddenKeysHandlerVar;
    },
    hiddenKeysHandler: function() {
      var eventKeyLoop = null;
      var self = this;
      function getEventKeyLoop() {
        if (!eventKeyLoop) {
          eventKeyLoop = [];
          var ade = self.addKeyEvent;
          
          ade('I', function() {
            $('debugging').style.display = 'block';
          });
          ade('M', function() {
            alert('meow');
          });
          ade('T', function() {
            AltrecGlobals.Pages.Checkout.Timeout = 1000000;
            alert('Ajax timeout cancelled')
          });
          ade('R', function() {
            alert(PayloadMediator.renderStatistics);
          });
          ade('K', function() {
            $('debugging').innerHTML = '<img src="/images/shop/checkout/darth-easter.png" />';
          });
          ade('Q', function() {
            var str = '';
            for (var abtest in AltrecGlobals.Site.MultiVarInfo) {
              if (str.length > 0) {
                str += '\n';
              }
              str += abtest + "\t\t" + AltrecGlobals.Site.MultiVarInfo[abtest].val;
            }
            alert("Multi Variant Tests\n\n" + str);
          });
          ade('A', function() {
            var affInfoStr = getCookie('AffInf');
            alert(affInfoStr);
            var affInfo = eval('(' + eval('(' + affInfoStr + ')') + ')')[0];
            
            var output = "AFFILIATE COOKIE INFO\n\n";
            output += "\nNAME    : " + affInfo.t;
            output += "\nSESSION : " + affInfo.tr;
            output += "\nCREATE  : " + affInfo.c;
            output += "\nEXP     : " + affInfo.e;
            output += "\n\n------VALUE-----";
            for (var i in affInfo.v) {
              output += "\n   " + i + "   : " + affInfo.v[i];
            }
            alert(output);
          });
          ade('Z', function(){
            WebModal.Show('snippet_zip_popup',{width:300,callback: function(){ $('snippet_zip_code').activate() }});
          });
        }
        return eventKeyLoop;
      }
      
      this.addKeyEvent = function(key, func) {
        eventKeyLoop.push({
          'key': key,
          'func': func
        });
      };
      
      this.fire = function(event) {
        if (event.altKey && event.shiftKey) {
          getEventKeyLoop().each(function(eventObj) {
            if (event.keyCode == (Event['KEY_' + eventObj.key]) || event.keyCode == eventObj.key.charCodeAt(0)) {
              eventObj.func();
            }
          });
          
        }
      };
    }
  },
  Pages: {
    DefaultWidgetMode: 'edit',
    PageName: ''
  },
  Coremetrics: {},
  Links: {},
  Objects: {
    CMReg: {
      id: 'CMRegistration',
      fireCMReg: function(payloadData) {
        this.payloadData = payloadData;
        InstanceContainer.FireEventAsync(this.id, 'sendCMReg', 500);
      },
      sendCMReg: function() {
        var payloadData = this.payloadData;
        var key = payloadData.email + payloadData.BillingAddress.city + payloadData.BillingAddress.stateprov +
        payloadData.BillingAddress.postcode +
        payloadData.AccountAttribute.subscribedAltrec;
        
        if (this.lastKey != key) {
          this.lastKey = key;
          if (payloadData && payloadData.Customer && payloadData.email && payloadData.email.length > 0) 
            CreateRegistrationTag(payloadData.email, payloadData.email, payloadData.BillingAddress.city, payloadData.BillingAddress.stateprov, payloadData.BillingAddress.postcode, "Dispatch", ((payloadData.AccountAttribute.subscribedAltrec == 'true') ? "Y" : "N"));
        }
      }
      
    },
    Search: {
      getPageAttr: function(pageType, pageTypeValue, searchAlg) {
        var toolCode = gup('sto');
        if (toolCode) 
          toolCode = toolCode.toLowerCase();
        var toolStr = toolCode;
        var toolMap = {
          sfn: 'SearchBox',
          auto: 'AutoComplete',
          ddd: 'DailyDozenDeals',
          fct: 'facets',
          nv: 'NewNav'
        };
        for (var abbr in toolMap) 
          if (toolCode == abbr) 
            toolStr = toolMap[abbr];
        
        var facetData = [];
        ['color', 'gender', 'shop'].each(function(facet) {
          var facetVal = gup(facet);
          if (facetVal != null && facetVal.length > 0) {
            facetData.push(facet + ':' + facetVal);
          }
        });
        
        var cm_attr = [pageType, pageTypeValue, gup('st'), toolStr, facetData.join(","), gup('cn')];
        if (searchAlg) 
          cm_attr.push(searchAlg);
        return cm_attr;
        
      }
    },
    Date: {
      getWeekDay: function(dt) {
        if (!this.days) 
          this.days = ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'];
        return this.days[dt.getDay()];
      },
      getMonth: function(dt) {
        if (!this.months) 
          this.months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];
        return this.months[dt.getMonth()];
      },
      getDateObj: function(dt) {
        return {
          month: this.getMonth(dt),
          day: dt.getDate(),
          calMonth: 'cal' + this.getMonth(dt),
          calDay: 'cal' + dt.getDate(),
          year: dt.getFullYear(),
          shortDate: this.getMonth(dt) + ' ' + dt.getDate(),
          weekDay: this.getWeekDay(dt),
          date: dt
        };
      }
    }
  
  },
  Widgets: {}
};

InstanceContainer.RegisterInstance(AltrecGlobals.Objects.CMReg);


AltrecGlobals.Functions = {
  Element: {
    getFirstClassName: function(element, className, exact) {
      if (element.className && element.className.toLowerCase &&
      ((exact && element.className.toLowerCase() == className.toLowerCase()) ||
      (!exact && element.className.toLowerCase().contains(className.toLowerCase())))) 
        return element;
      
      for (var i = 0; i < element.childNodes.length; i++) {
        var val = AltrecGlobals.Functions.Element.getFirstClassName(element.childNodes[i], className, exact);
        if (val) 
          return val;
      }
      
      return null;
    },
    getByClassName: function(element, className, options, iteration) {
      var itr = (iteration) ? iteration : 1;
      if (options.recurseDepth && itr > options.recurseDepth) 
        return [];
      
      var ret = [];
      if (element && element.className && element.className.toLowerCase().indexOf(className.toLowerCase()) >= 0) {
        ret.push(element);
        if (options.stopChildRecurse) 
          return ret;
      }
      
      if (!element || !element.childNodes || element.childNodes.length == 0) 
        return ret;
      
      for (var i = 0; i < element.childNodes.length; i++) {
        ret = ret.concat(AltrecGlobals.Functions.Element.getByClassName(element.childNodes[i], className, options, itr + 1));
      }
      
      return ret;
    },
    Position: function(element) {
      this.width = Element.getWidth(element);
      this.height = Element.getHeight(element);
      this.top = Element.cumulativeOffset(element).top;
      this.left = Element.cumulativeOffset(element).left;
      this.element = element;
      
      this.right = this.left + this.width;
      this.bottom = this.top + this.height;
      
      this.within = function(x, y) {
        return (x >= this.left && x <= this.right && y <= this.bottom && y >= this.top);
      };
    }
  },
  Misc: {
    crumbs: false,
    getCrumbs: function(){
      if(!this.crumbs){
        var bc = $$('div.breadcrumbs a');
        var crumb_name = ['Home'];
        if(bc && bc.length > 0){
          for(var i=1; i < bc.length; i++){
            var n = bc[i].innerHTML;
            if(n.trim().length > 0){
              crumb_name.push(n.trim());
            };
          };
        }else{
          crumb_name = [];
        }
        this.crumbs = crumb_name;
      };
      return this.crumbs;
    }
  },
  LivePerson: {
    buttons: [], 
    init: function(state){
      this.buttons = $$('a.lpButtonOn');
      var newState = false;
      switch(state){
        case 'occupied':
          newState = 'lpButtonBusy';
          break;
        case 'offline':
          newState = 'lpButtonOff';
          break;
        default:
  
          break;
      };
      if(newState){
        for(var i=0; i<this.buttons.length; i++){
          var button = this.buttons[i];
          button.toggleClassName('lpButtonOn');
          button.toggleClassName(newState);
        };
      }
    } 
  },
  Cookies: {
    snarfedCookieName: 'altSNARF',
    snarfedCookies : ['postalcode','multivar','max_display','email','detail_bread_crumbs','checkoutCookie','bread_crumbs','bnDetailUrl'],
    snarfedValues: false,
    isSnarfed: function(name){
      return this.snarfedCookies.indexOf(name) >= 0;
    },
    loadSnarfedValues: function(){
      var value = this.get(this.snarfedCookieName);
      if(value){
        this.snarfedValues = {};
        var kvp = value.split('<<');
        for(var i=0;i<kvp.length;i++){
          var snarfbits = kvp[i].split('|');
          this.snarfedValues[snarfbits[0]] = snarfbits[1];
        }
      };
    },
	clearCombinedCache : function() {	
		this.snarfedValues = null;
	},
    packSnarfedValues: function(){
      var snarfed = [];
      for( var key in this.snarfedValues ){
        snarfed.push(key + '|' + this.snarfedValues[key])
      };
      return snarfed.join('<<');
    },
    unsetSnarfedValue: function(removeKey){
      var snarfed = [];
      for( var key in this.snarfedValues ){
        if(key != removeKey)
          snarfed.push(key + '|' + this.snarfedValues[key])
      };
      var v = snarfed.join('<<');
      var myDomain = document.domain;
      var myDomainParts = myDomain.split('.');
      myDomain = '.' + myDomainParts[myDomainParts.length - 2] + '.' + myDomainParts[myDomainParts.length - 1];
      var today = new Date();
      var expires = new Date();
      expires.setTime(today.getTime() + 1000 * 60 * 60 * 24 * 365);
      document.cookie = this.snarfedCookieName + "=" + escape(v) + "; expires=" + expires.toGMTString() + "; domain=" + myDomain + "; path=/";
    },
    getCurrentUser: function(){
      // stub until I get cookie name back
      var userId = this.get('cud');
      if(userId && userId.length > 4){
        return userId
      }else{
        return false;
      }
    },
    get: function(Name) {
      if(this.isSnarfed(Name)){
        if(!this.snarfedValues)
          this.loadSnarfedValues()
        return this.snarfedValues[Name]
      }else{
        var search = Name + "=";
        if (document.cookie.length > 0) {
          offset = document.cookie.indexOf(search);
          if (offset != -1) {
            offset += search.length;
            end = document.cookie.indexOf(";", offset);
            if (end == -1) 
              end = document.cookie.length;
            return unescape(document.cookie.substring(offset, end));
          }
        }
        return null;
      }
    },
    set: function(params) {
      var myDomain = document.domain;
      var myDomainParts = myDomain.split('.');
      myDomain = '.' + myDomainParts[myDomainParts.length - 2] + '.' + myDomainParts[myDomainParts.length - 1];
      if (params.domain) 
        myDomain = params.domain;
      
      var today = new Date();
      var expires = new Date();
      expires.setTime(today.getTime() + 1000 * 60 * 60 * 24 * (params.days || 365));
      if(this.isSnarfed(params.name)){
        if(!this.snarfedValues)
          this.loadSnarfedValues()
        this.snarfedValues[params.name] = params.value;
        params.name = this.snarfedCookieName;
        params.value = this.packSnarfedValues();
      };
      document.cookie = params.name + "=" + escape(params.value) + "; expires=" + expires.toGMTString() + "; domain=" + myDomain + "; path=/";
    }
  },
  Object: {
  
    isEmpty: function(obj) {
      for (var test in obj) 
        return false;
      return true;
    },
    CompareTo: function(obj1, obj2, deep, level) {
      if ((obj1 && !obj2) ||
      (!obj1 && obj2)) 
        return false;
      
      if (!obj1 && !obj2) 
        return true;
      
      if (!level) 
        level = 0;
      
      if ((Object.isString(obj1) && Object.isString(obj2)) ||
      (Object.isNumber(obj1) && Object.isNumber(obj2))) { return (obj1 === obj2); }
      
      if (Object.isArray(obj1) && Object.isArray(obj2)) {
        if (obj1.length != obj2.length) 
          return false;
        if (deep || level == 0) {
          for (var i = 0; i < obj1.length; i++) {
            if (!this.CompareTo(obj1[i], obj2[i], deep, (level + 1))) { return false; }
          }
          return true;
        }
      }
      
      if (!deep && level > 0) { return true; }
      
      var keys1 = Object.keys(obj1).sort();
      var keys2 = Object.keys(obj2).sort();
      if (keys1.length != keys2.length) 
        return false;
      
      for (var i = 0; i < keys1.length; i++) {
        if (!(this.CompareTo(obj1[keys1[i]], obj2[keys1[i]], deep, (level + 1)))) { return false; }
      }
      
      return true;
    },
    
    Inherit: function(baseClass, inheritingClass) {
      baseClass.call(inheritingClass);
      inheritingClass.base = {};
      
      var baseFunctions = {};
      for (i in inheritingClass) {
        if (Object.isFunction(inheritingClass[i])) {
          baseFunctions[i] = i;
        }
      }
      
      for (i in baseFunctions) {
        inheritingClass.base[i] = inheritingClass[i].bind(inheritingClass);
      }
    },
    Destroy: function(obj) {
      try {
        if (obj == null || obj == undefined) 
          return;
        
        if (Object.isString(obj) || Object.isNumber(obj)) { return; }
        
        if (Object.isArray(obj)) {
          for (var i = 0; i < obj.length; i++) {
            this.Destroy(obj[i]);
            obj[i] = null;
          }
        }
        else if (!Object.isFunction(obj)) {
          var keys = Object.keys(obj).sort();
          for (var i = 0; i < keys.length; i++) {
            this.Destroy(obj[keys[i]]);
            obj[keys[i]] = null;
          }
        }
        obj = null;
      } 
      catch (ex) {
      }
    }
  }
};



Element.addMethods({
  bindJSON: function(element, dataIslandAttrName, jsonObj) {
    element = $(element);
    bindJSON(element, dataIslandAttrName, jsonObj, true);
    return element;
  },
  findElementsByClassName: function(element, className) {
    element = $(element);
    if (Element.getElementsByClassName) 
      return Element.getElementsByClassName(element, className);
    else 
      return element.getElementsByClassName(className);
  }
});

var TimerQueue = {
  queue: new Array(),
  messages: new Array(),
  enabled: true,
  addTimer: function(timerID) {
    if (this.enabled) 
      this.queue.push({
        time: new Date(),
        id: timerID
      })
  },
  dequeueTimer: function() {
    try {
      if (!this.enabled) 
        return '';
      var now = new Date();
      var timerObj = this.queue.pop();
      this.messages.push(timerObj.id + ' took ' +
      (now.getTime() - timerObj.time.getTime()));
    } 
    catch (e) {
    }
  },
  
  getAllMessage: function(deliminator) {
    if (!this.enabled) 
      return '';
    var queueLength = this.queue.length;
    for (var i = 0; i < queueLength; i++) {
      this.dequeueTimer();
    }
    
    var returnStr = '';
    for (var i = 0; i < this.messages.length; i++) {
      returnStr += this.messages[i];
    }
    this.queue = [];
    this.messages = [];
    return returnStr;
  }
};

function PostForm(form, callback) {
  try {
    var inputs = form.getElementsByTagName('input');
    var parameters = {};
    for (var i = 0; i < inputs.length; i++) {
      var input = inputs[i];
      var type = input.getAttribute('type');
      if (!type) 
        type = input.type;
      if (type != 'submit' && type != 'button') {
        var name = input.getAttribute('name');
        var value = input.getAttribute('value');
        if (!name) 
          name = input.name;
        if (!value) 
          value = input.value;
        if(type == 'radio'){
          if(input.checked == true)
            parameters[name] = value;
        }else{
          parameters[name] = value;
        }
      };
    }
    var selects = form.getElementsByTagName('select');
    for (var i = 0; i < selects.length; i++) {
      var selectElement = selects[i];
      for (var i = 0; i < selectElement.options.length; i++) {
        var option = selectElement.options[i];
        if (option.selected) {
          if (!parameters[selectElement.name]) 
            parameters[selectElement.name] = [];
          parameters[selectElement.name].push(option.value);
        }
      }
    }

    var action = form.getAttribute('action');
    if (!action) 
      action = form.action;
    var request = new Ajax.Request(action, {
      method: 'post',
      parameters: parameters,
      onSuccess: function(transport) {
        var response = transport.responseText;
        callback(response);
      }
.bind(this)      ,
      onFailure: function(transport) {
        var response = transport.statusText;
        callback(response);
      }
.bind(this)
    });
  } 
  catch (e) {
  }
  
  return false;
}

function GetElement(elementId) {
  if (elementId.id) 
    return elementId;
  else 
    return document.getElementById(elementId);
}

var ProtoLite = {

  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(/Apple.*Mobile.*Safari/)
  },
  
  Object: {
    Extend: function(destination, source) {
      for (var property in source) 
        destination[property] = source[property];
      return destination;
    },
    CopyProperties: function(destination, source) {
      for (var property in source) {
        if (typeof source[property] != "function") {
          destination[property] = source[property];
        }
      }
      return destination;
    },
    CopyFunctions: function(destination, source) {
      for (var property in source) {
        if (typeof source[property] == "function") {
          destination[property] = source[property];
        }
      }
      return destination;
    },
    
    Clone: function(object) {
      return this.Extend({}, object);
    }
  },
  
  String: {
    capitalize: function(str) {
      return str.charAt(0).toUpperCase() +
      str.substring(1).toLowerCase();
    },
    
    camelize: function(str) {
      var parts = str.split('-'), len = parts.length;
      if (len == 1) 
        return parts[0];
      
      var camelized = str.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;
    }
  },
  
  Element: {
    getStyle: function(elementId, style) {
      var element = GetElement(elementId);
      style = style == 'float' ? 'cssFloat' : ProtoLite.String.camelize(style);
      var value = element.style[style];
      
      if (ProtoLite.Browser.IE) {
        if (!value && element.currentStyle) 
          value = element.currentStyle[style];
        
        if (style == 'opacity') {
          if (value = (this.getStyle(elementId, 'filter') || '').match(/alpha\(opacity=(.*)\)/)) 
            if (value[1]) 
              return parseFloat(value[1]) / 100;
          return 1.0;
        }
        
        if (value == 'auto') {
          if ((style == 'width' || style == 'height') &&
          (this.getStyle(elementId, 'display') != 'none')) 
            return element['offset' +
            this.capitalize(style)] +
            'px';
          return null;
        }
        return value;
      }
      else {
        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(elementId) {
      var element = GetElement(elementId);
      return this.getStyle(element, 'opacity');
    },
    
    setOpacity: function(elementId, value) {
      var element = GetElement(elementId);
      
      if (ProtoLite.Browser.IE) {
        function stripAlpha(filter) {
          return filter.replace(/alpha\([^\)]*\)/gi, '');
        }
        var currentStyle = element.currentStyle;
        if ((currentStyle && !currentStyle.hasLayout) ||
        (!currentStyle && element.style.zoom == 'normal')) 
          element.style.zoom = 1;
        
        var filter = this.getStyle(elementId, '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;
      }
      else {
        element.style.opacity = (value == 1 || value === '') ? '' : (value < 0.00001) ? 0 : value;
      }
      return element;
    }
  },
  
  Array: {
    IndexOf: function(arr, item) {
      var i = 0;
      var length = arr.length;
      if (i < 0) 
        i = length + i;
      for (; i < length; i++) 
        if (arr[i] === item) 
          return i;
      return -1;
    }
  }
};

AltrecGlobals.Pages.Checkout = {
  lastUniqueWidgetId: 1000,
  getNextUniqueId: function() {
    return this.lastUniqueWidgetId++;
  },
  Timeout: 30
};

function AltrecPageWidget() {
  this.id = null;
  this.payloadData;
  this.IsSingleton = true;
  this.eventMediator;
  this.childWidgets = {};
  this.renderCount = 0;
  this.events = new EventEmitter();
  this.dirty = false;
  this.skipMerge = false;
  this.eventCache = [];
  this.conversionEvents = {};
  this.sisterMenus = [];
  
  var attached = true;
  var WidgetType;
  var self = this;
  this.timedOutRequest = {};
  this.containersToBind = [];
  this.compareToMode = 'none';
  
  this.identify = function() {
    alert(this.Container.id + "\n" + this.constructor.toString());
  };
  
  this.getWidgetType = function() {
    if (!WidgetType) {
      var constructor = this.constructor.toString();
      constructor = constructor.replace("function ", "");
      WidgetType = constructor.substring(0, constructor.indexOf("("));
    }
    return WidgetType;
  };
  
  this.getDisplayName = function() {
    return this.getWidgetType();
  };
  
  this.initWidget = function(pContainer, pParent) {
    this.Container = $(pContainer);
    if (!this.Container) {
      attached = false;
      return;
    }
    this.Parent = pParent;
    this.id = this.Container.id;
    this.eventMediator = (this.useParentMediator) ? this.Parent.eventMediator : this;
    this.uniqueWidgetId = "WidgetID_" + AltrecGlobals.Pages.Checkout.getNextUniqueId();
    InstanceContainer.RegisterInstance(this, 'uniqueWidgetId');
    
    this.containersToBind.push(this.Container);
  };
  
  this.fireAsync = function(methodName, timeout) {
    return InstanceContainer.FireEventAsync(this.uniqueWidgetId, methodName, timeout);
  };
  
  this.isAttached = function() {
    return attached;
  };
  
  this.comparePayload = function() {
    if (this.compareToMode.contains('compare')) {
      var deep = this.compareToMode.toLowerCase().contains('deep');
      if (this.payloadData && this.lastPayload) {
        TimerQueue.addTimer(" - ");
        if (AltrecGlobals.Functions.Object.CompareTo(this.payloadData, this.lastPayload, deep)) {
          TimerQueue.addTimer("\nCompareTrue");
          TimerQueue.dequeueTimer();
          TimerQueue.dequeueTimer();
          
          return true;
        }
        TimerQueue.addTimer("\nCompareFalse");
        TimerQueue.dequeueTimer();
        TimerQueue.dequeueTimer();
      }
      
      return false;
    }
  };
  
  this.rerender = function() {
    this.render(this.payloadData);
  };
  
  this.render = function(pPayload) {
    if (!attached) 
      return;
    
    this.payloadData = pPayload;
    this.blankPayload = (!pPayload);
    
    if (this.startRender && this.startRender() == false) 
      return;
    
    if (this.massagePayload && this.payloadData) {
      if (this.massagePayload() == false) 
        return;
    }
    
    if (!this.skipMerge && this.renderCount > 0 && this.comparePayload() == true) 
      return;
    this.skipMerge = false;
    
    this.lastPayload = {};
    Object.extend(this.lastPayload, this.payloadData);
    
    if (this.preDraw && this.preDraw() == false) 
      return;
    
    if (this.draw) 
      this.draw();
    else 
      this.bindContainer();
    
    if (this.postDraw) 
      this.postDraw();
    this.renderCount++;
    this.lastRenderedPayload = this.payloadData;
    
    var message_name = (this.payloadData.results && this.payloadData.results.message_name) ? (this.payloadData.results.message_name) : this.payloadData.message_name;
    var eventWidget = this.cmEventProps.getWidget();
    if (eventWidget) 
      this.sendCmEvent(message_name, eventWidget);
    
    this.events.emit('afterRender');
  };
  
  this.registerRenderEvent = function(callbackDelegate) {
    this.events.addListener('afterRender', function() {
      callbackDelegate(this);
    }
.bind(this));
  };
  
  this.isDirty = function() {
    this.dirtyWidgets = [];
    var isDirty = JsonBinder.isDirty(this.payloadData);
    if (isDirty) {
      this.dirtyWidgets.push(this.getDisplayName());
      return true;
    }
    for (var i in this.childWidgets) {
      if (this.childWidgets[i].isDirty && this.childWidgets[i].isDirty()) {
        this.dirtyWidgets.push(this.childWidgets[i].getDisplayName());
        return true;
      }
    }
    
    return false;
  };
  
  this.validate = function() {
    this.validationErrors = [];
    this.fieldErrors = {};
    
    if (!this.isAttached()) 
      return true;
    
    var allValid = this.doValidate();
    for (var i in this.childWidgets) {
      if (!this.childWidgets[i].validate) 
        continue;
      
      allValid = (this.childWidgets[i].validate() && allValid);
      this.validationErrors.push(this.childWidgets[i].validationErrors);
      this.validationErrors = this.validationErrors.flatten();
    }
    
    if (this.validationErrors.length > 0 && this.handleValidationErrors) {
      this.handleValidationErrors(this.validationErrors);
    }
    
    for (var field in this.fieldErrors) {
      this.sendCmEvent('field_validation::' + field);
      this.fieldErrors[field] = null;
    }
    this.fieldErrors = null;
    return allValid;
  };
  
  this.doValidate = function() {
    return true;
  };
  
  this.fieldValidationCallback = function(result, elem) {
    if (!result && this.fieldErrors) {
      var name = (!elem.name) ? elem.name : elem.id;
      this.fieldErrors[name] = elem;
    }
  };
  
  this.mergePayloadData = function(payloadToMerge) {
    if (!this.payloadData) 
      this.payloadData = {};
    
    for (var prop in payloadToMerge) {
      this.payloadData[prop] = payloadToMerge[prop];
    }
  };
  
  this.getTemplateVar = function() {
    return this.getWidgetType() + 'Var';
  };
  
  this.getAsyncVar = function() {
    return 'asyncVar';
  };
  
  this.getContainersToBind = function() {
    return this.containersToBind;
  };
  
  this.addContainerToBind = function(container) {
    this.containersToBind.push(container);
  };
  
  this.bindContainer = function() {
    Element.bindJSON(this.getContainersToBind(), this.getTemplateVar(), this.payloadData);
  };
  
  this.bindAsyncStart = function(containersToBind, templateVar) {
    this.payloadData.asyncRequest = 'true';
    Element.bindJSON((containersToBind || this.getContainersToBind()), (templateVar || this.getAsyncVar()), this.payloadData);
  };
  
  this.bindAsyncEnd = function(containersToBind, templateVar) {
    delete this.payloadData['asyncRequest'];
    Element.bindJSON((containersToBind || this.getContainersToBind()), (templateVar || this.getAsyncVar()), this.payloadData);
  };
  
  this.pubEvent = function(funcName, name) {
    this.payloadData[((name) ? name : funcName)] = this.constructJavascriptLinkEvent(funcName);
  };
  
  this.constructJavascriptLinkEvent = function(mediatorEvent, argsArrayObj, mediator) {
    if (!attached) 
      return '';
    
    var argsArray = [];
    if (Object.isArray(argsArrayObj)) {
      argsArray = argsArrayObj;
    }
    else {
      if (argsArrayObj) 
        argsArray.push(argsArrayObj);
    }
    var mediatorToUse = (mediator) ? mediator : this.eventMediator;
    
    var eventStr = "javascript:InstanceContainer.GetInstance('" + mediatorToUse.uniqueWidgetId + "')." + mediatorEvent + "(";
    
    var first = true;
    argsArray.each(function(arg) {
      if (!first) 
        eventStr += ",";
      
      eventStr += "'" + arg + "'";
      first = false;
    });
    
    eventStr += ")";
    return eventStr;
  };
  
  this.attachReturnKey = function(domElement, functionParam, objectToBind) {
    if (!objectToBind) {
      objectToBind = this.eventMediator;
    }
    
    var closure = function(event) {
      this[functionParam](event);
    }
.bind(objectToBind);
    
    var returnKeyPress = function(event) {
      if (Event.KEY_RETURN == event.keyCode) {
        closure(event);
      }
    };
    
    this.attachEvent(domElement, 'keypress', returnKeyPress, this);
  };
  
  this.attachKeypress = function(domElement, functionParam, objectToBind, arg1) {
    this.attachEvent(domElement, 'keypress', functionParam, objectToBind, arg1);
  };
  this.attachClick = function(domElement, functionParam, objectToBind, arg1) {
    this.attachEvent(domElement, 'click', functionParam, objectToBind, arg1);
  };
  this.attachChange = function(domElement, functionParam, objectToBind, arg1) {
    this.attachEvent(domElement, 'change', functionParam, objectToBind, arg1);
  };
  this.attachKeydown = function(domElement, functionParam, objectToBind, arg1) {
    this.attachEvent(domElement, 'keydown', functionParam, objectToBind, arg1);
  };
  this.attachKeyup = function(domElement, functionParam, objectToBind, arg1) {
    this.attachEvent(domElement, 'keyup', functionParam, objectToBind, arg1);
  };
  this.attachMouseOver = function(domElement, functionParam, objectToBind, arg1) {
    this.attachEvent(domElement, 'mouseover', functionParam, objectToBind, arg1);
  };
  this.attachMouseMove = function(domElement, functionParam, objectToBind, arg1) {
    this.attachEvent(domElement, 'mousemove', functionParam, objectToBind, arg1);
  };
  this.attachMouseOut = function(domElement, functionParam, objectToBind, arg1) {
    this.attachEvent(domElement, 'mouseout', functionParam, objectToBind, arg1);
  };
  this.attachFocus = function(domElement, functionParam, objectToBind, arg1) {
    this.attachEvent(domElement, 'focus', functionParam, objectToBind, arg1);
  };
  this.attachBlur = function(domElement, functionParam, objectToBind, arg1) {
    this.attachEvent(domElement, 'blur', functionParam, objectToBind, arg1);
  };
  this.attachEvent = function(domElement, eventName, functionParam, objectToBind, arg1) {
    if (!$(domElement)) 
      return;
    
    if (!objectToBind) {
      objectToBind = this.eventMediator;
    }
    
    var func;
    if (Object.isFunction(functionParam)) {
      func = functionParam.bind(objectToBind);
    }
    else {
      if (!arg1) 
        arg1 = this;
      func = objectToBind[functionParam].bindAsEventListener(objectToBind, arg1);
    }
    Event.observe($(domElement), eventName, func);
    this.eventCache.push({
      element: $(domElement),
      event: eventName,
      func: func
    });
  };
  
  this.removeEvent = function(domElement, eventName, functionParam, objectToBind, arg1) {
    if (!$(domElement)) 
      return;
    
    if (!objectToBind) {
      objectToBind = this.eventMediator;
    }
    
    var func;
    if (Object.isFunction(functionParam)) {
      func = functionParam.bind(objectToBind);
    }
    else {
      if (!arg1) 
        arg1 = this;
      func = objectToBind[functionParam].bindAsEventListener(objectToBind, arg1);
    }
    Event.stopObserving($(domElement), eventName, func);
  };
  
  this.buildBoundObject = function() {
    return JsonBinder.buildObjFromBindings(this.payloadData);
  };
  
  this.remakeRequest = function(requestObj, widget) {
    var originalRequest = InstanceContainer.GetInstance(PayloadMediator.lastMakeRequestId);
    for (var prop in requestObj) {
      originalRequest[prop] = requestObj[prop];
    }
    
    if (!widget) 
      widget = originalRequest.widget;
    
    var doRemake = function() {
      this.makeRequest(originalRequest, widget)
    }
.bind(originalRequest.widget);
    
    doRemake();
  };
  
  this.timeoutRequest = function(requestId) {
    this.timedOutRequest[requestId] = true;
    this.endAsyncRequest(requestId);
    var requestObj = InstanceContainer.GetInstance(requestId);
    
    requestObj.timeoutCount++;
    requestObj.onTimeOutHandler();
  };
  
  this.endAsyncRequest = function(requestId) {
    var request = InstanceContainer.GetInstance(requestId);
    if (this.blockingRequest == requestId) 
      this.blockingRequest = null;
    if (PayloadMediator.blockingRequest == requestId) 
      PayloadMediator.blockingRequest = null;
    this.bindAsyncEnd(request.asyncContainers, request.asyncVar);
  };
  
  this.isBlockingRequests = function() {
    return (this.blockingRequest || PayloadMediator.blockingRequest)
  };
  
  this.makeRequest = function(requestObj, widget) {
    if (!attached) 
      return;
    
    if (!widget) 
      widget = this;
    
    if (requestObj && requestObj.id) 
      delete requestObj['id'];
    
    var requestBase = {
      id: "Request_" + this.uniqueWidgetId + "_" + AltrecGlobals.Pages.Checkout.getNextUniqueId(),
      hasRedirects: function() {
        return (this.responsePayload && this.responsePayload.MessagePayloads.RedirectPayload);
      },
      timeoutCount: 0,
      onTimeOutHandler: function() {
        TopMessagingWidget.ShowError("Request timed out.<br />Please resubmit request.");
      }
    };
    
    if (!requestObj.id) 
      requestObj.widget = widget;
    requestObj.invokingWidget = this;
    
    Object.extend(requestBase, requestObj);
    requestObj = requestBase;
    PayloadMediator.lastMakeRequestId = requestObj.id;
    InstanceContainer.RegisterInstance(requestObj);
    if (!requestObj.blockingType) 
      requestObj.blockingType = 'widget';//default
    if (this.isBlockingRequests()) 
      return;
    
    if (requestObj.blockingType == 'widget') {
      this.blockingRequest = requestObj.id;
    }
    if (requestObj.blockingType == 'page') 
      PayloadMediator.blockingRequest = requestObj.id;
    
    this.bindAsyncStart(requestObj.asyncContainers, requestObj.asyncVar);
    
    if ((requestObj.validateLevel == 'widget' && !this.validate()) ||
    (requestObj.validateLevel == 'page' && !PayloadMediator.validateWidgets())) {
      this.endAsyncRequest(requestObj.id);
      return;
    }
    
    if ((requestObj.dirtCheckLevel == 'widget' && this.isDirty() == true) ||
    (requestObj.dirtCheckLevel == 'page' && PayloadMediator.isPageDirty() == true)) {
      OverlaySingleton.show(this.getDirtyMessage(requestObj.dirtCheckLevel));
      this.endAsyncRequest(requestObj.id);
      return;
    }
    
    var timeoutEventLink = this.constructJavascriptLinkEvent('timeoutRequest', requestBase.id).replace("javascript:", '');
    //closure will clear this
    var timeout = setTimeout(timeoutEventLink, (1000 * (requestObj.timeout || AltrecGlobals.Pages.Checkout.Timeout)));
    
    var responseClosure = function(responseObj) {
	  AltrecGlobals.Functions.Cookies.clearCombinedCache();
      var invokingWidget = this.invokingWidget;
      if (invokingWidget.timedOutRequest[requestBase.id]) 
        return;
      
      if (responseObj && responseObj.responseText === '') 
        return;
      
      clearTimeout(timeout);
      
      try {
        if (responseObj && responseObj.responseText) {
          responseObj = eval('(' + responseObj.responseText + ')');
        }
        
        this.responsePayload = responseObj;
        
        var clearAsync = true;
        if (this.clearAsyncCheck) 
          clearAsync = this.clearAsyncCheck();
        if (clearAsync) 
          invokingWidget.endAsyncRequest(requestBase.id);
        
        if (this.callback) 
          this.callback(this.responsePayload);
        else {
          PayloadMediator.renderPayloads(this.responsePayload);
        }
        
        if (this.onComplete) 
          this.onComplete(this.responsePayload);
        
      } 
      catch (e) {
        //alert('Response Error\n'+e);
        throw e;
      }
      
    }
.bind(requestObj);
    
    TopMessagingWidget.HideError();
    var url = (requestObj.getUrl) ? requestObj.getUrl() : null;
    var pageName = (AltrecGlobals.Pages && AltrecGlobals.Pages.PageName) ? AltrecGlobals.Pages.PageName : '';
    var parameters = {
      pageName: pageName
    };
    if (requestObj.javaMethod) {
      var paramArr = (requestObj.getParamArray) ? requestObj.getParamArray() : [];
      if (requestObj.getOverrideParameters) {
        var overrideParameters = requestObj.getOverrideParameters();
        for (var paramToOverride in overrideParameters) {
          var idx = parseInt(paramToOverride);
          paramArr[idx] = overrideParameters[paramToOverride];
        }
        requestObj.getOverrideParameters = null;
      }
      
      for (var i = 0; i < paramArr.length; i++) {
        var obj = paramArr[i];
        if (!Object.isNumber(obj) && !Object.isString(obj)) {
          paramArr[i] = Object.toJSON(obj);
        }
      }
      
      if (!requestObj.getUrl) {
        url = '/servlets/newRemote';
        paramArr.splice(0, 0, pageName);
      }
      
      parameters = {
        javaMethod: requestObj.javaMethod,
        paramArray: paramArr,
        pageName: pageName,
        t_t: (new Date()).getTime()
      };
    }
    else {
      if (requestObj.getParameters) 
        parameters = Object.extend(parameters, requestObj.getParameters() || {});
      if (requestObj.getOverrideParameters) {
        var overrideParameters = requestObj.getOverrideParameters();
        for (var paramToOverride in overrideParameters) {
          parameters[paramToOverride] = overrideParameters[paramToOverride];
        }
        requestObj.getOverrideParameters = null;
      }
      
      
    }
    
    var method = "get";
    if (requestObj.method) 
      method = requestObj.method;
    
    if (method == 'jsonp') {
      parameters.callback = 'PayloadMediator.callback';
      parameters.callback_key = PayloadMediator.registerCallback(responseClosure);
      parameters.d = (new Date()).getTime();
      
      var paramStr = '';
      for (var paramName in parameters) {
        if (paramStr.length > 0) 
          paramStr += '&';
        var paramVal = parameters[paramName];
        if (Object.isArray(paramVal)) {
          for (var i = 0; i < paramVal.length; i++) {
            if (i > 0) 
              paramStr += '&';
            paramStr += paramName + '=' + encodeURIComponent(paramVal[i]);
          }
        }
        else {
          paramStr += paramName + '=' + encodeURIComponent(paramVal);
        }
        
      }
      
      var head = document.getElementsByTagName("head")[0];
      var script = document.createElement("script");
      
      script.setAttribute("type", "text/javascript", 0);
      script.setAttribute("src", url + "?" + paramStr, 0);
      head.appendChild(script);
      
    }
    else {
      var request = new Ajax.Request(url, {
        method: method,
        parameters: parameters,
        onSuccess: responseClosure,
        onFailure: function(e) {
        }
      });
    }
  };
  
  this.getDirtyMessage = function(level) {
    var message;
    var extraMsg = '';
    if (level == "page") {
      extraMsg = '<b>' + PayloadMediator.dirtyWidgets[0] + '</b> ';
    }
    else {
      extraMsg = '<b>' + this.dirtyWidgets[0] + '</b> ';
    }
    
    message = "There is unsaved " + extraMsg + "data on the page.";
    message += "  Please cancel or save your changes.";
    return message;
  };
  
  this.addChildWidget = function(widgetName, widget) {
    this.childWidgets[widgetName] = widget;
  };
  
  this.getChildWidget = function(widgetName) {
    return this.childWidgets[widgetName];
  };
  
  this.removeChildWidget = function(widgetName) {
  	if (this.childWidgets[widgetName])
	  	delete this.childWidgets[widgetName];
  };
  
  this.childWidgetCount = function() {
    var count = 0;
    for (var key in this.childWidgets) {
      count++;
    }
    return count;
  };
  
  this.cmEventProps = {
    blockEvent: false,
    getWidget: function() {
      return this;
    }
.bind(this)
  };
  
  this.show = function() {
    this.Container.style.display = '';
  };
  this.hide = function() {
    this.Container.style.display = 'none';
  };
  
  
  
  
  this.sendCmEvent = function(event, widget, tagType) {
    if (!event || event == '' || this.lastEventName == event) 
      return;
    
    this.lastEventName = event;
    
    var widgetToSend = widget || this;
    var fireNow = false;
    var args = [];
    if (event && event.trim() != '') {
      if (this.checkCmEvent && this.checkCmEvent() == false) 
        return;
      
      if (tagType && tagType.toLowerCase().indexOf('conversion') >= 0) {
        this.lastEventName = event + tagType;
        if (!this.conversionEvents.event) 
          this.conversionEvents.event = {
            count: 0
          };
        this.conversionEvents.event.count++;
        if (tagType.toLowerCase().indexOf('open') > 0) {
          if (this.conversionEvents.event.count > 1) 
            return;
          args = [1];
        }
        else 
          args = [2];
      }
      
      var asyncObj = new CoremetricsAyncHolder(widgetToSend.getWidgetType(), event, tagType, args);
      InstanceContainer.RegisterInstance(asyncObj);
      InstanceContainer['FireEvent' + ((fireNow) ? '' : 'Async')](asyncObj.id, 'send', 0);
    }
  };
  
  this.getFirstClass = function(className) {
    return AltrecGlobals.Functions.Element.getFirstClassName(this.Container, className, true);
  };
  
  this.getBoundElement = function(boundField) {
    try {
      return JsonBinder.getBoundElement(this.payloadData.jsonBindProp[boundField]);
    } 
    catch (e) {
      return null;
    }
  };
  
  this.createCollection = function(params) {
    //stubs
    var options = Object.extend({
      key: 'key',
      onCreate: function() {
      },
      onUpdate: function() {
      },
      onRemove: function() {
      }
    }, params || {});
    
    var collection = new WidgetModelCollection({
      key: options.key
    });
    
    collection.on(collection.name + '.*.create', function(obj) {
    
      var createdWidget = options.onCreate(obj);
      
      collection.on(collection.name + '.' + obj[options.key] + '.update', function(updatedObj) {
        options.onUpdate(updatedObj, createdWidget);
      });
      
      collection.on(collection.name + '.' + obj[options.key] + '.delete', function(deletedObj) {
        options.onDelete(deletedObj, createdWidget);
      });
    });
    
    return collection;
    
  };
  
  this.destroy = function() {
    for (var i in this.childWidgets) {
      this.childWidgets[i].destroy();
    }
    
    for (var i = 0; i < this.eventCache.length; i++) {
      try {
        Event.stopObserving(this.eventCache[i].element, this.eventCache[i].event);
      } 
      catch (ex) {
      }
    }
    
    AltrecGlobals.Functions.Object.Destroy(this.payloadData);
    this.payloadData = null;
    this.childWidgets = null;
    this.Container = null;
	this.destroyed = true;
    self = null;
  };
}


var WidgetExtender = {
  Setup: function(inheritingClass) {
    AltrecGlobals.Functions.Object.Inherit(AltrecPageWidget, inheritingClass);
  }
};

var CoremetricsAyncHolderId = 0;
function CoremetricsAyncHolder(widgetType, event, tagType, args) {
  this.widgetType = widgetType;
  this.event = event;
  this.tagType = tagType;
  this.args = args;
  this.id = 'CoremetricsAyncHolderId' + (CoremetricsAyncHolderId++);
  
  this.send = function() {
    if (!this.tagType || this.tagType.toLowerCase() == 'element') {
      AltrecGlobals.Coremetrics.SendCheckoutEvent(this.widgetType, this.event);
    }
    else {
      cmCreateConversionEventTag(this.event, this.args[0]);
    }
  }
}


function DropDownWidget() {
  WidgetExtender.Setup(this);
  
  this.initElement = function(widgetElement, parent) {
    this.initWidget(widgetElement, parent);
  };
  
  this.draw = function() {
    var optionsElement = this.Container;
    for (var i = optionsElement.options.length - 1; i >= 0; i--) {
      optionsElement.remove(i);
    }
    
    for (var i = 0; i < this.payloadData.length; i++) {
      var optn = document.createElement("OPTION");
      optn.text = this.payloadData[i].text;
      optn.value = this.payloadData[i].value;
      var selected = null;
      if (optn.value == this.defaultValue) {
        optn.setAttribute("selected", "selected");
        selected = i;
      }
      optionsElement.options.add(optn);
      if (selected) 
        optionsElement.selectedIndex = i;
    }
  };
  
  this.addOption = function(text, value) {
    var optn = document.createElement("OPTION");
    optn.text = text;
    optn.value = value;
    this.Container.options.add(optn);
  };
  
  this.setPayload = function(pPayload, pDefaultValue) {
    this.defaultValue = pDefaultValue;
    this.render(pPayload);
  };
  
  this.getSelectedValue = function() {
    for (var i = 0; i < this.Container.options.length; i++) {
      if (this.Container.options[i].selected == true) {
        return this.Container.options[i].value;
        break;
      }
    }
  };
  
  this.attachDropDownChange = function(func, objectToBind) {
    if (!objectToBind) 
      objectToBind = this;
    this.attachChange(this.Container.id, func, objectToBind);
  };
  
  
  this.destroy = function() {
    AltrecGlobals.Functions.Object.Destroy(this.Container.bindJSONProps);
    this.Container.bindJSONProps = null;
    this.Container.bindJSON = null;
    ;
    this.Container.tempId;
    this.base.destroy();
  };
}

function DropDownQtyWidget(parent, widgetElement) {
  DropDownWidget.call(this);
  this.initElement(widgetElement, parent);
  
  this.setQuantity = function(max, defaultQty) {
    this.max = max;
    var arr = [];
    for (var i = 0; i <= max; i++) {
      arr[i] = {
        text: i,
        value: i
      };
    }
    
    if (max < defaultQty) 
      defaultQty = max;
    
    this.setPayload(arr, defaultQty);
  };
  
  this.addMaxPlus = function() {
    var qty = this.max + 1;
    this.addOption(qty + "+", qty);
  };
}

AltrecGlobals.Variables.RadioBtnClass = 'shipRadio';

function RadioBtnWidget(id, parent, groupName, multiselect) {
  WidgetExtender.Setup(this);
  this.initWidget(id, parent);
  this.useParentMediator = false;
  this.multiSelect = multiselect ? multiselect : false;
  
  this.addOption = function(elementToAttach, value, clickArea) {
    var option = AltrecGlobals.Functions.Element.getFirstClassName(elementToAttach, AltrecGlobals.Variables.RadioBtnClass);
    var btnItem = new RadioBtnItemWidget(option, this, AltrecGlobals.Variables.RadioBtnClass, clickArea);
    btnItem.render({
      value: value
    });
    this.addChildWidget(btnItem.value, btnItem);
    return btnItem;
  };
  
  this.disabledSelect = function(e, item) {
  };
  
  this.selectOption = function(e, item) {
    for (var i in this.childWidgets) {
      if (!this.childWidgets[i].isDisabled()) {
        if (i != item.value && !this.multiSelect) 
          this.childWidgets[i].unselectItem();
      }
    }
    
    if (!item.isDisabled()) {
      item.selectItem();
    }
  };
  
  this.clearOptions = function() {
    for (var i in this.childWidgets) {
      this.childWidgets[i].destroy();
    }
    this.childWidgets = {};
  };
}

function RadioBtnItemWidget(id, parent, baseClass, clickArea) {
  WidgetExtender.Setup(this);
  this.useParentMediator = true;
  this.initWidget(id, parent);
  this.valueClass = 'shipRadioValue';
  this.baseClass = baseClass;
  this.disabled = false;
  if (!clickArea) 
    clickArea = this.Container;
  this.clickArea = clickArea;
  
  this.massagePayload = function() {
    if (this.payloadData.value === undefined || this.payloadData.value === null) 
      this.payloadData.value = AltrecGlobals.Functions.Element.getFirstClassName(this.Container, this.valueClass).innerHTML;
    this.value = this.payloadData.value;
    
  };
  
  this.getValue = function() {
    return this.value;
  };
  this.getTemplateVar = function() {
    return "rdBtn";
  };
  
  this.getSelectedClass = function() {
    return this.baseClass + 'Selected';
  };
  
  this.getDisabledClass = function() {
    return this.baseClass + 'Disabled';
  };
  
  
  this.isSelected = function() {
    return Element.hasClassName(this.Container, this.getSelectedClass());
  };
  
  this.isDisabled = function() {
    return this.disabled;
  };
  
  this.selectItem = function() {
    if (!this.isSelected()) {
      Element.addClassName(this.Container, this.getSelectedClass());
      if (this.onSelect) 
        this.onSelect(this.value);
    }
  };
  
  this.unselectItem = function() {
    if (this.isSelected()) 
      Element.removeClassName(this.Container, this.getSelectedClass());
  };
  
  this.disableItem = function() {
    if (!this.isDisabled()) {
      Element.addClassName(this.clickArea, this.getDisabledClass());
      Element.setOpacity(this.clickArea, .4);
      this.disabled = true;
      this.unselectItem();
    }
  };
  
  this.enableItem = function() {
    if (this.isDisabled()) {
      Element.removeClassName(this.clickArea, this.getDisabledClass());
      Element.setOpacity(this.clickArea, 1);
    }
  }
  
  this.postDraw = function() {
    if (this.renderCount == 0) {
      this.attachClick(this.clickArea, 'selectOption');
      Element.setOpacity(this.Container, 1);
    }
  };
}

var PayloadMediator = {

  id: "PayloadMediator",
  topLevelWidgets: {},
  lastMakeRequestId: null,
  lastCallbackId: 0,
  callbacks: {},
  blockingRequest: null,
  renderStatistics: '',
  
  init: function() {
    this.CompletedInitialLoad = false;
  },
  
  getWidgets: function(widgetName) {
    var createNew = false;
    if (this.topLevelWidgets[widgetName] && !this.topLevelWidgets[widgetName][0].IsSingleton) {
      createNew = true;
    }
    if (!this.topLevelWidgets[widgetName]) 
      createNew = true;
    
    if (createNew) {
      if (window[widgetName] || AltrecGlobals.Widgets[widgetName]) {
        this.topLevelWidgets[widgetName] = new Array();
        if (AltrecGlobals.Widgets[widgetName]) 
          this.topLevelWidgets[widgetName].push(new AltrecGlobals.Widgets[widgetName](null));
        else 
          this.topLevelWidgets[widgetName].push(new window[widgetName](null));
      }
      else 
        return [null];
    }
    
    return this.topLevelWidgets[widgetName];
  },
  
  addWidget: function(widgetName, widget) {
    this.topLevelWidgets[widgetName] = new Array();
    this.topLevelWidgets[widgetName].push(widget);
  },
  
  getWidget: function(widgetName) {
    var widgetArray = this.getWidgets(widgetName);
    return widgetArray[widgetArray.length - 1];
  },
  
  getPayload: function(widgetName) {
    var widgetPayloadEnv = widgetName.replace('Widget', '');
    if (this.PayloadEnvelope.MessagePayloads[widgetPayloadEnv]) 
      return this.PayloadEnvelope.MessagePayloads[widgetPayloadEnv];
    
    var widget = this.getWidget(widgetName);
    if (widget && widget.payloadData) { return widget.payloadData; }
    
    return null;
  },

  isWidgetLoaded: function(widgetName){
    return this.topLevelWidgets[widgetName];                 
  },
  
  isPageDirty: function() {
    this.dirtyWidgets = [];
    for (var i in this.topLevelWidgets) {
      for (var j = 0; j < this.topLevelWidgets[i].length; j++) {
        var isWidgetDirty = this.topLevelWidgets[i][j].isDirty();
        if (isWidgetDirty) {
          this.topLevelWidgets[i][j].dirtyWidgets.each(function(widgetName) {
            this.dirtyWidgets.push(widgetName);
          }
.bind(this));
        }
        if (this.topLevelWidgets[i][j].isDirty && isWidgetDirty) 
          return true;
      }
    }
    
    return false;
  },
  
  destroy: function() {
  
    for (var i in this.topLevelWidgets) {
      for (var j = 0; j < this.topLevelWidgets[i].length; j++) {
        this.topLevelWidgets[i][j].destroy();
      }
    }
    
    this.topLevelWidgets = null;
    this.lastMakeRequestId = null;
    this.lastCallbackId = null;
    this.callbacks = null;
    this.blockingRequest = null;
    this.PayloadEnvelope = null;
  },
  
  validateWidgets: function() {
    var allValid = true;
    for (var i in this.topLevelWidgets) {
      for (var j = 0; j < this.topLevelWidgets[i].length; j++) {
        if (this.topLevelWidgets[i][j].validate) 
          allValid = (this.topLevelWidgets[i][j].validate() && allValid);
      }
    }
    
    return allValid;
  },
  
  renderPayload: function(payloadName, payload) {
    var obj = {};
    obj.MessagePayloads = {};
    obj.MessagePayloads[payloadName] = payload;
    this.renderPayloads(obj);
  },
  
  renderPayloads: function(payloadObject) {
  
    TimerQueue.addTimer('renderStart');
    this.PayloadEnvelope = payloadObject;
    if (payloadObject.MessagePayloads.RedirectPayload) {
      var redirect = this.getWidget('RedirectPayloadWidget');
      if (redirect) {
        this.getWidget('RedirectPayloadWidget').render(payloadObject.MessagePayloads.RedirectPayload);
        return;
      }
    }
    
    var payloadMap = {};
    var payloadToRender = [];
    function addPayload(payloadName) {
      if (!payloadMap[payloadName] && payloadObject.MessagePayloads[payloadName]) {
        payloadToRender.push(payloadName);
        payloadMap[payloadName] = true;
      }
    }
    
    addPayload('CartPayload');
    addPayload('ShippingPayload');
    addPayload('ShippingOptionsPayload');
    addPayload('LoginPayload');
    addPayload('CustomerPayload');
    
    for (var payloadName in payloadObject.MessagePayloads) {
      addPayload(payloadName);
    }
    
    for (var i = 0; i < payloadToRender.length; i++) {
      var payloadName = payloadToRender[i];
      try {
        TimerQueue.addTimer('\n' + payloadName);
        var widget = null;
        var widgetName = payloadName + 'Widget';
        if (!window[widgetName] && !AltrecGlobals.Widgets[widgetName]) {
          var defaultWidget = "window." + payloadName + "Widget=function(parent){WidgetExtender.Setup(this);this.initWidget('topNav',parent);this.draw=function(){};}";
          eval(defaultWidget);
        }
        widget = this.getWidget(widgetName);
        if (widget) 
          widget.render(payloadObject.MessagePayloads[payloadName]);
        TimerQueue.dequeueTimer();
      } 
      catch (e) {
        alert('Error in mediation creation\n' + payloadName + '\n' + e.message);
      }
    }
    
    this.CompletedInitialLoad = true;
    
    this.renderStatistics += '\n\n' + TimerQueue.getAllMessage();
  },
  
  registerCallback: function(callback) {
    var callbackId = 'callback' + (this.lastCallbackId++);
    this.callbacks[callbackId] = callback;
    return callbackId;
  },
  
  callback: function(payloadEnvelopes, id) {
    this.callbacks[id](payloadEnvelopes);
    this.callbacks[id] = null;
    delete this.callbacks[id];
  }
  
};
PayloadMediator.init();

function RunMediator() {
  this.id = 'RunMediator';
  InstanceContainer.RegisterInstance(this);
  
  this.run = function() {
    if (AltrecGlobals.WaitForScript) {
      InstanceContainer.FireEventAsync(this.id, 'run', 300);
      return;
    }
    
    this.renderPayload();
  };
  
  this.renderPayload = function() {
    if (!onloadEnvelope.MessagePayloads.DiscountPayload) 
      onloadEnvelope.MessagePayloads.DiscountPayload = {};
    
    if (!onloadEnvelope.MessagePayloads.CheckoutPayload) 
      onloadEnvelope.MessagePayloads.CheckoutPayload = {};
    
    if (AltrecGlobals.MassageOnloadCallback) 
      AltrecGlobals.MassageOnloadCallback();
    
    PayloadMediator.renderPayloads(onloadEnvelope);
  };
}

//make this seperate to avoid closures
var bindJSONElementContainer = {

  boundElements: {},
  addElement: function(element) {
    if (!this.boundElements[element.tempId]) {
      if (!element.id || element.id == '') 
        element.id = element.tempId;
      this.boundElements[element.tempId] = element.id;
    }
  },
  getElement: function(id) {
    return $(this.boundElements[id]);
  },
  
  hasElement: function(id) {
    return (this.boundElements[id]);
  }
};

var bindJSONInterpreter = {
  elementTempId: 0,
  displayCmdMap: {},
  getProp: function(obj, prop) {
    var arr = prop.split('(');
    if (arr.length > 1) 
      return '' + obj[arr[0]]();
    else {
      if (prop.length >= 2 && prop.charAt(0) == "'" && prop.charAt(prop.length - 1) == "'") { return prop.substring(1, prop.length - 1); }
      else if (!obj || (obj[prop] != 0 && !obj[prop])) {
        //I think isNaN is spendy in IE
        if ((prop.charAt(0) != "0" &&
        prop.charAt(0) != "1" &&
        prop.charAt(0) != "2" &&
        prop.charAt(0) != "3" &&
        prop.charAt(0) != "4" &&
        prop.charAt(0) != "5" &&
        prop.charAt(0) != "6" &&
        prop.charAt(0) != "7" &&
        prop.charAt(0) != "8" &&
        prop.charAt(0) != "9")) 
          return '';
        
        if (isNaN(prop)) 
          return '';
        else 
          return prop;
      }
      else 
      
        return obj[prop];
    }
  },
  
  evalProperty: function(obj, props) {
    var objToEval = obj;
    if (props.indexOf('AltrecGlobals') == 0) {
      props = props.replace('AltrecGlobals.', '');
      objToEval = AltrecGlobals;
    }
    
    if (props.indexOf('Window') == 0) {
      props = props.replace('Window.', '');
      objToEval = window;
    }
    
    if (props.charAt(0) == "'" || !props.contains('.')) { return this.getProp(objToEval, props); }
    else {
      var dom = props.split('.');
      var remainingProps = [];
      for (var i = 1; i < dom.length; i++) {
        remainingProps.push(dom[i]);
      }
      return this.evalProperty(objToEval[dom[0]], remainingProps.join('.'));
    }
  },
  
  getFirstParamStmtParams: function(token) {
    return this.tokenize(token, ',');
  },
  
  tokenize: function(token, delminiter) {
    var stack = 0;
    var params = [];
    var leftOverStmt = null;
    var i = token.indexOf('(') + 1;
    var lastParamIdx = i;
    var done = false;
    for (; i < token.length - 1; i++) {
      if (token.charAt(i) == '(') 
        stack++;
      else if (token.charAt(i) == ')') 
        stack--;
      if (done) 
        continue;
      
      if ((token.charAt(i) == ',' && stack == 0) || (token.charAt(i) == ')' && stack == -1)) {
        params.push(token.substring(lastParamIdx, i).trim());
        lastParamIdx = i + 1;
        if (stack == -1) {
          stack = 0;
          done = true;
        }
      }
    }
    
    if (!done) 
      params.push(token.substring(lastParamIdx, token.length - 1).trim());
    else {
      leftOverStmt = token.substring(lastParamIdx, token.length - 1).trim();
      if (stack > 0) 
        leftOverStmt += ")";
    }
    
    return {
      params: params,
      leftOverStmt: leftOverStmt
    };
  },
  
  evalArgValues: function(args, jsonObject) {
    var argValues = [];
    for (var i = 0; i < args.length; i++) {
      argValues.push(this.evalProperty(jsonObject, args[i]));
    }
    
    return argValues;
  },
  
  evalBooleanStmts: function(val, jsonObject, element) {
    var returnBoolVal = true;
    if (val.indexOf('Else') == 0 && element.bindJSONProps && element.bindJSONProps.lastSiblingId) {
      var sibling = bindJSONElementContainer.getElement(element.bindJSONProps.lastSiblingId);
      if (sibling && sibling.bindJSONProps.evaledBool) 
        returnBoolVal = false;
    }
    
    if (val.indexOf('And(') == 0) {
      var stmtsStr = val.replace('And', '');
      var boolStmts = this.getFirstParamStmtParams(stmtsStr);
      
      var result = true;
      boolStmts.params.each(function(stmt) {
        result = (result && this.evalBooleanStmts(stmt, jsonObject).result);
      }
.bind(this));
      
      if (element) 
        element.bindJSONProps.evaledBool = result;
      return {
        'result': result,
        leftOverStmt: boolStmts.leftOverStmt
      };
    }
    if (val.indexOf('Or(') == 0) {
      var stmtsStr = val.replace('Or', '');
      var boolStmts = this.getFirstParamStmtParams(stmtsStr);
      
      var result = false;
      boolStmts.params.each(function(stmt) {
        if (!result) 
          result = (result || this.evalBooleanStmts(stmt, jsonObject).result);
      }
.bind(this));
      
      if (element) 
        element.bindJSONProps.evaledBool = result;
      return {
        'result': result,
        leftOverStmt: boolStmts.leftOverStmt
      };
    }
    
    var originalCondition = val;
    var leftOverStmt = null;
    
    var stmtParmas = this.getFirstParamStmtParams(val);
    var args = stmtParmas.params;
    var paramValues = this.evalArgValues(args, jsonObject);
    
    if (val.contains('EmptyObj')) {
      var hasValues = false;
      paramValues.each(function(columnToCheck) {
        if (originalCondition.contains('IsNotEmptyObj') && !columnToCheck.trim && columnToCheck) 
          hasValues = true;
        if (originalCondition.contains('IsEmptyObj') && !columnToCheck.trim && !columnToCheck === undefined) 
          hasValues = true;
        
      }
.bind(this));
      
      if (!hasValues) 
        returnBoolVal = false;
    }
    else if (val.contains('Empty')) {
      var hasValues = false;
      paramValues.each(function(columnToCheck) {
        if (originalCondition.contains('IsNotEmpty') && columnToCheck && columnToCheck.trim && columnToCheck.trim().length > 0) 
          hasValues = true;
        if (originalCondition.contains('IsEmpty') && (!columnToCheck || (columnToCheck.trim && columnToCheck.trim().length == 0))) 
          hasValues = true;
        
      }
.bind(this));
      
      if (!hasValues) 
        returnBoolVal = false;
    }
    else if (val.contains('Equal')) {
      var param1 = paramValues[0];
      var param2 = paramValues[1];
      
      if (!isNaN(param1) && !isNaN(param2)) {
        param1 = parseFloat(param1);
        param2 = parseFloat(param2);
      }
      
      if (originalCondition.contains('IsNotEqual')) {
        if (param1 == param2) 
          returnBoolVal = false;
      }
      else if (originalCondition.contains('IsEqual')) {
        if (param1 != param2) 
          returnBoolVal = false;
      }
    }
    else if (val.contains('Than')) {
      var param1 = paramValues[0];
      var param2 = paramValues[1];
      
      if (!isNaN(param1) && !isNaN(param2)) {
        param1 = parseFloat(param1);
        param2 = parseFloat(param2);
      }
      
      if (originalCondition.contains('IsLessThan') && param1 >= param2) 
        returnBoolVal = false;
      if (originalCondition.contains('IsGreaterThan') && param1 <= param2) 
        returnBoolVal = false;
    }
    else if (val.contains('With')) {
      var param1 = paramValues[0];
      var param2 = paramValues[1];
      
      if (!isNaN(param1) && !isNaN(param2)) {
        param1 = parseFloat(param1);
        param2 = parseFloat(param2);
      }
      
      if (originalCondition.contains('NotStartsWith')) {
        if (param1.startsWith(param2)) 
          returnBoolVal = false;
      }
      else if (originalCondition.contains('NotEndsWith')) {
        if (param1.endsWith(param2)) 
          returnBoolVal = false;
      }
      else if (originalCondition.contains('StartsWith')) {
        if (!param1.startsWith(param2)) 
          returnBoolVal = false;
      }
      else if (originalCondition.contains('EndsWith')) {
        if (!param1.endsWith(param2)) 
          returnBoolVal = false;
      }
    }
    else if (val.contains('IsTrue')) {
      var param1 = paramValues[0];
      if (param1 != true && param1 != 'true') 
        returnBoolVal = false;
    }
    else if (val.contains('IsFalse')) {
      var param1 = paramValues[0];
      if (param1 != false && param1 != 'false') 
        returnBoolVal = false;
    }
    
    if (element) 
      element.bindJSONProps.evaledBool = returnBoolVal;
    
    return {
      result: returnBoolVal,
      leftOverStmt: stmtParmas.leftOverStmt
    };
  },
  
  controlDisplay: function(element, showElement, transCmdStr, setValues, forceEfx) {
    var efxCmd = '.Efx';
    var cmd = null;
    var effectChain = [];
    if (transCmdStr && transCmdStr.indexOf(efxCmd) == 0) {
      effectChain = transCmdStr.split(efxCmd);
      cmd = this.getFirstParamStmtParams(effectChain[1]);
    }
    
    var transEfx = (cmd) ? cmd.params[0].toLowerCase() : null;
    var efxOptions = {
      duration: .3,
      queue: {
        scope: element.tempId,
        position: 'end'
      }
    };
    
    if (effectChain.length > 2 && showElement) {
      efxOptions.afterFinish = function() {
        var newCmd = '';
        for (var i = 2; i < effectChain.length; i++) {
          newCmd += efxCmd + effectChain[i];
        }
        this.controlDisplay(element, showElement, newCmd, setValues, true);
      }
.bind(this);
    }
    
    efxOptions.duration = 1;
    if (cmd && cmd.params.length == 2) {
      var obj = this.evalStatement(cmd.params[1]);
      for (var i in obj) {
        efxOptions[i] = obj[i];
      }
    }
    
    var id = (element && element.tempId) ? element.tempId : 'temp_' + this.elementTempId++;
    var key = '' + showElement + transEfx;
    var sameCmd = (this.displayCmdMap[id] == key);
    if ((!transEfx || !PayloadMediator.CompletedInitialLoad) && !efxOptions.force) {
      transEfx = '';
    }
    
    if (showElement) {
      if (transEfx.contains('appear') && !sameCmd) {
        Effect.Appear(element, efxOptions);
      }
      else if (transEfx.contains('blind') && transEfx.contains('down') && !sameCmd) {
        Effect.BlindDown(element, efxOptions);
      }
      else if (transEfx.contains('slide') && transEfx.contains('down') && !sameCmd) {
        Effect.SlideDown(element, efxOptions);
      }
      else if (transEfx.contains('move') && !sameCmd) {
        new Effect.Move(element, efxOptions);
      }
      else if (transEfx.contains('scroll') && !sameCmd) {
        var el = element;
        if (efxOptions.elementId) 
          el = $(efxOptions.elementId);
        Effect.ScrollTo(el, efxOptions);
      }
      else if (PayloadMediator.CompletedInitialLoad &&
      (forceEfx || (setValues && setValues.before != setValues.after))) {
        if (transEfx.contains('highlight')) {
          new Effect.Highlight(element, efxOptions);
        }
        else if (transEfx.contains('pulsate')) {
          efxOptions.afterFinish = function() {
            Element.setOpacity(element, 1);
          };
          Effect.Pulsate(element, efxOptions);
        }
      }
      else {
        element.style.display = '';
      }
    }
    else {
      if (transEfx.contains('fade') && !sameCmd) {
        Effect.Fade(element, efxOptions);
      }
      else if (transEfx.contains('blind') && transEfx.contains('up') && !sameCmd) {
        Effect.BlindUp(element, efxOptions);
      }
      else if (transEfx.contains('slide') && transEfx.contains('up') && !sameCmd) {
        Effect.SlideUp(element, efxOptions);
      }
      else if (transEfx.contains('move') && !sameCmd) {
        new Effect.Move(element, efxOptions);
      }
      else {
        element.style.display = 'none';
      }
    }
    
    element.bindJSONProps.display = showElement;
    if (!forceEfx) 
      this.displayCmdMap[id] = key;
  },
  
  evalStatement: function(stmt, element, jsonObject) {
    if (element && (!element.tempId || element.tempId == '')) {
      element.tempId = 'tempId_' + this.elementTempId++;
      bindJSONElementContainer.addElement(element);
    }
    
    if (stmt.indexOf('Display.') == 0) {
      var showElement = true;
      var evaledResponse = this.evalBooleanStmts(stmt.replace("Display.", ""), jsonObject, element);
      
      this.controlDisplay(element, evaledResponse.result, evaledResponse.leftOverStmt);
    }
    else if (stmt.indexOf('Stop()') == 0) {
      element.bindJSONProps.hasStop = true;
      return;
    }
    else if (stmt.indexOf('FC(') == 0) {
      var cmd = this.getFirstParamStmtParams(stmt);
      var params = this.evalArgValues(cmd.params, jsonObject);
      var result = FormatCurrency(params[0], false);
      var setValues = this.evalStatement('num', element, {
        'num': result
      });
      return setValues;
    }
    else if (stmt.indexOf('Set(') == 0 || stmt.indexOf('S(') == 0) {
      var start = 1;
      if (stmt.indexOf('Set(') == 0) 
        start = 3;
      stmt = stmt.substring(start, stmt.length);
      var cmd = this.getFirstParamStmtParams(stmt);
      
      var setValues = this.evalStatement(cmd.params[0], element, jsonObject);
      
      var showElement = true;
      if (setValues.after == '') 
        showElement = false;
      
      this.controlDisplay(element, showElement, cmd.leftOverStmt, setValues);
    }
    else if (stmt.indexOf('SetAttr(') == 0) {
      var cmd = this.getFirstParamStmtParams(stmt);
      var params = this.evalArgValues(cmd.params, jsonObject);
      element.setAttribute(cmd.params[0], params[1]);
    }
    else if (stmt.indexOf('ToObj(') == 0) {
      var cmd = this.getFirstParamStmtParams(stmt);
      var params = this.evalArgValues(cmd.params, jsonObject);
      var obj = {};
      var field = '';
      for (var i = 0; i < params.length; i++) {
        if ((i % 2) == 0) {
          field = params[i];
        }
        else {
          obj[field] = params[i];
        }
      }
      
      return obj;
    }
    else if (stmt.indexOf('Cond.') == 0) {
      var evaledResponse = this.evalBooleanStmts(stmt.trim().replace("Cond.", ""), jsonObject, element);
      
      if (evaledResponse.leftOverStmt.indexOf('.ThenElse') == 0) {
        var cmdResponse = this.getFirstParamStmtParams(evaledResponse.leftOverStmt.replace(".ThenElse", ""));
        if (evaledResponse.result) 
          this.evalStatement(cmdResponse.params[0], element, jsonObject);
        else 
          this.evalStatement(cmdResponse.params[1], element, jsonObject);
      }
      else if (evaledResponse.leftOverStmt.indexOf('.Then') == 0) {
        var cmdResponse = this.getFirstParamStmtParams(evaledResponse.leftOverStmt.replace(".Then", ""));
        if (evaledResponse.result) 
          this.evalStatement(cmdResponse.params[0], element, jsonObject);
      }
      else if (evaledResponse.leftOverStmt.indexOf('.Toggle') == 0) {
        var cmdResponse = this.getFirstParamStmtParams(evaledResponse.leftOverStmt.replace(".Toggle", ""));
        var toggleType = this.getProp(jsonObject, cmdResponse.params[0]).toLowerCase();
        if (toggleType.contains('class')) {
          if (evaledResponse.result) {
            this.evalStatement('addClassName:' + cmdResponse.params[1], element, jsonObject);
            if (cmdResponse.params[2]) 
              this.evalStatement('removeClassName:' + cmdResponse.params[2], element, jsonObject);
          }
          else {
            if (cmdResponse.params[2]) 
              this.evalStatement('addClassName:' + cmdResponse.params[2], element, jsonObject);
            this.evalStatement('removeClassName:' + cmdResponse.params[1], element, jsonObject);
          }
        }
        if (toggleType.contains('opacity')) {
          var opacity = (evaledResponse.result) ? parseFloat(cmdResponse.params[1]) : parseFloat(cmdResponse.params[2]);
          Element.setOpacity(element, opacity);
        }
        if (toggleType.contains('display')) {
          this.controlDisplay(element, evaledResponse.result, cmdResponse.leftOverStmt);
        }
        if (toggleType.contains('disabled')) {
          element.disabled = (evaledResponse.result);
        }
      }
    }
    else {
      var propertyToSet = 'innerHTML';
      var splitAttr = stmt.split(':');
      var attrArr = [];
      //fixes incidental colon's in value
      var itr = 0;
      for (var j = 0; j < splitAttr.length; j++) {
        if (itr < j) {
          attrArr[itr] += ":" + splitAttr[j];
        }
        else {
          attrArr.push(splitAttr[j]);
          if (splitAttr[j].charAt(0) != "'") 
            itr++;
        }
      }
      var objToSet = element;
      for (var j = 1; j < attrArr.length; j++) {
        propertyToSet = attrArr[j - 1];
        stmt = attrArr[j];
        if (j != (attrArr.length - 1)) 
          objToSet = objToSet[propertyToSet];
      }
      if (propertyToSet == 'addClassName') {
        Element.addClassName(element, this.evalProperty(jsonObject, stmt));
      }
      else if (propertyToSet == 'removeClassName') {
        Element.removeClassName(element, this.evalProperty(jsonObject, stmt));
      }
      else {
        var evaledProp = this.evalProperty(jsonObject, stmt);
        var valBefore = objToSet[propertyToSet];
        objToSet[propertyToSet] = evaledProp;
        
        
        if (propertyToSet == 'value') {
          if (!jsonObject.jsonBindProp) 
            jsonObject.jsonBindProp = {};
          
          var originalValue = valBefore;
          if (jsonObject.jsonBindProp[stmt]) 
            originalValue = evaledProp;
          else {
            jsonObject.jsonBindProp[stmt] = {
              originalValue: originalValue,
              history: [],
              elementTempId: element.tempId
            };
          }
          
          jsonObject.jsonBindProp[stmt].history.push(valBefore);
          jsonObject.jsonBindProp[stmt].current = evaledProp;
        }
        
        return {
          before: valBefore,
          after: evaledProp
        };
      }
    }
  },
  
  getBoundElement: function(binding) {
    return bindJSONElementContainer.getElement(binding.elementTempId);
  },
  
  getBoundElementValue: function(binding, preserveScripts) {
    var field = this.getBoundElement(binding);
    if (field.type && field.type == 'checkbox') 
      return ($F(field)) ? true : false;
    else {
      if (preserveScripts) 
        return field.value;
      else 
        return field.value.stripScripts().stripTags();
    }
  },
  
  buildObjFromBindings: function(boundJsonObject) {
    var returnObj = {};
    if (boundJsonObject.jsonBindProp) {
      for (var prop in boundJsonObject.jsonBindProp) {
        returnObj[prop] = this.getBoundElementValue(boundJsonObject.jsonBindProp[prop]);
      }
    }
    
    return returnObj;
  },
  
  isDirty: function(boundJsonObject) {
    if (boundJsonObject && boundJsonObject.jsonBindProp) {
      for (var prop in boundJsonObject.jsonBindProp) {
        var bindings = boundJsonObject.jsonBindProp[prop];
        var currentVal = this.evalProperty(boundJsonObject, prop);
        
        if (currentVal != null && currentVal != this.getBoundElementValue(bindings, true)) { return true; }
      }
    }
    
    return false;
  },
  
  collectBoundFields: function(boundJsonObject) {
    var returnArr = [];
    if (boundJsonObject && boundJsonObject.jsonBindProp) {
      for (var prop in boundJsonObject.jsonBindProp) {
        var bindings = boundJsonObject.jsonBindProp[prop];
        returnArr.push(this.getBoundElement(bindings));
      }
    }
    return returnArr;
  }
};

var JsonBinder = bindJSONInterpreter;

function bindJSON(elementToBind, dataIslandAttrName, jsonObject, includeRoot, callback) {
  if (elementToBind instanceof Array) {
    for (var i = 0; i < elementToBind.length; i++) {
      var element = elementToBind[i];
      if (element && $(element)) 
        bindJSON($(element), dataIslandAttrName, jsonObject, includeRoot, callback);
    }
    return;
  }
  
  if (!elementToBind) 
    return;
  
  var elementArray = [];
  var numChildren = elementToBind.childNodes.length;
  for (var i = 0; i < numChildren; i++) {
    if (elementToBind.childNodes[i].nodeName.indexOf('text') < 0) 
      elementArray.push(elementToBind.childNodes[i]);
  }
  
  var itemsToRecurse = elementArray.length;
  if (includeRoot) {
    elementArray.push(elementToBind);
  }
  
  for (var i = 0; i < elementArray.length; i++) {
    var element = elementArray[i];
    var statementsStr = null;
    try {
      statementsStr = Element.readAttribute(element, "tmpl:" + dataIslandAttrName) || Element.readAttribute(element, dataIslandAttrName);
    } 
    catch (e) {
    }
    
    if (callback) 
      callback(element);
    if (statementsStr && typeof(statementsStr) == 'string') {
      element.bindJSONProps = {
        parentId: ((elementToBind.tempId) ? elementToBind.tempId : null),
        lastSiblingId: ((i > 0) ? elementArray[i - 1].tempId : null)
      };
      
      var statements = statementsStr.split(";");
      for (var stItr = 0; stItr < statements.length; stItr++) {
        try {
          bindJSONInterpreter.evalStatement(statements[stItr], element, jsonObject);
        } 
        catch (e) {
          throw e;
        }
      }
    }
    
    if (element.bindJSONProps && element.bindJSONProps.hasStop) 
      continue;
    
    if ((!element.bindJSONProps || element.bindJSONProps.display != false) &&
    i < itemsToRecurse &&
    element.nodeName.toLowerCase() != 'select') 
      bindJSON(element, dataIslandAttrName, jsonObject, false, callback);
  }
}

Event.observe(document, 'keydown', AltrecGlobals.Site.getHiddenKeysHandler().fire);

var showUnloadStats = false;
if (window.attachEvent) {
  window.attachEvent("onunload", function() {
    if (!PayloadMediator) 
      return;
    if (showUnloadStats) 
      alert('Unloading Closures 2');
    
    TimerQueue.addTimer("onunload");
    PayloadMediator.destroy();
    //AltrecGlobals.Functions.Object.Destroy(PayloadMediator);
    PayloadMediator = null;
    InstanceContainer.destroy();
    AltrecGlobals.Functions.Object.Destroy(InstanceContainer);
    InstanceContainer = null;
    
    if (showUnloadStats) 
      alert(TimerQueue.getAllMessage());
  });
}


AltrecGlobals.Coremetrics.SendCheckoutEvent = function(Widget, Error) {
  this.SendEvent(Error, AltrecGlobals.Pages.PageName + "_" + Widget);
  return true;
}
.bind(AltrecGlobals.Coremetrics);

AltrecGlobals.Coremetrics.SendEvent = function(eventName, eventCategory) {
  cmCreatePageElementTag(eventName, eventCategory);
}
.bind(AltrecGlobals.Coremetrics);

var TopMessagingWidget = {
  showing: false,
  
  ShowError: function(errorMessage, message_name) {
  
  },
  
  HideError: function() {
  }
};

var SearchBar = Class.create({
  initialize: function(wrapper) {
    WidgetExtender.Setup(this);
    this.wrapper  = $(wrapper);
    this.inputs = this.wrapper.getElementsByTagName('input');
    this.searchField = this.inputs[0];
    this.submitButton = this.inputs[1];
    this.attachKeyup(this.searchField, 'keySearch', this);
    this.attachClick(this.submitButton, 'doSearch',this);
  },
  hasTerm: function() {
    return (this.searchField.value.length > 0);
  },
  getSearchTerm: function() {
    return this.searchField.value.replace(/ /g, "-");
  },
  keySearch: function(event){
    var key = event.which || event.keyCode;
    if(key == Event.KEY_RETURN)
      this.doSearch();
  },
  doSearch: function(){
    if(this.hasTerm())
      window.location = AltrecGlobals.Links.NonSecureDomain + "/" + this.getSearchTerm() + "/search.htm?sto=sfn&cn=1";         
  }
});

function SearchPayloadWidget(parent) {
  WidgetExtender.Setup(this);
  this.initWidget('searchBox', parent);
  this.lastSearchTerm = '';
  
  this.postDraw = function() {
    if (this.renderCount == 0) {
      this.searchField = this.getBoundElement('defaultText');
      this.searchField.setAttribute("autocomplete", "off");
      this.attachReturnKey(this.searchField, 'searchTerm');
      this.attachKeyup(this.searchField, 'keypress');
      this.attachKeydown(this.searchField, 'keydown');
      this.attachFocus(this.searchField, 'focus');
      ;
      this.attachBlur(this.searchField, 'blur');
      
    }
  };
  
  this.getResultsWidget = function() {
    return PayloadMediator.getWidget('SearchResultsPayloadWidget');
  };
  
  this.searchTerm = function() {
    var defaultSearcher = {
      hasTerm: function() {
        return (this.searchField.value);
      }
.bind(this)      ,
      getSearchTerm: function() {
        return this.searchField.value.replace(/ /g, "-");
      }
.bind(this)      ,
      search: function() {
        if (this.hasTerm()) {
          window.location = AltrecGlobals.Links.NonSecureDomain +
          "/" +
          this.getSearchTerm() +
          "/search.htm?sto=" +
          this.getSearchType() +
          "&cn=1";
        }
      },
      getSearchType: function() {
        return 'sfn'
      }
    };
    var searcher = (this.getResultsWidget() == null) ? defaultSearcher : this.getResultsWidget().getSearchObj(this.searchField, defaultSearcher);
    searcher.search();
  };
  
  this.focus = function() {
    if (this.searchField.value == this.payloadData.defaultText) {
      this.searchField.value = '';
    }
  };
  
  this.blur = function() {
    this.getResultsWidget().hide();
  };
  
  this.keypress = function(event) {
    var searchTerm = this.searchField.value;
    if (AltrecGlobals.Site.Search.autocompleteEnabled && this.lastSearchTerm != searchTerm && Event.KEY_RETURN != event.keyCode) {
      if (searchTerm.trim().length >= AltrecGlobals.Site.Search.minChars) {
        this.lastKeypress = new Date();
        InstanceContainer.FireEventAsync(this.uniqueWidgetId, 'sendAutocomplete', AltrecGlobals.Site.Search.msDelay);
      }
      else {
        var widget = this.getResultsWidget();
        if (widget != null) {
          widget.hide();
        }
      }
    }
    this.lastSearchTerm = searchTerm;
  };
  
  this.sendAutocomplete = function() {
    var now = new Date();
    if ((now.getTime() - this.lastKeypress.getTime()) >= (AltrecGlobals.Site.Search.msDelay)) {
      this.makeRequest({
        getUrl: function() {
          return AltrecGlobals.Site.Search.GetUrl();
        },
        method: 'jsonp',
        getParameters: function() {
          return {
            words: this.searchField.value
          };
        }
.bind(this)        ,
        blockingRequest: 'none'
      });
    }
  };
  
  this.keydown = function(event) {
    switch (event.keyCode) {
      case Event.KEY_UP:
        this.getResultsWidget().selectUp();
        break;
      case Event.KEY_DOWN:
        this.getResultsWidget().selectDown();
        break;
    }
  };
  
  this.getTemplateVar = function() {
    return "spw";
  };
}

function SearchResultsPayloadWidget(parent) {
  WidgetExtender.Setup(this);
  this.initWidget('autocomplete_choices', parent);
  this.items = [];
  this.bucketsElements = [];
  this.renderState = 'off';
  this.selectedItem = null;
  
  this.draw = function() {
    if (this.renderCount == 0) {
      this.listElement = $('auto_list');
      this.itemTemplate = $('auto_list_item');
      this.bucketHeader = $('auto_list_item_header');
    }
    
    var currentBucketName = '';
    for (var i = 0; i < this.bucketsElements.length; i++) {
      this.listElement.removeChild(this.bucketsElements[i]);
    }
    
    this.bucketsElements = [];
    this.payloadData.results = [];
    for (var i = 0; i < this.payloadData.buckets.length; i++) {
      for (var j = 0; j < this.payloadData.buckets[i].terms.length; j++) {
        var resultsObj = this.payloadData.buckets[i].terms[j];
        resultsObj.bucket = this.payloadData.buckets[i];
        this.payloadData.results.push(resultsObj);
      }
    }
    
    for (var i = 0; i < this.payloadData.results.length; i++) {
      var item;
      if (i < this.items.length) {
        item = this.items[i];
      }
      else {
        item = this.itemTemplate.cloneNode(true);
        this.listElement.appendChild(item);
        item.id = 'auto_item' + i;
        item.currentIndex = i;
        this.items.push(item);
        this.attachClick(item, 'clickItem', this, item);
        this.attachMouseOver(item, 'selectItem', this, item);
        this.attachMouseOut(item, 'unselectItem', this, item);
      }
      
      if (currentBucketName != this.payloadData.results[i].bucket.name) {
        var header = this.bucketHeader.cloneNode(true);
        currentBucketName = this.payloadData.results[i].bucket.name;
        Element.insert(item, {
          'before': header
        });
        header.style.display = 'block';
        this.bucketsElements.push(header);
        Element.bindJSON(header, this.getTemplateVar() + 'h', this.payloadData.results[i].bucket);
      }
      
      Element.bindJSON(item, this.getTemplateVar() + 'i', this.payloadData.results[i]);
    }
    
    for (var i = this.items.length; i > this.payloadData.results.length; i--) {
      var item = this.items.pop();
      this.listElement.removeChild(item);
    }
    
    this.unselectLast();
    if (this.renderState == 'off') {
      this.renderState = 'animatingOn';
      Effect.Appear(this.Container, {
        duration: 0.3,
        fps: 15,
        afterFinish: this.doneAnimatingOn.bind(this)
      });
    }
  };
  
  this.selectUp = function() {
    if (this.hasItemSelected()) {
      var idx = this.selectedItem.currentIndex;
      if (idx > 0) {
        this.selectItem(null, this.items[idx - 1]);
      }
    }
  };
  
  this.selectDown = function() {
    if (!this.hasItemSelected()) {
      this.selectItem(null, this.items[0]);
    }
    else {
      var idx = this.selectedItem.currentIndex;
      if (idx < this.items.length - 1) {
        this.selectItem(null, this.items[idx + 1]);
      }
    }
  };
  
  this.clickItem = function(event, item) {
    this.selectedItem = item;
    PayloadMediator.getWidget('SearchPayloadWidget').searchTerm();
  };
  
  this.selectItem = function(event, item) {
    this.unselectLast();
    this.selectedItem = item;
    Element.addClassName(item, 'selected');
  };
  
  this.unselectItem = function(event, item) {
    Element.removeClassName(item, 'selected');
    this.selectedItem = null;
  };
  
  this.hasItemSelected = function() {
    return (this.selectedItem != null);
  };
  
  this.unselectLast = function() {
    if (this.hasItemSelected()) 
      this.unselectItem(null, this.selectedItem)
  };
  
  this.getSearchObj = function(field, defaultSearcher) {
    if (!this.hasItemSelected()) 
      return defaultSearcher;
    
    var searchTermObj = this.payloadData.results[this.selectedItem.currentIndex];
    field.value = searchTermObj.p.stripTags();
    
    if (searchTermObj.bucket.search == "PID") { return {
      search: function() {
        AltrecGlobals.Coremetrics.SendEvent(searchTermObj.bucket.name, 'Site_Autocomplete');
        window.location = AltrecGlobals.Links.NonSecureDomain + "/" + searchTermObj.PID;
      }
.bind(this)
    }; }
    else {
      defaultSearcher.getSearchType = function() {
        return 'auto';
      };
      return defaultSearcher;
    }
  };
  
  this.hide = function() {
    if (this.renderState == 'on') {
      this.renderState = 'animatingOff';
      Effect.Fade(this.Container, {
        duration: 0.3,
        fps: 15,
        delay: .2,
        afterFinish: this.doneAnimatingOff.bind(this)
      });
    }
  };
  
  this.doneAnimatingOn = function() {
    this.renderState = 'on';
  };
  
  this.doneAnimatingOff = function() {
    this.renderState = 'off';
  };
  
  this.getTemplateVar = function() {
    return 'srpw';
  };
  
  this.destroy = function() {
    if (this.payloadData && this.payloadData.results) {
      //first break circular references
      for (var i = 0; i < this.payloadData.results.length; i++) {
        var item = this.items[i];
        item.bucket = null;
      }
    }
    
    this.payloadData = null;
    this.base.destroy();
  };
}

function CarouselBase() {
  this.currentPage = 1;
  this.visibleElements = [];
  this.forward = true;
  this.looping = false;
  this.hasTemplate = true;
  
  this.reset = function() {
    this.currentPage = 1;
    this.renderCount = 0;
    this.ListContainer.style.left = '0px';
  };
  
  this.draw = function() {
    if (!this.elemArr && this.getElemArray) 
      this.elemArr = this.getElemArray();
    
    if (!this.template && this.getTemplate) {
      this.template = this.getTemplate();
      this.templateWidth = Element.getWidth(this.template);
    }
    
    if (!this.templateWidth) 
      this.templateWidth = this.getTemplateWidth();
    
    if (!this.ListContainer) {
      this.ListContainer = this.getContainer();
    }
    
    if (this.hasTemplate) {
      var sizeStr = Element.readAttribute(this.Container, 'pagesize');
      if (sizeStr && sizeStr.length > 0) 
        this.pageSize = parseInt(sizeStr);
      
      if (this.renderCount == 0) {
        if (this.elemArr) {
          for (var i = 0; i < this.elemArr.length; i++) {
            this.bindElement(this.elemArr[i], this.addNewElement());
          }
        }
        
        if (!this.pageSize && this.options && this.options.viewPort) {
          this.pageSize = Math.floor(Element.getWidth(this.options.viewPort) / this.templateWidth);
        }
        this.pages = Math.ceil(this.getElemCount() / this.pageSize);
        if (this.pages > 1) 
          this.ListContainer.style.width = (this.templateWidth * this.getElemCount()) + 'px';
      }
      else {
        this.looping = true;
        var moveBy = this.templateWidth * this.pageSize;
        if (this.forward) 
          moveBy = moveBy * -1;
        new Effect.Move(this.ListContainer, {
          x: moveBy,
          y: 0,
          mode: 'relative',
          duration: .5,
          afterFinish: function() {
            this.looping = false;
          }
.bind(this)
        });
      }
      
      this.payloadData = {
        title: this.title,
        next: this.constructJavascriptLinkEvent('next'),
        prev: this.constructJavascriptLinkEvent('prev'),
        show_prev: (this.currentPage > 1),
        show_next: (this.currentPage < this.pages),
        pages: this.pages,
        current_page: this.currentPage
      };
      
      Element.bindJSON(this.Container, 'carousel', this.payloadData);
    }
  };
  
  this.next = function() {
    if (this.looping) 
      return;
    if (this.currentPage + 1 > this.pages) 
      return;
    this.currentPage++;
    this.forward = true;
    this.rerender();
    if (this.onNext) 
      this.onNext();
  };
  
  this.prev = function() {
    if (this.looping) 
      return;
    
    if (this.currentPage - 1 < 1) 
      return;
    
    this.currentPage--;
    this.forward = false;
    this.rerender();
    if (this.onPrev) 
      this.onPrev();
  };
}


function OldRecommender(id, cm_tag, URL, title) {
  WidgetExtender.Setup(this);
  this.isSingleton = false;
  CarouselBase.call(this);
  
  this.retryCount = 0;
  this.initWidget(id, null);
  
  this.cmTag = cm_tag;
  this.title = title;
  this.URLrequested = URL;
  this.elements = [];
  var isSecure = (window.location.protocol == "https:");

  this.getAssetDomain = function(){
    if(window.location.protocol.include('https')){
      return 'https://secure.altrec.com';
    }else{
      return 'http://static.altrec.com';
    }
  };

  this.massagePayload = function(){
    if(this.title == 'useBNtitle'){
      this.title = this.payloadData.myJSON.guides.w;
    };
  };
  
  this.getElemArray = function() {
    var returnArr = [];
    var myJSON = this.payloadData.myJSON;
    var assetDomain = this.getAssetDomain();
    if(!Object.isArray(myJSON.guides.r)){
      myJSON.guides.r = [myJSON.guides.r];
    }
    for (i = 0; i < myJSON.guides.r.length; i++) {
      var info = myJSON.guides.r[i];
      var img_link = info.a[0].v.replace("http://mirror.altrec.com", '').replace("http://www.altrec.com", "").replace("http://static.altrec.com", "");
      var prod = {
        price: ("$" + info.a[1].v + ((info.a.length > 2) ? (" - $" + info.a[2].v) : '')),
        product_image: [assetDomain, img_link].join(''),
        link: (AltrecGlobals.Links.NonSecureDomain + info.u.replace("http://www.altrec.com", "")),
        name: info.t,
        baynote_bnrank: (i + 1),
        baynote_req: myJSON.guides.gr,
        baynote_guide: myJSON.guides.g
      };
      
      try {
        if (prod.product_image.toLowerCase().indexOf('nophoto') < 0) {
          var imgParts = prod.product_image.toLowerCase().split('.');
          var pid = imgParts[imgParts.length - 2].split('_')[0];
          prod.baynote_pid = pid;
        }
      } 
      catch (e) {
      }
      
      returnArr.push(prod);
    }
    
    return returnArr;
  };
  
  this.getElemCount = function() {
    return this.elemArr.length;
  };
  
  
  this.getTemplate = function() {
    var temp = this.getFirstClass('bn_g_result');
    if (!temp) {
      var template = $('baynoteListTemplate').cloneNode(true);
      template.id = this.uniqueWidgetId + '_baynotTemplate';
      this.Container.appendChild(template);
      template.style.display = 'block';
      temp = this.getFirstClass('bn_g_result');
    }
    return temp;
  };
  
  this.onNext = function() {
    this.sendCmEvent('ReccomenderNext_' + cm_tag);
  };
  
  this.onPrev = function() {
    this.sendCmEvent('ReccomenderPrev_' + cm_tag);
  };
  
  this.getContainer = function() {
    return this.getFirstClass('bn_g_result_container');
  };
  
  this.addNewElement = function() {
    var elem = this.template.cloneNode(true);
    this.ListContainer.appendChild(elem);
    this.elements.push(elem);
    return elem;
  };
  
  this.bindElement = function(info, elem) {
    Element.bindJSON(elem, this.getTemplateVar(), info);
  };
  
  this.getTemplateVar = function() {
    return 'bnr';
  };
  
  this.makeRequestObj = {
    getUrl: function() {
      return "/servlets/xml_to_json_proxy";
    },
    method: 'get',
    getParameters: function() {
      var bnU = getCookie('bn_u');
      bnU = (bnU && bnU.length > 0) ? bnU : '2008ARP';
      var url = this.widget.URLrequested;
      return {
        url: (bnU && bnU.length > 0) ? url.replace("?", "?userId=" + bnU + "&amp;") : url
      };
    },
    callback: function(payload) {
      if (AltrecGlobals.Functions.Object.isEmpty(payload)) {
        Effect.Fade(this.widget.Container.id, {
          duration: .5
        });
      }
      else{
        this.widget.render({
          myJSON: payload
        });
      }
    }
  };
  
  
  this.payloadData = {};
  this.makeRequest(this.makeRequestObj);
  
  this.requestNewRecomenders = function(url, title) {
    if (!title) 
      this.title = title;
    this.URLrequested = url;
    for (var i = 0; i < this.elements.length; i++) {
      var elem = this.elements[i];
      this.ListContainer.removeChild(elem);
    }
    this.elements = [];
    this.reset();
    this.makeRequest(this.makeRequestObj);
  };
  
  this.init = function() {
  };//do nothing
}

var OverlaySingleton = {
  getContainer: function() {
    if (!this.overlayContainer) {
      this.overlayContainer = $('overlayContainer');
    }
    return this.overlayContainer;
  },
  
  show: function(message) {
    var overlay = new OverlayPayloadWidget(this);
    overlay.render({
      error_message: message
    });
  }
};

function OverlayBaseWidget(parent) {
  WidgetExtender.Setup(this);
  this.isOpen = false;
  this.openOnRender = true;
  
  this.cmEventProps.getWidget = function() {
    var obj = InstanceContainer.GetInstance(PayloadMediator.lastMakeRequestId);
    if (obj) 
      return obj.widget;
    else 
      return null;
  }
.bind(this);
  
  this.initElement = function(container, parent) {
    this.initWidget(container, parent);
    this.htmlHolderId = this.Container;
  };
  
  this.preDraw = function() {
    if (this.openOnRender) {
      this.openOverlay();
    }
  };
  
  this.closeOverlay = function() {
    this.modal.Hide();
  };
  
  this.fireDetachFromOverlay = function() {
    InstanceContainer.FireEventAsync(this.uniqueWidgetId, 'detachFromOverlay', 300);
  };
  
  this.detachFromOverlay = function() {
    this.content.style.display = 'none';
    document.body.appendChild(this.content);
    this.isOpen = false;
    
    if (this.reopen) 
      this.rerender();
  };
  
  this.openOverlay = function() {
    if (!this.OverlayContainer) {
      var overlayToClone = OverlaySingleton.getContainer();
      this.OverlayContainer = overlayToClone.cloneNode(true);
      this.OverlayContainer.id = 'overlay' + AltrecGlobals.Pages.Checkout.getNextUniqueId();
      document.body.appendChild(this.OverlayContainer);
      this.placeHolder = Element.findElementsByClassName(this.OverlayContainer, 'overlayPlaceholder')[0];
    }
    
    var objToBind = {
      closeLink: this.constructJavascriptLinkEvent('closeOverlay')
    };
    
    Object.extend(objToBind, this.payloadData || {});
    Element.bindJSON(this.OverlayContainer, 'OverlayMessagePayloadVar', objToBind);
    
    if (!this.defaultWidth) 
      this.defaultWidth = 250;
    if (!this.content) {
      this.content = $(this.htmlHolderId);
      var stmt = null;
      try {
        stmt = Element.readAttribute(this.content, "tmpl:props") || Element.readAttribute(this.content, 'props');
      } 
      catch (e) {
      }
      if (stmt) {
        var obj = JsonBinder.evalStatement(stmt, {}, this.content);
        if (obj.width) 
          this.defaultWidth = parseInt(obj.width);
      }
      
    }
    
    this.placeHolder.appendChild(this.content);
    this.content.style.display = '';
    
    this.isOpen = true;
    var options = {
      width: this.defaultWidth,
      transparent: true
    };
    
    if (this.onOpenOverlay) {
      options.callback = this.onOpenOverlay;
    }
    
    this.modal = WebModal.Show(this.OverlayContainer, options);
    this.modal.RegisterPostHide(this.fireDetachFromOverlay.bind(this));
  };
}

function OverlayPayloadWidget(parent) {
  this.isSingleton = true;
  OverlayBaseWidget.call(this);
  this.initElement('EmptyOverlayMessage', parent);
  
  this.massagePayload = function() {
    this.payloadData.submitLink = this.constructJavascriptLinkEvent('submit');
  };
};


function OverlayInteractivePayloadWidget(parent) {
  WidgetExtender.Setup(this);
  this.initWidget('overlayContainer', parent);
  this.InteractiveOverlay = null;
  
  
  this.draw = function() {
    this.InteractiveOverlay = null;
    for (var subPayload in this.payloadData.SubPayloads) {
      this.InteractiveOverlay = PayloadMediator.getWidget(subPayload + 'Widget');
      var payload = this.payloadData.SubPayloads[subPayload];
      if (Object.isString(payload)) 
        payload = {};
      Object.extend(payload, this.payloadData);
      this.InteractiveOverlay.render(payload);
    }
    
    if (!this.InteractiveOverlay) {
      var overlay = new OverlayPayloadWidget(parent);
      overlay.render(this.payloadData);
    }
  };
}


function OI_ContactCSPayloadWidget(parent) {
  this.isSingleton = true;
  OverlayBaseWidget.call(this);
  this.initElement('contactCSMessage', parent);
};

function loadWidgets() {
}


function cmNav() { return true; }

AltrecGlobals.Objects['BaseMenu'] = function(options) {
  WidgetExtender.Setup(this);
  this.options = Object.extend({
    openDelayAmount: 350
  }, options || {});
  
  this.initWidget(options.container);
  this.button = $(options.button);
  
  this.isOpen = false;
  this.subNavMap = {};
  this.openedSecond = null;
  this.closeTimeout = null;
  
  
  this.preDraw = function() {
    if (this.renderCount == 0) {
      this.payloadData.open = this.constructJavascriptLinkEvent('clickOpen');
      if (!this.options.menu) 
        this.options.menu = this.options.container;
      this.menu = $(this.options.menu);
      this.buttonContainer = new AltrecGlobals.Functions.Element.Position(this.button);
      
      
      this.attachMouseMove(document.body, this.recordXY);
      this.attachEvent((document.onresize ? document : window), "resize", this.resize, this);
      
      if (this.options.subItemClass) {
        for (var i = 0; i < this.menu.childNodes.length; i++) {
          var node = this.menu.childNodes[i];
          if (node.className && node.className.indexOf(this.options.subItemClass) >= 0) {
            if (!node.id || node.id == '') 
              node.id = 'navNode' + AltrecGlobals.Pages.Checkout.getNextUniqueId();
            this.attachMouseOver(node, 'subNav', this, node.id);
          }
        }
      }
      
    }
  };
  
  this.clickOpen = function() {
    if (!this.isOpen) 
      this.openNav();
    else 
      this.closeNav(0);
    
    this.buttonContainer = new AltrecGlobals.Functions.Element.Position(this.button);
  };
  
  this.postDraw = function() {
    if (AltrecGlobals.Variables.ShowNav) 
      this.openNav();
  };
  
  this.resize = function() {
    if (this.openedSecondContainer) 
      this.onSecondContainer();
    this.closeNav(0);
    this.menuContainer = null;
  };
  
  this.recordXY = function(event) {
    var x = Event.pointerX(event);
    var y = Event.pointerY(event);
    this.overBtn = this.buttonContainer.within(x, y);
    if (this.overBtn || (this.menuContainer && this.menuContainer.within(x, y)) ||
    (this.openedSecondContainer && this.openedSecondContainer.within(x, y))) {
      if (this.openedSecondContainer && this.openedSecondContainer.within(x, y)) 
        this.onSecondContainer();
      this.openDelay(this.overBtn);
    }
    else 
      this.closeNav(300);
  };
  
  
  this.openDelay = function(paramEvent) {
    if (AltrecGlobals.Variables.SupressNav) 
      return;
    
    if (this.animating || (!this.isOpen && (!paramEvent && !AltrecGlobals.Variables.ShowNav))) 
      return;
    if (this.isOpen) {
      if (!this.forceClose) 
        this.openNav();
    }
    else if (!this.openTimeout) 
      this.openTimeout = this.fireAsync('openNav', this.options.openDelayAmount);
  };
  
  this.openNav = function() {
    this.markedClosed = false;
    this.forceClose = false;
    
    if (this.openTimeout) {
      clearTimeout(this.openTimeout);
      this.openTimeout = null;
      if (!this.overBtn) 
        return;
    }
    
    if (this.isOpen) 
      return;
    this.sisterMenus.each(function(menu) {
      menu.forceClose = true;
      menu.closeNav(0);
    });
    
    if (this.options.onOpenNav) 
      this.options.onOpenNav();
    
    if (this.btnOpenClass) 
      this.button.className = this.btnOpenClass;
    this.isOpen = true;
    
    Element.setOpacity(this.menu, 0);
    this.menu.style.display = 'block';
    if (!this.menuContainer) {
      this.menuContainer = new AltrecGlobals.Functions.Element.Position(this.menu);
      var navFiller = $('homepageSideFiller');
      if (navFiller) 
        navFiller.style.height = this.menuContainer.height + 'px';
    }
    
    this.animating = true;
    new Effect.Opacity(this.menu, {
      from: 0.0,
      to: 1.0,
      duration: .3,
      afterFinish: function() {
        this.animating = false;
      }
.bind(this)
    });
    
  };
  
  this.counter = 0;
  this.closeNav = function(timeout) {
    if (!this.forceClose && (this.animating || !this.isOpen || this.tryingToClose)) 
      return;
    
    if (!AltrecGlobals.Variables.ShowNav && this.options.onCloseNav) 
      this.options.onCloseNav();
    
    this.markedClosed = true;
    this.tryingToClose = true;
    this.closeTimeout = this.fireAsync('tryClose', timeout);
  };
  
  this.tryClose = function() {
    clearTimeout(this.closeTimeout);
    this.closeTimeout = null;
    
    if (this.isOpen && this.markedClosed) {
      this.closeLastSubNav();
      if (!AltrecGlobals.Variables.ShowNav) {
        if (this.btnOpenClass) 
          this.button.className = '';
        this.isOpen = false;
        
        Element.setOpacity(this.menu, 1);
        this.animating = true;
        new Effect.Opacity(this.menu, {
          from: 1,
          to: 0,
          duration: .3,
          afterFinish: function() {
            this.animating = false;
            this.menu.style.display = 'none';
          }
.bind(this)
        });
        
      }
    }
    this.tryingToClose = false;
  };
  
  this.closeLastSubNav = function(elem) {
    if (this.options.onCloseSubNav) 
      this.options.onCloseSubNav();
    
    if (this.openedElem && (!elem || this.openedElem.id != elem.id)) 
      this.openedElem.className = "topMenuLI";
    if (this.openedSecond) {
      this.openedSecond.style.display = 'none';
    }
    this.openedSecondContainer = null;
    this.openedElem = null;
  };
  
  this.subNav = function(event, id) {
    var elem = $(id);
    if (this.openedElem && elem && this.openedElem.id == elem.id) 
      return;
    
    if (!this.subNavMap[elem.id]) 
      this.subNavMap[elem.id] = AltrecGlobals.Functions.Element.getFirstClassName(elem, 'secondMenuUL');
    ;
    
    elem.className = "topMenuLI Open";
    if (this.lastElemOver) 
      this.lastElemOver.className = "topMenuLI";
    this.lastElemOver = elem;
    
    if (this.closeNavTimeout) 
      clearTimeout(this.closeNavTimeout);
    
    this.requestedSubNav = id;
    this.closeNavTimeout = this.fireAsync('openSubNav', 300);
  };
  
  
  this.onSecondContainer = function() {
    clearTimeout(this.closeNavTimeout);
    if (this.lastElemOver) 
      this.lastElemOver.className = "topMenuLI";
  };
  
  this.openSubNav = function() {
    clearTimeout(this.closeNavTimeout);
    this.closeNavTimeout = null;
    
    var elem = $(this.requestedSubNav);
    this.closeLastSubNav(elem);
    var secondUl = this.subNavMap[elem.id];
    if (secondUl) {
      this.lastElemOver = null;
      secondUl.style.display = 'block';
      this.openedSecond = secondUl;
      this.openedElem = elem;
      this.openedElem.className = "topMenuLI Open";
      if (!this.openedSecondContainer) 
        this.openedSecondContainer = new AltrecGlobals.Functions.Element.Position(this.openedSecond);
    }
    else 
      this.openedSecondContainer = null;
  };
  
  this.getTemplateVar = function() {
    return 'nav';
  };
};

function NavBarWidget() {
  var removeZoom = function() {
    if (!AltrecGlobals.Variables.ShowNav && AltrecGlobals.Site.AssetArea) {
      AltrecGlobals.Site.AssetArea.showImage();
      if (AltrecGlobals.Site.AssetArea.zoom) 
        AltrecGlobals.Site.AssetArea.zoom.removeFloater();
    }
  };
  
  AltrecGlobals.Objects.BaseMenu.apply(this, [{
    container: 'topNavBarInner',
    subItemClass: 'topMenuLI',
    menu: 'menu',
    button: 'ddButton',
    //btnOpenClass : 'open',
    onCloseSubNav: removeZoom,
    onCloseNav: function() {
      var o = $('ddButtonImage')
      if (o.hasClassName('open')) 
        o.removeClassName('open');
      removeZoom;
    },
    onOpenNav: function() {
      var o = $('ddButtonImage')
      if (!o.hasClassName('open')) 
        o.addClassName('open');
      removeZoom;
    }
  }]);
}




var AltrecCookieManager = {
  previousAccess: null,
  lastAccessName: 'lastaccessdatetime',
  _setLastAccessCookie: function() {
    this.previousAccess = getCookie(this.lastAccessName);
    var now = expires = new Date();
    now.setTime(now.getTime());
    setCookie(this.lastAccessName, expires.valueOf(), 365);
    this.lastAccessCookie = getCookie(this.lastAccessName);
  },
  
  GetLocID: function() { return getCookie('locid'); },
  
  HasLocID: function() { return (this.GetLocID()) ? true : false; },
  
  Init: function() {
    var droppedNewCookie = false;
    if (!this.init) {
      this.init = true;
      var lastAccessCookie = getCookie(this.lastAccessName);
      if (!lastAccessCookie) {
        this._setLastAccessCookie();
        droppedNewCookie = true;
      }
      
      this.lastAccessCookie = getCookie(this.lastAccessName);
    }
    return droppedNewCookie;
  },
  
  GetElapsedTime: function() {
    try {
      return (parseInt(this.lastAccessCookie) - parseInt(this.previousAccess));
    } 
    catch (e) {
      return 0;
    }
  },
  
  UpdateLastAccess: function() {
    this.Init();
    this._setLastAccessCookie();
  },
  
  NeedToRefreshCart: function() {
    try {
      var serverTime = new Date(AltrecGlobals.Site.ServerTime);
      var cookie = getCookie('cart_recovery_time');
      if (cookie.charAt(0) == '"') 
        cookie = cookie.substring(1, getCookie('cart_recovery_time').length - 1);
      cookie = unescape(cookie).replace(/\+/g, " ");
      var cartExpiry = new Date(cookie);
      
      return (cartExpiry < serverTime);
      
    } 
    catch (e) {
      return false;
    }
    return false;
  }
};

function TopMessagingPayloadWidget(parent) {
  WidgetExtender.Setup(this);
  this.isSingleton = false;
  this.initWidget('pageErrorContainer', parent);
  var errorDiv = $('errorBox');
  var alertDiv = $('alertBox');
  this.cmEventProps.getWidget = function() {
    var obj = InstanceContainer.GetInstance(PayloadMediator.lastMakeRequestId);
    if (obj) 
      return obj.widget;
    else 
      return null;
  }
.bind(this);
  
  this.draw = function() {
    if (this.payloadData.top_page && this.payloadData.top_page.length > 0) {
      this.removeError();
      TopMessagingWidget.showing = false;
      TopMessagingWidget.ShowError(this.payloadData.top_page);
      return false;
    }
    
    if (this.payloadData.error_type.toLowerCase() == 'info' ||
    this.payloadData.error_type.toLowerCase() == 'alert' ||
    this.payloadData.error_type.toLowerCase() == 'message') {
      var clonedAlert = alertDiv.cloneNode(true);
      this.payloadData.closeLink = this.constructJavascriptLinkEvent('hideGhost');
      Element.bindJSON(clonedAlert, this.getTemplateVar(), this.payloadData);
      this.modal = WebModal.Show(clonedAlert, {
        maskBackground: true,
        transparent: true,
        hideBleeders: false,
        topPercentage: 40,
        width: 280,
        maxOpacity: 0
      });
      
      Element.setOpacity(clonedAlert, .95);
      if (this.payloadData.error_no_close != true) 
        setTimeout('WebModal.Hide("' + this.modal.id + '")', this.payloadData.error_duration || 5000);
    }
    else if (this.payloadData.error_type.toLowerCase() == 'error') {
      Element.bindJSON(errorDiv, this.getTemplateVar(), this.payloadData);
      if (!TopMessagingWidget.showing) {
        TopMessagingWidget.showing = true;
        Effect.BlindDown(errorDiv, {
          duration: 0.5,
          queue: 'end',
          afterFinish: function() {
            this.scrollToError();
          }
.bind(this)
        });
      }
      else {
        this.scrollToError();
      }
      TopMessagingWidget.showing = true;
    }
  };
  
  this.hideGhost = function() {
    WebModal.Hide(this.modal.id);
  };
  
  this.scrollToError = function() {
    var topTitle = $('topTitleHolder');
    if(topTitle){
      Effect.ScrollTo(topTitle, {
      duration: 0.3,
      queue: {
        position: 'end',
        scope: 'scrollQueue'
      },
      afterFinish: function() {
        new Effect.Highlight(Element.findElementsByClassName($('errorBox'), 'errorContent')[0], {
          startcolor: '#ffff99',
          endcolor: '#ffffff',
          queue: 'end',
          duration: 0.2
        });
      }
    });
  };
  };
  
  this.removeError = function() {
    if (TopMessagingWidget.showing) {
      TopMessagingWidget.showing = false;
      Effect.BlindUp(errorDiv, {
        duration: 0.3,
        queue: 'front'
      });
    }
    TopMessagingWidget.showing = false;
  };
  
  this.getTemplateVar = function() {
    return 'messageVar';
  };
}

//utility class to static-tize the widget 
var TopMessagingWidget = {
  showing: false,
  
  ShowError: function(errorMessage, message_name) {
    var errorObj = {
      error_message: errorMessage,
      error_type: 'error'
    };
    
    if (message_name) 
      errorObj.message_name = message_name;
    
    PayloadMediator.getWidget('TopMessagingPayloadWidget').render(errorObj);
  },
  
  HideError: function() {
    PayloadMediator.getWidget('TopMessagingPayloadWidget').removeError();
  }
};

function EmailValWidget(parent, options) {
  WidgetExtender.Setup(this);
  this.initWidget(options.Contianer, parent);
  this.email = options.email;
  this.eventName = options.submitName;
  
  this.validator = new Validation(options.Contianer, options.validatorParams || {});
  this.lastValidateStr = '';
  
  this.validate = function() {
    var result = this.validator.validate();
    return result;
  };
  
  this.reset = function() {
    this.validator.reset();
  };
  
  this.validateEmail = function(type, v, elem) {
    if (this.hasServerError && this.lastValidateStr == this.getFieldVals()) { return (type != 'main'); }
    return this.testMatch(type, v);
  };
  
  this.testMatch = function(type, v) {
    this.hasServerError = false;
    this.validator.reset();
    this.render({});
    this.setError('main', '');
    this.setError('confirm', '');
    
    if (v.trim().length == 0 || !AltrecGlobals.Site.checkEmail(v)) {
      this.setError(type, 'Please enter a valid email address, for example name@example.com');
      return false;
    }
    
    
    if (this.confirm) {
      if (this.confirm.value.trim().length > 0 &&
      this.email.value.trim().length > 0 &&
      this.email.value.trim() != this.confirm.value.trim()) {
        this.setError(type, 'Email fields do not match');
        return false;
      }
      
      if (this.confirm.value.trim().length == 0 || this.email.value.trim().length == 0) 
        return true;
    }
    
    if (this.lastValidateStr == this.getFieldVals()) 
      return true;
    
    
    this.lastValidateStr = this.getFieldVals();
    this.makeRequest({
      getUrl: function() { return "/servlets/AccountLogin_Service"} ,
      getParameters: function() { return {
        action: "emailvalidation",
        emailAddress: escape(this.email.value)
      }; }
.bind(this)      ,
      blockingRequest: 'none',
      callback: function(payload) {
        if (payload.responseType.toLowerCase().indexOf('error') >= 0) {
          this.setError('main', payload.message);
          this.hasServerError = true;
          this.validator.validate();
        }
        else {
          this.validator.reset();
          this.payload = {};
          if (this.eventName) {
            AltrecGlobals.Coremetrics.SendEvent(this.eventName, 'emailSignups');
          }
          
          if (payload.responseType.toLowerCase().indexOf('warning') >= 0) {
            this.payload = payload;
          }
          this.render(this.payload);
        }
      }
.bind(this)
    });
    
    return true;
  };
  
  this.getFieldVals = function() {
    return this.email.value + ":" + ((this.confirm) ? this.confirm.value.trim() : '');
  };
  
  this.getError = function(errorType) {
    return this['email' + errorType + 'Msg'];
  };
  
  this.setError = function(errorType, error) {
    this['email' + errorType + 'Msg'] = error;
  };
  
  this.getTemplateVar = function() {
    return 'email';
  };
  var prefix = (options.classPrefix) ? options.classPrefix : '';
  if (options.confirm) {
    this.confirm = options.confirm;
    Validation.add(prefix + 'required-confirm-email', this.getError.bind(this, 'confirm'), this.validateEmail.bind(this, 'confirm'));
  }
  
  Validation.add(prefix + 'required-email', this.getError.bind(this, 'main'), this.validateEmail.bind(this, 'main'));
  this.render({});
}

AltrecGlobals.Widgets.DODCountDownWidget = function(parent) {
  WidgetExtender.Setup(this);
  this.initWidget('dodCountdown', parent);
  
  this.preDraw = function() {
    if (!this.pageLoad) {
      this.pageLoad = new Date(this.payloadData.expires_at - this.payloadData.expires_in);
    }
    
    var secondsLeft = ((this.payloadData.expires_in -
    ((new Date().getTime()) - this.pageLoad.getTime())) /
    1000);
    
    var pd = this.payloadData;
    pd.expired = false;
    if (secondsLeft < 0 && this.hadPositive) {
      window.location = window.location.href;
      return false;
    }
    else {
      pd.hour = Math.floor(secondsLeft / 60 / 60);
      secondsLeft = secondsLeft - (pd.hour * 60 * 60);
      pd.min = Math.floor(secondsLeft / 60);
      secondsLeft = secondsLeft - (pd.min * 60);
      pd.sec = Math.floor(secondsLeft);
      this.hadPositive = true;
    }
    
    if (pd.hour < 10) 
      pd.hour = '0' + pd.hour;
    if (pd.min < 10) 
      pd.min = '0' + pd.min;
    if (pd.sec < 10) 
      pd.sec = '0' + pd.sec;
    
    pd.output = pd.hour + ":" + pd.min + ":" + pd.sec;
    this.payloadData = pd;
  };
  
  this.postDraw = function() {
    this.fireAsync('rerender', 500);
  };
  
  this.getTemplateVar = function() {
    return 'dod'
  };
  
};

SnippetPayloadWidget = function(parent) {
  WidgetExtender.Setup(this);
  this.popupId = 'snippet_zip_popup';
  this.initWidget(this.popupId, parent);
  this.popup = this.Container;
  this.isDetailPage = AltrecGlobals.Pages.PageName == 'DetailPage' ? true : false;
  var snippetContainers = AltrecGlobals.Variables.snippetContainers;
  this.containers = snippetContainers ? snippetContainers : ['header_banner','footer_banner','cart_banner','sub_hero_three','snippet_zip_popup','shippingList','detail_bullet','cart_upgrade','detail_icon'];

  this.getTemplateVar = function(){
    return 'snippetVar';
  };
  
  this.openPopup = function() {
    this.overlay = WebModal.Show(this.popupId,{width:300});
    $('snippet_zip_code').focus();
  };

  this.isCheckout = function(){
    var pages = ['cart','address','review'];
    return pages.indexOf(AltrecGlobals.Pages.PageName) >= 0;
  };

  this.showZone = function(){
    return AltrecGlobals.Functions.Cookies.get('csSessionID');
  }

  this.massagePayload = function(){
    this.pubEvent('getShipZoneFromField', 'zoneLink');
    this.pubEvent('openPopup');
    // hack
    var shipTo = 'Your House';
    var location = this.payloadData.Location ? this.payloadData.Location : false;
    if(location && location.city && location.city.length > 2){
      shipTo = location.city + ', ' + location.state_code;
    }else if(location && location.postalCode){
      shipTo = location.postalCode;
    };
    if(this.showZone())
      shipTo = [shipTo,' [',this.payloadData.ZoneName,']'].join('');
    this.payloadData.shipTo = shipTo;
    this.payloadData.disablePopUp = this.isCheckout() ? 'true' : '';

  };

  this.getPostCode = function() { 
    return this.getBoundElement('postalCodeMsg'); 
  };

  this.getPostalCodeValue = function() {
	return $('snippet_zip_code').value.replace(/\s*/g, "");
  };

  this.getShipZoneFromField = function(){
    this.getZone(this.getPostalCodeValue());
  };

  this.draw = function(){
    var loadedSnipplets = [];
    if(this.payloadData.snipplets){
      var snipplets = this.payloadData.snipplets;
      for(var i=0; i<this.containers.length; i++){
        var snippet = snipplets[this.containers[i]];
        if(snippet){
          var snippetDom = $(snippet.name);
          if(snippetDom && snippet.content){
            snippetDom.update(snippet.content);
          };
          var payload = Object.extend(snippet,this.payloadData);
          if(snippetDom){
            Element.bindJSON( snippet.name, this.getTemplateVar(), payload);
            loadedSnipplets.push(snippet.name);
          };
        };
      };
    }
    // we still need to wire in click actions for snippets loaded via server templating
    for(var i=0; i<this.containers.length; i++){
      if(loadedSnipplets.indexOf(this.containers[i]) < 0){
        var e = this.containers[i];
        var ob = $(e);
        if(ob)
          Element.bindJSON( this.containers[i], this.getTemplateVar(), this.payloadData);
      };
    }
  };

  this.postDraw = function(){
    this.lastFreeShipThreshold = this.payloadData.freeshipthreshold;
    var cart = PayloadMediator.getWidget('CartPayloadWidget');
    if(cart){
      this.freeShippingUpdate(cart.lastInvoiceSubTotal);
    }
  };

  this.getZone = function(postalCode){
    if(!this.isDetailPage){
      this.makeRequest({
        getUrl: function(){
          return '/servlets/zones';
        },
        javaMethod: 'identifyZone',
        getParamArray: function(){
          return [postalCode];
        },
        onComplete : function(){
          WebModal.Hide();
        }.bind(this),
        blockingRequest : 'none'
      });
    }else{
      var shipDeliveryWidget = PayloadMediator.getWidget('ShipDeliveryPayloadWidget');
      shipDeliveryWidget.getShippingLevels(postalCode);
    }
	};

  this.freeShippingUpdate = function(invoice_total){
    if(invoice_total){
      invoice_total = invoice_total.replace(',','');
    };
    var total = parseFloat(invoice_total);
    var addMore = 0;
    if(AltrecGlobals.Pages.PageName == 'cart' && this.lastFreeShipThreshold && this.lastFreeShipThreshold > 0 && total > 0){
      if(this.lastFreeShipThreshold > total){
        addMore = ((this.lastFreeShipThreshold * 100) - (total * 100))/100;
      };
    };
    var el = $('shippingUpgrade');
    if(el)
      Element.bindJSON('shippingUpgrade', 'snippetVar', Object.extend(this.payloadData,{addMore: addMore.toFixed(2)}));
  };
  this.attachReturnKey($('snippet_zip_code'), 'getShipZoneFromField');
};

TrackingPayloadWidget = function(parent) {
  WidgetExtender.Setup(this);
  this.initWidget('tracking_wrapper', parent);
  this.postDraw = function() {
    this.loadBeacons();
  };
  this.loadBeacons = function() {
    if (this.payloadData && this.payloadData.GoogleRemarketing) 
      this.loadGoogleRemarketing(this.payloadData.GoogleRemarketing);
  };
  this.loadGoogleRemarketing = function(labels) {
    var defaults = {
      width: 1,
      height: 1,
      conversion_id: 1037949193
    };
    var wrapper = $(this.id);
    for (var i = 0; i < labels.length; i++) {
      var options = Object.extend(defaults, labels[i] || {});
      if (!options.label) 
        continue;
      var image_source = ['http://www.googleadservices.com/pagead/conversion/', options.conversion_id, '/?label=', options.label, '&amp;guid=ON&amp;script=0'].join('');
      var googleImage = new Element('img', {
        'src': image_source,
        'width': options.width,
        'height': options.height
      });
      wrapper.appendChild(googleImage);
    };
      };
};


var WidgetModelCollection = Class.create({
  initialize: function(config) {
    this.config = {
      _id: 'WidgetModel' + AltrecGlobals.Pages.Checkout.getNextUniqueId(),
      key: 'id'
    };
    Object.extend(this.config, config);
    this.name = this.config._id;
    this.data = {};
  },
  curGen: 0,
  events: new EventEmitter(),
  getRecords: function() {
    var d = [];
    for (var k in this.data) {
      d.push(this.data[k].ob)
    };
    return d;
  },
  on: function(eventName, callback) {
    this.events.addListener(eventName, callback);
    return this;
  },
  addRecord: function(ob) {
    if (ob[this.config.key]) {
      var key = ob[this.config.key];
      if (this.data[key]) 
        this.fireEvent(key + '.update', ob);
      else 
        this.fireEvent(key + '.create', ob);
      this.data[key] = {
        ob: ob,
        gen: this.curGen
      };
    }
    //TODO add error handling here
  },
  find: function(key) {
    if (this.data[key]) { return this.data[key].ob; }
    else { return false; }
  },
  reloadRecords: function(records) {
    this.curGen++;
    for (var i = 0; i < records.length; i++) {
      this.addRecord(records[i]);
    }
    
    for (var k in this.data) {
      var obj = this.data[k];
      if (obj.gen === (this.curGen - 1)) {
        this.fireEvent(k + '.delete', obj.ob);
        delete this.data[k];
      }
    }
    
    this.fireEvent('reload', records);
  },
  fireEvent: function(name, ob) {
    this.events.emit([this.name, name].join('.'), ob);
  }
});

function Recommender(options) {
  WidgetExtender.Setup(this);
  this.isSingleton = false;
  CarouselBase.call(this);
  
  this.retryCount = 0;
  this.initWidget(options.id, null);
  
  this.cmTag = options.cm_tag;
  this.title = options.title;
  this.domId = options.id;
  this.URLrequested = options.url;
  this.defaultDiv = options.defaultDiv ? options.defaultDiv : false;
  this.elements = [];
  this.testEmpty = options.testEmpty ? options.testEmpty : false;
  var isSecure = (window.location.protocol == "https:");

  this.getAssetDomain = function(){
    if(window.location.protocol.include('https')){
      return 'https://secure.altrec.com';
    }else{
      return 'http://static.altrec.com';
    }
  };

  this.massagePayload = function(){
    if(this.title == 'useBNtitle'){
      this.title = this.payloadData.myJSON.guides.w;
    };
  };
  
  this.getElemArray = function() {
    var returnArr = [];
    var myJSON = this.payloadData.myJSON;
    var assetDomain = this.getAssetDomain();
    if(!Object.isArray(myJSON.guides.r)){
      myJSON.guides.r = [myJSON.guides.r];
    };
    
    for (i = 0; i < myJSON.guides.r.length; i++) {
      var info = myJSON.guides.r[i];
      var img_link = info.a[0].v.replace("http://mirror.altrec.com", '').replace("http://www.altrec.com", "").replace("http://static.altrec.com", "");
      if(options.useLargeImage){
        img_link = img_link.replace(/\_o/,'_d');
      };
      var prod = {
        price: ("$" + info.a[1].v + ((info.a.length > 2) ? (" - $" + info.a[2].v) : '')),
        product_image: [assetDomain, img_link].join(''),
        link: (AltrecGlobals.Links.NonSecureDomain + info.u.replace("http://www.altrec.com", "")),
        name: info.t,
        baynote_bnrank: (i + 1),
        baynote_req: myJSON.guides.gr,
        baynote_guide: myJSON.guides.g
      };
      
      try {
        if (prod.product_image.toLowerCase().indexOf('nophoto') < 0) {
          var imgParts = prod.product_image.toLowerCase().split('.');
          var pid = imgParts[imgParts.length - 2].split('_')[0];
          prod.baynote_pid = pid;
        }
      } 
      catch (e) {
      }
      
      returnArr.push(prod);
    }
    
    return returnArr;
  };
  
  this.getElemCount = function() {
    return this.elemArr.length;
  };
  
  
  this.getTemplate = function() {
    var temp = this.getFirstClass('bn_g_result');
    if (!temp) {
      var template = $('baynoteListTemplate').cloneNode(true);
      template.id = this.uniqueWidgetId + '_baynotTemplate';
      this.Container.appendChild(template);
      template.style.display = 'block';
      temp = this.getFirstClass('bn_g_result');
    }
    return temp;
  };
  
  this.onNext = function() {
    this.sendCmEvent('ReccomenderNext_' + cm_tag);
  };
  
  this.onPrev = function() {
    this.sendCmEvent('ReccomenderPrev_' + cm_tag);
  };
  
  this.getContainer = function() {
    return this.getFirstClass('bn_g_result_container');
  };
  
  this.addNewElement = function() {
    var elem = this.template.cloneNode(true);
    this.ListContainer.appendChild(elem);
    this.elements.push(elem);
    return elem;
  };
  
  this.bindElement = function(info, elem) {
    Element.bindJSON(elem, this.getTemplateVar(), info);
  };
  
  this.getTemplateVar = function() {
    return 'bnr';
  };
  
  this.makeRequestObj = {
    getUrl: function() {
      return "/servlets/xml_to_json_proxy";
    },
    method: 'get',
    getParameters: function() {
      var bnU = getCookie('bn_u');
      bnU = (bnU && bnU.length > 0) ? bnU : '2008ARP';
      var url = this.widget.URLrequested;
      return {
        url: (bnU && bnU.length > 0) ? url.replace("?", "?userId=" + bnU + "&amp;") : url
      };
    },
    callback: function(payload) {
      if (AltrecGlobals.Functions.Object.isEmpty(payload) || this.widget.testEmpty) {
        var pn = $(this.widget.domId).parentNode;
        Effect.Fade(pn, {
          duration: .5
        });
      }
      else{
        if(!payload.guides.r && this.widget.defaultDiv){
          $(this.widget.defaultDiv).toggle();
          var pn = $(this.widget.domId).parentNode;
          pn.toggle();
        };
        this.widget.render({
          myJSON: payload
        });
      }
    }
  };
  
  
  this.payloadData = {};
  this.makeRequest(this.makeRequestObj);
  
  this.requestNewRecomenders = function(url, title) {
    if (!title) 
      this.title = title;
    this.URLrequested = url;
    for (var i = 0; i < this.elements.length; i++) {
      var elem = this.elements[i];
      this.ListContainer.removeChild(elem);
    }
    this.elements = [];
    this.reset();
    this.makeRequest(this.makeRequestObj);
  };
  
  this.init = function() {
  };//do nothing
};

MoreProductsPayloadWidget = function(parent) {
  WidgetExtender.Setup(this);
  this.initWidget('more_products_wrapper', parent);
  this.detailTab = $('moreProductsDetails');
  this.overlayTab = $('moreProductsOverlay');
  this.isOpen = true;
  this.tabWidth = 20; // this is the width of the exposed 'tab'
  this.containerWidth = 1000; // this is the width of the main content area on page
  this.userClicked = false;

  this.setOffsets = function(){
    var offsets = $('detailMain').viewportOffset();
    var left = offsets[0];
    var overlayLeft = (this.containerWidth + left);
    var detailOffset = this.isOpen ? this.detailTab.getWidth() : this.tabWidth;
    var detailLeft = overlayLeft - detailOffset;
    this.overlayTab.setStyle({ left: overlayLeft+'px' });
    this.detailTab.setStyle({ left: detailLeft+'px' });
  };

  this.preDraw = function(){
    this.setOffsets();
    // if defaults to open, set timeout to auto-close
    if(this.isOpen){
      setTimeout(function(){
        if(!this.userClicked){
          this.toggleDetailTab();
}
      }.bind(this),20000);
    };
  };

  this.toggleDetailTab = function(){
    var move = this.detailTab.getWidth() - this.tabWidth;
    if(!this.isOpen){
      move = move * -1;
    }
    new Effect.Move(this.detailTab, { x: move, y: 0, mode: 'relative' });
    this.isOpen = !this.isOpen;
  };

  this.massagePayload = function(){
    this.payloadData.moreLink = 'foo'; // this is the page we redirect to
    this.payloadData.moreText = this.payloadData.moreProductCount + ' more sizes and colors';
  };

  this.getTemplateVar = function() {
    return 'moreProducts';
  };

  this.detailTabClick = function(){
    this.userClicked = true;
    this.toggleDetailTab();
  };

  this.attachEvent((document.onresize ? document : window), "resize", this.setOffsets, this);
  this.attachEvent(window, "scroll", this.setOffsets, this);
  this.attachEvent('moreProductTab', 'click', this.detailTabClick, this);
};

var Validator = null;
Validator = Class.create();

Validator.prototype = {
	initialize : function(className, error, test, options) {
		if(typeof test == 'function'){
			this.options = $H(options);
			this._test = test;
		} else {
			this.options = $H(test);
			this._test = function(){return true};
		}
		this.error = error || 'Validation failed.';
		this.className = className;
	},
	getError : function()
	{
		if (typeof this.error == 'function')
			return this.error();
		else
			return this.error;
	},
	
	test : function(v, elm) {
		return (this._test(v,elm) && this.options.all(function(p){
			return Validator.methods[p.key] ? Validator.methods[p.key](v,elm,p.value) : true;
		}));
	}
}
Validator.methods = {
	pattern : function(v,elm,opt) {return Validation.get('IsEmpty').test(v) || opt.test(v)},
	minLength : function(v,elm,opt) {return v.length >= opt},
	maxLength : function(v,elm,opt) {return v.length <= opt},
	min : function(v,elm,opt) {return v >= parseFloat(opt)}, 
	max : function(v,elm,opt) {return v <= parseFloat(opt)},
	notOneOf : function(v,elm,opt) {return $A(opt).all(function(value) {
		return v != value;
	})},
	oneOf : function(v,elm,opt) {return $A(opt).any(function(value) {
		return v == value;
	})},
	is : function(v,elm,opt) {return v == opt},
	isNot : function(v,elm,opt) {return v != opt},
	equalToField : function(v,elm,opt) {return v == $F(opt)},
	notEqualToField : function(v,elm,opt) {return v != $F(opt)},
	include : function(v,elm,opt) {return $A(opt).all(function(value) {
		return Validation.get(value).test(v,elm);
	})}
}

var ValidationSingleton = {
	validationIdCount: 0,
	isFocusing: false
};




var Validation = null;
var Validation = Class.create();

Validation.prototype = {
	initialize : function(form, options){
		this.options = Object.extend({
			onSubmit : true,
			stopOnFirst : false,
			immediate : false,
			focusOnError : true,
			useTitles : false,
			onFormValidate : function(result, form) {},
			onElementValidate : function(result, elm) {},
			elements : null
		}, options || {});
		this.form = $(form);
		
		if(this.options.immediate) {
			var useTitles = this.options.useTitles;
			var callback = this.options.onElementValidate;
			
			this.inputs = this.options.elements;
			if (!this.inputs) 
			{
				this.inputs = Form.getElements(this.form);
			}
				
			for (var itr= 0; itr<this.inputs.length; itr++)
			{
				var input = this.inputs[itr];
				Event.observe(input, 'blur', function(ev) { Validation.validate(Event.element(ev),{useTitle : useTitles, onElementValidate : callback}); });
			}
		}
		
		this.id = 'validation_'+ ValidationSingleton.validationIdCount++;
		InstanceContainer.RegisterInstance(this);
	},
	onSubmit :  function(ev){
		if(!this.validate()) Event.stop(ev);
	},
	validate : function() {
		var result = false;
		var useTitles = this.options.useTitles;
		var callback = this.options.onElementValidate;
		
		if (this.options.stopOnFirst) 
		{
			result = Form.getElements(this.form).all(function(elm)
			{
				return Validation.validate(elm, {
					useTitle: useTitles,
					onElementValidate: callback
				});
			});
		}
		else 
		{
			result = Form.getElements(this.form).collect(function(elm)
			{
				return Validation.validate(elm, {
					useTitle: useTitles,
					onElementValidate: callback
				});
			}).all();
		}
		if (!result && this.options.focusOnError) 
		{
			for (var itr = 0; itr < this.inputs.length; itr++) 
			{
				var input = this.inputs[itr];
				if (input.hasClassName('validation-failed'))
				{
					var firstElement = Element.up(input);
					if (!ValidationSingleton.isFocusing) 
					{
						ValidationSingleton.isFocusing = true;
						Effect.ScrollTo(firstElement, {
							duration: 0.5,
							queue: {
								position : 'front',
								scope: 'scrollQueue'
							},
							afterFinish : function()
							{
								firstElement.focus();
							}
						});
						InstanceContainer.FireEventAsync(this.id, 'finishFocus', 1000);
					}
					
					break;
				}
			}
		}
		this.options.onFormValidate(result, this.form);
		return result;
	},
	reset : function() {
		if (!this.inputs)
			return;
		for (var itr= 0; itr<this.inputs.length; itr++)
		{
			var input = this.inputs[itr];
			Validation.reset(input);
		}
	},
	finishFocus : function()
	{
		ValidationSingleton.isFocusing = false;
	},
	destroy : function()
	{
		if (this.inputs && this.inputs.length)
		{
			for (var i=0; i<this.inputs.length; i++)
			{
				Event.stopObserving(this.inputs[i], 'blur');
				this.inputs[i] = null;
			}
		}
		
		this.form = null;
		this.inputs = null;
		this.options.elements = null;
		this.options = null;
	}
}

Object.extend(Validation, {
	validate : function(elm, options){
		options = Object.extend({
			useTitle : false,
			onElementValidate : function(result, elm) {}
		}, options || {});
		elm = $(elm);

		if (elm.className && elm.className.length>0)
		{
			var classes = elm.className.split(' ');
			for (var i=0; i<classes.length; i++)
			{
				var className = classes[i];
				var test = Validation.test(className,elm,options.useTitle);
				options.onElementValidate(test, elm);
				if (!test)
					return false
			}
		}
		
		return true;
	},
	test: function(name, elm, useTitle)
	{
		var v = Validation.get(name);
		var prop = '__advice' + name.camelize();
		label = $('label-' + Validation.getElmID(elm));
		try 
		{
			if (Validation.isVisible(elm) && !v.test($F(elm), elm)) 
			{
				if (!elm[prop]) 
				{
					var advice = Validation.getAdvice(name, elm);
					if (advice == null) 
					{
						advice = '<div class="validation-advice" id="advice-' + name + '-' + Validation.getElmID(elm) + '" style="display:none"></div>'
						switch (elm.type.toLowerCase())
						{
							case 'checkbox':
							case 'radio':
								var p = elm.parentNode;
								if (p)  new Insertion.Bottom(p, advice);
								else  new Insertion.After(elm, advice);
								break;
							default:
								new Insertion.After(elm, advice);
						}
						advice = Validation.getAdvice(name, elm);
					}
					
					advice.innerHTML = useTitle ? ((elm && elm.title) ? elm.title : v.getError()) : v.getError();
					new Effect.Appear(advice, {
						duration: .25
					});
				}
				elm[prop] = true;
				elm.removeClassName('validation-passed');
				elm.addClassName('validation-failed');
				elm.up().removeClassName('validation-passed');
				elm.up().addClassName('validation-failed');
				if (label) 
				{
					label.removeClassName('validation-passed');
					label.addClassName('validation-failed');
				}
				
				return false;
			}
			else 
			{
				var advice = Validation.getAdvice(name, elm);
				if (advice != null) 
					advice.hide();
				elm[prop] = '';
				elm.removeClassName('validation-failed');
				elm.addClassName('validation-passed');
				elm.up().removeClassName('validation-failed');
				elm.up().addClassName('validation-passed');
				if (label) 
				{
					label.removeClassName('validation-failed');
					label.addClassName('validation-passed');
				}
				
				return true;
			}
		} 
		catch (e) 
		{
			throw (e)
		}
	},
	isVisible : function(elm) {
		while(elm.tagName != 'BODY') {
			if(!$(elm).visible()) return false;
			elm = elm.parentNode;
			if (!elm)
				return false;
		}
		return true;
	},
	getAdvice : function(name, elm) {
		return $('advice-' + name + '-' + Validation.getElmID(elm)) || $('advice-' + Validation.getElmID(elm));
	},
	getElmID : function(elm) {
		return elm.id ? elm.id : elm.name;
	},
	reset : function(elm) {
		elm = $(elm);
	
		if (elm.className && elm.className.length > 0) 
		{
			var classes = elm.className.split(' ');
			for (var i = 0; i < classes.length; i++) 
			{
				var value =  classes[i];
			
				var prop = '__advice'+value.camelize();
				if(elm[prop]) {
					var advice = Validation.getAdvice(value, elm);
					if (advice) 
					{
						advice.hide();
						elm[prop] = '';
					}
				}
				elm.removeClassName('validation-failed');
				elm.removeClassName('validation-passed');
				if (elm && elm.up()) 
				{
					elm.up().removeClassName('validation-failed');
					elm.up().removeClassName('validation-passed');
				}
				
			}
		}
		
	},
	add : function(className, error, test, options) {
		var nv = {};
		nv[className] = new Validator(className, error, test, options);
		Object.extend(Validation.methods, nv);
	},
	addAllThese : function(validators) {
		var nv = {};
		
		var rcs_validators = $A(validators);
		for (var itr= 0; itr<rcs_validators.length; itr++)
		{
			var value = rcs_validators[itr];
			nv[value[0]] = new Validator(value[0], value[1], value[2], (value.length > 3 ? value[3] : {}));
		}
		Object.extend(Validation.methods, nv);
	},
	get : function(name) {
		return  Validation.methods[name] ? Validation.methods[name] : Validation.methods['_LikeNoIDIEverSaw_'];
	},
	methods : {
		'_LikeNoIDIEverSaw_' : new Validator('_LikeNoIDIEverSaw_','',{})
	}
});



Validation.add('IsEmpty', '', function(v) {
				return  ((v == null) || (v.length == 0)); // || /^\s+$/.test(v));
			});

Validation.addAllThese([
	['required', 'This is a required field.', function(v) {
				return !Validation.get('IsEmpty').test(v);
			}],
  ['validate-one-email', 'Please enter a valid email address, i.e. name@example.com', function(v) {
    return !Validation.get('IsEmpty').test(v) && /\w{1,}[@][\w\-]{1,}([.]([\w\-]{1,})){1,3}$/.test(v);
  }],
  ['validate-multiple-emails', 'Please enter at least one valid comma-seperated email address, i.e. name2@example.com, name@example.com', function(v) {
    var valid = false;
    v.split(',').each(function(i){ 
      if(/\w{1,}[@][\w\-]{1,}([.]([\w\-]{1,})){1,3}$/.test(i) && !/(\s)/.test(i.strip())){
        valid = true;
      }else{
        return false;
      }
    }); 
    return valid;
  }],
	['validate-number', 'Please enter a valid number in this field.', function(v) {
				return Validation.get('IsEmpty').test(v) || (!isNaN(v) && !/^\s+$/.test(v));
			}],
	['validate-digits', 'Please use numbers only in this field. please avoid spaces or other characters such as dots or commas.', function(v) {
				return Validation.get('IsEmpty').test(v) ||  !/[^\d]/.test(v);
			}],
	['validate-alpha', 'Please use letters only (a-z) in this field.', function (v) {
				return Validation.get('IsEmpty').test(v) ||  /^[a-zA-Z]+$/.test(v)
			}],
	['validate-alphanum', 'Please use only letters (a-z) or numbers (0-9) only in this field. No spaces or other characters are allowed.', function(v) {
				return Validation.get('IsEmpty').test(v) ||  !/\W/.test(v)
			}],
	['validate-date', 'Please enter a valid date.', function(v) {
				var test = new Date(v);
				return Validation.get('IsEmpty').test(v) || !isNaN(test);
			}],
	['validate-url', 'Please enter a valid URL.', function (v) {
				return Validation.get('IsEmpty').test(v) || /^(http|https|ftp):\/\/(([A-Z0-9][A-Z0-9_-]*)(\.[A-Z0-9][A-Z0-9_-]*)+)(:(\d+))?\/?/i.test(v)
			}],
	['validate-date-au', 'Please use this date format: dd/mm/yyyy. For example 17/03/2006 for the 17th of March, 2006.', function(v) {
				if(Validation.get('IsEmpty').test(v)) return true;
				var regex = /^(\d{2})\/(\d{2})\/(\d{4})$/;
				if(!regex.test(v)) return false;
				var d = new Date(v.replace(regex, '$2/$1/$3'));
				return ( parseInt(RegExp.$2, 10) == (1+d.getMonth()) ) && 
							(parseInt(RegExp.$1, 10) == d.getDate()) && 
							(parseInt(RegExp.$3, 10) == d.getFullYear() );
			}],
	['validate-currency-dollar', 'Please enter a valid $ amount. For example $100.00 .', function(v) {
				// [$]1[##][,###]+[.##]
				// [$]1###+[.##]
				// [$]0.##
				// [$].##
				return Validation.get('IsEmpty').test(v) ||  /^\$?\-?([1-9]{1}[0-9]{0,2}(\,[0-9]{3})*(\.[0-9]{0,2})?|[1-9]{1}\d*(\.[0-9]{0,2})?|0(\.[0-9]{0,2})?|(\.[0-9]{1,2})?)$/.test(v)
			}],
	['validate-selection', 'Please make a selection', function(v,elm){
				return elm.options ? elm.selectedIndex > 0 : !Validation.get('IsEmpty').test(v);
			}],
	['validate-one-required', 'Please select one of the above options.', function (v,elm) {
				var p = elm.parentNode;
				var options = p.getElementsByTagName('INPUT');
				return $A(options).any(function(elm) {
					return $F(elm);
				});
			}]
]);

Validation.add('IsEmpty', '', function(v) {
				return  ((v == null) || (v.length == 0)); // || /^\s+$/.test(v));
			});

Validation.addAllThese([
	['validate-number', 'Please enter a valid number in this field.', function(v) {
				return Validation.get('IsEmpty').test(v) || (!isNaN(v) && !/^\s+$/.test(v));
			}],
	['validate-digits', 'Please use numbers only in this field. please avoid spaces or other characters such as dots or commas.', function(v) {
				return Validation.get('IsEmpty').test(v) ||  !/[^\d]/.test(v);
			}],
	['validate-alpha', 'Please use letters only (a-z) in this field.', function (v) {
				return Validation.get('IsEmpty').test(v) ||  /^[a-zA-Z]+$/.test(v)
			}],
	['validate-alphanum', 'Please use only letters (a-z) or numbers (0-9) only in this field. No spaces or other characters are allowed.', function(v) {
				return Validation.get('IsEmpty').test(v) ||  !/\W/.test(v)
			}],
	['validate-date', 'Please enter a valid date.', function(v) {
				var test = new Date(v);
				return Validation.get('IsEmpty').test(v) || !isNaN(test);
			}],
	['validate-url', 'Please enter a valid URL.', function (v) {
				return Validation.get('IsEmpty').test(v) || /^(http|https|ftp):\/\/(([A-Z0-9][A-Z0-9_-]*)(\.[A-Z0-9][A-Z0-9_-]*)+)(:(\d+))?\/?/i.test(v)
			}],
	['validate-date-au', 'Please use this date format: dd/mm/yyyy. For example 17/03/2006 for the 17th of March, 2006.', function(v) {
				if(Validation.get('IsEmpty').test(v)) return true;
				var regex = /^(\d{2})\/(\d{2})\/(\d{4})$/;
				if(!regex.test(v)) return false;
				var d = new Date(v.replace(regex, '$2/$1/$3'));
				return ( parseInt(RegExp.$2, 10) == (1+d.getMonth()) ) && 
							(parseInt(RegExp.$1, 10) == d.getDate()) && 
							(parseInt(RegExp.$3, 10) == d.getFullYear() );
			}],
	['validate-currency-dollar', 'Please enter a valid $ amount. For example $100.00 .', function(v) {
				return Validation.get('IsEmpty').test(v) ||  /^\$?\-?([1-9]{1}[0-9]{0,2}(\,[0-9]{3})*(\.[0-9]{0,2})?|[1-9]{1}\d*(\.[0-9]{0,2})?|0(\.[0-9]{0,2})?|(\.[0-9]{1,2})?)$/.test(v)
			}],
	['validate-selection', 'Please make a selection', function(v,elm){
				return elm.options ? elm.selectedIndex > 0 : !Validation.get('IsEmpty').test(v);
			}],
	['validate-one-required', 'Please select one of the above options.', function (v,elm) {
				var p = elm.parentNode;
				var options = p.getElementsByTagName('INPUT');
				return $A(options).any(function(elm) {
					return $F(elm);
				});
			}]
]);



function luhn_valid(number) {
	
	var number=number.replace(/\D/g, '');

	var number_length=number.length;
	var parity=number_length % 2;

	var total=0;
	for (i=0; i < number_length; i++) {
		var digit=number.charAt(i);
		if (i % 2 == parity) {
			digit=digit * 2;
			if (digit > 9) {
				digit=digit - 9;
			}
		}
		total = total + parseInt(digit);
	}

	if (total % 10 == 0 
		|| number == '1111000011110000'
		|| number == '4111111111111111'
		|| number == '5500000000000004'
		|| number == '5424000000000015'
		|| number == '378282246310005'
		|| number == '340000000000009'
		|| number == '370000000000002'
		|| number == '6011111111111117'
		|| number == '6011000000000004'
		|| number == '6011000000000012'
		|| number == '5555555555554444'
		|| number == '5555555555555555') {
		return true;
	} else {
		return false;
	}
}







var WebModal =
{
	modalId : 0,
	
	Modals : {},
	lastModal : null,

	ShowNow : function(modalElement, options)
	{
		this.Show(modalElement, options);
		return false;
	},
	
	Show : function(modalElement, options)
	{
		var modal = new WebModalObj();
		modal.Show(modalElement, options);
		
		this.Modals[modal.id] = modal;
		this.lastModal = modal;
		return modal;
	},
	
	Hide : function(id)
	{
		var modal = (id) ? this.Modals[id] : this.lastModal;
		if (modal)
			modal.Hide();
	},

	Close : function(id)
	{
		var modal = (id) ? this.Modals[id] : this.lastModal;
		if (modal)
		{
			modal.options.effects = null;
			modal.Hide();
		}
	},
	
	
	RegisterPostHide : function(func)
	{
		var modal = this.lastModal.RegisterPostHide(func);
	},
	
	RemoveModal : function(modal)
	{
		delete this.Modals[modal.id];
	}
};

var WebModalObj = Class.create({
	bgMaskUrl : '/new_includes/images/maskBG.png',
	PositionElements : function()
	{
		var theBody = this.theBody;
		var screenWidth = 0;
		if (window.innerWidth!=window.undefined) screenWidth = window.innerWidth; 
		else if (document.compatMode=='CSS1Compat') screenWidth = document.documentElement.clientWidth; 
		else if (document.body) screenWidth = document.body.clientWidth; 
		
		var screenHeight = 0;
		if (window.innerHeight!=window.undefined) screenHeight = window.innerHeight;
		else if (document.compatMode=='CSS1Compat') screenHeight = document.documentElement.clientHeight;
		else if (document.body) screenHeight = document.body.clientHeight; 
		var windowHeight = screenHeight;
		
		if (screenHeight < theBody.scrollHeight) 
		{
			screenHeight = theBody.scrollHeight;
		}
	
		if (screenWidth > theBody.scrollWidth) {
			screenWidth = theBody.scrollWidth;
		}
		
		var scrollTop = 0;
		if (self.pageYOffset){scrollTop = self.pageYOffset;}
		else if (document.documentElement && document.documentElement.scrollTop){scrollTop = document.documentElement.scrollTop;}
		else if (document.body) {scrollTop = document.body.scrollTop;}

		if (this.options.reposition || !this.placedOnce) 
		{
			this.container.style.top = (scrollTop + Math.round(windowHeight * (this.options.topPercentage / 100))) + "px";
		}


		if (this.options.maskBackground) 
		{
			this.bgMask.style.width = screenWidth + 'px';
			var bgMaskTop = scrollTop - 200;
			if (scrollTop < 0) 
				scrollTop = 0;
			var bgMaskLeft = 0;
			if (!(document.all && !/opera/i.test(navigator.userAgent)) && this.theBody.style.marginLeft) 
				bgMaskLeft = -1 * Number(this.theBody.style.marginLeft.split('px')[0]);
			this.bgMask.style.left = bgMaskLeft + 'px';
			this.bgMask.style.top = scrollTop + 'px';
			this.bgMask.style.top = '0px';
			this.bgMask.style.height = screenHeight + 'px';
		}
		
		this.container.style.marginLeft = '-'+(Element.getWidth(this.container)/2)+'px';
		this.placedOnce = true;

	},
	
	GrowWidth : function(width)
	{
		this.options.width = width;
		this.container.style.width = this.options.width+'px';
		this.PositionElements();
	},
	
	Show : function(modalElement, options)
		{
        	AltrecGlobals.Variables.SupressNav = true;
			this.id = "WebModal"+WebModal.modalId++;
			var defaults = {
				width : 400,
				height : null,
				topPercentage : 10,
				maxOpacity : .4,
				effects : true,
				reposition : true,
				callback : null,
				nocontent : false,
				animation : .4,
				maskBackground : true,
				hideBleeders : true,
				backgroundColor : '#000000'
			};
	
			this.options = Object.extend(defaults, options || {});
			if (!this.initialized)
			{
				this.initialized = true;
				this.theBody = document.body;
		
				this.eventWindowResize = this.PositionElements.bindAsEventListener(this);
				this.eventKeyPress = this.KeyPress.bindAsEventListener(this);
				this.eventKeyPressBody = this.KeyPress.bindAsEventListener(this);
				this.bgMask = document.createElement('div');

				this.container = document.createElement('div');
				this.theBody.appendChild(this.container);
			}
			
			if (this.options.maskBackground) 
			{
				this.bgMask = document.createElement('div');
				this.bgMask.innerHTML = '&nbsp;';
				this.bgMask.style.backgroundColor = this.options.backgroundColor;
				
				this.theBody.appendChild(this.bgMask);
				this.bgMask.style.zIndex = 11000;
				this.bgMask.style.position = 'absolute';
				this.bgMask.style.top = '0px';
				this.bgMask.style.left = '0px';
			}
			
			this.bgMask.style.display = 'none';
			
			this.container.style.position = 'absolute';
			this.container.style.zIndex = 11005;
			this.container.style.left = '50%';
			if (this.options.width)
				this.container.style.width = this.options.width+'px';
			if (this.options.height)
				this.container.style.height = this.options.height+'px';
			this.container.style.display = 'none';
			
			
			if (this.options.hideBleeders) 
			{
				//otherwise these will bleed
				for (var i = 0; i < document.forms.length; i++) 
				{
					for (var e = 0; e < document.forms[i].length; e++) 
					{
						if (document.forms[i].elements[e].tagName == "SELECT") 
						{
							document.forms[i].elements[e].style.visibility = "hidden";
						}
					}
				}
			}
			
			Event.observe(window, "resize", this.eventWindowResize);
			if (!this.options.noscroll)
				Event.observe(window, "scroll", this.eventWindowResize);
			Event.observe(window, "keypress", this.eventKeyPress);
			Event.observe(this.theBody, "keyup", this.eventKeyPressBody);
			
			if (this.options.maskBackground) 
			{
				Event.observe(this.bgMask, "click", this.Hide.bind(this));
				Element.setOpacity(this.bgMask, 0);
			}
			
			Element.setOpacity(this.container, 0);
			
			if (this.options.nocontent)
			{
				this.container.style.visibility = 'hidden';
				modalElement = document.createElement('span');
			}
				
			if (this.options.effects)
			{
				if (this.options.maskBackground) 
					Element.setOpacity(this.bgMask, this.options.maxOpacity);
					//new Effect.Opacity(this.bgMask, { from: 0, to: this.options.maxOpacity, duration: this.options.animation });
				new Effect.Opacity(this.container, { from: 0, to: 1, duration: this.options.animation});
				
			}
			else
			{
				Element.setOpacity(this.container, 1);
				if (this.options.maskBackground) 
					Element.setOpacity(this.bgMask, 1);
			}
			
			if (this.options.maskBackground) 		
				this.bgMask.style.display = 'block';
			this.container.style.display = 'block';
			
			this.modalDiv = $(modalElement);
			this.modalDiv.style.display = 'block';

      if(this.options.url){
        var modalContainer = this.modalDiv;
        modalContainer.innerHTML = '';
        this.loadingDiv = document.createElement('img');
        this.loadingDiv.src = 'http://static.altrec.com/images/bigrotation2.gif';
        modalContainer.appendChild(this.loadingDiv);
        var modalContainer = this.modalDiv;
        var request = new Ajax.Request(this.options.url, {
          method: 'get',
          onSuccess: function(e){
            modalContainer.innerHTML = e.responseText;
          },
          onFailure: function(e){}
        });
      };
			
			this.parent = Element.up(this.modalDiv);
			this.container.appendChild(this.modalDiv);
			this.PositionElements();
			if (this.options.callback)
				this.options.callback();
			return this.container;
		},
	KeyPress : function(e)
	{
      	if(e.keyCode == Event.KEY_ESC)
         	this.Hide();
	},
	Hide : function()
		{
			if (!this.modalDiv)
				return;
				
			if (this.options.hideBleeders) 
			{
				for (var i = 0; i < document.forms.length; i++) 
				{
					for (var e = 0; e < document.forms[i].length; e++) 
					{
						if (document.forms[i].elements[e].tagName == "SELECT") 
						{
							document.forms[i].elements[e].style.visibility = "visible";
						}
					}
				}
			}
			
	        AltrecGlobals.Variables.SupressNav = false;
			this.placedOnce = false;
			Event.stopObserving(window, 'resize', this.eventWindowResize);
			if (!this.options.noscroll)
				Event.stopObserving(window, "scroll", this.eventWindowResize);
			Event.stopObserving(window, "keypress", this.eventKeyPress);
			Event.stopObserving(this.theBody, "keyup");
			Event.stopObserving(this.bgMask, "click");
			
			if (this.options.effects) 
			{
				new Effect.Opacity(this.container, {
					from: this.options.maxOpacity,
					to: 0,
					queue : "parallel",
					duration: this.options.animation / 1.5,
					afterFinish: function()
					{
						this.RemoveFromDom();
					}.bind(this)
				});
				if (this.options.maskBackground) 
				{
					new Effect.Opacity(this.bgMask, {
						from: this.options.maxOpacity,
						to: 0,
						queue : "parallel",
						duration:this.options.animation / 2
					});
				}
			}
			else 
			{
				this.RemoveFromDom();
			}
			
			if (this.onPostHides) 
			{
				for (var i=0; i<this.onPostHides.length; i++)
				{
					this.onPostHides[i](this);
					this.onPostHides[i] = null;
				}
				this.onPostHides = null;
			}
		},
	RegisterPostHide : function(func)
	{
		if (!this.onPostHides)
			this.onPostHides=[];
		this.onPostHides.push(func);
	},
	RemoveFromDom : function()
		{	
			if (this.options.maskBackground) 
				this.bgMask.style.display = 'none';
			if (this.modalDiv) 
			{
				this.modalDiv.style.display = 'none';
				this.container.style.display = 'none';
				if (this.parent) 
					this.parent.appendChild(this.modalDiv);
				else 
					this.theBody.appendChild(this.modalDiv);
				this.modalDiv = null;
			}
			
			WebModal.RemoveModal(this);
		}
});




function getItemTotal()
{
	var itemTotal = 0;
	try {
		var tempTotalStr = unescape(getCookie('carttotal'));
		if(tempTotalStr.indexOf('"')==0)
			tempTotalStr = tempTotalStr.split("\"")[1];
		if (tempTotalStr.indexOf('$')>=0)
			tempTotalStr = tempTotalStr.replace("$", "");
		if (tempTotalStr.indexOf(',')>0)
			tempTotalStr = tempTotalStr.replace(",", "");
	
		itemTotal = Number(tempTotalStr);
		if (isNaN(itemTotal)) {
			itemTotal = 0;}}
	catch(e)
	{
		itemTotal=0;
	}
	
	return itemTotal;
}

function getItemCount()
{
	var itemCount = 0;
	try
	{
		itemCount = Number(getCookie('cartcount'));
		if (isNaN(itemCount)) {
			itemCount = 0;}
	}catch(e){itemCount=0;}
	
	return itemCount;
}

	
	
function CartSummary(itemCount, itemTotal)
{
	this.itemCount = itemCount;
	this.itemTotal = itemTotal;
	this.cartLink = (AltrecGlobals.Links.getCartUrl) ? AltrecGlobals.Links.getCartUrl() : '/servlets/viewcart';
	
	this.getItemCount = function()
	{
		var itemsStr = this.itemCount + ' item';
		if (this.itemCount != 1)
			itemsStr += 's';
		return itemsStr;
	};
	
	this.getItemTotal = function()
	{
		return FormatCurrency(this.itemTotal, true);
	};
	
	this.qualifiesFreeShipping = function()
	{
		return (this.itemTotal >= AltrecGlobals.Variables.freeShippingTreashold);
	};
}

function CartItem()
{
	var self = this;
	this.name;
	this.href;
	this.store;
	this.uniqueId;
	this.topMessage;
	this.description1;
	this.description2;
	this.quantity;
	this.image;
	this.price;
	this.brandName;
	this.pid;
	this.productName;
	
	this.getFormattedPrice = function()
	{
		return FormatCurrency(this.price, true);
	};
}
CartItem.prototype.equals = function(objToCompare) {
	return (this.store == objToCompare.store && this.uniqueId == objToCompare.uniqueId);
};

function AltrecItem() {
	
	this.store = 'Altrec';
	var skuData;
	CartItem.call(this);
	
	this.setData = function(pSkudData)
	{
		skuData = pSkudData;
		this.pid = skuData.pid;
		this.productName = skuData.productName;
		this.brandName = skuData.brandName;
		this.name = skuData.productName;
		this.href = skuData.href;
		this.uniqueId = skuData.sid;
		this.topMessage = "Just Added:";
		this.description1 = skuData.color;
		if (skuData.color=='') {
			this.description1 = '';}
		this.description2 = skuData.size;
		if (skuData.size=='') {
			this.description2 = '';}
		
		var swatchImg = skuData.sku_swatch_image;
		var domainPrefixVar = 'http://mirror.altrec.com';
		try
		{
			domainPrefixVar = domainPrefix;
		}
		catch(ex){}
		if (!swatchImg || swatchImg=='')
		{
			if (skuData.sku_swatch_image_d && skuData.sku_swatch_image_d !='')
				swatchImg = domainPrefixVar + skuData.sku_swatch_image_d;
			else
				swatchImg = 'http://mirror.altrec.com/images/shop/photos/nophoto_x.gif';
		}
		else
		{
			swatchImg = domainPrefixVar + swatchImg;
		}
		
		this.image = swatchImg;
		this.price = Number(skuData.sku_price);
	};
}

AltrecItem.prototype = new CartItem();

function CartMediator()
{
	var self = this;
	this.cartSummary;
	this.cartItems = new Array();
	this.cartEpanded = false;
	this.altrecCookieManager = AltrecCookieManager;
	this.canAdd = true;

	this.init = function()
	{
		
		this.cartSummary = new CartSummary(getItemCount(), getItemTotal());
		this._redrawCartSummary();

		this.collapseCartEvent = this.collapseCart.bindAsEventListener(this);
		
		this.collapseObservers = new Array();
		this.AddCollapseObserver(window, 'click');
		this.AddCollapseObserver(document.body, 'click');
	};
	
	function drawSecondaryDropdowns()
	{
		if (self.dropDownToRender < self.secondaryDropdowns.length)
			InstanceContainer.FireEventAsync(self.id, '_renderSecondaryDropdown', 500, self.dropDownToRender);
	}
	
	this.drawSecondaryDropdownsComplete = function()
	{
		this.dropDownToRender++;
		drawSecondaryDropdowns()
	};
	
	this.expandCartComplete = function ()
	{
		this.cartEpanded = true;
		this.collapseObservers.each(function(observer){
			Event.observe(observer.element, observer.event, this.collapseCartEvent);
		}.bind(this));
				
		this.dropDownToRender = 0;
		this.canAdd = true;
		drawSecondaryDropdowns();
	};
	
	this.AddCollapseObserver = function(element, event)
	{
		this.collapseObservers.push({element : element, event : event});
	};

	this.collapseCart = function(event, args)
	{
		if (!this.cartEpanded) {
			return;}
			
		this.cartEpanded = false;
		this.collapsingCart = true;
		this.collapseObservers.each(function(observer){
			Event.stopObserving(observer.element, observer.event, this.collapseCartEvent);
		}.bind(this));
		
		this.dropDownToCollapse = this.secondaryDropdowns.length-1;
		drawCollapsedSecondaryDropdowns();
	};
	
	function drawCollapsedSecondaryDropdowns()
	{
		if (self.dropDownToCollapse >= 0) {
			InstanceContainer.FireEventAsync(self.id, '_drawCollapsedSecondaryDropdown', 0, self.dropDownToCollapse);}
		else {
			mediateCollapseCart();}
	}
	
	this._renderCollapsedSecondaryComplete = function()
	{
		this.dropDownToCollapse--;
		drawCollapsedSecondaryDropdowns();
	};
	
	function mediateCollapseCart()
	{
		self.secondaryDropdowns.each(function(dropdown){
			dropdown.elem.style.display = 'none';
		}.bind(self));
		self._drawCollapsedCart();
		self._redrawCartSummary();
	}
	
	this.collapseCartComplete = function()
	{
		this.collapsingCart = false;
		
		if (this.triggerRedraw)
		{
			this._redrawCartSummary();
			this._drawExpandedCart();
			this.triggerRedraw = false;
		}
	};
	
	this.addToCart = function(event, cartItem, qty)
	{
		if (this.cartEpanded || !this.canAdd)
			return;
		
		if (qty == undefined)
			qty = 1;
		
		this._removeAllItems();
		this.cartItems = [];
		this.cartItems.push(cartItem);
		if (this.cartSummary.itemCount == 0) this._disableCheckoutBtns();
		
		try
		{
			CreateCartTag(cartItem.pid + '', cartItem.brandName + ": " + cartItem.productName, "1", cartItem.price, this.getCmCategory());
			DisplayShop5s();
      PayloadMediator.renderPayload('TrackingPayload',{GoogleRemarketing:[{'label':'RRkCCMnQgAIQibL37gM'}]});
      // TellApart
      if (__cmbLoaded) {
        var action = TellApartCrumb.makeCrumbAction("aTgzQHbtGBs2","updatecart"); 
        action.setActionAttr("UpdateCartType", "PartialAdd");
        action.beginItem();
        action.setItemAttr("SKU", cartItem.pid); 
        action.setItemAttr("ProductPrice", '' + cartItem.price); 
        action.setItemAttr("ProductCurrency", "USD"); 
        action.setItemAttr("ItemCount", '' + qty);
        action.endItem();
        action.finalize();
      };
            
		}catch(e){}
		
		var parameters = {ajax : 'true' , SKU : cartItem.uniqueId, action : 'Add', quantity : 1};
		if (this.getExtraParameters)
			parameters = Object.extend(parameters, this.getExtraParameters() || { });
			
		var url = '/servlets/addToCart?javaMethod=addItem&paramArray='+cartItem.uniqueId
			+'&paramArray='+qty;
			
		cartItem.qtyAdded = qty;
		this.cartSummary.itemCount = this.cartSummary.itemCount + qty;
		this.cartSummary.itemTotal += cartItem.price;
		this.canAdd = false;
		
		if (AltrecGlobals.Site.isHomepage && AltrecGlobals.Site.isOutlet && AltrecGlobals.Site.MultiVarInfo.nav_layout)
			cmCreatePageElementTag('homepage_add_'+AltrecGlobals.Site.MultiVarInfo.nav_layout.val, "abtests");

		var request = new Ajax.Request(url, {
			method : 'get',
			onSuccess: function(transport){
		      var response =  transport.responseText;
			  this.altrecCookieManager.UpdateLastAccess();
			  this._enableCheckoutBtns();
			  if (response.indexOf('DOCTYPE')>0)
			  {
			  	//do nothing
			  }
			  else
			  {
			  		var JSONResponse = eval('('+response+')');
		      		addToCartComplete(JSONResponse);
			  }
		    }.bind(this)
		});
		
		this._renderItem(cartItem);
		
		if (this.cartSummary.itemCount > 0) 
		{
			if (!this.collapsingCart)
			{
				this._redrawCartSummary();
				this._drawExpandedCart();
			}
			else
				this.triggerRedraw = true;
		}
	};
	
	this.getCartInfo = function(cartObj)
	{
		var count = 0;
		if (cartObj.cart_info) 
		{
			cartObj.cart_info.each(function(shipment)
			{
				shipment.package_items.each(function(item)
				{
					count += parseInt(item.quantity);
				});
			});
		}
		var total = (cartObj.invoice_sub_total) ? cartObj.invoice_sub_total.replace(',', '') : 0;
		
		return {
			count: count,
			total: Number(total)
		};
	}
	
	function addToCartComplete(JSONResponse)
	{
		
		var cartInfo = null;
		var errorMessage = null;
		
		try 
		{
			cartInfo = self.getCartInfo(JSONResponse.MessagePayloads.CartPayload);
		}
		catch(e)
		{
		}
		
		
		if (cartInfo) 
		{
			self.cartSummary.itemCount = cartInfo.count;
			self.cartSummary.itemTotal = cartInfo.total;
		}
		else 
		{
			self.cartSummary = new CartSummary(getItemCount(), getItemTotal());
		}
		
		self._updateCartSummary();
		PayloadMediator.renderPayloads(JSONResponse);
		
	}
	
	this.HideCheckoutBtns = function()
	{
		this._renderHiddenCheckoutBtns();
	};
}

function AltrecCart(parameters)
{
	CartMediator.call(this);
	
	var self = this;
	this.id = parameters.cartElement;
	this.cartElement = $(parameters.cartElement);
	var innerCartContainer = $(parameters.innerCartContainer);
	var itemContainer = $(parameters.itemContainer);
	var itemTemplate = $(parameters.itemTemplate);
	this.editCartBtns = parameters.cartEditBtns;
	var itemTemplateHeight = parameters.itemTemplateHeight;
	var cartSummaryElements = parameters.cartSummary;
	var cartStatusPane = $(parameters.cartStatusPane);
	var emptyCartPane = $(parameters.cartEmptyCart);
	this.secondaryDropdowns = parameters.secondaryDropdowns || [];
	this.secondaryDropdowns.each(function(dropdown){
		dropdown.elem = $(dropdown.id);
		dropdown.originalHeight = Element.getHeight(dropdown.elem);
	}.bind(this));
	var elementStack = new Array();
	var animationDuration = .8;

	
	
	this._drawExpandedCart = function()
	{
		itemContainer.style.height = (itemTemplateHeight * elementStack.length) +'px';
		Effect.SlideDown(innerCartContainer, {duration : animationDuration ,  queue: 'end' , afterFinish : this.expandCartComplete.bind(this)});
	};
	
	this._renderSecondaryDropdown = function(dropDownIdx)
	{
		if (!this.cartEpanded || this.collapsingCart) return;
		var elem = this.secondaryDropdowns[dropDownIdx].elem;
		new Effect.SlideDown(elem, {duration : animationDuration/2,  queue: 'end', afterFinish : this.drawSecondaryDropdownsComplete.bind(this)});
	};
		
	this._drawCollapsedCart = function()
	{
		Element.setOpacity(elementStack[elementStack.length-1], 1);
		Effect.SlideUp(innerCartContainer, {duration : animationDuration,  queue: 'end', afterFinish : this.collapseCartComplete.bind(this)});
	};
	
	this._drawCollapsedSecondaryDropdown = function(dropDownIdx)
	{
		var elem = this.secondaryDropdowns[dropDownIdx].elem;
		Effect.SlideUp(elem, {duration : animationDuration/2, queue: 'start', afterFinish : this._renderCollapsedSecondaryComplete.bind(this)});
	};
	
	this._redrawCartSummary = function()
	{
		if (this.cartSummary.itemCount == 0) 
		{
			cartStatusPane.style.display = 'none';
			emptyCartPane.style.display = 'block';
			this._updateCartSummary();
			return;
		}
		
		cartStatusPane.style.display = 'block';
		emptyCartPane.style.display = 'none';
		
		this._updateCartSummary();
	};
	
	this._updateCartSummary = function()
	{	
		this.cartSummary.shippingInfo = '';
		this.cartSummary.shippingLblDisplay = (this.cartSummary.qualifiesFreeShipping()) ? 'block' :'none';
		
		bindJSON(cartSummaryElements, 'cartSummaryVar', this.cartSummary);
	};
	
	this._removeAllItems = function()
	{
		elementStack.each(function(element){
				itemContainer.removeChild(element);
			});
			
		elementStack = new Array();
	};
	
	this._renderItem = function(cartItem)
	{	
		scroll(0,0);
		var newItem = itemTemplate.cloneNode(true);
		bindJSON(newItem, 'cartItemVar', cartItem);
		newItem.style.display='block';
		newItem.id = newItem.id + elementStack.length;
		
		itemContainer.insertBefore(newItem, itemContainer.firstChild);
		itemContainer.insertBefore(document.createElement('span'), newItem);
		if (elementStack.length>0)
		{
			elementStack[elementStack.length-1].style.borderTopStyle = 'solid';
			elementStack[elementStack.length-1].style.borderTopWidth = '1px';
		}
		elementStack.push(newItem);
	};
	
	this.getCmCategory = function()
	{
		return this.getExtraParameters().cm_catid;
	};
	
	this.getExtraParameters = function()
	{
		try
		{
			var form = document.getElementsByName('orderbox')[0];
			return {shop_id : document.getElementsByName('shop_id')[0].value ,
				cm_catid : document.getElementsByName('cm_catid')[0].value
			};
		}
		catch(e){return {cm_catid : ''};}
	};
	
	this._disableCheckoutBtns = function()
	{
		this.editCartBtns.each(function(btn){
			btn.onclick = function(){return false;};
			Element.setOpacity(btn, .6);
		});
	};
	
	this._enableCheckoutBtns = function()
	{
		this.editCartBtns.each(function(btn){
			btn.onclick = function(){return true;};
			Element.setOpacity(btn, 1);
		});
	};
	
	this._renderHiddenCheckoutBtns = function()
	{
		this.editCartBtns.each(function(btn){
			btn.style.visibility = 'hidden';
		});
	}
	
	this.init();
}

AltrecCart.prototype = new CartMediator();
var cart = {};

function initCart()
{
	
	cart = new AltrecCart({
		cartElement: 'CartContainer',
		innerCartContainer: 'innerCartContainer',
		itemContainer: 'cartItemContainer',
		itemTemplate: 'cartItemTemplate',
		cartEditBtns : [$('cartEditBtn'), $('cartCheckoutBtn')],
		cartSummary: [$('cartStatusPane'), $('static_cart_sum')],
		cartStatusPane: 'cartStatusPane',
		cartEmptyCart: 'cartEmptyCart',
		itemTemplateHeight: 130,
		secondaryDropdowns: [/*{id: "secondaryDropdown"}*/]
	});
	InstanceContainer.RegisterInstance(cart);
	
	try 
	{
		if (typeof document.body.style.maxHeight == "undefined") 
		{
			$('cartBodyContainer').style.marginRight = "1px";
		}
		if (Prototype.Browser.IE) document.execCommand("BackgroundImageCache", false, true);
	} 
	catch (err) 
	{
	}
	
}

function CreateTechPropsTag(pageid,catid){
       if(AltrecGlobals.Coremetrics.cm_onflag){
           cmCreateTechPropsTag(pageid,catid.toUpperCase());
       }
}
function CreatePageviewTag(pageid,catid,search,cnt,attributes){
       if(AltrecGlobals.Coremetrics.cm_onflag){
           cmCreatePageviewTag(pageid,catid.toUpperCase(),search,cnt,attributes);
       }
}
function CreateProductviewTag(pid,product,catid,attributes){
       if(AltrecGlobals.Coremetrics.cm_onflag){
           cmCreateProductviewTag(pid,product,catid.toUpperCase(),attributes);
       }
}
function CreateCartTag(pid,product,qty,val,catid){
       if(AltrecGlobals.Coremetrics.cm_onflag){
           cmCreateShopAction5Tag(pid,product,qty,val,catid.toUpperCase());
       }
}
function DisplayShop5s(){
       if(AltrecGlobals.Coremetrics.cm_onflag){
           cmDisplayShop5s();
       }
}
function DisplayShop9s(){
       if(AltrecGlobals.Coremetrics.cm_onflag){
           cmDisplayShop9s();
       }
}
function CreateInvoiceDetailTag(pid,product,qty,price,cust,inv,subt,catid){
       if(AltrecGlobals.Coremetrics.cm_onflag){
           cmCreateShopAction9Tag(pid,product,qty,price,cust,inv,subt,catid.toUpperCase());
       }
}
function CreateOrderTag(inv,subt,ship,cust,city,state,zip){
       if(AltrecGlobals.Coremetrics.cm_onflag){
           cmCreateOrderTag(inv,subt,ship,cust,city,state,zip);
       }
}
function CreateRegistrationTag(cust,email,city,state,zip,newsltr,subs){
       if(AltrecGlobals.Coremetrics.cm_onflag){
           cmCreateRegistrationTag(cust,email,city,state,zip,newsltr,subs);
       }
}
function CreateErrorTag(pageid,catid){
       if(AltrecGlobals.Coremetrics.cm_onflag){
           cmCreateErrorTag(pageid,catid.toUpperCase());
       }
}
try{var CRITEO=function(){var i,f;var j=(document.location.protocol=="https:"&&"https:"||"http:")+"//dis.us.criteo.com/dis/dis.aspx";var d={home:{eventType:"sendEvent",params:"v=2&pt1=0&pt2=1"},product:{eventType:"sendEvent",params:"v=2&pt1=2"},list:{eventType:"sendEvent",params:"v=2&pt1=3"},basket:{eventType:"transaction",params:"v=2&s=0"},confirmation:{eventType:"transaction",params:"v=2&s=1"},search:{eventType:"sendEvent",params:"v=2&pt1=4"}};function a(){return i[3]}function c(){return i[0]}function l(m){switch(m){case"sendEvent":return i[1];case"transaction":return i[2];default:break}}function k(z,w){var u=d[z.pageType];if(!u){return}var t=a();var s="";var o="";for(name in t){if(!z.hasOwnProperty(name)){continue}var r=t[name][0];var n=t[name][1];var y=t[name][2];var q=z[name];if(!q){continue}switch(n){case 0:o+="&"+r+"="+q;break;case 1:var m;if(typeof q==="string"){m=q.split(z.delimiter)}else{if(Object.prototype.toString.apply(q)==="[object Array]"){m=q}}var x=m.length;if(typeof y!="undefined"&&x>y){x=y}for(var v=1;v<=x;v++){o+="&"+r+v+"="+m[v-1]}break;case 2:s+="&"+r+"="+encodeURIComponent(q);break;default:break}}return"t"+w+"="+u.eventType+"&p"+w+"="+encodeURIComponent(u.params+"&wi="+l(u.eventType)+o)+s}function h(){var p=j+"?p="+c();var n=f&&f.length||0;for(var m=1;m<=n;m++){p+="&"+k(f[m-1],m)}p+="&cb="+Math.floor(Math.random()*99999999999);try{p+="&ref="+encodeURIComponent(document.referrer)}catch(o){}try{p+="&sc_r="+encodeURIComponent(screen.width+"x"+screen.height)}catch(o){}try{p+="&sc_d="+encodeURIComponent(screen.colorDepth)}catch(o){}return p.substring(0,2000)}function g(m){var n=window.onload;window.onload=function(){if(n&&typeof(n.fired)=="undefined"){n.fired=true;n()}m()}}function b(n){if(document.createElement){var o=document.createElement("iframe");if(o){o.width="1px";o.height="1px";o.style.display="none";o.src=n;var m=document.getElementById("cto_mg_div");if(m!=null&&m.appendChild){m.appendChild(o)}}}}function e(){document.write("<div id='cto_mg_div' style='display:none;'></div>")}return{Load:function(o,n){i=n;f=o;e();var m=h();g(function(){b(m)})}}}()}catch(err){};

