
/* /frameworks/prototype-1.6.0.3.js */

 /*  Prototype JavaScript framework, version 1.6.0.3
 *  (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.3',

  Browser: {
    IE:     !!(window.attachEvent &&
      navigator.userAgent.indexOf('Opera') === -1),
    Opera:  navigator.userAgent.indexOf('Opera') > -1,
    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,
    SelectorsAPI: !!document.querySelector,
    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 = (function(m) {
          return function() { return ancestor[m].apply(this, arguments) };
        })(property).wrap(method);

        value.valueOf = method.valueOf.bind(method);
        value.toString = method.toString.bind(method);
      }
      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]
      .replace(/\s+/g, '').split(',');
    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);
  },

  defer: function() {
    var args = [0.01].concat($A(arguments));
    return this.delay.apply(this, args);
  },

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

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.stripTags().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('')
});

String.prototype.escapeHTML.div.appendChild(String.prototype.escapeHTML.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;
    try {
      this._each(function(value) {
        iterator.call(context, value, index++);
      });
    } catch (e) {
      if (e != $break) throw e;
    }
    return this;
  },

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

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

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

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

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

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

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

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

    this.each(function(value, index) {
      if (filter.match(value))
        results.push(iterator.call(context, 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) {
    this.each(function(value, index) {
      memo = iterator.call(context, 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 || Prototype.K;
    var result;
    this.each(function(value, index) {
      value = iterator.call(context, value, index);
      if (result == null || value >= result)
        result = value;
    });
    return result;
  },

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

  partition: function(iterator, context) {
    iterator = iterator || Prototype.K;
    var trues = [], falses = [];
    this.each(function(value, index) {
      (iterator.call(context, 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) {
    var results = [];
    this.each(function(value, index) {
      if (!iterator.call(context, value, index))
        results.push(value);
    });
    return results;
  },

  sortBy: function(iterator, context) {
    return this.map(function(value, index) {
      return {
        value: value,
        criteria: iterator.call(context, 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 [];
    // In Safari, only use the `toArray` method if it's not a NodeList.
    // A NodeList is a function, has an function `item` property, and a numeric
    // `length` property. Adapted from Google Doctype.
    if (!(typeof iterable === 'function' && typeof iterable.length ===
        'number' && typeof iterable.item === 'function') && 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, context) {
    $R(0, this, true).each(iterator, context);
    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) {
      // simulating poorly supported hasOwnProperty
      if (this._object[key] !== Object.prototype[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.inject([], function(results, pair) {
        var key = encodeURIComponent(pair.key), values = pair.value;

        if (values && typeof values == 'object') {
          if (Object.isArray(values))
            return results.concat(values.map(toQueryPair.curry(key)));
        } else results.push(toQueryPair(key, values));
        return results;
      }).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 || { });
  if (element) this.Element.prototype = element.prototype;
}).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 = $(element);
    element.style.display = 'none';
    return element;
  },

  show: function(element) {
    element = $(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(element, 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);

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

    if (ancestor.contains)
      return ancestor.contains(element) && ancestor !== element;

    while (element = element.parentNode)
      if (element == ancestor) return true;

    return false;
  },

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

  getStyle: function(element, style) {
    element = $(element);
    style = style == 'float' ? 'cssFloat' : style.camelize();
    var value = element.style[style];
    if (!value || value == 'auto') {
      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 (Prototype.Browser.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.toUpperCase() == '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 element;
    // 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 element;
    // 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 && (element.tagName.toUpperCase() == '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);
      // IE throws an error if element is not in document
      try { element.offsetParent }
      catch(e) { return $(document.body) }
      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);
        try { element.offsetParent }
        catch(e) { return Element._returnOffset(0,0) }
        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.cumulativeOffset = Element.Methods.cumulativeOffset.wrap(
    function(proceed, element) {
      try { element.offsetParent }
      catch(e) { return Element._returnOffset(0,0) }
      return proceed(element);
    }
  );

  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 frameBorder').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.toUpperCase() == '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.toUpperCase(), 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 = { }, B = Prototype.Browser;
    $w('width height').each(function(d) {
      var D = d.capitalize();
      if (B.WebKit && !document.evaluate) {
        // Safari <3.0 needs self.innerWidth/Height
        dimensions[d] = self['inner' + D];
      } else if (B.Opera && parseFloat(window.opera.version()) < 9.5) {
        // Opera <9.5 needs document.body.clientWidth/Height
        dimensions[d] = document.body['client' + D]
      } else {
        dimensions[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();

    if (this.shouldUseSelectorsAPI()) {
      this.mode = 'selectorsAPI';
    } else if (this.shouldUseXPath()) {
      this.mode = 'xpath';
      this.compileXPathMatcher();
    } else {
      this.mode = "normal";
      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(e))
      return false;

    return true;
  },

  shouldUseSelectorsAPI: function() {
    if (!Prototype.BrowserFeatures.SelectorsAPI) return false;

    if (!Selector._div) Selector._div = new Element('div');

    // Make sure the browser treats the selector as valid. Test on an
    // isolated element to minimize cost of this check.
    try {
      Selector._div.querySelector(this.expression);
    } catch(e) {
      return false;
    }

    return true;
  },

  compileMatcher: function() {
    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;
    var e = this.expression, results;

    switch (this.mode) {
      case 'selectorsAPI':
        // querySelectorAll queries document-wide, then filters to descendants
        // of the context element. That's not what we want.
        // Add an explicit context to the selector if necessary.
        if (root !== document) {
          var oldId = root.id, id = $(root).identify();
          e = "#" + id + " " + e;
        }

        results = $A(root.querySelectorAll(e)).map(Element.extend);
        root.id = oldId;

        return results;
      case 'xpath':
        return document._getElementsByXPath(this.xpath, root);
      default:
       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)]",
      'checked':     "[@checked]",
      'disabled':    "[(@disabled) and (@type!='hidden')]",
      'enabled':     "[not(@disabled) and (@type!='hidden')]",
      '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]+:)?[\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) 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 && (!node.type || node.type !== 'hidden'))
          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 == v || nv && nv.startsWith(v); },
    '$=': function(nv, v) { return nv == v || nv && nv.endsWith(v); },
    '*=': function(nv, v) { return nv == v || nv && nv.include(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 != 'file' && (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.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, value) {
    if (Object.isUndefined(value))
      return this[element.type == 'select-one' ?
        'selectOne' : 'selectMany'](element);
    else {
      var opt, currentValue, single = !Object.isArray(value);
      for (var i = 0, length = element.length; i < length; i++) {
        opt = element.options[i];
        currentValue = this.optionValue(opt);
        if (single) {
          if (currentValue == value) {
            opt.selected = true;
            return;
          }
        }
        else opt.selected = value.include(currentValue);
      }
    }
  },

  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) {
      event = Event.extend(event);

      var node          = event.target,
          type          = event.type,
          currentTarget = event.currentTarget;

      if (currentTarget && currentTarget.tagName) {
        // Firefox screws up the "click" event when moving between radio buttons
        // via arrow keys. It also screws up the "load" and "error" events on images,
        // reporting the document as the target instead of the original image.
        if (type === 'load' || type === 'error' ||
          (type === 'click' && currentTarget.tagName.toLowerCase() === 'input'
            && currentTarget.type === 'radio'))
              node = currentTarget;
      }
      if (node.nodeType == Node.TEXT_NODE) node = node.parentNode;
      return Element.extend(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) {
      var docElement = document.documentElement,
      body = document.body || { scrollLeft: 0, scrollTop: 0 };
      return {
        x: event.pageX || (event.clientX +
          (docElement.scrollLeft || body.scrollLeft) -
          (docElement.clientLeft || 0)),
        y: event.pageY || (event.clientY +
          (docElement.scrollTop || body.scrollTop) -
          (docElement.clientTop || 0))
      };
    },

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


  // Internet Explorer needs to remove event handlers on page unload
  // in order to avoid memory leaks.
  if (window.attachEvent) {
    window.attachEvent("onunload", destroyCache);
  }

  // Safari has a dummy event handler on page unload so that it won't
  // use its bfcache. Safari <= 3.1 has an issue with restoring the "document"
  // object when page is returned to via the back button using its bfcache.
  if (Prototype.Browser.WebKit) {
    window.addEventListener('unload', Prototype.emptyFunction, false);
  }

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


/* /frameworks/scriptaculous/scriptaculous.js */

 // script.aculo.us scriptaculous.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)
//
// Permission is hereby granted, free of charge, to any person obtaining
// a copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to
// the following conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//
// For details, see the script.aculo.us web site: http://script.aculo.us/

var Scriptaculous = {
  Version: '1.8.2',
  require: function(libraryName) {
    // inserting via DOM fails in Safari 2.0, so brute force approach
    document.write('<script type="text/javascript" src="'+libraryName+'"><\/script>');
  },
  REQUIRED_PROTOTYPE: '1.6.0.3',
  load: function() {
    function convertVersionString(versionString) {
      var v = versionString.replace(/_.*|\./g, '');
      v = parseInt(v + '0'.times(4-v.length));
      return versionString.indexOf('_') > -1 ? v-1 : v;
    }

    if((typeof Prototype=='undefined') ||
       (typeof Element == 'undefined') ||
       (typeof Element.Methods=='undefined') ||
       (convertVersionString(Prototype.Version) <
        convertVersionString(Scriptaculous.REQUIRED_PROTOTYPE)))
       throw("script.aculo.us requires the Prototype JavaScript framework >= " +
        Scriptaculous.REQUIRED_PROTOTYPE);

    var js = /scriptaculous\.js(\?.*)?$/;
    $$('head script[src]').findAll(function(s) {
      return s.src.match(js);
    }).each(function(s) {
      var path = s.src.replace(js, ''),
      includes = s.src.match(/\?.*load=([a-z,]*)/);
      (includes ? includes[1] : 'builder,effects,dragdrop,controls,slider,sound').split(',').each(
       function(include) { Scriptaculous.require(path+include+'.js') });
    });
  }
};

Scriptaculous.load();


/* /frameworks/scriptaculous/effects.js */

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


/* /frameworks/scriptaculous/builder.js */

 // script.aculo.us builder.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)
//
// 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/

