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

var Prototype = {
  Version: '1.6.0.1',

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

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

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

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

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


/* Based on Alex Arnell's inheritance implementation. */
var Class = {
  create: function() {
    var parent = null, properties = $A(arguments);
    if (Object.isFunction(properties[0]))
      parent = properties.shift();

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

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

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

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

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

    klass.prototype.constructor = klass;

    return klass;
  }
};

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

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

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

    return this;
  }
};

var Abstract = { };

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

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

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

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

    var results = [];
    for (var property in object) {
      var value = Object.toJSON(object[property]);
      if (!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 && object.constructor === Array;
  },

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

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

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

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

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

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

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

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

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

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

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

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

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

Date.prototype.toJSON = function() {
  return '"' + this.getUTCFullYear() + '-' +
    (this.getUTCMonth() + 1).toPaddedString(2) + '-' +
    this.getUTCDate().toPaddedString(2) + 'T' +
    this.getUTCHours().toPaddedString(2) + ':' +
    this.getUTCMinutes().toPaddedString(2) + ':' +
    this.getUTCSeconds().toPaddedString(2) + 'Z"';
};

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

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

    return returnValue;
  }
};

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

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

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

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

    this.registerCallback();
  },

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

  times: function(count) {
    return count < 1 ? '' : new Array(count + 1).join(this);
  },

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

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

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

    return camelized;
  },

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

var $break = { };

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Object.extend(Enumerable, {
  map:     Enumerable.collect,
  find:    Enumerable.detect,
  select:  Enumerable.findAll,
  filter:  Enumerable.findAll,
  member:  Enumerable.include,
  entries: Enumerable.toArray,
  every:   Enumerable.all,
  some:    Enumerable.any
});
function $A(iterable) {
  if (!iterable) return [];
  if (iterable.toArray) return iterable.toArray();
  var length = iterable.length, results = new Array(length);
  while (length--) results[length] = iterable[length];
  return results;
}

if (Prototype.Browser.WebKit) {
  function $A(iterable) {
    if (!iterable) return [];
    if (!(Object.isFunction(iterable) && iterable == '[object NodeList]') &&
        iterable.toArray) return iterable.toArray();
    var length = iterable.length, results = new Array(length);
    while (length--) results[length] = iterable[length];
    return results;
  }
}

Array.from = $A;

Object.extend(Array.prototype, Enumerable);

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

$w('abs round ceil floor').each(function(method){
  Number.prototype[method] = Math[method].methodize();
});
function $H(object) {
  return new Hash(object);
};

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

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

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

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

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

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

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

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

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

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

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

    merge: function(object) {
      return this.clone().update(object);
    },

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

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

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

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

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

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

Hash.prototype.toTemplateReplacements = Hash.prototype.toObject;
Hash.from = $H;
var ObjectRange = Class.create(Enumerable, {
  initialize: function(start, end, exclusive) {
    this.start = start;
    this.end = end;
    this.exclusive = exclusive;
  },

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

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

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

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

  activeRequestCount: 0
};

Ajax.Responders = {
  responders: [],

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

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

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

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

Object.extend(Ajax.Responders, Enumerable);

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

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

    this.options.method = this.options.method.toLowerCase();

    if (Object.isString(this.options.parameters))
      this.options.parameters = this.options.parameters.toQueryParams();
    else if (Object.isHash(this.options.parameters))
      this.options.parameters = this.options.parameters.toObject();
  }
});

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

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

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

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

    this.parameters = params;

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    if(readyState == 4) {
      var xml = transport.responseXML;
      this.responseXML  = 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);
    } 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);
    } catch (e) {
      this.request.dispatchException(e);
    }
  }
});

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

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

    $super(url, options);
  },

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

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

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

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

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

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

    this.start();
  },

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

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

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

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

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

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

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

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

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

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

Element.cache = { };

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

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

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

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

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

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

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

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

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

    var content, t, range;

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

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

      content = Object.toHTML(content);

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

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

    return element;
  },

  wrap: function(element, wrapper, attributes) {
    element = $(element);
    if (Object.isElement(wrapper))
      $(wrapper).writeAttribute(attributes || { });
    else if (Object.isString(wrapper)) wrapper = new Element(wrapper, attributes);
    else wrapper = new Element('div', wrapper);
    if (element.parentNode)
      element.parentNode.replaceChild(wrapper, element);
    wrapper.appendChild(element);
    return 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).getElementsBySelector("*");
  },

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

  descendantOf: function(element, ancestor) {
    element = $(element), ancestor = $(ancestor);
    var originalAncestor = ancestor;

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

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

    while (element = element.parentNode)
      if (element == originalAncestor) return true;
    return false;
  },

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

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

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

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

    return element;
  },

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    return $(document.body);
  },

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

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

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

    } while (element = element.offsetParent);

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

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

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

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

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

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

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

Element.Methods.identify.counter = 1;

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

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


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

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

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

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

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

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

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

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

    return element;
  };
}

if (Prototype.Browser.Opera) {
  Element.Methods.getStyle = Element.Methods.getStyle.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) {
  $w('positionedOffset getOffsetParent viewportOffset').each(function(method) {
    Element.Methods[method] = Element.Methods[method].wrap(
      function(proceed, element) {
        element = $(element);
        var position = element.getStyle('position');
        if (position != 'static') return proceed(element);
        element.setStyle({ position: 'relative' });
        var value = proceed(element);
        element.setStyle({ position: position });
        return value;
      }
    );
  });

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

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

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

  Element.Methods.setOpacity = function(element, value) {
    function stripAlpha(filter){
      return filter.replace(/alpha\([^\)]*\)/gi,'');
    }
    element = $(element);
    var 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.clone(Element._attributeTranslations.read.names),
    values: {
      checked: function(element, value) {
        element.checked = !!value;
      },

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

  Element._attributeTranslations.has = {};

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

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

else if (Prototype.Browser.Gecko && /rv:1\.8\.0/.test(navigator.userAgent)) {
  Element.Methods.setOpacity = function(element, value) {
    element = $(element);
    element.style.opacity = (value == 1) ? 0.999999 :
      (value === '') ? '' : (value < 0.00001) ? 0 : value;
    return element;
  };
}

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

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

    return element;
  };

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Element.Methods.ByTag = { };

Object.extend(Element, Element.Methods);

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

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

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

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

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

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

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

    element._extendedByPrototype = Prototype.emptyFunction;
    return element;

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

document.viewport = {
  getDimensions: function() {
    var dimensions = { };
    var B = Prototype.Browser;
    $w('width height').each(function(d) {
      var D = d.capitalize();
      dimensions[d] = (B.WebKit && !document.evaluate) ? self['inner' + D] :
        (B.Opera) ? document.body['client' + D] : document.documentElement['client' + D];
    });
    return dimensions;
  },

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

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

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

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

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

    var e = this.expression;

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

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

    return true;
  },

  compileMatcher: function() {
    if (this.shouldUseXPath())
      return this.compileXPathMatcher();

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    return match;
  },

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    return matchingInputs;
  },

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

  cache: { },

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

Event.Methods = (function() {
  var isButton;

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

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

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

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

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

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

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

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

    stop: function(event) {
      Event.extend(event);
      event.preventDefault();
      event.stopPropagation();
      event.stopped = true;
    }
  };
})();

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

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

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

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

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

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

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

  function getDOMEventName(eventName) {
    if (eventName && eventName.include(':')) return "dataavailable";
    return eventName;
  }

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

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

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

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

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

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

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

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

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

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

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

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

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

      return element;
    },

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

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

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

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

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

      destroyWrapper(id, eventName, handler);

      return element;
    },

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

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

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

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

      return Event.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()
});

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

  var timer, fired = false;

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

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

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

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

  } else {
    document.write("<script id=__onDOMContentLoaded defer src=//:><\/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();

LowPro = {};
LowPro.Version = '0.4';

if (!Element.addMethods) 
  Element.addMethods = function(o) { Object.extend(Element.Methods, o) };

// Simple utility methods for working with the DOM
DOM = {
  nextElement : function(element) {
    element = $(element);
    while (element = element.nextSibling) 
      if (element.nodeType == 1) return element;
    return null;
  },
  previousElement : function(element) {
    element = $(element);
    while (element = element.previousSibling) 
      if (element.nodeType == 1) return element;
    return null;
  },
  remove : function(element) {
    element = $(element);
    return element.parentNode.removeChild(element);
  },
  insertAfter : function(element, node, otherNode) {
    element = $(element);
    return element.insertBefore(node, otherNode.nextSibling);
  },
  addBefore : function(element, node) {
    element = $(element);
    return element.parentNode.insertBefore(node, element);
  },
  addAfter : function(element, node) {
    element = $(element);
    return $(element.parentNode).insertAfter(node, element);
  },
  replaceElement : function(element, node) {
    $(element).parentNode.replaceChild(node, element);
    return node;
  }
};

// Add them to the element mixin
Element.addMethods(DOM);

// DOMBuilder for prototype
DOM.Builder = {
  IE_TRANSLATIONS : {
    'class' : 'className',
    'for' : 'htmlFor'
  },
  ieAttrSet : function(attrs, attr, el) {
    var trans;
    if (trans = this.IE_TRANSLATIONS[attr]) el[trans] = attrs[attr];
    else if (attr == 'style') el.style.cssText = attrs[attr];
    else if (attr.match(/^on/)) el[attr] = new Function(attrs[attr]);
    else el.setAttribute(attr, attrs[attr]);
  },
	tagFunc : function(tag) {
	  return function() {
	    var attrs, children; 
	    if (arguments.length>0) { 
	      if (arguments[0].nodeName || 
	        typeof arguments[0] == "string") 
	        children = arguments; 
	      else { 
	        attrs = arguments[0]; 
	        children = [].slice.call(arguments, 1); 
	      };
	    }
	    return DOM.Builder.create(tag, attrs, children);
	  };
  },
	create : function(tag, attrs, children) {
		attrs = attrs || {}; children = children || [];
		var isIE = navigator.userAgent.match(/MSIE/);
		var el = document.createElement(
		  (isIE && attrs.name) ? 
		  "<" + tag + " name=" + attrs.name + ">" : tag
		);
		
		for (var attr in attrs) {
		  if (typeof attrs[attr] != 'function') {
		    if (isIE) this.ieAttrSet(attrs, attr, el);
		    else el.setAttribute(attr, attrs[attr].toString());
		  };
	  }
	  
		for (var i=0; i<children.length; i++) {
			if (typeof children[i] == 'string') 
			  children[i] = document.createTextNode(children[i]);
			el.appendChild(children[i]);
		}
		return $(el);
	}
};

// Automatically create node builders as $tagName.
(function() { 
	var els = ("p|div|span|strong|em|img|table|tr|td|th|thead|tbody|tfoot|pre|code|" + 
				   "h1|h2|h3|h4|h5|h6|ul|ol|li|form|input|textarea|legend|fieldset|" + 
				   "select|option|blockquote|cite|br|hr|dd|dl|dt|address|a|button|abbr|acronym|" +
				   "script|link|style|bdo|ins|del|object|param|col|colgroup|optgroup|caption|" + 
				   "label|dfn|kbd|samp|var").split("|");
  var el, i=0;
	while (el = els[i++]) 
	  window['$' + el] = DOM.Builder.tagFunc(el);
})();



// Adapted from DOM Ready extension by Dan Webb
// http://www.vivabit.com/bollocks/2006/06/21/a-dom-ready-extension-for-prototype
// which was based on work by Matthias Miller, Dean Edwards and John Resig
//
// Usage:
//
// 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)
            var dummy = location.protocol == "https:" ?  "https://javascript:void(0)" : "javascript:void(0)";
            document.write("<script id=__ie_onload defer src='" + dummy + "'><\/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);
  }
});

// Extend Element with observe and stopObserving.
if (typeof Element.Methods.observe == 'undefined') Element.addMethods({
  observe : function(el, event, callback) {
    Event.observe(el, event, callback);
  },
  stopObserving : function(el, event, callback) {
    Event.stopObserving(el, event, callback);
  }
});

// Replace out existing event observe code with Dean Edwards' addEvent
// http://dean.edwards.name/weblog/2005/10/add-event/
Object.extend(Event, {
  observe : function(el, type, func) {
    el = $(el);
    if (!func.$$guid) func.$$guid = Event._guid++;
  	if (!el.events) el.events = {};
  	var handlers = el.events[type];
  	if (!handlers) {
  		handlers = el.events[type] = {};
  		if (el["on" + type]) {
  			handlers[0] = el["on" + type];
  		}
  	}
  	handlers[func.$$guid] = func;
  	el["on" + type] = Event._handleEvent;
  	
  	 if (!Event.observers) Event.observers = [];
  	 Event.observers.push([el, type, func, false]);
	},
	stopObserving : function(el, type, func) {
	  el = $(el);
    if (el.events && el.events[type]) delete el.events[type][func.$$guid];
    
    for (var i = 0; i < Event.observers.length; i++) {
      if (Event.observers[i] &&
          Event.observers[i][0] == el && 
          Event.observers[i][1] == type && 
          Event.observers[i][2] == func) delete Event.observers[i];
    }
  },
  _handleEvent : function(e) {
    var returnValue = true;
    e = e || Event._fixEvent(window.event);
    var handlers = this.events[e.type], el = $(this);
    for (var i in handlers) {
    	el.$$handleEvent = handlers[i];
    	if (el.$$handleEvent(e) === false) returnValue = false;
    }
  	return returnValue;
  },
  _fixEvent : function(e) {
    e.preventDefault = Event._preventDefault;
    e.stopPropagation = Event._stopPropagation;
    return e;
  },
  _preventDefault : function() { this.returnValue = false },
  _stopPropagation : function() { this.cancelBubble = true },
  _guid : 1
});

// Allows you to trigger an event element.  
Object.extend(Event, {
  trigger : function(element, event, fakeEvent) {
    element = $(element);
    fakeEvent = fakeEvent || { type :  event };
    if(element.events && element.events[event]) { 	
      $H(element.events[event]).each(function(cache) {
        cache[1].call(element, fakeEvent);
    	});
    }
  }
});

// Based on event:Selectors by Justin Palmer
// http://encytemedia.com/event-selectors/
//
// Usage:
//
// Event.addBehavior({
//      "selector:event" : function(event) { /* event handler.  this refers to the element. */ },
//      "selector" : function() { /* runs function on dom ready.  this refers to the element. */ }
//      ...
// });
//
// Multiple calls will add to exisiting rules.  Event.addBehavior.reassignAfterAjax and
// Event.addBehavior.autoTrigger can be adjusted to needs.
Event.addBehavior = function(rules) {
  var ab = this.addBehavior;
  Object.extend(ab.rules, rules);
  
  if (!ab.responderApplied) {
    Ajax.Responders.register({
      onComplete : function() { 
        if (Event.addBehavior.reassignAfterAjax) 
          setTimeout(function() { ab.unload(); ab.load(ab.rules) }, 10);
      }
    });
    ab.responderApplied = true;
  }
  
  if (ab.autoTrigger) {
    this.onReady(ab.load.bind(ab, rules));
  }
  
};

Object.extend(Event.addBehavior, {
  rules : {}, cache : [],
  reassignAfterAjax : true,
  autoTrigger : true,
  
  load : function(rules) {
    for (var selector in rules) {
      var observer = rules[selector];
      var sels = selector.split(',');
      sels.each(function(sel) {
        var parts = sel.split(/:(?=[a-z]+$)/), css = parts[0], event = parts[1];
        $$(css).each(function(element) {
          if (event) {
            $(element).observe(event, observer);
            Event.addBehavior.cache.push([element, event, observer]);
          } else {
            if (!element.$$assigned || !element.$$assigned.include(observer)) {
              if (observer.attach) observer.attach(element);
              
              else observer.call($(element));
              element.$$assigned = element.$$assigned || [];
              element.$$assigned.push(observer);
            }
          }
        });
      });
    }
  },
  
  unload : function() {
    this.cache.each(function(c) {
      Event.stopObserving.apply(Event, c);
    });
    this.cache = [];
  }
  
});

Event.observe(window, 'unload', Event.addBehavior.unload.bind(Event.addBehavior));

// Behaviors can be bound to elements to provide an object orientated way of controlling elements
// and their behavior.  Use Behavior.create() to make a new behavior class then use attach() to
// glue it to an element.  Each element then gets it's own instance of the behavior and any
// methods called onxxx are bound to the relevent event.
// 
// Usage:
// 
// var MyBehavior = Behavior.create({
//   onmouseover : function() { this.element.addClassName('bong') } 
// });
//
// Event.addBehavior({ 'a.rollover' : MyBehavior });
// 
// If you need to pass additional values to initialize use:
//
// Event.addBehavior({ 'a.rollover' : MyBehavior(10, { thing : 15 }) })
//
// You can also use the attach() method.  If you specify extra arguments to attach they get passed to initialize.
//
// MyBehavior.attach(el, values, to, init);
//
Behavior = {
  create : function(members) {
    var behavior = function() { 
      if (this == window) {
        var args = [], behavior = arguments.callee;
        for (var i = 0; i < arguments.length; i++) 
          args.push(arguments[i]);
          
        return function(element) {
          var initArgs = [this].concat(args);
          behavior.attach.apply(behavior, initArgs);
        };
      } else this.element = $(arguments[0]);
    };
    behavior.prototype.initialize = Prototype.K;
    Object.extend(behavior.prototype, members);
    Object.extend(behavior, Behavior.ClassMethods);
    return behavior;
  },
  ClassMethods : {
    attach : function() {
      var element = arguments[0];
      var bound = new this(element);
      bound.initialize.apply(bound, [].slice.call(arguments, 1));
      this._bindEvents(bound);
      return bound;
    },
    _bindEvents : function(bound) {
      for (var member in bound)
        if (member.match(/^on(.+)/) && typeof bound[member] == 'function')
          bound.element.observe(RegExp.$1, bound[member].bindAsEventListener(bound));
    }
  }
};




var Accordion = Class.create({
	effectInProgress: false,
	initialize: function(accordionMembers) {
		this.accordionMembers = [];
		for (var i=0;i<accordionMembers.length;i++) {
			this.register(accordionMembers[i]);
		}
		this.options = Object.extend({
			onAccordion: function() {},
			ifOne: function() {},
			openEffect: Effect.BlindDown,
			closeEffect: Effect.BlindUp,
			alwaysOpen: true,
			openOnStart: true
		}, arguments[1] || {});
		if (this.options.openOnStart || this.options.alwaysOpen) {
			this.accordion(this.accordionMembers[0]);
		}
		if (this.accordionMembers.length == 1) {
			this.options.ifOne(this.accordionMembers[0]);
		}
	},
	register: function(accordionMember) {
		for (var i=0;i<accordionMember.elements.length;i++) {
			Event.observe(accordionMember.elements[i], 'click', function() {
				this.accordion(accordionMember);
			}.bind(this));
		}
		this.accordionMembers.push(accordionMember);
	},
	getActiveAccordionMember: function() {
		for (var i=0;i<this.accordionMembers.length;i++) {
			if (this.accordionMembers[i].active) {
				return this.accordionMembers[i];
			}
		}

		return false;
	},
	accordion: function(accordionMember) {
		var activeAccordion = this.getActiveAccordionMember();
		if (accordionMember != activeAccordion && !this.effectInProgress) {
			this.effectInProgress = true;
			if (activeAccordion) {
				this.deactivate(activeAccordion);
			}
			this.activate(accordionMember);
			this.options.onAccordion(accordionMember, activeAccordion);
		} else if (accordionMember == activeAccordion && !this.effectInProgress && !this.options.alwaysOpen) {
			this.deactivate(activeAccordion);
			this.options.onAccordion(false, activeAccordion);
		}
		return false;
	},
	activate: function(accordionMember) {
		this.options.openEffect(accordionMember.contents, {afterFinish: this.afterEffect.bind(this)});
		accordionMember.active = true;
		(accordionMember.options.onAccordion.bind(accordionMember))();
		(accordionMember.options.onOpen.bind(accordionMember))();
	},
	deactivate: function(accordionMember) {
		this.options.closeEffect(accordionMember.contents);
		accordionMember.active = false;
		(accordionMember.options.onAccordion.bind(accordionMember))();
		(accordionMember.options.onOpen.bind(accordionMember))();
	},
	afterEffect: function() {
		this.effectInProgress = false;
	}
});

var AccordionMember = Class.create({
	active: false,
	contents: false,
	elements: false,
	options: false,
	initialize: function(contents, elements) {
		this.contents = $(contents);
		this.contents.style.display = 'none';
		this.elements = (elements.constructor == Array) ? elements:[elements];
		this.options = Object.extend({
			onAccordion: function() {},
			onOpen: function() {},
			onClose: function() {}
		}, arguments[2] || {});
	}
});


Element.addMethods({
    spin: function(element, text) {
        element = $(element);
				
        var img = '<img src="/images/ajaxloading.gif" class="icon" alt="Loading..." /> ';
        //make sure there's text in string form
        if (!text) {
            text = '';
        }
				//update the element with a div.spin and the img
        element.update('<div class="spin">' + img + text + '</div>')
        return element;
        //return the element for chaining
    },

    stopSpin: function(element) {
        element = $(element);
        if (element) {
            //element exists
            element.innerHTML = '';
            //reset it's contents to nothing
        }
    },

    spinBeside: function (element, text) {
        element = $(element);
        //the element next to which we will spin

        id = (element.id);
        id = 'spin_' + id;
        //id for the spinner

        var html = '<div id="' + id + '"></div>';
        //spinner html

        var body = $$('body')[0];
        //body

        new Insertion.Bottom(body, html);
        //insert

        var spinElement = $(id);
        //the spinner element

        spinElement.spin(text);
        //make it spin, see the spin() method above

        Position.absolutize(spinElement);
        Position.clone(element, spinElement, {
            offsetLeft: element.getWidth() + 10,
            offsetTop: Math.round(element.getHeight() / 2 - spinElement.getHeight() / 2),
            setWidth: false,
            setHeight: false
        });
        //position it

        return element;
        //return element for chaining
    },

    stopSpinBeside: function(element, id) {
        element = $(element);
        //the element next to which there's a spinner

        id = (element.id);
        id = 'spin_' + id;
        //id for the spinner

        if ($(id)) {
            //check whether it exists to be sure
            $(id).remove();
            //remove the element from the document
        }

        return element;
        //return element for chaining
    }

});

/********************************
** ajaxhelper.js
**
** global ajax handler
** Design Kitchen
** @author: Matthew Story
*********************************/
var AjaxHelper = Class.create({
	currentRequest: false,
	minTime: 200,
	queueRequests: true,
	hash: 'hash',
	queue: [],
	timeout: false,
	outstandingHashes: [],
	queueIsCurrent: true,

	initialize: function() {
		var options = Object.extend({
			queueRequests: true,
			queueIsCurrent: true,
			minTime: 500,
			//hash param name . .. if false, don't send
			hash: 'hash'
		}, arguments[0] || {});
		this.queueRequests = options.queueRequests;
		this.minTime = options.minTime;
		this.hash = options.hash;
		this.queueIsCurrent = options.queueIsCurrent;
	},

	destroy: function() {
		this.currentRequest = null;
		this.minTime = null;
		this.queueRequests = null;
		this.hash = null;
		this.queue = null;
		this.timeout = null;
		this.outstandingHashes = null;
		this.queueIsCurrent = null;
	},

	request: function(url, ajaxOptions) {
		if (this.queueIsCurrent) {
			ajaxOptions = this.setCurrentHash(ajaxOptions);
		}
		if (this.timeout) {
			var index = (this.queueRequests) ? this.queue.length:0;
			this.queue[index] = new AjaxHelper.QueueObject(url, ajaxOptions);
		} else {
			this.executeAjax(url, ajaxOptions);
		}
	},

	makeOptions: function(ajaxOptions) {
		if (this.hash) {
			if (!ajaxOptions.parameters) {
				ajaxOptions.parameters = {};
			}
			ajaxOptions.parameters[this.hash] = this.getUniqueHash();
			if (ajaxOptions.onSuccess) {
				var userSuccess = ajaxOptions.onSuccess;
				ajaxOptions.onSuccess = function(response, json) {
					if (this.checkHash(response, json)) {
						userSuccess(response, json);
					}
				}.bind(this);
			}
			if (ajaxOptions.onComplete) {
				var userComplete = ajaxOptions.onComplete;
				ajaxOptions.onComplete = function(response, json) {
					if (this.checkHash(response, json)) {
						userComplete(response, json);
					}
				}.bind(this);
			}
			var userFailure = (ajaxOptions.onFailure) ? ajaxOptions.onFailure:function() {};
			ajaxOptions.onFailure = function(response, json) {
				this.clearHashFromOutstanding(ajaxOptions.hash);
				userFailure(response, json);
			}.bind(this);
		}
		
		return ajaxOptions;
	},
	
	executeAjax: function(url, ajaxOptions) {
		if (!this.queueIsCurrent) {
			var ajaxOptions = this.setCurrentHash(ajaxOptions);
		}
		new Ajax.Request(url, ajaxOptions);
		this.timeout = window.setTimeout(this.queueHandler.bind(this), this.minTime);
	},

	queueHandler: function() {
		if (this.queue.length) {
			var next = this.queue.shift();
			this.executeAjax(next.url, next.ajaxOptions);
		} else {
			this.timeout = false;
		}
	},
	
	setCurrentHash: function(ajaxOptions) {
		ajaxOptions = this.makeOptions(ajaxOptions);
		if (this.hash) {
			this.currentRequest = ajaxOptions.parameters[this.hash];
			this.outstandingHashes.push(ajaxOptions.parameters[this.hash]);
		}

		return ajaxOptions;
	},

	getUniqueHash: function() {
		var dateHash = new Date();
		var i=0;
		while(!this.checkUniqueHash(hash)) {
			var hash = Date.parse(dateHash)*1000+dateHash.getMilliseconds()+i;
		}
		return hash;
	},

	checkUniqueHash: function(hash) {
		if (!hash) {
			return false;
		}
		for (var i=0;i<this.outstandingHashes.length;i++) {
			if (this.outstandingHashes[i] == hash) {
				return false;
			}
		}
		return true;
	},

	checkHash: function(response, json) {
		json = (json) ? json:{};
		try {
			response = response.responseText;
			response = (response.constructor != String) ? response:response.evalJSON();
		} catch (e) {
			response = response.responseText;
		}

		hash = (json[this.hash]) ? json[this.hash]:response[this.hash];
		this.clearHashFromOutstanding(hash);
		var hash = (this.currentRequest == hash) ? true:false;
		return hash;
	},

	clearHashFromOutstanding: function(hash) {
		var hashes = [];
		for (var i=0;i<this.outstandingHashes.length;i++) {
			if (this.outstandingHashes[i] != hash) {
				hashes.push(this.outstandingHashes[i]);
			}
		}
		this.outstandingHashes = hashes;
	}
});

AjaxHelper.QueueObject = Class.create({
	url: false,
	ajaxOptions: false,
	initialize: function(url, ajaxOptions) {
		this.url = url;
		this.ajaxOptions = ajaxOptions;
	},
	
	destroy: function() {
		this.url = null;
		this.ajaxOptions = null;
	}
});


var AjaxPopUp = Class.create({
	element: false,
	popupElement: false,
	hover: false,
	timeout: false,
	open: false,
	x: false,
	y: false,
	initialize: function(element, popup, url) {
		this.element = $(element);
		this.popupElement = $(popup);
		this.url = url;
		this.options = Object.extend({
			popupWrapper: false,
			onOpen: function() {},
			delay: 1,
			ajaxOptions: {},
			close: false,
			x: false,
			y: false,
			z: 1000
		}, arguments[3] || {})
		this.options.popupWrapper = $(this.options.popupWrapper) || this.popupElement;
		this.setupEvents();
	},

	setupEvents: function() {
		Event.observe(this.element, 'mouseover', this.mouseIn.bind(this));
		if (this.options.close) {
			Event.observe(this.options.close, 'click', this.closePopUp.bind(this));
		}
	},

	mouseIn: function(event) {
		this.hover = true;
		this.x = Event.pointerX(event);
		this.y = Event.pointerY(event);
		this.timeout = window.setTimeout(this.popUp.bind(this), this.options.delay*1000);
		Event.observe(this.element, 'mouseout', this.mouseOut.bind(this));
	},
	
	mouseOut: function(event) {
		//see if we moved to the popup element
		var el = $(event.relatedTarget)
		if (!el || (!el.descendantOf(this.options.popupWrapper) && !el.descendantOf(this.element) && el != this.element && el != this.options.popupWrapper)) {
			this.hover = false;
			this.closePopUp();
		}
	},

	popUp: function() {
		this.timeout = false;
		if (this.hover && !this.open) {
			this.positionPopUp();
			new Ajax.Updater(this.popupElement, this.url, this.options.ajaxOptions);
			Event.observe(this.options.popupWrapper, 'mouseout', this.mouseOut.bind(this));
			this.open = true;
			this.options.onOpen();
		}
	},

	positionPopUp: function() {
		this.options.popupWrapper.style.display = '';
		this.options.popupWrapper.style.position = 'absolute';
		this.options.popupWrapper.style.zIndex = this.options.z;
		this.options.popupWrapper.style.left = (this.options.x) ? ((this.options.x.constructor == Function) ? this.options.x(this.element)+'px':this.options.x+'px'):this.x+'px';
		this.options.popupWrapper.style.top = (this.options.y) ? ((this.options.y.constructor == Function) ? this.options.y(this.element)+'px':this.options.y+'px'):this.y+'px';
	},

	closePopUp: function() {
		this.open = false;
		this.options.popupWrapper.style.display = 'none';
		Event.stopObserving(this.options.popupWrapper, 'mouseout', this.mouseOut);
		Event.stopObserving(this.element, 'mouseout', this.mouseOut);
	}
});


SearchBoxBehaviour = Behavior.create({
	initialize:function(ctaText){
		this.ctaText = this.getCTAFromClassNames() || "Search";
		this.element.value = this.ctaText;
		// bringing this inline styling from DK code, not sure why necessary maybe for outer container
		if (Prototype.Browser.Gecko) {
			this.element.style.paddingTop = '7px';
		} else if (Prototype.Browser.IE) {
			this.element.style.paddingTop = '4px';
		}
	},
	onfocus:function(e){
		this.element.addClassName('active');
		this.reset();
	},
	onblur:function(e){
		this.element.removeClassName('active');	
		if(this.element.value.blank()){
			this.element.value = this.ctaText;
		}
	},
	reset:function(){
		this.element.value = '';
	}, 
	getCTAFromClassNames:function(){
		var classNames = this.element.classNames().toArray();
		return classNames.length > 1 ? "Search " + classNames.last().gsub("_", " ") : "Search";
	}
});

SearchBoxClearBehavior = Behavior.create({
	initialize:function(){
		var searchBox = this.element.siblings().first();
		if(searchBox.hasClassName('search_box')){
			this.searchBox = searchBox;
		} else {
			this.searchBox = false;
		}
	},
	onclick:function(e){
		if(this.searchBox){
			this.searchBox.value = '';
		}
	}
})

// the edit toolbar we see when rolling over a published essay (probably all over the site as well)
ShowEssayToolbar = Behavior.create({
	initialize:function(){
	  this.toolbar = this.element.down(".list_item_toolbar");
	},
	onmouseover:function(e){
	  this.toolbar.show();
	},
	onmouseout:function(e){
	  this.toolbar.hide();
	}
});

// 
AjaxClickManager = Behavior.create({
	initialize:function(httpMeth,message){
		this.httpMeth = httpMeth;
		this.message 	= message;

	},
	sendRequest:function(){
		var that = this;
		new Ajax.Request(this.element.href, {
			method : that.httpMeth,
			onLoading:function(){
				this.container = that.element.up();
				this.original_text = this.container.innerHTML;
				Element.update(this.container, that.message);				
			},
			onFailure:function(res){
				alert("Failed");
			}, 
			// reset original text
			onSuccess:function(res){
				this.container.innerHTML = this.original_text;
			}
		}, that);
	},
	onclick:function(e){
		Event.stop(e);
		if(this.httpMeth == "delete"){
			if(confirm("Are you sure?")){
				this.sendRequest();
			}	
		}else{
			this.sendRequest();
		}
	}
})

CloseParentModule = Behavior.create({
	initialize:function(blind_up){
		// superhacky; fix
		this.blind_up = blind_up;
	},
	onclick:function(e){
		Event.stop(e);
		var parentModule = this.element.up(1); // dom relationship too hard-coded for now
		if(this.blind_up == false){
			parentModule.hide();
		}else{
			new Effect.BlindUp(parentModule); 
		}
		
	}
})

TrackEvent = Behavior.create({
	// apply classNames like so
	// eventTracker track_Accordion_Open label_name-of-arbitrary-label
	initialize:function(){
		var classNames 		= this.element.className;		
		var actionMatches = classNames.match(/track_(\w+)_(\w+)/);
		var labelMatches 	= classNames.match(/label_([\w\s&\-\d\.]+)/)

		this.eventName 	 = actionMatches[1];
		this.eventAction = actionMatches[2];
		this.eventLabel  = labelMatches ? labelMatches[1].split(" ").first() : "";
	}, 
	onclick:function(e){
		if(location.href.match(/localhost/)){
			console.log("clicked from " + " : eventName " + this.eventName + " : label: " + this.eventLabel + " : action " + this.eventAction);	
		}else if(location.href.match(/http:\/\/74\.201\.255\.129\//)){
	 // do nothing on staging
		}else if(location.href.match(/radio-info/)){
			pageTracker._trackEvent(this.eventName, this.eventAction, this.eventLabel);
		}
	}
})

showHideFormat = Behavior.create({
	initialize:function(){
		this.opened = true;
		this.img_link = this.element.down();
	},
	onclick:function(e){
			Event.stop(e);
			var div_to_toggle = this.element.up(1).next();		
			 
			if(div_to_toggle.visible()){
				this.opened = false;
				div_to_toggle.hide();	
				this.element.innerHTML = "<img src='/images/btn-arrowright.gif', alt='Show Charts', title ='Show Charts'>";
			}else{
				this.opened = true;
				div_to_toggle.show();			
  			this.element.innerHTML = "<img src='/images/btn-arrowdown.gif', alt='Hide Charts', title='Hide Charts'>";
			}
	} 
	// onmouseover:function(e){
	// 	// console.log("in onmouseover " + this.opened);
	// 	if(this.opened == true){
	// 		this.img_link.src = "/images/btn-arrowdown-wht.gif";
	// 	}else{
	// 		console.log('in false onmouseover this img_link ' + this.img_link.src);
	// 		this.img_link.src = "/images/btn-arrowright-wht.gif";
	// 		console.log('--> in false onmouseover this img_link ' + this.img_link.src);
	// 	}
	// },
	// onmouseout:function(e){
	// 	// console.log("in onmouseout " + this.opened);
	// 	if(this.opened == true){
	// 		this.img_link.src = "/images/btn-arrowdown.gif";
	// 	}else{
	// 		console.log('in false onmouseout '  + this.img_link.src);
	// 		this.img_link.src = "/images/btn-arrowright-wht.gif";
	// 	}
	// 	
	// }
})

ToggleImageInLinkBehavior = Behavior.create({
	initialize:function(out, over){

		this.img_link = this.element.down();
		// console.log(this.img_link.src);
		this.out = "/images/" + out;
		this.over = "/images/" + over;
	}, 
	onmouseover:function(e){
		this.img_link.src = this.over;
		
	},
	onmouseout:function(e){
		this.img_link.src = this.out;
	}
})
Event.addBehavior({
	// ga event tracking
	'.eventTracker' : TrackEvent(),
	// Search box
	'#filter.search_box' : SearchBoxBehaviour(),
	'#search_field.search_box' : SearchBoxBehaviour(),
	'.clearSearch' : SearchBoxClearBehavior(),
	// essay cmt & editing mgmnt mgmnt
	'.view_essay' : ShowEssayToolbar(), 
 	'a.unallow_comments': AjaxClickManager("put", "dis-allowing..."),
 	'a.allow_comments': AjaxClickManager("put", "allowing..."),
	// Comment mgmnt, etc
 	".approve_comment":AjaxClickManager("put", "approving..."),
 	".unapprove_comment":AjaxClickManager("put", "un-approving..."),
	".delete_comment":AjaxClickManager("delete", "deleting..."), 
	".flash #close":CloseParentModule(true),
	"#content_associations .close":CloseParentModule(false),
	// resource association panel
	// "a.manage_associations":ResourceAssociationBehavior(),
	"a.manage_associations":AjaxClickManager("get", "Loading associations..."),
	// collapsing format panels
	".formatSection .collapse a":showHideFormat(),
	"#reset_password":function(e){
		// force reset of password fields, from when browser pre-fills them with a cookie on the edit form
		var divs = this.childElements();
		divs.each(function(el){
			var password_input = el.down("input");
			password_input.value = "";
		})
	},
	"a.view_article":ToggleImageInLinkBehavior( 'btn-viewarticle-1.gif', 'btn-viewarticle-hover-1.gif'),
	'#change_password_link:click':function(e){
		Event.stop(e);
		Element.toggle("reset_password");
	},
	'#comment_anonymous':function(e){
		var nn = $('nickname');
		var identity = $('identity');
		var anon = "Anonymous Static";
		var uname = $('nickname_field').value;
		// force checked state
		if(this.checked){
			nn.innerHTML = anon;
		}else{
			nn.innerHTML = uname;
		}
	},
	'#comment_anonymous:click': function(event) {
		var nn = $('nickname');
		var identity = $('identity');
		var anon = "Anonymous Static";
		var uname = $('nickname_field').value;
		nn.innerHTML = nn.innerHTML == anon ? uname : anon;
  }, 
	// checkbox category behavior
	"a#add_category:click":function(e){
		Event.stop(e);
		var that = this;
		var category = $("category").value
		new Ajax.Request(this.href, {
			method: "post",
			parameters :{ 
				name: category
			},
			onLoading:function(){
				Element.update(this, "adding...");
			}
		});
	} 
}); // 

String.prototype.trim = function() {
	return this.replace(/^\s+|\s+$/g,"");
}

// comments...
// This could be one behavior and then the comments form inherits w/the specific comment specific markup applied there.
document.observe('dom:loaded', function(){
  $$('form.remote').each(function(form){
    form.observe('submit', function(e){
	  var textarea = form.down("textarea");
      e.stop(); // Stop the default action, which is to submit the form the normal way
      form.request({
				onLoading:function(){
					// hm, need to work out this effect...
					// new Effect.Highlight("comment_feedback",{appear:5}); 
					var loading_image = "<img src='/images/ajax-loader-bar-blue.gif' class='icon' alt='Loading...' />";
					if(textarea.value.trim() == ""){
						Element.update("comment_feedback", loading_image);						
					}else{
						textarea.disable();
						textarea.addClassName('disabled');
						Element.update("comment_feedback", loading_image);						
					}
				},
				onLoaded:function(){
					textarea.removeClassName('disabled');
					textarea.enable();
				},
		    onComplete: function(res){
				}
      });
    });
  });
});



// DK
function toggleTree(pageId, sender)
{
	var e = $('tree_' + pageId);
	e.toggle();
	if ( e.visible() )
	{
		sender.className = 'expanded';
		sender.innerHTML = '&times;';
 	}
 	else
 	{
		sender.className = 'collapsed';
		sender.innerHTML = '+';
	}
	new Ajax.Request('/admin/pages/' + pageId + ';tree_toggle?state=' + e.visible(), { method: 'get' });
}


// DK
function limit_chars(textarea, limit, info, msg) {
    if (!msg) msg = 'Left %d:'+limit+'.'
    var text = textarea.value
    var textlength = text.length
    if (textlength > limit) {
        info.innerHTML = limit + ' characters maximum!'
        textarea.value = text.substr(0,limit)
        return false
    } else {
        info.innerHTML = msg.sub('%d', limit - textlength)
        return true
    }
}

// Lowpro Image Path Replacer
// var imageReplace = function(){
// 	if(location.href.match(/localhost/) || location.href.match(/http:\/\/[\d\.]{1,}/) ){
// 		$$("img").each(function(img){
// 			var new_src = img.src.replace(/http:\/\/[\w:\d\.]{1,}/, "http://www.radio-info.com")
// 			img.src = new_src;
// 		});
// 	}
// }
// Event.onReady(imageReplace);




// script.aculo.us builder.js v1.8.1, Thu Jan 03 22:07:12 -0500 2008

// Copyright (c) 2005-2007 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)));  
      } 
    });
  }
}