var Builder = {
  NODEMAP: {
    AREA: 'map',
    CAPTION: 'table',
    COL: 'table',
    COLGROUP: 'table',
    LEGEND: 'fieldset',
    OPTGROUP: 'select',
    OPTION: 'select',
    PARAM: 'object',
    TBODY: 'table',
    TD: 'table',
    TFOOT: 'table',
    TH: 'table',
    THEAD: 'table',
    TR: 'table'
  },
  // note: For Firefox < 1.5, OPTION and OPTGROUP tags are currently broken,
  //       due to a Firefox bug
  node: function(elementName) {
    elementName = elementName.toUpperCase();

    // try innerHTML approach
    var parentTag = this.NODEMAP[elementName] || 'div';
    var parentElement = document.createElement(parentTag);
    try { // prevent IE "feature": http://dev.rubyonrails.org/ticket/2707
      parentElement.innerHTML = "<" + elementName + "></" + elementName + ">";
    } catch(e) {}
    var element = parentElement.firstChild || null;

    // see if browser added wrapping tags
    if(element && (element.tagName.toUpperCase() != elementName))
      element = element.getElementsByTagName(elementName)[0];

    // fallback to createElement approach
    if(!element) element = document.createElement(elementName);

    // abort if nothing could be created
    if(!element) return;

    // attributes (or text)
    if(arguments[1])
      if(this._isStringOrNumber(arguments[1]) ||
        (arguments[1] instanceof Array) ||
        arguments[1].tagName) {
          this._children(element, arguments[1]);
        } else {
          var attrs = this._attributes(arguments[1]);
          if(attrs.length) {
            try { // prevent IE "feature": http://dev.rubyonrails.org/ticket/2707
              parentElement.innerHTML = "<" +elementName + " " +
                attrs + "></" + elementName + ">";
            } catch(e) {}
            element = parentElement.firstChild || null;
            // workaround firefox 1.0.X bug
            if(!element) {
              element = document.createElement(elementName);
              for(attr in arguments[1])
                element[attr == 'class' ? 'className' : attr] = arguments[1][attr];
            }
            if(element.tagName.toUpperCase() != elementName)
              element = parentElement.getElementsByTagName(elementName)[0];
          }
        }

    // text, or array of children
    if(arguments[2])
      this._children(element, arguments[2]);

     return $(element);
  },
  _text: function(text) {
     return document.createTextNode(text);
  },

  ATTR_MAP: {
    'className': 'class',
    'htmlFor': 'for'
  },

  _attributes: function(attributes) {
    var attrs = [];
    for(attribute in attributes)
      attrs.push((attribute in this.ATTR_MAP ? this.ATTR_MAP[attribute] : attribute) +
          '="' + attributes[attribute].toString().escapeHTML().gsub(/"/,'&quot;') + '"');
    return attrs.join(" ");
  },
  _children: function(element, children) {
    if(children.tagName) {
      element.appendChild(children);
      return;
    }
    if(typeof children=='object') { // array can hold nodes and text
      children.flatten().each( function(e) {
        if(typeof e=='object')
          element.appendChild(e);
        else
          if(Builder._isStringOrNumber(e))
            element.appendChild(Builder._text(e));
      });
    } else
      if(Builder._isStringOrNumber(children))
        element.appendChild(Builder._text(children));
  },
  _isStringOrNumber: function(param) {
    return(typeof param=='string' || typeof param=='number');
  },
  build: function(html) {
    var element = this.node('div');
    $(element).update(html.strip());
    return element.down();
  },
  dump: function(scope) {
    if(typeof scope != 'object' && typeof scope != 'function') scope = window; //global scope

    var tags = ("A ABBR ACRONYM ADDRESS APPLET AREA B BASE BASEFONT BDO BIG BLOCKQUOTE BODY " +
      "BR BUTTON CAPTION CENTER CITE CODE COL COLGROUP DD DEL DFN DIR DIV DL DT EM FIELDSET " +
      "FONT FORM FRAME FRAMESET H1 H2 H3 H4 H5 H6 HEAD HR HTML I IFRAME IMG INPUT INS ISINDEX "+
      "KBD LABEL LEGEND LI LINK MAP MENU META NOFRAMES NOSCRIPT OBJECT OL OPTGROUP OPTION P "+
      "PARAM PRE Q S SAMP SCRIPT SELECT SMALL SPAN STRIKE STRONG STYLE SUB SUP TABLE TBODY TD "+
      "TEXTAREA TFOOT TH THEAD TITLE TR TT U UL VAR").split(/\s+/);

    tags.each( function(tag){
      scope[tag] = function() {
        return Builder.node.apply(Builder, [tag].concat($A(arguments)));
      };
    });
  }
};


/* /frameworks/scriptaculous/controls.js */

 // script.aculo.us controls.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)
//           (c) 2005-2008 Ivan Krstic (http://blogs.law.harvard.edu/ivan)
//           (c) 2005-2008 Jon Tirsen (http://www.tirsen.com)
// Contributors:
//  Richard Livsey
//  Rahul Bhargava
//  Rob Wills
//
// 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/

// Autocompleter.Base handles all the autocompletion functionality
// that's independent of the data source for autocompletion. This
// includes drawing the autocompletion menu, observing keyboard
// and mouse events, and similar.
//
// Specific autocompleters need to provide, at the very least,
// a getUpdatedChoices function that will be invoked every time
// the text inside the monitored textbox changes. This method
// should get the text for which to provide autocompletion by
// invoking this.getToken(), NOT by directly accessing
// this.element.value. This is to allow incremental tokenized
// autocompletion. Specific auto-completion logic (AJAX, etc)
// belongs in getUpdatedChoices.
//
// Tokenized incremental autocompletion is enabled automatically
// when an autocompleter is instantiated with the 'tokens' option
// in the options parameter, e.g.:
// new Ajax.Autocompleter('id','upd', '/url/', { tokens: ',' });
// will incrementally autocomplete with a comma as the token.
// Additionally, ',' in the above example can be replaced with
// a token array, e.g. { tokens: [',', '\n'] } which
// enables autocompletion on multiple tokens. This is most
// useful when one of the tokens is \n (a newline), as it
// allows smart autocompletion after linebreaks.

if(typeof Effect == 'undefined')
  throw("controls.js requires including script.aculo.us' effects.js library");

var Autocompleter = { };
Autocompleter.Base = Class.create({
  baseInitialize: function(element, update, options) {
    element          = $(element);
    this.element     = element;
    this.update      = $(update);
    this.hasFocus    = false;
    this.changed     = false;
    this.active      = false;
    this.index       = 0;
    this.entryCount  = 0;
    this.oldElementValue = this.element.value;

    if(this.setOptions)
      this.setOptions(options);
    else
      this.options = options || { };

    this.options.paramName    = this.options.paramName || this.element.name;
    this.options.tokens       = this.options.tokens || [];
    this.options.frequency    = this.options.frequency || 0.4;
    this.options.minChars     = this.options.minChars || 1;
    this.options.onShow       = this.options.onShow ||
      function(element, update){
        if(!update.style.position || update.style.position=='absolute') {
          update.style.position = 'absolute';
          Position.clone(element, update, {
            setHeight: false,
            offsetTop: element.offsetHeight
          });
        }
        Effect.Appear(update,{duration:0.15});
      };
    this.options.onHide = this.options.onHide ||
      function(element, update){ new Effect.Fade(update,{duration:0.15}) };

    if(typeof(this.options.tokens) == 'string')
      this.options.tokens = new Array(this.options.tokens);
    // Force carriage returns as token delimiters anyway
    if (!this.options.tokens.include('\n'))
      this.options.tokens.push('\n');

    this.observer = null;

    this.element.setAttribute('autocomplete','off');

    Element.hide(this.update);

    Event.observe(this.element, 'blur', this.onBlur.bindAsEventListener(this));
    Event.observe(this.element, 'keydown', this.onKeyPress.bindAsEventListener(this));
  },

  show: function() {
    if(Element.getStyle(this.update, 'display')=='none') this.options.onShow(this.element, this.update);
    if(!this.iefix &&
      (Prototype.Browser.IE) &&
      (Element.getStyle(this.update, 'position')=='absolute')) {
      new Insertion.After(this.update,
       '<iframe id="' + this.update.id + '_iefix" '+
       'style="display:none;position:absolute;filter:progid:DXImageTransform.Microsoft.Alpha(opacity=0);" ' +
       'src="javascript:false;" frameborder="0" scrolling="no"></iframe>');
      this.iefix = $(this.update.id+'_iefix');
    }
    if(this.iefix) setTimeout(this.fixIEOverlapping.bind(this), 50);
  },

  fixIEOverlapping: function() {
    Position.clone(this.update, this.iefix, {setTop:(!this.update.style.height)});
    this.iefix.style.zIndex = 1;
    this.update.style.zIndex = 2;
    Element.show(this.iefix);
  },

  hide: function() {
    this.stopIndicator();
    if(Element.getStyle(this.update, 'display')!='none') this.options.onHide(this.element, this.update);
    if(this.iefix) Element.hide(this.iefix);
  },

  startIndicator: function() {
    if(this.options.indicator) Element.show(this.options.indicator);
  },

  stopIndicator: function() {
    if(this.options.indicator) Element.hide(this.options.indicator);
  },

  onKeyPress: function(event) {
    if(this.active)
      switch(event.keyCode) {
       case Event.KEY_TAB:
       case Event.KEY_RETURN:
         this.selectEntry();
         Event.stop(event);
       case Event.KEY_ESC:
         this.hide();
         this.active = false;
         Event.stop(event);
         return;
       case Event.KEY_LEFT:
       case Event.KEY_RIGHT:
         return;
       case Event.KEY_UP:
         this.markPrevious();
         this.render();
         Event.stop(event);
         return;
       case Event.KEY_DOWN:
         this.markNext();
         this.render();
         Event.stop(event);
         return;
      }
     else
       if(event.keyCode==Event.KEY_TAB || event.keyCode==Event.KEY_RETURN ||
         (Prototype.Browser.WebKit > 0 && event.keyCode == 0)) return;

    this.changed = true;
    this.hasFocus = true;

    if(this.observer) clearTimeout(this.observer);
      this.observer =
        setTimeout(this.onObserverEvent.bind(this), this.options.frequency*1000);
  },

  activate: function() {
    this.changed = false;
    this.hasFocus = true;
    this.getUpdatedChoices();
  },

  onHover: function(event) {
    var element = Event.findElement(event, 'LI');
    if(this.index != element.autocompleteIndex)
    {
        this.index = element.autocompleteIndex;
        this.render();
    }
    Event.stop(event);
  },

  onClick: function(event) {
    var element = Event.findElement(event, 'LI');
    this.index = element.autocompleteIndex;
    this.selectEntry();
    this.hide();
  },

  onBlur: function(event) {
    // needed to make click events working
    setTimeout(this.hide.bind(this), 250);
    this.hasFocus = false;
    this.active = false;
  },

  render: function() {
    if(this.entryCount > 0) {
      for (var i = 0; i < this.entryCount; i++)
        this.index==i ?
          Element.addClassName(this.getEntry(i),"selected") :
          Element.removeClassName(this.getEntry(i),"selected");
      if(this.hasFocus) {
        this.show();
        this.active = true;
      }
    } else {
      this.active = false;
      this.hide();
    }
  },

  markPrevious: function() {
    if(this.index > 0) this.index--;
      else this.index = this.entryCount-1;
    this.getEntry(this.index).scrollIntoView(true);
  },

  markNext: function() {
    if(this.index < this.entryCount-1) this.index++;
      else this.index = 0;
    this.getEntry(this.index).scrollIntoView(false);
  },

  getEntry: function(index) {
    return this.update.firstChild.childNodes[index];
  },

  getCurrentEntry: function() {
    return this.getEntry(this.index);
  },

  selectEntry: function() {
    this.active = false;
    this.updateElement(this.getCurrentEntry());
  },

  updateElement: function(selectedElement) {
    if (this.options.updateElement) {
      this.options.updateElement(selectedElement);
      return;
    }
    var value = '';
    if (this.options.select) {
      var nodes = $(selectedElement).select('.' + this.options.select) || [];
      if(nodes.length>0) value = Element.collectTextNodes(nodes[0], this.options.select);
    } else
      value = Element.collectTextNodesIgnoreClass(selectedElement, 'informal');

    var bounds = this.getTokenBounds();
    if (bounds[0] != -1) {
      var newValue = this.element.value.substr(0, bounds[0]);
      var whitespace = this.element.value.substr(bounds[0]).match(/^\s+/);
      if (whitespace)
        newValue += whitespace[0];
      this.element.value = newValue + value + this.element.value.substr(bounds[1]);
    } else {
      this.element.value = value;
    }
    this.oldElementValue = this.element.value;
    this.element.focus();

    if (this.options.afterUpdateElement)
      this.options.afterUpdateElement(this.element, selectedElement);
  },

  updateChoices: function(choices) {
    if(!this.changed && this.hasFocus) {
      this.update.innerHTML = choices;
      Element.cleanWhitespace(this.update);
      Element.cleanWhitespace(this.update.down());

      if(this.update.firstChild && this.update.down().childNodes) {
        this.entryCount =
          this.update.down().childNodes.length;
        for (var i = 0; i < this.entryCount; i++) {
          var entry = this.getEntry(i);
          entry.autocompleteIndex = i;
          this.addObservers(entry);
        }
      } else {
        this.entryCount = 0;
      }

      this.stopIndicator();
      this.index = 0;

      if(this.entryCount==1 && this.options.autoSelect) {
        this.selectEntry();
        this.hide();
      } else {
        this.render();
      }
    }
  },

  addObservers: function(element) {
    Event.observe(element, "mouseover", this.onHover.bindAsEventListener(this));
    Event.observe(element, "click", this.onClick.bindAsEventListener(this));
  },

  onObserverEvent: function() {
    this.changed = false;
    this.tokenBounds = null;
    if(this.getToken().length>=this.options.minChars) {
      this.getUpdatedChoices();
    } else {
      this.active = false;
      this.hide();
    }
    this.oldElementValue = this.element.value;
  },

  getToken: function() {
    var bounds = this.getTokenBounds();
    return this.element.value.substring(bounds[0], bounds[1]).strip();
  },

  getTokenBounds: function() {
    if (null != this.tokenBounds) return this.tokenBounds;
    var value = this.element.value;
    if (value.strip().empty()) return [-1, 0];
    var diff = arguments.callee.getFirstDifferencePos(value, this.oldElementValue);
    var offset = (diff == this.oldElementValue.length ? 1 : 0);
    var prevTokenPos = -1, nextTokenPos = value.length;
    var tp;
    for (var index = 0, l = this.options.tokens.length; index < l; ++index) {
      tp = value.lastIndexOf(this.options.tokens[index], diff + offset - 1);
      if (tp > prevTokenPos) prevTokenPos = tp;
      tp = value.indexOf(this.options.tokens[index], diff + offset);
      if (-1 != tp && tp < nextTokenPos) nextTokenPos = tp;
    }
    return (this.tokenBounds = [prevTokenPos + 1, nextTokenPos]);
  }
});

Autocompleter.Base.prototype.getTokenBounds.getFirstDifferencePos = function(newS, oldS) {
  var boundary = Math.min(newS.length, oldS.length);
  for (var index = 0; index < boundary; ++index)
    if (newS[index] != oldS[index])
      return index;
  return boundary;
};

Ajax.Autocompleter = Class.create(Autocompleter.Base, {
  initialize: function(element, update, url, options) {
    this.baseInitialize(element, update, options);
    this.options.asynchronous  = true;
    this.options.onComplete    = this.onComplete.bind(this);
    this.options.defaultParams = this.options.parameters || null;
    this.url                   = url;
  },

  getUpdatedChoices: function() {
    this.startIndicator();

    var entry = encodeURIComponent(this.options.paramName) + '=' +
      encodeURIComponent(this.getToken());

    this.options.parameters = this.options.callback ?
      this.options.callback(this.element, entry) : entry;

    if(this.options.defaultParams)
      this.options.parameters += '&' + this.options.defaultParams;

    new Ajax.Request(this.url, this.options);
  },

  onComplete: function(request) {
    this.updateChoices(request.responseText);
  }
});

// The local array autocompleter. Used when you'd prefer to
// inject an array of autocompletion options into the page, rather
// than sending out Ajax queries, which can be quite slow sometimes.
//
// The constructor takes four parameters. The first two are, as usual,
// the id of the monitored textbox, and id of the autocompletion menu.
// The third is the array you want to autocomplete from, and the fourth
// is the options block.
//
// Extra local autocompletion options:
// - choices - How many autocompletion choices to offer
//
// - partialSearch - If false, the autocompleter will match entered
//                    text only at the beginning of strings in the
//                    autocomplete array. Defaults to true, which will
//                    match text at the beginning of any *word* in the
//                    strings in the autocomplete array. If you want to
//                    search anywhere in the string, additionally set
//                    the option fullSearch to true (default: off).
//
// - fullSsearch - Search anywhere in autocomplete array strings.
//
// - partialChars - How many characters to enter before triggering
//                   a partial match (unlike minChars, which defines
//                   how many characters are required to do any match
//                   at all). Defaults to 2.
//
// - ignoreCase - Whether to ignore case when autocompleting.
//                 Defaults to true.
//
// It's possible to pass in a custom function as the 'selector'
// option, if you prefer to write your own autocompletion logic.
// In that case, the other options above will not apply unless
// you support them.

Autocompleter.Local = Class.create(Autocompleter.Base, {
  initialize: function(element, update, array, options) {
    this.baseInitialize(element, update, options);
    this.options.array = array;
  },

  getUpdatedChoices: function() {
    this.updateChoices(this.options.selector(this));
  },

  setOptions: function(options) {
    this.options = Object.extend({
      choices: 10,
      partialSearch: true,
      partialChars: 2,
      ignoreCase: true,
      fullSearch: false,
      selector: function(instance) {
        var ret       = []; // Beginning matches
        var partial   = []; // Inside matches
        var entry     = instance.getToken();
        var count     = 0;

        for (var i = 0; i < instance.options.array.length &&
          ret.length < instance.options.choices ; i++) {

          var elem = instance.options.array[i];
          var foundPos = instance.options.ignoreCase ?
            elem.toLowerCase().indexOf(entry.toLowerCase()) :
            elem.indexOf(entry);

          while (foundPos != -1) {
            if (foundPos == 0 && elem.length != entry.length) {
              ret.push("<li><strong>" + elem.substr(0, entry.length) + "</strong>" +
                elem.substr(entry.length) + "</li>");
              break;
            } else if (entry.length >= instance.options.partialChars &&
              instance.options.partialSearch && foundPos != -1) {
              if (instance.options.fullSearch || /\s/.test(elem.substr(foundPos-1,1))) {
                partial.push("<li>" + elem.substr(0, foundPos) + "<strong>" +
                  elem.substr(foundPos, entry.length) + "</strong>" + elem.substr(
                  foundPos + entry.length) + "</li>");
                break;
              }
            }

            foundPos = instance.options.ignoreCase ?
              elem.toLowerCase().indexOf(entry.toLowerCase(), foundPos + 1) :
              elem.indexOf(entry, foundPos + 1);

          }
        }
        if (partial.length)
          ret = ret.concat(partial.slice(0, instance.options.choices - ret.length));
        return "<ul>" + ret.join('') + "</ul>";
      }
    }, options || { });
  }
});

// AJAX in-place editor and collection editor
// Full rewrite by Christophe Porteneuve <tdd@tddsworld.com> (April 2007).

// Use this if you notice weird scrolling problems on some browsers,
// the DOM might be a bit confused when this gets called so do this
// waits 1 ms (with setTimeout) until it does the activation
Field.scrollFreeActivate = function(field) {
  setTimeout(function() {
    Field.activate(field);
  }, 1);
};

Ajax.InPlaceEditor = Class.create({
  initialize: function(element, url, options) {
    this.url = url;
    this.element = element = $(element);
    this.prepareOptions();
    this._controls = { };
    arguments.callee.dealWithDeprecatedOptions(options); // DEPRECATION LAYER!!!
    Object.extend(this.options, options || { });
    if (!this.options.formId && this.element.id) {
      this.options.formId = this.element.id + '-inplaceeditor';
      if ($(this.options.formId))
        this.options.formId = '';
    }
    if (this.options.externalControl)
      this.options.externalControl = $(this.options.externalControl);
    if (!this.options.externalControl)
      this.options.externalControlOnly = false;
    this._originalBackground = this.element.getStyle('background-color') || 'transparent';
    this.element.title = this.options.clickToEditText;
    this._boundCancelHandler = this.handleFormCancellation.bind(this);
    this._boundComplete = (this.options.onComplete || Prototype.emptyFunction).bind(this);
    this._boundFailureHandler = this.handleAJAXFailure.bind(this);
    this._boundSubmitHandler = this.handleFormSubmission.bind(this);
    this._boundWrapperHandler = this.wrapUp.bind(this);
    this.registerListeners();
  },
  checkForEscapeOrReturn: function(e) {
    if (!this._editing || e.ctrlKey || e.altKey || e.shiftKey) return;
    if (Event.KEY_ESC == e.keyCode)
      this.handleFormCancellation(e);
    else if (Event.KEY_RETURN == e.keyCode)
      this.handleFormSubmission(e);
  },
  createControl: function(mode, handler, extraClasses) {
    var control = this.options[mode + 'Control'];
    var text = this.options[mode + 'Text'];
    if ('button' == control) {
      var btn = document.createElement('input');
      btn.type = 'submit';
      btn.value = text;
      btn.className = 'editor_' + mode + '_button';
      if ('cancel' == mode)
        btn.onclick = this._boundCancelHandler;
      this._form.appendChild(btn);
      this._controls[mode] = btn;
    } else if ('link' == control) {
      var link = document.createElement('a');
      link.href = '#';
      link.appendChild(document.createTextNode(text));
      link.onclick = 'cancel' == mode ? this._boundCancelHandler : this._boundSubmitHandler;
      link.className = 'editor_' + mode + '_link';
      if (extraClasses)
        link.className += ' ' + extraClasses;
      this._form.appendChild(link);
      this._controls[mode] = link;
    }
  },
  createEditField: function() {
    var text = (this.options.loadTextURL ? this.options.loadingText : this.getText());
    var fld;
    if (1 >= this.options.rows && !/\r|\n/.test(this.getText())) {
      fld = document.createElement('input');
      fld.type = 'text';
      var size = this.options.size || this.options.cols || 0;
      if (0 < size) fld.size = size;
    } else {
      fld = document.createElement('textarea');
      fld.rows = (1 >= this.options.rows ? this.options.autoRows : this.options.rows);
      fld.cols = this.options.cols || 40;
    }
    fld.name = this.options.paramName;
    fld.value = text; // No HTML breaks conversion anymore
    fld.className = 'editor_field';
    if (this.options.submitOnBlur)
      fld.onblur = this._boundSubmitHandler;
    this._controls.editor = fld;
    if (this.options.loadTextURL)
      this.loadExternalText();
    this._form.appendChild(this._controls.editor);
  },
  createForm: function() {
    var ipe = this;
    function addText(mode, condition) {
      var text = ipe.options['text' + mode + 'Controls'];
      if (!text || condition === false) return;
      ipe._form.appendChild(document.createTextNode(text));
    };
    this._form = $(document.createElement('form'));
    this._form.id = this.options.formId;
    this._form.addClassName(this.options.formClassName);
    this._form.onsubmit = this._boundSubmitHandler;
    this.createEditField();
    if ('textarea' == this._controls.editor.tagName.toLowerCase())
      this._form.appendChild(document.createElement('br'));
    if (this.options.onFormCustomization)
      this.options.onFormCustomization(this, this._form);
    addText('Before', this.options.okControl || this.options.cancelControl);
    this.createControl('ok', this._boundSubmitHandler);
    addText('Between', this.options.okControl && this.options.cancelControl);
    this.createControl('cancel', this._boundCancelHandler, 'editor_cancel');
    addText('After', this.options.okControl || this.options.cancelControl);
  },
  destroy: function() {
    if (this._oldInnerHTML)
      this.element.innerHTML = this._oldInnerHTML;
    this.leaveEditMode();
    this.unregisterListeners();
  },
  enterEditMode: function(e) {
    if (this._saving || this._editing) return;
    this._editing = true;
    this.triggerCallback('onEnterEditMode');
    if (this.options.externalControl)
      this.options.externalControl.hide();
    this.element.hide();
    this.createForm();
    this.element.parentNode.insertBefore(this._form, this.element);
    if (!this.options.loadTextURL)
      this.postProcessEditField();
    if (e) Event.stop(e);
  },
  enterHover: function(e) {
    if (this.options.hoverClassName)
      this.element.addClassName(this.options.hoverClassName);
    if (this._saving) return;
    this.triggerCallback('onEnterHover');
  },
  getText: function() {
    return this.element.innerHTML.unescapeHTML();
  },
  handleAJAXFailure: function(transport) {
    this.triggerCallback('onFailure', transport);
    if (this._oldInnerHTML) {
      this.element.innerHTML = this._oldInnerHTML;
      this._oldInnerHTML = null;
    }
  },
  handleFormCancellation: function(e) {
    this.wrapUp();
    if (e) Event.stop(e);
  },
  handleFormSubmission: function(e) {
    var form = this._form;
    var value = $F(this._controls.editor);
    this.prepareSubmission();
    var params = this.options.callback(form, value) || '';
    if (Object.isString(params))
      params = params.toQueryParams();
    params.editorId = this.element.id;
    if (this.options.htmlResponse) {
      var options = Object.extend({ evalScripts: true }, this.options.ajaxOptions);
      Object.extend(options, {
        parameters: params,
        onComplete: this._boundWrapperHandler,
        onFailure: this._boundFailureHandler
      });
      new Ajax.Updater({ success: this.element }, this.url, options);
    } else {
      var options = Object.extend({ method: 'get' }, this.options.ajaxOptions);
      Object.extend(options, {
        parameters: params,
        onComplete: this._boundWrapperHandler,
        onFailure: this._boundFailureHandler
      });
      new Ajax.Request(this.url, options);
    }
    if (e) Event.stop(e);
  },
  leaveEditMode: function() {
    this.element.removeClassName(this.options.savingClassName);
    this.removeForm();
    this.leaveHover();
    this.element.style.backgroundColor = this._originalBackground;
    this.element.show();
    if (this.options.externalControl)
      this.options.externalControl.show();
    this._saving = false;
    this._editing = false;
    this._oldInnerHTML = null;
    this.triggerCallback('onLeaveEditMode');
  },
  leaveHover: function(e) {
    if (this.options.hoverClassName)
      this.element.removeClassName(this.options.hoverClassName);
    if (this._saving) return;
    this.triggerCallback('onLeaveHover');
  },
  loadExternalText: function() {
    this._form.addClassName(this.options.loadingClassName);
    this._controls.editor.disabled = true;
    var options = Object.extend({ method: 'get' }, this.options.ajaxOptions);
    Object.extend(options, {
      parameters: 'editorId=' + encodeURIComponent(this.element.id),
      onComplete: Prototype.emptyFunction,
      onSuccess: function(transport) {
        this._form.removeClassName(this.options.loadingClassName);
        var text = transport.responseText;
        if (this.options.stripLoadedTextTags)
          text = text.stripTags();
        this._controls.editor.value = text;
        this._controls.editor.disabled = false;
        this.postProcessEditField();
      }.bind(this),
      onFailure: this._boundFailureHandler
    });
    new Ajax.Request(this.options.loadTextURL, options);
  },
  postProcessEditField: function() {
    var fpc = this.options.fieldPostCreation;
    if (fpc)
      $(this._controls.editor)['focus' == fpc ? 'focus' : 'activate']();
  },
  prepareOptions: function() {
    this.options = Object.clone(Ajax.InPlaceEditor.DefaultOptions);
    Object.extend(this.options, Ajax.InPlaceEditor.DefaultCallbacks);
    [this._extraDefaultOptions].flatten().compact().each(function(defs) {
      Object.extend(this.options, defs);
    }.bind(this));
  },
  prepareSubmission: function() {
    this._saving = true;
    this.removeForm();
    this.leaveHover();
    this.showSaving();
  },
  registerListeners: function() {
    this._listeners = { };
    var listener;
    $H(Ajax.InPlaceEditor.Listeners).each(function(pair) {
      listener = this[pair.value].bind(this);
      this._listeners[pair.key] = listener;
      if (!this.options.externalControlOnly)
        this.element.observe(pair.key, listener);
      if (this.options.externalControl)
        this.options.externalControl.observe(pair.key, listener);
    }.bind(this));
  },
  removeForm: function() {
    if (!this._form) return;
    this._form.remove();
    this._form = null;
    this._controls = { };
  },
  showSaving: function() {
    this._oldInnerHTML = this.element.innerHTML;
    this.element.innerHTML = this.options.savingText;
    this.element.addClassName(this.options.savingClassName);
    this.element.style.backgroundColor = this._originalBackground;
    this.element.show();
  },
  triggerCallback: function(cbName, arg) {
    if ('function' == typeof this.options[cbName]) {
      this.options[cbName](this, arg);
    }
  },
  unregisterListeners: function() {
    $H(this._listeners).each(function(pair) {
      if (!this.options.externalControlOnly)
        this.element.stopObserving(pair.key, pair.value);
      if (this.options.externalControl)
        this.options.externalControl.stopObserving(pair.key, pair.value);
    }.bind(this));
  },
  wrapUp: function(transport) {
    this.leaveEditMode();
    // Can't use triggerCallback due to backward compatibility: requires
    // binding + direct element
    this._boundComplete(transport, this.element);
  }
});

Object.extend(Ajax.InPlaceEditor.prototype, {
  dispose: Ajax.InPlaceEditor.prototype.destroy
});

Ajax.InPlaceCollectionEditor = Class.create(Ajax.InPlaceEditor, {
  initialize: function($super, element, url, options) {
    this._extraDefaultOptions = Ajax.InPlaceCollectionEditor.DefaultOptions;
    $super(element, url, options);
  },

  createEditField: function() {
    var list = document.createElement('select');
    list.name = this.options.paramName;
    list.size = 1;
    this._controls.editor = list;
    this._collection = this.options.collection || [];
    if (this.options.loadCollectionURL)
      this.loadCollection();
    else
      this.checkForExternalText();
    this._form.appendChild(this._controls.editor);
  },

  loadCollection: function() {
    this._form.addClassName(this.options.loadingClassName);
    this.showLoadingText(this.options.loadingCollectionText);
    var options = Object.extend({ method: 'get' }, this.options.ajaxOptions);
    Object.extend(options, {
      parameters: 'editorId=' + encodeURIComponent(this.element.id),
      onComplete: Prototype.emptyFunction,
      onSuccess: function(transport) {
        var js = transport.responseText.strip();
        if (!/^\[.*\]$/.test(js)) // TODO: improve sanity check
          throw('Server returned an invalid collection representation.');
        this._collection = eval(js);
        this.checkForExternalText();
      }.bind(this),
      onFailure: this.onFailure
    });
    new Ajax.Request(this.options.loadCollectionURL, options);
  },

  showLoadingText: function(text) {
    this._controls.editor.disabled = true;
    var tempOption = this._controls.editor.firstChild;
    if (!tempOption) {
      tempOption = document.createElement('option');
      tempOption.value = '';
      this._controls.editor.appendChild(tempOption);
      tempOption.selected = true;
    }
    tempOption.update((text || '').stripScripts().stripTags());
  },

  checkForExternalText: function() {
    this._text = this.getText();
    if (this.options.loadTextURL)
      this.loadExternalText();
    else
      this.buildOptionList();
  },

  loadExternalText: function() {
    this.showLoadingText(this.options.loadingText);
    var options = Object.extend({ method: 'get' }, this.options.ajaxOptions);
    Object.extend(options, {
      parameters: 'editorId=' + encodeURIComponent(this.element.id),
      onComplete: Prototype.emptyFunction,
      onSuccess: function(transport) {
        this._text = transport.responseText.strip();
        this.buildOptionList();
      }.bind(this),
      onFailure: this.onFailure
    });
    new Ajax.Request(this.options.loadTextURL, options);
  },

  buildOptionList: function() {
    this._form.removeClassName(this.options.loadingClassName);
    this._collection = this._collection.map(function(entry) {
      return 2 === entry.length ? entry : [entry, entry].flatten();
    });
    var marker = ('value' in this.options) ? this.options.value : this._text;
    var textFound = this._collection.any(function(entry) {
      return entry[0] == marker;
    }.bind(this));
    this._controls.editor.update('');
    var option;
    this._collection.each(function(entry, index) {
      option = document.createElement('option');
      option.value = entry[0];
      option.selected = textFound ? entry[0] == marker : 0 == index;
      option.appendChild(document.createTextNode(entry[1]));
      this._controls.editor.appendChild(option);
    }.bind(this));
    this._controls.editor.disabled = false;
    Field.scrollFreeActivate(this._controls.editor);
  }
});

//**** DEPRECATION LAYER FOR InPlace[Collection]Editor! ****
//**** This only  exists for a while,  in order to  let ****
//**** users adapt to  the new API.  Read up on the new ****
//**** API and convert your code to it ASAP!            ****

Ajax.InPlaceEditor.prototype.initialize.dealWithDeprecatedOptions = function(options) {
  if (!options) return;
  function fallback(name, expr) {
    if (name in options || expr === undefined) return;
    options[name] = expr;
  };
  fallback('cancelControl', (options.cancelLink ? 'link' : (options.cancelButton ? 'button' :
    options.cancelLink == options.cancelButton == false ? false : undefined)));
  fallback('okControl', (options.okLink ? 'link' : (options.okButton ? 'button' :
    options.okLink == options.okButton == false ? false : undefined)));
  fallback('highlightColor', options.highlightcolor);
  fallback('highlightEndColor', options.highlightendcolor);
};

Object.extend(Ajax.InPlaceEditor, {
  DefaultOptions: {
    ajaxOptions: { },
    autoRows: 3,                                // Use when multi-line w/ rows == 1
    cancelControl: 'link',                      // 'link'|'button'|false
    cancelText: 'cancel',
    clickToEditText: 'Click to edit',
    externalControl: null,                      // id|elt
    externalControlOnly: false,
    fieldPostCreation: 'activate',              // 'activate'|'focus'|false
    formClassName: 'inplaceeditor-form',
    formId: null,                               // id|elt
    highlightColor: '#ffff99',
    highlightEndColor: '#ffffff',
    hoverClassName: '',
    htmlResponse: true,
    loadingClassName: 'inplaceeditor-loading',
    loadingText: 'Loading...',
    okControl: 'button',                        // 'link'|'button'|false
    okText: 'ok',
    paramName: 'value',
    rows: 1,                                    // If 1 and multi-line, uses autoRows
    savingClassName: 'inplaceeditor-saving',
    savingText: 'Saving...',
    size: 0,
    stripLoadedTextTags: false,
    submitOnBlur: false,
    textAfterControls: '',
    textBeforeControls: '',
    textBetweenControls: ''
  },
  DefaultCallbacks: {
    callback: function(form) {
      return Form.serialize(form);
    },
    onComplete: function(transport, element) {
      // For backward compatibility, this one is bound to the IPE, and passes
      // the element directly.  It was too often customized, so we don't break it.
      new Effect.Highlight(element, {
        startcolor: this.options.highlightColor, keepBackgroundImage: true });
    },
    onEnterEditMode: null,
    onEnterHover: function(ipe) {
      ipe.element.style.backgroundColor = ipe.options.highlightColor;
      if (ipe._effect)
        ipe._effect.cancel();
    },
    onFailure: function(transport, ipe) {
      alert('Error communication with the server: ' + transport.responseText.stripTags());
    },
    onFormCustomization: null, // Takes the IPE and its generated form, after editor, before controls.
    onLeaveEditMode: null,
    onLeaveHover: function(ipe) {
      ipe._effect = new Effect.Highlight(ipe.element, {
        startcolor: ipe.options.highlightColor, endcolor: ipe.options.highlightEndColor,
        restorecolor: ipe._originalBackground, keepBackgroundImage: true
      });
    }
  },
  Listeners: {
    click: 'enterEditMode',
    keydown: 'checkForEscapeOrReturn',
    mouseover: 'enterHover',
    mouseout: 'leaveHover'
  }
});

Ajax.InPlaceCollectionEditor.DefaultOptions = {
  loadingCollectionText: 'Loading options...'
};

// Delayed observer, like Form.Element.Observer,
// but waits for delay after last key input
// Ideal for live-search fields

Form.Element.DelayedObserver = Class.create({
  initialize: function(element, delay, callback) {
    this.delay     = delay || 0.5;
    this.element   = $(element);
    this.callback  = callback;
    this.timer     = null;
    this.lastValue = $F(this.element);
    Event.observe(this.element,'keyup',this.delayedListener.bindAsEventListener(this));
  },
  delayedListener: function(event) {
    if(this.lastValue == $F(this.element)) return;
    if(this.timer) clearTimeout(this.timer);
    this.timer = setTimeout(this.onTimerEvent.bind(this), this.delay * 1000);
    this.lastValue = $F(this.element);
  },
  onTimerEvent: function() {
    this.timer = null;
    this.callback(this.element, $F(this.element));
  }
});


/* /frameworks/scriptaculous/dragdrop.js */

 // script.aculo.us dragdrop.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)
//           (c) 2005-2008 Sammi Williams (http://www.oriontransfer.co.nz, sammi@oriontransfer.co.nz)
//
// 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/

if(Object.isUndefined(Effect))
  throw("dragdrop.js requires including script.aculo.us' effects.js library");

var Droppables = {
  drops: [],

  remove: function(element) {
    this.drops = this.drops.reject(function(d) { return d.element==$(element) });
  },

  add: function(element) {
    element = $(element);
    var options = Object.extend({
      greedy:     true,
      hoverclass: null,
      tree:       false
    }, arguments[1] || { });

    // cache containers
    if(options.containment) {
      options._containers = [];
      var containment = options.containment;
      if(Object.isArray(containment)) {
        containment.each( function(c) { options._containers.push($(c)) });
      } else {
        options._containers.push($(containment));
      }
    }

    if(options.accept) options.accept = [options.accept].flatten();

    Element.makePositioned(element); // fix IE
    options.element = element;

    this.drops.push(options);
  },

  findDeepestChild: function(drops) {
    deepest = drops[0];

    for (i = 1; i < drops.length; ++i)
      if (Element.isParent(drops[i].element, deepest.element))
        deepest = drops[i];

    return deepest;
  },

  isContained: function(element, drop) {
    var containmentNode;
    if(drop.tree) {
      containmentNode = element.treeNode;
    } else {
      containmentNode = element.parentNode;
    }
    return drop._containers.detect(function(c) { return containmentNode == c });
  },

  isAffected: function(point, element, drop) {
    return (
      (drop.element!=element) &&
      ((!drop._containers) ||
        this.isContained(element, drop)) &&
      ((!drop.accept) ||
        (Element.classNames(element).detect(
          function(v) { return drop.accept.include(v) } ) )) &&
      Position.within(drop.element, point[0], point[1]) );
  },

  deactivate: function(drop) {
    if(drop.hoverclass)
      Element.removeClassName(drop.element, drop.hoverclass);
    this.last_active = null;
  },

  activate: function(drop) {
    if(drop.hoverclass)
      Element.addClassName(drop.element, drop.hoverclass);
    this.last_active = drop;
  },

  show: function(point, element) {
    if(!this.drops.length) return;
    var drop, affected = [];

    this.drops.each( function(drop) {
      if(Droppables.isAffected(point, element, drop))
        affected.push(drop);
    });

    if(affected.length>0)
      drop = Droppables.findDeepestChild(affected);

    if(this.last_active && this.last_active != drop) this.deactivate(this.last_active);
    if (drop) {
      Position.within(drop.element, point[0], point[1]);
      if(drop.onHover)
        drop.onHover(element, drop.element, Position.overlap(drop.overlap, drop.element));

      if (drop != this.last_active) Droppables.activate(drop);
    }
  },

  fire: function(event, element) {
    if(!this.last_active) return;
    Position.prepare();

    if (this.isAffected([Event.pointerX(event), Event.pointerY(event)], element, this.last_active))
      if (this.last_active.onDrop) {
        this.last_active.onDrop(element, this.last_active.element, event);
        return true;
      }
  },

  reset: function() {
    if(this.last_active)
      this.deactivate(this.last_active);
  }
};

var Draggables = {
  drags: [],
  observers: [],

  register: function(draggable) {
    if(this.drags.length == 0) {
      this.eventMouseUp   = this.endDrag.bindAsEventListener(this);
      this.eventMouseMove = this.updateDrag.bindAsEventListener(this);
      this.eventKeypress  = this.keyPress.bindAsEventListener(this);

      Event.observe(document, "mouseup", this.eventMouseUp);
      Event.observe(document, "mousemove", this.eventMouseMove);
      Event.observe(document, "keypress", this.eventKeypress);
    }
    this.drags.push(draggable);
  },

  unregister: function(draggable) {
    this.drags = this.drags.reject(function(d) { return d==draggable });
    if(this.drags.length == 0) {
      Event.stopObserving(document, "mouseup", this.eventMouseUp);
      Event.stopObserving(document, "mousemove", this.eventMouseMove);
      Event.stopObserving(document, "keypress", this.eventKeypress);
    }
  },

  activate: function(draggable) {
    if(draggable.options.delay) {
      this._timeout = setTimeout(function() {
        Draggables._timeout = null;
        window.focus();
        Draggables.activeDraggable = draggable;
      }.bind(this), draggable.options.delay);
    } else {
      window.focus(); // allows keypress events if window isn't currently focused, fails for Safari
      this.activeDraggable = draggable;
    }
  },

  deactivate: function() {
    this.activeDraggable = null;
  },

  updateDrag: function(event) {
    if(!this.activeDraggable) return;
    var pointer = [Event.pointerX(event), Event.pointerY(event)];
    // Mozilla-based browsers fire successive mousemove events with
    // the same coordinates, prevent needless redrawing (moz bug?)
    if(this._lastPointer && (this._lastPointer.inspect() == pointer.inspect())) return;
    this._lastPointer = pointer;

    this.activeDraggable.updateDrag(event, pointer);
  },

  endDrag: function(event) {
    if(this._timeout) {
      clearTimeout(this._timeout);
      this._timeout = null;
    }
    if(!this.activeDraggable) return;
    this._lastPointer = null;
    this.activeDraggable.endDrag(event);
    this.activeDraggable = null;
  },

  keyPress: function(event) {
    if(this.activeDraggable)
      this.activeDraggable.keyPress(event);
  },

  addObserver: function(observer) {
    this.observers.push(observer);
    this._cacheObserverCallbacks();
  },

  removeObserver: function(element) {  // element instead of observer fixes mem leaks
    this.observers = this.observers.reject( function(o) { return o.element==element });
    this._cacheObserverCallbacks();
  },

  notify: function(eventName, draggable, event) {  // 'onStart', 'onEnd', 'onDrag'
    if(this[eventName+'Count'] > 0)
      this.observers.each( function(o) {
        if(o[eventName]) o[eventName](eventName, draggable, event);
      });
    if(draggable.options[eventName]) draggable.options[eventName](draggable, event);
  },

  _cacheObserverCallbacks: function() {
    ['onStart','onEnd','onDrag'].each( function(eventName) {
      Draggables[eventName+'Count'] = Draggables.observers.select(
        function(o) { return o[eventName]; }
      ).length;
    });
  }
};

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

var Draggable = Class.create({
  initialize: function(element) {
    var defaults = {
      handle: false,
      reverteffect: function(element, top_offset, left_offset) {
        var dur = Math.sqrt(Math.abs(top_offset^2)+Math.abs(left_offset^2))*0.02;
        new Effect.Move(element, { x: -left_offset, y: -top_offset, duration: dur,
          queue: {scope:'_draggable', position:'end'}
        });
      },
      endeffect: function(element) {
        var toOpacity = Object.isNumber(element._opacity) ? element._opacity : 1.0;
        new Effect.Opacity(element, {duration:0.2, from:0.7, to:toOpacity,
          queue: {scope:'_draggable', position:'end'},
          afterFinish: function(){
            Draggable._dragging[element] = false
          }
        });
      },
      zindex: 1000,
      revert: false,
      quiet: false,
      scroll: false,
      scrollSensitivity: 20,
      scrollSpeed: 15,
      snap: false,  // false, or xy or [x,y] or function(x,y){ return [x,y] }
      delay: 0
    };

    if(!arguments[1] || Object.isUndefined(arguments[1].endeffect))
      Object.extend(defaults, {
        starteffect: function(element) {
          element._opacity = Element.getOpacity(element);
          Draggable._dragging[element] = true;
          new Effect.Opacity(element, {duration:0.2, from:element._opacity, to:0.7});
        }
      });

    var options = Object.extend(defaults, arguments[1] || { });

    this.element = $(element);

    if(options.handle && Object.isString(options.handle))
      this.handle = this.element.down('.'+options.handle, 0);

    if(!this.handle) this.handle = $(options.handle);
    if(!this.handle) this.handle = this.element;

    if(options.scroll && !options.scroll.scrollTo && !options.scroll.outerHTML) {
      options.scroll = $(options.scroll);
      this._isScrollChild = Element.childOf(this.element, options.scroll);
    }

    Element.makePositioned(this.element); // fix IE

    this.options  = options;
    this.dragging = false;

    this.eventMouseDown = this.initDrag.bindAsEventListener(this);
    Event.observe(this.handle, "mousedown", this.eventMouseDown);

    Draggables.register(this);
  },

  destroy: function() {
    Event.stopObserving(this.handle, "mousedown", this.eventMouseDown);
    Draggables.unregister(this);
  },

  currentDelta: function() {
    return([
      parseInt(Element.getStyle(this.element,'left') || '0'),
      parseInt(Element.getStyle(this.element,'top') || '0')]);
  },

  initDrag: function(event) {
    if(!Object.isUndefined(Draggable._dragging[this.element]) &&
      Draggable._dragging[this.element]) return;
    if(Event.isLeftClick(event)) {
      // abort on form elements, fixes a Firefox issue
      var src = Event.element(event);
      if((tag_name = src.tagName.toUpperCase()) && (
        tag_name=='INPUT' ||
        tag_name=='SELECT' ||
        tag_name=='OPTION' ||
        tag_name=='BUTTON' ||
        tag_name=='TEXTAREA')) return;

      var pointer = [Event.pointerX(event), Event.pointerY(event)];
      var pos     = Position.cumulativeOffset(this.element);
      this.offset = [0,1].map( function(i) { return (pointer[i] - pos[i]) });

      Draggables.activate(this);
      Event.stop(event);
    }
  },

  startDrag: function(event) {
    this.dragging = true;
    if(!this.delta)
      this.delta = this.currentDelta();

    if(this.options.zindex) {
      this.originalZ = parseInt(Element.getStyle(this.element,'z-index') || 0);
      this.element.style.zIndex = this.options.zindex;
    }

    if(this.options.ghosting) {
      this._clone = this.element.cloneNode(true);
      this._originallyAbsolute = (this.element.getStyle('position') == 'absolute');
      if (!this._originallyAbsolute)
        Position.absolutize(this.element);
      this.element.parentNode.insertBefore(this._clone, this.element);
    }

    if(this.options.scroll) {
      if (this.options.scroll == window) {
        var where = this._getWindowScroll(this.options.scroll);
        this.originalScrollLeft = where.left;
        this.originalScrollTop = where.top;
      } else {
        this.originalScrollLeft = this.options.scroll.scrollLeft;
        this.originalScrollTop = this.options.scroll.scrollTop;
      }
    }

    Draggables.notify('onStart', this, event);

    if(this.options.starteffect) this.options.starteffect(this.element);
  },

  updateDrag: function(event, pointer) {
    if(!this.dragging) this.startDrag(event);

    if(!this.options.quiet){
      Position.prepare();
      Droppables.show(pointer, this.element);
    }

    Draggables.notify('onDrag', this, event);

    this.draw(pointer);
    if(this.options.change) this.options.change(this);

    if(this.options.scroll) {
      this.stopScrolling();

      var p;
      if (this.options.scroll == window) {
        with(this._getWindowScroll(this.options.scroll)) { p = [ left, top, left+width, top+height ]; }
      } else {
        p = Position.page(this.options.scroll);
        p[0] += this.options.scroll.scrollLeft + Position.deltaX;
        p[1] += this.options.scroll.scrollTop + Position.deltaY;
        p.push(p[0]+this.options.scroll.offsetWidth);
        p.push(p[1]+this.options.scroll.offsetHeight);
      }
      var speed = [0,0];
      if(pointer[0] < (p[0]+this.options.scrollSensitivity)) speed[0] = pointer[0]-(p[0]+this.options.scrollSensitivity);
      if(pointer[1] < (p[1]+this.options.scrollSensitivity)) speed[1] = pointer[1]-(p[1]+this.options.scrollSensitivity);
      if(pointer[0] > (p[2]-this.options.scrollSensitivity)) speed[0] = pointer[0]-(p[2]-this.options.scrollSensitivity);
      if(pointer[1] > (p[3]-this.options.scrollSensitivity)) speed[1] = pointer[1]-(p[3]-this.options.scrollSensitivity);
      this.startScrolling(speed);
    }

    // fix AppleWebKit rendering
    if(Prototype.Browser.WebKit) window.scrollBy(0,0);

    Event.stop(event);
  },

  finishDrag: function(event, success) {
    this.dragging = false;

    if(this.options.quiet){
      Position.prepare();
      var pointer = [Event.pointerX(event), Event.pointerY(event)];
      Droppables.show(pointer, this.element);
    }

    if(this.options.ghosting) {
      if (!this._originallyAbsolute)
        Position.relativize(this.element);
      delete this._originallyAbsolute;
      Element.remove(this._clone);
      this._clone = null;
    }

    var dropped = false;
    if(success) {
      dropped = Droppables.fire(event, this.element);
      if (!dropped) dropped = false;
    }
    if(dropped && this.options.onDropped) this.options.onDropped(this.element);
    Draggables.notify('onEnd', this, event);

    var revert = this.options.revert;
    if(revert && Object.isFunction(revert)) revert = revert(this.element);

    var d = this.currentDelta();
    if(revert && this.options.reverteffect) {
      if (dropped == 0 || revert != 'failure')
        this.options.reverteffect(this.element,
          d[1]-this.delta[1], d[0]-this.delta[0]);
    } else {
      this.delta = d;
    }

    if(this.options.zindex)
      this.element.style.zIndex = this.originalZ;

    if(this.options.endeffect)
      this.options.endeffect(this.element);

    Draggables.deactivate(this);
    Droppables.reset();
  },

  keyPress: function(event) {
    if(event.keyCode!=Event.KEY_ESC) return;
    this.finishDrag(event, false);
    Event.stop(event);
  },

  endDrag: function(event) {
    if(!this.dragging) return;
    this.stopScrolling();
    this.finishDrag(event, true);
    Event.stop(event);
  },

  draw: function(point) {
    var pos = Position.cumulativeOffset(this.element);
    if(this.options.ghosting) {
      var r   = Position.realOffset(this.element);
      pos[0] += r[0] - Position.deltaX; pos[1] += r[1] - Position.deltaY;
    }

    var d = this.currentDelta();
    pos[0] -= d[0]; pos[1] -= d[1];

    if(this.options.scroll && (this.options.scroll != window && this._isScrollChild)) {
      pos[0] -= this.options.scroll.scrollLeft-this.originalScrollLeft;
      pos[1] -= this.options.scroll.scrollTop-this.originalScrollTop;
    }

    var p = [0,1].map(function(i){
      return (point[i]-pos[i]-this.offset[i])
    }.bind(this));

    if(this.options.snap) {
      if(Object.isFunction(this.options.snap)) {
        p = this.options.snap(p[0],p[1],this);
      } else {
      if(Object.isArray(this.options.snap)) {
        p = p.map( function(v, i) {
          return (v/this.options.snap[i]).round()*this.options.snap[i] }.bind(this));
      } else {
        p = p.map( function(v) {
          return (v/this.options.snap).round()*this.options.snap }.bind(this));
      }
    }}

    var style = this.element.style;
    if((!this.options.constraint) || (this.options.constraint=='horizontal'))
      style.left = p[0] + "px";
    if((!this.options.constraint) || (this.options.constraint=='vertical'))
      style.top  = p[1] + "px";

    if(style.visibility=="hidden") style.visibility = ""; // fix gecko rendering
  },

  stopScrolling: function() {
    if(this.scrollInterval) {
      clearInterval(this.scrollInterval);
      this.scrollInterval = null;
      Draggables._lastScrollPointer = null;
    }
  },

  startScrolling: function(speed) {
    if(!(speed[0] || speed[1])) return;
    this.scrollSpeed = [speed[0]*this.options.scrollSpeed,speed[1]*this.options.scrollSpeed];
    this.lastScrolled = new Date();
    this.scrollInterval = setInterval(this.scroll.bind(this), 10);
  },

  scroll: function() {
    var current = new Date();
    var delta = current - this.lastScrolled;
    this.lastScrolled = current;
    if(this.options.scroll == window) {
      with (this._getWindowScroll(this.options.scroll)) {
        if (this.scrollSpeed[0] || this.scrollSpeed[1]) {
          var d = delta / 1000;
          this.options.scroll.scrollTo( left + d*this.scrollSpeed[0], top + d*this.scrollSpeed[1] );
        }
      }
    } else {
      this.options.scroll.scrollLeft += this.scrollSpeed[0] * delta / 1000;
      this.options.scroll.scrollTop  += this.scrollSpeed[1] * delta / 1000;
    }

    Position.prepare();
    Droppables.show(Draggables._lastPointer, this.element);
    Draggables.notify('onDrag', this);
    if (this._isScrollChild) {
      Draggables._lastScrollPointer = Draggables._lastScrollPointer || $A(Draggables._lastPointer);
      Draggables._lastScrollPointer[0] += this.scrollSpeed[0] * delta / 1000;
      Draggables._lastScrollPointer[1] += this.scrollSpeed[1] * delta / 1000;
      if (Draggables._lastScrollPointer[0] < 0)
        Draggables._lastScrollPointer[0] = 0;
      if (Draggables._lastScrollPointer[1] < 0)
        Draggables._lastScrollPointer[1] = 0;
      this.draw(Draggables._lastScrollPointer);
    }

    if(this.options.change) this.options.change(this);
  },

  _getWindowScroll: function(w) {
    var T, L, W, H;
    with (w.document) {
      if (w.document.documentElement && documentElement.scrollTop) {
        T = documentElement.scrollTop;
        L = documentElement.scrollLeft;
      } else if (w.document.body) {
        T = body.scrollTop;
        L = body.scrollLeft;
      }
      if (w.innerWidth) {
        W = w.innerWidth;
        H = w.innerHeight;
      } else if (w.document.documentElement && documentElement.clientWidth) {
        W = documentElement.clientWidth;
        H = documentElement.clientHeight;
      } else {
        W = body.offsetWidth;
        H = body.offsetHeight;
      }
    }
    return { top: T, left: L, width: W, height: H };
  }
});

Draggable._dragging = { };

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

var SortableObserver = Class.create({
  initialize: function(element, observer) {
    this.element   = $(element);
    this.observer  = observer;
    this.lastValue = Sortable.serialize(this.element);
  },

  onStart: function() {
    this.lastValue = Sortable.serialize(this.element);
  },

  onEnd: function() {
    Sortable.unmark();
    if(this.lastValue != Sortable.serialize(this.element))
      this.observer(this.element)
  }
});

var Sortable = {
  SERIALIZE_RULE: /^[^_\-](?:[A-Za-z0-9\-\_]*)[_](.*)$/,

  sortables: { },

  _findRootElement: function(element) {
    while (element.tagName.toUpperCase() != "BODY") {
      if(element.id && Sortable.sortables[element.id]) return element;
      element = element.parentNode;
    }
  },

  options: function(element) {
    element = Sortable._findRootElement($(element));
    if(!element) return;
    return Sortable.sortables[element.id];
  },

  destroy: function(element){
    element = $(element);
    var s = Sortable.sortables[element.id];

    if(s) {
      Draggables.removeObserver(s.element);
      s.droppables.each(function(d){ Droppables.remove(d) });
      s.draggables.invoke('destroy');

      delete Sortable.sortables[s.element.id];
    }
  },

  create: function(element) {
    element = $(element);
    var options = Object.extend({
      element:     element,
      tag:         'li',       // assumes li children, override with tag: 'tagname'
      dropOnEmpty: false,
      tree:        false,
      treeTag:     'ul',
      overlap:     'vertical', // one of 'vertical', 'horizontal'
      constraint:  'vertical', // one of 'vertical', 'horizontal', false
      containment: element,    // also takes array of elements (or id's); or false
      handle:      false,      // or a CSS class
      only:        false,
      delay:       0,
      hoverclass:  null,
      ghosting:    false,
      quiet:       false,
      scroll:      false,
      scrollSensitivity: 20,
      scrollSpeed: 15,
      format:      this.SERIALIZE_RULE,

      // these take arrays of elements or ids and can be
      // used for better initialization performance
      elements:    false,
      handles:     false,

      onChange:    Prototype.emptyFunction,
      onUpdate:    Prototype.emptyFunction
    }, arguments[1] || { });

    // clear any old sortable with same element
    this.destroy(element);

    // build options for the draggables
    var options_for_draggable = {
      revert:      true,
      quiet:       options.quiet,
      scroll:      options.scroll,
      scrollSpeed: options.scrollSpeed,
      scrollSensitivity: options.scrollSensitivity,
      delay:       options.delay,
      ghosting:    options.ghosting,
      constraint:  options.constraint,
      handle:      options.handle };

    if(options.starteffect)
      options_for_draggable.starteffect = options.starteffect;

    if(options.reverteffect)
      options_for_draggable.reverteffect = options.reverteffect;
    else
      if(options.ghosting) options_for_draggable.reverteffect = function(element) {
        element.style.top  = 0;
        element.style.left = 0;
      };

    if(options.endeffect)
      options_for_draggable.endeffect = options.endeffect;

    if(options.zindex)
      options_for_draggable.zindex = options.zindex;

    // build options for the droppables
    var options_for_droppable = {
      overlap:     options.overlap,
      containment: options.containment,
      tree:        options.tree,
      hoverclass:  options.hoverclass,
      onHover:     Sortable.onHover
    };

    var options_for_tree = {
      onHover:      Sortable.onEmptyHover,
      overlap:      options.overlap,
      containment:  options.containment,
      hoverclass:   options.hoverclass
    };

    // fix for gecko engine
    Element.cleanWhitespace(element);

    options.draggables = [];
    options.droppables = [];

    // drop on empty handling
    if(options.dropOnEmpty || options.tree) {
      Droppables.add(element, options_for_tree);
      options.droppables.push(element);
    }

    (options.elements || this.findElements(element, options) || []).each( function(e,i) {
      var handle = options.handles ? $(options.handles[i]) :
        (options.handle ? $(e).select('.' + options.handle)[0] : e);
      options.draggables.push(
        new Draggable(e, Object.extend(options_for_draggable, { handle: handle })));
      Droppables.add(e, options_for_droppable);
      if(options.tree) e.treeNode = element;
      options.droppables.push(e);
    });

    if(options.tree) {
      (Sortable.findTreeElements(element, options) || []).each( function(e) {
        Droppables.add(e, options_for_tree);
        e.treeNode = element;
        options.droppables.push(e);
      });
    }

    // keep reference
    this.sortables[element.id] = options;

    // for onupdate
    Draggables.addObserver(new SortableObserver(element, options.onUpdate));

  },

  // return all suitable-for-sortable elements in a guaranteed order
  findElements: function(element, options) {
    return Element.findChildren(
      element, options.only, options.tree ? true : false, options.tag);
  },

  findTreeElements: function(element, options) {
    return Element.findChildren(
      element, options.only, options.tree ? true : false, options.treeTag);
  },

  onHover: function(element, dropon, overlap) {
    if(Element.isParent(dropon, element)) return;

    if(overlap > .33 && overlap < .66 && Sortable.options(dropon).tree) {
      return;
    } else if(overlap>0.5) {
      Sortable.mark(dropon, 'before');
      if(dropon.previousSibling != element) {
        var oldParentNode = element.parentNode;
        element.style.visibility = "hidden"; // fix gecko rendering
        dropon.parentNode.insertBefore(element, dropon);
        if(dropon.parentNode!=oldParentNode)
          Sortable.options(oldParentNode).onChange(element);
        Sortable.options(dropon.parentNode).onChange(element);
      }
    } else {
      Sortable.mark(dropon, 'after');
      var nextElement = dropon.nextSibling || null;
      if(nextElement != element) {
        var oldParentNode = element.parentNode;
        element.style.visibility = "hidden"; // fix gecko rendering
        dropon.parentNode.insertBefore(element, nextElement);
        if(dropon.parentNode!=oldParentNode)
          Sortable.options(oldParentNode).onChange(element);
        Sortable.options(dropon.parentNode).onChange(element);
      }
    }
  },

  onEmptyHover: function(element, dropon, overlap) {
    var oldParentNode = element.parentNode;
    var droponOptions = Sortable.options(dropon);

    if(!Element.isParent(dropon, element)) {
      var index;

      var children = Sortable.findElements(dropon, {tag: droponOptions.tag, only: droponOptions.only});
      var child = null;

      if(children) {
        var offset = Element.offsetSize(dropon, droponOptions.overlap) * (1.0 - overlap);

        for (index = 0; index < children.length; index += 1) {
          if (offset - Element.offsetSize (children[index], droponOptions.overlap) >= 0) {
            offset -= Element.offsetSize (children[index], droponOptions.overlap);
          } else if (offset - (Element.offsetSize (children[index], droponOptions.overlap) / 2) >= 0) {
            child = index + 1 < children.length ? children[index + 1] : null;
            break;
          } else {
            child = children[index];
            break;
          }
        }
      }

      dropon.insertBefore(element, child);

      Sortable.options(oldParentNode).onChange(element);
      droponOptions.onChange(element);
    }
  },

  unmark: function() {
    if(Sortable._marker) Sortable._marker.hide();
  },

  mark: function(dropon, position) {
    // mark on ghosting only
    var sortable = Sortable.options(dropon.parentNode);
    if(sortable && !sortable.ghosting) return;

    if(!Sortable._marker) {
      Sortable._marker =
        ($('dropmarker') || Element.extend(document.createElement('DIV'))).
          hide().addClassName('dropmarker').setStyle({position:'absolute'});
      document.getElementsByTagName("body").item(0).appendChild(Sortable._marker);
    }
    var offsets = Position.cumulativeOffset(dropon);
    Sortable._marker.setStyle({left: offsets[0]+'px', top: offsets[1] + 'px'});

    if(position=='after')
      if(sortable.overlap == 'horizontal')
        Sortable._marker.setStyle({left: (offsets[0]+dropon.clientWidth) + 'px'});
      else
        Sortable._marker.setStyle({top: (offsets[1]+dropon.clientHeight) + 'px'});

    Sortable._marker.show();
  },

  _tree: function(element, options, parent) {
    var children = Sortable.findElements(element, options) || [];

    for (var i = 0; i < children.length; ++i) {
      var match = children[i].id.match(options.format);

      if (!match) continue;

      var child = {
        id: encodeURIComponent(match ? match[1] : null),
        element: element,
        parent: parent,
        children: [],
        position: parent.children.length,
        container: $(children[i]).down(options.treeTag)
      };

      /* Get the element containing the children and recurse over it */
      if (child.container)
        this._tree(child.container, options, child);

      parent.children.push (child);
    }

    return parent;
  },

  tree: function(element) {
    element = $(element);
    var sortableOptions = this.options(element);
    var options = Object.extend({
      tag: sortableOptions.tag,
      treeTag: sortableOptions.treeTag,
      only: sortableOptions.only,
      name: element.id,
      format: sortableOptions.format
    }, arguments[1] || { });

    var root = {
      id: null,
      parent: null,
      children: [],
      container: element,
      position: 0
    };

    return Sortable._tree(element, options, root);
  },

  /* Construct a [i] index for a particular node */
  _constructIndex: function(node) {
    var index = '';
    do {
      if (node.id) index = '[' + node.position + ']' + index;
    } while ((node = node.parent) != null);
    return index;
  },

  sequence: function(element) {
    element = $(element);
    var options = Object.extend(this.options(element), arguments[1] || { });

    return $(this.findElements(element, options) || []).map( function(item) {
      return item.id.match(options.format) ? item.id.match(options.format)[1] : '';
    });
  },

  setSequence: function(element, new_sequence) {
    element = $(element);
    var options = Object.extend(this.options(element), arguments[2] || { });

    var nodeMap = { };
    this.findElements(element, options).each( function(n) {
        if (n.id.match(options.format))
            nodeMap[n.id.match(options.format)[1]] = [n, n.parentNode];
        n.parentNode.removeChild(n);
    });

    new_sequence.each(function(ident) {
      var n = nodeMap[ident];
      if (n) {
        n[1].appendChild(n[0]);
        delete nodeMap[ident];
      }
    });
  },

  serialize: function(element) {
    element = $(element);
    var options = Object.extend(Sortable.options(element), arguments[1] || { });
    var name = encodeURIComponent(
      (arguments[1] && arguments[1].name) ? arguments[1].name : element.id);

    if (options.tree) {
      return Sortable.tree(element, arguments[1]).children.map( function (item) {
        return [name + Sortable._constructIndex(item) + "[id]=" +
                encodeURIComponent(item.id)].concat(item.children.map(arguments.callee));
      }).flatten().join('&');
    } else {
      return Sortable.sequence(element, arguments[1]).map( function(item) {
        return name + "[]=" + encodeURIComponent(item);
      }).join('&');
    }
  }
};

// Returns true if child is contained within element
Element.isParent = function(child, element) {
  if (!child.parentNode || child == element) return false;
  if (child.parentNode == element) return true;
  return Element.isParent(child.parentNode, element);
};

Element.findChildren = function(element, only, recursive, tagName) {
  if(!element.hasChildNodes()) return null;
  tagName = tagName.toUpperCase();
  if(only) only = [only].flatten();
  var elements = [];
  $A(element.childNodes).each( function(e) {
    if(e.tagName && e.tagName.toUpperCase()==tagName &&
      (!only || (Element.classNames(e).detect(function(v) { return only.include(v) }))))
        elements.push(e);
    if(recursive) {
      var grandchildren = Element.findChildren(e, only, recursive, tagName);
      if(grandchildren) elements.push(grandchildren);
    }
  });

  return (elements.length>0 ? elements.flatten() : []);
};

Element.offsetSize = function (element, type) {
  return element['offset' + ((type=='vertical' || type=='height') ? 'Height' : 'Width')];
};


/* /frameworks/scriptaculous/slider.js */

 // script.aculo.us slider.js v1.8.2, Tue Nov 18 18:30:58 +0100 2008

// Copyright (c) 2005-2008 Marty Haught, Thomas Fuchs
//
// 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/

if (!Control) var Control = { };

// options:
//  axis: 'vertical', or 'horizontal' (default)
//
// callbacks:
//  onChange(value)
//  onSlide(value)
Control.Slider = Class.create({
  initialize: function(handle, track, options) {
    var slider = this;

    if (Object.isArray(handle)) {
      this.handles = handle.collect( function(e) { return $(e) });
    } else {
      this.handles = [$(handle)];
    }

    this.track   = $(track);
    this.options = options || { };

    this.axis      = this.options.axis || 'horizontal';
    this.increment = this.options.increment || 1;
    this.step      = parseInt(this.options.step || '1');
    this.range     = this.options.range || $R(0,1);

    this.value     = 0; // assure backwards compat
    this.values    = this.handles.map( function() { return 0 });
    this.spans     = this.options.spans ? this.options.spans.map(function(s){ return $(s) }) : false;
    this.options.startSpan = $(this.options.startSpan || null);
    this.options.endSpan   = $(this.options.endSpan || null);

    this.restricted = this.options.restricted || false;

    this.maximum   = this.options.maximum || this.range.end;
    this.minimum   = this.options.minimum || this.range.start;

    // Will be used to align the handle onto the track, if necessary
    this.alignX = parseInt(this.options.alignX || '0');
    this.alignY = parseInt(this.options.alignY || '0');

    this.trackLength = this.maximumOffset() - this.minimumOffset();

    this.handleLength = this.isVertical() ?
      (this.handles[0].offsetHeight != 0 ?
        this.handles[0].offsetHeight : this.handles[0].style.height.replace(/px$/,"")) :
      (this.handles[0].offsetWidth != 0 ? this.handles[0].offsetWidth :
        this.handles[0].style.width.replace(/px$/,""));

    this.active   = false;
    this.dragging = false;
    this.disabled = false;

    if (this.options.disabled) this.setDisabled();

    // Allowed values array
    this.allowedValues = this.options.values ? this.options.values.sortBy(Prototype.K) : false;
    if (this.allowedValues) {
      this.minimum = this.allowedValues.min();
      this.maximum = this.allowedValues.max();
    }

    this.eventMouseDown = this.startDrag.bindAsEventListener(this);
    this.eventMouseUp   = this.endDrag.bindAsEventListener(this);
    this.eventMouseMove = this.update.bindAsEventListener(this);

    // Initialize handles in reverse (make sure first handle is active)
    this.handles.each( function(h,i) {
      i = slider.handles.length-1-i;
      slider.setValue(parseFloat(
        (Object.isArray(slider.options.sliderValue) ?
          slider.options.sliderValue[i] : slider.options.sliderValue) ||
         slider.range.start), i);
      h.makePositioned().observe("mousedown", slider.eventMouseDown);
    });

    this.track.observe("mousedown", this.eventMouseDown);
    document.observe("mouseup", this.eventMouseUp);
    document.observe("mousemove", this.eventMouseMove);

    this.initialized = true;
  },
  dispose: function() {
    var slider = this;
    Event.stopObserving(this.track, "mousedown", this.eventMouseDown);
    Event.stopObserving(document, "mouseup", this.eventMouseUp);
    Event.stopObserving(document, "mousemove", this.eventMouseMove);
    this.handles.each( function(h) {
      Event.stopObserving(h, "mousedown", slider.eventMouseDown);
    });
  },
  setDisabled: function(){
    this.disabled = true;
  },
  setEnabled: function(){
    this.disabled = false;
  },
  getNearestValue: function(value){
    if (this.allowedValues){
      if (value >= this.allowedValues.max()) return(this.allowedValues.max());
      if (value <= this.allowedValues.min()) return(this.allowedValues.min());

      var offset = Math.abs(this.allowedValues[0] - value);
      var newValue = this.allowedValues[0];
      this.allowedValues.each( function(v) {
        var currentOffset = Math.abs(v - value);
        if (currentOffset <= offset){
          newValue = v;
          offset = currentOffset;
        }
      });
      return newValue;
    }
    if (value > this.range.end) return this.range.end;
    if (value < this.range.start) return this.range.start;
    return value;
  },
  setValue: function(sliderValue, handleIdx){
    if (!this.active) {
      this.activeHandleIdx = handleIdx || 0;
      this.activeHandle    = this.handles[this.activeHandleIdx];
      this.updateStyles();
    }
    handleIdx = handleIdx || this.activeHandleIdx || 0;
    if (this.initialized && this.restricted) {
      if ((handleIdx>0) && (sliderValue<this.values[handleIdx-1]))
        sliderValue = this.values[handleIdx-1];
      if ((handleIdx < (this.handles.length-1)) && (sliderValue>this.values[handleIdx+1]))
        sliderValue = this.values[handleIdx+1];
    }
    sliderValue = this.getNearestValue(sliderValue);
    this.values[handleIdx] = sliderValue;
    this.value = this.values[0]; // assure backwards compat

    this.handles[handleIdx].style[this.isVertical() ? 'top' : 'left'] =
      this.translateToPx(sliderValue);

    this.drawSpans();
    if (!this.dragging || !this.event) this.updateFinished();
  },
  setValueBy: function(delta, handleIdx) {
    this.setValue(this.values[handleIdx || this.activeHandleIdx || 0] + delta,
      handleIdx || this.activeHandleIdx || 0);
  },
  translateToPx: function(value) {
    return Math.round(
      ((this.trackLength-this.handleLength)/(this.range.end-this.range.start)) *
      (value - this.range.start)) + "px";
  },
  translateToValue: function(offset) {
    return ((offset/(this.trackLength-this.handleLength) *
      (this.range.end-this.range.start)) + this.range.start);
  },
  getRange: function(range) {
    var v = this.values.sortBy(Prototype.K);
    range = range || 0;
    return $R(v[range],v[range+1]);
  },
  minimumOffset: function(){
    return(this.isVertical() ? this.alignY : this.alignX);
  },
  maximumOffset: function(){
    return(this.isVertical() ?
      (this.track.offsetHeight != 0 ? this.track.offsetHeight :
        this.track.style.height.replace(/px$/,"")) - this.alignY :
      (this.track.offsetWidth != 0 ? this.track.offsetWidth :
        this.track.style.width.replace(/px$/,"")) - this.alignX);
  },
  isVertical:  function(){
    return (this.axis == 'vertical');
  },
  drawSpans: function() {
    var slider = this;
    if (this.spans)
      $R(0, this.spans.length-1).each(function(r) { slider.setSpan(slider.spans[r], slider.getRange(r)) });
    if (this.options.startSpan)
      this.setSpan(this.options.startSpan,
        $R(0, this.values.length>1 ? this.getRange(0).min() : this.value ));
    if (this.options.endSpan)
      this.setSpan(this.options.endSpan,
        $R(this.values.length>1 ? this.getRange(this.spans.length-1).max() : this.value, this.maximum));
  },
  setSpan: function(span, range) {
    if (this.isVertical()) {
      span.style.top = this.translateToPx(range.start);
      span.style.height = this.translateToPx(range.end - range.start + this.range.start);
    } else {
      span.style.left = this.translateToPx(range.start);
      span.style.width = this.translateToPx(range.end - range.start + this.range.start);
    }
  },
  updateStyles: function() {
    this.handles.each( function(h){ Element.removeClassName(h, 'selected') });
    Element.addClassName(this.activeHandle, 'selected');
  },
  startDrag: function(event) {
    if (Event.isLeftClick(event)) {
      if (!this.disabled){
        this.active = true;

        var handle = Event.element(event);
        var pointer  = [Event.pointerX(event), Event.pointerY(event)];
        var track = handle;
        if (track==this.track) {
          var offsets  = Position.cumulativeOffset(this.track);
          this.event = event;
          this.setValue(this.translateToValue(
           (this.isVertical() ? pointer[1]-offsets[1] : pointer[0]-offsets[0])-(this.handleLength/2)
          ));
          var offsets  = Position.cumulativeOffset(this.activeHandle);
          this.offsetX = (pointer[0] - offsets[0]);
          this.offsetY = (pointer[1] - offsets[1]);
        } else {
          // find the handle (prevents issues with Safari)
          while((this.handles.indexOf(handle) == -1) && handle.parentNode)
            handle = handle.parentNode;

          if (this.handles.indexOf(handle)!=-1) {
            this.activeHandle    = handle;
            this.activeHandleIdx = this.handles.indexOf(this.activeHandle);
            this.updateStyles();

            var offsets  = Position.cumulativeOffset(this.activeHandle);
            this.offsetX = (pointer[0] - offsets[0]);
            this.offsetY = (pointer[1] - offsets[1]);
          }
        }
      }
      Event.stop(event);
    }
  },
  update: function(event) {
   if (this.active) {
      if (!this.dragging) this.dragging = true;
      this.draw(event);
      if (Prototype.Browser.WebKit) window.scrollBy(0,0);
      Event.stop(event);
   }
  },
  draw: function(event) {
    var pointer = [Event.pointerX(event), Event.pointerY(event)];
    var offsets = Position.cumulativeOffset(this.track);
    pointer[0] -= this.offsetX + offsets[0];
    pointer[1] -= this.offsetY + offsets[1];
    this.event = event;
    this.setValue(this.translateToValue( this.isVertical() ? pointer[1] : pointer[0] ));
    if (this.initialized && this.options.onSlide)
      this.options.onSlide(this.values.length>1 ? this.values : this.value, this);
  },
  endDrag: function(event) {
    if (this.active && this.dragging) {
      this.finishDrag(event, true);
      Event.stop(event);
    }
    this.active = false;
    this.dragging = false;
  },
  finishDrag: function(event, success) {
    this.active = false;
    this.dragging = false;
    this.updateFinished();
  },
  updateFinished: function() {
    if (this.initialized && this.options.onChange)
      this.options.onChange(this.values.length>1 ? this.values : this.value, this);
    this.event = null;
  }
});


/* /frameworks/swfobject.js */

 /**
 * SWFObject v1.5: Flash Player detection and embed - http://blog.deconcept.com/swfobject/
 *
 * SWFObject is (c) 2007 Geoff Stearns and is released under the MIT License:
 * http://www.opensource.org/licenses/mit-license.php
 *
 */
if(typeof deconcept=="undefined"){var deconcept=new Object();}if(typeof deconcept.util=="undefined"){deconcept.util=new Object();}if(typeof deconcept.SWFObjectUtil=="undefined"){deconcept.SWFObjectUtil=new Object();}deconcept.SWFObject=function(_1,id,w,h,_5,c,_7,_8,_9,_a){if(!document.getElementById){return;}this.DETECT_KEY=_a?_a:"detectflash";this.skipDetect=deconcept.util.getRequestParameter(this.DETECT_KEY);this.params=new Object();this.variables=new Object();this.attributes=new Array();if(_1){this.setAttribute("swf",_1);}if(id){this.setAttribute("id",id);}if(w){this.setAttribute("width",w);}if(h){this.setAttribute("height",h);}if(_5){this.setAttribute("version",new deconcept.PlayerVersion(_5.toString().split(".")));}this.installedVer=deconcept.SWFObjectUtil.getPlayerVersion();if(!window.opera&&document.all&&this.installedVer.major>7){deconcept.SWFObject.doPrepUnload=true;}if(c){this.addParam("bgcolor",c);}var q=_7?_7:"high";this.addParam("quality",q);this.setAttribute("useExpressInstall",false);this.setAttribute("doExpressInstall",false);var _c=(_8)?_8:window.location;this.setAttribute("xiRedirectUrl",_c);this.setAttribute("redirectUrl","");if(_9){this.setAttribute("redirectUrl",_9);}};deconcept.SWFObject.prototype={useExpressInstall:function(_d){this.xiSWFPath=!_d?"expressinstall.swf":_d;this.setAttribute("useExpressInstall",true);},setAttribute:function(_e,_f){this.attributes[_e]=_f;},getAttribute:function(_10){return this.attributes[_10];},addParam:function(_11,_12){this.params[_11]=_12;},getParams:function(){return this.params;},addVariable:function(_13,_14){this.variables[_13]=_14;},getVariable:function(_15){return this.variables[_15];},getVariables:function(){return this.variables;},getVariablePairs:function(){var _16=new Array();var key;var _18=this.getVariables();for(key in _18){_16[_16.length]=key+"="+_18[key];}return _16;},getSWFHTML:function(){var _19="";if(navigator.plugins&&navigator.mimeTypes&&navigator.mimeTypes.length){if(this.getAttribute("doExpressInstall")){this.addVariable("MMplayerType","PlugIn");this.setAttribute("swf",this.xiSWFPath);}_19="<embed type=\"application/x-shockwave-flash\" src=\""+this.getAttribute("swf")+"\" width=\""+this.getAttribute("width")+"\" height=\""+this.getAttribute("height")+"\" style=\""+this.getAttribute("style")+"\"";_19+=" id=\""+this.getAttribute("id")+"\" name=\""+this.getAttribute("id")+"\" ";var _1a=this.getParams();for(var key in _1a){_19+=[key]+"=\""+_1a[key]+"\" ";}var _1c=this.getVariablePairs().join("&");if(_1c.length>0){_19+="flashvars=\""+_1c+"\"";}_19+="/>";}else{if(this.getAttribute("doExpressInstall")){this.addVariable("MMplayerType","ActiveX");this.setAttribute("swf",this.xiSWFPath);}_19="<object id=\""+this.getAttribute("id")+"\" classid=\"clsid:D27CDB6E-AE6D-11cf-96B8-444553540000\" width=\""+this.getAttribute("width")+"\" height=\""+this.getAttribute("height")+"\" style=\""+this.getAttribute("style")+"\">";_19+="<param name=\"movie\" value=\""+this.getAttribute("swf")+"\" />";var _1d=this.getParams();for(var key in _1d){_19+="<param name=\""+key+"\" value=\""+_1d[key]+"\" />";}var _1f=this.getVariablePairs().join("&");if(_1f.length>0){_19+="<param name=\"flashvars\" value=\""+_1f+"\" />";}_19+="</object>";}return _19;},write:function(_20){if(this.getAttribute("useExpressInstall")){var _21=new deconcept.PlayerVersion([6,0,65]);if(this.installedVer.versionIsValid(_21)&&!this.installedVer.versionIsValid(this.getAttribute("version"))){this.setAttribute("doExpressInstall",true);this.addVariable("MMredirectURL",escape(this.getAttribute("xiRedirectUrl")));document.title=document.title.slice(0,47)+" - Flash Player Installation";this.addVariable("MMdoctitle",document.title);}}if(this.skipDetect||this.getAttribute("doExpressInstall")||this.installedVer.versionIsValid(this.getAttribute("version"))){var n=(typeof _20=="string")?document.getElementById(_20):_20;n.innerHTML=this.getSWFHTML();return true;}else{if(this.getAttribute("redirectUrl")!=""){document.location.replace(this.getAttribute("redirectUrl"));}}return false;}};deconcept.SWFObjectUtil.getPlayerVersion=function(){var _23=new deconcept.PlayerVersion([0,0,0]);if(navigator.plugins&&navigator.mimeTypes.length){var x=navigator.plugins["Shockwave Flash"];if(x&&x.description){_23=new deconcept.PlayerVersion(x.description.replace(/([a-zA-Z]|\s)+/,"").replace(/(\s+r|\s+b[0-9]+)/,".").split("."));}}else{if(navigator.userAgent&&navigator.userAgent.indexOf("Windows CE")>=0){var axo=1;var _26=3;while(axo){try{_26++;axo=new ActiveXObject("ShockwaveFlash.ShockwaveFlash."+_26);_23=new deconcept.PlayerVersion([_26,0,0]);}catch(e){axo=null;}}}else{try{var axo=new ActiveXObject("ShockwaveFlash.ShockwaveFlash.7");}catch(e){try{var axo=new ActiveXObject("ShockwaveFlash.ShockwaveFlash.6");_23=new deconcept.PlayerVersion([6,0,21]);axo.AllowScriptAccess="always";}catch(e){if(_23.major==6){return _23;}}try{axo=new ActiveXObject("ShockwaveFlash.ShockwaveFlash");}catch(e){}}if(axo!=null){_23=new deconcept.PlayerVersion(axo.GetVariable("$version").split(" ")[1].split(","));}}}return _23;};deconcept.PlayerVersion=function(_29){this.major=_29[0]!=null?parseInt(_29[0]):0;this.minor=_29[1]!=null?parseInt(_29[1]):0;this.rev=_29[2]!=null?parseInt(_29[2]):0;};deconcept.PlayerVersion.prototype.versionIsValid=function(fv){if(this.major<fv.major){return false;}if(this.major>fv.major){return true;}if(this.minor<fv.minor){return false;}if(this.minor>fv.minor){return true;}if(this.rev<fv.rev){return false;}return true;};deconcept.util={getRequestParameter:function(_2b){var q=document.location.search||document.location.hash;if(_2b==null){return q;}if(q){var _2d=q.substring(1).split("&");for(var i=0;i<_2d.length;i++){if(_2d[i].substring(0,_2d[i].indexOf("="))==_2b){return _2d[i].substring((_2d[i].indexOf("=")+1));}}}return "";}};deconcept.SWFObjectUtil.cleanupSWFs=function(){var _2f=document.getElementsByTagName("OBJECT");for(var i=_2f.length-1;i>=0;i--){_2f[i].style.display="none";for(var x in _2f[i]){if(typeof _2f[i][x]=="function"){_2f[i][x]=function(){};}}}};if(deconcept.SWFObject.doPrepUnload){if(!deconcept.unloadSet){deconcept.SWFObjectUtil.prepUnload=function(){__flash_unloadHandler=function(){};__flash_savedUnloadHandler=function(){};window.attachEvent("onunload",deconcept.SWFObjectUtil.cleanupSWFs);};window.attachEvent("onbeforeunload",deconcept.SWFObjectUtil.prepUnload);deconcept.unloadSet=true;}}if(!document.getElementById&&document.all){document.getElementById=function(id){return document.all[id];};}var getQueryParamValue=deconcept.util.getRequestParameter;var FlashObject=deconcept.SWFObject;var SWFObject=deconcept.SWFObject;


/* /frameworks/sha1.js */

 /*
 * A JavaScript implementation of the Secure Hash Algorithm, SHA-1, as defined
 * in FIPS PUB 180-1
 * Version 2.1a Copyright Paul Johnston 2000 - 2002.
 * Other contributors: Greg Holt, Andrew Kepert, Ydnar, Lostinet
 * Distributed under the BSD License
 * See http://pajhome.org.uk/crypt/md5 for details.
 */

/*
 * Configurable variables. You may need to tweak these to be compatible with
 * the server-side, but the defaults work in most cases.
 */
var hexcase = 0;  /* hex output format. 0 - lowercase; 1 - uppercase        */
var b64pad  = ""; /* base-64 pad character. "=" for strict RFC compliance   */
var chrsz   = 8;  /* bits per input character. 8 - ASCII; 16 - Unicode      */

/*
 * These are the functions you'll usually want to call
 * They take string arguments and return either hex or base-64 encoded strings
 */
function hex_sha1(s){return binb2hex(core_sha1(str2binb(s),s.length * chrsz));}
function b64_sha1(s){return binb2b64(core_sha1(str2binb(s),s.length * chrsz));}
function str_sha1(s){return binb2str(core_sha1(str2binb(s),s.length * chrsz));}
function hex_hmac_sha1(key, data){ return binb2hex(core_hmac_sha1(key, data));}
function b64_hmac_sha1(key, data){ return binb2b64(core_hmac_sha1(key, data));}
function str_hmac_sha1(key, data){ return binb2str(core_hmac_sha1(key, data));}

/*
 * Perform a simple self-test to see if the VM is working
 */
function sha1_vm_test()
{
  return hex_sha1("abc") == "a9993e364706816aba3e25717850c26c9cd0d89d";
}

/*
 * Calculate the SHA-1 of an array of big-endian words, and a bit length
 */
function core_sha1(x, len)
{
  /* append padding */
  x[len >> 5] |= 0x80 << (24 - len % 32);
  x[((len + 64 >> 9) << 4) + 15] = len;

  var w = Array(80);
  var a =  1732584193;
  var b = -271733879;
  var c = -1732584194;
  var d =  271733878;
  var e = -1009589776;

  for(var i = 0; i < x.length; i += 16)
  {
    var olda = a;
    var oldb = b;
    var oldc = c;
    var oldd = d;
    var olde = e;

    for(var j = 0; j < 80; j++)
    {
      if(j < 16) w[j] = x[i + j];
      else w[j] = rol(w[j-3] ^ w[j-8] ^ w[j-14] ^ w[j-16], 1);
      var t = safe_add(safe_add(rol(a, 5), sha1_ft(j, b, c, d)),
                       safe_add(safe_add(e, w[j]), sha1_kt(j)));
      e = d;
      d = c;
      c = rol(b, 30);
      b = a;
      a = t;
    }

    a = safe_add(a, olda);
    b = safe_add(b, oldb);
    c = safe_add(c, oldc);
    d = safe_add(d, oldd);
    e = safe_add(e, olde);
  }
  return Array(a, b, c, d, e);

}

/*
 * Perform the appropriate triplet combination function for the current
 * iteration
 */
function sha1_ft(t, b, c, d)
{
  if(t < 20) return (b & c) | ((~b) & d);
  if(t < 40) return b ^ c ^ d;
  if(t < 60) return (b & c) | (b & d) | (c & d);
  return b ^ c ^ d;
}

/*
 * Determine the appropriate additive constant for the current iteration
 */
function sha1_kt(t)
{
  return (t < 20) ?  1518500249 : (t < 40) ?  1859775393 :
         (t < 60) ? -1894007588 : -899497514;
}

/*
 * Calculate the HMAC-SHA1 of a key and some data
 */
function core_hmac_sha1(key, data)
{
  var bkey = str2binb(key);
  if(bkey.length > 16) bkey = core_sha1(bkey, key.length * chrsz);

  var ipad = Array(16), opad = Array(16);
  for(var i = 0; i < 16; i++)
  {
    ipad[i] = bkey[i] ^ 0x36363636;
    opad[i] = bkey[i] ^ 0x5C5C5C5C;
  }

  var hash = core_sha1(ipad.concat(str2binb(data)), 512 + data.length * chrsz);
  return core_sha1(opad.concat(hash), 512 + 160);
}

/*
 * Add integers, wrapping at 2^32. This uses 16-bit operations internally
 * to work around bugs in some JS interpreters.
 */
function safe_add(x, y)
{
  var lsw = (x & 0xFFFF) + (y & 0xFFFF);
  var msw = (x >> 16) + (y >> 16) + (lsw >> 16);
  return (msw << 16) | (lsw & 0xFFFF);
}

/*
 * Bitwise rotate a 32-bit number to the left.
 */
function rol(num, cnt)
{
  return (num << cnt) | (num >>> (32 - cnt));
}

/*
 * Convert an 8-bit or 16-bit string to an array of big-endian words
 * In 8-bit function, characters >255 have their hi-byte silently ignored.
 */
function str2binb(str)
{
  var bin = Array();
  var mask = (1 << chrsz) - 1;
  for(var i = 0; i < str.length * chrsz; i += chrsz)
    bin[i>>5] |= (str.charCodeAt(i / chrsz) & mask) << (32 - chrsz - i%32);
  return bin;
}

/*
 * Convert an array of big-endian words to a string
 */
function binb2str(bin)
{
  var str = "";
  var mask = (1 << chrsz) - 1;
  for(var i = 0; i < bin.length * 32; i += chrsz)
    str += String.fromCharCode((bin[i>>5] >>> (32 - chrsz - i%32)) & mask);
  return str;
}

/*
 * Convert an array of big-endian words to a hex string.
 */
function binb2hex(binarray)
{
  var hex_tab = hexcase ? "0123456789ABCDEF" : "0123456789abcdef";
  var str = "";
  for(var i = 0; i < binarray.length * 4; i++)
  {
    str += hex_tab.charAt((binarray[i>>2] >> ((3 - i%4)*8+4)) & 0xF) +
           hex_tab.charAt((binarray[i>>2] >> ((3 - i%4)*8  )) & 0xF);
  }
  return str;
}

/*
 * Convert an array of big-endian words to a base-64 string
 */
function binb2b64(binarray)
{
  var tab = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
  var str = "";
  for(var i = 0; i < binarray.length * 4; i += 3)
  {
    var triplet = (((binarray[i   >> 2] >> 8 * (3 -  i   %4)) & 0xFF) << 16)
                | (((binarray[i+1 >> 2] >> 8 * (3 - (i+1)%4)) & 0xFF) << 8 )
                |  ((binarray[i+2 >> 2] >> 8 * (3 - (i+2)%4)) & 0xFF);
    for(var j = 0; j < 4; j++)
    {
      if(i * 8 + j * 6 > binarray.length * 32) str += b64pad;
      else str += tab.charAt((triplet >> 6*(3-j)) & 0x3F);
    }
  }
  return str;
}



/* /frameworks/nofirebug.js */

 if ( typeof console === "undefined" ) {
  console = { log: function() {} };
};



/* /frameworks/prototype.wz_tooltip.js */

 Element.addMethods({
   wzTooltip: function(element,contentArgument){
    var options = {
      balloon: true,
      above: true,
      centerMouse: true,
      offsetX: 0,
      padding: 20,
      width: 400,
      fontColor: '#848482'
     };

    Event.observe(element,'mouseover',function() {
      config.Balloon = options.balloon;
      config.Above = options.above;
      config.CenterMouse = options.centerMouse;
      config.OffsetX = options.offsetX;
      config.Padding	= options.padding;
      config.Width = options.width;
      config.FontColor = options.fontColor;
      Tip.apply(element, [contentArgument]);
    });

    Event.observe(element,'mouseout',function() {
      UnTip();
    });
     return element;
   }
});


/* /ad_serving/offermatica_mbox.js */

 var mboxCopyright = "&copy; 2004-2007 Offermatica &trade; Corporation";mboxUrlBuilder = function(a, b) { this.a = a; this.b = b; this.c = new Array(); this.d = function(e) { return e; }; this.f = null;};mboxUrlBuilder.prototype.addParameter = function(g, h) { var i = new RegExp('(\'|")'); if (i.exec(g)) { throw "Parameter '" + g + "' contains invalid characters"; } for (var j = 0; j < this.c.length; j++) { var k = this.c[j]; if (k.name == g) { k.value = h; return this; } } var l = new Object(); l.name = g; l.value = h; this.c[this.c.length] = l; return this;};mboxUrlBuilder.prototype.addParameters = function(c) { if (!c) { return this; } for (var j = 0; j < c.length; j++) { var m = c[j].indexOf('='); if (m == -1 || m == 0) { continue; } this.addParameter(c[j].substring(0, m), c[j].substring(m + 1, c[j].length)); } return this;};mboxUrlBuilder.prototype.setServerType = function(n) { this.o = n;};mboxUrlBuilder.prototype.setBasePath = function(f) { this.f = f;};mboxUrlBuilder.prototype.setUrlProcessAction = function(p) { this.d = p;};mboxUrlBuilder.prototype.buildUrl = function() { var q = this.f ? this.f : '/m2/' + this.b + '/mbox/' + this.o; var r = document.location.protocol == 'file:' ? 'http:' : document.location.protocol; var e = r + "//" + this.a + q; var s = e.indexOf('?') != -1 ? '&' : '?'; for (var j = 0; j < this.c.length; j++) { var k = this.c[j]; e += s + k.name + '=' + encodeURIComponent(k.value); s = '&'; } return this.t(this.d(e));};mboxUrlBuilder.prototype.getParameters = function() { return this.c;};mboxUrlBuilder.prototype.setParameters = function(c) { this.c = c;};mboxUrlBuilder.prototype.clone = function() { var u = new mboxUrlBuilder(this.a, this.b); u.setServerType(this.o); u.setBasePath(this.f); u.setUrlProcessAction(this.d); for (var j = 0; j < this.c.length; j++) { u.addParameter(this.c[j].name, this.c[j].value); } return u;};mboxUrlBuilder.prototype.t = function(v) { return v.replace(/\"/g, '&quot;').replace(/>/g, '&gt;');};mboxStandardFetcher = function() { };mboxStandardFetcher.prototype.getType = function() { return 'standard';};mboxStandardFetcher.prototype.fetch = function(w) { w.setServerType(this.getType()); document.write('<' + 'scr' + 'ipt src="' + w.buildUrl() + '" language="JavaScript"><' + '\/scr' + 'ipt>');};mboxStandardFetcher.prototype.cancel = function() {};mboxAjaxFetcher = function() { };mboxAjaxFetcher.prototype.getType = function() { return 'ajax';};mboxAjaxFetcher.prototype.fetch = function(w) { w.setServerType(this.getType()); var e = w.buildUrl(); this.x = document.createElement('script'); this.x.src = e; document.body.appendChild(this.x);};mboxAjaxFetcher.prototype.cancel = function() { };mboxFactory = function(y, b) { this.z = new mboxList(); this.A = typeof document.createElement('div').replaceChild != 'undefined' && (function() { return true; })() && typeof document.getElementById != 'undefined' && typeof (window.attachEvent || document.addEventListener || window.addEventListener) != 'undefined' && typeof encodeURIComponent != 'undefined'; this.B = this.A; if (mboxGetPageParameter('mboxDisable') != null) { this.B = false; } if (!mboxCookies.isEnabled()) { this.B = false; } if (mboxCookies.getCookie('disable') == 'true') { this.B = false; } if (this.isAdmin()) { this.enable(); } this.C = mboxGenerateId(); this.D = new mboxSession(this.C, 'mboxSession', 'session', 31 * 60); this.E = new mboxPC(this.C, 'PC', 63072000); this.w = new mboxUrlBuilder(y, b); this.F(); this.G = new Date().getTime(); this.H = this.G; var I = this; this.addOnLoad(function() { I.H = new Date().getTime(); }); if (this.A) { this.addOnLoad(function() { I.getMboxes().each(function(J) { J.finalize(); }); }); this.limitTraffic(100, 10368000); if (this.B) { this.K(); this.L = new mboxSignaler(function(M, c) { return I.create(M, c); }); } }};mboxFactory.prototype.isEnabled = function() { return this.B;};mboxFactory.prototype.isSupported = function() { return this.A;};mboxFactory.prototype.disable = function(N) { if (typeof duration == 'undefined') { N = 60 * 60; } if (!this.isAdmin()) { this.B = false; mboxCookies.setCookie('disable', 'true', N); }};mboxFactory.prototype.enable = function() { this.B = true; mboxCookies.deleteCookie('disable');};mboxFactory.prototype.isAdmin = function() { return document.location.href.indexOf( 'mboxEnv') != -1;};mboxFactory.prototype.limitTraffic = function(O, N) {};mboxFactory.prototype.addOnLoad = function(p) { if (window.addEventListener) { window.addEventListener('load', p, false); } else if (document.addEventListener) { document.addEventListener('load', p, false); } else if (document.attachEvent) { window.attachEvent('onload', p); }};mboxFactory.prototype.getEllapsedTime = function() { return this.H - this.G;};mboxFactory.prototype.getEllapsedTimeUntil = function(P) { return P - this.G;};mboxFactory.prototype.getMboxes = function() { return this.z;};mboxFactory.prototype.get = function(M, Q) { return this.z.get(M).getById(Q || 0);};mboxFactory.prototype.update = function(M, c) { if (!this.isEnabled()) { return; } if (this.z.get(M).length() == 0) { throw "Mbox " + M + " is not defined"; } this.z.get(M).each(function(J) { J.setFetcher(new mboxAjaxFetcher()); J.getUrlBuilder() .addParameter('mboxRandomRequestId', mboxGenerateId()); J.load(c); });};mboxFactory.prototype.create = function(M, c) { if (!this.isSupported()) { return null; } var e = this.w.clone(); e.addParameter("mboxCount", this.z.length() + 1); e.addParameters(c); try { var I = this; var R = new mbox(M, this.z.get(M).length(), e); R.setOnError(function(S, n) { R.setMessage(S); if (n == 'standard') { R.activate(); if (!R.isActivated()) { I.disable(); window.location.reload(false); } } else { throw S; } }); R.setFetcher(new mboxStandardFetcher()); this.z.add(R); } catch (T) { this.disable(); throw 'Failed creating mbox, the error was: ' + T; } return R;};mboxFactory.prototype.getPageId = function() { return this.C;};mboxFactory.prototype.getPCId = function() { return this.E;};mboxFactory.prototype.getSessionId = function() { return this.D;};mboxFactory.prototype.getSignaler = function() { return this.L;};mboxFactory.prototype.getUrlBuilder = function() { return this.w;};mboxFactory.prototype.F = function() { var e = this.w; e.addParameter('mboxHost', document.location.hostname) .addParameter('mboxSession', this.D.getId()) .addParameter('mboxPC', this.E.getId()) .addParameter('mboxPage', this.C); var U = mboxCookies.getCookie('edge'); if (U != null) { e.addParameter('edge', U); } e.setUrlProcessAction(function(e) { e += '&mboxURL=' + escape(document.location); var V = escape(document.referrer); if (e.length + V.length < 2000) { e += '&mboxReferrer=' + V; } e += '&mboxVersion=' + mboxVersion; return e; });};mboxFactory.prototype.W = function() { return "";};mboxFactory.prototype.K = function() { document.write('<style>.' + 'mboxDefault' + ' { visibility:hidden; }</style>');};mboxSignaler = function(X) { var Y = mboxCookies.getCookieNames('signal-'); for (var j = 0; j < Y.length; j++) { var Z = Y[j]; var _ = mboxCookies.getCookie(Z).split('&'); var J = X(_[0], _); J.put(); J.load(); mboxCookies.deleteCookie(Z); }};mboxSignaler.prototype.signal = function(ab, M ) { mboxCookies.setCookie('signal-' + ab, mboxShiftArray(arguments).join('&'), 45 * 60);};mboxList = function() { this.z = new Array();};mboxList.prototype.add = function(J) { if (J != null) { this.z[this.z.length] = J; }};mboxList.prototype.get = function(M) { var bb = new mboxList(); for (var j = 0; j < this.z.length; j++) { var J = this.z[j]; if (J.getName() == M) { bb.add(J); } } return bb;};mboxList.prototype.getById = function(cb) { return this.z[cb];};mboxList.prototype.length = function() { return this.z.length;};mboxList.prototype.each = function(p) { if (typeof p != 'function') { throw 'Action must be a function, was: ' + typeof(p); } for (var j = 0; j < this.z.length; j++) { p(this.z[j]); }};mboxUpdate = function(M ) { mboxFactoryDefault.update(M, mboxShiftArray(arguments));};mboxCreate = function(M ) { var J = mboxFactoryDefault.create(M, mboxShiftArray(arguments)); if (J) { J.put(); if (mboxFactoryDefault.isEnabled()) { J.load(); } } return J;};mbox = function(g, db, w) { this.eb = null; this.fb = 0; this.gb = null; this.hb = new mboxOfferContent(); this.ib = this.getDefaultDiv(); this.w = w; this.message = ''; this.jb = new Object(); this.kb = 0; this.db = db; this.g = g; this.lb(); this.mb = new mboxStandardFetcher(); w.addParameter('mbox', g) .addParameter('mboxId', db); this.nb = function() {}; this.ob = function() {};};mbox.prototype.getId = function() { return this.db;};mbox.prototype.lb = function() { if (this.g.length > 250) { throw "Mbox Name " + this.g + " exceeds max length of " + "250 characters."; } else if (this.g.match(/^\s+|\s+$/g)) { throw "Mbox Name " + this.g + " has leading/trailing whitespace(s)."; }};mbox.prototype.getName = function() { return this.g;};mbox.prototype.parameters = function() { return this.w.getParameters();};mbox.prototype.setOnLoad = function(p) { this.ob = p; return this;};mbox.prototype.setMessage = function(S) { this.message = S; return this;};mbox.prototype.setOnError = function(nb) { this.nb = nb; return this;};mbox.prototype.setFetcher = function(pb) { this.mb.cancel(); this.mb = pb; return this;};mbox.prototype.getFetcher = function() { return this.mb;};mbox.prototype.put = function() { document.write('<div id="' + this.getMarkerName() + '" style="visibility:hidden;display:none"></div>');};mbox.prototype.load = function(c) { this.setEventTime("load.start"); this.cancelTimeout(); var w = (c && c.length > 0) ? this.w.clone().addParameters(c) : this.w; this.mb.fetch(w); var I = this; this.qb = setTimeout(function() { I.nb('Fetch of mbox timed out', I.mb.getType()); }, 15000); this.setEventTime("load.end");};mbox.prototype.activate = function() { if (this.fb) { return this.fb; } this.setEventTime('activate' + ++this.kb + '.start'); if (this.show()) { this.cancelTimeout(); this.fb = 1; } this.setEventTime('activate' + this.kb + '.end'); return this.fb;};mbox.prototype.isActivated = function() { return this.fb;};mbox.prototype.setOffer = function(hb) { if (hb && hb.show && hb.setOnLoad) { this.hb = hb; } else { throw 'Invalid offer'; } return this;};mbox.prototype.getOffer = function() { return this.hb;};mbox.prototype.show = function() { this.setEventTime('show.start'); var bb = this.hb.show(this); this.setEventTime(bb == 1 ? "show.end.ok" : "show.end"); return bb;};mbox.prototype.showContent = function(rb) { if (rb == null) { return 0; } if (this.ib == null) { this.ib = this.getDefaultDiv(); if (this.ib == null) { return 0; } } this.sb(this.ib); this.ib.parentNode.replaceChild(rb, this.ib); this.ib = rb; this.tb(rb); this.ob(); return 1;};mbox.prototype.hide = function() { this.setEventTime('hide.start'); var bb = this.showContent(this.getDefaultDiv()); this.setEventTime(bb == 1 ? 'hide.end.ok' : 'hide.end.fail'); return bb;};mbox.prototype.finalize = function() { this.setEventTime('finalize.start'); this.cancelTimeout(); if (this.getDefaultDiv() == null) { this.gb = document.createElement('div'); this.gb.className = 'mboxDefault'; var ub = document.getElementById(this.getMarkerName()); ub.parentNode.insertBefore(this.gb, ub); this.setMessage('No default content, an empty one has been added'); } if (!this.activate()) { this.hide(); this.setEventTime('finalize.end.hide'); } this.setEventTime('finalize.end.ok');};mbox.prototype.cancelTimeout = function() { if (this.qb) { clearTimeout(this.qb); } this.mb.cancel();};mbox.prototype.getDiv = function() { return this.ib;};mbox.prototype.getDefaultDiv = function() { if (this.gb != null) { return this.gb; } var vb = document.getElementById(this.getMarkerName()); while (vb != null) { if ((vb.nodeType == 1) && (vb.nodeName == "DIV")) { if (vb.className.indexOf('mboxMarker-') > 0) { return null; } else if (vb.className == 'mboxDefault') { this.gb = vb; return vb; } } vb = vb.previousSibling; } return null;};mbox.prototype.getMarkerName = function() { return 'mboxMarker-' + this.g + '-' + this.db;};mbox.prototype.setEventTime = function(wb) { this.jb[wb] = (new Date()).getTime();};mbox.prototype.getEventTimes = function() { return this.jb;};mbox.prototype.getURL = function() { return this.w.buildUrl();};mbox.prototype.getUrlBuilder = function() { return this.w;};mbox.prototype.xb = function(ib) { return ib.style.display != 'none';};mbox.prototype.tb = function(ib) { this.yb(ib, true);};mbox.prototype.sb = function(ib) { this.yb(ib, false);};mbox.prototype.yb = function(ib, zb) { ib.style.visibility = zb ? "visible" : "hidden"; ib.style.display = zb ? "block" : "none";};mboxOfferContent = function() { this.ob = function() {};};mboxOfferContent.prototype.show = function(J) { var bb = J.showContent(document.getElementById( this.Ab(J))); if (bb == 1) { this.ob(); } return bb;};mboxOfferContent.prototype.setOnLoad = function(ob) { this.ob = ob;};mboxOfferContent.prototype.Ab = function(J) { return 'mboxImported-' + J.getName() + '-' + J.getId();};mboxOfferAjax = function(rb) { this.rb = rb; this.ob = function() {};};mboxOfferAjax.prototype.setOnLoad = function(ob) { this.ob = ob;};mboxOfferAjax.prototype.show = function(J) { var Bb = document.createElement('div'); Bb.id = 'mboxImported-' + J.getName() + '-' + J.getId(); Bb.innerHTML = this.rb; var bb = J.showContent(Bb); if (bb == 1) { this.ob(); } return bb;};mboxOfferDefault = function() {};mboxOfferDefault.prototype.setOnLoad = function(ob) { };mboxOfferDefault.prototype.show = function(J) { return J.hide();};mboxCookieManager = function mboxCookieManager(g, Cb) { this.g = g; this.Cb = Cb == '' || Cb.indexOf('.') == -1 ? '' : '; domain=' + Cb; this.loadCookies();};mboxCookieManager.prototype.isEnabled = function() { this.setCookie('check', 'true', 60); this.loadCookies(); return this.getCookie('check') == 'true';};mboxCookieManager.prototype.setCookie = function(g, h, N) { if (typeof g != 'undefined' && typeof h != 'undefined' && typeof N != 'undefined') { var cb = this.Db.length; for (var j = 0; j < this.Db.length; j++) { if (this.Db[j].name == g) { cb = j; break; } } var Eb = new Object(); Eb.name = g; Eb.value = escape(h); Eb.expireOn = Math.ceil(N + new Date().getTime() / 1000); this.Db[cb] = Eb; this.saveCookies(); }};mboxCookieManager.prototype.getCookie = function(g) { for (var j = 0; j < this.Db.length; j++) { var Eb = this.Db[j]; if (Eb.name == g) { return unescape(Eb.value); } } return null;};mboxCookieManager.prototype.deleteCookie = function(g) { var Fb = new Array(); for (var j = 0; j < this.Db.length; j++) { var Eb = this.Db[j]; if (Eb.name != g) { Fb[Fb.length] = Eb; } } this.Db = Fb; this.saveCookies();};mboxCookieManager.prototype.getCookieNames = function(Gb) { var Hb = new Array(); for (var j = 0; j < this.Db.length; j++) { var Eb = this.Db[j]; if (Eb.name.indexOf(Gb) == 0) { Hb[Hb.length] = Eb.name; } } return Hb;};mboxCookieManager.prototype.saveCookies = function() { var Ib = new Array(); var Jb = 0; for (var j = 0; j < this.Db.length; j++) { var Eb = this.Db[j]; Ib[Ib.length] = Eb.name + '#' + Eb.value + '#' + Eb.expireOn; if (Jb < Eb.expireOn) { Jb = Eb.expireOn; } } var Kb = new Date(Jb * 1000); document.cookie = this.g + '=' + Ib.join('|') + '; expires=' + Kb.toGMTString() + '; path=/' + this.Cb;};mboxCookieManager.prototype.loadCookies = function() { this.Db = new Array(); var Lb = document.cookie.indexOf(this.g + '='); if (Lb != -1) { var Mb = document.cookie.indexOf(';', Lb); if (Mb == -1) { Mb = document.cookie.indexOf(',', Lb); if (Mb == -1) { Mb = document.cookie.length; } } var Nb = document.cookie.substring( Lb + this.g.length + 1, Mb).split('|'); var Ob = Math.ceil(new Date().getTime() / 1000); for (var j = 0; j < Nb.length; j++) { var Eb = Nb[j].split('#'); if (Ob <= Eb[2]) { var Pb = new Object(); Pb.name = Eb[0]; Pb.value = Eb[1]; Pb.expireOn = Eb[2]; this.Db[this.Db.length] = Pb; } } }};mboxSession = function(Qb, Rb, Z, Sb) { this.Rb = Rb; this.Z = Z; this.Sb = Sb; this.Tb = false; this.db = typeof mboxForceSessionId != 'undefined' ? mboxForceSessionId : mboxGetPageParameter(this.Rb); if (this.db == null || this.db.length == 0) { this.db = mboxCookies.getCookie(Z); if (this.db == null || this.db.length == 0) { this.db = Qb; this.Tb = true; } } mboxCookies.setCookie(Z, this.db, Sb);};mboxSession.prototype.getId = function() { return this.db;};mboxSession.prototype.forceId = function(Ub) { this.db = Ub; mboxCookies.setCookie(this.Z, this.db, this.Sb);};mboxPC = function(Qb, Z, Sb) { this.Z = Z; this.Sb = Sb; this.db = typeof mboxForcePCId != 'undefined' ? mboxForcePCId : mboxCookies.getCookie(Z); if (this.db == null || this.db.length == 0) { this.db = Qb; } mboxCookies.setCookie(Z, this.db, Sb);};mboxPC.prototype.getId = function() { return this.db;};mboxPC.prototype.forceId = function(Ub) { if (this.db != Ub) { this.db = Ub; mboxCookies.setCookie(this.Z, this.db, this.Sb); return true; } return false;};mboxGetPageParameter = function(g) { var bb = null; var Vb = new RegExp(g + "=([^\&]*)"); var Wb = Vb.exec(document.location); if (Wb != null && Wb.length >= 2) { bb = Wb[1]; } return bb;};mboxSetCookie = function(g, h, N) { return mboxCookies.setCookie(g, h, N);};mboxGetCookie = function(g) { return mboxCookies.getCookie(g);};mboxCookiePageDomain = function() { var Cb = (/([^:]*)(:[0-9]{0,5})?/).exec(document.location.host)[1]; var Xb = /[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}/; if (!Xb.exec(Cb)) { var Yb = (/([^\.]+\.[^\.]{3}|[^\.]+\.[^\.]+\.[^\.]{2})$/).exec(Cb); if (Yb) { Cb = Yb[0]; } } return Cb ? Cb: "";};mboxShiftArray = function(Zb) { var bb = new Array(); for (var j = 1; j < Zb.length; j++) { bb[bb.length] = Zb[j]; } return bb;};mboxGenerateId = function() { return (new Date()).getTime() + "-" + Math.floor(Math.random() * 999999);};if (typeof mboxVersion == 'undefined') { var mboxVersion = 28; var mboxCookies = new mboxCookieManager("mbox", (function() { return mboxCookiePageDomain(); })()); var mboxFactoryDefault = new mboxFactory('mbox3.offermatica.com', 'searsholdings');};mboxDisplayDebugInfo = function() { mboxDebugger.show();};mboxCookieIsEnabled = function() { mboxCookies.setCookie('check', "true", 60); return mboxCookies.getCookie('check') == "true"};mboxDebug = function(_b, ac, bc) { this.cc = _b; this.p = null; var dc = mboxGetPageParameter(ac); if (dc == null) { dc = mboxCookies.getCookie(this.cc); } if (dc != null) { if (dc.indexOf("x") == 0) { this.p = new mboxDebugActionNone(); document.write('<' + 'scr' + 'ipt language="Javascript1.2" src=' + '"http://admin3.offermatica.com/admin/mbox/mbox_debug_' + dc + '.jsp?mboxServerHost=mbox3.offermatica.com&clientCode=searsholdings&' + 'session=' + mboxCookies.getCookie('session') + '"><' + '\/scr' + 'ipt>'); } else if (dc.indexOf("log") == 0) { this.p = new mboxDebugActionLog(dc); } else if (dc == "reset") { dc = null; } else { this.p = new mboxDebugActionDefault(); } } if (this.p != null) { if (bc.isSupported()) { bc.addOnLoad(mboxDisplayDebugInfo); } else { alert("mbox functionality is not supported on this browser"); this.disable(); } } if (dc != null) { mboxCookies.setCookie(this.cc, dc, 45 * 60); } else { this.disable() }};mboxDebug.prototype.isEnabled = function() { return this.p != null;};mboxDebug.prototype.disable = function() { mboxCookies.deleteCookie(this.cc);};mboxDebug.prototype.setShowAction = function(p) { this.p = p;};mboxDebug.prototype.show = function() { if (this.p != null) { this.p.show(); }};mboxDebugActionLog = function(dc) { this.dc = dc; this.ec = (new Date()).getTime();};mboxDebugActionLog.prototype.show = function() { var fc = (new Date()).getTime(); var gc = new mboxDebugWindow("Debug Log"); gc.putPageStart(); gc.put("<img sr" + "c='" + document.location.protocol + "//mbox3.offermatica.com/ima" + "ges/log.gif" + "?mboxDebug=" + this.dc + "&mboxClient=searsholdings" + "&mboxPageLoadTime=" + (fc - this.ec) + "'/>"); gc.put("<br />"); gc.putCloseButton(); gc.putPageEnd();};mboxDebugActionNone = function() {};mboxDebugActionNone.prototype.show = function() { alert("Debug mode not defined" + "\n('x-mode' not supported when debug is remote in mbox.js)");};mboxDebugActionDefault = function() {};mboxDebugActionDefault.prototype.show = function() { var window = new mboxDebugWindow("Debug"); window.putPageStart(); window.put("  <b>Mbox Debug Window (version:" + mboxVersion + ")</b></br>"); window.put("<p/>"); window.put("<b>Page</b>: " + document.location); window.put(" <ul>"); if (mboxFactoryDefault.isEnabled()) { window.put("  <li>Enabled: true</li>"); } else { window.put( '  <li>Enabled: <span style="color:red"><b>false</b></span></li>'); } window.put("  <li>Cookies enabled: " + mboxCookieIsEnabled() + "</li>"); window.put("  <li>Page URL: '" + document.location + "'</li>"); window.put("  <li>Cookies: '" + document.cookie + "'</li>"); mboxFactoryDefault.getMboxes().each(function(J) { var Q = J.getName(); window.put("  <li>mbox: '" + Q + "'"); window.put("   <ul>"); if (J.message != '') { window.put('   <li><span style="color: red"><b>Error: ' + J.message + '</b></span></li>'); } var ub = document.getElementById(J.getMarkerName()); if (ub == null) { window.put('    <li><span style="color: red">' + '<b>Error: can not find mbox in dom</b></span></li>'); } window.put('    <li><a href="' + J.getURL() + '">' + J.getURL() + '</a></li>'); window.put("   </ul>"); window.put("  </li>"); }); window.put(" </ul>"); window.putCloseButton(); window.putPageEnd();};mboxDebugWindow = function(g) { this.g = g + " " + document.location.hostname; var hc = /\W/g; var ic = this.g.replace(hc, '_'); this.gc = window.open("", "mboxDebugWindow" + ic, "width=600,height=300,resizable,scrollbars=yes,toolbar=yes"); if (this.gc == null) { alert("Unable to open Offermatica's mboxDebugWindow Window.\n" + "Are you blocking popups?\n"); }};mboxDebugWindow.prototype.put = function(kc) { if (this.gc == null) { return; } try { this.gc.document.writeln(kc); this.gc.scrollBy(0, 1000); } catch (e) { alert("Unable to write to the current mboxDebug window.\n" + "Please close any open debug window"); this.gc = null; }};mboxDebugWindow.prototype.putCloseButton = function() { this.put("<a href=\"javascript:mboxDebugWindowClose()\">" + "click here to close debug window</a>");};mboxDebugWindow.prototype.putPageStart = function() { this.put("<html><head>" + " <title>" + this.g + "</title>" + "  <" + "scr" + "ipt>" + "   function mboxDebugWindowClose() {" + "     try {" + "       window.opener.mboxDebugger.disable();" + "     } catch(e) {" + "       alert('Could not disable debug mode.\\n'" + "         + 'Browse to a page containing an mbox and\\n'" + "         + 'click on close link.');" + "     }" + "     window.close();" + "   }" + "  <" + "\/scr" + "ipt>" + " </head>" + " <body>");};mboxDebugWindow.prototype.putPageEnd = function() { this.put("</body></html>"); if (this.gc != null) { this.gc.document.close(); }};if (typeof mboxDebugger == 'undefined') { mboxDebugger = new mboxDebug("debug", "mboxDebug", mboxFactoryDefault);}


/* /mmh/utils_src.js */

 //
//<General utilities>
//
function collectionFormData(form){    
  var queryString = '';
  if (form.elements){
    for (var i = 0; i < form.elements.length; i++)
    { 
      if (form.elements[i].type == 'radio'){
        if (form.elements[i].checked){
          if (queryString.length > 0){
            queryString += "&";
          }
          queryString += form.elements[i].name + "=" + form.elements[i].value; 
        }
      }else if (form.elements[i].type == 'checkbox'){ 
        if (form.elements[i].checked){
          if (queryString.length > 0){
            queryString += "&";
          }
          queryString += form.elements[i].name + "=" + form.elements[i].value; 
        }
      }else{
        if (queryString.length > 0){
          queryString += "&";
        }
        queryString += form.elements[i].name + "=" + form.elements[i].value;
      }
    }
  }
  return queryString;
}

function form_is_modified(oForm)
{
  var el, opt, hasDefault, i = 0, j;
  while (el = oForm.elements[i++]) {
    switch (el.type) {
    case 'text' :
    case 'textarea' :
    case 'hidden' :
      if (!/^\s*$/.test(el.value) && el.value != el.defaultValue) return true;
      break;
    case 'checkbox' :
    case 'radio' :
      if (el.checked != el.defaultChecked) return true;
      break;
    case 'select-one' :
    case 'select-multiple' :
      j = 0, hasDefault = false;
      while (opt = el.options[j++])
        if (opt.defaultSelected) hasDefault = true;
      j = hasDefault ? 0 : 1;
      while (opt = el.options[j++]) 
        if (opt.selected != opt.defaultSelected) return true;
      break;
    }
  }
  return false;
}

function ajaxOnClick(item){
  var titleX = document.title;
  var path = document.getElementById("hier").value;
  var target = "titleText." + item.id.split(/\./)[1];
  var text = path + " > " + document.getElementById(target).value;
  s.hier1=text;
  void(s.t());
  var options = { 
    asynchronous: false,
    method : 'get',
    evalScripts : true,
    onComplete : function(response) {
      showContentSavedConfirmation();
      showSaveButtons();
      var url = item.href;
      
      var queryParameter = url.substring(url.indexOf('?'), url.length);
      updateBreadcrumbs(queryParameter);
      updateMostPopularArticles('/mmh/ideas/viewIKHrightLow.action' + queryParameter);
      Behaviour.apply();
    }
  };
  new Ajax.Updater('ajaxContainer', item.href, options);
  document.title = titleX;
  return false;
}

function updateMostPopularArticles(mpaurl){
  var options = {
    onComplete : function(response){
    },
    asynchronous : true
  };
  if($('mostpopulararticlesdiv') != null){
    new Ajax.Updater({success : 'mostpopulararticlesdiv'}, mpaurl, options); 
  }
}

function updateMboxParams(mboxName, keywords, taxonomy) {
  mboxUpdate(mboxName, keywords, taxonomy);
}

function generateRatings()
{
  var aj_rater1 = new AjaxJspTag.Toggle(
    "/mmh/rating/Submit.action", {
      messageClass: "star-rating-message",
      parameters: "rating={ajaxParameter},uid=" + $('uidFormValue').value + ", track=" + $('level1FormValue').value + ", topic=" + $('level2IndexFormValue').value,
      selectedLessClass: "selectedless",
      selectedClass: "selected",
      ratings: "Poor,Fair,Good,Very Good,Excellent",
      defaultRating: "",
      state: "raterField",
      overClass: "over",
      containerClass: "star-rating",
      source: "rater1",
      selectedOverClass: "selectedover",
      onOff: "false"
    });
  
  $('rater1_message').innerHTML = "Rate this Article";
}

function onMouseOverAjax(element){
  var target = "articleAbstract." + element.id.split(/\./)[1];
  var myAbstract = document.getElementById(target);
  document.getElementsBySelector(".articleTitle").forEach(function(item){
    var key = "articleAbstract." + item.id.split(/\./)[1];
    var myCallout = document.getElementById(key);
    var tips = $(key);
    if (tips) tips.className = (target == key) ? "mvArr_containerVisible" : "mvArr_container";
  })
}

function openCategoryOrSubCategory(){
  var queryString = window.location.hash.replace("#", "");
  if($('level2IndexFormValue') != null) {
    var cat = $('level2IndexFormValue').value;
    showSubCat('leftNavCatItem' + cat, 'leftNavSubCat' + cat); 
    if($('level3IndexFormValue') != null){
      var subCat = $('level3IndexFormValue').value;
      actSubCat('leftNavCat' + cat + 'Sub' + subCat);
      curElement = $('leftNavCat' + cat + 'Sub' + subCat).getElementsByTagName('a')[0];
    }
  }else if(queryString.length > 0)
  {
    var params = queryString.split("&");
    var cat = params[0].split("=")[1];

    if(params.length > 0)
    {
      showSubCat('leftNavCatItem' + cat, 'leftNavSubCat' + cat); 
    }
    
    var subCat;
    
    if(params.length > 1){
      subCat = params[1].split("=")[1];
      actSubCat('leftNavCat' + cat + 'Sub' + subCat);
    }
  }
}



//var newwindow;
function popUp(url)
{
  var newwindow;
  newwindow=window.open(url,'name','toolbar=0,scrollbars=1,location=0,statusbar=1,menubar=0,resizable=1,width=800,height=800');
  if (window.focus) {newwindow.focus()}
}

function popUpWithEverythingEnabled(url)
{
  var newwindow;
  newwindow=window.open(url,'name', 'toolbar=1,location=1,scrollbars=1,statusbar=1,menubar=1,resizable=1,width=800,height=800');
  if (window.focus) {newwindow.focus()}
}

function popUpWithSize(url, height, width)
{
  var newwindow;
  newwindow=window.open(url,'name','height=' + height + ',width=' + width + ',scrollbars=1');
  newwindow.opener = self;
  if (window.focus) {newwindow.focus()}
}

function generateSearchURL(referrer, baseUrl, queryString)
{
  //var url = baseUrl;
  var url = referrer;
  
  if(url.length < 1)
  {
    //url = referrer;
    url = baseUrl;
  }
  
  if(url.indexOf('?') >= 0)
  {
    url = url.substring(0, url.indexOf('?'));
  }

  if(queryString.length > 0)
  {
    url = url + queryString;
  }
  return url;
}

function generateHistoryURL(queryString)
{
  var curURL = "";
  var tempURL = "";
  tempURL = tempURL + window.location;
  
  if(tempURL.indexOf('?') >= 0)
  {
    tempURL = tempURL.substring(0, tempURL.indexOf('?'));
  }

  curURL += tempURL;
  
  if(queryString.length > 0)
  {
    curURL += "&queryString=";
    curURL += queryString;
  }
  return curURL;
}

function clearSearchStringTextBox(element, str){
  if(element.value == str){
    element.value='';
  }
};

function PopWindow(url)
{
  window.open(url);
}

function showContentSavedDiv(saveMessageDiv, message) {
  if(message) {
    saveMessageDiv.innerHTML= message;
  } else {
    saveMessageDiv.innerHTML= 'Content was successfully saved.';
  }
  new Effect.Highlight(saveMessageDiv,  {duration:2.0, startcolor:'#FFFF00', endcolor:'#ffffff'});
}


/***********************************************
* Textarea Maxlength script- © Dynamic Drive (www.dynamicdrive.com)
* This notice must stay intact for legal use.
* Visit http://www.dynamicdrive.com/ for full source code
***********************************************/

function ismaxlength(obj){
  var mlength=obj.getAttribute? parseInt(obj.getAttribute("maxlength")) : ""
  if (obj.getAttribute && obj.value.length>mlength)
    obj.value=obj.value.substring(0,mlength)
}

function formatPhone(elem)
{
  if (elem != null && elem != ''){
    elem = "(" +elem.substring(0,3)+") "+elem.substring(3,6)+ "-"+elem.substring(6,10);
  }  
  return elem;
}

//Captures tab key pressed and forces the 
//focus to the 1st field passed as a 2nd arg
//to this function.
function captureTabKey(e, field) {
  var keynum;
  if(window.event) // IE
  {
    keynum = e.keyCode
  }
  else if(e.which) // Netscape/Firefox/Opera
  {
    keynum = e.which
  }
  
  if (keynum == 9) {
    document.getElementById(field).focus();
  }
}

function updateBreadcrumbs(queryParameters) {
  new Ajax.Request('<s:url namespace="/ideas" action="BreadcrumbAjax"/>',{
    method: 'get', parameters: queryParameters,
    onSuccess: function(new_breadcrumbs) {
      if ($('breadcrumbs')) { 
        $('breadcrumbs').update(new_breadcrumbs.responseText);
      } else { 
        $('breadcrumb').getElementsByClassName('breadcrumbs').each(function(breadcrumbs) { 
          $(breadcrumbs).replace(new_breadcrumbs.responseText); 
        });
      }
    }
  });
}

/**
 * Ajax upload plugin for Prototype
 * Project page - http://valums.com/ajax-upload/
 * Copyright (c) 2008 Andris Valums, http://valums.com
 * Licensed under the MIT license (http://valums.com/mit-license/)
 * Version 1.1 (14.02.2009)
 *
 * Port to prototype was made with the help of Yury Velikanau, Ribose Inc.
 */
(function(){
  // we need Prototype to run
  if (!Prototype) return;

  /**
   * Function generates unique id
   */
  var get_uid = function(){
    var uid = 0;
    return function(){
      return uid++;
    }
  }();

  window.Ajax_upload = Class.create({
  /**
   * form - form to submit
   * option - hash of options :
   *   onSubmit - callback to fire on file submit
   *   onComplete - callback to fire when iframe has finished loading
   */
    initialize: function(form, action, options) {
      // get element by id
      if (typeof form == "string") {
        form = $(form);
      }
      this.form = form;
      this.form.action = action;
      
      this.wrapper = null;
      this.input = null;
      this.iframe = null;
      
      this.disabled = false;
      this.submitting = false;
      
      this.settings = $H({
        // Fired when file upload is completed
        onComplete: function(response) {}
      })
      // Merge the users options with our defaults
        .update(options);
      
      this.create_iframe();
      this.submit()
    },
    /**
     * Creates iframe with unique name
     */
    create_iframe : function(){
      // unique name
      // We cannot use getTime, because it sometimes return
      // same value in safari :(
      var id = 'valumsl8mh6sdc_' + get_uid();
      
      // create iframe, so we dont need to refresh page
      this.iframe = new Element('iframe', { id: id, name: id });
      this.iframe.setStyle({ display: 'none' });
      document.body.insert(this.iframe);
    },
    /**
     * Upload file without refreshing the page
     */
    submit : function(){
      var iframe = this.iframe;
      settings = this.settings;
      iframe.observe('load', function(){
        if (!iframe || iframe.src == "about:blank") {
          return;
        }
        var obj = {};

			  try
			  {
				  obj.responseText = iframe.contentWindow.document.body?iframe.contentWindow.document.body.innerHTML:null;
				  obj.responseXML = iframe.contentWindow.document.XMLDocument?iframe.contentWindow.document.XMLDocument:iframe.contentWindow.document;
			  }
			  catch(e){
        }

        settings.get('onComplete').call(this, obj);
        // Workaround for FF2 bug, which causes cursor to be in busy state after post.
        setTimeout(function(){
          // Workaround for ie6 bug, which causes progress bar to be in busy state after post.
          // Thanks to Lei for the fix
          iframe.src = "about:blank";
          
          iframe.remove();
          iframe = null;
        }, 1);
      });
      this.form.target = iframe.name
      this.form.submit();
    }
  });
})();

//
//</General utilities>
//



/* /functions/namespace.js */

 // Just to make sure.
if (typeof Prototype === 'undefined') {
  throw new Error('Support Library requires Prototype');
}

// create the MMH namespace if it doesn't exist
var MMH;
if (typeof MMH === 'undefined') {
	MMH = {};
}


/* /functions/dom_on_ready.js */

 /* DOMready (Event onReady) Function
=============================================================================== */
// Event.onReady(callbackFunction);
Object.extend(Event, {
  _domReady : function() {
    if (arguments.callee.done) return;
    arguments.callee.done = true;
    if (Event._timer)  clearInterval(Event._timer);
    Event._readyCallbacks.each(function(f) { f() });
    Event._readyCallbacks = null;
  },
  onReady : function(f) {
    if (!this._readyCallbacks) {
      var domReady = this._domReady;
      if (domReady.done) return f();
      if (document.addEventListener)
        document.addEventListener("DOMContentLoaded", domReady, false);
        /*@cc_on @*/
        /*@if (@_win32)
            document.write("<script id=__ie_onload defer src=javascript:void(0)><\/script>");
            document.getElementById("__ie_onload").onreadystatechange = function() {
                if (this.readyState == "complete") { domReady(); }
            };
        /*@end @*/
        if (/WebKit/i.test(navigator.userAgent)) { 
          this._timer = setInterval(function() {
            if (/loaded|complete/.test(document.readyState)) domReady(); 
          }, 10);
        }
        Event.observe(window, 'load', domReady);
        Event._readyCallbacks =  [];
    }
    Event._readyCallbacks.push(f);
  }
});



/* /functions/cookies.js */

 /* Cookies
=============================================================================== */
var MMH;
if (typeof MMH === 'undefined') {
    MMH = {};
}

(function () {
	MMH.CookieJar = { 
		cookies: {},
		init:function() { 
			return CookieJar.packageCookies();
		},
		// Pass This: { name:'cookie', value:'value', [expires:'365'], [domain:'domain'], [path:'path'], [secure:true] }  
		addCookie:function(new_cookie) { 
			cookie = [];
			
			// Build the New Cookie Array
			for(label in new_cookie) {
				switch(label) {
					case 'name':	
						cookie[0] = new_cookie[label];
						break;
					
					case 'value':	
						cookie[0] = cookie[0] + "=" + new_cookie[label]; 
						break;
					
					case 'expires': 
						dateObj = new Date();
						dateObj.setDate(dateObj.getDate() + parseInt(new_cookie[label]));
						value = dateObj.toGMTString(); 
						cookie[1] = label + "=" + value;
						break;
					
					case 'path':	
						cookie[2] = label + "=" + new_cookie[label];	
						break;
					
					case 'domain':	
						cookie[3] = label + "=" + new_cookie[label];	
						break;		
					
					case 'secure':	
						if(new_cookie[label]) { 
							cookie[4] = label 
						};
						break; 
				}
			}
			
			// Remove Blanks in New Cookie Array
			var cleanCookie = []; 
			for(i = 0; i < 5; i++) { 
				if(cookie[i]) { 
					cleanCookie.push(cookie[i]); 
				} 
			}
			
			// Submit New Cookie 
			document.cookie = cleanCookie.join(';') + ';';

			// Update Cookies Object and Return
			
			return (MMH.CookieJar.packageCookies()) ? true : false
		},
		getCookie:function(cookie) {
			return (MMH.CookieJar.cookies[cookie]) ? MMH.CookieJar.cookies[cookie] : false
		},
		updateCookie:function(cookie) {
			return (MMH.CookieJar.getCookie(cookie.name)) ? MMH.CookieJar.addCookie(cookie) : false 
		},
		tossCookie:function(cookie) {
			// Ready the Cookie Object to Remove
			oldCookie = {};
			oldCookie.name = cookie;
			oldCookie.value = MMH.CookieJar.getCookie(cookie);
			oldCookie.expires = '-1';
			
			return MMH.CookieJar.updateCookie(oldCookie);
		},
		packageCookies:function() {
			// Stores Available Cookies as Object
			document.cookie.split(';').each(function(cookie) {
				name = cookie.split('=')[0];
				value = cookie.split('=')[1];
				MMH.CookieJar.cookies[name] = value;
			});
			return true;
		}
	};
})();



/* /functions/common.js */

 /* Common Methods
=============================================================================== */
MMH.Window = {
  redirectTo: function( url ) {
    window.location.href = url;
  },

  reload: function() { window.location.reload(); }
};


(function () {
	MMH.Common = {
	
		// Caveat: these functions don't take the body margin and padding into account,
		// so to get truly correct sizes, you should set them to 0
		
		getWindowSize: function(){
			return {
				width: (self.innerWidth ||
				document.documentElement.clientWidth ||
				document.body.clientWidth ||
				0),
				height: (self.innerHeight ||
				document.documentElement.clientHeight ||
				document.body.clientHeight ||
				0)
			}
		},
		getDocumentSize: function(){
			var winsize = MMH.Common.getWindowSize();
			return {
				width: Math.min(document.body.scrollWidth, winsize.width),
				height: Math.max(document.body.scrollHeight, winsize.height)
			}
		},
		createTextCounter: function(field, cntfield, maxlimit, options){
			var opts_merged = {text: ' characters remaining', limit_text: 'Limit is reached'};
			opts_merged = Object.extend(opts_merged, options);
			var field_ele = $(field);
			var cntfield_ele = $(cntfield);
			
			MMH.Common.textCounter(field_ele, cntfield_ele, maxlimit, opts_merged);
			Event.observe(field_ele, 'keyup', function() {
				var local_field = field_ele;
				var local_cntfield = cntfield_ele;
				var local_maxlimit = maxlimit;
				var local_opts = opts_merged;
				MMH.Common.textCounter(local_field, local_cntfield, local_maxlimit, local_opts);
			});
		}, 
		
		textCounter: function(field, cntfield, maxlimit, options) {
			if (field.value.length >= maxlimit) {
				field.value = field.value.substring(0, maxlimit);
				cntfield.innerHTML = options['limit_text'];
			}
			else {
				var cnt = maxlimit - field.value.length;
				cntfield.innerHTML = cnt + options['text'];
			}
			//Override if needed
		    if(field.hasClassName('js_prompt_text')) {
		        if(field.value == field.title) {
		            cntfield.innerHTML = maxlimit + options['text'];
		        }
		    }
		},


      createWordCounter: function(field, cntfield, maxlimit, options){
			var opts_merged = {text: ' words left', limit_text: 'Limit is reached', restricted: true};
			opts_merged = Object.extend(opts_merged, options);
			var field_ele = $(field);
			var cntfield_ele = $(cntfield);

			MMH.Common.wordCounter(field_ele, cntfield_ele, maxlimit, opts_merged);
			Event.observe(field_ele, 'keyup', function() {
				var local_field = field_ele;
				var local_cntfield = cntfield_ele;
				var local_maxlimit = maxlimit;
				var local_opts = opts_merged;
				MMH.Common.wordCounter(local_field, local_cntfield, local_maxlimit, local_opts);
			});
		},

		wordCounter: function(field, cntfield, maxlimit, options) {
      var words_array = field.value.split(" ");
      if (words_array.length >= maxlimit) {
        if(options['restricted']){
				  field.value = words_array.slice(0, maxlimit).join(" ");
        }
				cntfield.innerHTML = options['limit_text'];
			}
			else {
				var cnt = maxlimit - words_array.length;
				cntfield.innerHTML = cnt + options['text'];
			}
			//Override if needed
		    if(field.hasClassName('js_prompt_text')) {
		        if(field.value == field.title) {
                cntfield.innerHTML = maxlimit + options['text'];
		        }
		    }
		},

    twoCol: function(twoColList) {
			colArray = $(twoColList).childElements();
			colMiddle = Math.round(colArray.size()/2);
			leftItems = [];
			rightItems = [];
			
			colArray.each( function(item,index){
				//alert(item)
				if(index < colMiddle){
					leftItems.push(item);
				}else{
					rightItems.push(item);
				}
			});
			
			colArray.clear();
			
			leftItems.each(function(item, index){
				
				colArray.push(item);
					
				if(rightItems[index]){
				
					colArray.push(rightItems[index]);
				
				}
			});
			
			$(twoColList).innerHTML = "";
			newList = document.createElement('ul');
			colArray.each(function(item){
				
				$(twoColList).innerHTML += "<li>"+item.innerHTML+"</li>";
				
			});
		},
		twoList: function() {
		//Splits one list into two
		$$(".two_column_list ul").each(function(thisList){
			
				var colMiddle = Math.round(thisList.childElements().size()/2);
				var itemsContainer = document.createElement('div');
				itemsContainer.setAttribute('class','clearfix');
				var leftItems = document.createElement('ul');
				var rightItems = document.createElement('ul');
				
				thisList.childElements().each( function(item,index){
					
					if(index < colMiddle){
						leftItems.appendChild(item);
					}else{
						rightItems.appendChild(item);
					}
					
				});
				itemsContainer.appendChild(leftItems);
				itemsContainer.appendChild(rightItems);
				$(thisList).parentNode.insertBefore(itemsContainer,$(thisList));
				$(thisList).remove();
				
			});
		},
		popUp: function (options) {
			    Object.extend(options, options || {});
			    window.open(options.url, '', 'width='+options.width+',height='+options.height+',resizeable=1,scrollbars=1');
	
		},
		loadRemote: function(url) { 
			var headID = document.getElementsByTagName("head")[0];
			var newScript = document.createElement('script');
			newScript.type = 'text/javascript';
			newScript.src = url;
			headID.appendChild(newScript);
		}
	}})();



/* /functions/notify_system.js */

 /* Event & Notify System
=============================================================================== */
(function () {

	var $cancel = {};

	MMH.Event = {
		extend: function (object) {
			// add event behavior to an object
			return Object.extend(object||window, MMH.Event.EventManager);
		}
	};

	MMH.Event.EventManager = {
		_getListeners: function () {
			// ensure that callback array is propertly initialized
			if (!this._eventListeners) {
				this._eventListeners = [];
			}
			return this._eventListeners;
		},

		notify: function () {
			var fullargs = arguments;
			var shortargs = $A(arguments);
			var event = shortargs.shift();
			var E = Prototype.emptyFunction;

			try {
				// fire all the listeners
				this._getListeners().each(function (listener) {
					typeof listener === 'function'?
						listener.apply(this, fullargs):
						(listener[event]||E).apply(this, shortargs);
				}.bind(this));
			} catch (ex) {
				// user callback caused an abort
				if (ex === $cancel) {
					return false;
				}

				// need to rethrow
				throw ex;
			}

			// ok
			return true;
		},

		listen: function (listener) {
			// install a listener
			this._getListeners().push(listener);
		},

		cancel: function () {
			// allow user to abort
			throw $cancel;
		}
	};
})();


/* /functions/form_validation.js */

 /* Form Validation
=============================================================================== */
(function () {
	MMH.Form = {
		rules: {
			// When adding rules, add the rule to the active array
			// and copy and paste an existing rule and modify the if statement.
			// If it is a complicated rule, you can call roy over.
			active: ['is_float','is_present','at_least_one','at_least_one_selected_option'],
			is_float: function(properties) {
				properties.elements.each(function(field) {
					if(!parseFloat(field.value)) {
						properties = displayErrors(properties, field);
					}
				});
				return properties;
			},
			is_present: function(properties) {
				properties.elements.each(function(field) {
					if((field.value == '') || (field.value == field.title) || (field.value.gsub(/\s/, '') == '')) {
						properties = displayErrors(properties, field);
					}
				});
				return properties;
			},
			at_least_one:function(properties) {
				properties.elements.each(function(field) {
					found_a_check = false;
					if(field.getValue()) {
						found_a_check = true;
						throw $break;
					}
				});
				if(!found_a_check) {
					properties = displayErrors(properties);
				}
				return properties;
			},
			at_least_one_selected_option:function(properties) {
                properties.elements.each(function(field) {
                	found_a_selected_option = false;
                    if(field.selected) {
                        found_a_selected_option = true;
                        throw $break;
                    }
                });
                if(!found_a_selected_option) {
                    properties = displayErrors(properties);
                }
                return properties;
        	}
		},
		validate: function(targets, context) {
			var errors = false;
			var properties = {};

			// Clear old messages
			$$('p.error','p.errorMsg').each(function(p) {
				p.innerHTML = "";
			});
			$$('.has_error').each(function(field) {
				field.removeClassName('has_error');
			});

			// Parse the targets and context
			for(class_name in targets) {
				if(targets.hasOwnProperty(class_name)) {
					elements = (context === undefined ? $$('.' + class_name) : $(context).getElementsBySelector('.' + class_name));
					var properties = {
						elements: 			elements,
						error_container: 	$(class_name.replace('js_validate_','') + '_error'),
						global_error_shown: false,
						errors:				false,
						rules:				targets[class_name]
					};
					// Run the rules
					for(rule in targets[class_name]) {
						if(targets[class_name].hasOwnProperty(rule)) {
							properties.rule = rule;
							if(properties.rule != 'message') {
								if(MMH.Form.rules.active.indexOf(properties.rule) != -1) {
									MMH.Form.rules[properties.rule](properties);
								}
							}
						}
					}
					// Respond to the validation
					if(properties.errors) {
						properties.error_container.show();
						errors = true;
					}
					properties = {};
				}
			}
			return ((errors) ? false : true);
		}
	};

	function displayErrors(properties, field) {
		error_class = 'has_error';
		if(properties.rules[properties.rule] == true) {
			if(!properties.global_error_shown && properties.rules.hasOwnProperty('message')) {
				properties.error_container.innerHTML = properties.rules.message;
				properties.global_error_shown = true;
			}
			if(field !== undefined) {
				if(!field.hasClassName(error_class)) {
					field.addClassName(error_class);
				}
			}
		} else {
			properties.error_container.innerHTML += properties.rules[properties.rule];
			if(field !== undefined) {
				if(!field.hasClassName(error_class)) {
					field.addClassName(error_class);
				}
			}
		}
		properties.errors = true;
		return properties;
	}

})();



/* /functions/calculator.js */

 /* Calculator
=============================================================================== */
(function () {
	
	MMH.Calculator = {
		name: null,
		init: function(name) { 
			MMH.Calculator.name = name;
			MMH.Calculator.observers();
		},
		observers: function() {	        
	        /* Reset Form */
	        $$('a.start_over').each(function(a) {
	        	a.observe('click', function(ev) {
					$$('.error').each(function(div) { 
						div.innerHTML = "";
						div.hide();
					});
					$$('.js_prompt_text').each(function(el) {
						el.setStyle({ color:'#83847F'});
					});
					Event.stop(ev);
				});
	        });
		},	
		getArea: function (width, length) {
			if(parseFloat(width.value) && parseFloat(length.value)){
				return parseInt(width.value)*parseInt(length.value);	
			}
			return false;
		},
		getSurfaceArea: function(width, length, height) { 
			if(parseFloat(width.value) && parseFloat(length.value) && parseFloat(height.value)) { 
				return (2 * length.value * width.value) + (2 * length.value * height.value) + (2 * width.value + height.value)
			}	
			return false;
		},
		popUp: function (options) {
		  	MMH.Calculator._omniture_tagger(options);
			Object.extend(options, options || {});
			if (options.name === undefined){
			  options.name = '';
			}
			popup = window.open(options.url, options.name, 'width='+options.width+',height='+options.height+',resizeable=1,scrollbars='+options.scrollbars+',left=150,top=150');
			return popup;
		},
		_omniture_tagger: function(options) { 
			var eVar7;
			if(options.url.match(/fertilizer/)) { 
				eVar7 = 'Fertilizer Calculator'; 
			} else if(options.url.match(/mulch/)) { 
				eVar7 = 'Mulch Calculator'; 
			} else if(options.url.match(/grass_seed/)) { 
				eVar7 = 'Grass Seed Calculator'; 
			} else if(options.url.match(/interior_paint/)) { 
				eVar7 = 'Interior Paint Calculator'; 
			} else if(options.url.match(/exterior_paint/)) { 
				eVar7 = 'Exterior Paint Calculator'; 
			} else if(options.url.match(/driveway_sealer/)) { 
				eVar7 = 'Driveway Sealer Calculator';
			} else if(options.url.match(/plant_symptom/)) { 
				eVar7 = 'Plant Symptom Finder';
			} else if(options.url.match(/plant_symptom/)) { 
				eVar7 = 'Plant Symptom Finder';
			} else if(options.url.match(/paint_bundle/)) { 
				eVar7 = 'Recycling Center Finder';
			}
		  	s.linkTrackVars="eVar7,events";
		  	s.linkTrackEvents="event3";
		  	s.eVar7=eVar7;
		  	s.events="event3";
		  	void(s.t());
		  	s.tl(options.url,'o',eVar7);
		}
	};
})();


/* /functions/pixel_math.js */

 PixelMath = {
  toPx: function(number, negative) {
    if (number == null) {
      number = '0';
    }
    if (negative === undefined) {
      return number + 'px';
    }
    return (negative ? Math.abs(number) + 'px' : -number + 'px');
  },
  toNum: function(pixel, negative) {
    if (typeof pixel == 'number') {
      if (negative) {
        return Math.abs(pixel);
      }
      else {
        return pixel;
      }
    }
    if (pixel == null) {
      pixel = '0';
    }
    if (negative === undefined) {
      return parseInt(pixel.replace('px', ''));
    }
    return (negative ? Math.abs(pixel.replace('px', '')) : -pixel.replace('px', ''));
  },
  getCompleteElementWidth: function(element) {
    var width = 0;
    ['width','paddingRight','paddingLeft','marginRight','marginLeft','borderRightWidth','borderLeftWidth'].each(function(css_property) {
      css_value = PixelMath.toNum(element.getStyle(css_property));
      if (isNaN(css_value)) {
        css_value = 0;
      }
      width = width + css_value;
    });
    return width;
  },
  getCompleteElementHeight: function(element) {
    var height = 0;
    ['height','paddingTop','paddingBottom','marginTop','marginBottom','borderTopHeight','borderBottomHeight'].each(function(css_property) {
      css_value = PixelMath.toNum(element.getStyle(css_property));
      if (isNaN(css_value)) {
        css_value = 0;
      }
      width = width + css_value;
    });
    return height;
  }
}



/* /functions/buttons.js */

 var button = {
	
  disable : function(submit_button) {
   if (submit_button) {
    if(submit_button.hasClassName('buttons')) {
      this.newButton.disable(submit_button);
    } else {
      this.oldButton.disable(submit_button);
    }

    p_link = this.preview_link.exists(submit_button);
    if(p_link) {
      this.preview_link.disable(p_link);
    }
   }
  },
  enable : function(submit_button) {
    if(submit_button.hasClassName('buttons')) {
      this.newButton.enable(submit_button);
    } else {
      this.oldButton.enable(submit_button);
    }

    p_link = this.preview_link.exists(submit_button);
    if(p_link) {
      this.preview_link.enable(p_link);
    }
  },
  newButton : {
    // New way
    disable : function(submit_button) {
      submit_button.addClassName('disabled');
      if(submit_button.hasClassName('js_button_indicator')) {
        submit_button.removeClassName('indicator');
      }
    },
    enable : function(submit_button) {
      submit_button.removeClassName('disabled');
      if(submit_button.hasClassName('js_button_indicator')) {
        submit_button.observe('click', function(){
          submit_button.addClassName('indicator');
        });
      }
    }
  },
  oldButton : {
    // From the days of past
    disable : function(submit_button) {
      var class_name = this.findClass(submit_button);
      if(!submit_button.hasClassName('disabled')) {
        var disabled_class_name = class_name + '_off';
        submit_button.addClassName('disabled');
        submit_button.removeClassName(class_name);
        submit_button.addClassName(disabled_class_name);
      }
      if(submit_button.hasClassName('js_has_indicator')) {
        submit_button.up('form').select('.js_indicator').invoke('hide');
      }
    },
    enable : function(submit_button) {
      var class_name = this.findClass(submit_button);
      var enabled_class_name = class_name.replace('_off', '');
      submit_button.removeClassName('disabled');
      submit_button.removeClassName(class_name);
      submit_button.addClassName(enabled_class_name);
      if(submit_button.hasClassName('js_has_indicator')) {
        submit_button.observe('click', function(){
          submit_button.up('form').select('.js_indicator').invoke('show');
        });
      }
    },
    findClass : function(submit_button) {
      var button_text = submit_button.innerHTML;
      button_text = button_text.replace(/ /g, "_");
      var regular_expression = new RegExp("^" + button_text, "i");
      var value;
      $w(submit_button.className).each(function(clazz) {
        if(clazz.match(regular_expression) != null) {
          value = clazz;
        }
      });
      return value;
    }
  },

  preview_link : {
    enable : function(preview) {
      preview.removeClassName('disabled');
    },
    disable : function(preview) {
      preview.addClassName('disabled');
    },
    exists : function(submit_button) {
      return submit_button.up('form').select('.js_preview').first();
    }
  }
};



/* /classes/validate_fields.js */

 ValidateFields = Class.create();
ValidateFields.prototype = {
  initialize: function(form_object){
    this.form = form_object;
    this.button = this.form.select('.js_submit_form').first();
    this.required_fields = this.form.select('.js_required');
    this.valid_email = this.form.select('.js_email');
    this.valid_zip = this.form.select('.js_zip');
    this.onload_required_flag = true;
  },

  onLoadValidateRequired: function() {
    this.required_fields.each(function(element) {
      if(element.value == element.title || element.value == '') {
        this.onload_required_flag = false;
      }
    });
  },

  validateRequired: function() {
    input_flag = true;
    this.required_fields.each(function(element) {

      if(element.value == element.title || element.value == '') {
  	input_flag = false;
      }
      return input_flag; //FIXME: if so, why iterate?
    });
  },

  validateNonEmpty: function() {
    input_flag = true; //FIXME: im forced to use the GLOBAL variable because i do not want to refactor this file without consulting original authors; please fix this.
    this.required_fields.each(function(element) {
			input_flag = input_flag && !(element.value == element.title || element.value.strip() == '');
    });
    return input_flag;
  },

  validateEmail: function() {
    email_flag = true;
    this.valid_email.each(function(element) {
      var email_pattern = /^([0-9a-zA-Z]([-.\w]*[0-9a-zA-Z])*@([0-9a-zA-Z][-\w]*[0-9a-zA-Z]\.)+[a-zA-Z]{2,9})$/;

      /* for required email */
      if(element.hasClassName('js_required')){
        input_flag = false;
      }

      /* for email with prompt text */
      if(element.hasClassName('js_prompt_text')) {
        var email_prompt = element.title;
      }

      /* for email not matching pattern */
      if (element.value && element.value != email_prompt && !email_pattern.test(element.value)) {
        email_flag = false;
        $('email_error').show();

      /* for valid email or optional email */
      }	else {
        $('email_error').hide();
        email_flag = true;
      }

      return email_flag;

    });
  },

  validateZip: function() {
    zip_flag = true;
    this.valid_zip.each(function(element) {
      var zip_pattern = /^(\d{5})$/i;

      /* for required zip */
      if(element.hasClassName('js_required')){
        input_flag = false;
      }

      /* for zip not matching pattern */
      if (element.value && !zip_pattern.test(element.value)) {
        zip_flag = false;
    	$('maint_zip_code_error').show();

        /* for valid zip or optional zip */
        } else {
          $('maint_zip_code_error').hide();
          zip_flag = true;
        }

        return zip_flag;

    });
  },

  onLoadButtonRequiredInput: function() {
    if(this.onload_required_flag == false) {
      button.disable(this.button);
    }
  },

  updateButtonRequiredInput: function() {
    ((input_flag == false) ? button.disable : button.enable).call(button, this.button);
  },

  updateButton: function() {

    if(typeof zip_flag == 'undefined') {
      zip_flag = true;
    }

    if(typeof email_flag == 'undefined') {
      email_flag = true;
    }

    if(zip_flag == false || email_flag == false) {
      button.disable(this.button);
    } else {
      if(input_flag == true) {button.enable(this.button);}
    }
  }

};



/* /classes/base_carousel.js */

 
/* classes/base_carousel.js */

 BaseCarousel = Class.create();
Object.extend(BaseCarousel.prototype, {
    initialize: function(carousel_id, params, callbacks) {
        if (typeof carousel_id == 'undefined') return;   //this hack makes object.extend work for inheritance in prototype1.5
        this.carousel_id = carousel_id;
        this.disabled_class = 'js_disabled';
        var config = Object.extend({}, {
            slide_by      : 1,
            window_size   : 1,
            loop          : false,
            duration      : 1,
            add_to_ul_width : 0,
            window_overflow: 0
        });

        this.config = Object.extend(config, params || {});

        this.setCallbacks(callbacks);
    },

    customize: function() {

    },

    toggleControls: function(){
      if (this.dom.previous_button) {
        if(this.last_current_item == this.dom.li_items.length){
          this.dom.next_button.hide();
        } else {
          this.dom.next_button.show();
        }
      }

      if (this.dom.previous_button) {
        if(this.first_current_item== 1){
          this.dom.previous_button.hide();
        } else {
          this.dom.previous_button.show();
        }
      }
    },

    setCallbacks: function(callbacks) {
        var default_callbacks = Object.extend({}, {
            beforeSlide:function() {
            },
            afterSlide:function() {
            }
        });

        this.callbacks = Object.extend(default_callbacks, callbacks);
    },

    setUp: function() {
        var carousel = $(this.carousel_id);
        this.dom = this.setDOM(carousel);
        this.total_items = this.dom.li_items.length;
        this.first_current_item = 1;
        var itemViewCount = this.total_items;
        if (this.total_items > this.config.window_size) {
            itemViewCount = this.config.window_size;
        }
        this.last_current_item = this.first_current_item + itemViewCount - 1;


        this.dimensions = this.setDimensions();
        var scroll = new Scroll(this.first_current_item, this.first_current_item, this.last_current_item, this.total_items, this.config, this.dom, this.callbacks);
        scroll.setPositionInfo();
        this.setupUI();
        this.attachListeners();
        this.customize();
        this.toggleControls();
    },

    itemsSmallerThanWindow: function(total_items) {
        return total_items < this.config.window_size;
    },

    setDOM: function(carousel) {
        return {
            carousel:           carousel,
            window:             carousel.down('.carousel_window'),
            ul_item:            carousel.down('.carousel_items'),
            li_items:           carousel.getElementsBySelector('.carousel_item'),
            next_button:        carousel.down('.carousel_next'),
            previous_button:    carousel.down('.carousel_previous'),
            first_current_item: carousel.down('.carousel_first_current_item'),
            last_current_item:  carousel.down('.carousel_last_current_item'),
            total_items:        carousel.down('.carousel_total'),
            section_items:      carousel.getElementsBySelector('.carousel_section')
        };
    },

    setDimensions: function() {
        dimensions = {};
        dimensions.ul_width = this.calculateWidth(this.total_items, this.config.add_to_ul_width);
        dimensions.window_width = this.calculateWidth(this.config.window_size, this.config.window_overflow);
        return dimensions;
    },

    setupUI: function() {
        this.dom.li_items.first().addClassName('first-child');
        this.dom.li_items.last().addClassName('last-child');
        this.dom.carousel.addClassName('horizontal_carousel');
        this.dom.window.setStyle({
            width:PixelMath.toPx(this.dimensions.window_width)
        });
        this.dom.ul_item.setStyle({
            width:PixelMath.toPx(this.dimensions.ul_width)
        });
    },

    calculateWidth: function(no_of_items, margin) {
        var total_width = 0;
        for (i = 0; i < no_of_items; i++) {
            var element = this.dom.li_items[i];
            if (element) total_width += PixelMath.getCompleteElementWidth(element);
        }

        return total_width + margin;
    },

    next: function() {
        var scroll = new Scroll(this.first_current_item, this.first_current_item, this.last_current_item, this.total_items, this.config, this.dom, this.callbacks);
        items = scroll.next();
        this.first_current_item = items[0];
        this.last_current_item = items[1];
        this.toggleControls();
        return true;
    },

    previous: function() {
        var scroll = new Scroll(this.first_current_item, this.first_current_item, this.last_current_item, this.total_items, this.config, this.dom, this.callbacks);
        items = scroll.previous();
        this.first_current_item = items[0];
        this.last_current_item = items[1];
        this.toggleControls();
        return true;
    },

    attachListeners: function() {
        if (!this.attached) {
            this.attached = true;
            if (this.dom.next_button) {
                this.dom.next_button.observe('click', function(ev) {
                    ev.preventDefault();
                    Event.stop(ev);
                    this.next();
                }.bindAsEventListener(this));
            }
            if (this.dom.previous_button) {
                this.dom.previous_button.observe('click', function(ev) {
                    ev.preventDefault();
                    Event.stop(ev);
                    this.previous();
                }.bindAsEventListener(this));
            }
        }
    },
    register: function(observer) {
        this.observer = observer;
    }
});



/* /classes/base_expanding_menu.js */

 BaseExpandingMenu = Class.create();
Object.extend(BaseExpandingMenu.prototype, {
  initialize: function(base_expanding_menu_id) {
    if (typeof base_expanding_menu_id == 'undefined') return;   //this hack makes object.extend work for inheritance in prototype1.5
    this.base_expanding_menu = $(base_expanding_menu_id);
  },
  setup:function() {
    this.setupDOM();
    this.setupUI(this.dom.sub_menu_uls);
    this.setupEventListeners(this.dom.menu_lis);    
  },
  setupDOM: function() { 
    this.dom = {};
    this.dom.menu_ul = this.base_expanding_menu;
    this.dom.menu_lis = this.base_expanding_menu.getElementsBySelector('li.menu_item');
    this.dom.sub_menu_uls = this.base_expanding_menu.getElementsBySelector('ul.sub_menu');
    this.dom.sub_menu_lis = this.base_expanding_menu.getElementsBySelector('li.sub_menu_item');
    this.dom.lis = this.base_expanding_menu.getElementsBySelector('li');
  },
  setupUI: function(sub_menu_uls) {
    sub_menu_uls.each(function(sub_menu_ul) {
      sub_menu_ul.addClassName('hide');
    });
  },    
  setupEventListeners: function(menu_lis) {
    menu_lis.each(function(li) {
      if(li.down('ul')) {
        li.down('a').observe('click', function(ev) {
          Event.stop(ev);
          a = Event.element(ev);
          this.selectMenuItem(a.up('li').id);
        }.bind(this));
      }
    }.bind(this));
  },
  
  /* Route to correct menu selection */
  selectItem: function(item) {
    if(item) {
      if($(item).hasClassName('menu_item')) {
        this.selectMenuItem(item); // Open a menu item
      } else {
        this.selectSubMenuItem(item); // Open a sub menu item
      }
    }
  },
  
  /* Menu Items */ 
  selectMenuItem: function(menu_li) {
    selected_menu_item = this.getSelectedMenuItem();
    if (selected_menu_item) {
      this.unselectMenuItem(selected_menu_item);
    }
    sub_menu_ul = $(menu_li).down('ul.sub_menu');
    if (sub_menu_ul) {
      sub_menu_ul.removeClassName('hide').addClassName('show');
      $(menu_li).addClassName('expanded');
    }
  },
  unselectMenuItem: function(menu_li) {
    sub_menu_ul = $(menu_li).down('ul.sub_menu');
    if (sub_menu_ul) {
      sub_menu_ul.removeClassName('show').addClassName("hide");
      $(menu_li).removeClassName('expanded');
    }
  },
  getSelectedMenuItem: function() {
    return this.dom.menu_ul.getElementsBySelector('li.expanded').first();
  },

  /* Sub Menu Items */
  selectSubMenuItem: function(sub_menu_li) {
    selected_li = this.getSelectedSubMenuItem();
    if(selected_li) {
      this.unselectSubMenuItem(selected_li);
    }
    this.selectMenuItem($(sub_menu_li).up('li'));
    
    $(sub_menu_li).addClassName('selected');
  },
  unselectSubMenuItem: function(sub_menu_li) {
    $(sub_menu_li).removeClassName('selected');
  },
  getSelectedSubMenuItem: function() {
    return this.dom.menu_ul.getElementsBySelector('li.selected').first();
  }
});



/* /classes/scroll.js */

 
/* classes/scroll.js */

 Scroll = Class.create();
Object.extend(Scroll.prototype, {
  initialize: function(previous_state, first_current_item, last_current_item, total_items, config, dom, callbacks) {
    this.previous_state = previous_state;
    this.first_current_item = first_current_item;
    this.last_current_item = last_current_item;
    this.total_items = total_items;
    this.width = PixelMath.getCompleteElementWidth(dom.li_items[0]);
    var base_config = Object.extend({}, {
        total_per_items : total_items,
        per_item       : 1
    });
    this.config = Object.extend(base_config, config || {});
    this.dom = dom;
    this.callbacks = callbacks;
  },

  next: function() {
    if (this.last_current_item < this.total_items) {
      this.calculatePositionInfo(true);
      this.setPositionInfo();
      this.run(this.toOffset());
    }
    return [this.first_current_item, this.last_current_item];
  },

  previous: function() {
    if (this.first_current_item > 1) {
      this.calculatePositionInfo(false);
      this.setPositionInfo();
      this.run(this.toOffset());
    }
    return [this.first_current_item, this.last_current_item];
  },

  toOffset: function() {
    var end = this.first_current_item;
    var offset = 0;
    for( i = 0; i< end - 1; i++){
        var element = this.dom.li_items[i];
        offset += PixelMath.getCompleteElementWidth(element);
    }
    return -(offset);
    // TODO: loop effect
  },

  move: function() {
    this.setPositionInfo();
    this.run(this.toOffset());
  },

  slide: function(previous_state){
  	var start = this.previous_state;
  	var end = this.first_current_item;
    this.setPositionInfo();
    this.run(this.toOffset());

  },
  calculatePositionInfo: function(is_forward) {
    if (is_forward) {
      if (this.total_items - this.last_current_item < this.config.slide_by) {
        this.first_current_item += this.total_items - this.last_current_item;
      } else {
        this.first_current_item += this.config.slide_by;
      }
    } else {
      if (this.first_current_item - this.config.slide_by < 1) {
        this.first_current_item = 1;
      } else {
        this.first_current_item -= this.config.slide_by;
      }
    }
    if (this.config.window_size == 1) {
      this.last_current_item = this.first_current_item;
    } else {
      this.last_current_item = this.first_current_item + (this.config.window_size - 1);
    }
  },

  setPositionInfo: function() {
    if (this.dom.first_current_item) {
      this.dom.first_current_item.innerHTML = 1 + (this.config.per_item * (this.first_current_item-1));
    }
    if (this.dom.last_current_item) {
      last = this.config.per_item + (this.config.per_item * (this.last_current_item-1));
      this.dom.last_current_item.innerHTML = last > this.config.total_per_items ? this.config.total_per_items : last;
    }
    if (this.dom.total_items) {
      this.dom.total_items.innerHTML = this.config.total_per_items ? this.config.total_per_items : this.total_items;
    }
  },

  run: function(to_offset) {
    new Effect.Move(this.dom.ul_item, {
      x: to_offset, y: 0, mode: 'absolute', duration: this.config.duration,
      beforeStart: (function() {
        if(this.callbacks.beforeSlide) {
          this.callbacks.beforeSlide();
        }
      }.bind(this)),
      afterFinish: (function() {
        this.callbacks.afterSlide();
      }.bind(this))
    });
    return true;
  }
});



/* /classes/drag_support.js */

 // create the MMH namespace if it doesn't exist
var MMH;
if (typeof MMH === 'undefined') {
	MMH = {};
}

// dependencies
if (typeof MMH.Common === 'undefined') {
  throw new Error('MMH.DragSupport requires MMH.Common');
}

var Prototype;
if (typeof Prototype === 'undefined') {
  throw new Error('MMH.DragSupport requires Prototype');
}

// definitions
MMH.DragSupport = Class.create();
Object.extend(MMH.DragSupport.prototype, {
    initialize: function(element, handle) {
        this.element = $(element);
        this.handle = $(handle);        

        // bind these methods for events
        this._down = this.down.bindAsEventListener(this);
        this._move = this.move.bindAsEventListener(this);
        this._up = this.up.bindAsEventListener(this);
        this.handle.observe('mousedown', this._down);
    },
    
    down: function(event) {
        Event.stop(event);
        this.pos = Position.cumulativeOffset(this.element);
        this.startx = Event.pointerX(event) - this.pos[0];
        this.starty = Event.pointerY(event) - this.pos[1];
        $(document.body).observe('mouseup', this._up);
        $(document.body).observe('mousemove', this._move);
    },

    move: function(event) {
        Event.stop(event);
        var newx = Event.pointerX(event),
            newy = Event.pointerY(event);
        this.element.setStyle({
            left: newx - this.startx + 'px',
            top: newy - this.starty + 'px'
        });
    },

    up: function(event) {
        Event.stop(event);
        $(document.body).stopObserving('mouseup', this._up);
        $(document.body).stopObserving('mousemove', this._move);
    }
});



/* /classes/expand_collapse.js */

 ExpandCollapse = Class.create();
Object.extend(ExpandCollapse.prototype, {
  initialize: function(collapse_marker, expand_marker, optional_element_marker, collapse_all_marker, expand_all_marker){
    this.marker_for_collapse = collapse_marker;
    this.marker_for_expand = expand_marker;
    this.marker_for_optional_element = optional_element_marker;

    this.marker_for_collapse_all = collapse_all_marker;
    this.marker_for_expand_all = expand_all_marker;
  },
             
  observe: function() {
    $(this.marker_for_collapse).observe('click', function(event) {
      this.collapse();
      Event.stop(event);
    }.bind(this));
    $(this.marker_for_expand).observe('click', function(event) {
      this.expand();
      Event.stop(event);
    }.bind(this));
  },

  collapse: function() {
    $(this.marker_for_collapse).hide();
    $(this.marker_for_expand).show();

    $(this.marker_for_optional_element).hide();
  },

  expand: function() {
    $(this.marker_for_collapse).show();
    $(this.marker_for_expand).hide();

    $(this.marker_for_optional_element).show();
  },

  observeAll: function(){
    $(this.marker_for_collapse_all).observe('click', function(event){
      this.collapseAll();
      Event.stop(event);
    }.bind(this));
    
    $(this.marker_for_expand_all).observe('click', function(event){
      this.expandAll();
      Event.stop(event);
    }.bind(this));
  },

  collapseAll: function(){
    $$(this.marker_for_collapse).invoke('hide');
    $$(this.marker_for_expand).invoke('show');
    $$(this.marker_for_optional_element).invoke('hide');
  },

  expandAll: function(){
    $$(this.marker_for_collapse).invoke('show');
    $$(this.marker_for_expand).invoke('hide');
    $$(this.marker_for_optional_element).invoke('show');
  }
});



/* /classes/filter_list.js */

 FilterList = Class.create();
FilterList.prototype = {
  initialize:function(select_box_id, params, callbacks) { 
    this.dom = {};
    this.dom.select_box 	= $(select_box_id);
    this.dom.select_options	= $(select_box_id).getElementsBySelector('option');
    
    // Merge params
    var config = { default_selection: 'all' };
    this.config = Object.extend(config, params);
    var default_callbacks = { beforeChange:function(){}, afterChange:function(){} };
    this.callbacks = Object.extend(default_callbacks, callbacks);

    // Hide all if needed
    this.dom.select_options.each(function(option) { 
      if(option.value != 'all') {
        $(option.value).hide();
      }
    });
    
    // Attach listeners
    this.dom.select_box.observe('change', function(ev) { 
      this.filter(this.dom.select_box.getValue());
    }.bindAsEventListener(this));
    
    // Select default
    this.filter(this.config.default_selection);
  },
  filter:function(option_value) { 
    this.callbacks.beforeChange(this.dom.select_box.getValue());
    
    // Hide all, if not showing all
    if(option_value != 'all') { 
      this.dom.select_options.each(function(option) {
        if(option.value != 'all') {
          $(option.value).hide();
        }
      });
    }
    
    // Filter to new selection
    if(option_value == 'all') { 
      this.dom.select_options.each(function(option) { 
        if(option.value != 'all') {
          $(option.value).show();
        }
      });
    } else { 
      $(option_value).show();
    }
    
    this.callbacks.afterChange(option_value);
  },
  reset:function() { 
    this.filter(this.config.default_selection);
  }
};


/* /classes/tabs.js */

 Tabs = Class.create();
Tabs.prototype = {

  onTabChange : null,
  autoPlayInterval : 0,
  currentTab : null,

  initialize: function( tabs_id, params, callbacks ) {
    this.tabs_id = tabs_id;
    // Merge params & callbacks
    var config = {
      target_event      : 'click',
      default_tab       : 0,
      container         : 'container',
      hide_previous_tab : true,
      ajax              : false,
      ajax_parameters   : {},
      autoload          : true,
      autoPlay          : false,
      animate           : false,
      autoPlayDelay     : 5000,
	  stopOnEvent		: true
    };

    this.config = Object.extend(config, params || {} );
    var default_callbacks = {
      beforeChange : Prototype.emptyFunction,
      afterChange  : Prototype.emptyFunction
    };

    this.callbacks =  Object.extend(default_callbacks, callbacks || {} );

    if(this.config.autoload == true) {
      this.setUp();
    }
  },

  setUp: function(){
    // Class used to disable UI controls
    this.disabled_class = 'js_disabled';
    // Store DOM Elements
    this.dom = {};

    this.dom.tabs = $(this.tabs_id);
    this.dom.tab_links = this.dom.tabs.getElementsBySelector('a');

    // Insert :first-child and :last-child on the li
    this._insertPseudoClasses(true);

    // Attach event listeners on tabs
    this.dom.tab_links.each( function( tab_link ) {
      if( !this.config.ajax ) {
        element_div_source = $(this._getContentIdFromTabLink( tab_link ));
        if( element_div_source && this.config.hide_previous_tab ) {
          element_div_source.hide();
        }
      }

      tab_link.observe( this.config.target_event,
                        function(ev) {
                          Event.stop(ev);
		                      
		                      if( this.config.stopOnEvent ) {
		  	                    this.killAutoPlaying();
		                      }
		                      
                          if( !tab_link.hasClassName( this.disabled_class ) ) {
                            this.select( tab_link );
                          }
                        }.bindAsEventListener(this)
                      );
    }.bind(this));

    // Store the default tab as a Prototype object
    if( isNaN(this.config.default_tab) ) {
      this.dom.tab_links.each( function(el) {
        if( this._getContentIdFromTabLink(el) == this.config.default_tab ) {
          this.config.default_tab = el;
        }
      }.bind(this));
    } else {
      this.config.default_tab = this.dom.tab_links[this.config.default_tab];
    }

    // Select the default tab
    this.select( this.config.default_tab );
    this.currentTab = this.config.default_tab;
    this._toggleControls(true);
    if(this.config.autoPlay){
      var scope = this;
      this.addEventsAutoPlaying();
      this.autoPlayInterval = setInterval(function(){
        scope.autoPlaying(scope);
      }, this.config.autoPlayDelay, this);
    }
  },

  autoPlaying : function(scope){
    if(scope.autoPlayingRules()){
      var nextTab = scope.getNextTab();
      scope.select($(nextTab));
    }
  },

  addEventsAutoPlaying : function(){
    var content_id = this.config.container, scope = this;
    $(content_id).observe( "mouseover", function( event ){
      scope.killAutoPlaying();
    });
  },

  getNextTab : function() {
    var nextTab = (
      $( $(this.current_tab).parentNode ).nextSiblings().length ) ?
        $( $( $(this.current_tab).parentNode ).nextSiblings()[0].
           getElementsBySelector("a")[0]) :
        $(this.dom.tab_links[0]);
    return nextTab;
  },

  autoPlayingRules : function(){
  return true ;
  },

  killAutoPlaying : function(){
    clearInterval(this.autoPlayInterval);
  },

  reload: function() {
    if( typeof this.dom != 'undefined' ) {
      this.select(this.current_tab);
    } else {
      this.setUp();
    }
  },

  _insertPseudoClasses: function(initializing) {
    // Store DOM Queries
    var first_tab = this.dom.tab_links.first().up('li');
    var last_tab = this.dom.tab_links.last().up('li');

    // Initial Pseudo class insertions, done once
    if(initializing !== undefined) {
      first_tab.addClassName('first-child');
      last_tab.addClassName('last-child');
    }

    // We can't select by two classes in IE6, so we must insert a another class
    if(first_tab.down('a').hasClassName('active')) {
      first_tab.addClassName('first-child-active');
    } else {
      first_tab.removeClassName('first-child-active');
    }
    if(last_tab.down('a').hasClassName('active')) {
      last_tab.addClassName('last-child-active');
    } else {
      last_tab.removeClassName('last-child-active');
    }
  },

  loadContentForTab: function( tab ) {
    if( this.config.ajax ) {
      this._hide( this.config.container );
      new Ajax.Updater(
        this.config.container,
        tab.href.split('#')[0],
        {
          method      : 'get',
          parameters  : this.config.ajax_parameters,
          evalScripts : true,

          onCreate    : function() {
            this._toggleControls(false);
            $(this.config.container).addClassName('loading');
          }.bind(this),

          onComplete  : function() {
            this._toggleControls(true);
            $(this.config.container).removeClassName('loading');
            this.callbacks.afterChange( tab );
            this._show( this.config.container );
          }.bind(this)
        }
      );
    } else {
      this._toggleControls(true);
      element_div_source = $(this._getContentIdFromTabLink( tab ));

      if( element_div_source ) {
        var div = element_div_source;
        this._show( div );
      }
      this.callbacks.afterChange( tab );
    }
  },

  deselectTabs: function () {
    var self = this;
    this.dom.tab_links.each(
      function( tab_link ) {
        if( tab_link.hasClassName('active') ) {
          this.callbacks.beforeChange( tab_link );

          tab_link.removeClassName( 'active' );
          tab_link.removeClassName(
            this._getContentIdFromTabLink(tab_link) + '_tab_active'
          );
          tab_link.up('li').removeClassName( 'active' );

          if( !this.config.ajax ) {
            element_div_source = $(this._getContentIdFromTabLink(tab_link));
            if(element_div_source && this.config.hide_previous_tab) {
              var div = element_div_source;
              self._hide( div );
            }
          } else {
            $(this.config.container).childElements().each( function( el ) {
              self._hide( el );
            });
          }
          throw $break;
        }
      }.bind(this)
    );
  },

  _hide: function( element ) {
    var el = $(element);
    if( this.config.animate ) {
      new Effect.Fade( el, {
        duration: 0.5,
        queue: {
          position: 'end',
          scope: 'tab_transition'
        }
      });
    } else {
      el.hide();
    }
  },

  _show: function( element ) {
    var el = $(element);
    if( !this.isloaded ) this.isloaded = this.config.ajax;
    if( this.config.animate && this.isloaded ) {
      new Effect.Appear( el, {
        duration: 0.5,
        queue: {
          position: 'end',
          scope: 'tab_transition'
        }
      });
    } else {
      el.show();
    }
    this.isloaded = true;
  },

  select: function( tab_to_select ) {
    this.current_tab = tab_to_select;
    this._toggleControls(false);

    this.deselectTabs();

    tab_to_select.addClassName('active');

    tab_to_select.addClassName(
      this._getContentIdFromTabLink(tab_to_select) + '_tab_active'
    );

    tab_to_select.up('li').addClassName('active');

    this._insertPseudoClasses();

    this.loadContentForTab( tab_to_select );
  },

  reset: function() {
    this.select(this.config.default_tab);
    this.loadContentForTab( this.config.default_tab );
  },

  _getContentIdFromTabLink: function(tab_link) {
    return tab_link.href.split('#')[1];
  },

  showEightTabs: function() {
    this.dom.tab_links.each( function(tab_link, index){
      if(index > 7) tab_link.up('li').addClassName('hide');
    });
  },

  _toggleControls: function(enable) {
    if(enable) {
      this.dom.tab_links.each(function(el) {
        el.removeClassName(this.disabled_class);
      }.bind(this));
    } else {
      this.dom.tab_links.each(function(el) {
        el.addClassName(this.disabled_class);
      }.bind(this));
    }
  }
};



/* /classes/accordion.js */

 Accordion = Class.create();
Accordion.prototype = {
  initialize: function(toggle, content){
    this.toggle = toggle;
    this.content = content;
  },

  observe: function(){
    $$(this.toggle).each(function(item){
      item.down('a').observe('click', this.respond.bindAsEventListener(this));
    }, this);
  },

  respond: function(event){
    var element = Event.element(event);
    this.play(element);
    event.stop();
  },
  
  play: function(element){  
    var h4 = element.up('h4');
    var bellow = h4.next(this.content);
    $$(this.content).each(function(item){
      if(item.id != bellow.id) {
        item.hide();
      }
    },this);
    bellow.show();
  },

  onLoad: function(){
     $$(this.content).each(function(item){
       item.hide();
     }, this);
     $$(this.content)[0].show();
  }
};


/* /classes/menu.js */

 /* PLEASE USE BASE_EXPANDING_MENU.JS from now on -- thanks! 11/10/08 */
(function () {

  MMH.Menu = {
    
    menuId: null,
    listItems: { 
      mainMenu:	'li.leftNavCatItem',
      subMenu:	'li.leftNavSubCatItem'
    },

    initMenu: function (menuId, list_item, sub_list_item) {			
      // Store the prototype menu object 
      MMH.Menu.menuId = menuId;
      
      // Setup listeners on all main list items and sub menu list items
      $$(MMH.Menu.listItems.mainMenu).each(function(list_item) {
        if (!list_item.hasClassName('follow_href')){
        list_item.firstDescendant().observe('click', function(e) {
          MMH.Menu.page.close();
          MMH.Menu.page.open($(list_item));
          Event.stop(e);
        });
      }
      });
      $$(MMH.Menu.listItems.subMenu).each(function(list_item) { 
        list_item.firstDescendant().observe('click', function(e) { 
          MMH.Menu.page.highlight($(list_item));
          Event.stop(e);
        });
      });
      
      // Register external links that interact with the menu
      MMH.Menu.page.registerExternalLinks();
      
      // Expand Main Menu and highlight Sub Menu Item on pageload if needed
      if(list_item !== undefined) { 
        MMH.Menu.page.open($(list_item));
      }
      if(sub_list_item !== undefined) { 
        MMH.Menu.page.highlight($(sub_list_item));
      }
    },

    page: {		
      // Interacts with the Main Menu		
      open:function(list_item) { 
        if(list_item === undefined) { 
          $(MMH.Menu.menuId).childElements().each(function(list_item) { 
            showMainMenuTasks(list_item);
          });
        } else { 
          if(list_item) { 
            showMainMenuTasks(list_item);
          }
        }
        MMH.Menu.page.scrollToTop();
      },
      
      // Interacts with the Main Menu
      close:function(list_item) { 
        if(list_item === undefined) { 
          $(MMH.Menu.menuId).childElements().each(function(list_item) {
            hideMainMenuTasks(list_item);
          });
        } else { 
          if(list_item) { 
            hideMainMenuTasks(list_item);
          }
        }
        toggleSelectedListItems(MMH.Menu.listItems.subMenu);
      },
      
      // Interacts with the Sub Menu
      highlight:function(list_item) { 
        if(list_item) { 
          toggleSelectedListItems(MMH.Menu.listItems.subMenu, list_item);
        }	
        MMH.Menu.page.scrollToTop();
      },
      
      // Interacts with the Main Menu & Sub Menu
      scrollToTop:function() {
        
        //Brings the user to top of page after menu click
        window.scrollTo(0,0);

      },
      
      // Interacts with the Main Menu & Sub Menu
      // Registers new links with listeners as they are added with ajax. Call this in the ajax callback
      registerExternalLinks:function(context) { 
        if(context === undefined) { 
          links = $$('a.externalMenuLink');	
        } else {
          links = $(context).getElementsBySelector('a.externalMenuLink');
        }
        links.each(function(link) { 
          link.observe('click', function(ev) { 
            menuInteractionThroughQueryString(link.href);
            Event.stop(ev);
          });
        });
      }
    }
  };
  
    
  /* Heavy Lifting Below */
    
  function showMainMenuTasks(list_item) {
    $A($(list_item).childElements()).each(function(unordered_list) { 
      $(unordered_list).show();
    });

    // Modification for calculator lists
    $A(list_item.getElementsByClassName('calculator')).each(function(calculator) { 
      $(calculator).show();
    });
    
    toggleSelectedListItems(MMH.Menu.listItems.mainMenu, list_item);
  }
    
  function hideMainMenuTasks(list_item) { 
    $A(list_item.getElementsByTagName('ul')).each(function(unordered_list) { 
      $(unordered_list).hide();
    });
    
    // Modification for calculator lists
    $A(list_item.getElementsByClassName('calculator')).each(function(calculator) { 
      $(calculator).hide();
    });
    
    toggleSelectedListItems(MMH.Menu.listItems.mainMenu);
  }
  
  function toggleSelectedListItems(context, list_item) { 
    $$(context).each(function(list_item) { 
      $(list_item).removeClassName('on');
      // alert('removing class');
    });
    if(list_item) { 
      $(list_item).addClassName('on');
    }
  }
  
  function menuInteractionThroughQueryString(query) { 
    // Get the query parameters

    //	might be able to use location.href.toQueryParams()
    query =	query.slice(query.indexOf('?'), query.length).replace('?','').split('&');
    
    // parse the query parameters and assign the values 
    query.each(function(value) { 
      parameter = value.split('=');
      switch(parameter[0]) { 
        case 'level2Index': 
          level2Index = parameter[1]; 
          break;
          
        case 'level3Index':	
          level3Index = parameter[1];	
          break;
      }				
    });

    // Interact with the menu
    if(level2Index) { 
      MMH.Menu.page.close();
      MMH.Menu.page.open($('leftNavCatItem' + level2Index));
      if(level3Index) { 
        MMH.Menu.page.highlight($('leftNavCat' + level2Index + 'Sub' + level3Index))
      }	
    }
  }
  
})();



/* /classes/modal.js */

 /*
   1. Work with old modals
   2. Positioning via JS
         1. Fixed or relative
         2. Offset possible
         3. Mouse based or trigger element based.
   3. Style via JS (compass direction)
         1. Directional arrows
         2. Close buttons.
   4. Attach and pass parameters
   5. Stacking
         1. Modal over modal
   6. Contents update (forgot password)
   7. Flags
         1. Draggable flag
         2. Close flag
         3. Effects Flags
 */

(function () {

  /*
  ** Modal: manages popup behavior between a link and a modal window
  **
  ** supported options:
  **  position = center (default), mouse, or element
  **  direction = auto (default), SE, SW, NE, NW
  **  distance = hash of {X: 4, Y: 4} (default)
  **  content = string modal content
  **  contentElement = element or ID that contains modal content
  **  href = link to modal content (default is element.href)
  **  anchor = element to display modal relative to (default is element)
  **  overlayStyles = hash of styles to be applied to overlay mask
  **  header = text to be inserted before modal content
  **  footer = text to be inserted after modal content
  **  tooltip = makes the modal behavior as a tooltip hover box (hooray!)
  */

  MMH.Modal = Class.create();

  var $modals = $A();
  var $zIndex = 50000;

  var $spacerGif = '/mmh/images/spacer.gif';

  var $contentCache = {};

  function $contentFor(element) {
    element = $(element);
    var content = $contentCache[element.id];
    if (content) {
      return content;
    }
    content = element.innerHTML;
    element.update();
    return ($contentCache[element.id] = content);
  }

  function $openedModal(element, options) {
    var modal = new MMH.Modal(element, options);
    modal.open();
    return modal;
  }

  Object.extend(MMH.Modal, {
    version: '0.1',

    openURL: function (url, options) {
      var element = $(document.createElement('a'));
      element.href = url;
      return $openedModal(element, options);
    },

    open: function (options) {
      var element = $(document.createElement('a'));
      element.href = '#';
      return $openedModal(element, options);
    },

    existsFor: function (element) {
      elem = $(element);
      return elem && elem.__modal__;
    }
  });

  Object.extend(MMH.Modal.prototype, {
    initialize: function (element, options) {
      this.options = Object.extend({
        position: 'center',
        direction: 'auto',
        distance: {X:4, Y:4},
        method: 'get',
        close_button: true,
        allowClick: false
      }, options);

      if (this.options.content) {
        this.content = this.options.content;
      } else if (this.options.contentElement) {
        this.content = $contentFor( this.options.contentElement );
      }

      if(this.options.tooltip) {
          this.options.close_button = false;
          this.options.overlay_hide = true;
      }

      this.element = $(element);
      if ( this.element && !this.element.__modal__ ) {
        this.element.__modal__ = this;
        this.href = this.options.href || this.element.href;
        if (this.options.position === 'mouse' && this.options.hover) {
          this.observer = function (ev) {
            this.pointer = {
              X: Event.pointerX(ev),
              Y: Event.pointerY(ev)
            };
            Event.stop( ev );
            this.open();
          }.bindAsEventListener(this);

          this.element.observe('mouseover', this.observer);
        } else if( ! this.options.tooltip ) {
          this.observer = function (ev) {
            //MMH-6933: Preventing login when button is disabled
            if (!this.element.hasClassName('disabled')) {
              this.pointer = {
                X: Event.pointerX(ev),
                Y: Event.pointerY(ev)
              };

              this.open();

              // Tim - In order to work with omniture nicely we should allow all clicks through,
              // but there are too many places we rely on the event stopping
              if (this.options.allowClick) {
                return true;
              } else {
                Event.stop(ev);
              }
            }
          }.bindAsEventListener(this);
          this.element.observe('click', this.observer);
        }
      }

      // get anchoring element for positioning
      this.anchor = $(this.options.anchor) || this.element;
    },

    destroy: function() {
      if( this.options.position === 'mouse' && this.options.hover ) {
        this.element.stopObserving('mouseover', this.observer);
      } else {
        this.element.stopObserving('click', this.observer);
      }
    },

    createElements: function () {
      // an IFRAME element to handle covering select boxes in IE
      if (Prototype.Browser.IE && !this.options.tooltip) {
        this.frame = $(document.createElement('iframe'));
        this.frame.src = 'javascript:void(0);';
        this.frame.setStyle({
          position: 'absolute',
          border: '0px',
          top: '0px',
          left: '0px',
          zIndex: $zIndex++,
          opacity: 0
        });
        this.frame.hide();
        document.body.appendChild(this.frame);
      }

      // the overlay div to block out mouse input
      if(!this.options.tooltip) {
          this.overlay = $(document.createElement('div'));
          this.overlay.setStyle({
            position: 'absolute',
            top: '0px',
            left: '0px',
            zIndex: $zIndex++
          });
          if (this.options.overlayStyles) {
            this.overlay.setStyle(this.options.overlayStyles);
          }
          this.overlay.addClassName('mmhModalOverlay');
          this.overlay.hide();
          document.body.appendChild(this.overlay);
      }
      // the container which holds the modal content
      this.container = $(document.createElement('div'));
      this.container.setStyle({
        position: 'absolute',
        zIndex: $zIndex++
      });
      this.container.addClassName('mmhModal');
      this.container.hide();
      document.body.appendChild(this.container);

      var body_container = this.container.parentNode;
      $(body_container).addClassName('modal_open');


    },

    setPosition: function (isInitial) {
      if (this.container) {
        // resize the overlay window
        var docsize = MMH.Common.getDocumentSize();
        if(!this.options.tooltip) {
            if (Prototype.Browser.IE) {
              this.frame.setStyle({
                width: docsize.width+'px',
                height: docsize.height+'px'
              });
            }
            this.overlay.setStyle({
              width: docsize.width+'px',
              height: docsize.height+'px'
            });
        }

        // resize the container if it's larger than the window
        if (this.options.position === 'center') {
          var winsize = MMH.Common.getWindowSize();
          var size = this.container.getDimensions();
          var height = (size.height > winsize.height*.8?
            Math.floor(winsize.height*.8)+'px':
            null);
          this.container.down('.mmhModalInset').setStyle({
            height: height
          });
        }

        // reposition the container window if necessary
        var offset;
        if (this.options.position === 'center') {
          offset = this.positionCenter();
        } else if (isInitial) {
          if (this.options.position === 'mouse') {
            offset = this.positionMouse();
          } else if (this.options.position === 'element') {
            offset = this.positionElement();
          }
        }

        if (offset) {
          offset = {
            left: offset.left+'px',
            top: offset.top+'px'
          };
          this.container.setStyle(offset);
        }
      }
    },

    positionCenter: function () {
      Position.prepare();
      var size = this.container.getDimensions();
      var winsize = MMH.Common.getWindowSize();

      return {
        top: Position.deltaY+Math.floor((winsize.height-size.height)/2),
        left: Position.deltaX+Math.floor((winsize.width-size.width)/2)
      };
    },

    positionMouse: function () {
      return this.positionCoords(this.pointer);
    },

    positionElement: function () {
      var elemsize = this.anchor.getDimensions();
      var elempos = Position.cumulativeOffset(this.anchor);
      if(this.options.in_scrollable) {
        elempos[1] = elempos[1] - $(this.options.in_scrollable).scrollTop;
      }
      return this.positionCoords({
        X: elempos[0]+Math.floor(elemsize.width/2),
        Y: elempos[1]+Math.floor(elemsize.height/2)
      });
    },

    positionCoords: function (coords) {
      Position.prepare();
      var direction = this.options.direction;
      var size = this.container.getDimensions();

      // if direction is auto, figure out what quadrant we're in
      if (direction === 'auto') {
        var winsize = MMH.Common.getWindowSize();
        direction = (
          coords.Y-Position.deltaY < winsize.height/2?
            (coords.X-Position.deltaX < winsize.width/2? 'SE': 'SW'):
            (coords.X-Position.deltaX < winsize.width/2? 'NE': 'NW'));
      }

      // inject arrow element
      if (!this.container.hasArrow) {
        this.container.hasArrow = true;
        var img = $(document.createElement('img'));
        img.src = $spacerGif;
        img.addClassName('arrow'+direction);
        this.container.appendChild(img);
      }

      // calculate offset for container
      var distance = this.options.distance;
      return {
        top: (
          direction === 'SE' || direction === 'SW'?
            coords.Y+distance.Y: // down
            coords.Y-size.height-distance.Y), // up
            left: (
          direction === 'SE' || direction === 'NE'?
            coords.X+distance.X: // right
            coords.X-size.width-distance.X) // left
      };
    },

    updateContainer: function (text) {
      this.container.update(text);
      if (this.options.header) {
        new Insertion.Top(
          this.container.down('.mmhModalInset'),
          this.options.header);
      }
      if (this.options.footer) {
        new Insertion.Bottom(
          this.container.down('.mmhModalInset'),
          this.options.footer);
      }
      if (Prototype.Browser.IE && !this.options.tooltip) {
        this.frame.show();
      }
      if (!this.options.overlay_hide || !this.options.tooltip) {
        this.overlay.show();
      }
      this.setPosition(true);
      this.container.show();
      if (!/MSIE 6.0/.test(navigator.userAgent)) {
        this.addShadow();
      }
      this.addBackgroundOpacity();
      if (this.options.close_button) {
        this.addCloseButton();
      }
    },
    addShadow: function () {
//      var height = this.container.getStyle('height');
//      var width = this.container.getStyle('width');
      ['one', 'two', 'three', 'four', 'five'].each(function(number) {
        var shadow = $(document.createElement('div'));
        shadow.addClassName('shadow ' + number);
//        shadow.setStyle({ width:width, height:height });
        this.container.appendChild(shadow);
      }.bind(this));
    },
    addBackgroundOpacity: function () {
      if (this.options.position === 'center') {
        this.overlay.addClassName('lightbox');
      }
    },
    addCloseButton: function () {
      if (this.options.position === 'center') {
        this.container.has_close_button = true;
        var a = $(document.createElement('a'));
        a.innerHTML = 'close';
        a.addClassName('image modal_close_button');
        this.container.appendChild(a);
        a.observe('click', function(ev){
          Event.stop(ev);

          var body_container = this.container.parentNode;
          $(body_container).removeClassName('modal_open');

          this.close();
        }.bind(this));
      }
    },

    open: function () {
      if( this.isOpen ) return false;

      if (!this.container) {
        if (!this.notify('beforeOpen')) {
          return false;
        }

        this.isOpen = true;

        if (this.content) {
          this.createElements();
          this.updateContainer(this.content);
          this.setupObservers();
          $modals.push(this);
          this.notify('afterOpen');
          this.isOpen = false;
          FormObserver.attachAllObservers();
          ModalObserver.attachAllObservers();
          LinkObserver.attachAllObservers();
        } else {
          new Ajax.Request(this.href, {
            method      : this.options.method,
            evalScripts : true,
            onSuccess   : function(res) {
              this.createElements();
              this.updateContainer(res.responseText);
              this.setupObservers();
              $modals.push(this);
              this.notify('afterOpen');
              this.isOpen = false;
              FormObserver.attachAllObservers();
              ModalObserver.attachAllObservers();
              LinkObserver.attachAllObservers();
            }.bind(this)
          });
        }
      }
      return true;
    },

    setupObservers: function() {
      this._setPosition = this.setPosition.bind(this);
      Event.observe(window, 'resize', this._setPosition);
      Event.observe(window, 'scroll', this._setPosition);
    },

    close: function () {
      if (this.container) {
        if (!this.notify('beforeClose')) {
          return;
        }

        $modals.pop();
        this.container.remove();
        delete this.container;
        if(!this.options.tooltip) {
            this.overlay.remove();
            delete this.overlay;
            if (Prototype.Browser.IE) {
              this.frame.remove();
              delete this.frame;
            }
        }
        // stop listening for resize/scroll events
        Event.stopObserving(window, 'resize', this._setPosition);
        Event.stopObserving(window, 'scroll', this._setPosition);
        delete this._setPosition;

        // notify of close
        this.notify('afterClose');
      }
    }
  });

  // extend modals with event listening behavior
  MMH.Event.extend(MMH.Modal.prototype);

  // add methods to Modal class to forward to top modal on the stack
  ['notify', 'close', 'setPosition'].each( function (name) {
    MMH.Modal[name] = function () {
      var modal = $modals.last();
      modal[name].apply(modal, arguments);
    };
  });

  /*
  ** LoginModal: manages a login popup
  **
  ** supported options: (see package vars below for defaults)
  **  loginUrl = url of login box
  **  redirectUrl = url to redirect after login
  **  redirectParams = params added to redirect url (hash)
  **  registerUrl = url to redirect after register
  **  registerParams = params added to register url (hash)
  **  sourceParam = offermatica mbox 'source' param on reg page
  **  landingPageParam = offerm. mbox 'landingPage' param on reg page
  */

  MMH.LoginModal = Class.create();
  MMH.LoginModal.prototype = new MMH.Modal();

  var $loginUrl = '/mmh/user-profile/ShowLogin.action';
  var $registerUrl = '/mmh/registration/new';
  var $preregisteredUrl = '/mmh/user-profile/DisplayPreRegistrationSignup.action?userName=';
  var $redirectUrl = '/mmh/user-profile/RegisteredHome.action';

  var $headerUrl = '/mmh/rails/partials?partial=/shared/header';
  var $headerElements = ['text_nav', 'branding'];
  var $footerUrl = '/mmh/rails/partials?partial=/shared/foot';
  var $footerElements = ['footer'];

  // redraw a set of elements from a partial url
  function $redraw(url, elements) {
    new Ajax.Request(url, {
      method: 'get',
      onSuccess: function (res) {
        // update the elements from the response
        var holder = $(document.createElement('div'));
        holder.update(res.responseText);
        elements.each(function (id) {
          var replaced = holder.getElementsBySelector('[id='+id+']')[0];
          $(id).update((replaced||holder).innerHTML);
        });
        // dispose of the temporary content
        holder.update();
      }
    });
  }

  Object.extend(MMH.LoginModal.prototype, {
    initialize: function (element, options) {
      options = Object.extend({
        position: 'center',
        method: 'post'
      }, options);
      MMH.Modal.prototype.initialize.call(this, element, options);

      // grab original href, and point at the loginUrl
      var elementUrl = this.href;
      this.href = this.getLoginUrl();

      //grab the form if present
      if(this.element.up('form')) {
        this.form_element = element.up('form');
        this.form_element.stopObserving('submit');
        this.form_element.onsubmit = function() {
          this.open();        	  
          return false;
        }.bind(this);
      }

      // grab the redirectUrl and redirectParams from the link
      if(elementUrl) {
        var qs = elementUrl.split(/\?/);
        this.redirectUrl = this.options.redirectUrl||qs[0];
        this.redirectParams = (this.options.redirectParams ?
                               Hash.toQueryString(this.options.redirectParams) :
                               qs[1]);
      }
    },

    getLoginUrl: function () {
      return this.options.loginUrl||$loginUrl;
    },

    getRegisterUrl: function () {
      var url = this.options.registerUrl||$registerUrl;
      if (this.options.registerParams) {
        url += '?'+Hash.toQueryString(this.options.registerParams);
      }

      // extra params the sign-up page knows about
      ['source', 'landingPage'].each(function (param) {
        if (this.options[param]) {
          if (url.match(/\?/)) {
            url += "&";
          } else {
            url += "?";
          }
          url += param+'Param='+encodeURIComponent(this.options[param]);
        }
      }.bind(this));

      return url;
    },

    register: function (shouldClose) {
      if (shouldClose) {
        this.close();
      }
      if (this.notify('beforeRegister')) {
        /* Hack: IE does not send HTTP_REFERER from javascript redirecTo */
          if(this.form_element) {
            // Build a form, which contains the underlying element form data,
            // and submit it to the workflow controller.
            var form = new Element('form', {
              'action': '/mmh/workflow_items',
              'method': 'post'
            });

            var form_data_input = new Element('input');
            form_data_input.type = 'hidden';
            form_data_input.name = 'body';
            form_data_input.value = this.form_element.serialize();

            var target_uri_input = new Element('input');
            target_uri_input.type = 'hidden';
            target_uri_input.name = 'target_uri';
            target_uri_input.value = this.form_element.action;

            var passthrough_method_input = new Element('input');
            passthrough_method_input.type = 'hidden';
            passthrough_method_input.name = 'passthrough_method';
            passthrough_method_input.value = this.form_element ? 'post' : 'get';

            form.insert({bottom:form_data_input});
            form.insert({bottom:target_uri_input});
            form.insert({bottom:passthrough_method_input});

            $(document.body).insert({bottom: form});
            form.submit();
         } else {
            if (Prototype.Browser.IE) {
              var referLink = document.createElement('a');
              referLink.href = this.getRegisterUrl();
              document.body.appendChild(referLink);
              referLink.click();
            } else {
              MMH.Window.redirectTo( this.getRegisterUrl());
            }
         }
      }
    },

    getRedirectUrl: function () {
      var url = $redirectUrl;
      if (this.redirectUrl && this.redirectUrl !== '') {
        url = this.redirectUrl;
        if (this.redirectParams && this.redirectParams !== '') {
          url += '?'+this.redirectParams;
        }
      }
      return url;
    },

    success: function (shouldClose) {

      if (shouldClose) {
        this.close();
      }
      if (this.notify('afterLogin')) {
        if (this.options.reload) {
          MMH.Window.reload();
        } else {
          if(this.form_element) {
            if(this.form_element.onsubmit) {
              this.form_element.onsubmit();
            } else {
              this.form_element.submit();
            }
          } else {
            MMH.Window.redirectTo( this.getRedirectUrl() );
          }

        }
      }
    },

    preregistered: function (userName, shouldClose) {
      if (shouldClose) {
        this.close();
      }
      MMH.Window.redirectTo( $preregisteredUrl+userName );
    },

    redrawHeader: function () {
      // redraw header and footer elements on login
      $redraw($headerUrl, $headerElements);
      $redraw($footerUrl, $footerElements);

      // remove the Home/Register menu item
      if ($('navHomeRegister')) $('navHomeRegister').hide();
    }
  });

  // add methods to LoginModal class to forward to top modal on the stack
  ['register', 'success', 'preregistered', 'redrawHeader'].each(
    function (name) {
      MMH.LoginModal[name] = function () {
        var modal = $modals.last();
        modal[name].apply(modal, arguments);
      };
    });

  // Loader for AJAX requests
  MMH.KurtLoader = Class.create();
  MMH.KurtLoader.prototype = new MMH.Modal();

  Object.extend(MMH.KurtLoader.prototype, {
    initialize: function (element, options) {
      MMH.Modal.prototype.initialize.call(this, element, options);
    },
    show: function() {},
    hide: function() {}
  });
})();

MMH.LoginModal.login_current_location = function() {
  MMH.LoginModal.login_and_go_to(window.location.href);
};

MMH.LoginModal.login_and_go_to = function(url) {
  var login_modal = new MMH.LoginModal('navWidgetLogin', {});
  login_modal.redirectUrl = url;
  login_modal.open();
};




/* /classes/tablekit.js */

 // Use the TableKit class constructure if you'd prefer to init your tables as JS objects
var TableKit = Class.create();

TableKit.prototype = {
  initialize : function(elm, options) {
    var table = $(elm);
    if(table.tagName !== "TABLE") {
      return;
    }
    TableKit.register(table,Object.extend(TableKit.options,options || {}));
    this.id = table.id;
    var op = TableKit.option('sortable resizable editable', this.id);
    if(op.sortable) {
      TableKit.Sortable.init(table);
    } 
    if(op.resizable) {
      TableKit.Resizable.init(table);
    }
    if(op.editable) {
      TableKit.Editable.init(table);
    }
  },
  sort : function(column, order) {
    TableKit.Sortable.sort(this.id, column, order);
  },
  resizeColumn : function(column, w) {
    TableKit.Resizable.resize(this.id, column, w);
  },
  editCell : function(row, column) {
    TableKit.Editable.editCell(this.id, row, column);
  }
};

Object.extend(TableKit, {
  getBodyRows : function(table) {
    table = $(table);
    var id = table.id;
    if(!TableKit.rows[id]) {
      TableKit.rows[id] = (table.tHead && table.tHead.rows.length > 0) ? $A(table.tBodies[0].rows) : $A(table.rows).without(table.rows[0]);
    }
    return TableKit.rows[id];
  },
  getHeaderCells : function(table, cell) {
    if(!table) { table = $(cell).up('table'); }
    var id = table.id;
    if(!TableKit.heads[id]) {
      TableKit.heads[id] = $A((table.tHead && table.tHead.rows.length > 0) ? table.tHead.rows[table.tHead.rows.length-1].cells : table.rows[0].cells);
    }
    return TableKit.heads[id];
  },
  getCellIndex : function(cell) {
    return $A(cell.parentNode.cells).indexOf(cell);
  },
  getRowIndex : function(row) {
    return $A(row.parentNode.rows).indexOf(row);
  },
  getCellText : function(cell, refresh) {
    if(!cell) { return ""; }
    TableKit.registerCell(cell);
    var data = TableKit.cells[cell.id];
    if(refresh || data.refresh || !data.textContent) {
      data.textContent = cell.textContent ? cell.textContent : cell.innerText;
      data.refresh = false;
    }
    return data.textContent;
  },
  register : function(table, options) {
    if(!table.id) {
      TableKit._tblcount += 1;
      table.id = "tablekit-table-" + TableKit._tblcount;
    }
    var id = table.id;
    TableKit.tables[id] = TableKit.tables[id] ? Object.extend(TableKit.tables[id], options || {}) : Object.extend({sortable:false,resizable:false,editable:false}, options || {});
  },
  registerCell : function(cell) {
    if(!cell.id) {
      TableKit._cellcount += 1;
      cell.id = "tablekit-cell-" + TableKit._cellcount;
    }
    if(!TableKit.cells[cell.id]) {
      TableKit.cells[cell.id] = {textContent : '', htmlContent : '', active : false};
    }
  },
  isSortable : function(table) {
    return TableKit.tables[table.id] ? TableKit.tables[table.id].sortable : false;
  },
  isResizable : function(table) {
    return TableKit.tables[table.id] ? TableKit.tables[table.id].resizable : false;
  },
  isEditable : function(table) {
    return TableKit.tables[table.id] ? TableKit.tables[table.id].editable : false;
  },
  setup : function(o) {
    Object.extend(TableKit.options, o || {} );
  },
  option : function(s, id, o1, o2) {
    o1 = o1 || TableKit.options;
    o2 = o2 || (id ? (TableKit.tables[id] ? TableKit.tables[id] : {}) : {});
    var key = id + s;
    if(!TableKit._opcache[key]){
      TableKit._opcache[key] = $A($w(s)).inject([],function(a,v){
        a.push(a[v] = o2[v] || o1[v]);
        return a;
      });
    }
    return TableKit._opcache[key];
  },
  e : function(event) {
    return event || window.event;
  },
  tables : {},
  _opcache : {},
  cells : {},
  rows : {},
  heads : {},
  options : {
    autoLoad : true,
    stripe : true,
    sortable : true,
    resizable : true,
    editable : true,
    rowEvenClass : 'roweven',
    rowOddClass : 'rowodd',
    sortableSelector : ['table.sortable'],
    columnClass : 'sortcol',
    descendingClass : 'sortdesc',
    ascendingClass : 'sortasc',
    noSortClass : 'nosort',
    sortFirstAscendingClass : 'sortfirstasc',
    sortFirstDecendingClass : 'sortfirstdesc',
    resizableSelector : ['table.resizable'],
    minWidth : 10,
    showHandle : true,
    resizeOnHandleClass : 'resize-handle-active',
    editableSelector : ['table.editable'],
    formClassName : 'editable-cell-form',
    noEditClass : 'noedit',
    editAjaxURI : '/',
    editAjaxOptions : {}
  },
  _tblcount : 0,
  _cellcount : 0,
  load : function() {
    if(TableKit.options.autoLoad) {
      if(TableKit.options.sortable) {
        $A(TableKit.options.sortableSelector).each(function(s){
          $$(s).each(function(t) {
            TableKit.Sortable.init(t);
          });
        });
      }
      if(TableKit.options.resizable) {
        $A(TableKit.options.resizableSelector).each(function(s){
          $$(s).each(function(t) {
            TableKit.Resizable.init(t);
          });
        });
      }
      if(TableKit.options.editable) {
        $A(TableKit.options.editableSelector).each(function(s){
          $$(s).each(function(t) {
            TableKit.Editable.init(t);
          });
        });
      }
    }
  }
});

TableKit.Rows = {
  stripe : function(table) {
    var rows = TableKit.getBodyRows(table);
    rows.each(function(r,i) {
      TableKit.Rows.addStripeClass(table,r,i);
    });
  },
  addStripeClass : function(t,r,i) {
    t = t || r.up('table');
    var op = TableKit.option('rowEvenClass rowOddClass', t.id);
    var css = ((i+1)%2 === 0 ? op[0] : op[1]);
    // using prototype's assClassName/RemoveClassName was not efficient for large tables, hence:
    var cn = r.className.split(/\s+/);
    var newCn = [];
    for(var x = 0, l = cn.length; x < l; x += 1) {
      if(cn[x] !== op[0] && cn[x] !== op[1]) { newCn.push(cn[x]); }
    }
    newCn.push(css);
    r.className = newCn.join(" ");
  }
};

TableKit.Sortable = {
  init : function(elm, options){
    var table = $(elm);
    if(table.tagName !== "TABLE") {
      return;
    }
    TableKit.register(table,Object.extend(options || {},{sortable:true}));
    var sortFirst;
    var cells = TableKit.getHeaderCells(table);
    var op = TableKit.option('noSortClass columnClass sortFirstAscendingClass sortFirstDecendingClass', table.id);
    cells.each(function(c){
      c = $(c);
      if(!c.hasClassName(op.noSortClass)) {
        Event.observe(c, 'mousedown', TableKit.Sortable._sort);
        c.addClassName(op.columnClass);
        if(c.hasClassName(op.sortFirstAscendingClass) || c.hasClassName(op.sortFirstDecendingClass)) {
          sortFirst = c;
        }
      }
    });

    if(sortFirst) {
      if(sortFirst.hasClassName(op.sortFirstAscendingClass)) {
        TableKit.Sortable.sort(table, sortFirst, 1);
      } else {
        TableKit.Sortable.sort(table, sortFirst, -1);
      }
    } else { // just add row stripe classes
      TableKit.Rows.stripe(table);
    }
  },
  reload : function(table) {
    table = $(table);
    var cells = TableKit.getHeaderCells(table);
    var op = TableKit.option('noSortClass columnClass', table.id);
    cells.each(function(c){
      c = $(c);
      if(!c.hasClassName(op.noSortClass)) {
        Event.stopObserving(c, 'mousedown', TableKit.Sortable._sort);
        c.removeClassName(op.columnClass);
      }
    });
    TableKit.Sortable.init(table);
  },
  _sort : function(e) {
    if(TableKit.Resizable._onHandle) {return;}
    e = TableKit.e(e);
    Event.stop(e);
    var cell = Event.element(e);
    while(!(cell.tagName && cell.tagName.match(/td|th/gi))) {
      cell = cell.parentNode;
    }
    TableKit.Sortable.sort(null, cell);
  },
  sort : function(table, index, order) {
    var cell;
    if(typeof index === 'number') {
      if(!table || (table.tagName && table.tagName !== "TABLE")) {
        return;
      }
      table = $(table);
      index = Math.min(table.rows[0].cells.length, index);
      index = Math.max(1, index);
      index -= 1;
      cell = (table.tHead && table.tHead.rows.length > 0) ? $(table.tHead.rows[table.tHead.rows.length-1].cells[index]) : $(table.rows[0].cells[index]);
    } else {
      cell = $(index);
      table = table ? $(table) : cell.up('table');
      index = TableKit.getCellIndex(cell);
    }
    var op = TableKit.option('noSortClass descendingClass ascendingClass', table.id);
    
    if(cell.hasClassName(op.noSortClass)) {return;}	
    
    order = order ? order : (cell.hasClassName(op.descendingClass) ? 1 : -1);
    var rows = TableKit.getBodyRows(table);

    if(cell.hasClassName(op.ascendingClass) || cell.hasClassName(op.descendingClass)) {
      rows.reverse(); // if it was already sorted we just need to reverse it.
    } else {
      var datatype = TableKit.Sortable.getDataType(cell,index,table);
      var tkst = TableKit.Sortable.types;
      rows.sort(function(a,b) {
        return order * tkst[datatype].compare(TableKit.getCellText(a.cells[index]),TableKit.getCellText(b.cells[index]));
      });
    }
    var tb = table.tBodies[0];
    var tkr = TableKit.Rows;
    rows.each(function(r,i) {
      tb.appendChild(r);
      tkr.addStripeClass(table,r,i);
    });
    var hcells = TableKit.getHeaderCells(null, cell);
    $A(hcells).each(function(c,i){
      c = $(c);
      c.removeClassName(op.ascendingClass);
      c.removeClassName(op.descendingClass);
      if(index === i) {
        if(order === 1) {
          c.removeClassName(op.descendingClass);
          c.addClassName(op.ascendingClass);
        } else {
          c.removeClassName(op.ascendingClass);
          c.addClassName(op.descendingClass);
        }
      }
    });
  },
  types : {},
  detectors : [],
  addSortType : function() {
    $A(arguments).each(function(o){
      TableKit.Sortable.types[o.name] = o;
    });
  },
  getDataType : function(cell,index,table) {
    cell = $(cell);
    index = (index || index === 0) ? index : TableKit.getCellIndex(cell);
    
    var colcache = TableKit.Sortable._coltypecache;
    var cache = colcache[table.id] ? colcache[table.id] : (colcache[table.id] = {});
    
    if(!cache[index]) {
      var t = '';
      // first look for a data type id on the heading row cell
      if(cell.id && TableKit.Sortable.types[cell.id]) {
        t = cell.id;
      }
      t = cell.classNames().detect(function(n){ // then look for a data type classname on the heading row cell
        return (TableKit.Sortable.types[n]) ? true : false;
      });
      if(!t) {
        var rows = TableKit.getBodyRows(table);
        cell = rows[0].cells[index]; // grab same index cell from body row to try and match data type
        t = TableKit.Sortable.detectors.detect(
            function(d){
              return TableKit.Sortable.types[d].detect(TableKit.getCellText(cell));
            });
      }
      cache[index] = t;
    }
    return cache[index];
  },
  _coltypecache : {}
};

TableKit.Sortable.detectors = $A($w('date-iso date date-eu date-au time currency datasize number casesensitivetext text')); // setting it here because Safari complained when I did it above...

TableKit.Sortable.Type = Class.create();
TableKit.Sortable.Type.prototype = {
  initialize : function(name, options){
    this.name = name;
    options = Object.extend({
      normal : function(v){
        return v;
      },
      pattern : /.*/
    }, options || {});
    this.normal = options.normal;
    this.pattern = options.pattern;
    if(options.compare) {
      this.compare = options.compare;
    }
    if(options.detect) {
      this.detect = options.detect;
    }
  },
  compare : function(a,b){
    return TableKit.Sortable.Type.compare(this.normal(a), this.normal(b));
  },
  detect : function(v){
    return this.pattern.test(v);
  }
};

TableKit.Sortable.Type.compare = function(a,b) {
  return a < b ? -1 : a === b ? 0 : 1;
};

TableKit.Sortable.addSortType(
  new TableKit.Sortable.Type('number', {
    pattern : /^[-+]?[\d]*\.?[\d]+(?:[eE][-+]?[\d]+)?/,
    normal : function(v) {
      // This will grab the first thing that looks like a number from a string, so you can use it to order a column of various srings containing numbers.
      v = parseFloat(v.replace(/^.*?([-+]?[\d]*\.?[\d]+(?:[eE][-+]?[\d]+)?).*$/,"$1"));
      return isNaN(v) ? 0 : v;
    }}),
  new TableKit.Sortable.Type('text',{
    normal : function(v) {
      return v ? v.toLowerCase() : '';
    }}),
  new TableKit.Sortable.Type('casesensitivetext',{pattern : /^[A-Z]+$/}),
  new TableKit.Sortable.Type('datasize',{
    pattern : /^[-+]?[\d]*\.?[\d]+(?:[eE][-+]?[\d]+)?\s?[k|m|g|t]b$/i,
    normal : function(v) {
      var r = v.match(/^([-+]?[\d]*\.?[\d]+([eE][-+]?[\d]+)?)\s?([k|m|g|t]?b)?/i);
      var b = r[1] ? Number(r[1]).valueOf() : 0;
      var m = r[3] ? r[3].substr(0,1).toLowerCase() : '';
      var result = b;
      switch(m) {
        case  'k':
          result = b * 1024;
          break;
        case  'm':				
          result = b * 1024 * 1024;
          break;
        case  'g':
          result = b * 1024 * 1024 * 1024;
          break;
        case  't':
          result = b * 1024 * 1024 * 1024 * 1024;
          break;
      }
      return result;
    }}),
  new TableKit.Sortable.Type('date-au',{
    pattern : /^\d{2}\/\d{2}\/\d{4}\s?(?:\d{1,2}\:\d{2}(?:\:\d{2})?\s?[a|p]?m?)?/i,
    normal : function(v) {
      if(!this.pattern.test(v)) {return 0;}
      var r = v.match(/^(\d{2})\/(\d{2})\/(\d{4})\s?(?:(\d{1,2})\:(\d{2})(?:\:(\d{2}))?\s?([a|p]?m?))?/i);
      var yr_num = r[3];
      var mo_num = parseInt(r[2],10)-1;
      var day_num = r[1];
      var hr_num = r[4] ? r[4] : 0;
      if(r[7] && r[7].toLowerCase().indexOf('p') !== -1) {
        hr_num = parseInt(r[4],10) + 12;
      }
      var min_num = r[5] ? r[5] : 0;
      var sec_num = r[6] ? r[6] : 0;
      return new Date(yr_num, mo_num, day_num, hr_num, min_num, sec_num, 0).valueOf();
    }}),
  new TableKit.Sortable.Type('date-us',{
    pattern : /^\d{2}\/\d{2}\/\d{4}\s?(?:\d{1,2}\:\d{2}(?:\:\d{2})?\s?[a|p]?m?)?/i,
    normal : function(v) {
      if(!this.pattern.test(v)) {return 0;}
      var r = v.match(/^(\d{2})\/(\d{2})\/(\d{4})\s?(?:(\d{1,2})\:(\d{2})(?:\:(\d{2}))?\s?([a|p]?m?))?/i);
      var yr_num = r[3];
      var mo_num = parseInt(r[1],10)-1;
      var day_num = r[2];
      var hr_num = r[4] ? r[4] : 0;
      if(r[7] && r[7].toLowerCase().indexOf('p') !== -1) {
        hr_num = parseInt(r[4],10) + 12;
      }
      var min_num = r[5] ? r[5] : 0;
      var sec_num = r[6] ? r[6] : 0;
      return new Date(yr_num, mo_num, day_num, hr_num, min_num, sec_num, 0).valueOf();
    }}),
  new TableKit.Sortable.Type('date-eu',{
    pattern : /^\d{2}-\d{2}-\d{4}/i,
    normal : function(v) {
      if(!this.pattern.test(v)) {return 0;}
      var r = v.match(/^(\d{2})-(\d{2})-(\d{4})/);
      var yr_num = r[3];
      var mo_num = parseInt(r[2],10)-1;
      var day_num = r[1];
      return new Date(yr_num, mo_num, day_num).valueOf();
    }}),
  new TableKit.Sortable.Type('date-iso',{
    pattern : /[\d]{4}-[\d]{2}-[\d]{2}(?:T[\d]{2}\:[\d]{2}(?:\:[\d]{2}(?:\.[\d]+)?)?(Z|([-+][\d]{2}:[\d]{2})?)?)?/, // 2005-03-26T19:51:34Z
    normal : function(v) {
      if(!this.pattern.test(v)) {return 0;}
        var d = v.match(/([\d]{4})(-([\d]{2})(-([\d]{2})(T([\d]{2}):([\d]{2})(:([\d]{2})(\.([\d]+))?)?(Z|(([-+])([\d]{2}):([\d]{2})))?)?)?)?/);		
        var offset = 0;
        var date = new Date(d[1], 0, 1);
        if (d[3]) { date.setMonth(d[3] - 1) ;}
        if (d[5]) { date.setDate(d[5]); }
        if (d[7]) { date.setHours(d[7]); }
        if (d[8]) { date.setMinutes(d[8]); }
        if (d[10]) { date.setSeconds(d[10]); }
        if (d[12]) { date.setMilliseconds(Number("0." + d[12]) * 1000); }
        if (d[14]) {
            offset = (Number(d[16]) * 60) + Number(d[17]);
            offset *= ((d[15] === '-') ? 1 : -1);
        }
        offset -= date.getTimezoneOffset();
        if(offset !== 0) {
          var time = (Number(date) + (offset * 60 * 1000));
          date.setTime(Number(time));
        }
      return date.valueOf();
    }}),
  new TableKit.Sortable.Type('date',{
    pattern: /^(?:sun|mon|tue|wed|thu|fri|sat)\,\s\d{1,2}\s(?:jan|feb|mar|apr|may|jun|jul|aug|sep|oct|nov|dec)\s\d{4}(?:\s\d{2}\:\d{2}(?:\:\d{2})?(?:\sGMT(?:[+-]\d{4})?)?)?/i, //Mon, 18 Dec 1995 17:28:35 GMT
    compare : function(a,b) { // must be standard javascript date format
      if(a && b) {
        return TableKit.Sortable.Type.compare(new Date(a),new Date(b));
      } else {
        return TableKit.Sortable.Type.compare(a ? 1 : 0, b ? 1 : 0);
      }
    }}),
  new TableKit.Sortable.Type('time',{
    pattern : /^\d{1,2}\:\d{2}(?:\:\d{2})?(?:\s[a|p]m)?$/i,
    compare : function(a,b) {
      var d = new Date();
      var ds = d.getMonth() + "/" + d.getDate() + "/" + d.getFullYear() + " ";
      return TableKit.Sortable.Type.compare(new Date(ds + a),new Date(ds + b));
    }}),
  new TableKit.Sortable.Type('currency',{
    pattern : /^[$ï¿½ï¿½ï¿½ï¿½]/, // dollar,pound,yen,euro,generic currency symbol
    normal : function(v) {
      return v ? parseFloat(v.replace(/[^-\d\.]/g,'')) : 0;
    }})
);

TableKit.Resizable = {
  init : function(elm, options){
    var table = $(elm);
    if(table.tagName !== "TABLE") {return;}
    TableKit.register(table,Object.extend(options || {},{resizable:true}));		 
    var cells = TableKit.getHeaderCells(table);
    cells.each(function(c){
      c = $(c);
      Event.observe(c, 'mouseover', TableKit.Resizable.initDetect);
      Event.observe(c, 'mouseout', TableKit.Resizable.killDetect);
    });
  },
  resize : function(table, index, w) {
    var cell;
    if(typeof index === 'number') {
      if(!table || (table.tagName && table.tagName !== "TABLE")) {return;}
      table = $(table);
      index = Math.min(table.rows[0].cells.length, index);
      index = Math.max(1, index);
      index -= 1;
      cell = (table.tHead && table.tHead.rows.length > 0) ? $(table.tHead.rows[table.tHead.rows.length-1].cells[index]) : $(table.rows[0].cells[index]);
    } else {
      cell = $(index);
      table = table ? $(table) : cell.up('table');
      index = TableKit.getCellIndex(cell);
    }
    var pad = parseInt(cell.getStyle('paddingLeft'),10) + parseInt(cell.getStyle('paddingRight'),10);
    w = Math.max(w-pad, TableKit.option('minWidth', table.id)[0]);
    
    cell.setStyle({'width' : w + 'px'});
  },
  initDetect : function(e) {
    e = TableKit.e(e);
    var cell = Event.element(e);
    Event.observe(cell, 'mousemove', TableKit.Resizable.detectHandle);
    Event.observe(cell, 'mousedown', TableKit.Resizable.startResize);
  },
  detectHandle : function(e) {
    e = TableKit.e(e);
    var cell = Event.element(e);
      if(TableKit.Resizable.pointerPos(cell,Event.pointerX(e),Event.pointerY(e))){
        cell.addClassName(TableKit.option('resizeOnHandleClass', cell.up('table').id)[0]);
        TableKit.Resizable._onHandle = true;
      } else {
        cell.removeClassName(TableKit.option('resizeOnHandleClass', cell.up('table').id)[0]);
        TableKit.Resizable._onHandle = false;
      }
  },
  killDetect : function(e) {
    e = TableKit.e(e);
    TableKit.Resizable._onHandle = false;
    var cell = Event.element(e);
    Event.stopObserving(cell, 'mousemove', TableKit.Resizable.detectHandle);
    Event.stopObserving(cell, 'mousedown', TableKit.Resizable.startResize);
    cell.removeClassName(TableKit.option('resizeOnHandleClass', cell.up('table').id)[0]);
  },
  startResize : function(e) {
    e = TableKit.e(e);
    if(!TableKit.Resizable._onHandle) {return;}
    var cell = Event.element(e);
    Event.stopObserving(cell, 'mousemove', TableKit.Resizable.detectHandle);
    Event.stopObserving(cell, 'mousedown', TableKit.Resizable.startResize);
    Event.stopObserving(cell, 'mouseout', TableKit.Resizable.killDetect);
    TableKit.Resizable._cell = cell;
    var table = cell.up('table');
    TableKit.Resizable._tbl = table;
    if(TableKit.option('showHandle', table.id)[0]) {
      TableKit.Resizable._handle = $(document.createElement('div')).addClassName('resize-handle').setStyle({
        'top' : Position.cumulativeOffset(cell)[1] + 'px',
        'left' : Event.pointerX(e) + 'px',
        'height' : table.getDimensions().height + 'px'
      });
      document.body.appendChild(TableKit.Resizable._handle);
    }
    Event.observe(document, 'mousemove', TableKit.Resizable.drag);
    Event.observe(document, 'mouseup', TableKit.Resizable.endResize);
    Event.stop(e);
  },
  endResize : function(e) {
    e = TableKit.e(e);
    var cell = TableKit.Resizable._cell;
    TableKit.Resizable.resize(null, cell, (Event.pointerX(e) - Position.cumulativeOffset(cell)[0]));
    Event.stopObserving(document, 'mousemove', TableKit.Resizable.drag);
    Event.stopObserving(document, 'mouseup', TableKit.Resizable.endResize);
    if(TableKit.option('showHandle', TableKit.Resizable._tbl.id)[0]) {
      $$('div.resize-handle').each(function(elm){
        document.body.removeChild(elm);
      });
    }
    Event.observe(cell, 'mouseout', TableKit.Resizable.killDetect);
    TableKit.Resizable._tbl = TableKit.Resizable._handle = TableKit.Resizable._cell = null;
    Event.stop(e);
  },
  drag : function(e) {
    e = TableKit.e(e);
    if(TableKit.Resizable._handle === null) {
      try {
        TableKit.Resizable.resize(TableKit.Resizable._tbl, TableKit.Resizable._cell, (Event.pointerX(e) - Position.cumulativeOffset(TableKit.Resizable._cell)[0]));
      } catch(e) {}
    } else {
      TableKit.Resizable._handle.setStyle({'left' : Event.pointerX(e) + 'px'});
    }
    return false;
  },
  pointerPos : function(element, x, y) {
      var offset = Position.cumulativeOffset(element);
      return (y >= offset[1] &&
              y <  offset[1] + element.offsetHeight &&
              x >= offset[0] + element.offsetWidth - 5 &&
              x <  offset[0] + element.offsetWidth);
    },
  _onHandle : false,
  _cell : null,
  _tbl : null,
  _handle : null
};


TableKit.Editable = {
  init : function(elm, options){
    var table = $(elm);
    if(table.tagName !== "TABLE") {return;}
    TableKit.register(table,Object.extend(options || {},{editable:true}));
    Event.observe(table.tBodies[0], 'click', TableKit.Editable._editCell);
  },
  _editCell : function(e) {
    e = TableKit.e(e);
    var cell = Event.findElement(e,'td');
    TableKit.Editable.editCell(null, cell);
  },
  editCell : function(table, index, cindex) {
    var cell, row;
    if(typeof index === 'number') {
      if(!table || (table.tagName && table.tagName !== "TABLE")) {return;}
      table = $(table);
      index = Math.min(table.tBodies[0].rows.length, index);
      index = Math.max(1, index);
      index -= 1;
      cindex = Math.min(table.rows[0].cells.length, cindex);
      cindex = Math.max(1, cindex);
      cindex -= 1;
      row = $(table.tBodies[0].rows[index]);
      cell = $(row.cells[cindex]);
    } else {
      cell = $(index);
      table = (table && table.tagName && table.tagName !== "TABLE") ? $(table) : cell.up('table');
      row = cell.up('tr');
    }
    var op = TableKit.option('noEditClass', table.id);
    if(cell.hasClassName(op.noEditClass)) {return;}
    
    var head = $(TableKit.getHeaderCells(table, cell)[TableKit.getCellIndex(cell)]);
    if(head.hasClassName(op.noEditClass)) {return;}
    
    TableKit.registerCell(cell);
    var data = TableKit.cells[cell.id];
    if(data.active) {return;}
    data.htmlContent = cell.innerHTML;
    var ftype = TableKit.Editable.types['text-input'];
    if(head.id && TableKit.Editable.types[head.id]) {
      ftype = TableKit.Editable.types[head.id];
    } else {
      var n = head.classNames().detect(function(n){
          return (TableKit.Editable.types[n]) ? true : false;
      });
      ftype = n ? TableKit.Editable.types[n] : ftype;
    }
    ftype.edit(cell);
    data.active = true;
  },
  types : {},
  addCellEditor : function(o) {
    if(o && o.name) { TableKit.Editable.types[o.name] = o; }
  }
};

TableKit.Editable.CellEditor = Class.create();
TableKit.Editable.CellEditor.prototype = {
  initialize : function(name, options){
    this.name = name;
    this.options = Object.extend({
      element : 'input',
      attributes : {name : 'value', type : 'text'},
      selectOptions : [],
      showSubmit : true,
      submitText : 'OK',
      showCancel : true,
      cancelText : 'Cancel',
      ajaxURI : null,
      ajaxOptions : null
    }, options || {});
  },
  edit : function(cell) {
    cell = $(cell);
    var op = this.options;
    var table = cell.up('table');
    
    var form = $(document.createElement("form"));
    form.id = cell.id + '-form';
    form.addClassName(TableKit.option('formClassName', table.id)[0]);
    form.onsubmit = this._submit.bindAsEventListener(this);
    
    var field = document.createElement(op.element);
      $H(op.attributes).each(function(v){
        field[v.key] = v.value;
      });
      switch(op.element) {
        case 'input':
        case 'textarea':
        field.value = TableKit.getCellText(cell);
        break;
        
        case 'select':
        var txt = TableKit.getCellText(cell);
        $A(op.selectOptions).each(function(v){
          field.options[field.options.length] = new Option(v[0], v[1]);
          if(txt === v[1]) {
            field.options[field.options.length-1].selected = 'selected';
          }
        });
        break;
      }
      form.appendChild(field);
      if(op.element === 'textarea') {
        form.appendChild(document.createElement("br"));
      }
      if(op.showSubmit) {
        var okButton = document.createElement("input");
        okButton.type = "submit";
        okButton.value = op.submitText;
        okButton.className = 'editor_ok_button';
        form.appendChild(okButton);
      }
      if(op.showCancel) {
        var cancelLink = document.createElement("a");
        cancelLink.href = "#";
        cancelLink.appendChild(document.createTextNode(op.cancelText));
        cancelLink.onclick = this._cancel.bindAsEventListener(this);
        cancelLink.className = 'editor_cancel';      
        form.appendChild(cancelLink);
      }
      cell.innerHTML = '';
      cell.appendChild(form);
  },
  _submit : function(e) {
    var cell = Event.findElement(e,'td');
    var form = Event.findElement(e,'form');
    Event.stop(e);
    this.submit(cell,form);
  },
  submit : function(cell, form) {
    var op = this.options;
    form = form ? form : cell.down('form');
    var head = $(TableKit.getHeaderCells(null, cell)[TableKit.getCellIndex(cell)]);
    var row = cell.up('tr');
    var table = cell.up('table');
    var s = '&row=' + (TableKit.getRowIndex(row)+1) + '&cell=' + (TableKit.getCellIndex(cell)+1) + '&id=' + row.id + '&field=' + head.id + '&' + Form.serialize(form);
    this.ajax = new Ajax.Updater(cell, op.ajaxURI || TableKit.option('editAjaxURI', table.id)[0], Object.extend(op.ajaxOptions || TableKit.option('editAjaxOptions', table.id)[0], {
      postBody : s,
      onComplete : function() {
        var data = TableKit.cells[cell.id];
        data.active = false;
        data.refresh = true; // mark cell cache for refreshing, in case cell contents has changed and sorting is applied
      }
    }));
  },
  _cancel : function(e) {
    var cell = Event.findElement(e,'td');
    Event.stop(e);
    this.cancel(cell);
  },
  cancel : function(cell) {
    this.ajax = null;
    var data = TableKit.cells[cell.id];
    cell.innerHTML = data.htmlContent;
    data.htmlContent = '';
    data.active = false;
  },
  ajax : null
};

TableKit.Editable.textInput = function(n,attributes) {
  TableKit.Editable.addCellEditor(new TableKit.Editable.CellEditor(n, {
    element : 'input',
    attributes : Object.extend({name : 'value', type : 'text'}, attributes||{})
  }));
};
TableKit.Editable.textInput('text-input');

TableKit.Editable.multiLineInput = function(n,attributes) {
  TableKit.Editable.addCellEditor(new TableKit.Editable.CellEditor(n, {
    element : 'textarea',
    attributes : Object.extend({name : 'value', rows : '5', cols : '20'}, attributes||{})
  }));	
};	
TableKit.Editable.multiLineInput('multi-line-input');

TableKit.Editable.selectInput = function(n,attributes,selectOptions) {
  TableKit.Editable.addCellEditor(new TableKit.Editable.CellEditor(n, {
    element : 'select',
    attributes : Object.extend({name : 'value'}, attributes||{}),
    'selectOptions' : selectOptions
  }));	
};

/*
TableKit.Bench = {
  bench : [],
  start : function(){
    TableKit.Bench.bench[0] = new Date().getTime();
  },
  end : function(s){
    TableKit.Bench.bench[1] = new Date().getTime();
    alert(s + ' ' + ((TableKit.Bench.bench[1]-TableKit.Bench.bench[0])/1000)+' seconds.') //console.log(s + ' ' + ((TableKit.Bench.bench[1]-TableKit.Bench.bench[0])/1000)+' seconds.')
    TableKit.Bench.bench = [];
  }
} */

if(window.FastInit) {
  FastInit.addOnLoad(TableKit.load);
} else {
  Event.observe(window, 'load', TableKit.load);
}
//
// END TABLEKIT LIBRARY
//


/* /classes/vote.js */

 if( typeof console == "undefined" ) {
  console = { log: function() { } };
}
// To do: replace specific id and class references
Vote = Class.create(
{
  initialize: function( votable, vote_count ) {
    this.container  = $(votable);
    this.vote_link  = this.container.down( ".vote_link a" );
    this.vote_count = vote_count;

    if( typeof this.vote_link == "undefined" ) return;

    var listener = null;

    var not_logged_in = Element.hasClassName(
      this.container.down( ".vote_link a" ), 'login_required' );

    if( not_logged_in ){
      this.register_login_modal();
    }
    else {
      this.vote_link.observe( "click", this.click.bindAsEventListener(this) );
    }
  },

  click: function(ev){
    ev.stop();
    var url = this.vote_link.href;
    new Ajax.Request( url, {
      method: "post",
      onSuccess: function(request) {
        this.updateAfterVote(request);
      }.bind(this)
    },this);
  },

  // To do: refactor to use with js_open_modal. Doesn't work right now.
  register_login_modal: function(){
    this.vote_link.addClassName("js_login_modal");

    var target = window.location.href;

    var separator = "?";
    if( target.match(/\?/) ) separator = "&";

    this.vote_link.href = window.location.href +
      separator +
      "tab=contest_project&view=grouped#contest_project";

    modalObserver.attachLoginModal();
  },

  // To do: replace specific id and class references
  updateAfterVote: function( response ) {
    var voteResponse = response.responseJSON;
    this.vote_count  = voteResponse.number_of_votes;
    var count_label = this.container.down( ".vote_count_label" );
    count_label.innerHTML = this.vote_count + " votes";
    count_label.show();
    $$("#" + this.container.id + " .has_voted").invoke( "show" );
    $$(".to_vote").invoke( "hide" );
    $('contest_vote_success_message').show();

  }

});



/* /classes/smartwindow.js */

 var num_menuInterval = 0, SmartWindow = Class.create();
SmartWindow.prototype = {
	testmode : false,
	elements : $$(".smartwindow"),

	initialize : function(){
		var scope = this;
		scope.elements = (scope.testmode === true)? $$("." + scope.createElement().className) : $$(".smartwindow");
		Event.observe(window, "resize", function(){
			scope.changePosition();
		});
		scope.changePosition();
	},

	changePosition : function(){
		var scope = this, bounds;
		this.elements.each(function(element, index){
			bounds = scope.getWindowBounds(element);
			element.setStyle({left : bounds.x + "px"});
			$("menupointer").setStyle({left:bounds.xPointer+"px"});
		});
	},

	getWindowBounds : function(element){
		var windowsize = this.getWindowSize(), bounds = {}, menuWidth;
		menuWidth = Math.round((PixelMath.getCompleteElementWidth(element) - PixelMath.getCompleteElementWidth($("menu_ideas")) )/2) + PixelMath.getCompleteElementWidth($("menu"));

		if(windowsize["windowWidth"] > menuWidth + 80){
			bounds.x = (Math.round($(element).getWidth()/4)) * -1;
			bounds.xPointer = Math.round(($(element.parentNode).getWidth() - 20) /2);;
		} else {
			bounds.x = (Math.round($(element).getWidth()/2)) * -1;
			bounds.xPointer = Math.round(($(element.parentNode).getWidth() - 20) /2);
		}

		return bounds;
	},

	getWindowSize: function(){
        var xScroll, yScroll, windowWidth, windowHeight;
        if (window.innerHeight && window.scrollMaxY) {
            xScroll = document.body.scrollWidth + window.scrollMaxX;
            yScroll = window.innerHeight + window.scrollMaxY;
        } else
            if (document.body.scrollHeight > document.body.offsetHeight) {
                xScroll = document.body.scrollWidth;
                yScroll = document.body.scrollHeight;
            } else {
                xScroll = document.body.offsetWidth;
                yScroll = document.body.offsetHeight;
            }
        if (self.innerHeight) {
            windowWidth = self.innerWidth;
            windowHeight = self.innerHeight;
        } else
            if (document.documentElement && document.documentElement.clientHeight) {
                windowWidth = document.documentElement.clientWidth;
                windowHeight = document.documentElement.clientHeight;
            } else
                if (document.body) {
                    windowWidth = document.body.clientWidth;
                    windowHeight = document.body.clientHeight;
                }
        (yScroll < windowHeight)?pageHeight = windowHeight:pageHeight = yScroll;
        (xScroll < windowWidth)?pageWidth = windowWidth:pageWidth = xScroll;
        return {'pageWidth': pageWidth,'pageHeight': pageHeight,'windowWidth': windowWidth,'windowHeight': windowHeight};
    },

	createElement : function(){
		var testelement = document.createElement("ul")	;
		$(testelement).setStyle({width:"500px", height:"100px", backgroundColor:"red", position:"absolute"});
		testelement.className = "smartwindow";
		return document.body.appendChild(testelement);
	}
};

function getParam(param){
	var params = window.location.href.replace("?", "&").split("&"), par, value;
	for(var i = 0; i<params.lenght; i++){
		par = params[i].split("=");
		if(par[0] === param){
			value = par[1];
			break;
		}
	}
	return value;
}

Event.observe(document, "dom:loaded", function(){
	var smartwindow = new SmartWindow();
	var showHideControl = function(e){
		var showHide = (e)?"block":"none";
		$("ideas_know").style.display = showHide;
		$("menupointer").style.display = showHide;
		return;
	};

  if ($("menu_ideas") != null && $("ideas_know") != null) {
    $("menu_ideas").observe("mouseover", function(){
      showHideControl(true);
      clearTimeout(num_menuInterval);
    });

    $("menu_ideas").observe("mouseout", function(){
      clearTimeout(num_menuInterval);
      num_menuInterval = setTimeout(function(){
        showHideControl(false);
      }, 1000);
    });

    $("ideas_know").observe("mouseover", function(){
      showHideControl(true);
      clearTimeout(num_menuInterval);
    });
    $("ideas_know").observe("mouseout", function(){
      clearTimeout(num_menuInterval);
      num_menuInterval = setTimeout(function(){
        showHideControl(false);
      }, 1000);
    });
  }

});



/* /classes/todo_lists/prototype.add_item.js */

 if( typeof window.redirect_to !== "function" ) {
  window.redirect_to = function( href ) {
    window.location.href = href;
  };
}

var AddItemForm = Class.create();
AddItemForm.prototype = {
  initialize: function( element, redirect_to ) {
    var self = this;
    this.element = element;
    this.form    = $(element).down( "form" );

    if( typeof authenticityToken !== "undefined" ) {
      this.form.getInputs( "hidden", "authenticity_token" ).value =
        authenticityToken;
    }

    function doRequest() {
      var form = this;

      $(form).request({
        onSuccess: function( transport ) {
          if( redirect_to ) {
           window.redirect_to( redirect_to );
          }
        },

        onFailure: function( transport ) {
          var data = transport.responseJSON;
          form.down( '#todo_lists_item_title' ).value = data.title;

          for( var i in data.errors ) {
            var errorElement = element.down( "." + i + "_error .message" );
            if( errorElement ) {
              errorElement.update( data.errors[i] ).up().show();
            }
          }
        }
      });
    }

    this.form._submit = this.form.submit;
    this.form.submit = function() {
      this.fire('mmhHome:todoItemAdded');
      doRequest.call( self.form );
    };

    Event.observe( this.form, 'submit', function( event ) {
      Event.stop( event );
      this.fire('mmhHome:todoItemAdded');
      doRequest.call( this );
    });
  }
};



/* /observers/js_class_observers.js */

 var JSClassObserver = Class.create({
  initialize: function() {

  },
  findElements: function(class_name) {
    $$('.' + class_name).each(function(element, index) {
      if(element.js_class_observers == null) {
        element.js_class_observers = {};
      }
      if(element.js_class_observers[class_name] != true) {
        this.attachBehavior(element, index);
        /*
          Ideally use the line below, but we need to modify some observers
          to remove dependencies on the 'js_' class names
        */
        // element.removeClassName(class_name);
        element.js_class_observers[class_name] = true;
      }
    }, this);
  }
});



/* /observers/modal_observers.js */

 var ModalObserver = {};

ModalObserver.attachLoginModal = Class.create(JSClassObserver, {
  initialize: function() {
    this.findElements('js_login_modal');
  },
  attachBehavior: function(element) {
    element.stopObserving('click');
    element.onclick = function() { return null;};
    modal = new MMH.LoginModal(element);
  }
});

ModalObserver.attachOpenModal = Class.create(JSClassObserver, {
  initialize: function() {
    this.findElements('js_open_modal');
  },
  attachBehavior: function(element) {
    var split_href = element.href.split('#');
    var modal_id = split_href[1];
    var link_href = split_href[0];
    var modal_position =  'mouse';

    if($(modal_id).hasClassName('js_center_modal')) {
      modal_position = 'center';
    }
      new MMH.Modal(element, {
      contentElement: modal_id,
      position: modal_position
    });
  }
});

ModalObserver.attachCloseModal = Class.create(JSClassObserver, {
  initialize: function() {
    this.findElements('js_close_modal');
  },
  attachBehavior: function(element) {
    element.observe('click', function(ev){
      MMH.Modal.close();
      ev.stop();
    });
  }
});

ModalObserver.attachAllObservers = function() {
  new ModalObserver.attachLoginModal();
  new ModalObserver.attachOpenModal();
  new ModalObserver.attachCloseModal();
}

Event.onReady(function() {
  ModalObserver.attachAllObservers();
});



/* /observers/form_observers.js */

 var FormObserver = {};

FormObserver.attachIndicator = Class.create(JSClassObserver, {
  initialize: function() {
    this.findElements('js_button_indicator');
  },
  attachBehavior: function(element) {
    if(!element.hasClassName('disabled')) {
      element.observe('click', function() {
        element.addClassName('indicator');
      });
    }
   }
});

FormObserver.attachSubmitForm = Class.create(JSClassObserver, {
  initialize: function() {
    this.findElements('js_submit_form');
  },
  attachBehavior: function(element) {
    if (!logged_in && element.hasClassName('js_login_modal'))
      return;
    var form = element.up('form');

    element.observe('click', function(ev){
      if(!element.hasClassName('disabled')) {
        if(element.up('form').onsubmit) {
          element.up('form').onsubmit();
        } else {
          element.up('form').submit();
        }
      }
      ev.stop();
    });
   }
});


FormObserver.attachPromptText = Class.create(JSClassObserver, {
  initialize: function() {
    this.findElements('js_prompt_text');
  },
  attachBehavior: function(element) {
    if(element.value == element.title) {
	    	element.addClassName('prompt_text');
    }
    element.observe('focus', function() {
      if(element.value == element.title) {
      	element.value = '';
			  element.removeClassName('prompt_text');
      }
    });
    element.observe('blur', function() {
      if(element.value == '') {
      	element.value = element.title;
      	element.addClassName('prompt_text');
      }
    });
    element.observe('keydown', function() {
      if(element.value == element.title) {
      	element.value = '';
			  element.removeClassName('prompt_text');
      }
    });
  }
});

FormObserver.attachConstrainToNumbers = Class.create(JSClassObserver, {
  initialize: function() {
    this.findElements('js_numbers_only');
  },
  attachBehavior: function(element) {
    var number_codes = [
			8,9,12,27,35,36,37,39,46,	// navigation keys
			48,49,50,51,52,53,54,55,56,57, // number keys
			96,97,98,99,100,101,102,103,104,105]; // number keypad
    element.observe('keydown', function(ev) {
      if (!number_codes.include(ev.keyCode) || ev.shiftKey == true) {
	ev.stop();
      }
    });
  }
});


FormObserver.attachValidEmail = Class.create(JSClassObserver, {
  initialize: function() {
    this.findElements('js_email');
  },
  attachBehavior: function(element) {
    element.observe('keyup', function(ev) {
      var form = element.up('form');
      valEmail = new ValidateFields(form);
      valEmail.validateEmail();
      valEmail.updateButton();
    });
  }
});

FormObserver.attachValidZip = Class.create(JSClassObserver, {
  initialize: function() {
    this.findElements('js_zip');
  },
  attachBehavior: function(element) {
    element.observe('keyup', function(ev) {
      var form = element.up('form');
      valZip = new ValidateFields(form);
      valZip.validateZip();
      valZip.updateButton();
    });
  }
});

FormObserver.attachCharacterCountLimit = Class.create(JSClassObserver, {
  initialize: function() {
    this.findElements('js_character_limit');
  },
  attachBehavior: function(element) {
    var field_id_to_limit = element.id;
    var character_count_id = element.id + '_limit';
    var regular_expression = new RegExp("js_limit_to_");
    var value;
    $w($(character_count_id).className).each(function(clazz) {
      if(clazz.match(regular_expression) != null) {
        value = clazz;
      }
    });
    var character_limit = value.replace('js_limit_to_', '');
    MMH.Common.createTextCounter(field_id_to_limit, character_count_id, character_limit);
  }
});


FormObserver.attachEnableButtonByInput = Class.create(JSClassObserver, {
  initialize: function() {
    this.findElements('js_required');
  },
  attachBehavior: function(element) {
    var form = element.up('form');

    element.observe('keyup', function(ev) {
      valRequired = new ValidateFields(form); //FIXME: i am forced to use GLOBAL because of lack of context (orignal authors must fix this)
      valRequired.validateNonEmpty();
      valRequired.updateButtonRequiredInput();
    });
    element.observe('keydown',function(ev){
      if(ev.keyCode == Event.KEY_RETURN && form.select('.disabled').size() > 0 ){
        ev.stop();
      }
    });
    
    Event.observe(window,'load', function(ev) {
      onLoadValRequired = new ValidateFields(form);
      onLoadValRequired.onLoadValidateRequired();
      onLoadValRequired.onLoadButtonRequiredInput();
    });
  }
});

FormObserver.attachAllObservers = function() {
  new FormObserver.attachIndicator();
  new FormObserver.attachSubmitForm();
  new FormObserver.attachPromptText();
  new FormObserver.attachConstrainToNumbers();
  new FormObserver.attachCharacterCountLimit();
  new FormObserver.attachEnableButtonByInput();
  new FormObserver.attachValidEmail();
  new FormObserver.attachValidZip();
}

Event.onReady(function() {
  FormObserver.attachAllObservers();
});




/* /observers/link_observers.js */

 var LinkObserver = {};
LinkObserver.opened_windows = []; //hack, come back

LinkObserver.attachExternalLink = Class.create(JSClassObserver, {
  initialize: function() {
    this.findElements('js_external_link');
  },
  attachBehavior: function(element) {
    element.observe('click', function(ev) {
			if( !element.href.startsWith('javascript:') ) {
				new_window = window.open(element.href);
				LinkObserver.opened_windows.push(new_window);
				ev.stop();
			}
    });
  }
});

LinkObserver.attachTooltip = Class.create(JSClassObserver, {
  initialize: function() {
    this.findElements('js_tooltip');
  },
  attachBehavior: function(element, index) {
    var html = "<div><div id='tooltip_modal_" +
      index +
      "' class='mmhModalInset tooltip' style='padding:7px 10px;'><strong>" +
      element.title + "</strong></div></div>";
    element.title = "";

    var x_offset = element.getWidth() / 2;
    var y_offset = element.getHeight() / 2;

    var tooltip = new MMH.Modal(element, {
        content:html,
        position:'element',
        distance: {
            X:x_offset,
            Y:y_offset
        },
        tooltip:true
    });

    Event.observe(element, 'mouseover', function() {
      tooltip.open();
    });

    Event.observe(element, 'mouseout', function() {
      tooltip.close();
    });
  }
});

LinkObserver.attachShowToggleLink = Class.create(JSClassObserver, {
  initialize: function() {
    this.findElements('js_view_toggle');
  },
  attachBehavior: function(element) {
    element.previous('span').hide();
    element.observe('click', function(ev) {
      var link_text = (this.innerHTML == "view more" ? "hide" : "view more");
      this.next('span.js_hide_description').toggle();
      this.previous('span').toggle();
      this.update(link_text);
      ev.stop();
    });
  }
});

LinkObserver.attachClickToCall = Class.create(JSClassObserver, {
  initialize: function() {
    this.findElements('js_click_to_call');
  },
  attachBehavior: function(element) {

		var template = 'Template=' + element.title;
		element.writeAttribute('title', 'Click to call');
		element.observe('click', function(ev) {
			webVoicePop(template);
//			new_window = window.open(element.href);
//			LinkObserver.opened_windows.push(new_window);
			ev.stop();
		});

    }

});


// Todo: refactor into parent class, maybe?
LinkObserver.attachAllObservers = function() {
  new LinkObserver.attachExternalLink();
  new LinkObserver.attachTooltip();
  new LinkObserver.attachShowToggleLink();
  new LinkObserver.attachClickToCall();
}

Event.onReady(function() {
  LinkObserver.attachAllObservers();
});



/* /observers/omniture_tracker.js */

 ;var MMHHomePage = {};

MMHHomePage.OmnitureTracker = function(options){

  $$(".tabs li a").each(function(showcase_tab){
    showcase_tab.observe("click",function(){
      s.linkTrackVars = "prop19";
      s.prop19 ="HomePage:" + $(showcase_tab).innerHTML + " tab";
      void(s.t());
      s.linkTrackVars = "";
      s.prop19 = "";
    })
  },this);

  $("todo_smart_bar").observe("mmhHome:todoItemAdded",function(event){
    s.events = "event9";
    s.eVar15 = "HomePage:Custom";
    void(s.t());
    s.events = "";
    s.eVar15 = "";

  });

  $("answers").observe("mmhHome:questionAsked",function(event){
    //Todo : Event needs to be confirmed with business
    //s.events = "event1";
    //s.eVar4 = "HomePage:Answers:" + event.memo.questionAsked;
    //void(s.t());
  });

  $("inventory").observe("mmhHome:inventoryItemAdded",function(event){
    s.events="event15";
    s.eVar17= "HomePage:Inventory:" + event.memo.itemAdded;
    void(s.t());
    s.events="";
    s.eVar17= "";
  });

  $("chatter_items").observe("mmhHome:twitterClicked",function(event){
    s.prop7= "HomePage:Twitter";
    void(s.t());
    s.prop7= "";
  });
}


/* /classes/project_media_carousel.js */

 ProjectMediaCarousel = Class.create();
ProjectMediaCarousel.prototype = new BaseCarousel();
Object.extend(ProjectMediaCarousel.prototype, {
  customize: function() {
    this.toggleButtons();
  },
  toggleButtons: function() {

  },
  scrollRight: function() {
    this.next();
    this.updateComponents();
  },
  updateComponents: function() {
    if ($('add_captions_link')) {
      $('add_captions_link').hide();
    }
    if ($('callouts_container')) {
      $('add_captions_controls').hide();
      if(!$$('.send_to_friend_link').invoke('visible').first()){
    	  $$('.publish_results_controls').invoke('show');
      }
    }
    $('right_container').show();
  },
  setupUI: function() {
    this.dom.li_items.first().addClassName('first-child');
    this.dom.li_items.last().addClassName('last-child');
    this.dom.carousel.addClassName('horizontal_carousel');
    this.setWidth();
  },
  setWidth: function(){
    // the 10 extra pixels is for IE6
    var width = (this.calcCurrentWidth() + 10) + 'px';
    this.dom.ul_item.setStyle({
      width: width
    });
  },
  calcCurrentWidth: function() {
    var end = this.dom.li_items.length;
    var offset = 0;
    for (i = 0; i < end; i++) {
      var element = this.dom.li_items[i];
      offset += PixelMath.getCompleteElementWidth(element);
    }
    return offset;
  },
  attachListeners: function() {
    if (!this.attached) {
      this.attached = true;
      if (this.dom.next_button) {
        $$('.carousel_next').invoke('observe', 'click', function(ev) {
          this.scrollRight();
        }.bindAsEventListener(this));
      }
    }
  }
});



/* /classes/suggested_products_carousel.js */

 SuggestedProductsCarousel = Class.create();
SuggestedProductsCarousel.prototype = new BaseCarousel();
Object.extend(SuggestedProductsCarousel.prototype, {
  scrollRight: function() {
    this.next();
    this.toggleButtons();
  },
  scrollLeft: function() {
    this.previous();
    this.toggleButtons();
  },
  
  toggleButtons: function() {
    if (this.dom.li_items.length > 2) {
      this.togglePrevious('disabled_previous');
      this.toggleNext('disabled_next');
    } else{
      this.dom.previous_button.addClassName('hide');
      this.dom.next_button.addClassName('hide');
    }
  },
  togglePrevious: function(class_name) {
    if (this.first_current_item == 1) {
      this.dom.previous_button.addClassName(class_name);
    } else {
      this.dom.previous_button.removeClassName(class_name);
    }
  },
  toggleNext: function(class_name) {
    if (this.last_current_item == this.total_items) {
      this.dom.next_button.addClassName(class_name);
    } else {
      this.dom.next_button.removeClassName(class_name);
    }
  },
  
  toggleControls: function() {
    if (this.dom.li_items.length <= 2) {
      $$('.carousel_controls').first().addClassName('hide');
    }
  },
  
  attachListeners: function() {
    if (!this.attached) {
      this.attached = true;
      if (this.dom.next_button) {
        this.dom.next_button.observe('click', function(ev) {
          Event.stop(ev);
          this.scrollRight();
        }.bindAsEventListener(this));
      }
      if (this.dom.previous_button) {
        this.dom.previous_button.observe('click', function(ev) {
          Event.stop(ev);
          this.scrollLeft();
        }.bindAsEventListener(this));
      }
    }
  }
});


/* /sections/projects/my_results.js */

 function display_upload_control(link_id, number_view){
  if(number_view != null){
    $(number_view).innerHTML = "";
  }
  if($(link_id)){
    $(link_id).up('ol').getElementsBySelector('li').each(function(li) {
      li.removeClassName('upload_form_mode');
    });
    $(link_id).show();
    $(link_id).up('li').addClassName('upload_form_mode');
  }
}

function showCaptioningControls(image_count, isEditing) {
  var secondPart = "images have been uploaded";
  if(image_count == 1) {
    secondPart = "image has been uploaded";
  }

  var imageCountText = image_count + " " + secondPart;
  $('image_count_top').innerHTML = imageCountText;

  $('image_count_bottom').addClassName('count_' + image_count);
  if(!isEditing) {
    Effect.BlindDown('callouts_container');
    $('add_captions_link').removeClassName("hide");
  }
}


function showImage(url, img_id, rmv_id, upload_id, zoom_img_id) {

  if($(upload_id)) {
    $(upload_id).hide();
  }
  if($(zoom_img_id)){
    $(zoom_img_id).src = url;
  }
  $(img_id).src = url;

  // Media is being processed, modify UI
  if(url == "" || url == null){
    $(img_id).hide();
    $(img_id).up('form').replace("<div class='processing'></div>");
  } else {
    Effect.Appear(img_id,{
      duration: 3.0
    });
  }

  $(rmv_id).show();

  $(upload_id).up('li').addClassName('image_mode');
}

function showLink(link_id){
  $(link_id).show();
}

function hideElement(link_id){
  $(link_id).hide();
}

function set_published_doms(is_published, upload_policy) {
  if(is_published) {
    $('is_published_span').innerHTML = "public";
    $('publish_link_edit_media_settings').show();
    $('publish_link_edit_media_settings').innerHTML = "Make private";
    $('media_policy_link_edit_media_settings').hide();
    $$('.publish_results_controls').invoke('hide');
    $$('.send_to_friend_link').invoke('show');
  } else {
    $('is_published_span').innerHTML = "private";
    $('publish_link_edit_media_settings').innerHTML = "Make public";
    $('publish_link_bottom').show();
    $$('.publish_results_controls').invoke('show');
    $$('.send_to_friend_link').invoke('hide');
  }
  if(upload_policy) {
    $$(".media_policy_link").invoke('hide');
  } else {
    $$(".media_policy_link").invoke('show');
  }
}

function toggle_publish_link(upload_policy, location) {
  if(upload_policy) {
    $("media_policy_link_" + location).hide();
  } else {
    $("publish_link_" + location).hide();
  }
}

function setup_carousel(){
  var params = {
    slide_by: 1,
    window_size: 2
  };
  var callbacks = {};
  var projectMediaCarousel = new ProjectMediaCarousel("project_media", params, callbacks);
  projectMediaCarousel.setUp();
}

function setup_screen_name_check(screen_name_check_path, project_id){
  return function( screen_name ) {
    new Ajax.Updater( "screen_name_availability_message",
      screen_name_check_path,
      {
        method: 'get',
        parameters: {
          "screen_name": screen_name,
          "success_modal": "projects/my_results/screen_name_available_with_submit",
          "id": project_id
        },
        onFailure: function( response ) {
          $("screen_name_availability_message").replace(
            "<div id='screen_name_availability_message'><p class='error'>An Error occurred processing your request.</p></div>"
            );
        }
      }
      );
  }
}

function zoom_media(element_clicked) {
    $$('#uploaded_content li').each(function(li) {
      li.removeClassName('selected');
    });
    var elem = element_clicked.up('li');
    if(elem){
        elem.addClassName('selected');
    }
  }

function setup_observe_add_captions_link() {
  $$('.pre_editing').invoke('observe', 'click', function(){
    $('li_1').addClassName('selected');
  });
}

function setup_create_policy_link(){
  Event.observe(window, 'load', function() {
    create_upload_policy_link();
  });
}

function setup_initial_state(started_editing_media, ready_to_edit_media, has_media,
  isPublished, image_count) {

  setup_carousel();

  if (started_editing_media){
    $('right_container').show();
    $$('.add_captions').invoke('hide');
    $('li_1').addClassName('selected');
  }


  if (ready_to_edit_media) {
    $('photos_uploaded').innerHTML=  image_count + " items have been uploaded successfully.";
      if (image_count == 1) {
        $('photos_uploaded').innerHTML=  image_count + " item has been uploaded successfully.";
      }
  }

  if (has_media) {
    $('callouts_container').show();
  }

  if (started_editing_media && !isPublished) {
    $$('.publish_results_controls').invoke('show');
    $$('.send_to_friend_link').invoke('hide');
  } else {
    $$('.publish_results_controls').invoke('hide');
    if (isPublished && image_count != 0) {
      $$('.send_to_friend_link').invoke('show');
    }
  }

  if (ready_to_edit_media) {
    $('add_captions_link').removeClassName('hide');
  }

  setup_create_policy_link();
}

function create_upload_policy_link() {
  $$(".media_policy_link").each( function( link ) {

    afterModal = new MMH.Modal( link.id, {
      contentElement: 'media_policy_modal',
      position:'center'
    });
  });
}

//contains the approved media formats for upload of photos
function upload_photo_formats() {
  return ['.jpg', '.gif', '.jpeg', '.png', '.bmp', '.tif', '.tiff'];
}

//contains the approved media formats for upload of videos
function upload_video_formats() {
  return ['.avi', '.wmv', '.mpg', '.mpeg', '.mov',
  '.rm', '.3gp', '.3gp2', '.flv', '.mp4', '.dv'];
}

function check_file_formats(file_name, file_format_array) {

  for (i = 0; i < file_format_array.length; i++) {
    if (file_name.toLowerCase().indexOf(file_format_array[i]) != -1) {
      return 1; // one of the file extensions found
    }
  }
  return 0; //one of the file extensions wasn't found'
}

function update_media_upload_doms(){
  $('cancel_link').hide();
  $('mmh_loginSpinner').show();
  $('upload_button').addClassName('upload_off');
  $('first_child').addClassName('last_child');
  $('add_photo_error').hide();
}

function show_media_upload_error(message) {
  $('add_photo_error').show();
  $('add_photo_error').innerHTML = message;
  $('cancel_link').show();
  $('mmh_loginSpinner').hide();
  $('upload_button').removeClassName('upload_off');
  $('upload_button').addClassName('first_child');
  $('cancel_link').addClassName('last_child');
}

function upload_media_form_observer(){
  return function(evt){
    if(isValidMediaType( $F('file_name') )){
      if(!$('upload_button').hasClassName('upload_off')){
        update_media_upload_doms();
      } else {
        Event.stop(evt);
      }
    } else {
      $('add_photo_error').show();
      Event.stop(evt);
    }
  };
}

function isValidMediaType(file_name) {
  var fileOK = 0;

  fileOK = check_file_formats(file_name, upload_photo_formats());

  if(fileOK != 1) {
    fileOK = check_file_formats(file_name, upload_video_formats());
  }
  return file_name != '' && fileOK == 1;
}




/* /classes/image_and_video_carousel.js */

 ImageAndVideoCarousel = Class.create();
ImageAndVideoCarousel.prototype = new BaseCarousel();
Object.extend(ImageAndVideoCarousel.prototype, {
  scrollRight: function() {
    carousel_item = this.dom.li_items[this.first_current_item - 1];
    if(this.hasVideo(carousel_item)) {
      this.transitionToStill(carousel_item); 
    }
    this.next();
  },
  scrollLeft: function() {
    carousel_item = this.dom.li_items[this.first_current_item - 1];
    if(this.hasVideo(carousel_item)) {
      this.transitionToStill(carousel_item); 
    }
    this.previous();
  },
  attachListeners: function() {
    if (!this.attached) {
      this.attached = true;
      if (this.dom.next_button) {
        this.dom.next_button.observe('click', function(ev) {
          Event.stop(ev);
          this.scrollRight();
        }.bindAsEventListener(this));
      }
      if (this.dom.previous_button) {
        this.dom.previous_button.observe('click', function(ev) {
          Event.stop(ev);
          this.scrollLeft();
        }.bindAsEventListener(this));
      }
    }
    // to show a video
    $$('a.launch_video').each(function(link) {
        link.observe('click', function(ev) {
            Event.stop(ev);
            clicked_link = Event.element(ev);
            carousel_item = clicked_link.up('li');
            this.transitionToVideo(carousel_item);
        }.bind(this)); 
    }.bind(this));
  },
  hideVideoThumbnail:function(carousel_item) {
      carousel_item.down('img').addClassName('hide');
  },
  hideVideoLaunchLink:function(carousel_item) {
      carousel_item.down('a').addClassName('hide');
  },
  addVideo:function(carousel_item) {
    video_link = carousel_item.down('a');
    video_player = '<object height="315" width="386" codebase="http://download.macromedia.com/pub/shockwave/cabs/flash/swflash.cab" data="http://images.fotki.com/flash/FlowPlayerSears.swf" type="application/x-shockwave-flash"> <param value="http://images.fotki.com/flash/FlowPlayerSears.swf" name="movie"/> <param value="high" name="quality"/><param value="true" name="allowFullScreen"/> <param value="config={autoBuffering:false,splashImageFile:\'http://media.fotki.com/1_p,wssrkffbqqqbsbtxfgrfqqtdwbts,vi/ddbbrbdtf//1/1186146/5187139/215511261-vi.jpg\',videoFile: \'' + video_link.href + '\'}" name="flashvars"/> </object>';
    carousel_item.down('div.video_player').update( video_player );
  },
  showVideoThumbnail:function(carousel_item) {
      carousel_item.down('img').removeClassName('hide');
  },
  showVideoLaunchLink:function(carousel_item) {
      carousel_item.down('a').removeClassName('hide');
  },
  removeVideo:function(carousel_item) {
    carousel_item.down('div.video_player').innerHTML = '';
  },
  transitionToStill: function(carousel_item) {
      this.showVideoThumbnail(carousel_item);
      this.showVideoLaunchLink(carousel_item);
      this.removeVideo(carousel_item);
  },
  transitionToVideo: function(carousel_item) {
      this.hideVideoThumbnail(carousel_item);
      this.hideVideoLaunchLink(carousel_item);
      this.addVideo(carousel_item);
  },
  hasVideo: function(carousel_item) {
      if(carousel_item.down('div.video_player')) {
          return true;
      } else {
          return false;
      }
  }
});