/* common JS functions */

function currentElement(element, parentElement) {
	element = $(element);
	parentElement = $(parentElement);
	
	var selecteds = parentElement.getElementsByTagName('a');
	
	for (i = 0; i < selecteds.length; i++) {
		selecteds[i].className = selecteds[i].className.replace("current", "");
	}
	
	element.addClassName('current');

	element = null;
	parentElement = null;
	selecteds = null;
	
	return true;
}

function swapListItems(element, parentElement) {
	element = $(element);
	parentElement = $(parentElement);
	
	var moduleLists = parentElement.getElementsByTagName('ul');
	
	for (i=0;i<moduleLists.length;i++) {
		var list = element.id + "_list";
		if (moduleLists[i].id == list) {
			$(moduleLists[i].id).style.display = "block";
		} else {
			$(moduleLists[i].id).style.display = "none";
		}
	}
}

function showTab(item) {
	$('authorsContainer').style.display = "none";
	$('featuresContainer').style.display = "none";
	switch (item) {
		case 'authors':
			$('authorsContainer').style.display = "block";
			break;
		case 'features':
			$('featuresContainer').style.display = "block";
			break;
		default:
			break;
	}
}



// 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) + 0.5;
    },
    reverse: function(pos) {
      return 1-pos;
    },
    flicker: function(pos) {
      var pos = ((-Math.cos(pos*Math.PI)/4) + 0.75) + Math.random()/4;
      return pos > 1 ? 1 : pos;
    },
    wobble: function(pos) {
      return (-Math.cos(pos*Math.PI*(9*pos))/2) + 0.5;
    },
    pulse: function(pos, pulses) { 
      pulses = pulses || 5; 
      return (
        ((pos % (1/pulses)) * pulses).round() == 0 ? 
              ((pos * pulses * 2) - (pos * pulses * 2).floor()) : 
          1 - ((pos * pulses * 2) - (pos * pulses * 2).floor())
        );
    },
    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;
    
    eval('this.render = function(pos){ '+
      'if (this.state=="idle"){this.state="running";'+
      codeForEvent(this.options,'beforeSetup')+
      (this.setup ? 'this.setup();':'')+ 
      codeForEvent(this.options,'afterSetup')+
      '};if (this.state=="running"){'+
      'pos=this.options.transition(pos)*'+this.fromToDelta+'+'+this.options.from+';'+
      'this.position=pos;'+
      codeForEvent(this.options,'beforeUpdate')+
      (this.update ? 'this.update(pos);':'')+
      codeForEvent(this.options,'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(),
    max = (window.height || document.body.scrollHeight) - document.viewport.getHeight();  

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

  return new Effect.Tween(null,
    scrollOffsets.top,
    elementOffsets[1] > max ? max : 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] || { };
  var oldOpacity = element.getInlineOpacity();
  var transition = options.transition || Effect.Transitions.sinoidal;
  var reverser   = function(pos){ return transition(1-Effect.Transitions.pulse(pos, options.pulses)) };
  reverser.bind(transition);
  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(hash, property) {
      hash.set(property, css[property]);
      return hash;
    });
    if (!styles.opacity) styles.set('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);


// Copyright (c) 2005-2008 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us)
//           (c) 2005-2007 Ivan Krstic (http://blogs.law.harvard.edu/ivan)
//           (c) 2005-2007 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;
  },
  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));
  }
});


/*
*
* Copyright (c) 2007 Andrew Tetlaw
* 
* 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.
* * 
*
*
* FastInit
* http://tetlaw.id.au/view/javascript/fastinit
* Andrew Tetlaw
* Version 1.3 (2007-01-09)
* Based on:
* http://dean.edwards.name/weblog/2006/03/faster
* http://dean.edwards.name/weblog/2006/06/again/
* Help from:
* http://www.cherny.com/webdev/26/domloaded-object-literal-updated
* 
*/
var FastInit = {
	onload : function() {
		if (FastInit.done) { return; }
		FastInit.done = true;
		for(var x = 0, al = FastInit.f.length; x < al; x++) {
			FastInit.f[x]();
		}
	},
	addOnLoad : function() {
		var a = arguments;
		for(var x = 0, al = a.length; x < al; x++) {
			if(typeof a[x] === 'function') { FastInit.f.push(a[x]); }
		}
	},
	listen : function() {
		if (/WebKit|khtml/i.test(navigator.userAgent)) {
			FastInit.timer = setInterval(function() {
				if (/loaded|complete/.test(document.readyState)) {
					clearInterval(FastInit.timer);
					delete FastInit.timer;
					FastInit.onload();
				}}, 10);
		} else if (document.addEventListener) {
			document.addEventListener('DOMContentLoaded', FastInit.onload, false);
		} else if(!FastInit.iew32) {
			Event.observe(window, 'load', FastInit.onload);
		}
	},
	f:[],done:false,timer:null,iew32:false
};
/*@cc_on @*/
/*@if (@_win32)
FastInit.iew32 = true;
document.write('<script id="__ie_onload" defer src="' + ((location.protocol == 'https:') ? '//0' : 'javascript:void(0)') + '"><\/script>');
document.getElementById('__ie_onload').onreadystatechange = function(){if (this.readyState == 'complete') { FastInit.onload(); }};
/*@end @*/
FastInit.listen();


/*-------------------------------------------------------------------------
 *    FieldHints version 1.1
 *    http://pauldowman.com/projects/fieldhints
 *
 *    Copyright 2007 Paul Dowman, http://pauldowman.com/
 *
 *    FieldHints is free software; you can redistribute it and/or modify
 *    it under the terms of the GNU General Public License as published by
 *    the Free Software Foundation; either version 2 of the License, or
 *    (at your option) any later version.
 *
 *    FieldHints is distributed in the hope that it will be useful,
 *    but WITHOUT ANY WARRANTY; without even the implied warranty of
 *    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *    GNU General Public License for more details.
 *
 *    You should have received a copy of the GNU General Public License
 *    along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *    
 *--------------------------------------------------------------------------
 *  
 *  This script requires the Prototype JavaScript library:
 *  http://prototypejs.org
 *
 *--------------------------------------------------------------------------*/


var FieldHints = {
    
    fieldWithHintClass: 'fieldWithHint',
    labelClass: 'hintText',

    initialize: function() {
        var labels = $$('label.hintText');
        var f = FieldHints.initializeField.bind(FieldHints);
        labels.each(f);
    },
    
    // Registers a blur handler and a focus handler for the field, and adds a
    // submit handler to a chain of submit handlers for the form.
    initializeField: function(label) {
        var fieldId = label.htmlFor;
        if (!fieldId) return;

        var field = $(fieldId);
        if (!field) return;
            
        if (!field.fieldHintsInitialized) {
            field.fieldHintsInitialized = true;
            var hint = label.innerHTML.strip();
            var form = field.form;
            
            this.addFocusHandler(field, hint);
            this.addBlurHandler(field, hint);
            
            var oldSubmitHandler;
            if (form.onsubmit) oldSubmitHandler = form.onsubmit.bind(form);
            field.form.onsubmit = this.hintSubmitHandler(hint, field, oldSubmitHandler);
            
            this.doBlur(field, hint);
        }
    },
    
    addFocusHandler: function(field, hint) {
      field.observe('focus', function(ev) {
        var el = Event.element(ev);
        if (el.value.strip() == hint) {
          el.value = '';
        }
        el.removeClassName(FieldHints.fieldWithHintClass);
      });
    },
    
    addBlurHandler: function(field, hint) {
      var obj = this;
      field.observe('blur', function(ev) {
        var el = Event.element(ev);
        obj.doBlur(el, hint)
      });
    },
    
    doBlur: function(el, hint) {
      if (el.value == '') el.value = hint;
      if (el.value == hint) el.addClassName(FieldHints.fieldWithHintClass);
    },
    
    // If the field never received focus then it will still have the hint text
    // in it. In that case it should be empty on submit, instead of submitting
    // the hint text, so register a submit handler for the form. There may
    // already be a submit handler on the form, so we need to keep a reference
    // to it and call it at the end.
    hintSubmitHandler: function(hint, field, oldSubmitHandler) {
        return function() {
            if (field.value == hint) {
                field.value = ''
                $(this).removeClassName(FieldHints.fieldWithHintClass);
            }
            if (oldSubmitHandler) {
                try {
                    var retval = oldSubmitHandler();
                } catch (error) {
                    return false;
                }
                return retval;
            } else {
                return true;
            }
        }
    }
    
}



function followThroughSearch(text, li) {
	var rule = '#' + li.id + ' a';
	var link = $$(rule)[0].href;
	location.href = link;
}

function setItem(text, li) {
	var itemChanged = true;
	var item = $(li.readAttribute('class').gsub(' selected', ''));
	item.setAttribute('disabled', 'disabled');
	item.blur();
	if (Prototype.Browser.Gecko) {
		eval(item.readAttribute('onblur'));
	}
}

/********************************
** linegraph.js
**
** line grapher handler
** Design Kitchen
** @author: Matthew Story
*********************************/

//depends on processing.js -- http://ejohn.org/blog/processingjs/
function LineGraph(element, xHashes, yHashes, graphs) {
	this.element = (element.constructor == String) ? document.getElementById(element):element;
	this.xHashes = xHashes;
	this.yHashes = yHashes;
	this.graphs = graphs;
	this.graphs.sort(this.sort);
	var options = this.extend({
		hashColor: new LineGraph.Color(0,0,0),
		minorHashColor: new LineGraph.Color(0,0,0),
		xHashTextColor: new LineGraph.Color(0,0,0),
		yHashTextColor: new LineGraph.Color(0,0,0),
		xMinorHashTextColor: new LineGraph.Color(0,0,0),
		yMinorHashTextColor: new LineGraph.Color(0,0,0),
		hashWeight: 1,
		minorHashWeight: 1,
		xHashHeight: 10,
		minorXHashHeight: 5,
		yHashWidth: 10,
		minorYHashWidth: 5,
		//one of: left, right, center
		horizXHashLabelPos: 'center',
		minorHorizXHashLabelPos: 'center',
		horizYHashLabelPos: 'left',
		minorHorizYHashLabelPos: 'left',
		//one of: top, bottom, center
		vertXHashLabelPos: 'bottom',
		minorVertXHashLabelPos: 'bottom',
		vertYHashLabelPos: 'center',
		minorVertYHashLabelPos: 'center',
		hashLabelPadding: 2,
		minorHashLabelPadding: 2,
		backgroundColor: new LineGraph.Color(255, 255, 255),
		//between 0 and 100, a percentage of similarity
		minColorThreshold: 0,
		maxColorThreshold: 25,
		fontFamily: 'Arial',
		minorFontFamily: 'Arial',
		fontSize: 12,
		fontWeight: false,
		minorFontSize: 8,
		minorFontWeight: false,
		padding: 4,
		xAxis: true,
		yAxis: true,
		origin: new LineGraph.Point(0,0),
		legend: true,
		legendLocation: false,
		onInit: function() {}
	}, arguments[4] || {});
	this.hashColor = options.hashColor;
	this.minorHashColor = options.minorHashColor;
	this.xHashTextColor = options.xHashTextColor;
	this.yHashTextColor = options.yHashTextColor;
	this.xMinorHashTextColor = options.xMinorHashTextColor;
	this.yMinorHashTextColor = options.yMinorHashTextColor;
	this.hashWeight = options.hashWeight;
	this.minorHashWeight = options.minorHashWeight;
	this.xHashHeight = options.xHashHeight;
	this.minorXHashHeight = options.minorXHashHeight;
	this.yHashWidth = options.yHashWidth;
	this.minorYHashWidth = options.minorYHashWidth;
	this.horizXHashLabelPos = options.horizXHashLabelPos;
	this.minorHorizXHashLabelPos = options.minorHorizXHashLabelPos;
	this.horizYHashLabelPos = options.horizYHashLabelPos;
	this.minorHorizYHashLabelPos = options.minorHorizYHashLabelPos;
	this.vertXHashLabelPos = options.vertXHashLabelPos;
	this.minorVertXHashLabelPos = options.minorVertXHashLabelPos;
	this.vertYHashLabelPos = options.vertYHashLabelPos;
	this.minorVertYHashLabelPos = options.minorVertYHashLabelPos;
	this.hashLabelPadding = options.hashLabelPadding;
	this.minorHashLabelPadding = options.minorHashLabelPadding;
	this.backgroundColor = options.backgroundColor;
	this.colorIncrement = options.colorIncrement;
	this.fontFamily = options.fontFamily;
	this.minorFontFamily = options.minorFontFamily;
	this.fontWeight = options.fontWeight;
	this.fontSize = options.fontSize;
	this.minorFontSize = options.minorFontSize;
	this.minorFontWeight = options.minorFontWeight;
	this.padding = options.padding;
	this.xAxis = options.xAxis;
	this.yAxis = options.yAxis;
	this.origin = options.origin;
	this.legend = options.legend;
	this.legendLocation = options.legendLocation;
  	this.onInit = options.onInit;
	this.maxColorThreshold = options.maxColorThreshold;
	this.minColorThreshold = options.minColorThreshold;

	this.relativizeParent();
    this.uniqueifyLineColors();
    this.initializeProcessing();
    this.update();
	this.onInit();
}
LineGraph.prototype = {
	destroy: function() {
		this.element = null;
		this.xHashes = null;
		this.yHashes = null;
		this.lines = null;
		this.hashColor = null;
		this.xHashHeight = null;
		this.yHashWidth = null;
		this.backgroundColor = null;
		this.colorIncrement = null;
		this.fontFamily = null;
		this.fontSize = null;
	},
	
	initializeProcessing: function() {
		//generate processing object
		this.processing = Processing(this.element);
		this.processing.draw = this.makeProcessingDraw(this, this.draw);

		//set properties of the canvas
		this.processing.size(this.element.clientWidth, this.element.clientHeight);
	},
	
	render: function() {
		var options = this.makeChangeOptions(arguments[0]);
		if (options.render) {
			this.update();
		}
	},

	update: function() {
		this.processing.redraw();
	},
	
	draw: function() {
        this.tare();
		this.processing.background(this.backgroundColor.red, this.backgroundColor.green, this.backgroundColor.blue);
		this.drawAxes();
		for (var i=0;i<this.xHashes.length;i++) {
			this.drawHash(this.xHashes[i], 'x');
		}
		for (var i=0;i<this.yHashes.length;i++) {
			this.drawHash(this.yHashes[i], 'y');
		}
		for (var i=0;i<this.graphs.length;i++) {
			if (this.graphs[i].show) {
				if (this.graphs[i].type == 'line') {
    	            this.drawLine(this.graphs[i]);
        	    } else if (this.graphs[i].type == 'scatter') {
            	    this.drawScatter(this.graphs[i]);
	            } else if (this.graphs[i].type == 'bar') {
					this.drawBar(this.graphs[i]);
				} else if (this.graphs[i].type == 'pie') {
					this.drawPie(this.graphs[i]);
				}
			}
        }
	},
	
	//a simple ripoff of prototype bind
	makeProcessingDraw: function(obj, func) {
		return function() {
			func.apply(obj, []);
		};
	},

	drawAxes: function() {
		//set the color and weight of the axis
		this.processing.stroke(this.hashColor.red, this.hashColor.green, this.hashColor.blue);
        this.processing.strokeWeight(this.hashWeight);
    	
		//draw the axis if they are enabled
		if (this.yAxis) {    
			this.processing.line(this.hashWeight+this.padding, this.invertY(this.hashWeight+this.padding), this.hashWeight+this.padding, this.invertY(this.hashWeight*2+this.padding+this.getCanvasHeight()));
		}
		if (this.xAxis) {
	        this.processing.line(this.hashWeight+this.padding, this.invertY(this.hashWeight+this.padding), this.hashWeight*2+this.padding+this.getCanvasWidth(), this.invertY(this.hashWeight+this.padding));
		}
	},

	drawHash: function(hash, axis) {
		//set the color and width of the hash
		if (hash.type == 'minor') {
			this.processing.stroke(this.minorHashColor.red, this.minorHashColor.green, this.minorHashColor.blue);
			this.processing.strokeWeight(this.minorHashWeight);
		} else {
			this.processing.stroke(this.hashColor.red, this.hashColor.green, this.hashColor.blue);
			this.processing.strokeWeight(this.hashWeight);
		}
		
		//draw the hash
		var hashOrigin = (axis.toLowerCase() == 'y') ? this.getRealPoint(new LineGraph.Point(0, hash.point), axis):this.getRealPoint(new LineGraph.Point(hash.point, 0), axis);
		if (hashOrigin.x-this.xZero != 0 || hashOrigin.y-this.yZero != 0) {
			if (axis.toLowerCase() == 'y') {
				this.processing.line(
					hashOrigin.x, this.invertY(hashOrigin.y),
					hashOrigin.x+((hash.type == 'minor') ? this.minorYHashWidth:this.yHashWidth), this.invertY(hashOrigin.y)
				);
			} else {
                this.processing.line(
                    hashOrigin.x, this.invertY(hashOrigin.y),
                    hashOrigin.x, this.invertY(hashOrigin.y+((hash.type == 'minor') ? this.minorXHashHeight:this.xHashHeight))
                );
			}
		}
		if (!hash.labelElement) {
			this.generateHashLabel(hash, hashOrigin, axis);
		}
		this.setHashLabelInner(hash);
		this.vAlignLabel(hash, hashOrigin, axis);
		this.hAlignLabel(hash, hashOrigin, axis); 
	},
	
	//generate and position the label for the hash element
	generateHashLabel: function(hash, hashLocation, axis) {
		hash.labelElement = document.createElement('span');
		hash.labelElement.style.position = 'absolute'; 
		if (hash.type == 'minor') {
			var textColor = (axis.toLowerCase() == 'x') ? this.xMinorHashTextColor:this.yMinorHashTextColor;
			hash.labelElement.style.color = 'rgb('+textColor.red+','+textColor.green+','+textColor.blue+')';
			hash.labelElement.style.fontFamily = this.minorFontFamily;
			hash.labelElement.style.fontSize = this.minorFontSize+'px';
			hash.labelElement.style.fontWeight = this.minorFontWeight || '';
			hash.labelElement.style.padding = this.minorHashLabelPadding+'px';
			hash.labelElement.style.height = (this.minorFontSize+this.minorHashLabelPadding*2)+'px';
		} else {
			var textColor = (axis.toLowerCase() == 'x') ? this.xHashTextColor:this.yHashTextColor;
			hash.labelElement.style.color = 'rgb('+textColor.red+','+textColor.green+','+textColor.blue+')';
			hash.labelElement.style.fontFamily = this.fontFamily;
			hash.labelElement.style.fontSize = this.fontSize+'px';
			hash.labelElement.style.fontWeight = this.fontWeight || '';
			hash.labelElement.style.padding = this.hashLabelPadding+'px';
			hash.labelElement.style.height = (this.fontSize+this.hashLabelPadding*2)+'px';
		}
		this.element.parentNode.appendChild(hash.labelElement);
	},

	setHashLabelInner: function(hash) {
		hash.labelElement.innerHTML = '';
		hash.labelElement.appendChild(document.createTextNode(hash.label));
	},
	
	//position the hash labels based on the vertX/vertYHashLabelPos properties
	vAlignLabel: function(hash, hashLocation, axis) {
		var align = (axis.toLowerCase() == 'x') ? ((hash.type == 'minor') ? this.minorVertXHashLabelPos:this.vertXHashLabelPos):((hash.type == 'minor') ? this.minorVertYHashLabelPos:this.vertYHashLabelPos);
		if (align.toLowerCase() == 'bottom') {
			hash.labelElement.style.top = (this.element.offsetTop+this.invertY(hashLocation.y)+this.hashWeight)+'px';
		} else if (align.toLowerCase() == 'top') {
			hash.labelElement.style.top = (this.element.offsetTop+this.invertY(hashLocation.y)-hash.labelElement.offsetHeight)+'px';
		} else if (align.toLowerCase() == 'center') {
			hash.labelElement.style.top = (this.element.offsetTop+this.invertY(hashLocation.y)-hash.labelElement.offsetHeight+(Math.floor(Math.abs(hash.labelElement.offsetHeight-this.hashWeight)/2)))+'px';
		}
	},

	//position the hash labels based on the horizX/horizYHashLabelPos properties
	hAlignLabel: function(hash, hashLocation, axis) {
		var align = (axis.toLowerCase() == 'x') ? ((hash.type == 'minor') ? this.minorHorizXHashLabelPos:this.horizXHashLabelPos):((hash.type == 'minor') ? this.minorHorizYHashLabelPos:this.horizYHashLabelPos);
		if (align.toLowerCase() == 'right') {
			hash.labelElement.style.left = (this.element.offsetLeft+hashLocation.x+this.hashWeight)+'px';
		} else if (align.toLowerCase() == 'left') {
			hash.labelElement.style.left = (this.element.offsetLeft+hashLocation.x-hash.labelElement.offsetWidth)+'px';
		} else if (align.toLowerCase() == 'center') {
			hash.labelElement.style.left = (this.element.offsetLeft+hashLocation.x-hash.labelElement.offsetWidth+(Math.floor(Math.abs(hash.labelElement.offsetWidth-this.hashWeight)/2)))+'px';
		}
	},
	
	addGraph: function(graph) {
		var options = this.makeChangeOptions(arguments[1]);
		if (!graph.color) {
			this.randomizeGraphColor(graph);
		}
		this.graphs.push(graph);
		this.graphs.sort(this.sort);
		this.render(options);
	},
	
	removeGraph: function(name) {
		var graphs = [];
		for (var i=0;i<this.graphs.length;i++) {
			if (this.graphs[i].name != name) {
				graphs.push(this.graphs[i]);
			}
		}
		this.graphs = graphs;
		this.render(options);
	},

	addHash: function(hash, axis) {
		var options = this.makeChangeOptions(arguments[2]);
	},

	removeHash: function(hash, axis) {
		var options = this.makeChangeOptions(arguments[2]);
	},

	changeHashColor: function(color) {
		var options = this.makeChangeOptions(arguments[1]);
		this.hashColor = color;
		this.render(options);
	},
	
	changeHashWeight: function(weight) {
		var options = this.makeChangeOptions(arguments[1]);
		this.hashWeight = weight;
		this.tare();
		this.render(options);
	},
	
	changePadding: function(padding) {
		var options = this.makeChangeOptions(arguments[1]);
		this.padding = padding;
		this.tare();
		this.render(options);
	},
	
	changeBackgroundColor: function(color) {
		var options = this.makeChangeOptions(arguments[1]);
		this.backgroundColor = color;
		this.render(options);
	},
	
	toggleGraph: function(name) {
		var options = this.makeChangeOptions(arguments[1]);
		try {
			if (this.getGraphByName(name).show) {
				this.hideGraph(name, options);
			} else {
				this.showGraph(name, options);
			}
		} catch(e) {
			return false;
		}
	},

	showGraph: function(name) {
		var options = this.makeChangeOptions(arguments[1]);
		try {
			this.getGraphByName(name).showGraph();
			this.render(options);
		} catch(e) {
			return false;
		}
	},

	hideGraph: function(name) {
		var options = this.makeChangeOptions(arguments[1]);
		try {
			this.getGraphByName(name).hideGraph();
			this.render(options);
		} catch(e) {
			return false;
		}
	},
	
	changeGraphDepth: function(name, depth) {
		var options = this.makeChangeOptions(arguments[1]);
		try {
			var graph = this.getGraphByName(name);
			graph.depth = depth;
			this.graphs.sort(this.sort);
			options.render = (graph.show) ? options.render:false;
			this.render(options);
		} catch(e) {
			return true;
		}
	},

	toggleGraphFill: function(name) {
		var options = this.makeChangeOptions(arguments[1]);
		try {
			if (this.getGraphByName(name).fill) {
				this.unfillGraph(name, options);
			} else {
				this.fillGraph(name, options);
			}
		} catch(e) {
			return false;
		}
	},

	fillGraph: function(name) {
		var options = this.makeChangeOptions(arguments[1]);
		try {
			var graph = this.getGraphByName(name);
			graph.fillGraph();
			options.render = (graph.show) ? options.render:false;
			this.render(options);
		} catch(e) {
			return false;
		}
	},

	unfillGraph: function(name) {
		var options = this.makeChangeOptions(arguments[1]);
		try {
			var graph = this.getGraphByName(name);
			graph.unfillGraph();
			options.render = (graph.show) ? options.render:false;
			this.render(options);
		} catch(e) {
			return false;
		}
	},
	
	toggleInvertGraphFill: function(name) {
		var options = this.makeChangeOptions(arguments[1]);
		try {
			if (this.getGraphByName(name).invertFill) {
				this.uninvertGraphFill(name);
			} else {
				this.invertGraphFill(name);
			}
		} catch(e) {
			return false;
		}
	},

	invertGraphFill: function(name) {
		var options = this.makeChangeOptions(arguments[1]);
		try {
			var graph = this.getGraphByName(name);
			graph.invertFill = true;
			options.render = (graph.show && (graph.fill || graph.type == 'bar')) ? options.render:false;
			this.render(options);
		} catch(e) {
			return false;
		}
	},

	uninvertGraphFill: function(name) {
		var options = this.makeChangeOptions(arguments[1]);
		try {
			var graph = this.getGraphByName(name);
			graph.invertFill = false;
			options.render = (graph.show && graph.fill || graph.type == 'bar') ? options.render:false;
			this.render(options);
		} catch(e) {
			return false;
		}
	},
	
	changeFillColor: function(name, color) {
		var options = this.makeChangeOptions(arguments[2]);
		try {
			var graph = this.getGraphByName(name)
			graph.fillColor = color;
			options.render = (graph.show && graph.fill) ? options.render:false;
			this.render(options);
		} catch(e) {
			return false;
		}
	},
	
	changeOrientation: function(name, orientation) {
		var options = this.makeChangeOptions(arguments[2]);
		try {
			var graph = this.getGraphByName(name);
			graph.changeOrientation(orientation);
			options.render = (graph.show) ? options.render:false;
			this.render(options);
		} catch(e) {
			return false;
		}

	},

	toggleOrientation: function(name) {
		var options = this.makeChangeOptions(arguments[2]);
		try {
			var graph = this.getGraphByName(name);
			var orientation = (graph.orientation == 'y') ? 'x':'y';
			graph.changeOrientation(orientation);
			options.render = (graph.show) ? options.render:false;
			this.render(options);
		} catch(e) {
			return false;
		}
	},

	addPointToGraph: function(name, point) {
		var options = this.makeChangeOptions(arguments[2]);
		try {
			var graph = this.getGraphByName(name);
			graph.registerPoint(point);
			options.render = (graph.show) ? options.render:false;
			this.render(options);
		} catch(e) {
			return false;
		}
	},

	removePointFromGraph: function(name, point) {
		var options = this.makeChangeOptions(arguments[2]);
		try {
			var graph = this.getGraphByName(name);
			graph.unregisterPoint(point);
			options.render = (graph.show) ? options.render:false;
			this.render(options);
		} catch(e) {
			return false;
		}
	},
	
	updateGraphDataSet: function(name, points) {
		var options = this.makeChangeOptions(arguments[2]);
		try {
			var graph = this.getGraphByName(name);
			graph.updatePoints(points);
			options.render = (graph.show) ? options.render:false;
			this.render(options);
		} catch(e) {
			return false;
		}
	},
	
	changeGraphColor: function(name, color) {
		var options = this.makeChangeOptions(arguments[2]);
		try {
			graph = this.getGraphByName(name);
			graph.color = color;
			options.render = (graph.show) ? options.render:false;
			this.render(options);
		} catch(e) {
			return false;
		}
	},
	
	changeGraphPointShape: function(name, shape) {
		var options = this.makeChangeOptions(arguments[2]);
		try {
			graph = this.getGraphByName(name);
			graph.pointShape = shape;
			options.render = (graph.show) ? options.render:false;
			this.render(options);
		} catch(e) {
			return false;
		}
	},

	changeGraphPointRadius: function(name, radius) {
		var options = this.makeChangeOptions(arguments[2]);
		try {
			graph = this.getGraphByName(name);
			graph.pointRadius = radius;
			options.render = (graph.show) ? options.render:false;
			this.render(options);
		} catch(e) {
			return false;
		}
	},
	
	changeGraphWeight: function(name, weight) {
		var options = this.makeChangeOptions(arguments[2]);
		try {
			graph = this.getGraphByName(name);
			graph.weight = weight;
			options.render = (graph.show) ? options.render:false;
			this.render(options);
		} catch(e) {
			return false;
		}
	},
	
	makeChangeOptions: function() {
		return this.extend({
			render: true
		}, arguments[0]|| {});
	},

	getGraphByName: function(name) {
		for (var i=0;i<this.graphs.length;i++) {
			if (this.graphs[i].name == name) {
				return this.graphs[i];
			}
		}
		return false;
	},

	drawLine: function(line) {
		//if there is a fill, we need to generate the fill first
		if (line.fill) {
			this.generateFill(line);
		}

		//generate the actual line
		this.processing.beginShape();
		this.processing.noFill();
		this.processing.stroke(line.color.red, line.color.green, line.color.blue);
		this.processing.strokeWeight(line.weight);
		this.processing.smooth();
		for (var i=0;i<line.points.length;i++) {
			this.drawVertex(line.points[i]);
		}
		this.processing.endShape();

		//we have to draw it after all points have been drawn to avoid any color overlapping wierdness
		if (line.showPoints) {
			this.drawPoints(line);
		}
	},
	
	drawScatter: function(scatter) {
		this.processing.noStroke();
		this.processing.fill(scatter.color.red, scatter.color.green, scatter.color.blue);
		for (var i=0;i<scatter.points.length;i++) {
			var realPoint = this.getRealPoint(scatter.points[i]);
			this.drawPoint(realPoint.x, realPoint.y, scatter.weight, scatter.pointShape, scatter.color);
		}
	},
	
	drawBar: function(bar) {
		if (!bar.outlineColor) {
			this.processing.noStroke();
		} else {
			this.processing.stroke(bar.outlineColor.red, bar.outlineColor.green, bar.outlineColor.blue);
			this.processing.strokeWeight(bar.outlineWeight);
		}
		this.processing.fill(bar.color.red, bar.color.green, bar.color.blue);
		var basePoint = (bar.invertFill) ? (bar.orientation == 'y') ? this.maxHash(this.yHashes):this.maxHash(this.xHashes):0;
		for (var i=0;i<bar.points.length;i++) {
			if (bar.orientation == 'y') {
				this.processing.quad(
					this.getRealPoint(new LineGraph.Point(basePoint, 0)).x,
					this.invertY(this.getRealPoint(new LineGraph.Point(0, bar.points[i].y-((bar.align == 'before') ? bar.weight:(bar.align == 'after') ? 0:bar.weight/2))).y),
					this.getRealPoint(bar.points[i]).x,
					this.invertY(this.getRealPoint(new LineGraph.Point(0, bar.points[i].y-((bar.align == 'before') ? bar.weight:(bar.align == 'after') ? 0:bar.weight/2))).y),
					this.getRealPoint(bar.points[i]).x,
					this.invertY(this.getRealPoint(new LineGraph.Point(0, bar.points[i].y+((bar.align == 'before') ? 0:(bar.align == 'after') ? bar.weight:bar.weight/2))).y),
                    this.getRealPoint(new LineGraph.Point(basePoint, 0)).x,
                    this.invertY(this.getRealPoint(new LineGraph.Point(0, bar.points[i].y+((bar.align == 'before') ? 0:(bar.align == 'after') ? bar.weight:bar.weight/2))).y)
					);
			} else {
				this.processing.quad(
					this.getRealPoint(new LineGraph.Point(bar.points[i].x-((bar.align == 'before') ? bar.weight:(bar.align == 'after') ? 0:bar.weight/2), 0)).x,
					this.invertY(this.getRealPoint(new LineGraph.Point(0, basePoint)).y),
					this.getRealPoint(new LineGraph.Point(bar.points[i].x-((bar.align == 'before') ? bar.weight:(bar.align == 'after') ? 0:bar.weight/2), 0)).x,
					this.invertY(this.getRealPoint(new LineGraph.Point(0, bar.points[i].y)).y),
					this.getRealPoint(new LineGraph.Point(bar.points[i].x+((bar.align == 'before') ? 0:(bar.align == 'after') ? bar.weight:bar.weight/2), 0)).x,
					this.invertY(this.getRealPoint(new LineGraph.Point(0, bar.points[i].y)).y),
					this.getRealPoint(new LineGraph.Point(bar.points[i].x+((bar.align == 'before') ? 0:(bar.align == 'after') ? bar.weight:bar.weight/2), 0)).x,
					this.invertY(this.getRealPoint(new LineGraph.Point(0, basePoint)).y)
				);
			}
		}
	},
	
	drawPie: function(pie) {
		if (!pie.outlineColor) {
			this.processing.noStroke();
		} else {
			this.processing.stroke(pie.outlineColor.red, pie.outlineColor.green, pie.outlineColor.blue);
			this.processing.strokeWeight(pie.outlineWeight);
		}
		this.processing.noFill();
		this.processing.smooth();
		
		var origin = this.getRealPoint(pie.origin);
		var radius = (pie.orientation == 'y') ? this.getRealPoint(new LineGraph.Point(0, pie.origin.y+pie.radius)).y-origin.y:this.getRealPoint(new LineGraph.Point(pie.origin.x+pie.radius, 0)).x-origin.x;
		
		var angle = 0;
		for (var i=0;i<pie.wedges.length;i++) {
			this.processing.fill(pie.wedges[i].color.red, pie.wedges[i].color.green, pie.wedges[i].color.blue);
			this.processing.arc(origin.x, this.invertY(origin.y), radius*2, radius*2, angle, angle+this.processing.radians(360*(pie.wedges[i].percent/100)));
			angle += this.processing.radians(360*(pie.wedges[i].percent/100));
		}
	},

	drawPoints: function(graph) {
		var pointColor = (graph.pointColor) ? graph.pointColor:graph.color;
		for (var i=0;i<graph.points.length;i++) {
			var point = this.getRealPoint(graph.points[i]);
			this.drawPoint(point.x, point.y, graph.pointRadius, graph.pointShape, pointColor);
		}
	},

	drawPoint: function(x, y, radius, shape, color) {
		this.processing.noStroke();
		this.processing.fill(color.red, color.green, color.blue);
		if (shape == 'circle') {
			this.processing.ellipse(x, this.invertY(y), radius*2, radius*2);
		} else if (shape == 'square') {
			this.processing.rect(x-radius, this.invertY(y+radius), radius*2, radius*2);
		} else if (shape == 'triangle') {
			var height = Math.floor(Math.sqrt(Math.pow(radius*2, 2)-Math.pow(radius, 2)));
			this.processing.triangle(x-Math.floor(radius), this.invertY(y-Math.floor(height/2)), x, this.invertY(y+height/2), x+radius, this.invertY(y-Math.floor(height/2)));
		} else if (shape == 'diamond') {
			this.processing.quad(x-Math.floor(radius/2), this.invertY(y), x, this.invertY(y+radius), x+Math.floor(radius/2), this.invertY(y), x, this.invertY(y-radius));
		}
	},
	
	drawVertex: function(point) {
		point = this.getRealPoint(point);
		this.processing.vertex(point.x, this.invertY(point.y));
	},

	generateFill: function(line) {
		var fillColor = (line.fillColor) ? line.fillColor:line.color;
		this.processing.noStroke();
		this.processing.fill(fillColor.red, fillColor.green, fillColor.blue);
		this.processing.beginShape();

		//generate the first point
		var basePoint = (line.invertFill) ? (line.orientation == 'y') ? this.maxHash(this.yHashes):this.maxHash(this.xHashes):0;
		var point = (line.orientation == 'y') ? new LineGraph.Point(basePoint, line.points[0].y):new LineGraph.Point(line.points[0].x, basePoint);
		this.drawVertex(point);
		
		//draw all the real points
		for (var i=0;i<line.points.length;i++) {
			this.drawVertex(line.points[i]);
		}
		
		//draw last point
		point = (line.orientation == 'y') ? new LineGraph.Point(basePoint, line.points[line.points.length-1].y):new LineGraph.Point(line.points[line.points.length-1].x, basePoint);
		this.drawVertex(point);
		
		this.processing.endShape();
	},

	maxHash: function(hashes) {
		var max = 0;
		for (var i=0;i<hashes.length;i++) {
			if (hashes[i].point > max) {
				max = hashes[i].point;
			}
		}

		return max;
	},

	minHash: function(hashes) {
		var min;
		for (var i=0;i<hashes.length;i++) {
			if (hashes[i].point < min || (!min && min !== 0)) {
				min = hashes[i].point;
			}
		}

		return min;
	},

	uniqueifyLineColors: function() {
		//this will eventually generate unique colors for lines in the graph, but not yet
		for (var i=0;i<this.graphs.length;i++) {
			if (!this.graphs[i].color) {
				this.randomizeGraphColor(this.graphs[i]);
			}
			//also generate unique colors for wedges
			if (this.graphs[i].type == 'pie') {
				for (var j=0;j<this.graphs[i].wedges.length;j++) {
					if (!this.graphs[i].wedges.color) {
						this.randomizeGraphColor(this.graphs[i].wedges[j]);
					}
				}
			}
		}
	},
	
	randomizeGraphColor: function(graph) {
		while(!graph.color || !this.checkUniqueColor(graph)) {
			graph.color = this.randomColor();
		}
	},

	randomColor: function() {
		return new LineGraph.Color(
			Math.floor(Math.random()*256), 
			Math.floor(Math.random()*256), 
			Math.floor(Math.random()*256)
		);
	},
	
	checkUniqueColor: function(graph) {
		//check to see if the color threshold is set
		this.colorThreshold = this.colorThreshold || this.setColorThreshold();
		//if it's too similar to the background color
		if (this.tooSimilar(graph.color, this.backgroundColor)) {
			return false;
		}

		//check to see if it's too close to another defined color
		for (var i=0;i<this.graphs.length;i++) {
			if (this.graphs[i] != graph && this.tooSimilar(graph.color, this.graphs[i].color)) {
				return false;
			}
			if (this.graphs[i].type == 'Pie') {
				for (j=0;j<this.graphs[i].wedges.length;j++) {
					if (graph != this.graphs[i].wedges[j] && this.tooSimilar(graph.color, this.graphs[i].wedges[j].color)) {
						return false;
					}
				}
			}
		}

		return true;
	},
	
	setColorThreshold: function() {
		var numUnique = this.graphs.length+1; //plus one to account for background color
		for (var i=0;i<this.graphs.length;i++) {
			if (this.graphs[i].type == 'Pie') {
				numUnique += this.graphs[i].wedges.length-1 //minus one to account for the Pie chart already in the numUnique
			}
		}
		var threshold = 50/Math.pow(numUnique, 1/3);
		return (threshold > this.minColorThreshold && threshold < this.maxColorThreshold) ? threshold:(threshold <= this.minColorThreshold) ? this.minColorThreshold:this.maxColorThreshold;
	},

	tooSimilar: function(color1, color2) {
		if (Math.sqrt(Math.pow(color1.red - color2.red, 2) + Math.pow(color1.blue - color2.blue, 2) + Math.pow(color1.green - color2.green,2)) <= (this.colorThreshold/100)*Math.sqrt(3*Math.pow(256,2))) {
			return true;
		}
		
		return false;
	},

	getRealPoint: function(point, hash) {
		//need to gracefully handle a 0 width || 0 height graph
		if (this.maxHash(this.xHashes) == this.minHash(this.xHashes)) {
			var x=((hash && hash.toLowerCase() == 'y') ? 0:this.getCanvasWidth()/2)+this.xZero;
		}
		if (this.maxHash(this.yHashes) == this.minHash(this.yHashes)) {
			var y=((hash && hash.toLowerCase() == 'x') ? 0:this.getCanvasHeight()/2)+this.yZero;
		}
		return new LineGraph.Point(
			x || Math.floor((this.getCanvasWidth()/(this.maxHash(this.xHashes)-this.minHash(this.xHashes)))*Math.max((point.x-this.minHash(this.xHashes)),0))+this.xZero,
			y || Math.floor((this.getCanvasHeight()/(this.maxHash(this.yHashes)-this.minHash(this.yHashes)))*Math.max((point.y-this.minHash(this.yHashes)),0))+this.yZero
		);
	},

	invertY: function(y) {
		return this.element.clientHeight-y;
	},

	//this will also account for text eventually
	tare: function() {
		this.xZero = (this.yAxis) ? (this.hashWeight*2)+this.padding:this.padding;
		this.yZero = (this.xAxis) ? (this.hashWeight*2)+this.padding:this.padding;
	},

	getCanvasWidth: function() {
		return this.element.clientWidth-this.xZero-this.padding;
	},

	getCanvasHeight: function() {
		return this.element.clientHeight-this.yZero-this.padding;
	},

	relativizeParent: function() {
		if (this.element.parentNode != document.body) {
			this.element.parentNode.style.position = 'relative';
		}
	},

	sort: function(a,b) {
		return b.depth-a.depth;
	},
	
	//this is pretty much a ripoff of prototype's Object.Extend
	extend: function(obj1, obj2) {
		for (var name in obj2) {
			obj1[name] = obj2[name];
		}
		return obj1;
	}
};

//a full line
LineGraph.Graph = {
	initialize: function(name, points) {
		this.name = name;
		this.points = [];
		for (var i=0;i<points.length;i++) {
			if (points[i].checkReal()) {
				this.points.push(points[i]);
			}
		}
		var options = LineGraph.prototype.extend({
			color: false,
			weight: 2,
			showPoints: false,
			//one of: circle, square, triangle, possibly diamond eventually
			pointShape: 'circle',
			pointRadius: 4,
			pointColor: false,
			regression: false,
			show: true,
			//this is like zIndex in reverse . . . higher values are further back in the graph
			depth: 0,
			fill: false,
			fillColor: false,
			invertFill: false,
			orientation: 'x'
		}, arguments[2] || {});
		this.color = options.color;
		this.weight = options.weight;
		this.showPoints = options.showPoints;
		this.pointShape = options.pointShape;
		this.pointRadius = options.pointRadius;
		this.pointColor = options.pointColor;
		this.show = options.show;
		this.depth = options.depth;
		this.fill = options.fill;
		this.fillColor = options.fillColor;
		this.invertFill = options.invertFill;
		this.orientation = options.orientation.toLowerCase();
		this.sortPoints();
	},
	
	updatePoints: function(points) {
		this.points = points;
		this.sortPoints();
	},

	showGraph: function() {
		this.show = true;
	},

	hideGraph: function() {
		this.show = false;
	},

	fillGraph: function() {
		this.fill = true;
	},

	unfillGraph: function() {
		this.fill = false;
	},

	destroy: function() {
		this.points = null;
		this.color = null;
	},

	registerPoint: function(point) {
		this.points.push(point);
		this.sortPoints();
	},

	unregisterPoint: function(point) {
		var points = [];
		for (var i=0;i<this.points.length;i++) {
			if (this.points[i].x != point.x || this.points[i].y != point.y) {
				points.push(this.points[i]);
			}
		}
		this.points = points;
	},
	
	changeOrientation: function(orientation) {
		this.orientation = orientation.toLowerCase();
		this.sortPoints();
	},

	sortPoints: function() {
		if (this.orientation == 'y') {
			this.points.sort(this.sortY);
		} else {
			this.points.sort(this.sortX);
		}
	},

	sortX: function(a,b) {
		return a.x-b.x;
	},

	sortY: function(a,b) {
		return a.y-b.y;
	}
};

//line data type
LineGraph.Line = function() {
	this.initialize.apply(this, arguments);
	this.type = 'line';
}
LineGraph.Line.prototype = LineGraph.Graph;

//scatter plot data type
LineGraph.Scatter = function() {
	this.initialize.apply(this, arguments);
	this.type = 'scatter';
	this.linearRegression = function() {};
}
LineGraph.Scatter.prototype = LineGraph.Graph;

//bar graph data type
LineGraph.Bar = function() {
	this.initialize.apply(this, arguments);
	this.type = 'bar';
	//unlike the other graphs, this weight is not pixels, this weight is in the units that the points are in
	var options = LineGraph.prototype.extend({
		//one of: before, center, after
		align: 'center',
		outlineColor: false,
		//this weight is in pixels
		outlineWeight: 1,
		//this weight is in the same units as x or y (depending on orientation)
		weight: 5
	}, arguments[2] || {});
	this.align = options.align.toLowerCase();
	this.outlineColor = options.outlineColor;
	this.outlineWeight = options.outlineWeight;
	this.weight = options.weight;
}
LineGraph.Bar.prototype = LineGraph.Graph;

//pie graph data type
LineGraph.Pie = function(name, origin, radius, wedges) {
	this.initialize.apply(this, [name, [], arguments[4]]);
	this.type = 'pie'
	this.origin = origin;
	this.radius = radius;
	this.wedges = wedges;
	
	//radius is in the same units as x or y (depending on orientation)
	this.radius = radius;
	var options = LineGraph.prototype.extend({
		//line in between regions of pie graph and outside
		outlineWeight: 1,
		outlineColor: false
	}, arguments[4] || {});
	this.outlineWeight = options.outlineWeight;
	this.outlineColor = options.outlineColor;
}
LineGraph.Pie.prototype = LineGraph.Graph;

LineGraph.Pie.Wedge = function(name, percent) {
	this.name = name;
	this.percent = percent;
	var options = LineGraph.prototype.extend({
		color: false
	}, arguments[1] || {});
	this.color = options.color;
}

//point in a line
LineGraph.Point = function(x,y) {
	this.x = x;
	this.y = y;
};

LineGraph.Point.prototype = {
	destroy: function() {
		this.x = null;
		this.y = null;
	},

	checkReal: function() {
		if ((this.x !== 0 && (!this.x || this.x == '')) || (this.y !== 0 && (!this.y || this.y == ''))) {
			return false;
		}
		return true;
	}
};

//hash mark object
LineGraph.Hash = function(point, label) {
	var options = LineGraph.prototype.extend({type: 'major'}, (arguments[2] || {}));
	this.point = point;
	this.label = label;
	//one of 'major' || 'minor'
	this.type = options.type;
};

LineGraph.Hash.prototype = {
	labelElement: false,

	destroy: function() {
		this.point = null;
		this.label = null;
	}
};

//color class
LineGraph.Color = function(red, green, blue) {
	this.red = red;
	this.green = green;
	this.blue = blue;
};

LineGraph.Color.prototype = {
	destroy: function() {
		this.red = null;
		this.green = null;
		this.blue = null;
	}
};


/********************************
** marquee.js
**
** marqueeing text
** Design Kitchen
** @author: Matthew Story
*********************************/

var Marquee = Class.create();
Marquee.prototype = {
	element: false,
	wrapper: false,
	
	//speed is in pixels per second
	speed: 20,
	
	//duration is in ms
	duration: false,
	effect: Effect.Transitions.linear,
	delay: 2000,
	stopAtLeft: true,
	loop: false,
	returnOnFinish: true,
	returnDelay: 0,
	singleLine: false,
	autoStart: true,
	inProgress: false,
	currentEffect: false,
	currentTimeout: false,
	onComplete: function() {},
	afterEffect: function() {},

	initialize: function(element, wrapper) {
		this.element = $(element);
		this.wrapper = $(wrapper);
		
		var options = (arguments[2]) ? arguments[2]:{};
		var defaults = {
			//in px/s
			speed: 20,
			effect: Effect.Transitions.linear,
			delay: 2000,
			stopAtLeft: true,
			loop: false,
			returnOnFinish: true,
			returnDelay: 0,
			singleLine: false,
			autoStart: true,
			onComplete: function() {},
			afterEffect: function() {},
			manualReturn: false
		};
		Object.extend(defaults, options);
		this.effect = defaults.effect;
		this.delay = defaults.delay;
		this.stopAtLeft = defaults.stopAtLeft;
		this.loop = defaults.loop;
		this.returnOnFinish = defaults.returnOnFinish;
		this.returnDelay = defaults.returnDelay;
		this.autoStart = defaults.autoStart;
		this.singleLine = defaults.singleLine;
		this.onComplete = defaults.onComplete;
		this.afterEffect = defaults.afterEffect;
		this.manualReturn = defaults.manualReturn;

		this.clipWrapper();
		this.relativizeElement();
		
		this.changeSpeed(defaults.speed);

		if (this.autoStart) {
			this.start();
		}

		return true;
	},

	destroy: function() {
		this.unclipWrapper();
		this.unrelativizeElement();
		
		this.element = null;
		this.wrapper = null;
		this.speed = null;
		this.effect = null;
		this.delay = null;
		this.stopAtLeft = null;
		this.loop = null;
		this.returnOnFinish = null;
		this.returnDelay = null;
	},

	start: function() {
		this.currentTimeout = window.setTimeout(this.play.bindAsEventListener(this), this.delay+10);
		this.inProgress = true;

		return true;
	},

	play: function() {
		var options = {duration: this.duration/1000, afterFinish: this.afterFinish.bindAsEventListener(this)};
        var moveBy = (this.stopAtLeft) ? this.element.offsetWidth:Math.max(this.element.offsetWidth-this.wrapper.clientWidth, 0);
		moveBy = (moveBy+parseInt(this.element.style.left))*-1;
        
		if (this.effect) {
            options.transition = this.effect;
        }
        this.currentEffect = new Effect.MoveBy(this.element, 0, moveBy, options);
        //this.currentTimeout = window.setTimeout(this.stop.bindAsEventListener(this), this.duration+this.returnDelay+10);

		return true;

	},
	
	afterFinish: function() {
		if (!this.manualReturn) {
			this.setReturnDelay();
		}
		this.afterEffect();
		return true;
	},
	setReturnDelay: function() {
		this.currentTimeout = window.setTimeout(this.stop.bindAsEventListener(this), this.returnDelay);
	},
	stop: function() {
		//run the complete event handler
		this.onComplete();

		//if it returns on finish
		if (this.returnOnFinish) {
			this.relativizeElement();
		}

		if (this.loop) {
			if (!this.returnOnFinish) {
				this.element.style.left = this.wrapper.offsetWidth + 'px';
			}
			this.currentTimeout = window.setTimeout(this.start.bindAsEventListener(this), this.loopTimeout+10);
		} else {
			this.inProgress = false;
		}
	},
	
	changeSpeed: function(speed) {
		this.speed = speed;
		this.duration = (this.stopAtLeft) ? Math.floor(this.element.offsetWidth/this.speed)*1000:Math.floor(Math.max(this.element.offsetWidth-this.wrapper.clientWidth, 0)/this.speed)*1000;
		
		return true;
	},

	clipWrapper: function() {
		this.wrapper.style.overflow = 'hidden';
		return true;
	},

	unclipWrapper: function() {
		this.wrapper.style.overflow = '';
		return true;
	},

	relativizeElement: function() {
		this.element.style.position = 'relative';
		this.element.style.left = '0px';
		
		return true;
	},

	unrelativizeElement: function() {
		this.element.style.position = '';
		this.element.style.left = '';
		
		return true;
	},

	cancel: function() {
		if (this.currentTimeout) {
			window.clearTimeout(this.currentTimeout);
			this.currentTimeout = false;
		}
		if (this.currentEffect) {
			//cancel the effect
			this.currentEffect.cancel();
		}
		
		//return to start position
		this.relativizeElement();
		return true;
	}
};

var iMarquee = Class.create();
iMarquee.prototype = {
	wrapper: false,
	marqueeContainer: false,
	marqueeWrapperClass: false,
	marqueeTextClass: false,
	manualForward: [],
	manualBackward: [],
	startDelay: 4000,
	stopDelay: 4000,
	//in px/s
	marqueeSpeed: 20,
	//in ms
	speed: 1000,
	//this will stop the current element from scrolling out of view, but will not pause any marquing text
	pauseOnHover: false,
	//verticle is false, horizontal is true
	horizontal: false,

	loop: true,
	changingMarquee: false,
	listeners: [],

	initialize: function(wrapper, marqueeContainer, marqueeWrapperClass, marqueeTextClass) {
		this.wrapper = $(wrapper);
		this.marqueeContainer = $(marqueeContainer);
		this.marqueeWrapperClass = marqueeWrapperClass;
		this.marqueeTextClass = marqueeTextClass;
		this.marquees = [];		

		var options = (arguments[4]) ? arguments[4]:{};
		var defaults = {
			manualForward: false,
			manualBackward: false,
			loop: true,
			startDelay: 4000,
			stopDelay: 4000,
			marqueeSpeed: 20,
			speed: 1000,
			pauseOnHover: false,
			horizontal: false
		};
		Object.extend(defaults, options);
		
		this.manualForward = (defaults.manualForward.constructor == Array || !defaults.manualForward) ? defaults.manualForward:[defaults.manualForward];
		this.manualBackward = (defaults.manualBackward.constructor == Array || !defaults.manualBackward) ? defaults.manualBackward:[defaults.manualBackward];
		this.loop = defaults.loop;
		this.startDelay = defaults.startDelay;
		this.stopDelay = defaults.stopDelay;
		this.marqueeSpeed = defaults.marqueeSpeed;
		this.speed = defaults.speed;
		this.pauseOnHover = defaults.pauseOnHover;
		this.paused = false;
		this.timeoutPaused = false;
		if (this.pauseOnHover) {
			this.registerPauseListeners();
		}
		this.horizontal = defaults.horizontal;

		this.registerManualListeners();
		this.registerMarqueesFromClassNames();
		this.relativizeContainer();
		this.clipWrapper();
		
		//start the marquee
		this.onFinish();
	},

	destroy: function() {

	},

	registerMarquee: function(marquee) {
		this.marquees.push(marquee);
		marquee.afterEffect = this.setChangeMarqueeTimeout.bindAsEventListener(this);
		return true;
	},
	
	setChangeMarqueeTimeout: function() {
		this.changeMarqueeTimeout = window.setTimeout(function() {
			this.changeMarquee();
		}.bindAsEventListener(this), Math.floor(this.stopDelay/2));
	},

	unregisterMarquee: function(marquee) {
		var marquees = [];
		for (var i=0;i<this.marquees.length;i++) {
			if (this.marquees[i] != marquee) {
				marquees.push(marquee);
			}
		}
		this.marquees = marquees;

		return true;
	},

	registerMarqueesFromClassNames: function() {
		var wrappers = this.marqueeContainer.getElementsByClassName(this.marqueeWrapperClass);
		var texts = this.marqueeContainer.getElementsByClassName(this.marqueeTextClass);
		for (var i=0;i<wrappers.length;i++) {
			this.registerMarquee(new Marquee(texts[i], wrappers[i], {stopAtLeft: false, speed: this.marqueeSpeed, delay: this.startDelay, returnDelay: this.stopDelay, autoStart: false, manualReturn: true}));
		}

		return true;
	},
	
	forward: function() {
		var marquee = this.getCurrentMarquee();
		this.changeMarquee({cancelMarquee: marquee});

		return true;
	},

	backward: function() {
		var marquee = this.getCurrentMarquee();
		this.changeMarquee({backwards: true, cancelMarquee: marquee});

		return true;
	},

	changeMarquee: function() {
		//if a change isn't in progress
		if (!this.changingMarquee && !this.paused) {
			var options = Object.extend({
				backwards: false,
				cancelMarquee: false
			}, arguments[0] || {});
					
			this.lastMarquee = this.getCurrentMarquee();
			if (!this.checkNextMarquee(options.backwards) && this.loop) {
				this.moveFirstLast(options.backwards);
			//if there is no next, and we aren't looping, then we are done
			} else if (!this.checkNextMarquee(options.backwards)) {
				return true;
			}
			
			var afterFinish = (options.cancelMarquee) ? function() {this.onFinish();options.cancelMarquee.cancel();}:this.onFinish;
			
			var effectOptions = {
				transition: Effect.Transitions.linear, 
				duration: this.speed/1000, 
				afterFinish: afterFinish.bindAsEventListener(this)
			};
			var moveBy = {
				y: (options.backwards) ? this.wrapper.clientHeight:this.wrapper.clientHeight*-1,
				x: (options.backwards) ? this.wrapper.clientWidth:this.wrapper.clientWidth*-1
			}
			new Effect.MoveBy(this.marqueeContainer, (this.horizontal) ? 0:moveBy.y, (this.horizontal) ? moveBy.x:0, effectOptions);

			this.changingMarquee = true;
		} else if (this.paused) {
			this.waitingChange = true;
		}

		return true;
	},

	onFinish: function() {
		var marquee = this.getCurrentMarquee();
		marquee.start();
		this.changingMarquee = false;
		if (this.lastMarquee) {
			this.lastMarquee.stop();
		}

		return true;
	},
	
	getCurrentMarquee: function() {
		for (var i=0;i<this.marquees.length;i++) {
			if (this.getMarqueeOffsetPos(this.marquees[i]) == this.getMarqueeContainerOffsetPos()) {
				return this.marquees[i];
			}
		}
		
		return false;
	},
	
	checkNextMarquee: function(backwards) {
		var nextOffset = this.getMarqueeContainerOffsetPos();
		nextOffset += (backwards) ? this.getWrapperOffsetDim()*-1:this.getWrapperOffsetDim();
		for (var i=0;i<this.marquees.length;i++) {
			if (nextOffset == this.getMarqueeOffsetPos(this.marquees[i])) {
				return true;
			}
		}

		return false;
	},
	
	getMarqueeContainerOffsetPos: function(skipAbs) {
		var pos = (this.horizontal) ? parseInt(this.marqueeContainer.style.left):parseInt(this.marqueeContainer.style.top);
		return (skipAbs) ? pos:Math.abs(pos);
	},

	getWrapperOffsetDim: function() {
		return (this.horizontal) ? this.wrapper.clientWidth:this.wrapper.clientHeight;
	},

	getMarqueeOffsetPos: function(marquee) {
		return (this.horizontal) ? marquee.wrapper.offsetLeft:marquee.wrapper.offsetTop;
	},

	getMarqueeOffsetDim: function(marquee) {
		return (this.horizontal) ? marquee.wrapper.offsetWidth:marquee.wrapper.offsetHeight;
	},

	moveFirstLast: function(backwards) {
		//get first and last element
		var first = this.marquees[0];
		var last = this.marquees[0];
		for (var i=0;i<this.marquees.length;i++) {
			if (this.getMarqueeOffsetPos(this.marquees[i]) > this.getMarqueeOffsetPos(last)) {
				last = this.marquees[i];
			} else if (this.getMarqueeOffsetPos(this.marquees[i]) < this.getMarqueeOffsetPos(first)) {
				first = this.marquees[i];
			}
		}
		if (backwards) {
			this.marqueeContainer.removeChild(last.wrapper);
			this.marqueeContainer.insertBefore(last.wrapper, first.wrapper);
			this.marqueeContainer.style[(this.horizontal) ? 'left':'top'] = (this.getMarqueeContainerOffsetPos(true)-this.getMarqueeOffsetDim(last))+'px';
		} else {
			this.marqueeContainer.removeChild(first.wrapper);
			this.marqueeContainer.appendChild(first.wrapper);
			this.marqueeContainer.style[(this.horizontal) ? 'left':'top'] = (this.getMarqueeContainerOffsetPos(true)+this.getMarqueeOffsetDim(first))+'px';
		}
	},
	
	pause: function() {
		this.paused = true;
	},

	unpause: function() {
		this.paused = false;
		if (this.waitingChange) {
			this.changeMarquee();
			this.waitingChange = false;
		}
	},

	clipWrapper: function() {
		this.wrapper.style.overflow = 'hidden';
		return true;
	},

	unclipWrapper: function() {
		this.wrapper.style.overflow = '';
		return true;
	},

	relativizeContainer: function() {
		this.marqueeContainer.style.position = 'relative';
		if (this.horizontal) {
			this.marqueeContainer.style.left = '0px';
		} else {
			this.marqueeContainer.style.top = '0px';
		}
		return true;
	},

	unrelativizeContainer: function() {
		this.marqueeContainer.style.position = '';
		this.marqueeContainer.style.top = '';
	},

	registerManualListeners: function() {
		if (this.manualForward) {
			for (var i=0;i<this.manualForward.length;i++) {
				this.registerForwardListener(this.manualForward[i]);
			}
		}
		if (this.manualBackward) {
			for (var i=0;i<this.manualBackward.length;i++) {
				this.registerBackwardListener(this.manualBackward[i]);
			}
		}

		return true;
	},
	
	registerPauseListeners: function() {
		Event.observe(this.marqueeContainer, 'mouseover', this.pause.bind(this));
		Event.observe(this.marqueeContainer, 'mouseout', this.unpause.bind(this));
	},

	registerForwardListener: function(element) {
		element = $(element);
		var event = (arguments[1]) ? arguments[1]:'click';

		this.registerListener(element, event, this.forward.bindAsEventListener(this));
		return true;
	},

	registerBackwardListener: function(element) {
		element = $(element);
		var event = (arguments[1]) ? arguments[1]:'click';

		this.registerListener(element, event, this.backward.bindAsEventListener(this));
	},
	
    registerListener: function(element, event, handler) {
        this.listeners.push({
            element: element,
            event: event,
            handler: handler
        });
        Event.observe(element, event, handler);
    },

    destroyListener: function(element, event, handler) {
        var newListeners = [];
        for (var i=0;i<this.listeners.length;i++) {
            if (this.listeners[i] == {element: element, event: event, handler: handler}) {
                Event.stopObserving(this.listeners[i].element, this.listeners[i].event, this.listeners[i].handler);
            } else {
                newListeners.push(this.listeners[i]);
            }
        }
        this.listeners = newListeners;

        return true;
    }
};


/********************************
** newsitem.js
**
** handling for snappy news items
** relies on verticalcenter.js
** Design Kitchen
** @author: Matthew Story
*********************************/
var NewsItems = {
	newsItems: [],
	currentTimestamp: false,
	parentNode: 'news_items',
	currentNewsItem: false,

	register: function(newsItem) {
		this.newsItems.push(newsItem);
		return true;
	},

	unregister: function(newsItem) {
		var newsItems = [];
		for (var i=0;i<newsItems.length;i++) {
			if (this.newsItems[i] != newsItem) {
				newsItems.push(this.newsItems[i]);
			}
		}
		this.newsItems = newsItems;

		return true;
	},
	
	displayNewsItems: function(hours, max) {
		this.clearCurrentNewsItems();
		this.makeNewsItemsInRange(this.makeTimestampFromHours(hours), max);
	},

	clearCurrentNewsItems: function() {
		for (var i=0;i<this.newsItems.length;i++) {
			this.newsItems[i].hideElement();
		}

		return true;
	},

	makeNewsItemsInRange: function(timestamp, max) {
		var count = 0;
		this.newsItems.sort(this.sort);
		
		for (var i=0;i<this.newsItems.length&&count<max;i++) {
			if (this.newsItems[i].timestamp >= timestamp) {
				this.newsItems[i].showElement();
				count++;
			}
		}

		return true;
	},

	sort: function(a,b) {
		return a.position-b.position;
	},

	makeTimestampFromHours: function(hours) {
		return this.currentTimestamp-(hours*60*60);
	}
};

var NewsItem = Class.create();
NewsItem.prototype = {
	href: false,
	title: false,
	timestamp: false,
	expiration: false,
	onclick: false,
	articleId: false,
	img: false,
	position: false,
	element: false,

	initialize: function(title, href, timestamp, expiration, onclick, articleId, position, img) {
		this.href = href;
		this.title = title;
		this.timestamp = timestamp;
		this.expiration = expiration;
		this.onclick = onclick;
		this.articleId = articleId;
		this.position = position;
		this.img = img;
		if (this.expiration > (parseInt(new Date().getTime().toString().sub(/\d{3}$/, '')) + (new Date().getTimezoneOffset() * 60) - 5 * 60 * 60)) {
		  this.makeElement();
		  //this.hideElement();
		  NewsItems.register(this);
    }

		return true;
	},

	destroy: function() {
		NewsItems.unregister();
		this.href = null;
		this.title = null;
		this.timestamp = null;
		this.onclick = null;
		this.destroyElement();
		this.element = null;

		return true;
	},

	makeElement: function() {
		this.element = document.createElement('li');
		var div = document.createElement('div');
		var a = document.createElement('a');
		var textNode = document.createTextNode(this.title);
		div.setAttribute('id', 'news_item_'+this.articleId);
		div.setAttribute('class', 'news_item');
		a.setAttribute('href', this.href);
		a.onclick = this.onclick;
		if (this.img) {
			var img = document.createElement('img');
			img.src = this.img;
			if (this.img.match(/scaled/)) {
				img.className = 'scaled'
			}
			div.appendChild(img);
			
			//vertically center image
			div.verticalCenter = new VerticalCenter(img);
		}
		a.appendChild(textNode);
		div.appendChild(a);
		this.element.appendChild(div);
		$(NewsItems.parentNode).appendChild(this.element);

		//vertically center contents
		this.element.verticalCenter = new VerticalCenter(div);

		return true;
	},

	destroyElement: function() {
		if (this.element) {
			this.element.verticalCenter.destroy();
			this.element.parentNode.removeChild(this.element);
			this.element = false;
		}

		return true;
	},

	showElement: function() {
		Element.show(this.element);
		return true;
	},

	hideElement: function() {
		Element.hide(this.element);
		return true;
	}
};


// Lowpro
Event.addBehavior({
  'li div.news_item a:click': function(e) {
		Event.stop(e);
		var href = this.href;
		new Ajax.Request(href,{
			method: 'get',
			onLoading:function(){
				$('news_item').spin();
			},
			onFailure:function(response){
				alert("The news item failed to load. Please try again.");
			}
		});	
	}
});
// Event.onReady(function(){
// 	$$("li div.news_item a").each(function(el){
// 		Event.observe(el, 'click', function(e){			
// 			Event.stop(e);
// 			var href = el.href;
// 			new Ajax.Request(href,{
// 				method: 'get',
// 				onLoading:function(){
// 					$('news_item').spin();
// 				},
// 				onFailure:function(response){
// 					alert("The news item failed to load. Please try again.");
// 				}
// 			});
// 		});
// 	});
// });


/********************************
** oneliner.js
**
** marqueing text
** Design Kitchen
** @author: Matthew Story
*********************************/

//relies on resizer.js resizer object
var OneLiner = Class.create();
OneLiner.prototype = {
	element: false,
	oldMargin: false,
	oldPadding: false,
	oldBorder: false,

	initialize: function(element) {
		this.element = $(element);
		this.registerResizeHandlers();
		this.oneLineify();

		return true;
	},

	destroy: function() {
		this.element = null;
		return true;
	},

	oneLineify: function() {
		//strip styles and save them
		this.stripStyle();
		//this will make it all one line with trailing whitespace
		this.hardWidthify(this.element.offsetWidth*this.getNumLines());
		//reinstateStyles
		this.reinstateStyle();
		//this will trim the trailing whitespace
		this.hardWidthify(this.getContentWidth());
		
		return true;
	},

	registerResizeHandlers: function() {
		Resizer.register(this.oneLineify.bindAsEventListener(this));
		return true;
	},

	getNumLines: function() {
		return ((this.element.offsetHeight-Resizer.height)/(Resizer.height+Resizer.extraHeight))+1;
	},
	
	stripStyle: function() {
		this.element.style.height = '';
		this.element.style.width = '';
		this.oldBorder = this.element.style.border;
		this.oldPadding = this.element.style.padding;
		this.oldMargin = this.element.style.margin;
		this.element.style.border = '0px';
		this.element.style.margin = '0px';
		this.element.style.padding = '0px';

		return true;
	},

	reinstateStyle: function() {
		this.element.style.border = this.oldBorder;
		this.element.style.margin = this.oldMargin;
		this.element.style.padding = this.oldPadding;

		return true;
	},
	
	getContentWidth: function() {
		//make a unique id
		var id = this.getUniqueId();
		this.element.innerHTML = "<span style='border:0px;margin:0px;padding:0px;'id='" + id + "'>" + this.element.innerHTML + "</span>";
		var width = $(id).offsetWidth;
		this.element.innerHTML = $(id).innerHTML;
		
		return width;
	},

	hardWidthify: function(width) {
		this.element.style.width = width + 'px';
		return true;
	},

	getUniqueId: function() {
		var id = '_';
        while ($(id)) {
            id += '_';
        }
		
		return id;
	}
};


/*
 * Processing.js - John Resig (http://ejohn.org/)
 * MIT Licensed
 * http://ejohn.org/blog/processingjs/
 *
 * This is a port of the Processing Visualization Language.
 * More information: http://processing.org/
 */

(function(){

this.Processing = function Processing( aElement, aCode )
{
  var p = buildProcessing( aElement );
  p.init( aCode );
  return p;
};

function log()
{
  try
  {
    console.log.apply( console, arguments );
  }
  catch(e)
  {
    try
    {
      opera.postError.apply( opera, arguments );
    }
    catch(e){}
  }
}

function parse( aCode, p )
{
  // Angels weep at this parsing code :-(

  // Remove end-of-line comments
  aCode = aCode.replace(/\/\/ .*\n/g, "\n");

  // Weird parsing errors with %
  aCode = aCode.replace(/([^\s])%([^\s])/g, "$1 % $2");
 
  // Simple convert a function-like thing to function
  aCode = aCode.replace(/(?:static )?(\w+ )(\w+)\s*(\([^\)]*\)\s*{)/g, function(all, type, name, args)
  {
    if ( name == "if" || name == "for" || name == "while" )
    {
      return all;
    }
    else
    {
      return "Processing." + name + " = function " + name + args;
    }
  });

  // Force .length() to be .length
  aCode = aCode.replace(/\.length\(\)/g, ".length");

  // foo( int foo, float bar )
  aCode = aCode.replace(/([\(,]\s*)(\w+)((?:\[\])+| )\s*(\w+\s*[\),])/g, "$1$4");
  aCode = aCode.replace(/([\(,]\s*)(\w+)((?:\[\])+| )\s*(\w+\s*[\),])/g, "$1$4");

  // float[] foo = new float[5];
  aCode = aCode.replace(/new (\w+)((?:\[([^\]]*)\])+)/g, function(all, name, args)
  {
    return "new ArrayList(" + args.slice(1,-1).split("][").join(", ") + ")";
  });
  
  aCode = aCode.replace(/(?:static )?\w+\[\]\s*(\w+)\[?\]?\s*=\s*{.*?};/g, function(all)
  {
    return all.replace(/{/g, "[").replace(/}/g, "]");
  });

  // int|float foo;
  var intFloat = /(\n\s*(?:int|float)(?:\[\])?(?:\s*|[^\(]*?,\s*))([a-z]\w*)(;|,)/i;
  while ( intFloat.test(aCode) )
  {
    aCode = aCode.replace(new RegExp(intFloat), function(all, type, name, sep)
    {
      return type + " " + name + " = 0" + sep;
    });
  }

  // float foo = 5;
  aCode = aCode.replace(/(?:static )?(\w+)((?:\[\])+| ) *(\w+)\[?\]?(\s*[=,;])/g, function(all, type, arr, name, sep)
  {
    if ( type == "return" )
      return all;
    else
      return "var " + name + sep;
  });

  // Fix Array[] foo = {...} to [...]
  aCode = aCode.replace(/=\s*{((.|\s)*?)};/g, function(all,data)
  {
    return "= [" + data.replace(/{/g, "[").replace(/}/g, "]") + "]";
  });
  
  // static { ... } blocks
  aCode = aCode.replace(/static\s*{((.|\n)*?)}/g, function(all, init)
  {
    // Convert the static definitons to variable assignments
    //return init.replace(/\((.*?)\)/g, " = $1");
    return init;
  });

  // super() is a reserved word
  aCode = aCode.replace(/super\(/g, "superMethod(");

  var classes = ["int", "float", "boolean", "string"];

  function ClassReplace(all, name, extend, vars, last)
  {
    classes.push( name );

    var static = "";

    vars = vars.replace(/final\s+var\s+(\w+\s*=\s*.*?;)/g, function(all,set)
    {
      static += " " + name + "." + set;
      return "";
    });

    // Move arguments up from constructor and wrap contents with
    // a with(this), and unwrap constructor
    return "function " + name + "() {with(this){\n  " +
      (extend ? "var __self=this;function superMethod(){extendClass(__self,arguments," + extend + ");}\n" : "") +
      // Replace var foo = 0; with this.foo = 0;
      // and force var foo; to become this.foo = null;
      vars
        .replace(/,\s?/g, ";\n  this.")
        .replace(/\b(var |final |public )+\s*/g, "this.")
        .replace(/this.(\w+);/g, "this.$1 = null;") + 
	(extend ? "extendClass(this, " + extend + ");\n" : "") +
        "<CLASS " + name + " " + static + ">" + (typeof last == "string" ? last : name + "(");
  }

  var matchClasses = /(?:public |abstract |static )*class (\w+)\s*(?:extends\s*(\w+)\s*)?{\s*((?:.|\n)*?)\b\1\s*\(/g;
  var matchNoCon = /(?:public |abstract |static )*class (\w+)\s*(?:extends\s*(\w+)\s*)?{\s*((?:.|\n)*?)(Processing)/g;
  
  aCode = aCode.replace(matchClasses, ClassReplace);
  aCode = aCode.replace(matchNoCon, ClassReplace);

  var matchClass = /<CLASS (\w+) (.*?)>/, m;
  
  while ( (m = aCode.match( matchClass )) )
  {
    var left = RegExp.leftContext,
      allRest = RegExp.rightContext,
      rest = nextBrace(allRest),
      className = m[1],
      staticVars = m[2] || "";
      
    allRest = allRest.slice( rest.length + 1 );

    rest = rest.replace(new RegExp("\\b" + className + "\\(([^\\)]*?)\\)\\s*{", "g"), function(all, args)
    {
      args = args.split(/,\s*?/);
      
      if ( args[0].match(/^\s*$/) )
        args.shift();
      
      var fn = "if ( arguments.length == " + args.length + " ) {\n";
        
      for ( var i = 0; i < args.length; i++ )
      {
        fn += "    var " + args[i] + " = arguments[" + i + "];\n";
      }
        
      return fn;
    });
    
    // Fix class method names
    // this.collide = function() { ... }
    // and add closing } for with(this) ...
    rest = rest.replace(/(?:public )?Processing.\w+ = function (\w+)\((.*?)\)/g, function(all, name, args)
    {
      return "ADDMETHOD(this, '" + name + "', function(" + args + ")";
    });
    
    var matchMethod = /ADDMETHOD([\s\S]*?{)/, mc;
    var methods = "";
    
    while ( (mc = rest.match( matchMethod )) )
    {
      var prev = RegExp.leftContext,
        allNext = RegExp.rightContext,
        next = nextBrace(allNext);

      methods += "addMethod" + mc[1] + next + "});"
      
      rest = prev + allNext.slice( next.length + 1 );
      
    }

    rest = methods + rest;
    
    aCode = left + rest + "\n}}" + staticVars + allRest;
  }

  // Do some tidying up, where necessary
  aCode = aCode.replace(/Processing.\w+ = function addMethod/g, "addMethod");
  
  function nextBrace( right )
  {
    var rest = right;
    var position = 0;
    var leftCount = 1, rightCount = 0;
    
    while ( leftCount != rightCount )
    {
      var nextLeft = rest.indexOf("{");
      var nextRight = rest.indexOf("}");
      
      if ( nextLeft < nextRight && nextLeft != -1 )
      {
        leftCount++;
        rest = rest.slice( nextLeft + 1 );
        position += nextLeft + 1;
      }
      else
      {
        rightCount++;
        rest = rest.slice( nextRight + 1 );
        position += nextRight + 1;
      }
    }
    
    return right.slice(0, position - 1);
  }

  // Handle (int) Casting
  aCode = aCode.replace(/\(int\)/g, "0|");

  // Remove Casting
  aCode = aCode.replace(new RegExp("\\((" + classes.join("|") + ")(\\[\\])?\\)", "g"), "");
  
  // Convert 3.0f to just 3.0
  aCode = aCode.replace(/(\d+)f/g, "$1");

  // Force numbers to exist
  //aCode = aCode.replace(/([^.])(\w+)\s*\+=/g, "$1$2 = ($2||0) +");

  // Force characters-as-bytes to work
  aCode = aCode.replace(/('[a-zA-Z0-9]')/g, "$1.charCodeAt(0)");

  // Convert #aaaaaa into color
  aCode = aCode.replace(/#([a-f0-9]{6})/ig, function(m, hex){
    var num = toNumbers(hex);
    return "color(" + num[0] + "," + num[1] + "," + num[2] + ")";
  });

  function toNumbers( str ){
    var ret = [];
     str.replace(/(..)/g, function(str){
      ret.push( parseInt( str, 16 ) );
    });
    return ret;
  }

//log(aCode);

  return aCode;
}

function buildProcessing( curElement ){

  var p = {};

  // init
  p.PI = Math.PI;
  p.TWO_PI = 2 * p.PI;
  p.HALF_PI = p.PI / 2;
  p.P3D = 3;
  p.CORNER = 0;
  p.CENTER = 1;
  p.CENTER_RADIUS = 2;
  p.RADIUS = 2;
  p.POLYGON = 1;
  p.TRIANGLES = 6;
  p.POINTS = 7;
  p.LINES = 8;
  p.TRIANGLE_STRIP = 9;
  p.CORNERS = 10;
  p.CLOSE = true;
  p.RGB = 1;
  p.HSB = 2;

  // "Private" variables used to maintain state
  var curContext = curElement.getContext("2d");
  var doFill = true;
  var doStroke = true;
  var loopStarted = false;
  var hasBackground = false;
  var doLoop = true;
  var curRectMode = p.CORNER;
  var curEllipseMode = p.CENTER;
  var inSetup = false;
  var inDraw = false;
  var curBackground = "rgba(204,204,204,1)";
  var curFrameRate = 1000;
  var curShape = p.POLYGON;
  var curShapeCount = 0;
  var opacityRange = 255;
  var redRange = 255;
  var greenRange = 255;
  var blueRange = 255;
  var pathOpen = false;
  var mousePressed = false;
  var keyPressed = false;
  var firstX, firstY, prevX, prevY;
  var curColorMode = p.RGB;
  var curTint = -1;
  var curTextSize = 12;
  var curTextFont = "Arial";
  var getLoaded = false;
  var start = (new Date).getTime();

  // Global vars for tracking mouse position
  p.pmouseX = 0;
  p.pmouseY = 0;
  p.mouseX = 0;
  p.mouseY = 0;

  // Will be replaced by the user, most likely
  p.mouseDragged = undefined;
  p.mouseMoved = undefined;
  p.mousePressed = undefined;
  p.mouseReleased = undefined;
  p.keyPressed = undefined;
  p.keyReleased = undefined;
  p.draw = undefined;
  p.setup = undefined;

  // The height/width of the canvas
  p.width = curElement.width - 0;
  p.height = curElement.height - 0;
  
  // In case I ever need to do HSV conversion:
  // http://srufaculty.sru.edu/david.dailey/javascript/js/5rml.js
  p.color = function color( aValue1, aValue2, aValue3, aValue4 )
  {
    var aColor = "";
    
    if ( arguments.length == 3 )
    {
      aColor = p.color( aValue1, aValue2, aValue3, opacityRange );
    }
    else if ( arguments.length == 4 )
    {
      var a = aValue4 / opacityRange;
      a = isNaN(a) ? 1 : a;

      if ( curColorMode == p.HSB )
      {
        var rgb = HSBtoRGB(aValue1, aValue2, aValue3);
        var r = rgb[0], g = rgb[1], b = rgb[2];
      }
      else
      {
        var r = getColor(aValue1, redRange);
        var g = getColor(aValue2, greenRange);
        var b = getColor(aValue3, blueRange);
      }

      aColor = "rgba(" + r + "," + g + "," + b + "," + a + ")";
    }
    else if ( typeof aValue1 == "string" )
    {
      aColor = aValue1;

      if ( arguments.length == 2 )
      {
        var c = aColor.split(",");
	c[3] = (aValue2 / opacityRange) + ")";
	aColor = c.join(",");
      }
    }
    else if ( arguments.length == 2 )
    {
      aColor = p.color( aValue1, aValue1, aValue1, aValue2 );
    }
    else if ( typeof aValue1 == "number" )
    {
      aColor = p.color( aValue1, aValue1, aValue1, opacityRange );
    }
    else
    {
      aColor = p.color( redRange, greenRange, blueRange, opacityRange );
    }

    // HSB conversion function from Mootools, MIT Licensed
    function HSBtoRGB(h, s, b)
    {
      h = (h / redRange) * 100;
      s = (s / greenRange) * 100;
      b = (b / blueRange) * 100;
      if (s == 0){
        return [b, b, b];
      } else {
        var hue = h % 360;
        var f = hue % 60;
        var br = Math.round(b / 100 * 255);
        var p = Math.round((b * (100 - s)) / 10000 * 255);
        var q = Math.round((b * (6000 - s * f)) / 600000 * 255);
        var t = Math.round((b * (6000 - s * (60 - f))) / 600000 * 255);
        switch (Math.floor(hue / 60)){
          case 0: return [br, t, p];
          case 1: return [q, br, p];
          case 2: return [p, br, t];
          case 3: return [p, q, br];
          case 4: return [t, p, br];
          case 5: return [br, p, q];
        }
      }
    }

    function getColor( aValue, range )
    {
      return Math.round(255 * (aValue / range));
    }
    
    return aColor;
  }

  p.nf = function( num, pad )
  {
    var str = "" + num;
    while ( pad - str.length )
      str = "0" + str;
    return str;
  };

  p.AniSprite = function( prefix, frames )
  {
    this.images = [];
    this.pos = 0;

    for ( var i = 0; i < frames; i++ )
    {
      this.images.push( prefix + p.nf( i, ("" + frames).length ) + ".gif" );
    }

    this.display = function( x, y )
    {
      p.image( this.images[ this.pos ], x, y );

      if ( ++this.pos >= frames )
        this.pos = 0;
    };

    this.getWidth = function()
    {
      return getImage(this.images[0]).width;
    };

    this.getHeight = function()
    {
      return getImage(this.images[0]).height;
    };
  };

  function buildImageObject( obj )
  {
    var pixels = obj.data;
    var data = p.createImage( obj.width, obj.height );

    if ( data.__defineGetter__ && data.__lookupGetter__ && !data.__lookupGetter__("pixels") )
    {
      var pixelsDone;
      data.__defineGetter__("pixels", function()
      {
        if ( pixelsDone )
	  return pixelsDone;

	pixelsDone = [];

        for ( var i = 0; i < pixels.length; i += 4 )
        {
          pixelsDone.push( p.color(pixels[i], pixels[i+1], pixels[i+2], pixels[i+3]) );
        }

	return pixelsDone;
      });
    }
    else
    {
      data.pixels = [];

      for ( var i = 0; i < pixels.length; i += 4 )
      {
        data.pixels.push( p.color(pixels[i], pixels[i+1], pixels[i+2], pixels[i+3]) );
      }
    }

    return data;
  }

  p.createImage = function createImage( w, h, mode )
  {
    var data = {
      width: w,
      height: h,
      pixels: new Array( w * h ),
      get: function(x,y)
      {
        return this.pixels[w*y+x];
      },
      _mask: null,
      mask: function(img)
      {
        this._mask = img;
      },
      loadPixels: function()
      {
      },
      updatePixels: function()
      {
      }
    };

    return data;
  }

  p.createGraphics = function createGraphics( w, h )
  {
    var canvas = document.createElement("canvas");
    var ret = buildProcessing( canvas );
    ret.size( w, h );
    ret.canvas = canvas;
    return ret;
  }

  p.beginDraw = function beginDraw()
  {

  }

  p.endDraw = function endDraw()
  {

  }

  p.tint = function tint( rgb, a )
  {
    curTint = a;
  }

  function getImage( img ) {
    if ( typeof img == "string" )
    {
      return document.getElementById(img);
    }

    if ( img.img || img.canvas )
    {
      return img.img || img.canvas;
    }

    img.data = [];

    for ( var i = 0, l = img.pixels.length; i < l; i++ )
    {
      var c = (img.pixels[i] || "rgba(0,0,0,1)").slice(5,-1).split(",");
      img.data.push( parseInt(c[0]), parseInt(c[1]), parseInt(c[2]), parseFloat(c[3]) * 100 );
    }

    var canvas = document.createElement("canvas")
    canvas.width = img.width;
    canvas.height = img.height;
    var context = canvas.getContext("2d");
    context.putImageData( img, 0, 0 );

    img.canvas = canvas;

    return canvas;
  }

  p.image = function image( img, x, y, w, h )
  {
    x = x || 0;
    y = y || 0;

    var obj = getImage(img);

    if ( curTint >= 0 )
    {
      var oldAlpha = curContext.globalAlpha;
      curContext.globalAlpha = curTint / opacityRange;
    }

    if ( arguments.length == 3 )
    {
      curContext.drawImage( obj, x, y );
    }
    else
    {
      curContext.drawImage( obj, x, y, w, h );
    }

    if ( curTint >= 0 )
    {
      curContext.globalAlpha = oldAlpha;
    }

    if ( img._mask )
    {
      var oldComposite = curContext.globalCompositeOperation;
      curContext.globalCompositeOperation = "darker";
      p.image( img._mask, x, y );
      curContext.globalCompositeOperation = oldComposite;
    }
  }

  p.exit = function exit()
  {

  }

  p.save = function save( file )
  {

  }

  p.loadImage = function loadImage( file )
  {
    var img = document.getElementById(file);
    if ( !img )
      return;

    var h = img.height, w = img.width;

    var canvas = document.createElement("canvas");
    canvas.width = w;
    canvas.height = h;
    var context = canvas.getContext("2d");

    context.drawImage( img, 0, 0 );
    var data = buildImageObject( context.getImageData( 0, 0, w, h ) );
    data.img = img;
    return data;
  }

  p.loadFont = function loadFont( name )
  {
    return {
      name: name,
      width: function( str )
      {
        if ( curContext.mozMeasureText )
          return curContext.mozMeasureText( typeof str == "number" ?
            String.fromCharCode( str ) :
            str) / curTextSize;
	else
	  return 0;
      }
    };
  }

  p.textFont = function textFont( name, size )
  {
    curTextFont = name;
    p.textSize( size );
  }

  p.textSize = function textSize( size )
  {
    if ( size )
    {
      curTextSize = size;
    }
  }

  p.textAlign = function textAlign()
  {

  }

  p.text = function text( str, x, y )
  {
    if ( str && curContext.mozDrawText )
    {
      curContext.save();
      curContext.mozTextStyle = curTextSize + "px " + curTextFont.name;
      curContext.translate(x, y);
      curContext.mozDrawText( typeof str == "number" ?
        String.fromCharCode( str ) :
	str );
      curContext.restore();
    }
  }

  p.char = function char( key )
  {
    //return String.fromCharCode( key );
    return key;
  }

  p.println = function println()
  {

  }

  p.map = function map( value, istart, istop, ostart, ostop )
  {
    return ostart + (ostop - ostart) * ((value - istart) / (istop - istart));
  };

  String.prototype.replaceAll = function(re, replace)
  {
    return this.replace(new RegExp(re, "g"), replace);
  };

  p.Point = function Point( x, y )
  {
    this.x = x;
    this.y = y;
    this.copy = function()
    {
      return new Point( x, y );
    }
  }

  p.Random = function()
  {
    var haveNextNextGaussian = false;
    var nextNextGaussian;

    this.nextGaussian = function()
    {
      if (haveNextNextGaussian) {
        haveNextNextGaussian = false;

        return nextNextGaussian;
      } else {
        var v1, v2, s;
        do { 
          v1 = 2 * p.random(1) - 1;   // between -1.0 and 1.0
          v2 = 2 * p.random(1) - 1;   // between -1.0 and 1.0
          s = v1 * v1 + v2 * v2;
        } while (s >= 1 || s == 0);
        var multiplier = Math.sqrt(-2 * Math.log(s)/s);
        nextNextGaussian = v2 * multiplier;
        haveNextNextGaussian = true;

        return v1 * multiplier;
      }
    };
  }

  p.ArrayList = function ArrayList( size, size2, size3 )
  {
    var array = new Array( 0 | size );
    
    if ( size2 )
    {
      for ( var i = 0; i < size; i++ )
      {
        array[i] = [];

        for ( var j = 0; j < size2; j++ )
        {
	  var a = array[i][j] = size3 ? new Array( size3 ) : 0;
	  for ( var k = 0; k < size3; k++ )
	  {
	    a[k] = 0;
	  }
        }
      }
    }
    else
    {
      for ( var i = 0; i < size; i++ )
      {
        array[i] = 0;
      }
    }
    
    array.size = function()
    {
      return this.length;
    };
    array.get = function( i )
    {
      return this[ i ];
    };
    array.remove = function( i )
    {
      return this.splice( i, 1 );
    };
    array.add = function( item )
    {
      for ( var i = 0; this[ i ] != undefined; i++ ) {}
      this[ i ] = item;
    };
    array.clone = function()
    {
      var a = new ArrayList( size );
      for ( var i = 0; i < size; i++ )
      {
        a[ i ] = this[ i ];
      }
      return a;
    };
    array.isEmpty = function()
    {
      return !this.length;
    };
    array.clear = function()
    {
      this.length = 0;
    };
    
    return array;
  }
  
  p.colorMode = function colorMode( mode, range1, range2, range3, range4 )
  {
    curColorMode = mode;

    if ( arguments.length >= 4 )
    {
      redRange = range1;
      greenRange = range2;
      blueRange = range3;
    }

    if ( arguments.length == 5 )
    {
      opacityRange = range4;
    }

    if ( arguments.length == 2 )
    {
      p.colorMode( mode, range1, range1, range1, range1 );
    }
  }
  
  p.beginShape = function beginShape( type )
  {
    curShape = type;
    curShapeCount = 0; 
  }
  
  p.endShape = function endShape( close )
  {
    if ( curShapeCount != 0 )
    {
      curContext.lineTo( firstX, firstY );

      if ( doFill )
        curContext.fill();
        
      if ( doStroke )
        curContext.stroke();
    
      curContext.closePath();
      curShapeCount = 0;
      pathOpen = false;
    }

    if ( pathOpen )
    {
      curContext.closePath();
    }
  }
  
  p.vertex = function vertex( x, y, x2, y2, x3, y3 )
  {
    if ( curShapeCount == 0 && curShape != p.POINTS )
    {
      pathOpen = true;
      curContext.beginPath();
      curContext.moveTo( x, y );
    }
    else
    {
      if ( curShape == p.POINTS )
      {
        p.point( x, y );
      }
      else if ( arguments.length == 2 )
      {
        if ( curShape == p.TRIANGLE_STRIP && curShapeCount == 2 )
	{
          curContext.moveTo( prevX, prevY );
          curContext.lineTo( firstX, firstY );
	}

        curContext.lineTo( x, y );
      }
      else if ( arguments.length == 4 )
      {
        if ( curShapeCount > 1 )
        {
	  curContext.moveTo( prevX, prevY );
          curContext.quadraticCurveTo( firstX, firstY, x, y );
	  curShapeCount = 1;
        }
      }
      else if ( arguments.length == 6 )
      {
        curContext.bezierCurveTo( x, y, x2, y2, x3, y3 );
        curShapeCount = -1;
      }
    }

    prevX = firstX;
    prevY = firstY;
    firstX = x;
    firstY = y;

    
    curShapeCount++;
    
    if ( curShape == p.LINES && curShapeCount == 2 ||
         (curShape == p.TRIANGLES || curShape == p.TRIANGLE_STRIP) && curShapeCount == 3 )
    {
      p.endShape();
    }

    if ( curShape == p.TRIANGLE_STRIP && curShapeCount == 3 )
    {
      curShapeCount = 2;
    }
  }

  p.curveTightness = function()
  {

  }

  // Unimplmented - not really possible with the Canvas API
  p.curveVertex = function( x, y, x2, y2 )
  {
    p.vertex( x, y, x2, y2 );
  }

  p.bezierVertex = p.vertex
  
  p.rectMode = function rectMode( aRectMode )
  {
    curRectMode = aRectMode;
  }

  p.imageMode = function()
  {

  }
  
  p.ellipseMode = function ellipseMode( aEllipseMode )
  {
    curEllipseMode = aEllipseMode;
  }
  
  p.dist = function dist( x1, y1, x2, y2 )
  {
    return Math.sqrt( Math.pow( x2 - x1, 2 ) + Math.pow( y2 - y1, 2 ) );
  }

  p.year = function year()
  {
    return (new Date).getYear() + 1900;
  }

  p.month = function month()
  {
    return (new Date).getMonth();
  }

  p.day = function day()
  {
    return (new Date).getDay();
  }

  p.hour = function hour()
  {
    return (new Date).getHours();
  }

  p.minute = function minute()
  {
    return (new Date).getMinutes();
  }

  p.second = function second()
  {
    return (new Date).getSeconds();
  }

  p.millis = function millis()
  {
    return (new Date).getTime() - start;
  }
  
  p.ortho = function ortho()
  {
  
  }
  
  p.translate = function translate( x, y )
  {
    curContext.translate( x, y );
  }
  
  p.scale = function scale( x, y )
  {
    curContext.scale( x, y || x );
  }
  
  p.rotate = function rotate( aAngle )
  {
    curContext.rotate( aAngle );
  }
  
  p.pushMatrix = function pushMatrix()
  {
    curContext.save();
  }
  
  p.popMatrix = function popMatrix()
  {
    curContext.restore();
  }
  
  p.redraw = function redraw()
  {
    if ( hasBackground )
    {
      p.background();
    }
    
    inDraw = true;
    p.pushMatrix();
    p.draw();
    p.popMatrix();
    inDraw = false;
  }
  
  p.loop = function loop()
  {
    if ( loopStarted )
      return;
    
    var looping = setInterval(function()
    {
      try
      {
        p.redraw();
      }
      catch(e)
      {
        clearInterval( looping );
        throw e;
      }
    }, 1000 / curFrameRate );
    
    loopStarted = true;
  }
  
  p.frameRate = function frameRate( aRate )
  {
    curFrameRate = aRate;
  }
  
  p.background = function background( img )
  {
    if ( arguments.length )
    {
      if ( img && img.img )
      {
        curBackground = img;
      }
      else
      {
        curBackground = p.color.apply( this, arguments );
      }
    }
    

    if ( curBackground.img )
    {
      p.image( curBackground, 0, 0 );
    }
    else
    {
      var oldFill = curContext.fillStyle;
      curContext.fillStyle = curBackground + "";
      curContext.fillRect( 0, 0, p.width, p.height );
      curContext.fillStyle = oldFill;
    }
  }

  p.sq = function sq( aNumber )
  {
    return aNumber * aNumber;
  }

  p.sqrt = function sqrt( aNumber )
  {
    return Math.sqrt( aNumber );
  }
  
  p.int = function int( aNumber )
  {
    return Math.floor( aNumber );
  }

  p.min = function min( aNumber, aNumber2 )
  {
    return Math.min( aNumber, aNumber2 );
  }

  p.max = function max( aNumber, aNumber2 )
  {
    return Math.max( aNumber, aNumber2 );
  }

  p.ceil = function ceil( aNumber )
  {
    return Math.ceil( aNumber );
  }

  p.floor = function floor( aNumber )
  {
    return Math.floor( aNumber );
  }

  p.float = function float( aNumber )
  {
    return typeof aNumber == "string" ?
	p.float( aNumber.charCodeAt(0) ) :
        parseFloat( aNumber );
  }

  p.byte = function byte( aNumber )
  {
    return aNumber || 0;
  }
  
  p.random = function random( aMin, aMax )
  {
    return arguments.length == 2 ?
      aMin + (Math.random() * (aMax - aMin)) :
      Math.random() * aMin;
  }

  // From: http://freespace.virgin.net/hugo.elias/models/m_perlin.htm
  p.noise = function( x, y, z )
  {
    return arguments.length >= 2 ?
      PerlinNoise_2D( x, y ) :
      PerlinNoise_2D( x, x );
  }

  function Noise(x, y)
  {
    var n = x + y * 57;
    n = (n<<13) ^ n;
    return Math.abs(1.0 - (((n * ((n * n * 15731) + 789221) + 1376312589) & 0x7fffffff) / 1073741824.0));
  }

  function SmoothedNoise(x, y)
  {
    var corners = ( Noise(x-1, y-1)+Noise(x+1, y-1)+Noise(x-1, y+1)+Noise(x+1, y+1) ) / 16;
    var sides   = ( Noise(x-1, y)  +Noise(x+1, y)  +Noise(x, y-1)  +Noise(x, y+1) ) /  8;
    var center  =  Noise(x, y) / 4;
    return corners + sides + center;
  }

  function InterpolatedNoise(x, y)
  {
    var integer_X    = Math.floor(x);
    var fractional_X = x - integer_X;

    var integer_Y    = Math.floor(y);
    var fractional_Y = y - integer_Y;

    var v1 = SmoothedNoise(integer_X,     integer_Y);
    var v2 = SmoothedNoise(integer_X + 1, integer_Y);
    var v3 = SmoothedNoise(integer_X,     integer_Y + 1);
    var v4 = SmoothedNoise(integer_X + 1, integer_Y + 1);

    var i1 = Interpolate(v1 , v2 , fractional_X);
    var i2 = Interpolate(v3 , v4 , fractional_X);

    return Interpolate(i1 , i2 , fractional_Y);
  }

  function PerlinNoise_2D(x, y)
  {
      var total = 0;
      var p = 0.25;
      var n = 3;

      for ( var i = 0; i <= n; i++ )
      {
          var frequency = Math.pow(2, i);
          var amplitude = Math.pow(p, i);

          total = total + InterpolatedNoise(x * frequency, y * frequency) * amplitude;
      }

      return total;
  }

  function Interpolate(a, b, x)
  {
    var ft = x * p.PI;
    var f = (1 - p.cos(ft)) * .5;
    return  a*(1-f) + b*f;
  }

  p.red = function( aColor )
  {
    return parseInt(aColor.slice(5));
  }

  p.green = function( aColor )
  {
    return parseInt(aColor.split(",")[1]);
  }

  p.blue = function( aColor )
  {
    return parseInt(aColor.split(",")[2]);
  }

  p.alpha = function( aColor )
  {
    return parseInt(aColor.split(",")[3]);
  }

  p.abs = function abs( aNumber )
  {
    return Math.abs( aNumber );
  }
  
  p.cos = function cos( aNumber )
  {
    return Math.cos( aNumber );
  }
  
  p.sin = function sin( aNumber )
  {
    return Math.sin( aNumber );
  }
  
  p.pow = function pow( aNumber, aExponent )
  {
    return Math.pow( aNumber, aExponent );
  }
  
  p.constrain = function constrain( aNumber, aMin, aMax )
  {
    return Math.min( Math.max( aNumber, aMin ), aMax );
  }
  
  p.sqrt = function sqrt( aNumber )
  {
  	return Math.sqrt( aNumber );
  }
  
  p.atan2 = function atan2( aNumber, aNumber2 )
  {
  	return Math.atan2( aNumber, aNumber2 );
  }
  
  p.radians = function radians( aAngle )
  {
    return ( aAngle / 180 ) * p.PI;
  }
  
  p.size = function size( aWidth, aHeight )
  {
    var fillStyle = curContext.fillStyle;
    var strokeStyle = curContext.strokeStyle;

    curElement.width = p.width = aWidth;
    curElement.height = p.height = aHeight;

    curContext.fillStyle = fillStyle;
    curContext.strokeStyle = strokeStyle;
  }
  
  p.noStroke = function noStroke()
  {
    doStroke = false;
  }
  
  p.noFill = function noFill()
  {
    doFill = false;
  }
  
  p.smooth = function smooth()
  {
  
  }
  
  p.noLoop = function noLoop()
  {
    doLoop = false;
  }
  
  p.fill = function fill()
  {
    doFill = true;
    curContext.fillStyle = p.color.apply( this, arguments );
  }
  
  p.stroke = function stroke()
  {
    doStroke = true;
    curContext.strokeStyle = p.color.apply( this, arguments );
  }

  p.strokeWeight = function strokeWeight( w )
  {
    curContext.lineWidth = w;
  }
  
  p.point = function point( x, y )
  {
    var oldFill = curContext.fillStyle;
    curContext.fillStyle = curContext.strokeStyle;
    curContext.fillRect( Math.round( x ), Math.round( y ), 1, 1 );
    curContext.fillStyle = oldFill;
  }

  p.get = function get( x, y )
  {
    if ( arguments.length == 0 )
    {
      var c = p.createGraphics( p.width, p.height );
      c.image( curContext, 0, 0 );
      return c;
    }

    if ( !getLoaded )
    {
      getLoaded = buildImageObject( curContext.getImageData(0, 0, p.width, p.height) );
    }

    return getLoaded.get( x, y );
  }

  p.set = function set( x, y, color )
  {
    var oldFill = curContext.fillStyle;
    curContext.fillStyle = color;
    curContext.fillRect( Math.round( x ), Math.round( y ), 1, 1 );
    curContext.fillStyle = oldFill;
  }
  
  p.arc = function arc( x, y, width, height, start, stop )
  {
    if ( width <= 0 )
      return;

    if ( curEllipseMode == p.CORNER )
    {
      x += width / 2;
      y += height / 2;
    }

    curContext.beginPath();
  
    curContext.moveTo( x, y );
    curContext.arc( x, y, curEllipseMode == p.CENTER_RADIUS ? width : width/2, start, stop, false );
    
    if ( doFill )
      curContext.fill();
      
    if ( doStroke )
      curContext.stroke();
    
    curContext.closePath();
  }
  
  p.line = function line( x1, y1, x2, y2 )
  {
    curContext.lineCap = "round";
    curContext.beginPath();
  
    curContext.moveTo( x1 || 0, y1 || 0 );
    curContext.lineTo( x2 || 0, y2 || 0 );
    
    curContext.stroke();
    
    curContext.closePath();
  }

  p.bezier = function bezier( x1, y1, x2, y2, x3, y3, x4, y4 )
  {
    curContext.lineCap = "butt";
    curContext.beginPath();
  
    curContext.moveTo( x1, y1 );
    curContext.bezierCurveTo( x2, y2, x3, y3, x4, y4 );
    
    curContext.stroke();
    
    curContext.closePath();
  }

  p.triangle = function triangle( x1, y1, x2, y2, x3, y3 )
  {
    p.beginShape();
    p.vertex( x1, y1 );
    p.vertex( x2, y2 );
    p.vertex( x3, y3 );
    p.endShape();
  }

  p.quad = function quad( x1, y1, x2, y2, x3, y3, x4, y4 )
  {
    p.beginShape();
    p.vertex( x1, y1 );
    p.vertex( x2, y2 );
    p.vertex( x3, y3 );
    p.vertex( x4, y4 );
    p.endShape();
  }
  
  p.rect = function rect( x, y, width, height )
  {
    if ( width == 0 && height == 0 )
      return;

    curContext.beginPath();
    
    var offsetStart = 0;
    var offsetEnd = 0;

    if ( curRectMode == p.CORNERS )
    {
      width -= x;
      height -= y;
    }
    
    if ( curRectMode == p.RADIUS )
    {
      width *= 2;
      height *= 2;
    }
    
    if ( curRectMode == p.CENTER || curRectMode == p.RADIUS )
    {
      x -= width / 2;
      y -= height / 2;
    }
  
    curContext.rect(
      Math.round( x ) - offsetStart,
      Math.round( y ) - offsetStart,
      Math.round( width ) + offsetEnd,
      Math.round( height ) + offsetEnd
    );
      
    if ( doFill )
      curContext.fill();
      
    if ( doStroke )
      curContext.stroke();
    
    curContext.closePath();
  }
  
  p.ellipse = function ellipse( x, y, width, height )
  {
    x = x || 0;
    y = y || 0;

    if ( width <= 0 && height <= 0 )
      return;

    curContext.beginPath();
    
    if ( curEllipseMode == p.RADIUS )
    {
      width *= 2;
      height *= 2;
    }
    
    var offsetStart = 0;
    
    // Shortcut for drawing a circle
    if ( width == height )
      curContext.arc( x - offsetStart, y - offsetStart, width / 2, 0, Math.PI * 2, false );
  
    if ( doFill )
      curContext.fill();
      
    if ( doStroke )
      curContext.stroke();
    
    curContext.closePath();
  }

  p.link = function( href, target )
  {
    window.location = href;
  }

  p.loadPixels = function()
  {
    p.pixels = buildImageObject( curContext.getImageData(0, 0, p.width, p.height) ).pixels;
  }

  p.updatePixels = function()
  {
    var colors = /(\d+),(\d+),(\d+),(\d+)/;
    var pixels = {};
    var data = pixels.data = [];
    pixels.width = p.width;
    pixels.height = p.height;

    var pos = 0;

    for ( var i = 0, l = p.pixels.length; i < l; i++ ) {
      var c = (p.pixels[i] || "rgba(0,0,0,1)").match(colors);
      data[pos] = parseInt(c[1]);
      data[pos+1] = parseInt(c[2]);
      data[pos+2] = parseInt(c[3]);
      data[pos+3] = parseFloat(c[4]) * 100;
      pos += 4;
    }

    curContext.putImageData(pixels, 0, 0);
  }

  p.extendClass = function extendClass( obj, args, fn )
  {
    if ( arguments.length == 3 )
    {
      fn.apply( obj, args );
    }
    else
    {
      args.call( obj );
    }
  }

  p.addMethod = function addMethod( object, name, fn )
  {
    if ( object[ name ] )
    {
      var args = fn.length;
      
      var oldfn = object[ name ];
      object[ name ] = function()
      {
        if ( arguments.length == args )
          return fn.apply( this, arguments );
        else
          return oldfn.apply( this, arguments );
      };
    }
    else
    {
      object[ name ] = fn;
    }
  }

  p.init = function init(code){
    p.stroke( 0 );
    p.fill( 255 );
  
    // Canvas has trouble rendering single pixel stuff on whole-pixel
    // counts, so we slightly offset it (this is super lame).
    curContext.translate( 0.5, 0.5 );

    if ( code )
    {
      (function(Processing){with (p){
        eval(parse(code, p));
      }})(p);
    }
  
    if ( p.setup )
    {
      inSetup = true;
      p.setup();
    }
    
    inSetup = false;
    
    if ( p.draw )
    {
      if ( !doLoop )
      {
        p.redraw();
      }
      else
      {
        p.loop();
      }
    }
    
    attach( curElement, "mousemove", function(e)
    {
      p.pmouseX = p.mouseX;
      p.pmouseY = p.mouseY;
      p.mouseX = e.clientX - curElement.offsetLeft;
      p.mouseY = e.clientY - curElement.offsetTop;

      if ( p.mouseMoved )
      {
        p.mouseMoved();
      }      

      if ( mousePressed && p.mouseDragged )
      {
        p.mouseDragged();
      }      
    });
    
    attach( curElement, "mousedown", function(e)
    {
      mousePressed = true;

      if ( typeof p.mousePressed == "function" )
      {
        p.mousePressed();
      }
      else
      {
        p.mousePressed = true;
      }
    });
      
    attach( curElement, "mouseup", function(e)
    {
      mousePressed = false;

      if ( typeof p.mousePressed != "function" )
      {
        p.mousePressed = false;
      }

      if ( p.mouseReleased )
      {
        p.mouseReleased();
      }
    });

    attach( document, "keydown", function(e)
    {
      keyPressed = true;

      p.key = e.keyCode + 32;

      if ( e.shiftKey )
      {
        p.key = String.fromCharCode(p.key).toUpperCase().charCodeAt(0);
      }

      if ( typeof p.keyPressed == "function" )
      {
        p.keyPressed();
      }
      else
      {
        p.keyPressed = true;
      }
    });

    attach( document, "keyup", function(e)
    {
      keyPressed = false;

      if ( typeof p.keyPressed != "function" )
      {
        p.keyPressed = false;
      }

      if ( p.keyReleased )
      {
        p.keyReleased();
      }
    });

    function attach(elem, type, fn)
    {
      if ( elem.addEventListener )
        elem.addEventListener( type, fn, false );
      else
        elem.attachEvent( "on" + type, fn );
    }
  };

  return p;
}

})();


/*********************************************
** Resizer
**
** This is a simple little object that helps
** to figure out when text size is changed
** by the browser.  It is lighter weight than the
** textresizedetector.js . . . and should
** lessen the weight of the application
**
** Resizer relies on prototype
*********************************************/
var Resizer = {
    element: false,
    height: false,
    extraHeight: false,
    handlers: [],
    interval: false,
    lineHeight: false,
    checkInterval: false,

    initialize: function() {
        var defaults = {
            checkInterval: 500,
            parentElement: document.body,
            dontStart: false,
            //in EM
            fontSize: '1.0em'
        };
        var options = (arguments[0]) ? arguments[0]:{};
        if (options.parentNode) {
            options.parentNode = $(parentNode);
        }
        Object.extend(defaults, options);
        Resizer.lineHeight = defaults.lineHeight;
        Resizer.checkInterval = defaults.checkInterval;

        Resizer.element = document.createElement('span');
        Resizer.element.className = 'padAbove';
        Resizer.element.innerHTML = '&nbsp;';
        Resizer.element.style.left = '-9999px';
        Resizer.element.style.position = 'relative';
        Resizer.element.style.margin = '0px';
        Resizer.element.style.padding = '0px';
        Resizer.element.style.fontSize = defaults.fontSize;
        if (Resizer.lineHeight) {
            Resizer.element.style.lineHeight = defaults.lineHeight;
        }

        defaults.parentElement.appendChild(Resizer.element);
        Resizer.setHeights();

        if (!defaults.dontStart) {
            this.start();
        }

        return true;
    },

    destroy: function() {
        Resizer.stop();
        Resizer.interval = false;
        Resizer.handlers = [];
        Resizer.height = false;
        Resizer.element = false;

        return true;
    },

    setEM: function(em) {
        Resizer.element.style.fontSize = em + 'em';
        return true;
    },

    setHeights: function() {
        Resizer.height = Resizer.element.offsetHeight;
        Resizer.element.innerHTML += '<br />&nbsp;';
        Resizer.extraHeight = Resizer.element.offsetHeight - 2*Resizer.height;
        Resizer.element.innerHTML = '&nbsp;';
    },

    start: function() {
        Resizer.height = Resizer.element.offsetHeight;
        Resizer.interval = window.setInterval(Resizer.checkHeight, Resizer.checkInterval);
    },

    stop: function() {
        window.clearInterval(Resizer.interval);
    },

    register: function(handler) {
        Resizer.handlers.push(handler);

        return true;
    },

    unregister: function(handler) {
        var newHandlers = [];
        for (var i=0;i<Resizer.handlers.length;i++) {
            if (Resizer.handlers[i] != handler) {
                newHandlers.push(Resizer.handlers[i]);
            }
        }
        Resizer.handlers = newHandlers;

        return true;
    },

    checkHeight: function() {
        if (Resizer.element.offsetHeight != Resizer.height) {
            var resizer = Resizer._buildResponse(Resizer.element.offsetHeight);
            Resizer.setHeights();
            for (var i=0;i<Resizer.handlers.length;i++) {
                Resizer.handlers[i](resizer);
            }
        }
    },

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

    getExtraHeight: function() {
        return this.extraHeight;
    },

    //These functions take the object returned by a resizer event handler and return usefull information
    //Kind of like Event.element(event)
    getOldHeight: function(resizer) {
        if (resizer.oldHeight) {
            return resizer.oldHeight;
        } else {
            return false;
        }
    },

    getNewHeight: function(resizer) {
        if (resizer.newHeight) {
            return resizer.newHeight;
        } else {
            return false;
        }
    },
    
	updateLineHeight: function(lineHeight) {
        if (lineHeight != Resizer.lineHeight) {
            Resizer.lineHeight = lineHeight;
            Resizer.element.style.lineHeight = lineHeight;
        }

        return true;
    },

    changeParent: function(newParent) {
        $(newParent).appendChild(this.element);
        return true;
    },

    _buildResponse: function(newHeight) {
        var response = {
            newHeight: newHeight,
            oldHeight: Resizer.height
        };

        return response;
    }

};


// script.aculo.us scriptaculous.js v1.8.1, Thu Jan 03 22:07:12 -0500 2008

// Copyright (c) 2005-2007 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.1',
  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',
  load: function() {
    function convertVersionString(versionString){
      var r = versionString.split('.');
      return parseInt(r[0])*100000 + parseInt(r[1])*1000 + parseInt(r[2]);
    }
 
    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);
    
    $A(document.getElementsByTagName("script")).findAll( function(s) {
      return (s.src && s.src.match(/scriptaculous\.js(\?.*)?$/))
    }).each( function(s) {
      var path = s.src.replace(/scriptaculous\.js(\?.*)?$/,'');
      var 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();

// script.aculo.us slider.js v1.8.1, Thu Jan 03 22:07:12 -0500 2008

// Copyright (c) 2005-2007 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;
  }
});


/***************************************************
** sliderule.js
**
** relies on prototype.js and scriptaculous effects.js
** Design Kitchen
** @author: Matthew Story
****************************************************/
var SlideRule = Class.create();
SlideRule.prototype = {
	showBox: false,
	slider: false,
	transition: false,
	onPageChange: false,
	elementByElement: false,
	
	//these attributes handle the behavior for easing on mouse hold
	clickMove: true,
	startSpeed: 1200,
	maxSpeed: 400,
	currentSpeed: false,
	//this boolean is true for moving foreward, false for moving backwards
	direction: false,
	slideTimeout: false,
	
	//this is a scriptaculous transition effect . . . the real effect not the name:
	//e.g. Effect.Transitions.slowstop
	transitionEffect: false,
	
	/*the constructor takes 2 arguments and an optional 3rd
	 * 1. the viewer/showBox element, this is the outer element
	 * 2. the element that will be sliding to and fro inside the viewer/showBox
	 * 3. (optional) options object with the following options:
	 * 	- pageForeward: element/element id or array of elements/element ids that on click go foreward a page
	 * 	- pageBackward: element/element id or array of elements/element ids that on click go backward a page
	 * 	- transition: boolean, weather or not to use a scriptaculous effect to slide
	 * 	- transitionEffect: class constructor of a scriptaculous transition effect, to define a custom transition
	 * 	- elementByElement: boolean, sides one element at a time if true, one slide viewer width if false
	 * 	- clickMove: boolean, if true moves on click, false, moves on mousedown
	 * 	- startSpeed: length of transition at start (in MS)
	 * 	- maxSpeed: length of transition at max (in MS)
	 * 	- onPageChange: function to be called after a page change (usefull for enabling/disabling nav
	*/
	initialize: function(showBox, slider) {
		var defaults = {
			pageForeward: false,
			pageBackward: false,
			transition: false,
			transitionEffect: false,
			elementByElement: false,
			clickMove: true,
			startSpeed: 1200,
			maxSpeed: 400,
			onPageChange: function() {}
		};
		var options = (arguments[2]) ? arguments[2]:{};
		Object.extend(defaults, options);
		defaults.pageForeward = this.toArray(defaults.pageForeward);
		defaults.pageBackward = this.toArray(defaults.pageBackward);
		defaults.transition = (defaults.transitionEffect) ? true:defaults.transition;

		//set up the sliderule
		this.showBox = $(showBox);
		this.slider = $(slider);
		this.listeners = [];

		//lets hard set the slider width if it's not
		if (!this.slider.style.width) {
			this.hardWidthifySlider();
		}

		this.clip(this.showBox);
		this.relativize(this.slider);
		this.position(this.slider);
		
		//set up effect
		this.transition = defaults.transition;
		this.transitionEffect = defaults.transitionEffect;
		this.onPageChange = defaults.onPageChange;

		//set up how the transition occurs
		this.elementByElement = defaults.elementByElement;
		this.clickMove = defaults.clickMove;
		this.startSpeed = defaults.startSpeed;
		this.maxSpeed = (defaults.maxSpeed >= 0) ? defaults.maxSpeed:1;
		
		//set up listeners
		for (var i=0;i<defaults.pageForeward.length;i++) {
			this.registerPageForewardListener(defaults.pageForeward[i]);
		}
		for (var i=0;i<defaults.pageBackward.length;i++) {
			this.registerPageBackwardListener(defaults.pageBackward[i]);
		}
		
		//turn to first page
		this.goToPage(1);
	},

	destroy: function() {
		this.showBox = false;
		this.slider = false;
		this.destroyListeners();
		this.transition = false;
		this.transitionEffect = false;

		return true;
	},
	
	getNumPages: function() {
		var pageNum = (this.elementByElement) ? this._getNumElements():this._getNumPages;
		return pageNum;
	},
	
	getCurrentPage: function() {
		var currentPage = (this.elementByElement) ? this._getCurrentElement():this._getCurrentPage();
		return currentPage;
	},
	
	getElementOffset: function(elementIndex) {
		return this.slider.childElements()[elementIndex-1].offsetWidth;
	},

	turnPage: function() {
		this.direction = true;
		this.goToPage(this.getCurrentPage() + 1);
		return true;
	},

	turnBackPage: function() {
		this.direction = false;
		this.goToPage(Math.max((this.getCurrentPage() - 1), 1));
		return true;
	},
	
	startSliding: function(direction) {
		if (this.slideInProgress) {
			return false;
		}
		this.direction = direction;
		this.currentSpeed = this.startSpeed;
		this.slide();
	},

	stopSliding: function() {
		this.direction = false;
		this.currentSpeed = false;

		return true;
	},
	
	afterFinish: function() {
		this.slideInProgress = false;
		this.slide();
	},

	slide: function() {
		if (this.currentSpeed) {
			var pageNum = (this.direction) ? this.getCurrentPage()+1:this.getCurrentPage()-1;
			this.goToPage(pageNum, {afterFinish: this.afterFinish.bindAsEventListener(this)});
			//this.slideTimeout = window.setTimeout(this.slide.bindAsEventListener(this), this.currentSpeed+10);
			this.currentSpeed = (this.currentSpeed-100 > this.maxSpeed) ? this.currentSpeed-100:this.maxSpeed;
		} else {
			this.onPageChange(this.getCurrentPage(), this);
		}

		return true;
	},

	goToPage: function(pageNum) {
		if (this.elementByElement) {
			this._goToElement(pageNum, arguments[1]);
		} else {
			this._goToPage(pageNum);
		}

		this.onPageChange(pageNum+1, this);
	},

	bindEventToPage: function(page, element) {
		var event = (arguments[2]) ? arguments[2]:'click';
		var listener = this.goToPage.bindAsEventListener(this);
		this.registerListener(element, event, function() {listener(page);});

		return true;
	},
	
	registerPageForewardListener: function(element) {
		if (this.clickMove) {
			var event = (arguments[1]) ? arguments[1]:'click';
			this.registerListener(element, event, this.turnPage.bindAsEventListener(this));
		} else {
			var mouseDown = function() {
				this.startSliding(true);
			};
			this.registerListener(element, 'mousedown', mouseDown.bindAsEventListener(this));
			this.registerListener(element, 'mouseup', this.stopSliding.bindAsEventListener(this));
		}

		return true;
	},

	registerPageBackwardListener: function(element) {
		if (this.clickMove) {
			var event = (arguments[1]) ? arguments[1]:'click';
			this.registerListener(element, event, this.turnBackPage.bindAsEventListener(this));
		} else {
			var mouseDown = function() {
				this.startSliding(false);
			};
			this.registerListener(element, 'mousedown', mouseDown.bindAsEventListener(this));
			this.registerListener(element, 'mouseup', this.stopSliding.bindAsEventListener(this));
		}

		return true;
	},

	registerListener: function(element, event, handler) {
		this.listeners.push({
			element: element,
			event: event,
			handler: handler
		});
		Event.observe(element, event, handler);
	},
	
	destroyListener: function(element, event, handler) {
		var newListeners = [];
		for (var i=0;i<this.listeners.length;i++) {
			if (this.listeners[i] == {element: element, event: event, handler: handler}) {
				Event.stopObserving(this.listeners[i].element, this.listeners[i].event, this.listeners[i].handler);
			} else {
				newListeners.push(this.listeners[i]);
			}
		}
		this.listeners = newListeners;

		return true;
	},
	
	destroyListeners: function() {
		for (var i=0;i<this.listeners.length;i++) {
			Event.stopObserving(this.listeners[i].element, this.listeners[i].event, this.listeners[i].handler);
		}
		this.listeners = [];
	},

	clip: function(element) {
		element.style.overflow = 'hidden';
		return true;
	},

	unclip: function(element) {
		element.style.overflow = '';
		return true;
	},

	relativize: function(element) {
		element.style.position = 'relative';
		return true;
	},

	unrelativize: function(element) {
		element.style.position = '';
		return true;
	},

	position: function(element) {
		var offsetRight = (arguments[1]) ? arguments[1]:0;
		element.style.left = offsetRight + 'px';

		return true;
	},

	unposition: function(element) {
		element.style.right = '';
		return true;
	},
	
	hardWidthifySlider: function() {
		this.slider.style.position = 'absolute';
		this.slider.style.width = this.slider.offsetWidth + 'px';
		this.relativize(this.slider);
		this.showBox.appendChild(this.slider);
	},

	toArray: function(value) {
		value = (value && value.constructor != Array) ? [value]:value;
		return value;
	},

    _goToPage: function(pageNum) {
        pageNum = (pageNum > this.getNumPages()) ? this.getNumPages()-1:pageNum-1;
        if (!this.transition) {
            this._move(this.showBox.clientWidth*pageNum*-1);
        } else if (this.pageNum != this.getCurrentPage()-1) {
            this._transitionMove((this.getCurrentPage()-pageNum-1)*this.showBox.clientWidth);
        }

        return true;
    },

    _goToElement: function(elementNum) {
		elementNum = (elementNum > this.getNumPages()) ? this.getNumPages()-1:elementNum-1;
		if (!this.transition && elementNum != this.getCurrentPage()-1) {
			var offset = parseInt(this.slider.style.left);
			offset += (elementNum > this.getCurrentPage()-1) ? this.getElementOffset(elementNum+1)*-1:this.getElementOffset(elementNum+1);
			this._move(offset);
		} else if (this.getCurrentPage() && elementNum != this.getCurrentPage()-1 && elementNum > 0) {
			var offset = (elementNum > this.getCurrentPage()-1) ? this.getElementOffset(elementNum+1)*-1:this.getElementOffset(elementNum+1);
			this._transitionMove(offset, arguments[1]);
		}

		return true;
    },

	_move: function(newPosition) {
		this.position(this.slider, newPosition);
		return true;
	},

	_transitionMove: function(moveBy) {
		var options = Object.extend((this.transitionEffect) ? {transition: this.transitionEffect}:{}, arguments[1] || {});
		this.slideInProgress = true;
		if (this.currentSpeed) {
			options.duration = this.currentSpeed/1000;
		}
		new Effect.MoveBy(this.slider, 0, moveBy, options);

		return true;
	},
	
	_getNumPages: function() {
    	return Math.floor(this.slider.offsetWidth/this.showBox.clientWidth)+1;
	},

	_getNumElements: function() {
		return this.slider.childElements().length;
	},
    
	_getCurrentPage: function() {
		return Math.floor(Math.abs(parseInt(this.slider.style.left)/this.showBox.clientWidth) + 1);
	},

	_getCurrentElement: function() {
		var children = this.slider.childElements();
		//if the slider hasn't moved . . . it's the first element
		if (parseInt(this.slider.style.left) == 0) {
			return 1;
		}
		
		//otherwise we go and find where the last visable element is
		for (var i=0;i<children.length;i++) {
			if (children[i].offsetLeft == Math.abs(parseInt(this.slider.style.left)) + this.showBox.clientWidth) {
				return i-1;
			}
		}
		if (Math.abs(parseInt(this.slider.style.left)) + this.showBox.clientWidth >= children[children.length-1].offsetLeft) {
			return children.length;
		}

		return false;
	}
};


var SortableTable = Class.create({
	initialize: function(table, columns, rows) {
		this.table = $(table).getElementsByTagName('tbody')[0] || $(table);
		this.columns = [];
		for (var i=0;i<columns.length;i++) {
			this.register(columns[i]);
		}
		this.rows = rows;
		this.options = Object.extend({
			firstSort: false,
			sortOnStart: true,
			onSort: function() {}
		}, arguments[3] || {});
		if (this.options.sortOnStart) {
			this.sort(this.getColumnByName(this.options.firstSort) || this.columns[0]);
		}
	},
	
	getColumnByName: function(name) {
		for (var i=0;i<this.columns.length;i++) {
			if (this.columns[i].name == name) {
				return this.columns[i];
			}
		}
		return false;
	},

	register: function(column) {
		if (column.options.sortElement) {
			this.registerColumnListener(column, column.options.sortElement);
		}
		this.columns.push(column);
	},

	registerColumnListener: function(column, element) {
		Event.observe(element, 'click', function(event) {
			this.sort(column, event);
		}.bind(this));
	},
	
	buildTable: function() {
		for (var i=0;i<this.rows.length;i++) {
			this.table.appendChild(this.rows[i].tr);
		}
	},
	
	clearTable: function() {
		for (var i=0;i<this.rows.length;i++) {
			if (this.rows[i].tr.parentNode) {
				this.rows[i].tr.parentNode.removeChild(this.rows[i].tr);
			}
		}
	},

	sort: function(column, event) {
		if (column == this.currentColumn) {
			this.direction = (this.direction == 'reverse') ? 'forward':'reverse';
		} else {
			this.direction = (column.options.reverse) ? 'reverse':'forward';
		}
		this.clearTable();
		this.currentColumn = column;
		this.rows.sort(this._sort.bind(this));
		this.buildTable();
		this.options.onSort(column, this.direction);
	},

	_sort: function(a, b, column, direction) {
		column = column || this.currentColumn;
		direction = direction || this.direction;
		var aData = (a.data[column.name]+'').toLowerCase();
		var bData = (b.data[column.name]+'').toLowerCase();
		if (column.type == 'string') {
			if (this.direction == 'reverse') {
				var compare = (!aData.length) ? -1:(aData == bData) ? 0:(aData < bData) ? 1:-1;
			} else {
				var compare = (!aData.length) ? 1:(aData == bData) ? 0:(aData > bData) ? 1:-1;
			}
		} else {
			var compare = (this.direction == 'reverse') ? (bData.replace(/[^0-9.]/g, '') - aData.replace(/[^0-9.]/g, '')):(aData.replace(/[^0-9.]/g, '') - bData.replace(/[^0-9.]/g, ''));
		}
		
		return (compare == 0 && column.options.subSort) ? this._sort(a,b,this.getColumnByName(column.options.subSort), (this.getColumnByName(column.options.subSort).options.reverse ? ((this.direction == 'reverse') ? 'forward':'reverse'):this.direction)):compare;
	}
});

SortableTable.Column = Class.create({
	//type is one of: number, string
	initialize: function(name, type) {
		this.name = name;
		this.type = type;
		this.options = Object.extend({
			sortElement: false,
			subSort: false,
			reverse: false
		}, arguments[2] || {});
	}
});

SortableTable.Row = Class.create({
	//data is a set of name value pairs, where the name is the name of the column
	initialize: function(data, tr) {
		this.data = data;
		this.tr = $(tr);
	}
});


/****************************
** truncate.js
**
** relies on prototype.js v.1.6
** @author: Matthew Story
****************************/
var Truncate = Class.create({
	initialize: function(bounding, elements) {
		this.bounding = $(bounding);
		this.elements = this.sort($A(elements));
		this.options = Object.extend({
			equalize: true,
			charTruncate: false,
			extraSpace: 0,
			//extraCharSpace and ellipsificate work with charTruncate only
			extraCharSpace: 0,
			ellipsificate: false,

			onTruncate: function() {}
		}, arguments[2] || {});
		this.checkTruncate();
	},

	checkTruncate: function(elements) {
		elements = elements || this.elements;
		this.bounding.style.overflow = 'hidden';
		if (this.checkWidth(elements)) {
			if (this.options.equalize) {
				this.equalize(elements);
			}
		} else {
			this.truncate(elements, this.bounding.clientWidth-this.options.extraSpace);
		}
	},

	truncate: function(elements, width) {
		elements = elements || this.elements;
		//var last = elements.shift();
		this.widthify(elements, width, this.onTruncate.bind(this));
	},

	sort: function(elements, reverse) {
		elements = elements || this.elements;
		return (reverse) ? elements.sort(function(a,b) {return b.offsetWidth-a.offsetWidth;}):elements.sort(function(a,b) {return a.offsetWidth-b.offsetWidth;})
	},

	equalize: function(elements, width) {
		elements = elements || this.elements;
		width = width || this.bounding.clientWidth-this.options.extraSpace;
		elements = this.sort(elements, true);
		if (elements[0].offsetWidth < width/elements.length) {
			this.widthify(elements, width);
			return true;
		} else {
			var last = elements.shift();
			this.equalize(elements, width-last.offsetWidth);
		}
	},
	
	widthify: function(elements, width) {
        elements = elements || this.elements;
        width = width || this.bounding.clientWidth-this.options.extraSpace;
        var numElements = elements.length;
		var onWidthify = (arguments[2]) ? arguments[2]:function() {};
        for (var i=0;i<elements.length;i++) {
            if (this.options.charTruncate) {
				elements[i].style.width = '';
				this.binaryTrim(Math.floor(width/numElements)-this.options.extraCharSpace-((this.options.ellipsificate) ? this.getStringWidth(elements[i], '...'):0), elements[i]);
				elements[i].innerHTML += (this.options.ellipsificate) ? '...':'';
			}
			//first we set width
			elements[i].style.width = Math.floor(width/numElements)+'px';
			//we do this so that we don't have any trailing space due to the Floor
			width -= parseInt(elements[i].style.width);			
			//see if it's actually the right width, if not adjust
			elements[i].style.width = (parseInt(elements[i].style.width)-(elements[i].offsetWidth-parseInt(elements[i].style.width)))+'px';
            numElements--;
			onWidthify(elements[i]);
        }
	},
   	
	//low, high, margin are optional 
	binaryTrim: function(width, elem, low, high, margin) {
		//set args if not set, this should only happen the first time
		high = high || elem.innerHTML.length+1;
		low = low || 0;

		//get the original contents	
		var inner = elem.innerHTML;
		
		//if no margin set set margin to widest charecter in font set 'W'
		margin = margin || this.getStringWidth(elem, 'W');
		
		//if the width is out of range
		if (width > elem.offsetWidth || high < low) {
			return false;
		} else if (width < margin) {
			elem.innerHTML = '';
			return false;
		}
		
		//go half way there every time
		var mid = Math.floor((high-low)/2+low);
		elem.innerHTML = inner.substring(mid, mid*-1);
		var elemWidth = elem.offsetWidth;
		elem.innerHTML = inner;

		//within an acceptable margin (is it equal enough)
		if (elemWidth <= width && elemWidth > width-margin) {
			elem.innerHTML = inner.substring(mid, mid*-1);
			return true;
		//if too short -- we go up
		} else if (elemWidth < width) {
			return this.binaryTrim(width, elem, mid+1, high, margin);
		//if too long -- we go down
		} else {
			return this.binaryTrim(width, elem, low, mid-1, margin);
		}
    },
	
	getStringWidth: function(elem, str) {
		var inner = elem.innerHTML;
		this.stripStyles(elem);
		elem.innerHTML = str;
		var margin = elem.offsetWidth;
		this.reinstateStyles(elem);
		elem.innerHTML = inner;	
		
		return margin;
	},
	
	stripStyles: function(element) {
		this.oldPadding = element.style.padding;
		element.style.padding = '0px';
		this.oldMargin = element.style.margin;
		element.style.margin = '0px';
		this.oldBorder = element.style.border;
		element.style.border = '0px';
	},

	reinstateStyles: function(element) {
		element.style.padding = this.oldPadding;
		this.oldPadding = null;
		element.style.margin = this.oldMargin;
		this.oldMargin = null;
		element.style.border = this.oldBorder;
		this.oldBorder = null;
	},

	onTruncate: function(element) {
		element.style.overflow = 'hidden';
		this.options.onTruncate(element);
	},

	checkWidth: function(elements) {
		elements = elements || this.elements;
		var totalWidth = 0;
		for (var i=0;i<elements.length;i++) {
			totalWidth += $(elements[i]).offsetWidth;
		}
		return totalWidth <= this.bounding.clientWidth;
	}
});


// script.aculo.us unittest.js v1.8.1, Thu Jan 03 22:07:12 -0500 2008

// Copyright (c) 2005-2007 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us)
//           (c) 2005-2007 Jon Tirsen (http://www.tirsen.com)
//           (c) 2005-2007 Michael Schuerig (http://www.schuerig.de/michael/)
//
// 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/

// experimental, Firefox-only
Event.simulateMouse = function(element, eventName) {
  var options = Object.extend({
    pointerX: 0,
    pointerY: 0,
    buttons:  0,
    ctrlKey:  false,
    altKey:   false,
    shiftKey: false,
    metaKey:  false
  }, arguments[2] || {});
  var oEvent = document.createEvent("MouseEvents");
  oEvent.initMouseEvent(eventName, true, true, document.defaultView, 
    options.buttons, options.pointerX, options.pointerY, options.pointerX, options.pointerY, 
    options.ctrlKey, options.altKey, options.shiftKey, options.metaKey, 0, $(element));
  
  if(this.mark) Element.remove(this.mark);
  this.mark = document.createElement('div');
  this.mark.appendChild(document.createTextNode(" "));
  document.body.appendChild(this.mark);
  this.mark.style.position = 'absolute';
  this.mark.style.top = options.pointerY + "px";
  this.mark.style.left = options.pointerX + "px";
  this.mark.style.width = "5px";
  this.mark.style.height = "5px;";
  this.mark.style.borderTop = "1px solid red;"
  this.mark.style.borderLeft = "1px solid red;"
  
  if(this.step)
    alert('['+new Date().getTime().toString()+'] '+eventName+'/'+Test.Unit.inspect(options));
  
  $(element).dispatchEvent(oEvent);
};

// Note: Due to a fix in Firefox 1.0.5/6 that probably fixed "too much", this doesn't work in 1.0.6 or DP2.
// You need to downgrade to 1.0.4 for now to get this working
// See https://bugzilla.mozilla.org/show_bug.cgi?id=289940 for the fix that fixed too much
Event.simulateKey = function(element, eventName) {
  var options = Object.extend({
    ctrlKey: false,
    altKey: false,
    shiftKey: false,
    metaKey: false,
    keyCode: 0,
    charCode: 0
  }, arguments[2] || {});

  var oEvent = document.createEvent("KeyEvents");
  oEvent.initKeyEvent(eventName, true, true, window, 
    options.ctrlKey, options.altKey, options.shiftKey, options.metaKey,
    options.keyCode, options.charCode );
  $(element).dispatchEvent(oEvent);
};

Event.simulateKeys = function(element, command) {
  for(var i=0; i<command.length; i++) {
    Event.simulateKey(element,'keypress',{charCode:command.charCodeAt(i)});
  }
};

var Test = {}
Test.Unit = {};

// security exception workaround
Test.Unit.inspect = Object.inspect;

Test.Unit.Logger = Class.create();
Test.Unit.Logger.prototype = {
  initialize: function(log) {
    this.log = $(log);
    if (this.log) {
      this._createLogTable();
    }
  },
  start: function(testName) {
    if (!this.log) return;
    this.testName = testName;
    this.lastLogLine = document.createElement('tr');
    this.statusCell = document.createElement('td');
    this.nameCell = document.createElement('td');
    this.nameCell.className = "nameCell";
    this.nameCell.appendChild(document.createTextNode(testName));
    this.messageCell = document.createElement('td');
    this.lastLogLine.appendChild(this.statusCell);
    this.lastLogLine.appendChild(this.nameCell);
    this.lastLogLine.appendChild(this.messageCell);
    this.loglines.appendChild(this.lastLogLine);
  },
  finish: function(status, summary) {
    if (!this.log) return;
    this.lastLogLine.className = status;
    this.statusCell.innerHTML = status;
    this.messageCell.innerHTML = this._toHTML(summary);
    this.addLinksToResults();
  },
  message: function(message) {
    if (!this.log) return;
    this.messageCell.innerHTML = this._toHTML(message);
  },
  summary: function(summary) {
    if (!this.log) return;
    this.logsummary.innerHTML = this._toHTML(summary);
  },
  _createLogTable: function() {
    this.log.innerHTML =
    '<div id="logsummary"></div>' +
    '<table id="logtable">' +
    '<thead><tr><th>Status</th><th>Test</th><th>Message</th></tr></thead>' +
    '<tbody id="loglines"></tbody>' +
    '</table>';
    this.logsummary = $('logsummary')
    this.loglines = $('loglines');
  },
  _toHTML: function(txt) {
    return txt.escapeHTML().replace(/\n/g,"<br/>");
  },
  addLinksToResults: function(){ 
    $$("tr.failed .nameCell").each( function(td){ // todo: limit to children of this.log
      td.title = "Run only this test"
      Event.observe(td, 'click', function(){ window.location.search = "?tests=" + td.innerHTML;});
    });
    $$("tr.passed .nameCell").each( function(td){ // todo: limit to children of this.log
      td.title = "Run all tests"
      Event.observe(td, 'click', function(){ window.location.search = "";});
    });
  }
}

Test.Unit.Runner = Class.create();
Test.Unit.Runner.prototype = {
  initialize: function(testcases) {
    this.options = Object.extend({
      testLog: 'testlog'
    }, arguments[1] || {});
    this.options.resultsURL = this.parseResultsURLQueryParameter();
    this.options.tests      = this.parseTestsQueryParameter();
    if (this.options.testLog) {
      this.options.testLog = $(this.options.testLog) || null;
    }
    if(this.options.tests) {
      this.tests = [];
      for(var i = 0; i < this.options.tests.length; i++) {
        if(/^test/.test(this.options.tests[i])) {
          this.tests.push(new Test.Unit.Testcase(this.options.tests[i], testcases[this.options.tests[i]], testcases["setup"], testcases["teardown"]));
        }
      }
    } else {
      if (this.options.test) {
        this.tests = [new Test.Unit.Testcase(this.options.test, testcases[this.options.test], testcases["setup"], testcases["teardown"])];
      } else {
        this.tests = [];
        for(var testcase in testcases) {
          if(/^test/.test(testcase)) {
            this.tests.push(
               new Test.Unit.Testcase(
                 this.options.context ? ' -> ' + this.options.titles[testcase] : testcase, 
                 testcases[testcase], testcases["setup"], testcases["teardown"]
               ));
          }
        }
      }
    }
    this.currentTest = 0;
    this.logger = new Test.Unit.Logger(this.options.testLog);
    setTimeout(this.runTests.bind(this), 1000);
  },
  parseResultsURLQueryParameter: function() {
    return window.location.search.parseQuery()["resultsURL"];
  },
  parseTestsQueryParameter: function(){
    if (window.location.search.parseQuery()["tests"]){
        return window.location.search.parseQuery()["tests"].split(',');
    };
  },
  // Returns:
  //  "ERROR" if there was an error,
  //  "FAILURE" if there was a failure, or
  //  "SUCCESS" if there was neither
  getResult: function() {
    var hasFailure = false;
    for(var i=0;i<this.tests.length;i++) {
      if (this.tests[i].errors > 0) {
        return "ERROR";
      }
      if (this.tests[i].failures > 0) {
        hasFailure = true;
      }
    }
    if (hasFailure) {
      return "FAILURE";
    } else {
      return "SUCCESS";
    }
  },
  postResults: function() {
    if (this.options.resultsURL) {
      new Ajax.Request(this.options.resultsURL, 
        { method: 'get', parameters: 'result=' + this.getResult(), asynchronous: false });
    }
  },
  runTests: function() {
    var test = this.tests[this.currentTest];
    if (!test) {
      // finished!
      this.postResults();
      this.logger.summary(this.summary());
      return;
    }
    if(!test.isWaiting) {
      this.logger.start(test.name);
    }
    test.run();
    if(test.isWaiting) {
      this.logger.message("Waiting for " + test.timeToWait + "ms");
      setTimeout(this.runTests.bind(this), test.timeToWait || 1000);
    } else {
      this.logger.finish(test.status(), test.summary());
      this.currentTest++;
      // tail recursive, hopefully the browser will skip the stackframe
      this.runTests();
    }
  },
  summary: function() {
    var assertions = 0;
    var failures = 0;
    var errors = 0;
    var messages = [];
    for(var i=0;i<this.tests.length;i++) {
      assertions +=   this.tests[i].assertions;
      failures   +=   this.tests[i].failures;
      errors     +=   this.tests[i].errors;
    }
    return (
      (this.options.context ? this.options.context + ': ': '') + 
      this.tests.length + " tests, " + 
      assertions + " assertions, " + 
      failures   + " failures, " +
      errors     + " errors");
  }
}

Test.Unit.Assertions = Class.create();
Test.Unit.Assertions.prototype = {
  initialize: function() {
    this.assertions = 0;
    this.failures   = 0;
    this.errors     = 0;
    this.messages   = [];
  },
  summary: function() {
    return (
      this.assertions + " assertions, " + 
      this.failures   + " failures, " +
      this.errors     + " errors" + "\n" +
      this.messages.join("\n"));
  },
  pass: function() {
    this.assertions++;
  },
  fail: function(message) {
    this.failures++;
    this.messages.push("Failure: " + message);
  },
  info: function(message) {
    this.messages.push("Info: " + message);
  },
  error: function(error) {
    this.errors++;
    this.messages.push(error.name + ": "+ error.message + "(" + Test.Unit.inspect(error) +")");
  },
  status: function() {
    if (this.failures > 0) return 'failed';
    if (this.errors > 0) return 'error';
    return 'passed';
  },
  assert: function(expression) {
    var message = arguments[1] || 'assert: got "' + Test.Unit.inspect(expression) + '"';
    try { expression ? this.pass() : 
      this.fail(message); }
    catch(e) { this.error(e); }
  },
  assertEqual: function(expected, actual) {
    var message = arguments[2] || "assertEqual";
    try { (expected == actual) ? this.pass() :
      this.fail(message + ': expected "' + Test.Unit.inspect(expected) + 
        '", actual "' + Test.Unit.inspect(actual) + '"'); }
    catch(e) { this.error(e); }
  },
  assertInspect: function(expected, actual) {
    var message = arguments[2] || "assertInspect";
    try { (expected == actual.inspect()) ? this.pass() :
      this.fail(message + ': expected "' + Test.Unit.inspect(expected) + 
        '", actual "' + Test.Unit.inspect(actual) + '"'); }
    catch(e) { this.error(e); }
  },
  assertEnumEqual: function(expected, actual) {
    var message = arguments[2] || "assertEnumEqual";
    try { $A(expected).length == $A(actual).length && 
      expected.zip(actual).all(function(pair) { return pair[0] == pair[1] }) ?
        this.pass() : this.fail(message + ': expected ' + Test.Unit.inspect(expected) + 
          ', actual ' + Test.Unit.inspect(actual)); }
    catch(e) { this.error(e); }
  },
  assertNotEqual: function(expected, actual) {
    var message = arguments[2] || "assertNotEqual";
    try { (expected != actual) ? this.pass() : 
      this.fail(message + ': got "' + Test.Unit.inspect(actual) + '"'); }
    catch(e) { this.error(e); }
  },
  assertIdentical: function(expected, actual) { 
    var message = arguments[2] || "assertIdentical"; 
    try { (expected === actual) ? this.pass() : 
      this.fail(message + ': expected "' + Test.Unit.inspect(expected) +  
        '", actual "' + Test.Unit.inspect(actual) + '"'); } 
    catch(e) { this.error(e); } 
  },
  assertNotIdentical: function(expected, actual) { 
    var message = arguments[2] || "assertNotIdentical"; 
    try { !(expected === actual) ? this.pass() : 
      this.fail(message + ': expected "' + Test.Unit.inspect(expected) +  
        '", actual "' + Test.Unit.inspect(actual) + '"'); } 
    catch(e) { this.error(e); } 
  },
  assertNull: function(obj) {
    var message = arguments[1] || 'assertNull'
    try { (obj==null) ? this.pass() : 
      this.fail(message + ': got "' + Test.Unit.inspect(obj) + '"'); }
    catch(e) { this.error(e); }
  },
  assertMatch: function(expected, actual) {
    var message = arguments[2] || 'assertMatch';
    var regex = new RegExp(expected);
    try { (regex.exec(actual)) ? this.pass() :
      this.fail(message + ' : regex: "' +  Test.Unit.inspect(expected) + ' did not match: ' + Test.Unit.inspect(actual) + '"'); }
    catch(e) { this.error(e); }
  },
  assertHidden: function(element) {
    var message = arguments[1] || 'assertHidden';
    this.assertEqual("none", element.style.display, message);
  },
  assertNotNull: function(object) {
    var message = arguments[1] || 'assertNotNull';
    this.assert(object != null, message);
  },
  assertType: function(expected, actual) {
    var message = arguments[2] || 'assertType';
    try { 
      (actual.constructor == expected) ? this.pass() : 
      this.fail(message + ': expected "' + Test.Unit.inspect(expected) +  
        '", actual "' + (actual.constructor) + '"'); }
    catch(e) { this.error(e); }
  },
  assertNotOfType: function(expected, actual) {
    var message = arguments[2] || 'assertNotOfType';
    try { 
      (actual.constructor != expected) ? this.pass() : 
      this.fail(message + ': expected "' + Test.Unit.inspect(expected) +  
        '", actual "' + (actual.constructor) + '"'); }
    catch(e) { this.error(e); }
  },
  assertInstanceOf: function(expected, actual) {
    var message = arguments[2] || 'assertInstanceOf';
    try { 
      (actual instanceof expected) ? this.pass() : 
      this.fail(message + ": object was not an instance of the expected type"); }
    catch(e) { this.error(e); } 
  },
  assertNotInstanceOf: function(expected, actual) {
    var message = arguments[2] || 'assertNotInstanceOf';
    try { 
      !(actual instanceof expected) ? this.pass() : 
      this.fail(message + ": object was an instance of the not expected type"); }
    catch(e) { this.error(e); } 
  },
  assertRespondsTo: function(method, obj) {
    var message = arguments[2] || 'assertRespondsTo';
    try {
      (obj[method] && typeof obj[method] == 'function') ? this.pass() : 
      this.fail(message + ": object doesn't respond to [" + method + "]"); }
    catch(e) { this.error(e); }
  },
  assertReturnsTrue: function(method, obj) {
    var message = arguments[2] || 'assertReturnsTrue';
    try {
      var m = obj[method];
      if(!m) m = obj['is'+method.charAt(0).toUpperCase()+method.slice(1)];
      m() ? this.pass() : 
      this.fail(message + ": method returned false"); }
    catch(e) { this.error(e); }
  },
  assertReturnsFalse: function(method, obj) {
    var message = arguments[2] || 'assertReturnsFalse';
    try {
      var m = obj[method];
      if(!m) m = obj['is'+method.charAt(0).toUpperCase()+method.slice(1)];
      !m() ? this.pass() : 
      this.fail(message + ": method returned true"); }
    catch(e) { this.error(e); }
  },
  assertRaise: function(exceptionName, method) {
    var message = arguments[2] || 'assertRaise';
    try { 
      method();
      this.fail(message + ": exception expected but none was raised"); }
    catch(e) {
      ((exceptionName == null) || (e.name==exceptionName)) ? this.pass() : this.error(e); 
    }
  },
  assertElementsMatch: function() {
    var expressions = $A(arguments), elements = $A(expressions.shift());
    if (elements.length != expressions.length) {
      this.fail('assertElementsMatch: size mismatch: ' + elements.length + ' elements, ' + expressions.length + ' expressions');
      return false;
    }
    elements.zip(expressions).all(function(pair, index) {
      var element = $(pair.first()), expression = pair.last();
      if (element.match(expression)) return true;
      this.fail('assertElementsMatch: (in index ' + index + ') expected ' + expression.inspect() + ' but got ' + element.inspect());
    }.bind(this)) && this.pass();
  },
  assertElementMatches: function(element, expression) {
    this.assertElementsMatch([element], expression);
  },
  benchmark: function(operation, iterations) {
    var startAt = new Date();
    (iterations || 1).times(operation);
    var timeTaken = ((new Date())-startAt);
    this.info((arguments[2] || 'Operation') + ' finished ' + 
       iterations + ' iterations in ' + (timeTaken/1000)+'s' );
    return timeTaken;
  },
  _isVisible: function(element) {
    element = $(element);
    if(!element.parentNode) return true;
    this.assertNotNull(element);
    if(element.style && Element.getStyle(element, 'display') == 'none')
      return false;
    
    return this._isVisible(element.parentNode);
  },
  assertNotVisible: function(element) {
    this.assert(!this._isVisible(element), Test.Unit.inspect(element) + " was not hidden and didn't have a hidden parent either. " + ("" || arguments[1]));
  },
  assertVisible: function(element) {
    this.assert(this._isVisible(element), Test.Unit.inspect(element) + " was not visible. " + ("" || arguments[1]));
  },
  benchmark: function(operation, iterations) {
    var startAt = new Date();
    (iterations || 1).times(operation);
    var timeTaken = ((new Date())-startAt);
    this.info((arguments[2] || 'Operation') + ' finished ' + 
       iterations + ' iterations in ' + (timeTaken/1000)+'s' );
    return timeTaken;
  }
}

Test.Unit.Testcase = Class.create();
Object.extend(Object.extend(Test.Unit.Testcase.prototype, Test.Unit.Assertions.prototype), {
  initialize: function(name, test, setup, teardown) {
    Test.Unit.Assertions.prototype.initialize.bind(this)();
    this.name           = name;
    
    if(typeof test == 'string') {
      test = test.gsub(/(\.should[^\(]+\()/,'#{0}this,');
      test = test.gsub(/(\.should[^\(]+)\(this,\)/,'#{1}(this)');
      this.test = function() {
        eval('with(this){'+test+'}');
      }
    } else {
      this.test = test || function() {};
    }
    
    this.setup          = setup || function() {};
    this.teardown       = teardown || function() {};
    this.isWaiting      = false;
    this.timeToWait     = 1000;
  },
  wait: function(time, nextPart) {
    this.isWaiting = true;
    this.test = nextPart;
    this.timeToWait = time;
  },
  run: function() {
    try {
      try {
        if (!this.isWaiting) this.setup.bind(this)();
        this.isWaiting = false;
        this.test.bind(this)();
      } finally {
        if(!this.isWaiting) {
          this.teardown.bind(this)();
        }
      }
    }
    catch(e) { this.error(e); }
  }
});

// *EXPERIMENTAL* BDD-style testing to please non-technical folk
// This draws many ideas from RSpec http://rspec.rubyforge.org/

Test.setupBDDExtensionMethods = function(){
  var METHODMAP = {
    shouldEqual:     'assertEqual',
    shouldNotEqual:  'assertNotEqual',
    shouldEqualEnum: 'assertEnumEqual',
    shouldBeA:       'assertType',
    shouldNotBeA:    'assertNotOfType',
    shouldBeAn:      'assertType',
    shouldNotBeAn:   'assertNotOfType',
    shouldBeNull:    'assertNull',
    shouldNotBeNull: 'assertNotNull',
    
    shouldBe:        'assertReturnsTrue',
    shouldNotBe:     'assertReturnsFalse',
    shouldRespondTo: 'assertRespondsTo'
  };
  var makeAssertion = function(assertion, args, object) { 
   	this[assertion].apply(this,(args || []).concat([object]));
  }
  
  Test.BDDMethods = {};   
  $H(METHODMAP).each(function(pair) { 
    Test.BDDMethods[pair.key] = function() { 
       var args = $A(arguments); 
       var scope = args.shift(); 
       makeAssertion.apply(scope, [pair.value, args, this]); }; 
  });
  
  [Array.prototype, String.prototype, Number.prototype, Boolean.prototype].each(
    function(p){ Object.extend(p, Test.BDDMethods) }
  );
}

Test.context = function(name, spec, log){
  Test.setupBDDExtensionMethods();
  
  var compiledSpec = {};
  var titles = {};
  for(specName in spec) {
    switch(specName){
      case "setup":
      case "teardown":
        compiledSpec[specName] = spec[specName];
        break;
      default:
        var testName = 'test'+specName.gsub(/\s+/,'-').camelize();
        var body = spec[specName].toString().split('\n').slice(1);
        if(/^\{/.test(body[0])) body = body.slice(1);
        body.pop();
        body = body.map(function(statement){ 
          return statement.strip()
        });
        compiledSpec[testName] = body.join('\n');
        titles[testName] = specName;
    }
  }
  new Test.Unit.Runner(compiledSpec, { titles: titles, testLog: log || 'testlog', context: name });
};

/********************************
** verticalcenter.js
**
** vertical centering
** Design Kitchen
** @author: Matthew Story
*********************************/

//global helper object for vertical center objects
var VerticalCenters = {
	verticalCenters: [],
	checkVAlignInterval: false,
	interval: 500,
	
	//register a VerticalCenter object
	register: function(verticalCenter) {
		this.verticalCenters.push(verticalCenter);
		if (this.verticalCenters.length == 1) {
			this.setVAlignCheckInterval();
		}

		return true;
	},
	
	//unregister a VerticalCenter object
	unregister: function(verticalCenter) {
		this.removeVerticalCenter(verticalCenter);
		if (!this.verticalCenters.length) {
			this.removeVAlignCheckInterval();
		}

		return true;
	},
	
	//take a verticalCenter object out of the array
	removeVerticalCenter: function(verticalCenter) {
		var verticalCenters = [];
		for (var i=0;i<this.verticalCenters.length;i++) {
			if (this.verticalCenters[i] != verticalCenter) {
				verticalCenters.push(this.verticalCenters[i]);
			}
		}
		this.verticalCenters = verticalCenters;

		return true;
	},
	
	//set the interval to see if the vertical offset has changed
	setVAlignCheckInterval: function() {
		this.checkVAlignInterval = window.setInterval(this.checkVAlign.bindAsEventListener(this), this.interval);
		return true;
	},

	//remove the interval that checks for a change in vertical offset
	removeVAlignCheckInterval: function() {
		window.clearInterval(this.checkVAlignInterval);
		this.checkVAlignInterval = null;

		return true;
	},

	//check to see if the vertical offset is correct
	checkVAlign: function() {
		for (var i=0;i<this.verticalCenters.length;i++) {
			if (!this.verticalCenters[i].checkVAlign()) {
				//if the offset has changed, change the offset
				this.verticalCenters[i].vAlign();
			}
		}

		return true;
	}
};

//Vertical Center Class def
var VerticalCenter = Class.create();
VerticalCenter.prototype = {
	element: false,
	oldBorder: false,
	oldPadding: false,
	oldMargin: false,

	initialize: function(element) {
		//handle options
		var defaults = {};
		var options = (arguments[1]) ? arguments[1]:{};
		Object.extend(defaults, options);
		
		//handle the element
		this.element = $(element);
		
		//vertically center
		this.relativize();
		this.vAlign();

		//register with the global helper object
		VerticalCenters.register(this);

		return true;
	},

	destroy: function() {
		this.element = null;
		VerticalCenters.unregister(this);

		return true;
	},
	
	//vertically align the function
	vAlign: function() {
		this.move(this.getVAlignOffset());
		return true;
	},
	
	//get outer height of element
	getHeight: function() {
		return this.element.offsetHeight;
	},

	//get inner height of parent
	getParentHeight: function() {
		this.stripStyle();
		var height = this.element.parentNode.offsetHeight;
		this.reinstateStyle();

		return height;
	},

	//make relatively positioned
	relativize: function() {
//		this.element.style.position = 'relative';
		return true;
	},

	//move the element to topOffset from top
	move: function(topOffset) {
		this.element.style.top = topOffset + 'px';
		return true;
	},
	
	//get what the offset should be for valign
	getVAlignOffset: function() {
		return Math.floor((this.getParentHeight()-this.getHeight())/2);
	},
	
	//return true if properly aligned, false if not
	checkVAlign: function() {
		var aligned = (parseInt(this.element.style.top) == this.getVAlignOffset()) ? true:false;
		return aligned;
	},

    stripStyle: function() {
        this.oldBorder = this.element.style.border;
        this.oldPadding = this.element.style.padding;
        this.oldMargin = this.element.style.margin;
// possible cause of leaks
		this.element.parentNode.style.border = '0px';
		this.element.parentNode.style.margin = '0px';
		this.element.parentNode.style.padding = '0px';

        return true;
    },

    reinstateStyle: function() {
        this.element.parentNode.style.border = this.oldBorder;
        this.element.parentNode.style.margin = this.oldMargin;
        this.element.parentNode.style.padding = this.oldPadding;

        return true;
    }

};


/********************************
** fauxform.js
**
** DHTML form handler
** Design Kitchen
** @author: Matthew Story
*********************************/
var FauxForm = Class.create({
	form: false,
	name: false,
	action: false,
	ajax: false,
	onSubmit: false,
	method: false,
	ajaxOptions: false,
	ajaxHelper: false,
	fields: [],
	submitOnChange: false,
	submitOnInitialize: false,

	initialize: function(name, action, fields) {
		this.name = name;
		this.action = action;
		var options = Object.extend({
			ajax: false,
			onSubmit: function() {},
			method: 'post',
			ajaxOptions: {},
			//ajax helper object, if you are using this in conjunction with the ajaxhelper.js lib
			ajaxHelper: false,
			submitOnInitialize: false
		}, arguments[3] || {});
		this.ajax = options.ajax;
		this.onSubmit = options.onSubmit;
		this.method = options.method;
		this.ajaxOptions = options.ajaxOptions;
		this.ajaxHelper = options.ajaxHelper;
		this.submitOnChange = options.submitOnChange;
		this.submitOnInitialize = options.submitOnInitialize;
		this.generateForm();
		for (var i=0;i<fields.length;i++) {
			this.register(fields[i]);
		}
		if (this.submitOnInitialize) {
			this.submit();
		}
	},

	destroy: function() {

	},

	submit: function() {
		if (this.onSubmit() === false) {
			return false;
		}

		if (this.ajax && this.ajaxHelper) {
			this.ajaxHelper.request(this.action, Object.extend(this.ajaxOptions, {parameters: this.makeParams()}));
		} else if (this.ajax) {
			new Ajax.Request(this.action, Object.extend(this.ajaxOptions, {parameters: this.makeParams()}));
		} else {
			//need to encode HEADERS and send request . . . for now
			for (var i=0;i<this.fields.length;i++) {
				this.fields[i].realFormElement.setAttribute('value', this.fields[i].getValue());
			}
			this.form.submit();
		}
		
		return true;
	},

	makeParams: function() {
		var params = {};
		for (var i=0;i<this.fields.length;i++) {
			if (this.fields[i].getValue()) {
				if (this.checkArray(this.fields[i].getName()) && this.fields[i].type != 'slider') {
					var name = this.fields[i].getName();
					params[name] = (params[name]) ? params[name]:[];
					params[name].push(this.fields[i].getValue());
				} else {
					params[this.fields[i].getName()] = this.fields[i].getValue();
				}
			}
		}

		return params;
	},

	register: function(field) {
		if (this.checkName(field)) {
			//create the form element
			field.realFormElement = document.createElement('input');
			field.realFormElement.setAttribute('type', 'hidden');
			field.realFormElement.setAttribute('name', field.name);
			field.form = this;
			this.form.appendChild(field.realFormElement);
			//add to the list
			this.fields.push(field);

			return true;
		}
		
		return false;
	},

	unregister: function(type, obj) {
		var fields = [];
		for (var i=0;i<this.fields.length;i++) {
			if (this.fields[i] != obj) {
				fields.push(this.fields[i]);
			}
		}
		this.fields = fields;

		return true;
	},
	
	generateForm: function() {
		this.form = document.createElement('form');
		this.form.setAttribute('action', this.action);
		this.form.setAttribute('method', this.method);
		document.body.appendChild(this.form);
	},

	checkName: function(field) {
		if (this.checkArray(field.getName())) {
			return true;
		} else if (this.getFieldByName(field.getName()) || this.getFieldByName(field.getName+'[]')) {
			return false;
		}
		return true;
	},

	getFieldByName: function(name) {
		for (var i=0;i<this.fields.length;i++) {
			if (this.fields[i].getName() == name) {
				return this.fields[i];
			}
		}
		
		return false;
	},

	getFieldByElement: function(element) {
		for (var i=0;i<this.fields.length;i++) {
			if (this.fields[i].element == $(element)) {
				return this.fields[i];
			}
		}
		return false;
	},

	getFieldValue: function(name) {
		for (var i=0;i<this.fields.length;i++) {
			if (this.fields[i].getName() == name) {
				return this.fields[i].getValue();
			}
		}

		return false;
	},

	checkArray: function(name) {
		return (name.match(/\[\s*\]$/)) ? true:false;
	}
});

FauxForm.Field = Class.create({
	element: false,
	name: false,
	onChange: false,
	value: false,
	events: [],
	realFormElement: false,
	form: false,
	submitOnChange: false,

	initialize: function() {},
	
	destroy: function() {
		this.unregisterEvents();
		this.element = null;
		this.name = null;
		this.onChange = null;
		this.value = null;
		this.events = null;
	},

	getValue: function() {
		return this.value;
	},

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

	setValue: function(value) {
		var disableSubmitOnChange = (arguments[1] && this.submitOnChange) ? true:false;
		if (value != this.value) {
			this.value = value;
			this.onChange(this.element, this.value);
		}
		if (this.submitOnChange && !disableSubmitOnChange) {
			this.submitForm();
		}
		return true;
	},
	
	submitForm: function() {
		if (this.form) {
			this.form.submit();
		}
	},

	bindValueToEvent: function(element, event, value) {
		this.registerEvent(element, event, function() {
			this.setValue(value);	
		}.bindAsEventListener(this));
		
		return true;
	},
	
	bindReturnToEvent: function(element, event, func) {
		this.registerEvent(element, event, function() {
			this.setValue(func());
		}.bindAsEventListener(this));
		
		return true;
	},

	registerEvent: function(element, event, handler) {
		this.events.push({element: element, event: event, handler: handler});
		Event.observe(element, event, handler);

		return true;
	},

	unregisterEvent: function(element, event, handler) {
		var events = [];
		for (var i=0;i<this.events.length;i++) {
			if (this.events[i].element == element && this.events[i].event == event && this.events[i].handler == handler) {
				Event.stopObserving(element, event, handler);
			} else {
				events.push(this.events[i]);
			}
		}	
		this.events = events;

		return true;
	},

	unregisterEvents: function() {
		for (var i=0;this.events.length;i++) {
			Event.stopObserving(this.events[i].element, this.events[i].event, this.events[i].handler);
		}
		this.events = [];

		return true;
	}
});

FauxForm.Radio = Class.create(FauxForm.Field, {
	radioElements: [],
	currentElement: false,
	type: 'radio',
	
	initialize: function(name, radioElements) {
		this.radioElements = [];
		this.name = name;
		var options = Object.extend({
			submitOnChange: false,
			onChange: function() {}
		}, arguments[2] || {});
		this.onChange = options.onChange;
		this.submitOnChange = options.submitOnChange;
		// console.log("radioElements " + radioElements + " : this.radioElements " + this.radioElements);
		if(radioElements!=undefined){
			for (var i=0;i<radioElements.length;i++) {
				this.register(radioElements[i]);
			}			
		}
	},

	register: function(radioElement) {
		this.registerEvent(radioElement.element, 'click', this.eventHandler.bindAsEventListener(this));
		this.radioElements.push(radioElement);
		if (radioElement.checked || !this.currentElement) {
			this.setValue(radioElement);
		}

		return true;
	},

	unregister: function(radioElement) {
		var radioElements = [];
		for (var i=0;i<this.radioElements.length;i++) {
			if (this.radioElements[i] != radioElement) {
				radioElements.push(this.radioElements[i]);
			}
		}
		this.radioElements = radioElements;
		
		//if that one was checked:
		if (radioElement == this.currentElement) {
			this.setValue(this.radioElements[i]);
		}

		return true;
	},

	getValue: function() {
		if (this.currentElement) {
			return this.currentElement.value;
		}
		return false;
	},

	setValue: function(radioElement) {
		if (this.currentElement == radioElement) {
			return true;
		}

		if (this.currentElement) {
			this.currentElement.onChange(this.currentElement.element, false);
		}
		this.currentElement = radioElement;
		this.currentElement.onChange(this.currentElement.element, true);
		this.onChange(this.currentElement.element, true);
		this.value = this.currentElement.value;
		if (this.submitOnChange) {
			this.submitForm();
		}
	},
	
	eventHandler: function(event) {
		this.setValue(this.getRadioElementFromRadio(Event.element(event)));
	},

	getRadioElementFromRadio: function(element) {
		for (var i=0;i<this.radioElements.length;i++) {
			if (this.radioElements[i].element == $(element) || $(element).descendantOf(this.radioElements[i].element)) {
				return this.radioElements[i];
			}
		}
		return false;
	}
});

FauxForm.Radio.RadioElement = Class.create({
	element: false,
	value: false,
	onChange: false,
	eventHandler: false,
	
	initialize: function(element, value) {
		this.element = $(element);
		this.value = value;
		// console.log("this.element " + this.element + " this.value " + this.value + " , arguments " + arguments[2]);
		var options = Object.extend({
			checked: false,
			onChange: function() {}
		}, arguments[2] || {});
		this.checked = options.checked;
		this.onChange = options.onChange;
		
		return true;
	},

	destroy: function() {
		this.unregisterEventHandler();
		this.element = null;
		this.value = null;
		this.onCheck = null;
		this.onUnCheck = null;
		this.radio = null;

		return true;
	}
});

FauxForm.Checkbox = Class.create(FauxForm.Field, {
	type: 'checkbox',
	initialize: function(name, element) {
		this.name = name;
		this.element = $(element);
		
		var options = Object.extend({
			onChange: function() {},
			checked: false,
			submitOnChange: false,
			value: true
		}, arguments[2] || {});
		this.onChange = options.onChange;
		this.checkValue = options.value;
		this.submitOnChange = options.submitOnChange;
		this.setValue((options.checked) ? options.value:false);
		this.registerEvent(this.element, 'click', this.toggleValue.bindAsEventListener(this));
	},

	customInitialize: function() {
		this.registerEventHandler();	
	},

	toggleValue: function() {
		if (this.value) {
			this.uncheck();
		} else {
			this.check();
		}

		return true;
	},

	check: function() {
		this.setValue(this.checkValue, arguments[0]);
	},

	uncheck: function() {
		this.setValue(false, arguments[0]);
	}
});

FauxForm.Text = Class.create(FauxForm.Field, {

});

FauxForm.TextArea = Class.create(FauxForm.Field, {

});

FauxForm.Hidden = Class.create(FauxForm.Field, {

});

FauxForm.Slider = Class.create(FauxForm.Field, {
	slider: false,
	type: 'slider',
	initialize: function(name, slider) {
		this.name = name + ((slider.handles.constructor == Array && slider.handles.length > 1) ? '[]':'');
		this.slider = slider;
		var options = Object.extend({
			onChange: function() {},
			submitOnChange: false
		}, arguments[2] || {});
		this.onChange = options.onChange;
		this.submitOnChange = options.submitOnChange;
		var onChange = slider.options.onChange;
		var onSlide = slider.options.onSlide;
		slider.options.onChange = function(values, slider) {
			onChange(values, slider);
			this.setValue(values);
		}.bind(this);
		slider.options.onSlide = function(values, slider) {
			onSlide(values, slider);
			//this.setValue(values);
		}.bind(this);
	}
});

//don't know how this is going to be handled yet
FauxForm.Select = Class.create(FauxForm.Field, {

});

FauxForm.Select.Option = Class.create({

});

FauxForm.Submit = Class.create(FauxForm.Field, {
	submit: true,
	onSubmit: false,
	type: 'submit',
	initialize: function(name, element, value) {
		this.name = name;
		this.element = $(element);
		this.value = value;
		var options = Object.extend({
			onSubmit: function() {}
		}, arguments[3] || {});
		this.onSubmit = options.onSubmit;
		this.registerEvent(this.element, 'click', this.submit.bindAsEventListener(this));
	},
	submit: function() {
		if (this.form) {
			this.onSubmit();
			this.form.submit();
		}
	}
});

//this is for using real form elements along side of faux ones
FauxForm.Input = Class.create(FauxForm.Field, {
	type: 'input',
	initialize: function(element) {
		this.element = $(element);
		this.name = this.element.name;
		var options = Object.extend({
			onChange: function() {},
			submitOnChange: false
		}, arguments[1] || {});
		this.onChange = options.onChange;
		this.submitOnChange = options.submitOnChange;
		this.registerEvent(this.element, 'change', function() {
			this.onChange(this.element, this.getValue());
		}.bind(this));
	},

	setValue: function(value) {
		this.element.value = value;
		if (this.submitOnChange) {
			this.submitForm();
		}
		return true;
	},

	getValue: function() {
		return this.element.value;
	}
});


/**
 * @version: 1.0 Alpha-1
 * @author: Coolite Inc. http://www.coolite.com/
 * @date: 2008-05-13
 * @copyright: Copyright (c) 2006-2008, Coolite Inc. (http://www.coolite.com/). All rights reserved.
 * @license: Licensed under The MIT License. See license.txt and http://www.datejs.com/license/. 
 * @website: http://www.datejs.com/
 */
Date.CultureInfo={name:"en-US",englishName:"English (United States)",nativeName:"English (United States)",dayNames:["Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday"],abbreviatedDayNames:["Sun","Mon","Tue","Wed","Thu","Fri","Sat"],shortestDayNames:["Su","Mo","Tu","We","Th","Fr","Sa"],firstLetterDayNames:["S","M","T","W","T","F","S"],monthNames:["January","February","March","April","May","June","July","August","September","October","November","December"],abbreviatedMonthNames:["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"],amDesignator:"AM",pmDesignator:"PM",firstDayOfWeek:0,twoDigitYearMax:2029,dateElementOrder:"mdy",formatPatterns:{shortDate:"M/d/yyyy",longDate:"dddd, MMMM dd, yyyy",shortTime:"h:mm tt",longTime:"h:mm:ss tt",fullDateTime:"dddd, MMMM dd, yyyy h:mm:ss tt",sortableDateTime:"yyyy-MM-ddTHH:mm:ss",universalSortableDateTime:"yyyy-MM-dd HH:mm:ssZ",rfc1123:"ddd, dd MMM yyyy HH:mm:ss GMT",monthDay:"MMMM dd",yearMonth:"MMMM, yyyy"},regexPatterns:{jan:/^jan(uary)?/i,feb:/^feb(ruary)?/i,mar:/^mar(ch)?/i,apr:/^apr(il)?/i,may:/^may/i,jun:/^jun(e)?/i,jul:/^jul(y)?/i,aug:/^aug(ust)?/i,sep:/^sep(t(ember)?)?/i,oct:/^oct(ober)?/i,nov:/^nov(ember)?/i,dec:/^dec(ember)?/i,sun:/^su(n(day)?)?/i,mon:/^mo(n(day)?)?/i,tue:/^tu(e(s(day)?)?)?/i,wed:/^we(d(nesday)?)?/i,thu:/^th(u(r(s(day)?)?)?)?/i,fri:/^fr(i(day)?)?/i,sat:/^sa(t(urday)?)?/i,future:/^next/i,past:/^last|past|prev(ious)?/i,add:/^(\+|aft(er)?|from|hence)/i,subtract:/^(\-|bef(ore)?|ago)/i,yesterday:/^yes(terday)?/i,today:/^t(od(ay)?)?/i,tomorrow:/^tom(orrow)?/i,now:/^n(ow)?/i,millisecond:/^ms|milli(second)?s?/i,second:/^sec(ond)?s?/i,minute:/^mn|min(ute)?s?/i,hour:/^h(our)?s?/i,week:/^w(eek)?s?/i,month:/^m(onth)?s?/i,day:/^d(ay)?s?/i,year:/^y(ear)?s?/i,shortMeridian:/^(a|p)/i,longMeridian:/^(a\.?m?\.?|p\.?m?\.?)/i,timezone:/^((e(s|d)t|c(s|d)t|m(s|d)t|p(s|d)t)|((gmt)?\s*(\+|\-)\s*\d\d\d\d?)|gmt|utc)/i,ordinalSuffix:/^\s*(st|nd|rd|th)/i,timeContext:/^\s*(\:|a(?!u|p)|p)/i},timezones:[{name:"UTC",offset:"-000"},{name:"GMT",offset:"-000"},{name:"EST",offset:"-0500"},{name:"EDT",offset:"-0400"},{name:"CST",offset:"-0600"},{name:"CDT",offset:"-0500"},{name:"MST",offset:"-0700"},{name:"MDT",offset:"-0600"},{name:"PST",offset:"-0800"},{name:"PDT",offset:"-0700"}]};
(function(){var $D=Date,$P=$D.prototype,$C=$D.CultureInfo,p=function(s,l){if(!l){l=2;}
return("000"+s).slice(l*-1);};$P.clearTime=function(){this.setHours(0);this.setMinutes(0);this.setSeconds(0);this.setMilliseconds(0);return this;};$P.setTimeToNow=function(){var n=new Date();this.setHours(n.getHours());this.setMinutes(n.getMinutes());this.setSeconds(n.getSeconds());this.setMilliseconds(n.getMilliseconds());return this;};$D.today=function(){return new Date().clearTime();};$D.compare=function(date1,date2){if(isNaN(date1)||isNaN(date2)){throw new Error(date1+" - "+date2);}else if(date1 instanceof Date&&date2 instanceof Date){return(date1<date2)?-1:(date1>date2)?1:0;}else{throw new TypeError(date1+" - "+date2);}};$D.equals=function(date1,date2){return(date1.compareTo(date2)===0);};$D.getDayNumberFromName=function(name){var n=$C.dayNames,m=$C.abbreviatedDayNames,o=$C.shortestDayNames,s=name.toLowerCase();for(var i=0;i<n.length;i++){if(n[i].toLowerCase()==s||m[i].toLowerCase()==s||o[i].toLowerCase()==s){return i;}}
return-1;};$D.getMonthNumberFromName=function(name){var n=$C.monthNames,m=$C.abbreviatedMonthNames,s=name.toLowerCase();for(var i=0;i<n.length;i++){if(n[i].toLowerCase()==s||m[i].toLowerCase()==s){return i;}}
return-1;};$D.isLeapYear=function(year){return((year%4===0&&year%100!==0)||year%400===0);};$D.getDaysInMonth=function(year,month){return[31,($D.isLeapYear(year)?29:28),31,30,31,30,31,31,30,31,30,31][month];};$D.getTimezoneAbbreviation=function(offset){var z=$C.timezones,p;for(var i=0;i<z.length;i++){if(z[i].offset===offset){return z[i].name;}}
return null;};$D.getTimezoneOffset=function(name){var z=$C.timezones,p;for(var i=0;i<z.length;i++){if(z[i].name===name.toUpperCase()){return z[i].offset;}}
return null;};$P.clone=function(){return new Date(this.getTime());};$P.compareTo=function(date){return Date.compare(this,date);};$P.equals=function(date){return Date.equals(this,date||new Date());};$P.between=function(start,end){return this.getTime()>=start.getTime()&&this.getTime()<=end.getTime();};$P.isAfter=function(date){return this.compareTo(date||new Date())===1;};$P.isBefore=function(date){return(this.compareTo(date||new Date())===-1);};$P.isToday=function(){return this.isSameDay(new Date());};$P.isSameDay=function(date){return this.clone().clearTime().equals(date.clone().clearTime());};$P.addMilliseconds=function(value){this.setMilliseconds(this.getMilliseconds()+value);return this;};$P.addSeconds=function(value){return this.addMilliseconds(value*1000);};$P.addMinutes=function(value){return this.addMilliseconds(value*60000);};$P.addHours=function(value){return this.addMilliseconds(value*3600000);};$P.addDays=function(value){this.setDate(this.getDate()+value);return this;};$P.addWeeks=function(value){return this.addDays(value*7);};$P.addMonths=function(value){var n=this.getDate();this.setDate(1);this.setMonth(this.getMonth()+value);this.setDate(Math.min(n,$D.getDaysInMonth(this.getFullYear(),this.getMonth())));return this;};$P.addYears=function(value){return this.addMonths(value*12);};$P.add=function(config){if(typeof config=="number"){this._orient=config;return this;}
var x=config;if(x.milliseconds){this.addMilliseconds(x.milliseconds);}
if(x.seconds){this.addSeconds(x.seconds);}
if(x.minutes){this.addMinutes(x.minutes);}
if(x.hours){this.addHours(x.hours);}
if(x.weeks){this.addWeeks(x.weeks);}
if(x.months){this.addMonths(x.months);}
if(x.years){this.addYears(x.years);}
if(x.days){this.addDays(x.days);}
return this;};var $y,$m,$d;$P.getWeek=function(){var a,b,c,d,e,f,g,n,s,w;$y=(!$y)?this.getFullYear():$y;$m=(!$m)?this.getMonth()+1:$m;$d=(!$d)?this.getDate():$d;if($m<=2){a=$y-1;b=(a/4|0)-(a/100|0)+(a/400|0);c=((a-1)/4|0)-((a-1)/100|0)+((a-1)/400|0);s=b-c;e=0;f=$d-1+(31*($m-1));}else{a=$y;b=(a/4|0)-(a/100|0)+(a/400|0);c=((a-1)/4|0)-((a-1)/100|0)+((a-1)/400|0);s=b-c;e=s+1;f=$d+((153*($m-3)+2)/5)+58+s;}
g=(a+b)%7;d=(f+g-e)%7;n=(f+3-d)|0;if(n<0){w=53-((g-s)/5|0);}else if(n>364+s){w=1;}else{w=(n/7|0)+1;}
$y=$m=$d=null;return w;};$P.getISOWeek=function(){$y=this.getUTCFullYear();$m=this.getUTCMonth()+1;$d=this.getUTCDate();return p(this.getWeek());};$P.setWeek=function(n){return this.moveToDayOfWeek(1).addWeeks(n-this.getWeek());};$D._validate=function(n,min,max,name){if(typeof n=="undefined"){return false;}else if(typeof n!="number"){throw new TypeError(n+" is not a Number.");}else if(n<min||n>max){throw new RangeError(n+" is not a valid value for "+name+".");}
return true;};$D.validateMillisecond=function(value){return $D._validate(value,0,999,"millisecond");};$D.validateSecond=function(value){return $D._validate(value,0,59,"second");};$D.validateMinute=function(value){return $D._validate(value,0,59,"minute");};$D.validateHour=function(value){return $D._validate(value,0,23,"hour");};$D.validateDay=function(value,year,month){return $D._validate(value,1,$D.getDaysInMonth(year,month),"day");};$D.validateMonth=function(value){return $D._validate(value,0,11,"month");};$D.validateYear=function(value){return $D._validate(value,0,9999,"year");};$P.set=function(config){if($D.validateMillisecond(config.millisecond)){this.addMilliseconds(config.millisecond-this.getMilliseconds());}
if($D.validateSecond(config.second)){this.addSeconds(config.second-this.getSeconds());}
if($D.validateMinute(config.minute)){this.addMinutes(config.minute-this.getMinutes());}
if($D.validateHour(config.hour)){this.addHours(config.hour-this.getHours());}
if($D.validateMonth(config.month)){this.addMonths(config.month-this.getMonth());}
if($D.validateYear(config.year)){this.addYears(config.year-this.getFullYear());}
if($D.validateDay(config.day,this.getFullYear(),this.getMonth())){this.addDays(config.day-this.getDate());}
if(config.timezone){this.setTimezone(config.timezone);}
if(config.timezoneOffset){this.setTimezoneOffset(config.timezoneOffset);}
if(config.week&&$D._validate(config.week,0,53,"week")){this.setWeek(config.week);}
return this;};$P.moveToFirstDayOfMonth=function(){return this.set({day:1});};$P.moveToLastDayOfMonth=function(){return this.set({day:$D.getDaysInMonth(this.getFullYear(),this.getMonth())});};$P.moveToNthOccurrence=function(dayOfWeek,occurrence){var shift=0;if(occurrence>0){shift=occurrence-1;}
else if(occurrence===-1){this.moveToLastDayOfMonth();if(this.getDay()!==dayOfWeek){this.moveToDayOfWeek(dayOfWeek,-1);}
return this;}
return this.moveToFirstDayOfMonth().addDays(-1).moveToDayOfWeek(dayOfWeek,+1).addWeeks(shift);};$P.moveToDayOfWeek=function(dayOfWeek,orient){var diff=(dayOfWeek-this.getDay()+7*(orient||+1))%7;return this.addDays((diff===0)?diff+=7*(orient||+1):diff);};$P.moveToMonth=function(month,orient){var diff=(month-this.getMonth()+12*(orient||+1))%12;return this.addMonths((diff===0)?diff+=12*(orient||+1):diff);};$P.getOrdinalNumber=function(){return Math.ceil((this.clone().clearTime()-new Date(this.getFullYear(),0,1))/86400000)+1;};$P.getTimezone=function(){return $D.getTimezoneAbbreviation(this.getUTCOffset());};$P.setTimezoneOffset=function(offset){var here=this.getTimezoneOffset(),there=Number(offset)*-6/10;return this.addMinutes(there-here);};$P.setTimezone=function(offset){return this.setTimezoneOffset($D.getTimezoneOffset(offset));};$P.hasDaylightSavingTime=function(){return(Date.today().set({month:0,day:1}).getTimezoneOffset()!==Date.today().set({month:6,day:1}).getTimezoneOffset());};$P.isDaylightSavingTime=function(){return(this.hasDaylightSavingTime()&&new Date().getTimezoneOffset()===Date.today().set({month:6,day:1}).getTimezoneOffset());};$P.getUTCOffset=function(){var n=this.getTimezoneOffset()*-10/6,r;if(n<0){r=(n-10000).toString();return r.charAt(0)+r.substr(2);}else{r=(n+10000).toString();return"+"+r.substr(1);}};$P.getElapsed=function(date){return(date||new Date())-this;};if(!$P.toISOString){$P.toISOString=function(){function f(n){return n<10?'0'+n:n;}
return'"'+this.getUTCFullYear()+'-'+
f(this.getUTCMonth()+1)+'-'+
f(this.getUTCDate())+'T'+
f(this.getUTCHours())+':'+
f(this.getUTCMinutes())+':'+
f(this.getUTCSeconds())+'Z"';};}
$P._toString=$P.toString;$P.toString=function(format){var x=this;if(format&&format.length==1){var c=$C.formatPatterns;x.t=x.toString;switch(format){case"d":return x.t(c.shortDate);case"D":return x.t(c.longDate);case"F":return x.t(c.fullDateTime);case"m":return x.t(c.monthDay);case"r":return x.t(c.rfc1123);case"s":return x.t(c.sortableDateTime);case"t":return x.t(c.shortTime);case"T":return x.t(c.longTime);case"u":return x.t(c.universalSortableDateTime);case"y":return x.t(c.yearMonth);}}
var ord=function(n){switch(n*1){case 1:case 21:case 31:return"st";case 2:case 22:return"nd";case 3:case 23:return"rd";default:return"th";}};return format?format.replace(/(\\)?(dd?d?d?|MM?M?M?|yy?y?y?|hh?|HH?|mm?|ss?|tt?|S)/g,function(m){if(m.charAt(0)==="\\"){return m.replace("\\","");}
x.h=x.getHours;switch(m){case"hh":return p(x.h()<13?(x.h()===0?12:x.h()):(x.h()-12));case"h":return x.h()<13?(x.h()===0?12:x.h()):(x.h()-12);case"HH":return p(x.h());case"H":return x.h();case"mm":return p(x.getMinutes());case"m":return x.getMinutes();case"ss":return p(x.getSeconds());case"s":return x.getSeconds();case"yyyy":return p(x.getFullYear(),4);case"yy":return p(x.getFullYear());case"dddd":return $C.dayNames[x.getDay()];case"ddd":return $C.abbreviatedDayNames[x.getDay()];case"dd":return p(x.getDate());case"d":return x.getDate();case"MMMM":return $C.monthNames[x.getMonth()];case"MMM":return $C.abbreviatedMonthNames[x.getMonth()];case"MM":return p((x.getMonth()+1));case"M":return x.getMonth()+1;case"t":return x.h()<12?$C.amDesignator.substring(0,1):$C.pmDesignator.substring(0,1);case"tt":return x.h()<12?$C.amDesignator:$C.pmDesignator;case"S":return ord(x.getDate());default:return m;}}):this._toString();};}());
(function(){var $D=Date,$P=$D.prototype,$C=$D.CultureInfo,$N=Number.prototype;$P._orient=+1;$P._nth=null;$P._is=false;$P._same=false;$P._isSecond=false;$N._dateElement="day";$P.next=function(){this._orient=+1;return this;};$D.next=function(){return $D.today().next();};$P.last=$P.prev=$P.previous=function(){this._orient=-1;return this;};$D.last=$D.prev=$D.previous=function(){return $D.today().last();};$P.is=function(){this._is=true;return this;};$P.same=function(){this._same=true;this._isSecond=false;return this;};$P.today=function(){return this.same().day();};$P.weekday=function(){if(this._is){this._is=false;return(!this.is().sat()&&!this.is().sun());}
return false;};$P.at=function(time){return(typeof time==="string")?$D.parse(this.toString("d")+" "+time):this.set(time);};$N.fromNow=$N.after=function(date){var c={};c[this._dateElement]=this;return((!date)?new Date():date.clone()).add(c);};$N.ago=$N.before=function(date){var c={};c[this._dateElement]=this*-1;return((!date)?new Date():date.clone()).add(c);};var dx=("sunday monday tuesday wednesday thursday friday saturday").split(/\s/),mx=("january february march april may june july august september october november december").split(/\s/),px=("Millisecond Second Minute Hour Day Week Month Year").split(/\s/),pxf=("Milliseconds Seconds Minutes Hours Date Week Month FullYear").split(/\s/),nth=("final first second third fourth fifth").split(/\s/),de;$P.toObject=function(){var o={};for(var i=0;i<px.length;i++){o[px[i].toLowerCase()]=this["get"+pxf[i]]();}
return o;};$D.fromObject=function(config){config.week=null;return Date.today().set(config);};var df=function(n){return function(){if(this._is){this._is=false;return this.getDay()==n;}
if(this._nth!==null){if(this._isSecond){this.addSeconds(this._orient*-1);}
this._isSecond=false;var ntemp=this._nth;this._nth=null;var temp=this.clone().moveToLastDayOfMonth();this.moveToNthOccurrence(n,ntemp);if(this>temp){throw new RangeError($D.getDayName(n)+" does not occur "+ntemp+" times in the month of "+$D.getMonthName(temp.getMonth())+" "+temp.getFullYear()+".");}
return this;}
return this.moveToDayOfWeek(n,this._orient);};};var sdf=function(n){return function(){var t=$D.today(),shift=n-t.getDay();if(n===0&&$C.firstDayOfWeek===1&&t.getDay()!==0){shift=shift+7;}
return t.addDays(shift);};};for(var i=0;i<dx.length;i++){$D[dx[i].toUpperCase()]=$D[dx[i].toUpperCase().substring(0,3)]=i;$D[dx[i]]=$D[dx[i].substring(0,3)]=sdf(i);$P[dx[i]]=$P[dx[i].substring(0,3)]=df(i);}
var mf=function(n){return function(){if(this._is){this._is=false;return this.getMonth()===n;}
return this.moveToMonth(n,this._orient);};};var smf=function(n){return function(){return $D.today().set({month:n,day:1});};};for(var j=0;j<mx.length;j++){$D[mx[j].toUpperCase()]=$D[mx[j].toUpperCase().substring(0,3)]=j;$D[mx[j]]=$D[mx[j].substring(0,3)]=smf(j);$P[mx[j]]=$P[mx[j].substring(0,3)]=mf(j);}
var ef=function(j){return function(){if(this._isSecond){this._isSecond=false;return this;}
if(this._same){this._same=this._is=false;var o1=this.toObject(),o2=(arguments[0]||new Date()).toObject(),v="",k=j.toLowerCase();for(var m=(px.length-1);m>-1;m--){v=px[m].toLowerCase();if(o1[v]!=o2[v]){return false;}
if(k==v){break;}}
return true;}
if(j.substring(j.length-1)!="s"){j+="s";}
return this["add"+j](this._orient);};};var nf=function(n){return function(){this._dateElement=n;return this;};};for(var k=0;k<px.length;k++){de=px[k].toLowerCase();$P[de]=$P[de+"s"]=ef(px[k]);$N[de]=$N[de+"s"]=nf(de);}
$P._ss=ef("Second");var nthfn=function(n){return function(dayOfWeek){if(this._same){return this._ss(arguments[0]);}
if(dayOfWeek||dayOfWeek===0){return this.moveToNthOccurrence(dayOfWeek,n);}
this._nth=n;if(n===2&&(dayOfWeek===undefined||dayOfWeek===null)){this._isSecond=true;return this.addSeconds(this._orient);}
return this;};};for(var l=0;l<nth.length;l++){$P[nth[l]]=(l===0)?nthfn(-1):nthfn(l);}}());
(function(){Date.Parsing={Exception:function(s){this.message="Parse error at '"+s.substring(0,10)+" ...'";}};var $P=Date.Parsing;var _=$P.Operators={rtoken:function(r){return function(s){var mx=s.match(r);if(mx){return([mx[0],s.substring(mx[0].length)]);}else{throw new $P.Exception(s);}};},token:function(s){return function(s){return _.rtoken(new RegExp("^\s*"+s+"\s*"))(s);};},stoken:function(s){return _.rtoken(new RegExp("^"+s));},until:function(p){return function(s){var qx=[],rx=null;while(s.length){try{rx=p.call(this,s);}catch(e){qx.push(rx[0]);s=rx[1];continue;}
break;}
return[qx,s];};},many:function(p){return function(s){var rx=[],r=null;while(s.length){try{r=p.call(this,s);}catch(e){return[rx,s];}
rx.push(r[0]);s=r[1];}
return[rx,s];};},optional:function(p){return function(s){var r=null;try{r=p.call(this,s);}catch(e){return[null,s];}
return[r[0],r[1]];};},not:function(p){return function(s){try{p.call(this,s);}catch(e){return[null,s];}
throw new $P.Exception(s);};},ignore:function(p){return p?function(s){var r=null;r=p.call(this,s);return[null,r[1]];}:null;},product:function(){var px=arguments[0],qx=Array.prototype.slice.call(arguments,1),rx=[];for(var i=0;i<px.length;i++){rx.push(_.each(px[i],qx));}
return rx;},cache:function(rule){var cache={},r=null;return function(s){try{r=cache[s]=(cache[s]||rule.call(this,s));}catch(e){r=cache[s]=e;}
if(r instanceof $P.Exception){throw r;}else{return r;}};},any:function(){var px=arguments;return function(s){var r=null;for(var i=0;i<px.length;i++){if(px[i]==null){continue;}
try{r=(px[i].call(this,s));}catch(e){r=null;}
if(r){return r;}}
throw new $P.Exception(s);};},each:function(){var px=arguments;return function(s){var rx=[],r=null;for(var i=0;i<px.length;i++){if(px[i]==null){continue;}
try{r=(px[i].call(this,s));}catch(e){throw new $P.Exception(s);}
rx.push(r[0]);s=r[1];}
return[rx,s];};},all:function(){var px=arguments,_=_;return _.each(_.optional(px));},sequence:function(px,d,c){d=d||_.rtoken(/^\s*/);c=c||null;if(px.length==1){return px[0];}
return function(s){var r=null,q=null;var rx=[];for(var i=0;i<px.length;i++){try{r=px[i].call(this,s);}catch(e){break;}
rx.push(r[0]);try{q=d.call(this,r[1]);}catch(ex){q=null;break;}
s=q[1];}
if(!r){throw new $P.Exception(s);}
if(q){throw new $P.Exception(q[1]);}
if(c){try{r=c.call(this,r[1]);}catch(ey){throw new $P.Exception(r[1]);}}
return[rx,(r?r[1]:s)];};},between:function(d1,p,d2){d2=d2||d1;var _fn=_.each(_.ignore(d1),p,_.ignore(d2));return function(s){var rx=_fn.call(this,s);return[[rx[0][0],r[0][2]],rx[1]];};},list:function(p,d,c){d=d||_.rtoken(/^\s*/);c=c||null;return(p instanceof Array?_.each(_.product(p.slice(0,-1),_.ignore(d)),p.slice(-1),_.ignore(c)):_.each(_.many(_.each(p,_.ignore(d))),px,_.ignore(c)));},set:function(px,d,c){d=d||_.rtoken(/^\s*/);c=c||null;return function(s){var r=null,p=null,q=null,rx=null,best=[[],s],last=false;for(var i=0;i<px.length;i++){q=null;p=null;r=null;last=(px.length==1);try{r=px[i].call(this,s);}catch(e){continue;}
rx=[[r[0]],r[1]];if(r[1].length>0&&!last){try{q=d.call(this,r[1]);}catch(ex){last=true;}}else{last=true;}
if(!last&&q[1].length===0){last=true;}
if(!last){var qx=[];for(var j=0;j<px.length;j++){if(i!=j){qx.push(px[j]);}}
p=_.set(qx,d).call(this,q[1]);if(p[0].length>0){rx[0]=rx[0].concat(p[0]);rx[1]=p[1];}}
if(rx[1].length<best[1].length){best=rx;}
if(best[1].length===0){break;}}
if(best[0].length===0){return best;}
if(c){try{q=c.call(this,best[1]);}catch(ey){throw new $P.Exception(best[1]);}
best[1]=q[1];}
return best;};},forward:function(gr,fname){return function(s){return gr[fname].call(this,s);};},replace:function(rule,repl){return function(s){var r=rule.call(this,s);return[repl,r[1]];};},process:function(rule,fn){return function(s){var r=rule.call(this,s);return[fn.call(this,r[0]),r[1]];};},min:function(min,rule){return function(s){var rx=rule.call(this,s);if(rx[0].length<min){throw new $P.Exception(s);}
return rx;};}};var _generator=function(op){return function(){var args=null,rx=[];if(arguments.length>1){args=Array.prototype.slice.call(arguments);}else if(arguments[0]instanceof Array){args=arguments[0];}
if(args){for(var i=0,px=args.shift();i<px.length;i++){args.unshift(px[i]);rx.push(op.apply(null,args));args.shift();return rx;}}else{return op.apply(null,arguments);}};};var gx="optional not ignore cache".split(/\s/);for(var i=0;i<gx.length;i++){_[gx[i]]=_generator(_[gx[i]]);}
var _vector=function(op){return function(){if(arguments[0]instanceof Array){return op.apply(null,arguments[0]);}else{return op.apply(null,arguments);}};};var vx="each any all".split(/\s/);for(var j=0;j<vx.length;j++){_[vx[j]]=_vector(_[vx[j]]);}}());(function(){var $D=Date,$P=$D.prototype,$C=$D.CultureInfo;var flattenAndCompact=function(ax){var rx=[];for(var i=0;i<ax.length;i++){if(ax[i]instanceof Array){rx=rx.concat(flattenAndCompact(ax[i]));}else{if(ax[i]){rx.push(ax[i]);}}}
return rx;};$D.Grammar={};$D.Translator={hour:function(s){return function(){this.hour=Number(s);};},minute:function(s){return function(){this.minute=Number(s);};},second:function(s){return function(){this.second=Number(s);};},meridian:function(s){return function(){this.meridian=s.slice(0,1).toLowerCase();};},timezone:function(s){return function(){var n=s.replace(/[^\d\+\-]/g,"");if(n.length){this.timezoneOffset=Number(n);}else{this.timezone=s.toLowerCase();}};},day:function(x){var s=x[0];return function(){this.day=Number(s.match(/\d+/)[0]);};},month:function(s){return function(){this.month=(s.length==3)?"jan feb mar apr may jun jul aug sep oct nov dec".indexOf(s)/4:Number(s)-1;};},year:function(s){return function(){var n=Number(s);this.year=((s.length>2)?n:(n+(((n+2000)<$C.twoDigitYearMax)?2000:1900)));};},rday:function(s){return function(){switch(s){case"yesterday":this.days=-1;break;case"tomorrow":this.days=1;break;case"today":this.days=0;break;case"now":this.days=0;this.now=true;break;}};},finishExact:function(x){x=(x instanceof Array)?x:[x];for(var i=0;i<x.length;i++){if(x[i]){x[i].call(this);}}
var now=new Date();if((this.hour||this.minute)&&(!this.month&&!this.year&&!this.day)){this.day=now.getDate();}
if(!this.year){this.year=now.getFullYear();}
if(!this.month&&this.month!==0){this.month=now.getMonth();}
if(!this.day){this.day=1;}
if(!this.hour){this.hour=0;}
if(!this.minute){this.minute=0;}
if(!this.second){this.second=0;}
if(this.meridian&&this.hour){if(this.meridian=="p"&&this.hour<12){this.hour=this.hour+12;}else if(this.meridian=="a"&&this.hour==12){this.hour=0;}}
if(this.day>$D.getDaysInMonth(this.year,this.month)){throw new RangeError(this.day+" is not a valid value for days.");}
var r=new Date(this.year,this.month,this.day,this.hour,this.minute,this.second);if(this.timezone){r.set({timezone:this.timezone});}else if(this.timezoneOffset){r.set({timezoneOffset:this.timezoneOffset});}
return r;},finish:function(x){x=(x instanceof Array)?flattenAndCompact(x):[x];if(x.length===0){return null;}
for(var i=0;i<x.length;i++){if(typeof x[i]=="function"){x[i].call(this);}}
var today=$D.today();if(this.now&&!this.unit&&!this.operator){return new Date();}else if(this.now){today=new Date();}
var expression=!!(this.days&&this.days!==null||this.orient||this.operator);var gap,mod,orient;orient=((this.orient=="past"||this.operator=="subtract")?-1:1);if(!this.now&&"hour minute second".indexOf(this.unit)!=-1){today.setTimeToNow();}
if(this.month||this.month===0){if("year day hour minute second".indexOf(this.unit)!=-1){this.value=this.month+1;this.month=null;expression=true;}}
if(!expression&&this.weekday&&!this.day&&!this.days){var temp=Date[this.weekday]();this.day=temp.getDate();if(!this.month){this.month=temp.getMonth();}
this.year=temp.getFullYear();}
if(expression&&this.weekday&&this.unit!="month"){this.unit="day";gap=($D.getDayNumberFromName(this.weekday)-today.getDay());mod=7;this.days=gap?((gap+(orient*mod))%mod):(orient*mod);}
if(this.month&&this.unit=="day"&&this.operator){this.value=(this.month+1);this.month=null;}
if(this.value!=null&&this.month!=null&&this.year!=null){this.day=this.value*1;}
if(this.month&&!this.day&&this.value){today.set({day:this.value*1});if(!expression){this.day=this.value*1;}}
if(!this.month&&this.value&&this.unit=="month"&&!this.now){this.month=this.value;expression=true;}
if(expression&&(this.month||this.month===0)&&this.unit!="year"){this.unit="month";gap=(this.month-today.getMonth());mod=12;this.months=gap?((gap+(orient*mod))%mod):(orient*mod);this.month=null;}
if(!this.unit){this.unit="day";}
if(!this.value&&this.operator&&this.operator!==null&&this[this.unit+"s"]&&this[this.unit+"s"]!==null){this[this.unit+"s"]=this[this.unit+"s"]+((this.operator=="add")?1:-1)+(this.value||0)*orient;}else if(this[this.unit+"s"]==null||this.operator!=null){if(!this.value){this.value=1;}
this[this.unit+"s"]=this.value*orient;}
if(this.meridian&&this.hour){if(this.meridian=="p"&&this.hour<12){this.hour=this.hour+12;}else if(this.meridian=="a"&&this.hour==12){this.hour=0;}}
if(this.weekday&&!this.day&&!this.days){var temp=Date[this.weekday]();this.day=temp.getDate();if(temp.getMonth()!==today.getMonth()){this.month=temp.getMonth();}}
if((this.month||this.month===0)&&!this.day){this.day=1;}
if(!this.orient&&!this.operator&&this.unit=="week"&&this.value&&!this.day&&!this.month){return Date.today().setWeek(this.value);}
if(expression&&this.timezone&&this.day&&this.days){this.day=this.days;}
return(expression)?today.add(this):today.set(this);}};var _=$D.Parsing.Operators,g=$D.Grammar,t=$D.Translator,_fn;g.datePartDelimiter=_.rtoken(/^([\s\-\.\,\/\x27]+)/);g.timePartDelimiter=_.stoken(":");g.whiteSpace=_.rtoken(/^\s*/);g.generalDelimiter=_.rtoken(/^(([\s\,]|at|@|on)+)/);var _C={};g.ctoken=function(keys){var fn=_C[keys];if(!fn){var c=$C.regexPatterns;var kx=keys.split(/\s+/),px=[];for(var i=0;i<kx.length;i++){px.push(_.replace(_.rtoken(c[kx[i]]),kx[i]));}
fn=_C[keys]=_.any.apply(null,px);}
return fn;};g.ctoken2=function(key){return _.rtoken($C.regexPatterns[key]);};g.h=_.cache(_.process(_.rtoken(/^(0[0-9]|1[0-2]|[1-9])/),t.hour));g.hh=_.cache(_.process(_.rtoken(/^(0[0-9]|1[0-2])/),t.hour));g.H=_.cache(_.process(_.rtoken(/^([0-1][0-9]|2[0-3]|[0-9])/),t.hour));g.HH=_.cache(_.process(_.rtoken(/^([0-1][0-9]|2[0-3])/),t.hour));g.m=_.cache(_.process(_.rtoken(/^([0-5][0-9]|[0-9])/),t.minute));g.mm=_.cache(_.process(_.rtoken(/^[0-5][0-9]/),t.minute));g.s=_.cache(_.process(_.rtoken(/^([0-5][0-9]|[0-9])/),t.second));g.ss=_.cache(_.process(_.rtoken(/^[0-5][0-9]/),t.second));g.hms=_.cache(_.sequence([g.H,g.m,g.s],g.timePartDelimiter));g.t=_.cache(_.process(g.ctoken2("shortMeridian"),t.meridian));g.tt=_.cache(_.process(g.ctoken2("longMeridian"),t.meridian));g.z=_.cache(_.process(_.rtoken(/^((\+|\-)\s*\d\d\d\d)|((\+|\-)\d\d\:?\d\d)/),t.timezone));g.zz=_.cache(_.process(_.rtoken(/^((\+|\-)\s*\d\d\d\d)|((\+|\-)\d\d\:?\d\d)/),t.timezone));g.zzz=_.cache(_.process(g.ctoken2("timezone"),t.timezone));g.timeSuffix=_.each(_.ignore(g.whiteSpace),_.set([g.tt,g.zzz]));g.time=_.each(_.optional(_.ignore(_.stoken("T"))),g.hms,g.timeSuffix);g.d=_.cache(_.process(_.each(_.rtoken(/^([0-2]\d|3[0-1]|\d)/),_.optional(g.ctoken2("ordinalSuffix"))),t.day));g.dd=_.cache(_.process(_.each(_.rtoken(/^([0-2]\d|3[0-1])/),_.optional(g.ctoken2("ordinalSuffix"))),t.day));g.ddd=g.dddd=_.cache(_.process(g.ctoken("sun mon tue wed thu fri sat"),function(s){return function(){this.weekday=s;};}));g.M=_.cache(_.process(_.rtoken(/^(1[0-2]|0\d|\d)/),t.month));g.MM=_.cache(_.process(_.rtoken(/^(1[0-2]|0\d)/),t.month));g.MMM=g.MMMM=_.cache(_.process(g.ctoken("jan feb mar apr may jun jul aug sep oct nov dec"),t.month));g.y=_.cache(_.process(_.rtoken(/^(\d\d?)/),t.year));g.yy=_.cache(_.process(_.rtoken(/^(\d\d)/),t.year));g.yyy=_.cache(_.process(_.rtoken(/^(\d\d?\d?\d?)/),t.year));g.yyyy=_.cache(_.process(_.rtoken(/^(\d\d\d\d)/),t.year));_fn=function(){return _.each(_.any.apply(null,arguments),_.not(g.ctoken2("timeContext")));};g.day=_fn(g.d,g.dd);g.month=_fn(g.M,g.MMM);g.year=_fn(g.yyyy,g.yy);g.orientation=_.process(g.ctoken("past future"),function(s){return function(){this.orient=s;};});g.operator=_.process(g.ctoken("add subtract"),function(s){return function(){this.operator=s;};});g.rday=_.process(g.ctoken("yesterday tomorrow today now"),t.rday);g.unit=_.process(g.ctoken("second minute hour day week month year"),function(s){return function(){this.unit=s;};});g.value=_.process(_.rtoken(/^\d\d?(st|nd|rd|th)?/),function(s){return function(){this.value=s.replace(/\D/g,"");};});g.expression=_.set([g.rday,g.operator,g.value,g.unit,g.orientation,g.ddd,g.MMM]);_fn=function(){return _.set(arguments,g.datePartDelimiter);};g.mdy=_fn(g.ddd,g.month,g.day,g.year);g.ymd=_fn(g.ddd,g.year,g.month,g.day);g.dmy=_fn(g.ddd,g.day,g.month,g.year);g.date=function(s){return((g[$C.dateElementOrder]||g.mdy).call(this,s));};g.format=_.process(_.many(_.any(_.process(_.rtoken(/^(dd?d?d?|MM?M?M?|yy?y?y?|hh?|HH?|mm?|ss?|tt?|zz?z?)/),function(fmt){if(g[fmt]){return g[fmt];}else{throw $D.Parsing.Exception(fmt);}}),_.process(_.rtoken(/^[^dMyhHmstz]+/),function(s){return _.ignore(_.stoken(s));}))),function(rules){return _.process(_.each.apply(null,rules),t.finishExact);});var _F={};var _get=function(f){return _F[f]=(_F[f]||g.format(f)[0]);};g.formats=function(fx){if(fx instanceof Array){var rx=[];for(var i=0;i<fx.length;i++){rx.push(_get(fx[i]));}
return _.any.apply(null,rx);}else{return _get(fx);}};g._formats=g.formats(["\"yyyy-MM-ddTHH:mm:ssZ\"","yyyy-MM-ddTHH:mm:ssZ","yyyy-MM-ddTHH:mm:ssz","yyyy-MM-ddTHH:mm:ss","yyyy-MM-ddTHH:mmZ","yyyy-MM-ddTHH:mmz","yyyy-MM-ddTHH:mm","ddd, MMM dd, yyyy H:mm:ss tt","ddd MMM d yyyy HH:mm:ss zzz","MMddyyyy","ddMMyyyy","Mddyyyy","ddMyyyy","Mdyyyy","dMyyyy","yyyy","Mdyy","dMyy","d"]);g._start=_.process(_.set([g.date,g.time,g.expression],g.generalDelimiter,g.whiteSpace),t.finish);g.start=function(s){try{var r=g._formats.call({},s);if(r[1].length===0){return r;}}catch(e){}
return g._start.call({},s);};$D._parse=$D.parse;$D.parse=function(s){var r=null;if(!s){return null;}
if(s instanceof Date){return s;}
try{r=$D.Grammar.start.call({},s.replace(/^\s*(\S*(\s+\S+)*)\s*$/,"$1"));}catch(e){return null;}
return((r[1].length===0)?r[0]:null);};$D.getParseFunction=function(fx){var fn=$D.Grammar.formats(fx);return function(s){var r=null;try{r=fn.call({},s);}catch(e){return null;}
return((r[1].length===0)?r[0]:null);};};$D.parseExact=function(s,fx){return $D.getParseFunction(fx)(s);};}());

/*
*
* Copyright (c) 2007 Andrew Tetlaw
* 
* 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.
* * 
*
*
* FastInit
* http://tetlaw.id.au/view/javascript/fastinit
* Andrew Tetlaw
* Version 1.3 (2007-01-09)
* Based on:
* http://dean.edwards.name/weblog/2006/03/faster
* http://dean.edwards.name/weblog/2006/06/again/
* Help from:
* http://www.cherny.com/webdev/26/domloaded-object-literal-updated
* 
*/
var FastInit = {
	onload : function() {
		if (FastInit.done) { return; }
		FastInit.done = true;
		for(var x = 0, al = FastInit.f.length; x < al; x++) {
			FastInit.f[x]();
		}
	},
	addOnLoad : function() {
		var a = arguments;
		for(var x = 0, al = a.length; x < al; x++) {
			if(typeof a[x] === 'function') { FastInit.f.push(a[x]); }
		}
	},
	listen : function() {
		if (/WebKit|khtml/i.test(navigator.userAgent)) {
			FastInit.timer = setInterval(function() {
				if (/loaded|complete/.test(document.readyState)) {
					clearInterval(FastInit.timer);
					delete FastInit.timer;
					FastInit.onload();
				}}, 10);
		} else if (document.addEventListener) {
			document.addEventListener('DOMContentLoaded', FastInit.onload, false);
		} else if(!FastInit.iew32) {
			Event.observe(window, 'load', FastInit.onload);
		}
	},
	f:[],done:false,timer:null,iew32:false
};
/*@cc_on @*/
/*@if (@_win32)
FastInit.iew32 = true;
document.write('<script id="__ie_onload" defer src="' + ((location.protocol == 'https:') ? '//0' : 'javascript:void(0)') + '"><\/script>');
document.getElementById('__ie_onload').onreadystatechange = function(){if (this.readyState == 'complete') { FastInit.onload(); }};
/*@end @*/
FastInit.listen();


// Copyright (c) 2005-2008 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us)
//           (c) 2005-2007 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.element._originallyAbsolute = (this.element.getStyle('position') == 'absolute');
      if (!this.element._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.element._originallyAbsolute)
        Position.relativize(this.element);
      delete this.element._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){
    var s = Sortable.options(element);
    
    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')];
}


/*
   A A L        Source code at:
   T C A   <http://www.attacklab.net/>
   T K B
*/

var Showdown={};
Showdown.converter=function(){
var _1;
var _2;
var _3;
var _4=0;
this.makeHtml=function(_5){
_1=new Array();
_2=new Array();
_3=new Array();
_5=_5.replace(/~/g,"~T");
_5=_5.replace(/\$/g,"~D");
_5=_5.replace(/\r\n/g,"\n");
_5=_5.replace(/\r/g,"\n");
_5="\n\n"+_5+"\n\n";
_5=_6(_5);
_5=_5.replace(/^[ \t]+$/mg,"");
_5=_7(_5);
_5=_8(_5);
_5=_9(_5);
_5=_a(_5);
_5=_5.replace(/~D/g,"$$");
_5=_5.replace(/~T/g,"~");
return _5;
};
var _8=function(_b){
var _b=_b.replace(/^[ ]{0,3}\[(.+)\]:[ \t]*\n?[ \t]*<?(\S+?)>?[ \t]*\n?[ \t]*(?:(\n*)["(](.+?)[")][ \t]*)?(?:\n+|\Z)/gm,function(_c,m1,m2,m3,m4){
m1=m1.toLowerCase();
_1[m1]=_11(m2);
if(m3){
return m3+m4;
}else{
if(m4){
_2[m1]=m4.replace(/"/g,"&quot;");
}
}
return "";
});
return _b;
};
var _7=function(_12){
_12=_12.replace(/\n/g,"\n\n");
var _13="p|div|h[1-6]|blockquote|pre|table|dl|ol|ul|script|noscript|form|fieldset|iframe|math|ins|del";
var _14="p|div|h[1-6]|blockquote|pre|table|dl|ol|ul|script|noscript|form|fieldset|iframe|math";
_12=_12.replace(/^(<(p|div|h[1-6]|blockquote|pre|table|dl|ol|ul|script|noscript|form|fieldset|iframe|math|ins|del)\b[^\r]*?\n<\/\2>[ \t]*(?=\n+))/gm,_15);
_12=_12.replace(/^(<(p|div|h[1-6]|blockquote|pre|table|dl|ol|ul|script|noscript|form|fieldset|iframe|math)\b[^\r]*?.*<\/\2>[ \t]*(?=\n+)\n)/gm,_15);
_12=_12.replace(/(\n[ ]{0,3}(<(hr)\b([^<>])*?\/?>)[ \t]*(?=\n{2,}))/g,_15);
_12=_12.replace(/(\n\n[ ]{0,3}<!(--[^\r]*?--\s*)+>[ \t]*(?=\n{2,}))/g,_15);
_12=_12.replace(/(?:\n\n)([ ]{0,3}(?:<([?%])[^\r]*?\2>)[ \t]*(?=\n{2,}))/g,_15);
_12=_12.replace(/\n\n/g,"\n");
return _12;
};
var _15=function(_16,m1){
var _18=m1;
_18=_18.replace(/\n\n/g,"\n");
_18=_18.replace(/^\n/,"");
_18=_18.replace(/\n+$/g,"");
_18="\n\n~K"+(_3.push(_18)-1)+"K\n\n";
return _18;
};
var _9=function(_19){
_19=_1a(_19);
var key=_1c("<hr />");
_19=_19.replace(/^[ ]{0,2}([ ]?\*[ ]?){3,}[ \t]*$/gm,key);
_19=_19.replace(/^[ ]{0,2}([ ]?\-[ ]?){3,}[ \t]*$/gm,key);
_19=_19.replace(/^[ ]{0,2}([ ]?\_[ ]?){3,}[ \t]*$/gm,key);
_19=_1d(_19);
_19=_1e(_19);
_19=_1f(_19);
_19=_7(_19);
_19=_20(_19);
return _19;
};
var _21=function(_22){
_22=_23(_22);
_22=_24(_22);
_22=_25(_22);
_22=_26(_22);
_22=_27(_22);
_22=_28(_22);
_22=_11(_22);
_22=_29(_22);
_22=_22.replace(/  +\n/g," <br />\n");
return _22;
};
var _24=function(_2a){
var _2b=/(<[a-z\/!$]("[^"]*"|'[^']*'|[^'">])*>|<!(--.*?--\s*)+>)/gi;
_2a=_2a.replace(_2b,function(_2c){
var tag=_2c.replace(/(.)<\/?code>(?=.)/g,"$1`");
tag=_2e(tag,"\\`*_");
return tag;
});
return _2a;
};
var _27=function(_2f){
_2f=_2f.replace(/(\[((?:\[[^\]]*\]|[^\[\]])*)\][ ]?(?:\n[ ]*)?\[(.*?)\])()()()()/g,_30);
_2f=_2f.replace(/(\[((?:\[[^\]]*\]|[^\[\]])*)\]\([ \t]*()<?(.*?)>?[ \t]*((['"])(.*?)\6[ \t]*)?\))/g,_30);
_2f=_2f.replace(/(\[([^\[\]]+)\])()()()()()/g,_30);
return _2f;
};
var _30=function(_31,m1,m2,m3,m4,m5,m6,m7){
if(m7==undefined){
m7="";
}
var _39=m1;
var _3a=m2;
var _3b=m3.toLowerCase();
var url=m4;
var _3d=m7;
if(url==""){
if(_3b==""){
_3b=_3a.toLowerCase().replace(/ ?\n/g," ");
}
url="#"+_3b;
if(_1[_3b]!=undefined){
url=_1[_3b];
if(_2[_3b]!=undefined){
_3d=_2[_3b];
}
}else{
if(_39.search(/\(\s*\)$/m)>-1){
url="";
}else{
return _39;
}
}
}
url=_2e(url,"*_");
var _3e="<a href=\""+url+"\"";
if(_3d!=""){
_3d=_3d.replace(/"/g,"&quot;");
_3d=_2e(_3d,"*_");
_3e+=" title=\""+_3d+"\"";
}
_3e+=">"+_3a+"</a>";
return _3e;
};
var _26=function(_3f){
_3f=_3f.replace(/(!\[(.*?)\][ ]?(?:\n[ ]*)?\[(.*?)\])()()()()/g,_40);
_3f=_3f.replace(/(!\[(.*?)\]\s?\([ \t]*()<?(\S+?)>?[ \t]*((['"])(.*?)\6[ \t]*)?\))/g,_40);
return _3f;
};
var _40=function(_41,m1,m2,m3,m4,m5,m6,m7){
var _49=m1;
var _4a=m2;
var _4b=m3.toLowerCase();
var url=m4;
var _4d=m7;
if(!_4d){
_4d="";
}
if(url==""){
if(_4b==""){
_4b=_4a.toLowerCase().replace(/ ?\n/g," ");
}
url="#"+_4b;
if(_1[_4b]!=undefined){
url=_1[_4b];
if(_2[_4b]!=undefined){
_4d=_2[_4b];
}
}else{
return _49;
}
}
_4a=_4a.replace(/"/g,"&quot;");
url=_2e(url,"*_");
var _4e="<img src=\""+url+"\" alt=\""+_4a+"\"";
_4d=_4d.replace(/"/g,"&quot;");
_4d=_2e(_4d,"*_");
_4e+=" title=\""+_4d+"\"";
_4e+=" />";
return _4e;
};
var _1a=function(_4f){
_4f=_4f.replace(/^(.+)[ \t]*\n=+[ \t]*\n+/gm,function(_50,m1){
return _1c("<h1>"+_21(m1)+"</h1>");
});
_4f=_4f.replace(/^(.+)[ \t]*\n-+[ \t]*\n+/gm,function(_52,m1){
return _1c("<h2>"+_21(m1)+"</h2>");
});
_4f=_4f.replace(/^(\#{1,6})[ \t]*(.+?)[ \t]*\#*\n+/gm,function(_54,m1,m2){
var _57=m1.length;
return _1c("<h"+_57+">"+_21(m2)+"</h"+_57+">");
});
return _4f;
};
var _58;
var _1d=function(_59){
_59+="~0";
var _5a=/^(([ ]{0,3}([*+-]|\d+[.])[ \t]+)[^\r]+?(~0|\n{2,}(?=\S)(?![ \t]*(?:[*+-]|\d+[.])[ \t]+)))/gm;
if(_4){
_59=_59.replace(_5a,function(_5b,m1,m2){
var _5e=m1;
var _5f=(m2.search(/[*+-]/g)>-1)?"ul":"ol";
_5e=_5e.replace(/\n{2,}/g,"\n\n\n");
var _60=_58(_5e);
_60=_60.replace(/\s+$/,"");
_60="<"+_5f+">"+_60+"</"+_5f+">\n";
return _60;
});
}else{
_5a=/(\n\n|^\n?)(([ ]{0,3}([*+-]|\d+[.])[ \t]+)[^\r]+?(~0|\n{2,}(?=\S)(?![ \t]*(?:[*+-]|\d+[.])[ \t]+)))/g;
_59=_59.replace(_5a,function(_61,m1,m2,m3){
var _65=m1;
var _66=m2;
var _67=(m3.search(/[*+-]/g)>-1)?"ul":"ol";
var _66=_66.replace(/\n{2,}/g,"\n\n\n");
var _68=_58(_66);
_68=_65+"<"+_67+">\n"+_68+"</"+_67+">\n";
return _68;
});
}
_59=_59.replace(/~0/,"");
return _59;
};
_58=function(_69){
_4++;
_69=_69.replace(/\n{2,}$/,"\n");
_69+="~0";
_69=_69.replace(/(\n)?(^[ \t]*)([*+-]|\d+[.])[ \t]+([^\r]+?(\n{1,2}))(?=\n*(~0|\2([*+-]|\d+[.])[ \t]+))/gm,function(_6a,m1,m2,m3,m4){
var _6f=m4;
var _70=m1;
var _71=m2;
if(_70||(_6f.search(/\n{2,}/)>-1)){
_6f=_9(_72(_6f));
}else{
_6f=_1d(_72(_6f));
_6f=_6f.replace(/\n$/,"");
_6f=_21(_6f);
}
return "<li>"+_6f+"</li>\n";
});
_69=_69.replace(/~0/g,"");
_4--;
return _69;
};
var _1e=function(_73){
_73+="~0";
_73=_73.replace(/(?:\n\n|^)((?:(?:[ ]{4}|\t).*\n+)+)(\n*[ ]{0,3}[^ \t\n]|(?=~0))/g,function(_74,m1,m2){
var _77=m1;
var _78=m2;
_77=_79(_72(_77));
_77=_6(_77);
_77=_77.replace(/^\n+/g,"");
_77=_77.replace(/\n+$/g,"");
_77="<pre><code>"+_77+"\n</code></pre>";
return _1c(_77)+_78;
});
_73=_73.replace(/~0/,"");
return _73;
};
var _1c=function(_7a){
_7a=_7a.replace(/(^\n+|\n+$)/g,"");
return "\n\n~K"+(_3.push(_7a)-1)+"K\n\n";
};
var _23=function(_7b){
_7b=_7b.replace(/(^|[^\\])(`+)([^\r]*?[^`])\2(?!`)/gm,function(_7c,m1,m2,m3,m4){
var c=m3;
c=c.replace(/^([ \t]*)/g,"");
c=c.replace(/[ \t]*$/g,"");
c=_79(c);
return m1+"<code>"+c+"</code>";
});
return _7b;
};
var _79=function(_82){
_82=_82.replace(/&/g,"&amp;");
_82=_82.replace(/</g,"&lt;");
_82=_82.replace(/>/g,"&gt;");
_82=_2e(_82,"*_{}[]\\",false);
return _82;
};
var _29=function(_83){
//changed by M.S. to fix problem with trailing spaces
//_83=_83.replace(/(\*\*|__)(?=\S)([^\r]*?\S[*_]*)\1/g,"<strong>$2</strong>");
//_83=_83.replace(/(\*|_)(?=\S)([^\r]*?\S)\1/g,"<em>$2</em>");
_83=_83.replace(/(\*\*|__)(?=\S)([^\r]*?[*_]*)\1/g,"<strong>$2</strong>");
_83=_83.replace(/(\*|_)(?=\S)([^\r]*?)\1/g,"<em>$2</em>");
return _83;
};
var _1f=function(_84){
_84=_84.replace(/((^[ \t]*>[ \t]?.+\n(.+\n)*\n*)+)/gm,function(_85,m1){
var bq=m1;
bq=bq.replace(/^[ \t]*>[ \t]?/gm,"~0");
bq=bq.replace(/~0/g,"");
bq=bq.replace(/^[ \t]+$/gm,"");
bq=_9(bq);
bq=bq.replace(/(^|\n)/g,"$1  ");
bq=bq.replace(/(\s*<pre>[^\r]+?<\/pre>)/gm,function(_88,m1){
var pre=m1;
pre=pre.replace(/^  /mg,"~0");
pre=pre.replace(/~0/g,"");
return pre;
});
return _1c("<blockquote>\n"+bq+"\n</blockquote>");
});
return _84;
};
var _20=function(_8b){
_8b=_8b.replace(/^\n+/g,"");
_8b=_8b.replace(/\n+$/g,"");
var _8c=_8b.split(/\n{2,}/g);
var _8d=new Array();
var end=_8c.length;
for(var i=0;i<end;i++){
var str=_8c[i];
if(str.search(/~K(\d+)K/g)>=0){
_8d.push(str);
}else{
if(str.search(/\S/)>=0){
str=_21(str);
str=str.replace(/^([ \t]*)/g,"<p>");
str+="</p>";
_8d.push(str);
}
}
}
end=_8d.length;
for(var i=0;i<end;i++){
while(_8d[i].search(/~K(\d+)K/)>=0){
var _91=_3[RegExp.$1];
_91=_91.replace(/\$/g,"$$$$");
_8d[i]=_8d[i].replace(/~K\d+K/,_91);
}
}
return _8d.join("\n\n");
};
var _11=function(_92){
_92=_92.replace(/&(?!#?[xX]?(?:[0-9a-fA-F]+|\w+);)/g,"&amp;");
_92=_92.replace(/<(?![a-z\/?\$!])/gi,"&lt;");
return _92;
};
var _25=function(_93){
_93=_93.replace(/\\(\\)/g,_94);
_93=_93.replace(/\\([`*_{}\[\]()>#+-.!])/g,_94);
return _93;
};
var _28=function(_95){
_95=_95.replace(/<((https?|ftp|dict):[^'">\s]+)>/gi,"<a href=\"$1\">$1</a>");
_95=_95.replace(/<(?:mailto:)?([-.\w]+\@[-a-z0-9]+(\.[-a-z0-9]+)*\.[a-z]+)>/gi,function(_96,m1){
return _98(_a(m1));
});
return _95;
};
var _98=function(_99){
function char2hex(ch){
var _9b="0123456789ABCDEF";
var dec=ch.charCodeAt(0);
return (_9b.charAt(dec>>4)+_9b.charAt(dec&15));
}
var _9d=[function(ch){
return "&#"+ch.charCodeAt(0)+";";
},function(ch){
return "&#x"+char2hex(ch)+";";
},function(ch){
return ch;
}];
_99="mailto:"+_99;
_99=_99.replace(/./g,function(ch){
if(ch=="@"){
ch=_9d[Math.floor(Math.random()*2)](ch);
}else{
if(ch!=":"){
var r=Math.random();
ch=(r>0.9?_9d[2](ch):r>0.45?_9d[1](ch):_9d[0](ch));
}
}
return ch;
});
_99="<a href=\""+_99+"\">"+_99+"</a>";
_99=_99.replace(/">.+:/g,"\">");
return _99;
};
var _a=function(_a3){
_a3=_a3.replace(/~E(\d+)E/g,function(_a4,m1){
var _a6=parseInt(m1);
return String.fromCharCode(_a6);
});
return _a3;
};
var _72=function(_a7){
_a7=_a7.replace(/^(\t|[ ]{1,4})/gm,"~0");
_a7=_a7.replace(/~0/g,"");
return _a7;
};
var _6=function(_a8){
_a8=_a8.replace(/\t(?=\t)/g,"    ");
_a8=_a8.replace(/\t/g,"~A~B");
_a8=_a8.replace(/~B(.+?)~A/g,function(_a9,m1,m2){
var _ac=m1;
var _ad=4-_ac.length%4;
for(var i=0;i<_ad;i++){
_ac+=" ";
}
return _ac;
});
_a8=_a8.replace(/~A/g,"    ");
_a8=_a8.replace(/~B/g,"");
return _a8;
};
var _2e=function(_af,_b0,_b1){
var _b2="(["+_b0.replace(/([\[\]\\])/g,"\\$1")+"])";
if(_b1){
_b2="\\\\"+_b2;
}
var _b3=new RegExp(_b2,"g");
_af=_af.replace(_b3,_94);
return _af;
};
var _94=function(_b4,m1){
var _b6=m1.charCodeAt(0);
return "~E"+_b6+"E";
};
};



var converter;

function loadConverter() {
	converter = new Showdown.converter();
}

function markMeDown(field) {
	// console.log("field " + field);
	// console.log("field id " + field.id);
  var text = field.value;
	// console.log("text " + text);
  text = converter.makeHtml(text);
	// console.log("text 2" + text);
	if($(field.id + '_text')){
		$(field.id + '_text').innerHTML = text;
	}
}

function insertMarkdown(field, prefix, suffix) {   
  var selection;
  field = $(field);
  field.focus();
  
  // IE preliminary support
  if (document.selection) {
    selection = document.selection.createRange();
    var selectionText = (selection.text == '') ? 'enter text':selection.text;
    
	if (prefix.match(/\n[*]/) || prefix.match(/\n1[.]/)) {
		selectionText = selectionText.replace(/(\r\n|\n)/g, prefix);
		var duplicate = selection.duplicate();
		duplicate.moveStart('character', -1);
		prefix = (duplicate.text.match(/^(\r\n|\n)/)) ? prefix.replace(/(\r\n|\n)/, ''):prefix;
	}

    selection.text = prefix + selectionText + suffix;
  }
  
  // The rest
  if (field.selectionStart || field.selectionStart == '0') {
    var startSelection = field.selectionStart;
    var endSelection = field.selectionEnd;
    var selectionLength = endSelection - startSelection;
    var preSelection = field.value.substring(0, startSelection)
    var postSelection = field.value.substring(endSelection, field.value.length);
      
    if (typeof(selection) == 'undefined')
      selection = field.value.substring(startSelection, endSelection);
    
    if (prefix[0] == '\n' && prefix[prefix.length] != ' ' && preSelection[preSelection.length] != '\n' && !prefix.match(/\n[*]/) && !prefix.match(/\n1[.]/)) {
      var continuation;
      for(i = preSelection.length; (i >= 0 && preSelection[i] != '\n'); i--) {
        if(preSelection[i] == prefix[1]) {
          var newLineCheck = i - 1
          if(newLineCheck == 0 || preSelection[newLineCheck] == '\n')
            continuation = true;
        }
      }
      if (!continuation)
        prefix = '\n' + prefix;
    } else if (prefix.match(/\n[*]/) || prefix.match(/\n1[.]/)) {
		selection = selection.replace(/(\r\n|\n)/g, prefix);
		if (preSelection.substring(preSelection.length, 1).match(/(\r\n|\n)/)) {
			prefix = prefix.replace(/\n/, '');
		}
	}
        
    if (startSelection == endSelection) {
      var insertText = 'enter text'
      field.value = preSelection + prefix + insertText + suffix + postSelection;
      var startPoint = preSelection.length + prefix.length;
      var endPoint = preSelection.length + prefix.length + insertText.length;
    } else {
     field.value = preSelection + prefix + selection + suffix + postSelection;
     var startPoint = preSelection.length + prefix.length + selectionLength + suffix.length;
     var endPoint = startPoint;
    }
    
    field.blur(); field.focus(); // Reload preview
    field.setSelectionRange(startPoint, endPoint);
  }
}

function insertMarkdownLink(field, external) {
  var linkUrl = prompt('Please enter the address:', 'http://');
  if (linkUrl == null)
    return;
  else if (external)
    insertMarkdown(field, '<a href="' + linkUrl + '" target="_blank">', '</a>');
  else
    insertMarkdown(field, '[', '](' + linkUrl + ')');
}

function getNoLines(element) {
  var hardlines = element.value.split('\n');
  var total = hardlines.length;
  for (var i = 0, len = hardlines.length; i < len; i++) {
    total += Math.max(Math.round(hardlines[i].length / element.cols), 1) - 1;
  }
  return total;
}

function expandoCheck(element) {
  var lines = getNoLines(element);
  var aMaxSetting = parseInt(element.rows) * 0.9;
  var aMinSetting = parseInt(element.rows) * 0.6;
    
  if (((lines > aMaxSetting) || (lines < aMinSetting)) && lines > 16)
    element.rows = '' + (parseInt(getNoLines(element)) + 6);
}


// script.aculo.us sound.js v1.8.1, Thu Jan 03 22:07:12 -0500 2008

// Copyright (c) 2005-2007 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us)
//
// Based on code created by Jules Gravinese (http://www.webveteran.com/)
//
// 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/

Sound = {
  tracks: {},
  _enabled: true,
  template:
    new Template('<embed style="height:0" id="sound_#{track}_#{id}" src="#{url}" loop="false" autostart="true" hidden="true"/>'),
  enable: function(){
    Sound._enabled = true;
  },
  disable: function(){
    Sound._enabled = false;
  },
  play: function(url){
    if(!Sound._enabled) return;
    var options = Object.extend({
      track: 'global', url: url, replace: false
    }, arguments[1] || {});
    
    if(options.replace && this.tracks[options.track]) {
      $R(0, this.tracks[options.track].id).each(function(id){
        var sound = $('sound_'+options.track+'_'+id);
        sound.Stop && sound.Stop();
        sound.remove();
      })
      this.tracks[options.track] = null;
    }
      
    if(!this.tracks[options.track])
      this.tracks[options.track] = { id: 0 }
    else
      this.tracks[options.track].id++;
      
    options.id = this.tracks[options.track].id;
    $$('body')[0].insert( 
      Prototype.Browser.IE ? new Element('bgsound',{
        id: 'sound_'+options.track+'_'+options.id,
        src: options.url, loop: 1, autostart: true
      }) : Sound.template.evaluate(options));
  }
};

if(Prototype.Browser.Gecko && navigator.userAgent.indexOf("Win") > 0){
  if(navigator.plugins && $A(navigator.plugins).detect(function(p){ return p.name.indexOf('QuickTime') != -1 }))
    Sound.template = new Template('<object id="sound_#{track}_#{id}" width="0" height="0" type="audio/mpeg" data="#{url}"/>')
  else
    Sound.play = function(){}
}


/* Timeframe, version 0.2
 * (c) 2008 Stephen Celis
 *
 * Freely distributable under the terms of an MIT-style license. 
 * ------------------------------------------------------------- */

if (typeof Prototype == 'undefined' || parseFloat(Prototype.Version.substring(0, 3)) < 1.6)
  throw 'Timeframe requires Prototype version 1.6 or greater.';

// Checks for localized Datejs before defaulting to 'en-US'
var Locale = $H({
  format:     (typeof Date.CultureInfo == 'undefined' ? '%b %d, %Y' : Date.CultureInfo.formatPatterns.shortDate),
  monthNames: (typeof Date.CultureInfo == 'undefined' ? $w('January February March April May June July August September October November December') : Date.CultureInfo.monthNames),
  dayNames:   (typeof Date.CultureInfo == 'undefined' ? $w('Sunday Monday Tuesday Wednesday Thursday Friday Saturday') : Date.CultureInfo.dayNames),
  weekOffset: (typeof Date.CultureInfo == 'undefined' ? 0 : Date.CultureInfo.firstDayOfWeek)
});

var Timeframes = [];

var Timeframe = Class.create({
  Version: '0.2',

  initialize: function(element, options) {
    Timeframes.push(this);

    this.element = $(element);
    this.element.addClassName('timeframe_calendar')
    this.options = $H({ months: 2 }).merge(options || {});;
    this.months = this.options.get('months');

    this.weekdayNames = Locale.get('dayNames');
    this.monthNames   = Locale.get('monthNames');
    this.format       = this.options.get('format')     || Locale.get('format');
    this.weekOffset   = this.options.get('weekOffset') || Locale.get('weekOffset');
    this.maxRange = this.options.get('maxRange');

    this.buttons = $H({
      previous: $H({ label: '&larr;', element: $(this.options.get('previousButton')) }),
      today:    $H({ label: 'T',      element: $(this.options.get('todayButton')) }),
      reset:    $H({ label: 'R',      element: $(this.options.get('resetButton')) }),
      next:     $H({ label: '&rarr;', element: $(this.options.get('nextButton')) })
    })
    this.fields = $H({ start: $(this.options.get('startField')), end: $(this.options.get('endField')) });

    this.range = $H({});
    this._buildButtons()._buildFields();
    this.earliest = Date.parseToObject(this.options.get('earliest'));
    this.latest   = Date.parseToObject(this.options.get('latest'));

    this.calendars = [];
    this.element.insert(new Element('div', { id: this.element.id + '_container' }));
    this.months.times(function(month) { this.createCalendar(month) }.bind(this));

    this.register().populate().refreshRange();
  },

  // Scaffolding

  createCalendar: function() {
    var calendar = new Element('table', {
      id: this.element.id + '_calendar_' + this.calendars.length, border: 0, cellspacing: 0, cellpadding: 5
    });
    calendar.insert(new Element('caption'));

    var head = new Element('thead');
    var row  = new Element('tr');
    this.weekdayNames.length.times(function(column) {
      var weekday = this.weekdayNames[(column + this.weekOffset) % 7];
      var cell = new Element('th', { scope: 'col', abbr: weekday }).update(weekday.substring(0,1));
      row.insert(cell);
    }.bind(this));
    head.insert(row);
    calendar.insert(head);

    var body = new Element('tbody');
    (6).times(function(rowNumber) {
      var row = new Element('tr');
      this.weekdayNames.length.times(function(column) {
        var cell = new Element('td');
        row.insert(cell);
      });
      body.insert(row);
    }.bind(this));
    calendar.insert(body);

    this.element.down('div#' + this.element.id + '_container').insert(calendar);
    this.calendars.push(calendar);
    this.months = this.calendars.length;
    return this;
  },

  destroyCalendar: function() {
    this.calendars.pop().remove();
    this.months = this.calendars.length;
    return this;
  },

  populate: function() {
    var month = this.date.neutral();
    month.setDate(1);
    this.calendars.each(function(calendar) {
      var caption = calendar.select('caption').first();
      caption.update(this.monthNames[month.getMonth()] + ' ' + month.getFullYear());

      var iterator = new Date(month);
      var offset = (iterator.getDay() - this.weekOffset) % 7;
      var inactive = offset > 0 ? 'pre beyond' : false;
      iterator.setDate(iterator.getDate() - offset);
      if (iterator.getDate() > 1 && !inactive) {
        iterator.setDate(iterator.getDate() - 7);
        if (iterator.getDate() > 1) inactive = 'pre beyond';
      }

      calendar.select('td').each(function(day) {
        day.date = new Date(iterator); // Is this expensive (we unload these later)? We could store the epoch time instead.
        day.update(day.date.getDate()).writeAttribute('class', inactive || 'active');
        if ((this.earliest && day.date < this.earliest) || (this.latest && day.date > this.latest))
          day.addClassName('unselectable');
        else
          day.addClassName('selectable');
        if (iterator.toString() === new Date().neutral().toString()) day.addClassName('today');
        day.baseClass = day.readAttribute('class');

        iterator.setDate(iterator.getDate() + 1);
        if (iterator.getDate() == 1) inactive = inactive ? false : 'post beyond';
      }.bind(this));

      month.setMonth(month.getMonth() + 1);
    }.bind(this));
    return this;
  },

  _buildButtons: function() {
    var buttonList = new Element('ul', { id: this.element.id + '_menu', className: 'timeframe_menu' });
    this.buttons.each(function(pair) {
      if (pair.value.get('element'))
        pair.value.get('element').addClassName('timeframe_button').addClassName(pair.key);
      else {
        var item = new Element('li');
        var button = new Element('a', { className: 'timeframe_button ' + pair.key, href: '#', onclick: 'return false;' }).update(pair.value.get('label'));
        button.onclick = function() { return false; };
        pair.value.set('element', button);
        item.insert(button);
        buttonList.insert(item);
      }
    }.bind(this))
    if (buttonList.childNodes.length > 0) this.element.insert({ top: buttonList });
    this.clearButton = new Element('span', { className: 'clear' }).update(new Element('span').update('X'));
    return this;
  },

  _buildFields: function() {
    var fieldset = new Element('div', { id: this.element.id + '_fields', className: 'timeframe_fields' });
    this.fields.each(function(pair) {
      if (pair.value)
        pair.value.addClassName('timeframe_field').addClassName(pair.key);
      else {
        var container = new Element('div', { id: pair.key + this.element.id + '_field_container' });
        this.fields.set(pair.key, new Element('input', { id: this.element.id + '_' + pair.key + 'field', name: pair.key + 'field', type: 'text', value: '' }));
        container.insert(new Element('label', { 'for': pair.key + 'field' }).update(pair.key));
        container.insert(this.fields.get(pair.key));
        fieldset.insert(container);
      }
    }.bind(this));
    if (fieldset.childNodes.length > 0) this.element.insert(fieldset);
    this.parseField('start').refreshField('start').parseField('end').refreshField('end').initDate = new Date(this.date);
    return this;
  },

  // Event registration

  register: function() {
    document.observe('click', this.eventClick.bind(this));
    this.element.observe('mousedown', this.eventMouseDown.bind(this));
    this.element.observe('mouseover', this.eventMouseOver.bind(this));
    document.observe('mouseup', this.eventMouseUp.bind(this));
    document.observe('unload', this.unregister.bind(this));
    // mousemove listener for Opera in _disableTextSelection
    return this._registerFieldObserver('start')._registerFieldObserver('end')._disableTextSelection();
  },

  unregister: function() {
    this.element.select('td').each(function(day) { day.date = day.baseClass = null; });
  },

  _registerFieldObserver: function(fieldName) {
    var field = this.fields.get(fieldName);
    field.observe('focus', function() { field.hasFocus = true; this.parseField(fieldName, true); }.bind(this));
    field.observe('blur', function() { this.refreshField(fieldName); }.bind(this));
    new Form.Element.Observer(field, 0.2, function(element, value) { if (element.hasFocus) this.parseField(fieldName, true); }.bind(this));
    return this;
  },

  _disableTextSelection: function() {
    if (Prototype.Browser.IE) {
      this.element.onselectstart = function(event) {
        if (!/input|textarea/i.test(Event.element(event).tagName)) return false;
      };
    } else if (Prototype.Browser.Opera) {
      document.observe('mousemove', this.handleMouseMove.bind(this));
    } else {
      this.element.onmousedown = function(event) {
        if (!/input|textarea/i.test(Event.element(event).tagName)) return false;
      };
    }
    return this;
  },

  // Fields

  parseField: function(fieldName, populate) {
    var field = this.fields.get(fieldName);
    var date = Date.parseToObject($F(this.fields.get(fieldName)));
    var failure = this.validateField(fieldName, date);
    if (failure != 'hard') {
      this.range.set(fieldName, date);
      field.removeClassName('error');
    } else if (field.hasFocus)
      field.addClassName('error');
    var date = Date.parseToObject(this.range.get(fieldName));
    this.date = date || new Date();
    if (populate && date) this.populate()
    this.refreshRange();
    return this;
  },

  refreshField: function(fieldName) {
    var field = this.fields.get(fieldName);
    var initValue = $F(field);
    if (this.range.get(fieldName)) {
      field.setValue(typeof Date.CultureInfo == 'undefined' ?
        this.range.get(fieldName).strftime(this.format) :
        this.range.get(fieldName).toString(this.format));
    } else
      field.setValue('');
    field.hasFocus && $F(field) == '' && initValue != '' ? field.addClassName('error') : field.removeClassName('error');
    field.hasFocus = false;
    return this;
  },

  validateField: function(fieldName, date) {
    if (!date) return;
    var error;
    if ((this.earliest && date < this.earliest) || (this.latest && date > this.latest))
      error = 'hard';
    else if (fieldName == 'start' && this.range.get('end') && date > this.range.get('end'))
      error = 'soft';
    else if (fieldName == 'end' && this.range.get('start') && date < this.range.get('start'))
      error = 'soft';
    return error;
  },

  // Event handling

  eventClick: function(event) {
    if (!event.element().ancestors) return;
    var el;
    if (el = event.findElement('a.timeframe_button'))
      this.handleButtonClick(event, el);
  },

  eventMouseDown: function(event) {
    if (!event.element().ancestors) return;
    var el, em;
    if (el = event.findElement('span.clear')) {
      el.down('span').addClassName('active');
      if (em = event.findElement('td.selectable'))
        this.handleDateClick(em, true);
    } else if (el = event.findElement('td.selectable'))
      this.handleDateClick(el);
    else return;
  },

  handleButtonClick: function(event, element) {
    var el;
    var movement = this.months > 1 ? this.months - 1 : 1;
    if (element.hasClassName('next'))
      this.date.setMonth(this.date.getMonth() + movement);
    else if (element.hasClassName('previous'))
      this.date.setMonth(this.date.getMonth() - movement);
    else if (element.hasClassName('today'))
      this.date = new Date();
    else if (element.hasClassName('reset'))
      this.reset();
    this.populate().refreshRange();
  },

  reset: function() {
    this.fields.get('start').setValue(this.fields.get('start').defaultValue || '');
    this.fields.get('end').setValue(this.fields.get('end').defaultValue || '');
    this.date = new Date(this.initDate);
    this.parseField('start').refreshField('start').parseField('end').refreshField('end');
  },

  handleDateClick: function(element, couldClear) {
    this.mousedown = this.dragging = true;
    if (this.stuck) {
      this.stuck = false;
      return;
    } else if (couldClear) {
      if (!element.hasClassName('startrange')) return;
    } else if (this.maxRange != 1) {
      this.stuck = true;
      setTimeout(function() { if (this.mousedown) this.stuck = false; }.bind(this), 200);
    }
    this.getPoint(element.date);
  },

  getPoint: function(date) {
    if (this.range.get('start') && this.range.get('start').toString() == date && this.range.get('end'))
      this.startdrag = this.range.get('end');
    else {
      this.clearButton.hide();
      if (this.range.get('end') && this.range.get('end').toString() == date)
        this.startdrag = this.range.get('start');
      else
        this.startdrag = this.range.set('start', this.range.set('end', date));
    }
    this.refreshRange();
  },

  eventMouseOver: function(event) {
    var el;
    if (!this.dragging)
      this.toggleClearButton(event);
    else if (event.findElement('span.clear span.active'));
    else if (el = event.findElement('td.selectable'))
      this.extendRange(el.date);
    else this.toggleClearButton(event);
  },

  toggleClearButton: function(event) {
    var el;
    if(event.element().ancestors && event.findElement('td.selected')) {
      if(el = this.element.select('#' + this.calendars.first().id +  ' .pre.selected').first());
      else if(el = this.element.select('.active.selected').first());
      else if(el = this.element.select('.post.selected').first());
      if(el) Element.insert(el, { top: this.clearButton });
      this.clearButton.show().select('span').first().removeClassName('active');        
    } else
      this.clearButton.hide();
  },

  extendRange: function(date) {
    var start, end;
    this.clearButton.hide();
    if (date > this.startdrag) {
      start = this.startdrag;
      end = date;
    } else if (date < this.startdrag) {
      start = date;
      end = this.startdrag;
    } else
      start = end = date;
    this.validateRange(start, end);
    this.refreshRange();
  },

  validateRange: function(start, end) {
    if (this.maxRange) {
      var range = this.maxRange - 1;
      var days = parseInt((end - start) / 86400000);
      if (days > range) {
        if (start == this.startdrag) {
          end = new Date(this.startdrag);
          end.setDate(end.getDate() + range);
        } else {
          start = new Date(this.startdrag);
          start.setDate(start.getDate() - range);
        }
      }
    }
    this.range.set('start', start);
    this.range.set('end', end);
  },

  eventMouseUp: function(event) {
    if (!this.dragging) return;
    if (!this.stuck) {
      this.dragging = false;
      if (event.findElement('span.clear span.active'))
        this.clearRange();
    }
    this.mousedown = false;
    this.refreshRange();
  },

  clearRange: function() {
    this.clearButton.hide().select('span').first().removeClassName('active');
    this.range.set('start', this.range.set('end', null));
    this.refreshField('start').refreshField('end');
    if (this.options.keys().include('onClear')) this.options.get('onClear')();
  },

  refreshRange: function() {
    this.element.select('td').each(function(day) {
      day.writeAttribute('class', day.baseClass);
      if (this.range.get('start') && this.range.get('end') && this.range.get('start') <= day.date && day.date <= this.range.get('end')) {
        var baseClass = day.hasClassName('beyond') ? 'beyond_' : day.hasClassName('today') ? 'today_' : null;
        var state = this.stuck || this.mousedown ? 'stuck' : 'selected';
        if (baseClass) day.addClassName(baseClass + state);
        day.addClassName(state);
        var rangeClass = '';
        if (this.range.get('start').toString() == day.date) rangeClass += 'start';
        if (this.range.get('end').toString() == day.date) rangeClass += 'end';
        if (rangeClass.length > 0) day.addClassName(rangeClass + 'range');
      }
      if (Prototype.Browser.Opera) {
        day.unselectable = 'on'; // Trick Opera into refreshing the selection 
        day.unselectable = null;
      }
    }.bind(this));
    if (this.dragging) this.refreshField('start').refreshField('end');
  },

  setRange: function(start, end) {
    var range = $H({ start: start, end: end });
    range.each(function(pair) {
      this.range.set(pair.key, Date.parseToObject(pair.value));
      this.refreshField(pair.key);
      this.parseField(pair.key, true);
    }.bind(this));
    return this;
  },

  handleMouseMove: function(event) {
    if (event.findElement('#' + this.element.id + ' td')) window.getSelection().removeAllRanges(); // More Opera trickery
  }
});

Object.extend(Date, {
  parseToObject: function(string) {
    var date = Date.parse(string);
    if (!date) return null;
    date = new Date(date);
    return (date == 'Invalid Date' || date == 'NaN') ? null : date.neutral();
  }
});

Object.extend(Date.prototype, {
  // modified from http://alternateidea.com/blog/articles/2008/2/8/a-strftime-for-prototype
  strftime: function(format) {
    var day = this.getDay(), month = this.getMonth();
    var hours = this.getHours(), minutes = this.getMinutes();
    function pad(num) { return num.toPaddedString(2); };

    return format.gsub(/\%([aAbBcdHImMpSwyY])/, function(part) {
      switch(part[1]) {
        case 'a': return Locale.get('dayNames').invoke('substring', 0, 3)[day].escapeHTML(); break;
        case 'A': return Locale.get('dayNames')[day].escapeHTML(); break;
        case 'b': return Locale.get('monthNames').invoke('substring', 0, 3)[month].escapeHTML(); break;
        case 'B': return Locale.get('monthNames')[month].escapeHTML(); break;
        case 'c': return this.toString(); break;
        case 'd': return pad(this.getDate()); break;
        case 'H': return pad(hours); break;
        case 'I': return (hours % 12 == 0) ? 12 : pad(hours % 12); break;
        case 'm': return pad(month + 1); break;
        case 'M': return pad(minutes); break;
        case 'p': return hours >= 12 ? 'PM' : 'AM'; break;
        case 'S': return pad(this.getSeconds()); break;
        case 'w': return day; break;
        case 'y': return pad(this.getFullYear() % 100); break;
        case 'Y': return this.getFullYear().toString(); break;
      }
    }.bind(this));
  },

  neutral: function() {
    return new Date(this.getFullYear(), this.getMonth(), this.getDate(), 12);
  }
});


var WidthValidate = Class.create({
	initialize: function(width, inputs) {
		this.options = Object.extend({
			onPass: function() {},
			onFail: function() {},
			textSize: false,
			element: false,
			//this is a template element to be cloned
			validateWrap: false
		}, arguments[2] || {});
		this.width = width;
		this.makeElement(this.options.element);
		this.keypressHandler = this.keypressify.bind(this);
		this.unkeypressHandler = this.unkeypressify.bind(this);
		this.inputs = [];
		((inputs.constructor == Array) ? inputs:[inputs]).each(this.register.bind(this));
	},
	makeElement: function(element) {
		if (element) {
			this.element = $(element);
		} else {
			this.element = document.createElement('span');
			this.element.style.position = 'absolute';
			this.element.style.left = (this.width*-1-10)+'px';
			this.element.style.visibility = 'hidden';
			this.element.style.fontSize = (this.options.textSize) ? this.options.textSize+'px':'';
			document.body.appendChild(this.element);
			//set the orig height
			this.element.innerHTML = '&nbsp;';
			this.element.innerHTML = '';
		}

	},
	register: function(input) {
		Event.observe(input, 'focus', this.keypressHandler);
		Event.observe(input, 'blur', this.unkeypressHandler);
		this.inputs.push(new WidthValidate.Input(input));
	},
	unregister: function(input) {
		input = this.getInputObjectFromInputElement(input);
		Event.stopObserving(input, 'focus', this.keypressHandler);
		Event.stopObserving(input, 'blur', this.unkeypressHandler);
		inputs = [];
		for (var i=0;i<this.inputs.length;i++) {
			if (this.inputs[i] != input) {
				inputs.push(this.inputs[i]);
			}
		}
		this.inputs = inputs;
	},
	keypressify: function(event) {
		if (this.interval) {
			this.clearInterval();
		}
		this.currentInput = Event.element(event);
		this.setInterval();
	},
	unkeypressify: function(event) {
		this.clearInterval();
		this.currentInput = false;
	},
	validate: function() {
		var changed = false;
		this.element.innerHTML = '';
		for (var i=0;i<this.inputs.length;i++) {
			changed = (this.inputs[i].compare()) ? changed:true;
			if (this.options.validateWrap) {
				var wrap = this.options.validateWrap.cloneNode(true);
				wrap.innerHTML = this.inputs[i].getValue();
				this.element.appendChild(wrap);
			} else {
				this.element.innerHTML += this.inputs[i].getValue();
			}
		}

		if (changed) {
			if (this.element.offsetWidth <= this.width) {
				this.options.onPass(this.currentInput);
				this.resetOlds();
				return true;
			} else {
				this.clearInterval();
				this.options.onFail(this.currentInput);
				this.resetValues();
				this.setInterval();
				return false;
			}
		}
	},
	setInterval: function() {
		if (!this.interval) {
			this.validateHandler = this.validate.bind(this);
			this.interval = window.setInterval(this.validateHandler, 50);
		}
	},
	clearInterval: function() {
		if (this.interval) {
			window.clearInterval(this.interval);
			this.interval = false;
			this.validateHandler = false;
		}
	},
	resetOlds: function() {
		this.inputs.each(function(input) {
			input.resetOld();
		});
	},
	resetValues: function() {
		this.inputs.each(function(input) {
			input.resetValue();
		});
	},
	getInputObjectFromInputElement: function(inputEl) {
		for (var i=0;i<this.inputs.length;i++) {
			if (this.inputs[i].input == $(inputEl)) {
				return this.inputs[i];
			}
		}
		return false;
	}
});

WidthValidate.Input = Class.create({
	initialize: function(input) {
		this.input = $(input);
		this.resetOld();
	},
	compare: function() {
		return (this.input.value == this.oldValue);
	},
	resetOld: function() {
		this.oldValue = this.input.value;
	},
	resetValue: function() {
		this.input.value = this.oldValue;
	},
	getValue: function() {
		return this.input.value;
	}
});


/* global search box enhancements
                    panelLeft = where.left - 80;
\__________________________________________________*/

Event.observe(window, 'load', clearIESubmitButtons, false);
// this was the search box function, revisit this later
function clearIESubmitButtons() {	
	if (Prototype.Browser.IE) {
		$A($$('.fields .buttons input[type=submit]')).each( function(button){
			button.value = '';
		} );
	}
}


/* markets filter box placement
\__________________________________________________*/

Event.observe(window, 'load', filterBoxes, false);

function filterBoxes() {
	
	var filters = $A($$('a.filter'));
	
	if ( filters.length > 0 ) {
		for(var i=0; i<filters.length; i++) {
			offset = Element.cumulativeOffset( filters[i] );
			target = $( filters[i].id.replace('_trigger','') );
			target.style.top = ( offset.top + 12 ) + 'px';
			target.style.left = ( offset.left - 174 ) + 'px';
		}
	}
}

/* markets sorting
\__________________________________________________*/

/*Event.observe(window, 'load', marketSorting, false);

function marketSorting() {
	
	var grid = document.getElementById('market_grid');
	
	if (grid) {
		var fields = grid.getElementsByTagName('th');
	}
	
	if (fields) {
		for (var i=0; i<fields.length; i++) {
			
			Event.observe(fields[i], 'click', function(field){
				
				var others = document.getElementById('market_grid').getElementsByTagName('th');
				for (var i=0;i<others.length;i++) {
					others[i].className = others[i].className.replace(/(^|\s+)sorted(\s+|$)/g, ' ');
					
					if (this.className == 'population') {
						if (others[i].className == 'factor') others[i].className += ' sorted';
					} else if (this.className == 'factor') {
						if (others[i].className == 'population') others[i].className += ' sorted';
					}
					
				}
				
				this.className += ' sorted';
				
			}, false);
			
		}
		
		
		
	}
	
}*/
function displaySorted(column, direction) {
	var grid = $('market_grid') || $('stations_grid') || $('owner_grid');
	var others = grid.getElementsByTagName('th');
	for (var i=0;i<others.length;i++) {
		others[i].className = others[i].className.replace(/(^|\s+)(sorted)|(reverse)(\s+|$)/g, ' ');
		if (column.options.sortElement.className == 'population') {
			if (others[i].className == 'factor') others[i].className += ' sorted';
		} else if (column.options.sortElement.className == 'factor') {
			if (others[i].className == 'population') others[i].className += ' sorted';
		}

	}
	var tds = grid.getElementsByTagName('td');
	for (var i=0;i<tds.length;i++) {
		tds[i].className = tds[i].className.replace(/(^|\s+)sorted(\s+|$)/g, ' ');
		if (tds[i].className.match(new RegExp(column.name))) {
			tds[i].className += ' sorted';
		}
	}

	column.options.sortElement.className += ' sorted';
	if (direction == 'reverse') {
		column.options.sortElement.className += ' reverse';
	}
	evenOddify(grid.getElementsByTagName('table')[0]);
}

function evenOddify(table, rows) {
	rows = $$('table')[0].select('tbody tr');
	var trueCount = 0;
	// table = ($(table).getElementsByTagName('tbody')[0]) ? $(table):$(table).getElementsByTagname('tbody')[0];
	// rows = (rows) ? rows:table.getElementsByTagName('tr');
	// var trueCount = 0;
	for (var i=0;i<rows.length;i++) {
		$(rows[i]).removeClassName('even');
		$(rows[i]).removeClassName('odd');
		if (!rows[i].style.display.match(/none/)) {
			$(rows[i]).addClassName((trueCount%2) ? 'even':'odd');
			trueCount++;
		}
	}
}



/* subnav
\__________________________________________________*/

Event.observe(window, 'load', subnavigation, false);
Event.observe(window, 'resize', subnavigation, false);

// I'm going to want to rewrite this using lowpro as a behavior

// Event.addBehavior({
//   '#navigation ul li a:click': function(e) {
// 		Event.stop(e);
// 		alert("OMFG a menu!!!");
// 	}
// });


function subnavigation() {
	
	var breaking = false;
	if($('breaking')){breaking = true;}
	
	var nav = $('navigation');

	if (nav) {
		$(nav).observe('mouseover', function(){
			$$('.panel.open').invoke('removeClassName', 'open');
			if(breaking){$('breaking').style.zIndex = '0';}
		}); // Close any tooltips to avoid any weird overlaps
		
		// invisible subnavs
		var subnavs = $A($$('#navigation ul.subnavigation'));
		
		// Attach flyout menus and rollover behaviors
		subnavs.each(function(subnav){				
			var items = $A($$('#' + subnav.id + ' li'));		
			var hasSubmenus = items.length > 0 ? true : false;
			var trigger = $(subnav.id.substring(3)).firstChild;
			var triggerPosition = Element.cumulativeOffset(trigger);
				subnav.style.top = triggerPosition.top + 26 + 'px';
				subnav.style.left = triggerPosition.left + 'px';
				subnav.style.height = (items.length * 24) + 'px';
				
			Event.observe(trigger, 'mouseover', function(event){
				trigger.addClassName('on');
				if(hasSubmenus){
					subnav.addClassName('on');	
				}
			});
			
			Event.observe(trigger, 'mouseout', function(event){
				var toElement = (document.all) ? event.toElement:event.relatedTarget;
				if (!toElement || !$(toElement).descendantOf(trigger.parentNode) ) {
					trigger.removeClassName('on');
					if(hasSubmenus){
						subnav.removeClassName('on');
					}
				}
			});
			
			Event.observe(subnav, 'mouseout', function(event) {
				var toElement = (document.all) ? event.toElement : event.relatedTarget;
				if ( !toElement.descendantOf(trigger.parentNode) ) {
					trigger.removeClassName('on');
					if(hasSubmenus){
						subnav.removeClassName('on');	
					}	
				}
			});
		});
		
	}
}


/* start iMarquee
\__________________________________________________*/

Event.observe(window, 'load', function() {
	var reports = $$('#ticker .tickertape .report');
	if (reports && reports.length) {
		Resizer.initialize();
		for(var i=0; i<reports.length; i++) {
			new OneLiner(reports[i]);
		}
		new iMarquee('ticker', 'iMarquee', 'tickertape', 'report', {manualForward: 'nextMarquee', manualBackward: 'prevMarquee', pauseOnHover: true});
	}
});


/* tray controls
\__________________________________________________*/

Event.observe(window, 'load', traycontrols, false);

function traycontrols() {
	
	if (!$('tray_controls')) {
		return false;
	}

	var controls = $('tray_controls');
	var close = $$('#tray_controls a.close')[0];
	var more = $$('#tray_controls a.more')[0];
	var all_news = $$('#tray_controls a.all_news')[0];
	
	function fixIE() {
		// terrible terrible fix for boxes not clearing the news tray
		if(Prototype.Browser.IE) {
			var boxes = document.getElementsByClassName('newsOption');
			for(var b = 0; b < boxes.length; b++) {
				boxes[b].style.display = 'none';
				boxes[b].style.display = 'block';
			}
		}
	}
	
	if ( !$('homepage_body') ) { // not home page
		
		Event.observe(more, 'click', function(){	
			Effect.BlindDown('tray', {
				duration: 0.5,
				afterFinish: function() {
					// unclear if this is still necessary after removing slider & tray
					fixIE(); 
				}
			});
			
			this.style.display = 'none';
			close.style.display = 'block';
			all_news.style.display = 'block';
			controls.addClassName('open');
			
		});
		
		Event.observe(close, 'click', function(){

			Effect.BlindUp('tray', {
				duration: 0.5,
				afterFinish: function() {
					fixIE();
				}
			});

			this.style.display = 'none';
			more.style.display = 'block';
			all_news.style.display = 'none';
			controls.removeClassName('open');
			
		});
		
	} else {

		all_news.style.display = 'block';
	
	}
		
}
function newsTips() {
	$A($$('#news_item a.icon')).each(setupTip);
}

Event.observe(window, 'load', tooltips);
function tooltips() {
	
//	var tools = $A($$('.meta .do'));
	var tools = $A($$('.meta .do a.icon'));
	var panelHeight;
	var discuss = false;

	tools.each(setupTip);

}	

function setupTip(tool) {
	if ($(tool.parentNode).getElementsByClassName('pane_email')[0]) {
		$(tool.parentNode).getElementsByClassName('pane_email')[0].style.display = 'none';

	}
	$A($(tool.parentNode).getElementsByClassName('tab_email')).each(function(email) {
		email.style.cursor = 'pointer';
		Event.observe(email, 'click', showHide, false);
	});
	$A($(tool.parentNode).getElementsByClassName('tab_share')).each(function(share) {
		share.style.cursor = 'pointer';
		Event.observe(share, 'click', showHide, false);
	});
	
	function showHide() {
		this.parentNode.parentNode.getElementsByClassName('pane_email')[0].style.display = (this.hasClassName('tab_email')) ? 'block':'none';
		this.parentNode.parentNode.getElementsByClassName('pane_share')[0].style.display = (this.hasClassName('tab_email')) ? 'none':'block';
		(this.hasClassName('tab_email')) ? $(this.parentNode).addClassName('emailing'):$(this.parentNode).removeClassName('emailing');
		positionTip($(this.parentNode.parentNode.parentNode).getElementsByClassName('icon')[0], this.parentNode.parentNode);
	}
    
	Event.observe(tool, 'mouseover', function(){
    if ($$('.panel.open').size() == 0){ // don't show tips if a panel is open
      var panel = $(this.parentNode).getElementsByClassName('panel')[0];
      if (panel && !panel.hasClassName('open')) {
        var tip = $(this.parentNode).getElementsByClassName('tip')[0];
        tip.addClassName('open');
        positionTip(this, tip);
      }
    }
	}, false);
	Event.observe(tool, 'mouseout', function(){
		var tip = $(this.parentNode).getElementsByClassName('tip')[0];
		tip.removeClassName('open');
	}, false);
	
	Event.observe(tool, 'click', onToolClick, false);
	
	function onToolClick() { 
		if(tool.parentNode.className.indexOf('discuss', 0) < 0) {
			var panel = $(this.parentNode).getElementsByClassName('panel')[0];
			var tip = $(this.parentNode).getElementsByClassName('tip')[0];
			if ($(panel).hasClassName('open')) {
				panel.removeClassName('open');
				window.clearInterval(window.checkBody);
				if(breaking){$('breaking').style.zIndex = '0';}
			} else {
				$A($$('.panel')).each(function(panel){
					if ($(panel).hasClassName('open')) {
						$(panel).removeClassName('open');
					}
				});
				panel.addClassName('open');
				panel.style.zIndex = '4000';
				positionTip(this, panel);
				tip.removeClassName('open');
				window.docHeight = document.body.offsetHeight;
				window.checkBody = window.setInterval(checkBody, 50);
				
				
				var closeLinks = panel.getElementsByClassName('panel_close');
				for(var z = 0; z < closeLinks.length; z++) {
					closeLinks[z].onclick = function() { return false; }
					Event.observe(closeLinks[z], 'click', function() {
						panel.removeClassName('open');
						if(breaking){$('breaking').style.zIndex='0';}
						return false;
					}, false);
				}
				if(breaking){$('breaking').style.zIndex = '6';}
			}
		}
		return false;
	};
		
	function checkBody() {
		if (window.docHeight != document.body.offsetHeight) {
			$A($$('.panel')).each(function(panel) {
				if (panel.hasClassName('open')) {
					positionTip($(panel.parentNode).getElementsByClassName('icon')[0], panel);
				}
			});
			window.docHeight = document.body.offsetHeight;
		}
	}

	function positionTip(tool, tip) {
		var left = (tool.positionedOffset().left-Math.floor(tip.offsetWidth/2)+Math.floor(tool.offsetWidth/2));
		left += (tip.hasClassName('panel')) ? 0:1;
		if (tip.hasClassName('panel')) {
			tip.style.left = Math.max(left, 0)+'px';
			var pointer = tip.getElementsByClassName('pointer')[0];
			if (left < 0) {
				pointer.style.position = 'relative';
				pointer.style.left = (tool.cumulativeOffset().left+Math.floor(tool.offsetWidth/2)-Math.floor(pointer.offsetWidth/2))+'px';
			} else {
				pointer.style.position = '';
				pointer.style.left = '';
			}
		} else {
			tip.style.left = left+'px';
		}
		tip.style.top = (tool.positionedOffset().top-tip.offsetHeight-2)+'px';
		tip.style.zIndex = 10000;
	}

	Event.observe(window, 'resize', function() {
		$A($$('.panel')).each(function(panel) {
			if ($(panel).hasClassName('open')) {
				positionTip($(panel.parentNode).getElementsByClassName('icon')[0], panel);
			}
		});
	}, false);
		
};

function positionThing(thing, top, left) {
	thing.style.top =  top + 'px';
	thing.style.left = left + 'px';
}


/* news slider
\__________________________________________________*/

Event.observe(window, 'load', handlePop, false);

function handlePop() {
	var handle = $('handle');
	var bubble = $('hour');
	if (handle && bubble) {
		Event.observe(handle, 'mousedown', function(){
			bubble.style.display = 'block';
		}, false);
	}
}


/* popup panels
\__________________________________________________*/

/*Event.observe(window, 'load', globalPanels, false);

function globalPanels() {
	positionPanel('login', 'login_area', -183, -70);
	positionPanel('register', 'register_area', -10, 0);
}*/


/* utility
\__________________________________________________*/

/*function positionPanel(trigger, target, x, y) {
	
	// get elements
	
	var button = $(trigger);
	var panel = $(target);
	
	// IE needs extra attention
	
	if (Prototype.Browser.IE) {
		//x = x + 68;
		x = x + 30;
		y = y + 76;
	}
	
	// position!
	if (panel && target) {
		panel.style.top = button.offsetTop + button.getDimensions().height + y + 'px';
		panel.style.left = button.offsetLeft + x + 'px';
	}
	
}*/


/* article detail tabs
\__________________________________________________*/

Event.observe(window, 'load', featureTabs, false);

function featureTabs() {
	var tabDiv = document.getElementById('riFeaturesAuthorsTabs');

	if (tabDiv) {
		var tabs = tabDiv.getElementsByTagName('li');
		var panes = $(tabDiv.parentNode.parentNode).getElementsByClassName('pane');
		
		for(var i=0; i<tabs.length; i++) {
			
			tabs[i].getElementsByTagName('a')[0].href = 'javascript:void(0)';
			
			listen(tabs[i], 'click', bind(function() {
				var others = document.getElementById('riFeaturesAuthorsTabs').getElementsByTagName('li');
				for (var i=0;i<others.length;i++) {
					others[i].className = others[i].className.replace(/(^|\s+)selected(\s+|$)/g, ' ');
				}
				for (var i=0;i<panes.length;i++) {
					if ( panes[i].className.match( new RegExp("(^|\\s+)"+this.className.replace(/\s*/g, '')+"(\\s+|$)") ) ) {
						panes[i].className += ' selected';
					} else {
						panes[i].className = panes[i].className.replace(/(^|\s+)selected(\s+|$)/g, ' ');
					}
				}
				this.className += ' selected';
				
			}, tabs[i]));
			
		}
	}
	
}

function listen(element, event, handler) {
	if (element.attachEvent) {
		element.attachEvent('on'+event, handler);
	} else {
		element.addEventListener(event, handler, false);
	}
}

function bind(func, bind) {
	return function() {
		func.apply(bind, arguments);
	};
}
/* accordion 
\_____________________________________________________*/
Event.observe(window, 'load', function() {
	if ($('newslettersAccordion')) {
		makeAccordion('newslettersAccordion');
	}
	if ($('featuresAccordion')) {
		makeAccordion('featuresAccordion', $('featuresAccordion').getElementsByClassName('featureExcerpt'));
	}
	if ($('relatedContentAccordion')) {
		makeAccordion('relatedContentAccordion', false, false, {openOnStart: false, alwaysOpen: false, ifOne: function() {}});
	}

	if ($('authorsAccordion')) {
		makeAccordion('authorsAccordion', $('authorsAccordion').getElementsByClassName('featureExcerpt'));
	}
});

function makeAccordion(accordionWrapper, handles, contents, options) {
	var accordionMembers = getAccordionMembers(accordionWrapper, handles, contents);
	if (accordionMembers && accordionMembers.length) {
		new Accordion(accordionMembers, Object.extend(
			{
				onAccordion: function(active, deactive) {
        			if (active) {
						active.elements[0].addClassName('visible');
					}
			        if (deactive) {
    			        deactive.elements[0].removeClassName('visible');
        			}
			    }, ifOne: function(one) {
    			    one.elements[0].addClassName('one');
    			}
			}, options || {})
		);
	}
}

function getAccordionMembers(accordionWrapper, handles, contents) {
	var contents = contents || $(accordionWrapper).getElementsByClassName('article_body');
	var handles = handles || $(accordionWrapper).getElementsByClassName('accordionHandle');
	var accordionMembers = [];
	for (var i=0;i<contents.length;i++) {
		accordionMembers.push(new AccordionMember(contents[i], [handles[i]]));
	}
	return accordionMembers;
}

/* fauxform
\_____________________________________________________*/

function clearFields(event) {
	if (!window.fauxForm) {
		return false;
	}
	var checks = Event.element(event).parentNode.getElementsByClassName('check');
	for (var i=0;i<checks.length;i++) {
		window.fauxForm.getFieldByElement(checks[i]).uncheck(true);
	}
	highlightAllEl(Event.element(event));
	window.fauxForm.submit();
}

function checkFields(element) {
	var checks = element.getElementsByClassName('check');
	for (var i=0;i<checks.length;i++) {
		if (window.fauxForm.getFieldByElement(checks[i]).getValue()) {
			return false;
		}
	}
	return true;
}

function highlightAllEl(element) {
	element.addClassName('selected');
}

function unhighlightAllEl(element) {
	element.removeClassName('selected');
}

/* IE Fixes
\_______________________________________*/

if (document.all) { 
	Event.observe(window, 'load', function() {
		if ($('glance') || $$('.newsOpitons')[0]) {
			document.body.onresize = resetSlider;
		}
		function resetSlider() {
			var slider = $('slideshow');
			if (slider) {
				slider.style.display = 'none';
				slider.style.display = '';
            }

		};
		


	});
}

/* Popups                             *\
\*____________________________________*/

// this functionality has been removed from implementation on the site, but is still available for future builds

function loadifyPopup() {
    $('popup').innerHTML = '';
    $('popup').style.width = '516px';
    $('popup').style.height = '300px';
 	$('popup').style.backgroundImage = 'url(/images/backgrounds/article_popup_bottom.png)';
    var loading = document.createElement('DIV');
    loading.style.backgroundImage = 'none';
    loading.style.position = 'relative';
    loading.style.top = '125px';
	loading.style.width = '130px';
	loading.style.height = '150px';
	loading.style.minHeight = '150px';
    loading.style.textAlign = 'center';
    loading.style.display = 'block';
	loading.style.margin = '0 auto';
	loading.style.overflow = 'hidden';
    var img = document.createElement('IMG');
    img.src = '/images/ajaxloading.gif';
	img.style.display = 'block';
    var text = document.createElement('DIV');
    text.appendChild(document.createTextNode('Loading . . .'));
    text.style.background = 'none';
    loading.appendChild(img);
    loading.appendChild(text);
    $('popup').appendChild(loading);
}

function unloadifyPopup() {
    $('popup').style.width = '';
    $('popup').style.height = '';
    $('popup').style.backgroundImage = '';
}

function popupX(elem) {
	return elem.cumulativeOffset().left+50;
}

function popupY(elem) {
	return elem.cumulativeOffset().top-20;
}

function toggleGraph(id, callsign, options) {
	$('tr'+id).toggle();
	evenOddify(($('stations_grid') || $('market_grid') || $('owner_grid')).getElementsByTagName('table')[0]);
	var input = $('show_callsign_'+id) || $('show_market_'+id) || $('show_owner_'+id);
	input.checked = ($('tr'+id).style.display.match(/none/)) ? false:true;
	var showAll = $('show_all_stations') || $('show_all_markets') || $('show_all_owners');
	showAll.checked = (input.checked) ? showAll.checked:false; 
	if (window.linegraph) {
		window.linegraph.toggleGraph(callsign, options);
	}
}

function showHideAllGraphs(show) {
	var elements = $$('.owner_togglers');
	elements = (elements.length) ? elements:$$('.callsign_togglers');
	elements = (elements.length) ? elements:$$('.market_togglers');
	$A(elements).each(function(toggler) {
		if (show && !toggler.checked) {
			toggler.onclick({render: false});
		} else if (!show && toggler.checked) {
			toggler.onclick({render: false});
		}
	});
	if (window.linegraph) {
		window.linegraph.render();
	}
}

/* Info Popups for Arbitron              *\
\*_______________________________________*/

Event.observe(window, 'load', loadInfoPopups);
function loadInfoPopups() {
	$A($$('.info_popup')).each(function(info_popup) {
		info_popup.style.position = 'absolute';
		var handle = info_popup.up().getElementsByClassName('info_popup_handle')[0];
		if (handle) {
			Event.observe(handle, 'mouseover', popupInfo, false);
			Event.observe(handle, 'mouseout', hideInfo, false);
		}
	});
}

function popupInfo(event) {
	var popup = $(this.up().getElementsByClassName('info_popup')[0]);
	popup.show();
	popup.style.left = (this.positionedOffset().left+this.offsetWidth)+'px';
	popup.style.top = (navigator.userAgent.match(/MSIE/)) ? (popup.positionedOffset().top-popup.offsetHeight)+'px':(this.positionedOffset().top-popup.offsetHeight)+'px';
}

function hideInfo(event) {
	var popup = $(this.up().getElementsByClassName('info_popup')[0]);
	popup.hide();
	popup.style.top = '';
	popup.style.left = '';
}

Event.observe(window, 'load', function() {
	$A($$('.wildcard')).each(function(element) {
		$(element).style.height = $(element).offsetHeight+'px';
	});
});

function goToAutoLink(input, listElement) {
	window.location = listElement.getElementsByTagName('a')[0].href;
}

function popStationRow(input, listElement) {
	new Ajax.Updater(listElement, '/admin/reports/add_row_existing/'+listElement.getElementsByTagName('a')[0].text, {asynchronous:true, evalScripts:true});
	listElement.up('tr').remove();
}

/* Hot Topics                *\
\*___________________________*/
Event.observe(window, 'load', function() {
	if ($$('#hot_topics .hot_topic').length && !$$('#tray_controls a.more').length) {
		loadHotTopics();
	}
});

function loadHotTopics() {
	$('hot_topics').setStyle({display:'inline'})
	var hotTopics = $$('#hot_topics .hot_topic');
	var wrapper = $('hot_topics_wrapper');
	var width = 0;
	for (var i=0;i<hotTopics.length;i++) {
		if (!wrapper.style.height || parseInt(wrapper.style.height) < hotTopics[i].offsetHeight) {
			wrapper.style.height = hotTopics[i].offsetHeight+'px';
		}
		hotTopics[i].style.width = hotTopics[i].offsetWidth+'px';
		width += hotTopics[i].offsetWidth;
	}
	wrapper.style.width = width+'px';

	//set up effects
	new iMarquee('hot_topics', 'hot_topics_wrapper', 'hot_topic', 'hot_topic_list', {pauseOnHover: true, horizontal: true, startDelay: 6000, stopDelay: 6000, speed: 2000});
}
