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

  Browser: (function(){
    var ua = navigator.userAgent;
    var isOpera = Object.prototype.toString.call(window.opera) == '[object Opera]';
    return {
      IE:             !!window.attachEvent && !isOpera,
      Opera:          isOpera,
      WebKit:         ua.indexOf('AppleWebKit/') > -1,
      Gecko:          ua.indexOf('Gecko') > -1 && ua.indexOf('KHTML') === -1,
      MobileSafari:   /Apple.*Mobile/.test(ua)
    }
  })(),

  BrowserFeatures: {
    XPath: !!document.evaluate,

    SelectorsAPI: !!document.querySelector,

    ElementExtensions: (function() {
      var constructor = window.Element || window.HTMLElement;
      return !!(constructor && constructor.prototype);
    })(),
    SpecificElementExtensions: (function() {
      if (typeof window.HTMLDivElement !== 'undefined')
        return true;

      var div = document.createElement('div'),
          form = document.createElement('form'),
          isSupported = false;

      if (div['__proto__'] && (div['__proto__'] !== form['__proto__'])) {
        isSupported = true;
      }

      div = form = null;

      return isSupported;
    })()
  },

  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;


var Abstract = { };


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

/* Based on Alex Arnell's inheritance implementation. */

var Class = (function() {

  var IS_DONTENUM_BUGGY = (function(){
    for (var p in { toString: 1 }) {
      if (p === 'toString') return false;
    }
    return true;
  })();

  function subclass() {};
  function create() {
    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) {
      subclass.prototype = parent.prototype;
      klass.prototype = new subclass;
      parent.subclasses.push(klass);
    }

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

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

    klass.prototype.constructor = klass;
    return klass;
  }

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

    if (IS_DONTENUM_BUGGY) {
      if (source.toString != Object.prototype.toString)
        properties.push("toString");
      if (source.valueOf != Object.prototype.valueOf)
        properties.push("valueOf");
    }

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

        		value.valueOf = method.valueOf.bind(method);
        		value.toString = method.toString.bind(method);
      		}
      		this.prototype[property] = value;
		}
    }

    return this;
  }

  return {
    create: create,
    Methods: {
      addMethods: addMethods
    }
  };
})();
(function() {

  var _toString = Object.prototype.toString,
      NULL_TYPE = 'Null',
      UNDEFINED_TYPE = 'Undefined',
      BOOLEAN_TYPE = 'Boolean',
      NUMBER_TYPE = 'Number',
      STRING_TYPE = 'String',
      OBJECT_TYPE = 'Object',
      FUNCTION_CLASS = '[object Function]',
      BOOLEAN_CLASS = '[object Boolean]',
      NUMBER_CLASS = '[object Number]',
      STRING_CLASS = '[object String]',
      ARRAY_CLASS = '[object Array]',
      DATE_CLASS = '[object Date]',
      NATIVE_JSON_STRINGIFY_SUPPORT = window.JSON &&
        typeof JSON.stringify === 'function' &&
        JSON.stringify(0) === '0' &&
        typeof JSON.stringify(Prototype.K) === 'undefined';

  function Type(o) {
    switch(o) {
      case null: return NULL_TYPE;
      case (void 0): return UNDEFINED_TYPE;
    }
    var type = typeof o;
    switch(type) {
      case 'boolean': return BOOLEAN_TYPE;
      case 'number':  return NUMBER_TYPE;
      case 'string':  return STRING_TYPE;
    }
    return OBJECT_TYPE;
  }

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

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

  function toJSON(value) {
    return Str('', { '': value }, []);
  }


  function copyGettersSetters(source, destination, property) {
	  if(Object.__lookupSetter__) {
		var Setter = source.__lookupSetter__(property),
			Getter = source.__lookupGetter__(property);

		if(Setter)
			destination.__defineSetter__(property, Setter);
		if(Getter)
			destination.__defineGetter__(property, Getter);

		if(Setter != undefined || Getter != undefined)
			return true;

		/* Support Name: { get: function(), set: function() } syntax */
		var Descriptor = source[property];
		if(Descriptor && typeof Descriptor == 'object' && (Descriptor.get || Descriptor.set)) {
			if(Descriptor.set)
			  destination.__defineSetter__(property, Descriptor.set);
			if(Descriptor.get)
			  destination.__defineGetter__(property, Descriptor.get);
			return true;
		}
	  } else if(Object.getOwnPropertyDescriptor && (!Prototype.Browser.IE || Object.isElement(source))) {
		  /* Support Name: { get: function(), set: function() } syntax */
		  var Descriptor = Object.getOwnPropertyDescriptor(source, property) || source[property];
		  if(Descriptor.get || Descriptor.set) {
			  Object.defineProperty(destination, property, Descriptor);
			  return true;
		  }
	  }
	  return false;
  }

  function Str(key, holder, stack) {
    var value = holder[key],
        type = typeof value;

    if (Type(value) === OBJECT_TYPE && typeof value.toJSON === 'function') {
      value = value.toJSON(key);
    }

    var _class = _toString.call(value);

    switch (_class) {
      case NUMBER_CLASS:
      case BOOLEAN_CLASS:
      case STRING_CLASS:
        value = value.valueOf();
    }

    switch (value) {
      case null: return 'null';
      case true: return 'true';
      case false: return 'false';
    }

    type = typeof value;
    switch (type) {
      case 'string':
        return value.inspect(true);
      case 'number':
        return isFinite(value) ? String(value) : 'null';
      case 'object':

        for (var i = 0, length = stack.length; i < length; i++) {
          if (stack[i] === value) { throw new TypeError(); }
        }
        stack.push(value);

        var partial = [];
        if (_class === ARRAY_CLASS) {
          for (var i = 0, length = value.length; i < length; i++) {
            var str = Str(i, value, stack);
            partial.push(typeof str === 'undefined' ? 'null' : str);
          }
          partial = '[' + partial.join(',') + ']';
        } else {
          var keys = Object.keys(value);
          for (var i = 0, length = keys.length; i < length; i++) {
            var key = keys[i], str = Str(key, value, stack);
            if (typeof str !== "undefined") {
               partial.push(key.inspect(true)+ ':' + str);
             }
          }
          partial = '{' + partial.join(',') + '}';
        }
        stack.pop();
        return partial;
    }
  }

  function stringify(object) {
    return JSON.stringify(object);
  }

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

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

  function keys(object) {
    if (Type(object) !== OBJECT_TYPE) { throw new TypeError(); }
    var results = [];
    for (var property in object) {
      if (object.hasOwnProperty(property)) {
        results.push(property);
      }
    }
    return results;
  }

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

  function clone(object) {
    return extend({ }, object);
  }

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

  function isArray(object) {
    return _toString.call(object) === ARRAY_CLASS;
  }

  var hasNativeIsArray = (typeof Array.isArray == 'function')
    && Array.isArray([]) && !Array.isArray({});

  if (hasNativeIsArray) {
    isArray = Array.isArray;
  }

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

  function isFunction(object) {
    return _toString.call(object) === FUNCTION_CLASS;
  }

  function isString(object) {
    return _toString.call(object) === STRING_CLASS;
  }

  function isNumber(object) {
    return _toString.call(object) === NUMBER_CLASS;
  }

  function isDate(object) {
    return _toString.call(object) === DATE_CLASS;
  }

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

  extend(Object, {
    extend:        extend,
    inspect:       inspect,
    toJSON:        NATIVE_JSON_STRINGIFY_SUPPORT ? stringify : toJSON,
    toQueryString: toQueryString,
    toHTML:        toHTML,
    keys:          Object.keys || keys,
    values:        values,
    clone:         clone,
    isElement:     isElement,
    isArray:       isArray,
    isHash:        isHash,
    isFunction:    isFunction,
    isString:      isString,
    isNumber:      isNumber,
    isDate:        isDate,
    isUndefined:   isUndefined,
	copyGettersSetters:		copyGettersSetters
  });
})();
Object.extend(Function.prototype, (function() {
  var slice = Array.prototype.slice;

  function update(array, args) {
    var arrayLength = array.length, length = args.length;
    while (length--) array[arrayLength + length] = args[length];
    return array;
  }

  function merge(array, args) {
    array = slice.call(array, 0);
    return update(array, args);
  }

  function argumentNames() {
    var names = this.toString().match(/^[\s\(]*function[^(]*\(([^)]*)\)/)[1]
      .replace(/\/\/.*?[\r\n]|\/\*(?:.|[\r\n])*?\*\//g, '')
      .replace(/\s+/g, '').split(',');
    return names.length == 1 && !names[0] ? [] : names;
  }

  function bind(context) {
    if (arguments.length < 2 && Object.isUndefined(arguments[0])) return this;
    var __method = this, args = slice.call(arguments, 1);
    return function() {
      var a = merge(args, arguments);
      return __method.apply(context, a);
    }
  }

  function bindAsEventListener(context) {
    var __method = this, args = slice.call(arguments, 1);
    return function(event) {
      var a = update([event || window.event], args);
      return __method.apply(context, a);
    }
  }

  function curry() {
    if (!arguments.length) return this;
    var __method = this, args = slice.call(arguments, 0);
    return function() {
      var a = merge(args, arguments);
      return __method.apply(this, a);
    }
  }

  function delay(timeout) {
    var __method = this, args = slice.call(arguments, 1);
    timeout = timeout * 1000;
    return window.setTimeout(function() {
      return __method.apply(__method, args);
    }, timeout);
  }

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

  function wrap(wrapper) {
    var __method = this;
    return function() {
      var a = update([__method.bind(this)], arguments);
      return wrapper.apply(this, a);
    }
  }

  function methodize() {
    if (this._methodized) return this._methodized;
    var __method = this;
    return this._methodized = function() {
      var a = update([this], arguments);
      return __method.apply(null, a);
    };
  }

  return {
    argumentNames:       argumentNames,
    bind:                bind,
    bindAsEventListener: bindAsEventListener,
    curry:               curry,
    delay:               delay,
    defer:               defer,
    wrap:                wrap,
    methodize:           methodize
  }
})());



(function(proto) {


  function toISOString() {
    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';
  }


  function toJSON() {
    return this.toISOString();
  }

  if (!proto.toISOString) proto.toISOString = toISOString;
  if (!proto.toJSON) proto.toJSON = toJSON;

})(Date.prototype);


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();
        this.currentlyExecuting = false;
      } catch(e) {
        this.currentlyExecuting = false;
        throw e;
      }
    }
  }
});
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, (function() {
  var NATIVE_JSON_PARSE_SUPPORT = window.JSON &&
    typeof JSON.parse === 'function' &&
    JSON.parse('{"test": true}').test;

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

  function gsub(pattern, replacement) {
    var result = '', source = this, match;
    replacement = prepareReplacement(replacement);

    if (Object.isString(pattern))
      pattern = RegExp.escape(pattern);

    if (!(pattern.length || pattern.source)) {
      replacement = replacement('');
      return replacement + source.split('').join(replacement) + 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;
  }

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

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

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

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

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

  function stripTags() {
    return this.replace(/<\w+(\s+("[^"]*"|'[^']*'|[^>])+)?>|<\/\w+>/gi, '');
  }

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

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

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

  function escapeHTML() {
    return this.replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;');
  }

  function unescapeHTML() {
    return this.stripTags().replace(/&lt;/g,'<').replace(/&gt;/g,'>').replace(/&amp;/g,'&');
  }


  function toQueryParams(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()),
            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;
    });
  }

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

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

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

  function camelize() {
    return this.replace(/-+(.)?/g, function(match, chr) {
      return chr ? chr.toUpperCase() : '';
    });
  }

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

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

  function dasherize() {
    return this.replace(/_/g, '-');
  }

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

  function unfilterJSON(filter) {
    return this.replace(filter || Prototype.JSONFilter, '$1');
  }

  function isJSON() {
    var str = this;
    if (str.blank()) return false;
    str = str.replace(/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g, '@');
    str = str.replace(/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g, ']');
    str = str.replace(/(?:^|:|,)(?:\s*\[)+/g, '');
    return (/^[\],:{}\s]*$/).test(str);
  }

  function evalJSON(sanitize) {
    var json = this.unfilterJSON(),
        cx = /[\u0000\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g;
    if (cx.test(json)) {
      json = json.replace(cx, function (a) {
        return '\\u' + ('0000' + a.charCodeAt(0).toString(16)).slice(-4);
      });
    }
    try {
      if (!sanitize || json.isJSON()) return eval('(' + json + ')');
    } catch (e) { }
    throw new SyntaxError('Badly formed JSON string: ' + this.inspect());
  }

  function parseJSON() {
    var json = this.unfilterJSON();
    return JSON.parse(json);
  }

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

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

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

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

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

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

  return {
    gsub:           gsub,
    sub:            sub,
    scan:           scan,
    truncate:       truncate,
    strip:          String.prototype.trim || strip,
    stripTags:      stripTags,
    stripScripts:   stripScripts,
    extractScripts: extractScripts,
    evalScripts:    evalScripts,
    escapeHTML:     escapeHTML,
    unescapeHTML:   unescapeHTML,
    toQueryParams:  toQueryParams,
    parseQuery:     toQueryParams,
    toArray:        toArray,
    succ:           succ,
    times:          times,
    camelize:       camelize,
    capitalize:     capitalize,
    underscore:     underscore,
    dasherize:      dasherize,
    inspect:        inspect,
    unfilterJSON:   unfilterJSON,
    isJSON:         isJSON,
    evalJSON:       NATIVE_JSON_PARSE_SUPPORT ? parseJSON : evalJSON,
    include:        include,
    startsWith:     startsWith,
    endsWith:       endsWith,
    empty:          empty,
    blank:          blank,
    interpolate:    interpolate
  };
})());

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

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

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

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

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

      match = pattern.exec(expr);
      if (match == null) return before;

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

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

var $break = { };

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

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

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

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

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

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

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

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

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

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

  function include(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;
  }

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

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

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

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

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

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

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

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

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

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

  function zip() {
    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));
    });
  }

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

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









  return {
    each:       each,
    eachSlice:  eachSlice,
    all:        all,
    every:      all,
    any:        any,
    some:       any,
    collect:    collect,
    map:        collect,
    detect:     detect,
    findAll:    findAll,
    select:     findAll,
    filter:     findAll,
    grep:       grep,
    include:    include,
    member:     include,
    inGroupsOf: inGroupsOf,
    inject:     inject,
    invoke:     invoke,
    max:        max,
    min:        min,
    partition:  partition,
    pluck:      pluck,
    reject:     reject,
    sortBy:     sortBy,
    toArray:    toArray,
    entries:    toArray,
    zip:        zip,
    size:       size,
    inspect:    inspect,
    find:       detect
  };
})();

function $A(iterable) {
  if (!iterable) return [];
  if ('toArray' in Object(iterable)) return iterable.toArray();
  var length = iterable.length || 0, results = new Array(length);
  while (length--) results[length] = iterable[length];
  return results;
}


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

Array.from = $A;


(function() {
  var arrayProto = Array.prototype,
      slice = arrayProto.slice,
      _each = arrayProto.forEach; // use native browser JS 1.6 implementation if available

  function each(iterator, context) {
    for (var i = 0, length = this.length >>> 0; i < length; i++) {
      if (i in this) iterator.call(context, this[i], i, this);
    }
  }
  if (!_each) _each = each;

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

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

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

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

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

  function without() {
    var values = slice.call(arguments, 0);
    return this.select(function(value) {
      return !values.include(value);
    });
  }

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

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

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


  function clone() {
    return slice.call(this, 0);
  }

  function size() {
    return this.length;
  }

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

  function indexOf(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;
  }

  function lastIndexOf(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;
  }

  function concat() {
    var array = slice.call(this, 0), item;
    for (var i = 0, length = arguments.length; i < length; i++) {
      item = arguments[i];
      if (Object.isArray(item) && !('callee' in item)) {
        for (var j = 0, arrayLength = item.length; j < arrayLength; j++)
          array.push(item[j]);
      } else {
        array.push(item);
      }
    }
    return array;
  }

  Object.extend(arrayProto, Enumerable);

  if (!arrayProto._reverse)
    arrayProto._reverse = arrayProto.reverse;

  Object.extend(arrayProto, {
    _each:     _each,
    clear:     clear,
    first:     first,
    last:      last,
    compact:   compact,
    flatten:   flatten,
    without:   without,
    reverse:   reverse,
    uniq:      uniq,
    intersect: intersect,
    clone:     clone,
    toArray:   clone,
    size:      size,
    inspect:   inspect
  });

  var CONCAT_ARGUMENTS_BUGGY = (function() {
    return [].concat(arguments)[0][0] !== 1;
  })(1,2)

  if (CONCAT_ARGUMENTS_BUGGY) arrayProto.concat = concat;

  if (!arrayProto.indexOf) arrayProto.indexOf = indexOf;
  if (!arrayProto.lastIndexOf) arrayProto.lastIndexOf = lastIndexOf;
})();
function $H(object) {
  return new Hash(object);
};

var Hash = Class.create(Enumerable, (function() {
  function initialize(object) {
    this._object = Object.isHash(object) ? object.toObject() : Object.clone(object);
  }


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

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

  function get(key) {
    if (this._object[key] !== Object.prototype[key])
      return this._object[key];
  }

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

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



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

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

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

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

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

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

  function toQueryString() {
    return this.inject([], function(results, pair) {
      var key = encodeURIComponent(pair.key), values = pair.value;

      if (values && typeof values == 'object') {
        if (Object.isArray(values)) {
          var queryValues = [];
          for (var i = 0, len = values.length, value; i < len; i++) {
            value = values[i];
            queryValues.push(toQueryPair(key, value));
          }
          return results.concat(queryValues);
        }
      } else results.push(toQueryPair(key, values));
      return results;
    }).join('&');
  }

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

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

  return {
    initialize:             initialize,
    _each:                  _each,
    set:                    set,
    get:                    get,
    unset:                  unset,
    toObject:               toObject,
    toTemplateReplacements: toObject,
    keys:                   keys,
    values:                 values,
    index:                  index,
    merge:                  merge,
    update:                 update,
    toQueryString:          toQueryString,
    inspect:                inspect,
    toJSON:                 toObject,
    clone:                  clone
  };
})());

Hash.from = $H;
Object.extend(Number.prototype, (function() {
  function toColorPart() {
    return this.toPaddedString(2, 16);
  }

  function succ() {
    return this + 1;
  }

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

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

  function abs() {
    return Math.abs(this);
  }

  function round() {
    return Math.round(this);
  }

  function ceil() {
    return Math.ceil(this);
  }

  function floor() {
    return Math.floor(this);
  }

  return {
    toColorPart:    toColorPart,
    succ:           succ,
    times:          times,
    toPaddedString: toPaddedString,
    abs:            abs,
    round:          round,
    ceil:           ceil,
    floor:          floor
  };
})());

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

var ObjectRange = Class.create(Enumerable, (function() {
  function initialize(start, end, exclusive) {
    this.start = start;
    this.end = end;
    this.exclusive = exclusive;
  }

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

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

  return {
    initialize: initialize,
    _each:      _each,
    include:    include
  };
})());



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.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.isString(this.options.parameters) ?
          this.options.parameters :
          Object.toQueryString(this.options.parameters);

    if (!['get', 'post'].include(this.method)) {
      params += (params ? '&' : '') + "_method=" + this.method;
      this.method = 'post';
    }

    if (params && this.method === 'get') {
      this.url += (this.url.include('?') ? '&' : '?') + params;
    }

    this.parameters = params.toQueryParams();

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

    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) || status == 304;
  },

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

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

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

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

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

    if (state == 'Complete') {
      this.transport.onreadystatechange = Prototype.emptyFunction;
    }
  },

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

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

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

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

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








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

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

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

  status:      0,

  statusText: '',

  getStatus: Ajax.Request.prototype.getStatus,

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

  getHeader: Ajax.Request.prototype.getHeader,

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

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

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

  _getHeaderJSON: function() {
    var json = this.getHeader('X-JSON');
    if (!json) return null;
    json = decodeURIComponent(escape(json));
    try {
      return json.evalJSON(this.request.options.sanitizeJSON ||
        !this.request.isSameOrigin());
    } catch (e) {
      this.request.dispatchException(e);
    }
  },

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

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

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

    $super(url, options);
  },

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

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

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

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

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

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

    this.start();
  },

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

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

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

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

  onTimerEvent: function() {
    this.updater = new Ajax.Updater(this.container, this.url, this.options);
  }
});


function $(element) {
  if (arguments.length > 1) {
    for (var i = 0, elements = [], length = arguments.length; i < length; i++)
      elements.push($(arguments[i]));
    return elements;
  }
  if (Object.isString(element))
    element = document.getElementById(element);
  return Element.extend(element);
}

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

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

if (!Node) var Node = { };

if (!Node.ELEMENT_NODE) {
  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(global) {
  function shouldUseCache(tagName, attributes) {
    if (tagName === 'select') return false;
    if ('type' in attributes) return false;
    return true;
  }

  var HAS_EXTENDED_CREATE_ELEMENT_SYNTAX = (function(){
    try {
      var el = document.createElement('<input name="x">');
      return el.tagName.toLowerCase() === 'input' && el.name === 'x';
    }
    catch(err) {
      return false;
    }
  })();

  var element = global.Element;

  global.Element = function(tagName, attributes) {
    attributes = attributes || { };
    tagName = tagName.toLowerCase();
    var cache = Element.cache;

    if (HAS_EXTENDED_CREATE_ELEMENT_SYNTAX && 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));

    var node = shouldUseCache(tagName, attributes) ?
     cache[tagName].cloneNode(false) : document.createElement(tagName);

    return Element.writeAttribute(node, attributes);
  };

  Object.extend(global.Element, element || { });
  if (element) global.Element.prototype = element.prototype;

})(this);

Element.idCounter = 1;
Element.cache = { };

Element._purgeElement = function(element) {
  var uid = element._prototypeUID;
  if (uid) {
    Element.stopObserving(element);
    element._prototypeUID = void 0;
    delete Element.Storage[uid];
  }
}

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

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

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

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

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

  update: (function(){

    var SELECT_ELEMENT_INNERHTML_BUGGY = (function(){
      var el = document.createElement("select"),
          isBuggy = true;
      el.innerHTML = "<option value=\"test\">test</option>";
      if (el.options && el.options[0]) {
        isBuggy = el.options[0].nodeName.toUpperCase() !== "OPTION";
      }
      el = null;
      return isBuggy;
    })();

    var TABLE_ELEMENT_INNERHTML_BUGGY = (function(){
      try {
        var el = document.createElement("table");
        if (el && el.tBodies) {
          el.innerHTML = "<tbody><tr><td>test</td></tr></tbody>";
          var isBuggy = typeof el.tBodies[0] == "undefined";
          el = null;
          return isBuggy;
        }
      } catch (e) {
        return true;
      }
    })();

    var LINK_ELEMENT_INNERHTML_BUGGY = (function() {
      try {
        var el = document.createElement('div');
        el.innerHTML = "<link>";
        var isBuggy = (el.childNodes.length === 0);
        el = null;
        return isBuggy;
      } catch(e) {
        return true;
      }
    })();

    var ANY_INNERHTML_BUGGY = SELECT_ELEMENT_INNERHTML_BUGGY ||
     TABLE_ELEMENT_INNERHTML_BUGGY || LINK_ELEMENT_INNERHTML_BUGGY;

    var SCRIPT_ELEMENT_REJECTS_TEXTNODE_APPENDING = (function () {
      var s = document.createElement("script"),
          isBuggy = false;
      try {
        s.appendChild(document.createTextNode(""));
        isBuggy = !s.firstChild ||
          s.firstChild && s.firstChild.nodeType !== 3;
      } catch (e) {
        isBuggy = true;
      }
      s = null;
      return isBuggy;
    })();


    function update(element, content) {
      element = $(element);
      var purgeElement = Element._purgeElement;

      var descendants = element.getElementsByTagName('*'),
       i = descendants.length;
      while (i--) purgeElement(descendants[i]);

      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 === 'SCRIPT' && SCRIPT_ELEMENT_REJECTS_TEXTNODE_APPENDING) {
        element.text = content;
        return element;
      }

      if (ANY_INNERHTML_BUGGY) {
        if (tagName in Element._insertionTranslations.tags) {
          while (element.firstChild) {
            element.removeChild(element.firstChild);
          }
          Element._getContentFromAnonymousElement(tagName, content.stripScripts())
            .each(function(node) {
              element.appendChild(node)
            });
        } else if (LINK_ELEMENT_INNERHTML_BUGGY && Object.isString(content) && content.indexOf('<link') > -1) {
          while (element.firstChild) {
            element.removeChild(element.firstChild);
          }
          var nodes = Element._getContentFromAnonymousElement(tagName, content.stripScripts(), true);
          nodes.each(function(node) { element.appendChild(node) });
        }
        else {
          element.innerHTML = content.stripScripts();
        }
      }
      else {
        element.innerHTML = content.stripScripts();
      }

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

    return update;
  })(),

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

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

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

    var content, insert, tagName, childNodes;

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

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

      content = Object.toHTML(content);

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

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

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

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

    return element;
  },

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

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

  recursivelyCollect: function(element, property, maximumLength) {
    element = $(element);
    maximumLength = maximumLength || -1;
    var elements = [];

    while (element = element[property]) {
      if (element.nodeType == 1)
        elements.push(Element.extend(element));
      if (elements.length == maximumLength)
        break;
    }

    return elements;
  },

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

  descendants: function(element) {
    return Element.select(element, "*");
  },

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

  immediateDescendants: function(element) {
    var results = [], child = $(element).firstChild;
    while (child) {
      if (child.nodeType === 1) {
        results.push(Element.extend(child));
      }
      child = child.nextSibling;
    }
    return results;
  },

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

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

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

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

  up: function(element, expression, index) {
    element = $(element);
    if (arguments.length == 1) return $(element.parentNode);
    var ancestors = Element.ancestors(element);
    return Object.isNumber(expression) ? ancestors[expression] :
      Prototype.Selector.find(ancestors, expression, index);
  },

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

  previous: function(element, expression, index) {
    element = $(element);
    if (Object.isNumber(expression)) index = expression, expression = false;
    if (!Object.isNumber(index)) index = 0;

    if (expression) {
      return Prototype.Selector.find(element.previousSiblings(), expression, index);
    } else {
      return element.recursivelyCollect("previousSibling", index + 1)[index];
    }
  },

  next: function(element, expression, index) {
    element = $(element);
    if (Object.isNumber(expression)) index = expression, expression = false;
    if (!Object.isNumber(index)) index = 0;

    if (expression) {
      return Prototype.Selector.find(element.nextSiblings(), expression, index);
    } else {
      var maximumLength = Object.isNumber(index) ? index + 1 : 1;
      return element.recursivelyCollect("nextSibling", index + 1)[index];
    }
  },


  select: function(element) {
    element = $(element);
    var expressions = Array.prototype.slice.call(arguments, 1).join(', ');
    return Prototype.Selector.select(expressions, element);
  },

  adjacent: function(element) {
    element = $(element);
    var expressions = Array.prototype.slice.call(arguments, 1).join(', ');
    return Prototype.Selector.select(expressions, element.parentNode).without(element);
  },

  identify: function(element) {
    element = $(element);
    var id = Element.readAttribute(element, 'id');
    if (id) return id;
    do { id = 'anonymous_element_' + Element.idCounter++ } while ($(id));
    Element.writeAttribute(element, '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(element).height;
  },

  getWidth: function(element) {
    return Element.getDimensions(element).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(element, 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(element, className) ?
      'removeClassName' : 'addClassName'](element, className);
  },

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

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

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

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

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

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

    return false;
  },

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

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

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

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

    return element;
  },

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

  makePositioned: function(element) {
    element = $(element);
    var pos = Element.getStyle(element, 'position');
    if (pos == 'static' || !pos) {
      element._madePositioned = true;
      element.style.position = 'relative';
      if (Prototype.Browser.Opera) {
        element.style.top = 0;
        element.style.left = 0;
      }
    }
    return element;
  },

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

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

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

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

    source = $(source);
    var p = Element.viewportOffset(source), delta = [0, 0], parent = null;

    element = $(element);

    if (Element.getStyle(element, 'position') == 'absolute') {
      parent = Element.getOffsetParent(element);
      delta = Element.viewportOffset(parent);
    }

    if (parent == document.body) {
      delta[0] -= document.body.offsetLeft;
      delta[1] -= document.body.offsetTop;
    }

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

Object.extend(Element.Methods, {
  getElementsBySelector: Element.Methods.select,

  childElements: Element.Methods.immediateDescendants
});

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

if (Prototype.Browser.Opera) {
  Element.Methods.getStyle = Element.Methods.getStyle.wrap(
    function(proceed, element, style) {
      switch (style) {
        case 'height': case 'width':
          if (!Element.visible(element)) return null;

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

    var classProp = 'className',
        forProp = 'for',
        el = document.createElement('div');

    el.setAttribute(classProp, 'x');

    if (el.className !== 'x') {
      el.setAttribute('class', 'x');
      if (el.className === 'x') {
        classProp = 'class';
      }
    }
    el = null;

    el = document.createElement('label');
    el.setAttribute(forProp, 'x');
    if (el.htmlFor !== 'x') {
      el.setAttribute('htmlFor', 'x');
      if (el.htmlFor === 'x') {
        forProp = 'htmlFor';
      }
    }
    el = null;

    return {
      read: {
        names: {
          'class':      classProp,
          'className':  classProp,
          'for':        forProp,
          'htmlFor':    forProp
        },
        values: {
          _getAttr: function(element, attribute) {
            return element.getAttribute(attribute);
          },
          _getAttr2: function(element, attribute) {
            return element.getAttribute(attribute, 2);
          },
          _getAttrNode: function(element, attribute) {
            var node = element.getAttributeNode(attribute);
            return node ? node.value : "";
          },
          _getEv: (function(){

            var el = document.createElement('div'), f;
            el.onclick = Prototype.emptyFunction;
            var value = el.getAttribute('onclick');

            if (String(value).indexOf('{') > -1) {
              f = function(element, attribute) {
                attribute = element.getAttribute(attribute);
                if (!attribute) return null;
                attribute = attribute.toString();
                attribute = attribute.split('{')[1];
                attribute = attribute.split('}')[0];
                return attribute.strip();
              };
            }
            else if (value === '') {
              f = function(element, attribute) {
                attribute = element.getAttribute(attribute);
                if (!attribute) return null;
                return attribute.strip();
              };
            }
            el = null;
            return f;
          })(),
          _flag: function(element, attribute) {
            return $(element).hasAttribute(attribute) ? attribute : null;
          },
          style: function(element) {
            return element.style.cssText.toLowerCase();
          },
          title: function(element) {
            return element.title;
          }
        }
      }
    }
  })();

  Element._attributeTranslations.write = {
    names: Object.extend({
      cellpadding: 'cellPadding',
      cellspacing: 'cellSpacing'
    }, Element._attributeTranslations.read.names),
    values: {
      checked: function(element, value) {
        element.checked = !!value;
      },

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

  Element._attributeTranslations.has = {};

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

  (function(v) {
    Object.extend(v, {
      href:        v._getAttr2,
      src:         v._getAttr2,
      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);

  if (Prototype.BrowserFeatures.ElementExtensions) {
    (function() {
      function _descendants(element) {
        var nodes = element.getElementsByTagName('*'), results = [];
        for (var i = 0, node; node = nodes[i]; i++)
          if (node.tagName !== "!") // Filter out comment nodes.
            results.push(node);
        return results;
      }

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

}

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

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

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

    return element;
  };
}

if ('outerHTML' in document.documentElement) {
  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(),
          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, force) {
  var div = new Element('div'),
      t = Element._insertionTranslations.tags[tagName];

  var workaround = false;
  if (t) workaround = true;
  else if (force) {
    workaround = true;
    t = ['', '', 0];
  }

  if (workaround) {
    div.innerHTML = '&nbsp;' + t[0] + html + t[1];
    div.removeChild(div.firstChild);
    for (var i = t[2]; i--; ) {
      div = div.firstChild;
    }
  }
  else {
    div.innerHTML = html;
  }
  return $A(div.childNodes);
};

Element._insertionTranslations = {
  before: function(element, node) {
    element.parentNode.insertBefore(node, element);
  },
  top: function(element, node) {
    element.insertBefore(node, element.firstChild);
  },
  bottom: function(element, node) {
    element.appendChild(node);
  },
  after: function(element, node) {
    element.parentNode.insertBefore(node, element.nextSibling);
  },
  tags: {
    TABLE:  ['<table>',                '</table>',                   1],
    TBODY:  ['<table><tbody>',         '</tbody></table>',           2],
    TR:     ['<table><tbody><tr>',     '</tr></tbody></table>',      3],
    TD:     ['<table><tbody><tr><td>', '</td></tr></tbody></table>', 4],
    SELECT: ['<select>',               '</select>',                  1]
  }
};

(function() {
  var tags = Element._insertionTranslations.tags;
  Object.extend(tags, {
    THEAD: tags.TBODY,
    TFOOT: tags.TBODY,
    TH:    tags.TD
  });
})();

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

(function(div) {

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

  div = null;

})(document.createElement('div'));

Element.extend = (function() {

  function checkDeficiency(tagName) {
    if (typeof window.Element != 'undefined') {
      var proto = window.Element.prototype;
      if (proto) {
        var id = '_' + (Math.random()+'').slice(2),
            el = document.createElement(tagName);
        proto[id] = 'x';
        var isBuggy = (el[id] !== 'x');
        delete proto[id];
        el = null;
        return isBuggy;
      }
    }
    return false;
  }

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

  var HTMLOBJECTELEMENT_PROTOTYPE_BUGGY = checkDeficiency('object');

  if (Prototype.BrowserFeatures.SpecificElementExtensions) {
    if (HTMLOBJECTELEMENT_PROTOTYPE_BUGGY) {
      return function(element) {
        if (element && typeof element._extendedByPrototype == 'undefined') {
          var t = element.tagName;
          if (t && (/^(?:object|applet|embed)$/i.test(t))) {
            extendElementWith(element, Element.Methods);
            extendElementWith(element, Element.Methods.Simulated);
            extendElementWith(element, Element.Methods.ByTag[t.toUpperCase()]);
          }
        }
        return element;
      }
    }
    return Prototype.K;
  }

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

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

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

    if (ByTag[tagName]) Object.extend(methods, ByTag[tagName]);

    extendElementWith(element, methods);

    element._extendedByPrototype = Prototype.emptyFunction;
    return element;

  }, {
    refresh: function() {
      if (!Prototype.BrowserFeatures.ElementExtensions) {
        Object.extend(Methods, Element.Methods);
        Object.extend(Methods, Element.Methods.Simulated);
      }
    }
  });

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

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

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),
      "BUTTON":   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];

    var element = document.createElement(tagName),
        proto = element['__proto__'] || element.constructor.prototype;

    element = null;
    return proto;
  }

  var elementPrototype = window.HTMLElement ? HTMLElement.prototype :
   Element.prototype;

  if (F.ElementExtensions) {
    copy(Element.Methods, elementPrototype);
    copy(Element.Methods.Simulated, elementPrototype, 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() {
    return { width: this.getWidth(), height: this.getHeight() };
  },

  getScrollOffsets: function() {
    return Element._returnOffset(
      window.pageXOffset || document.documentElement.scrollLeft || document.body.scrollLeft,
      window.pageYOffset || document.documentElement.scrollTop  || document.body.scrollTop);
  }
};

(function(viewport) {
  var B = Prototype.Browser, doc = document, element, property = {};

  function getRootElement() {
    if (B.WebKit && !doc.evaluate)
      return document;

    if (B.Opera && window.parseFloat(window.opera.version()) < 9.5)
      return document.body;

    return document.documentElement;
  }

  function define(D) {
    if (!element) element = getRootElement();

    property[D] = 'client' + D;

    viewport['get' + D] = function() { return element[property[D]] };
    return viewport['get' + D]();
  }

  viewport.getWidth  = define.curry('Width');

  viewport.getHeight = define.curry('Height');
})(document.viewport);


Element.Storage = {
  UID: 1
};

Element.addMethods({
  getStorage: function(element) {
    if (!(element = $(element))) return;

    var uid;
    if (element === window) {
      uid = 0;
    } else {
      if (typeof element._prototypeUID === "undefined")
        element._prototypeUID = Element.Storage.UID++;
      uid = element._prototypeUID;
    }

    if (!Element.Storage[uid])
      Element.Storage[uid] = $H();

    return Element.Storage[uid];
  },

  store: function(element, key, value) {
    if (!(element = $(element))) return;

    if (arguments.length === 2) {
      Element.getStorage(element).update(key);
    } else {
      Element.getStorage(element).set(key, value);
    }

    return element;
  },

  retrieve: function(element, key, defaultValue) {
    if (!(element = $(element))) return;
    var hash = Element.getStorage(element), value = hash.get(key);

    if (Object.isUndefined(value)) {
      hash.set(key, defaultValue);
      value = defaultValue;
    }

    return value;
  },

  clone: function(element, deep) {
    if (!(element = $(element))) return;
    var clone = element.cloneNode(deep);
    clone._prototypeUID = void 0;
    if (deep) {
      var descendants = Element.select(clone, '*'),
          i = descendants.length;
      while (i--) {
        descendants[i]._prototypeUID = void 0;
      }
    }
    return Element.extend(clone);
  },

  purge: function(element) {
    if (!(element = $(element))) return;
    var purgeElement = Element._purgeElement;

    purgeElement(element);

    var descendants = element.getElementsByTagName('*'),
     i = descendants.length;

    while (i--) purgeElement(descendants[i]);

    return null;
  }
});

(function() {

  function toDecimal(pctString) {
    var match = pctString.match(/^(\d+)%?$/i);
    if (!match) return null;
    return (Number(match[1]) / 100);
  }

  function getPixelValue(value, property, context) {
    var element = null;
    if (Object.isElement(value)) {
      element = value;
      value = element.getStyle(property);
    }

    if (value === null) {
      return null;
    }

    if ((/^(?:-)?\d+(\.\d+)?(px)?$/i).test(value)) {
      return window.parseFloat(value);
    }

    var isPercentage = value.include('%'), isViewport = (context === document.viewport);

    if (/\d/.test(value) && element && element.runtimeStyle && !(isPercentage && isViewport)) {
      var style = element.style.left, rStyle = element.runtimeStyle.left;
      element.runtimeStyle.left = element.currentStyle.left;
      element.style.left = value || 0;
      value = element.style.pixelLeft;
      element.style.left = style;
      element.runtimeStyle.left = rStyle;

      return value;
    }

    if (element && isPercentage) {
      context = context || element.parentNode;
      var decimal = toDecimal(value);
      var whole = null;
      var position = element.getStyle('position');

      var isHorizontal = property.include('left') || property.include('right') ||
       property.include('width');

      var isVertical =  property.include('top') || property.include('bottom') ||
        property.include('height');

      if (context === document.viewport) {
        if (isHorizontal) {
          whole = document.viewport.getWidth();
        } else if (isVertical) {
          whole = document.viewport.getHeight();
        }
      } else {
        if (isHorizontal) {
          whole = $(context).measure('width');
        } else if (isVertical) {
          whole = $(context).measure('height');
        }
      }

      return (whole === null) ? 0 : whole * decimal;
    }

    return 0;
  }

  function toCSSPixels(number) {
    if (Object.isString(number) && number.endsWith('px')) {
      return number;
    }
    return number + 'px';
  }

  function isDisplayed(element) {
    var originalElement = element;
    while (element && element.parentNode) {
      var display = element.getStyle('display');
      if (display === 'none') {
        return false;
      }
      element = $(element.parentNode);
    }
    return true;
  }

  var hasLayout = Prototype.K;
  if ('currentStyle' in document.documentElement) {
    hasLayout = function(element) {
      if (!element.currentStyle.hasLayout) {
        element.style.zoom = 1;
      }
      return element;
    };
  }

  function cssNameFor(key) {
    if (key.include('border')) key = key + '-width';
    return key.camelize();
  }

  Element.Layout = Class.create(Hash, {
    initialize: function($super, element, preCompute) {
      $super();
      this.element = $(element);

      Element.Layout.PROPERTIES.each( function(property) {
        this._set(property, null);
      }, this);

      if (preCompute) {
        this._preComputing = true;
        this._begin();
        Element.Layout.PROPERTIES.each( this._compute, this );
        this._end();
        this._preComputing = false;
      }
    },

    _set: function(property, value) {
      return Hash.prototype.set.call(this, property, value);
    },

    set: function(property, value) {
      throw "Properties of Element.Layout are read-only.";
    },

    get: function($super, property) {
      var value = $super(property);
      return value === null ? this._compute(property) : value;
    },

    _begin: function() {
      if (this._prepared) return;

      var element = this.element;
      if (isDisplayed(element)) {
        this._prepared = true;
        return;
      }

      var originalStyles = {
        position:   element.style.position   || '',
        width:      element.style.width      || '',
        visibility: element.style.visibility || '',
        display:    element.style.display    || ''
      };

      element.store('prototype_original_styles', originalStyles);

      var position = element.getStyle('position'),
       width = element.getStyle('width');

      if (width === "0px" || width === null) {
        element.style.display = 'block';
        width = element.getStyle('width');
      }

      var context = (position === 'fixed') ? document.viewport :
       element.parentNode;

      element.setStyle({
        position:   'absolute',
        visibility: 'hidden',
        display:    'block'
      });

      var positionedWidth = element.getStyle('width');

      var newWidth;
      if (width && (positionedWidth === width)) {
        newWidth = getPixelValue(element, 'width', context);
      } else if (position === 'absolute' || position === 'fixed') {
        newWidth = getPixelValue(element, 'width', context);
      } else {
        var parent = element.parentNode, pLayout = $(parent).getLayout();

        newWidth = pLayout.get('width') -
         this.get('margin-left') -
         this.get('border-left') -
         this.get('padding-left') -
         this.get('padding-right') -
         this.get('border-right') -
         this.get('margin-right');
      }

      element.setStyle({ width: newWidth + 'px' });

      this._prepared = true;
    },

    _end: function() {
      var element = this.element;
      var originalStyles = element.retrieve('prototype_original_styles');
      element.store('prototype_original_styles', null);
      element.setStyle(originalStyles);
      this._prepared = false;
    },

    _compute: function(property) {
      var COMPUTATIONS = Element.Layout.COMPUTATIONS;
      if (!(property in COMPUTATIONS)) {
        throw "Property not found.";
      }

      return this._set(property, COMPUTATIONS[property].call(this, this.element));
    },

    toObject: function() {
      var args = $A(arguments);
      var keys = (args.length === 0) ? Element.Layout.PROPERTIES :
       args.join(' ').split(' ');
      var obj = {};
      keys.each( function(key) {
        if (!Element.Layout.PROPERTIES.include(key)) return;
        var value = this.get(key);
        if (value != null) obj[key] = value;
      }, this);
      return obj;
    },

    toHash: function() {
      var obj = this.toObject.apply(this, arguments);
      return new Hash(obj);
    },

    toCSS: function() {
      var args = $A(arguments);
      var keys = (args.length === 0) ? Element.Layout.PROPERTIES :
       args.join(' ').split(' ');
      var css = {};

      keys.each( function(key) {
        if (!Element.Layout.PROPERTIES.include(key)) return;
        if (Element.Layout.COMPOSITE_PROPERTIES.include(key)) return;

        var value = this.get(key);
        if (value != null) css[cssNameFor(key)] = value + 'px';
      }, this);
      return css;
    },

    inspect: function() {
      return "#<Element.Layout>";
    }
  });

  Object.extend(Element.Layout, {
    PROPERTIES: $w('height width top left right bottom border-left border-right border-top border-bottom padding-left padding-right padding-top padding-bottom margin-top margin-bottom margin-left margin-right padding-box-width padding-box-height border-box-width border-box-height margin-box-width margin-box-height'),

    COMPOSITE_PROPERTIES: $w('padding-box-width padding-box-height margin-box-width margin-box-height border-box-width border-box-height'),

    COMPUTATIONS: {
      'height': function(element) {
        if (!this._preComputing) this._begin();

        var bHeight = this.get('border-box-height');
        if (bHeight <= 0) {
          if (!this._preComputing) this._end();
          return 0;
        }

        var bTop = this.get('border-top'),
         bBottom = this.get('border-bottom');

        var pTop = this.get('padding-top'),
         pBottom = this.get('padding-bottom');

        if (!this._preComputing) this._end();

        return bHeight - bTop - bBottom - pTop - pBottom;
      },

      'width': function(element) {
        if (!this._preComputing) this._begin();

        var bWidth = this.get('border-box-width');
        if (bWidth <= 0) {
          if (!this._preComputing) this._end();
          return 0;
        }

        var bLeft = this.get('border-left'),
         bRight = this.get('border-right');

        var pLeft = this.get('padding-left'),
         pRight = this.get('padding-right');

        if (!this._preComputing) this._end();

        return bWidth - bLeft - bRight - pLeft - pRight;
      },

      'padding-box-height': function(element) {
        var height = this.get('height'),
         pTop = this.get('padding-top'),
         pBottom = this.get('padding-bottom');

        return height + pTop + pBottom;
      },

      'padding-box-width': function(element) {
        var width = this.get('width'),
         pLeft = this.get('padding-left'),
         pRight = this.get('padding-right');

        return width + pLeft + pRight;
      },

      'border-box-height': function(element) {
        if (!this._preComputing) this._begin();
        var height = element.offsetHeight;
        if (!this._preComputing) this._end();
        return height;
      },

      'border-box-width': function(element) {
        if (!this._preComputing) this._begin();
        var width = element.offsetWidth;
        if (!this._preComputing) this._end();
        return width;
      },

      'margin-box-height': function(element) {
        var bHeight = this.get('border-box-height'),
         mTop = this.get('margin-top'),
         mBottom = this.get('margin-bottom');

        if (bHeight <= 0) return 0;

        return bHeight + mTop + mBottom;
      },

      'margin-box-width': function(element) {
        var bWidth = this.get('border-box-width'),
         mLeft = this.get('margin-left'),
         mRight = this.get('margin-right');

        if (bWidth <= 0) return 0;

        return bWidth + mLeft + mRight;
      },

      'top': function(element) {
        var offset = element.positionedOffset();
        return offset.top;
      },

      'bottom': function(element) {
        var offset = element.positionedOffset(),
         parent = element.getOffsetParent(),
         pHeight = parent.measure('height');

        var mHeight = this.get('border-box-height');

        return pHeight - mHeight - offset.top;
      },

      'left': function(element) {
        var offset = element.positionedOffset();
        return offset.left;
      },

      'right': function(element) {
        var offset = element.positionedOffset(),
         parent = element.getOffsetParent(),
         pWidth = parent.measure('width');

        var mWidth = this.get('border-box-width');

        return pWidth - mWidth - offset.left;
      },

      'padding-top': function(element) {
        return getPixelValue(element, 'paddingTop');
      },

      'padding-bottom': function(element) {
        return getPixelValue(element, 'paddingBottom');
      },

      'padding-left': function(element) {
        return getPixelValue(element, 'paddingLeft');
      },

      'padding-right': function(element) {
        return getPixelValue(element, 'paddingRight');
      },

      'border-top': function(element) {
        return getPixelValue(element, 'borderTopWidth');
      },

      'border-bottom': function(element) {
        return getPixelValue(element, 'borderBottomWidth');
      },

      'border-left': function(element) {
        return getPixelValue(element, 'borderLeftWidth');
      },

      'border-right': function(element) {
        return getPixelValue(element, 'borderRightWidth');
      },

      'margin-top': function(element) {
        return getPixelValue(element, 'marginTop');
      },

      'margin-bottom': function(element) {
        return getPixelValue(element, 'marginBottom');
      },

      'margin-left': function(element) {
        return getPixelValue(element, 'marginLeft');
      },

      'margin-right': function(element) {
        return getPixelValue(element, 'marginRight');
      }
    }
  });

  if ('getBoundingClientRect' in document.documentElement) {
    Object.extend(Element.Layout.COMPUTATIONS, {
      'right': function(element) {
        var parent = hasLayout(element.getOffsetParent());
        var rect = element.getBoundingClientRect(),
         pRect = parent.getBoundingClientRect();

        return (pRect.right - rect.right).round();
      },

      'bottom': function(element) {
        var parent = hasLayout(element.getOffsetParent());
        var rect = element.getBoundingClientRect(),
         pRect = parent.getBoundingClientRect();

        return (pRect.bottom - rect.bottom).round();
      }
    });
  }

  Element.Offset = Class.create({
    initialize: function(left, top) {
      this.left = left.round();
      this.top  = top.round();

      this[0] = this.left;
      this[1] = this.top;
    },

    relativeTo: function(offset) {
      return new Element.Offset(
        this.left - offset.left,
        this.top  - offset.top
      );
    },

    inspect: function() {
      return "#<Element.Offset left: #{left} top: #{top}>".interpolate(this);
    },

    toString: function() {
      return "[#{left}, #{top}]".interpolate(this);
    },

    toArray: function() {
      return [this.left, this.top];
    }
  });

  function getLayout(element, preCompute) {
    return new Element.Layout(element, preCompute);
  }

  function measure(element, property) {
    return $(element).getLayout().get(property);
  }

  function getDimensions(element) {
    element = $(element);
    var display = Element.getStyle(element, 'display');

    if (display && display !== 'none') {
      return { width: element.offsetWidth, height: element.offsetHeight };
    }

    var style = element.style;
    var originalStyles = {
      visibility: style.visibility,
      position:   style.position,
      display:    style.display
    };

    var newStyles = {
      visibility: 'hidden',
      display:    'block'
    };

    if (originalStyles.position !== 'fixed')
      newStyles.position = 'absolute';

    Element.setStyle(element, newStyles);

    var dimensions = {
      width:  element.offsetWidth,
      height: element.offsetHeight
    };

    Element.setStyle(element, originalStyles);

    return dimensions;
  }

  function getOffsetParent(element) {
    element = $(element);

    if (isDocument(element) || isDetached(element) || isBody(element) || isHtml(element))
      return $(document.body);

    var isInline = (Element.getStyle(element, 'display') === 'inline');
    if (!isInline && element.offsetParent) return $(element.offsetParent);

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

    return $(document.body);
  }


  function cumulativeOffset(element) {
    element = $(element);
    var valueT = 0, valueL = 0;
    if (element.parentNode) {
      do {
        valueT += element.offsetTop  || 0;
        valueL += element.offsetLeft || 0;
        element = element.offsetParent;
      } while (element);
    }
    return new Element.Offset(valueL, valueT);
  }

  function positionedOffset(element) {
    element = $(element);

    var layout = element.getLayout();

    var valueT = 0, valueL = 0;
    do {
      valueT += element.offsetTop  || 0;
      valueL += element.offsetLeft || 0;
      element = element.offsetParent;
      if (element) {
        if (isBody(element)) break;
        var p = Element.getStyle(element, 'position');
        if (p !== 'static') break;
      }
    } while (element);

    valueL -= layout.get('margin-top');
    valueT -= layout.get('margin-left');

    return new Element.Offset(valueL, valueT);
  }

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

  function viewportOffset(forElement) {
    element = $(element);
    var valueT = 0, valueL = 0, docBody = document.body;

    var element = forElement;
    do {
      valueT += element.offsetTop  || 0;
      valueL += element.offsetLeft || 0;
      if (element.offsetParent == docBody &&
        Element.getStyle(element, 'position') == 'absolute') break;
    } while (element = element.offsetParent);

    element = forElement;
    do {
      if (element != docBody) {
        valueT -= element.scrollTop  || 0;
        valueL -= element.scrollLeft || 0;
      }
    } while (element = element.parentNode);
    return new Element.Offset(valueL, valueT);
  }

  function absolutize(element) {
    element = $(element);

    if (Element.getStyle(element, 'position') === 'absolute') {
      return element;
    }

    var offsetParent = getOffsetParent(element);
    var eOffset = element.viewportOffset(),
     pOffset = offsetParent.viewportOffset();

    var offset = eOffset.relativeTo(pOffset);
    var layout = element.getLayout();

    element.store('prototype_absolutize_original_styles', {
      left:   element.getStyle('left'),
      top:    element.getStyle('top'),
      width:  element.getStyle('width'),
      height: element.getStyle('height')
    });

    element.setStyle({
      position: 'absolute',
      top:    offset.top + 'px',
      left:   offset.left + 'px',
      width:  layout.get('width') + 'px',
      height: layout.get('height') + 'px'
    });

    return element;
  }

  function relativize(element) {
    element = $(element);
    if (Element.getStyle(element, 'position') === 'relative') {
      return element;
    }

    var originalStyles =
     element.retrieve('prototype_absolutize_original_styles');

    if (originalStyles) element.setStyle(originalStyles);
    return element;
  }

  if (Prototype.Browser.IE) {
    getOffsetParent = getOffsetParent.wrap(
      function(proceed, element) {
        element = $(element);

        if (isDocument(element) || isDetached(element) || isBody(element) || isHtml(element))
          return $(document.body);

        var position = element.getStyle('position');
        if (position !== 'static') return proceed(element);

        element.setStyle({ position: 'relative' });
        var value = proceed(element);
        element.setStyle({ position: position });
        return value;
      }
    );

    positionedOffset = positionedOffset.wrap(function(proceed, element) {
      element = $(element);
      if (!element.parentNode) return new Element.Offset(0, 0);
      var position = element.getStyle('position');
      if (position !== 'static') return proceed(element);

      var offsetParent = element.getOffsetParent();
      if (offsetParent && offsetParent.getStyle('position') === 'fixed')
        hasLayout(offsetParent);

      element.setStyle({ position: 'relative' });
      var value = proceed(element);
      element.setStyle({ position: position });
      return value;
    });
  } else if (Prototype.Browser.Webkit) {
    cumulativeOffset = function(element) {
      element = $(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 new Element.Offset(valueL, valueT);
    };
  }


  Element.addMethods({
    getLayout:              getLayout,
    measure:                measure,
    getDimensions:          getDimensions,
    getOffsetParent:        getOffsetParent,
    cumulativeOffset:       cumulativeOffset,
    positionedOffset:       positionedOffset,
    cumulativeScrollOffset: cumulativeScrollOffset,
    viewportOffset:         viewportOffset,
    absolutize:             absolutize,
    relativize:             relativize
  });

  function isBody(element) {
    return element.nodeName.toUpperCase() === 'BODY';
  }

  function isHtml(element) {
    return element.nodeName.toUpperCase() === 'HTML';
  }

  function isDocument(element) {
    return element.nodeType === Node.DOCUMENT_NODE;
  }

  function isDetached(element) {
    return element !== document.body &&
     !Element.descendantOf(element, document.body);
  }

  if ('getBoundingClientRect' in document.documentElement) {
    Element.addMethods({
      viewportOffset: function(element) {
        element = $(element);
        if (isDetached(element)) return new Element.Offset(0, 0);

        var rect = element.getBoundingClientRect(),
         docEl = document.documentElement;
        return new Element.Offset(rect.left - docEl.clientLeft,
         rect.top - docEl.clientTop);
      }
    });
  }
})();
window.$$ = function() {
  var expression = $A(arguments).join(', ');
  return Prototype.Selector.select(expression, document);
};

Prototype.Selector = (function() {

  function select() {
    throw new Error('Method "Prototype.Selector.select" must be defined.');
  }

  function match() {
    throw new Error('Method "Prototype.Selector.match" must be defined.');
  }

  function find(elements, expression, index) {
    index = index || 0;
    var match = Prototype.Selector.match, length = elements.length, matchIndex = 0, i;

    for (i = 0; i < length; i++) {
      if (match(elements[i], expression) && index == matchIndex++) {
        return Element.extend(elements[i]);
      }
    }
  }

  function extendElements(elements) {
    for (var i = 0, length = elements.length; i < length; i++) {
      Element.extend(elements[i]);
    }
    return elements;
  }


  var K = Prototype.K;

  return {
    select: select,
    match: match,
    find: find,
    extendElements: (Element.extend === K) ? K : extendElements,
    extendElement: Element.extend
  };
})();
Prototype._original_property = window.Sizzle;
/*!
 * Sizzle CSS Selector Engine
 *  Copyright 2011, The Dojo Foundation
 *  Released under the MIT, BSD, and GPL Licenses.
 *  More information: http://sizzlejs.com/
 */
(function(){

var chunker = /((?:\((?:\([^()]+\)|[^()]+)+\)|\[(?:\[[^\[\]]*\]|['"][^'"]*['"]|[^\[\]'"]+)+\]|\\.|[^ >+~,(\[\\]+)+|[>+~])(\s*,\s*)?((?:.|\r|\n)*)/g,
	done = 0,
	toString = Object.prototype.toString,
	hasDuplicate = false,
	baseHasDuplicate = true,
	rBackslash = /\\/g,
	rNonWord = /\W/;

// Here we check if the JavaScript engine is using some sort of
// optimization where it does not always call our comparision
// function. If that is the case, discard the hasDuplicate value.
//   Thus far that includes Google Chrome.
[0, 0].sort(function() {
	baseHasDuplicate = false;
	return 0;
});

var Sizzle = function( selector, context, results, seed ) {
	results = results || [];
	context = context || document;

	var origContext = context;

	if ( context.nodeType !== 1 && context.nodeType !== 9 ) {
		return [];
	}

	if ( !selector || typeof selector !== "string" ) {
		return results;
	}

	var m, set, checkSet, extra, ret, cur, pop, i,
		prune = true,
		contextXML = Sizzle.isXML( context ),
		parts = [],
		soFar = selector;

	// Reset the position of the chunker regexp (start from head)
	do {
		chunker.exec( "" );
		m = chunker.exec( soFar );

		if ( m ) {
			soFar = m[3];

			parts.push( m[1] );

			if ( m[2] ) {
				extra = m[3];
				break;
			}
		}
	} while ( m );

	if ( parts.length > 1 && origPOS.exec( selector ) ) {

		if ( parts.length === 2 && Expr.relative[ parts[0] ] ) {
			set = posProcess( parts[0] + parts[1], context );

		} else {
			set = Expr.relative[ parts[0] ] ?
				[ context ] :
				Sizzle( parts.shift(), context );

			while ( parts.length ) {
				selector = parts.shift();

				if ( Expr.relative[ selector ] ) {
					selector += parts.shift();
				}

				set = posProcess( selector, set );
			}
		}

	} else {
		// Take a shortcut and set the context if the root selector is an ID
		// (but not if it'll be faster if the inner selector is an ID)
		if ( !seed && parts.length > 1 && context.nodeType === 9 && !contextXML &&
				Expr.match.ID.test(parts[0]) && !Expr.match.ID.test(parts[parts.length - 1]) ) {

			ret = Sizzle.find( parts.shift(), context, contextXML );
			context = ret.expr ?
				Sizzle.filter( ret.expr, ret.set )[0] :
				ret.set[0];
		}

		if ( context ) {
			ret = seed ?
				{ expr: parts.pop(), set: makeArray(seed) } :
				Sizzle.find( parts.pop(), parts.length === 1 && (parts[0] === "~" || parts[0] === "+") && context.parentNode ? context.parentNode : context, contextXML );

			set = ret.expr ?
				Sizzle.filter( ret.expr, ret.set ) :
				ret.set;

			if ( parts.length > 0 ) {
				checkSet = makeArray( set );

			} else {
				prune = false;
			}

			while ( parts.length ) {
				cur = parts.pop();
				pop = cur;

				if ( !Expr.relative[ cur ] ) {
					cur = "";
				} else {
					pop = parts.pop();
				}

				if ( pop == null ) {
					pop = context;
				}

				Expr.relative[ cur ]( checkSet, pop, contextXML );
			}

		} else {
			checkSet = parts = [];
		}
	}

	if ( !checkSet ) {
		checkSet = set;
	}

	if ( !checkSet ) {
		Sizzle.error( cur || selector );
	}

	if ( toString.call(checkSet) === "[object Array]" ) {
		if ( !prune ) {
			results.push.apply( results, checkSet );

		} else if ( context && context.nodeType === 1 ) {
			for ( i = 0; checkSet[i] != null; i++ ) {
				if ( checkSet[i] && (checkSet[i] === true || checkSet[i].nodeType === 1 && Sizzle.contains(context, checkSet[i])) ) {
					results.push( set[i] );
				}
			}

		} else {
			for ( i = 0; checkSet[i] != null; i++ ) {
				if ( checkSet[i] && checkSet[i].nodeType === 1 ) {
					results.push( set[i] );
				}
			}
		}

	} else {
		makeArray( checkSet, results );
	}

	if ( extra ) {
		Sizzle( extra, origContext, results, seed );
		Sizzle.uniqueSort( results );
	}

	return results;
};

Sizzle.uniqueSort = function( results ) {
	if ( sortOrder ) {
		hasDuplicate = baseHasDuplicate;
		results.sort( sortOrder );

		if ( hasDuplicate ) {
			for ( var i = 1; i < results.length; i++ ) {
				if ( results[i] === results[ i - 1 ] ) {
					results.splice( i--, 1 );
				}
			}
		}
	}

	return results;
};

Sizzle.matches = function( expr, set ) {
	return Sizzle( expr, null, null, set );
};

Sizzle.matchesSelector = function( node, expr ) {
	return Sizzle( expr, null, null, [node] ).length > 0;
};

Sizzle.find = function( expr, context, isXML ) {
	var set;

	if ( !expr ) {
		return [];
	}

	for ( var i = 0, l = Expr.order.length; i < l; i++ ) {
		var match,
			type = Expr.order[i];

		if ( (match = Expr.leftMatch[ type ].exec( expr )) ) {
			var left = match[1];
			match.splice( 1, 1 );

			if ( left.substr( left.length - 1 ) !== "\\" ) {
				match[1] = (match[1] || "").replace( rBackslash, "" );
				set = Expr.find[ type ]( match, context, isXML );

				if ( set != null ) {
					expr = expr.replace( Expr.match[ type ], "" );
					break;
				}
			}
		}
	}

	if ( !set ) {
		set = typeof context.getElementsByTagName !== "undefined" ?
			context.getElementsByTagName( "*" ) :
			[];
	}

	return { set: set, expr: expr };
};

Sizzle.filter = function( expr, set, inplace, not ) {
	var match, anyFound,
		old = expr,
		result = [],
		curLoop = set,
		isXMLFilter = set && set[0] && Sizzle.isXML( set[0] );

	while ( expr && set.length ) {
		for ( var type in Expr.filter ) {
			if ( (match = Expr.leftMatch[ type ].exec( expr )) != null && match[2] ) {
				var found, item,
					filter = Expr.filter[ type ],
					left = match[1];

				anyFound = false;

				match.splice(1,1);

				if ( left.substr( left.length - 1 ) === "\\" ) {
					continue;
				}

				if ( curLoop === result ) {
					result = [];
				}

				if ( Expr.preFilter[ type ] ) {
					match = Expr.preFilter[ type ]( match, curLoop, inplace, result, not, isXMLFilter );

					if ( !match ) {
						anyFound = found = true;

					} else if ( match === true ) {
						continue;
					}
				}

				if ( match ) {
					for ( var i = 0; (item = curLoop[i]) != null; i++ ) {
						if ( item ) {
							found = filter( item, match, i, curLoop );
							var pass = not ^ !!found;

							if ( inplace && found != null ) {
								if ( pass ) {
									anyFound = true;

								} else {
									curLoop[i] = false;
								}

							} else if ( pass ) {
								result.push( item );
								anyFound = true;
							}
						}
					}
				}

				if ( found !== undefined ) {
					if ( !inplace ) {
						curLoop = result;
					}

					expr = expr.replace( Expr.match[ type ], "" );

					if ( !anyFound ) {
						return [];
					}

					break;
				}
			}
		}

		// Improper expression
		if ( expr === old ) {
			if ( anyFound == null ) {
				Sizzle.error( expr );

			} else {
				break;
			}
		}

		old = expr;
	}

	return curLoop;
};

Sizzle.error = function( msg ) {
	throw "Syntax error, unrecognized expression: " + msg;
};

var Expr = Sizzle.selectors = {
	order: [ "ID", "NAME", "TAG" ],

	match: {
		ID: /#((?:[\w\u00c0-\uFFFF\-]|\\.)+)/,
		CLASS: /\.((?:[\w\u00c0-\uFFFF\-]|\\.)+)/,
		NAME: /\[name=['"]*((?:[\w\u00c0-\uFFFF\-]|\\.)+)['"]*\]/,
		ATTR: /\[\s*((?:[\w\u00c0-\uFFFF\-]|\\.)+)\s*(?:(\S?=)\s*(?:(['"])(.*?)\3|(#?(?:[\w\u00c0-\uFFFF\-]|\\.)*)|)|)\s*\]/,
		TAG: /^((?:[\w\u00c0-\uFFFF\*\-]|\\.)+)/,
		CHILD: /:(only|nth|last|first)-child(?:\(\s*(even|odd|(?:[+\-]?\d+|(?:[+\-]?\d*)?n\s*(?:[+\-]\s*\d+)?))\s*\))?/,
		POS: /:(nth|eq|gt|lt|first|last|even|odd)(?:\((\d*)\))?(?=[^\-]|$)/,
		PSEUDO: /:((?:[\w\u00c0-\uFFFF\-]|\\.)+)(?:\((['"]?)((?:\([^\)]+\)|[^\(\)]*)+)\2\))?/
	},

	leftMatch: {},

	attrMap: {
		"class": "className",
		"for": "htmlFor"
	},

	attrHandle: {
		href: function( elem ) {
			return elem.getAttribute( "href" );
		},
		type: function( elem ) {
			return elem.getAttribute( "type" );
		}
	},

	relative: {
		"+": function(checkSet, part){
			var isPartStr = typeof part === "string",
				isTag = isPartStr && !rNonWord.test( part ),
				isPartStrNotTag = isPartStr && !isTag;

			if ( isTag ) {
				part = part.toLowerCase();
			}

			for ( var i = 0, l = checkSet.length, elem; i < l; i++ ) {
				if ( (elem = checkSet[i]) ) {
					while ( (elem = elem.previousSibling) && elem.nodeType !== 1 ) {}

					checkSet[i] = isPartStrNotTag || elem && elem.nodeName.toLowerCase() === part ?
						elem || false :
						elem === part;
				}
			}

			if ( isPartStrNotTag ) {
				Sizzle.filter( part, checkSet, true );
			}
		},

		">": function( checkSet, part ) {
			var elem,
				isPartStr = typeof part === "string",
				i = 0,
				l = checkSet.length;

			if ( isPartStr && !rNonWord.test( part ) ) {
				part = part.toLowerCase();

				for ( ; i < l; i++ ) {
					elem = checkSet[i];

					if ( elem ) {
						var parent = elem.parentNode;
						checkSet[i] = parent.nodeName.toLowerCase() === part ? parent : false;
					}
				}

			} else {
				for ( ; i < l; i++ ) {
					elem = checkSet[i];

					if ( elem ) {
						checkSet[i] = isPartStr ?
							elem.parentNode :
							elem.parentNode === part;
					}
				}

				if ( isPartStr ) {
					Sizzle.filter( part, checkSet, true );
				}
			}
		},

		"": function(checkSet, part, isXML){
			var nodeCheck,
				doneName = done++,
				checkFn = dirCheck;

			if ( typeof part === "string" && !rNonWord.test( part ) ) {
				part = part.toLowerCase();
				nodeCheck = part;
				checkFn = dirNodeCheck;
			}

			checkFn( "parentNode", part, doneName, checkSet, nodeCheck, isXML );
		},

		"~": function( checkSet, part, isXML ) {
			var nodeCheck,
				doneName = done++,
				checkFn = dirCheck;

			if ( typeof part === "string" && !rNonWord.test( part ) ) {
				part = part.toLowerCase();
				nodeCheck = part;
				checkFn = dirNodeCheck;
			}

			checkFn( "previousSibling", part, doneName, checkSet, nodeCheck, isXML );
		}
	},

	find: {
		ID: function( match, context, isXML ) {
			if ( typeof context.getElementById !== "undefined" && !isXML ) {
				var m = context.getElementById(match[1]);
				// Check parentNode to catch when Blackberry 4.6 returns
				// nodes that are no longer in the document #6963
				return m && m.parentNode ? [m] : [];
			}
		},

		NAME: function( match, context ) {
			if ( typeof context.getElementsByName !== "undefined" ) {
				var ret = [],
					results = context.getElementsByName( match[1] );

				for ( var i = 0, l = results.length; i < l; i++ ) {
					if ( results[i].getAttribute("name") === match[1] ) {
						ret.push( results[i] );
					}
				}

				return ret.length === 0 ? null : ret;
			}
		},

		TAG: function( match, context ) {
			if ( typeof context.getElementsByTagName !== "undefined" ) {
				return context.getElementsByTagName( match[1] );
			}
		}
	},
	preFilter: {
		CLASS: function( match, curLoop, inplace, result, not, isXML ) {
			match = " " + match[1].replace( rBackslash, "" ) + " ";

			if ( isXML ) {
				return match;
			}

			for ( var i = 0, elem; (elem = curLoop[i]) != null; i++ ) {
				if ( elem ) {
					if ( not ^ (elem.className && (" " + elem.className + " ").replace(/[\t\n\r]/g, " ").indexOf(match) >= 0) ) {
						if ( !inplace ) {
							result.push( elem );
						}

					} else if ( inplace ) {
						curLoop[i] = false;
					}
				}
			}

			return false;
		},

		ID: function( match ) {
			return match[1].replace( rBackslash, "" );
		},

		TAG: function( match, curLoop ) {
			return match[1].replace( rBackslash, "" ).toLowerCase();
		},

		CHILD: function( match ) {
			if ( match[1] === "nth" ) {
				if ( !match[2] ) {
					Sizzle.error( match[0] );
				}

				match[2] = match[2].replace(/^\+|\s*/g, '');

				// parse equations like 'even', 'odd', '5', '2n', '3n+2', '4n-1', '-n+6'
				var test = /(-?)(\d*)(?:n([+\-]?\d*))?/.exec(
					match[2] === "even" && "2n" || match[2] === "odd" && "2n+1" ||
					!/\D/.test( match[2] ) && "0n+" + match[2] || match[2]);

				// calculate the numbers (first)n+(last) including if they are negative
				match[2] = (test[1] + (test[2] || 1)) - 0;
				match[3] = test[3] - 0;
			}
			else if ( match[2] ) {
				Sizzle.error( match[0] );
			}

			// TODO: Move to normal caching system
			match[0] = done++;

			return match;
		},

		ATTR: function( match, curLoop, inplace, result, not, isXML ) {
			var name = match[1] = match[1].replace( rBackslash, "" );

			if ( !isXML && Expr.attrMap[name] ) {
				match[1] = Expr.attrMap[name];
			}

			// Handle if an un-quoted value was used
			match[4] = ( match[4] || match[5] || "" ).replace( rBackslash, "" );

			if ( match[2] === "~=" ) {
				match[4] = " " + match[4] + " ";
			}

			return match;
		},

		PSEUDO: function( match, curLoop, inplace, result, not ) {
			if ( match[1] === "not" ) {
				// If we're dealing with a complex expression, or a simple one
				if ( ( chunker.exec(match[3]) || "" ).length > 1 || /^\w/.test(match[3]) ) {
					match[3] = Sizzle(match[3], null, null, curLoop);

				} else {
					var ret = Sizzle.filter(match[3], curLoop, inplace, true ^ not);

					if ( !inplace ) {
						result.push.apply( result, ret );
					}

					return false;
				}

			} else if ( Expr.match.POS.test( match[0] ) || Expr.match.CHILD.test( match[0] ) ) {
				return true;
			}

			return match;
		},

		POS: function( match ) {
			match.unshift( true );

			return match;
		}
	},

	filters: {
		enabled: function( elem ) {
			return elem.disabled === false && elem.type !== "hidden";
		},

		disabled: function( elem ) {
			return elem.disabled === true;
		},

		checked: function( elem ) {
			return elem.checked === true;
		},

		selected: function( elem ) {
			// Accessing this property makes selected-by-default
			// options in Safari work properly
			if ( elem.parentNode ) {
				elem.parentNode.selectedIndex;
			}

			return elem.selected === true;
		},

		parent: function( elem ) {
			return !!elem.firstChild;
		},

		empty: function( elem ) {
			return !elem.firstChild;
		},

		has: function( elem, i, match ) {
			return !!Sizzle( match[3], elem ).length;
		},

		header: function( elem ) {
			return (/h\d/i).test( elem.nodeName );
		},

		text: function( elem ) {
			var attr = elem.getAttribute( "type" ), type = elem.type;
			// IE6 and 7 will map elem.type to 'text' for new HTML5 types (search, etc)
			// use getAttribute instead to test this case
			return elem.nodeName.toLowerCase() === "input" && "text" === type && ( attr === type || attr === null );
		},

		radio: function( elem ) {
			return elem.nodeName.toLowerCase() === "input" && "radio" === elem.type;
		},

		checkbox: function( elem ) {
			return elem.nodeName.toLowerCase() === "input" && "checkbox" === elem.type;
		},

		file: function( elem ) {
			return elem.nodeName.toLowerCase() === "input" && "file" === elem.type;
		},

		password: function( elem ) {
			return elem.nodeName.toLowerCase() === "input" && "password" === elem.type;
		},

		submit: function( elem ) {
			var name = elem.nodeName.toLowerCase();
			return (name === "input" || name === "button") && "submit" === elem.type;
		},

		image: function( elem ) {
			return elem.nodeName.toLowerCase() === "input" && "image" === elem.type;
		},

		reset: function( elem ) {
			var name = elem.nodeName.toLowerCase();
			return (name === "input" || name === "button") && "reset" === elem.type;
		},

		button: function( elem ) {
			var name = elem.nodeName.toLowerCase();
			return name === "input" && "button" === elem.type || name === "button";
		},

		input: function( elem ) {
			return (/input|select|textarea|button/i).test( elem.nodeName );
		},

		focus: function( elem ) {
			return elem === elem.ownerDocument.activeElement;
		}
	},
	setFilters: {
		first: function( elem, i ) {
			return i === 0;
		},

		last: function( elem, i, match, array ) {
			return i === array.length - 1;
		},

		even: function( elem, i ) {
			return i % 2 === 0;
		},

		odd: function( elem, i ) {
			return i % 2 === 1;
		},

		lt: function( elem, i, match ) {
			return i < match[3] - 0;
		},

		gt: function( elem, i, match ) {
			return i > match[3] - 0;
		},

		nth: function( elem, i, match ) {
			return match[3] - 0 === i;
		},

		eq: function( elem, i, match ) {
			return match[3] - 0 === i;
		}
	},
	filter: {
		PSEUDO: function( elem, match, i, array ) {
			var name = match[1],
				filter = Expr.filters[ name ];

			if ( filter ) {
				return filter( elem, i, match, array );

			} else if ( name === "contains" ) {
				return (elem.textContent || elem.innerText || Sizzle.getText([ elem ]) || "").indexOf(match[3]) >= 0;

			} else if ( name === "not" ) {
				var not = match[3];

				for ( var j = 0, l = not.length; j < l; j++ ) {
					if ( not[j] === elem ) {
						return false;
					}
				}

				return true;

			} else {
				Sizzle.error( name );
			}
		},

		CHILD: function( elem, match ) {
			var type = match[1],
				node = elem;

			switch ( type ) {
				case "only":
				case "first":
					while ( (node = node.previousSibling) )	 {
						if ( node.nodeType === 1 ) {
							return false;
						}
					}

					if ( type === "first" ) {
						return true;
					}

					node = elem;

				case "last":
					while ( (node = node.nextSibling) )	 {
						if ( node.nodeType === 1 ) {
							return false;
						}
					}

					return true;

				case "nth":
					var first = match[2],
						last = match[3];

					if ( first === 1 && last === 0 ) {
						return true;
					}

					var doneName = match[0],
						parent = elem.parentNode;

					if ( parent && (parent.sizcache !== doneName || !elem.nodeIndex) ) {
						var count = 0;

						for ( node = parent.firstChild; node; node = node.nextSibling ) {
							if ( node.nodeType === 1 ) {
								node.nodeIndex = ++count;
							}
						}

						parent.sizcache = doneName;
					}

					var diff = elem.nodeIndex - last;

					if ( first === 0 ) {
						return diff === 0;

					} else {
						return ( diff % first === 0 && diff / first >= 0 );
					}
			}
		},

		ID: function( elem, match ) {
			return elem.nodeType === 1 && elem.getAttribute("id") === match;
		},

		TAG: function( elem, match ) {
			return (match === "*" && elem.nodeType === 1) || elem.nodeName.toLowerCase() === match;
		},

		CLASS: function( elem, match ) {
			return (" " + (elem.className || elem.getAttribute("class")) + " ")
				.indexOf( match ) > -1;
		},

		ATTR: function( elem, match ) {
			var name = match[1],
				result = Expr.attrHandle[ name ] ?
					Expr.attrHandle[ name ]( elem ) :
					elem[ name ] != null ?
						elem[ name ] :
						elem.getAttribute( name ),
				value = result + "",
				type = match[2],
				check = match[4];

			return result == null ?
				type === "!=" :
				type === "=" ?
				value === check :
				type === "*=" ?
				value.indexOf(check) >= 0 :
				type === "~=" ?
				(" " + value + " ").indexOf(check) >= 0 :
				!check ?
				value && result !== false :
				type === "!=" ?
				value !== check :
				type === "^=" ?
				value.indexOf(check) === 0 :
				type === "$=" ?
				value.substr(value.length - check.length) === check :
				type === "|=" ?
				value === check || value.substr(0, check.length + 1) === check + "-" :
				false;
		},

		POS: function( elem, match, i, array ) {
			var name = match[2],
				filter = Expr.setFilters[ name ];

			if ( filter ) {
				return filter( elem, i, match, array );
			}
		}
	}
};

var origPOS = Expr.match.POS,
	fescape = function(all, num){
		return "\\" + (num - 0 + 1);
	};

for ( var type in Expr.match ) {
	Expr.match[ type ] = new RegExp( Expr.match[ type ].source + (/(?![^\[]*\])(?![^\(]*\))/.source) );
	Expr.leftMatch[ type ] = new RegExp( /(^(?:.|\r|\n)*?)/.source + Expr.match[ type ].source.replace(/\\(\d+)/g, fescape) );
}

var makeArray = function( array, results ) {
	array = Array.prototype.slice.call( array, 0 );

	if ( results ) {
		results.push.apply( results, array );
		return results;
	}

	return array;
};

// Perform a simple check to determine if the browser is capable of
// converting a NodeList to an array using builtin methods.
// Also verifies that the returned array holds DOM nodes
// (which is not the case in the Blackberry browser)
try {
	Array.prototype.slice.call( document.documentElement.childNodes, 0 )[0].nodeType;

// Provide a fallback method if it does not work
} catch( e ) {
	makeArray = function( array, results ) {
		var i = 0,
			ret = results || [];

		if ( toString.call(array) === "[object Array]" ) {
			Array.prototype.push.apply( ret, array );

		} else {
			if ( typeof array.length === "number" ) {
				for ( var l = array.length; i < l; i++ ) {
					ret.push( array[i] );
				}

			} else {
				for ( ; array[i]; i++ ) {
					ret.push( array[i] );
				}
			}
		}

		return ret;
	};
}

var sortOrder, siblingCheck;

if ( document.documentElement.compareDocumentPosition ) {
	sortOrder = function( a, b ) {
		if ( a === b ) {
			hasDuplicate = true;
			return 0;
		}

		if ( !a.compareDocumentPosition || !b.compareDocumentPosition ) {
			return a.compareDocumentPosition ? -1 : 1;
		}

		return a.compareDocumentPosition(b) & 4 ? -1 : 1;
	};

} else {
	sortOrder = function( a, b ) {
		// The nodes are identical, we can exit early
		if ( a === b ) {
			hasDuplicate = true;
			return 0;

		// Fallback to using sourceIndex (in IE) if it's available on both nodes
		} else if ( a.sourceIndex && b.sourceIndex ) {
			return a.sourceIndex - b.sourceIndex;
		}

		var al, bl,
			ap = [],
			bp = [],
			aup = a.parentNode,
			bup = b.parentNode,
			cur = aup;

		// If the nodes are siblings (or identical) we can do a quick check
		if ( aup === bup ) {
			return siblingCheck( a, b );

		// If no parents were found then the nodes are disconnected
		} else if ( !aup ) {
			return -1;

		} else if ( !bup ) {
			return 1;
		}

		// Otherwise they're somewhere else in the tree so we need
		// to build up a full list of the parentNodes for comparison
		while ( cur ) {
			ap.unshift( cur );
			cur = cur.parentNode;
		}

		cur = bup;

		while ( cur ) {
			bp.unshift( cur );
			cur = cur.parentNode;
		}

		al = ap.length;
		bl = bp.length;

		// Start walking down the tree looking for a discrepancy
		for ( var i = 0; i < al && i < bl; i++ ) {
			if ( ap[i] !== bp[i] ) {
				return siblingCheck( ap[i], bp[i] );
			}
		}

		// We ended someplace up the tree so do a sibling check
		return i === al ?
			siblingCheck( a, bp[i], -1 ) :
			siblingCheck( ap[i], b, 1 );
	};

	siblingCheck = function( a, b, ret ) {
		if ( a === b ) {
			return ret;
		}

		var cur = a.nextSibling;

		while ( cur ) {
			if ( cur === b ) {
				return -1;
			}

			cur = cur.nextSibling;
		}

		return 1;
	};
}

// Utility function for retreiving the text value of an array of DOM nodes
Sizzle.getText = function( elems ) {
	var ret = "", elem;

	for ( var i = 0; elems[i]; i++ ) {
		elem = elems[i];

		// Get the text from text nodes and CDATA nodes
		if ( elem.nodeType === 3 || elem.nodeType === 4 ) {
			ret += elem.nodeValue;

		// Traverse everything else, except comment nodes
		} else if ( elem.nodeType !== 8 ) {
			ret += Sizzle.getText( elem.childNodes );
		}
	}

	return ret;
};

// Check to see if the browser returns elements by name when
// querying by getElementById (and provide a workaround)
(function(){
	// We're going to inject a fake input element with a specified name
	var form = document.createElement("div"),
		id = "script" + (new Date()).getTime(),
		root = document.documentElement;

	form.innerHTML = "<a name='" + id + "'/>";

	// Inject it into the root element, check its status, and remove it quickly
	root.insertBefore( form, root.firstChild );

	// The workaround has to do additional checks after a getElementById
	// Which slows things down for other browsers (hence the branching)
	if ( document.getElementById( id ) ) {
		Expr.find.ID = function( match, context, isXML ) {
			if ( typeof context.getElementById !== "undefined" && !isXML ) {
				var m = context.getElementById(match[1]);

				return m ?
					m.id === match[1] || typeof m.getAttributeNode !== "undefined" && m.getAttributeNode("id").nodeValue === match[1] ?
						[m] :
						undefined :
					[];
			}
		};

		Expr.filter.ID = function( elem, match ) {
			var node = typeof elem.getAttributeNode !== "undefined" && elem.getAttributeNode("id");

			return elem.nodeType === 1 && node && node.nodeValue === match;
		};
	}

	root.removeChild( form );

	// release memory in IE
	root = form = null;
})();

(function(){
	// Check to see if the browser returns only elements
	// when doing getElementsByTagName("*")

	// Create a fake element
	var div = document.createElement("div");
	div.appendChild( document.createComment("") );

	// Make sure no comments are found
	if ( div.getElementsByTagName("*").length > 0 ) {
		Expr.find.TAG = function( match, context ) {
			var results = context.getElementsByTagName( match[1] );

			// Filter out possible comments
			if ( match[1] === "*" ) {
				var tmp = [];

				for ( var i = 0; results[i]; i++ ) {
					if ( results[i].nodeType === 1 ) {
						tmp.push( results[i] );
					}
				}

				results = tmp;
			}

			return results;
		};
	}

	// Check to see if an attribute returns normalized href attributes
	div.innerHTML = "<a href='#'></a>";

	if ( div.firstChild && typeof div.firstChild.getAttribute !== "undefined" &&
			div.firstChild.getAttribute("href") !== "#" ) {

		Expr.attrHandle.href = function( elem ) {
			return elem.getAttribute( "href", 2 );
		};
	}

	// release memory in IE
	div = null;
})();

if ( document.querySelectorAll ) {
	(function(){
		var oldSizzle = Sizzle,
			div = document.createElement("div"),
			id = "__sizzle__";

		div.innerHTML = "<p class='TEST'></p>";

		// Safari can't handle uppercase or unicode characters when
		// in quirks mode.
		if ( div.querySelectorAll && div.querySelectorAll(".TEST").length === 0 ) {
			return;
		}

		Sizzle = function( query, context, extra, seed ) {
			context = context || document;

			// Only use querySelectorAll on non-XML documents
			// (ID selectors don't work in non-HTML documents)
			if ( !seed && !Sizzle.isXML(context) ) {
				// See if we find a selector to speed up
				var match = /^(\w+$)|^\.([\w\-]+$)|^#([\w\-]+$)/.exec( query );

				if ( match && (context.nodeType === 1 || context.nodeType === 9) ) {
					// Speed-up: Sizzle("TAG")
					if ( match[1] ) {
						return makeArray( context.getElementsByTagName( query ), extra );

					// Speed-up: Sizzle(".CLASS")
					} else if ( match[2] && Expr.find.CLASS && context.getElementsByClassName ) {
						return makeArray( context.getElementsByClassName( match[2] ), extra );
					}
				}

				if ( context.nodeType === 9 ) {
					// Speed-up: Sizzle("body")
					// The body element only exists once, optimize finding it
					if ( query === "body" && context.body ) {
						return makeArray( [ context.body ], extra );

					// Speed-up: Sizzle("#ID")
					} else if ( match && match[3] ) {
						var elem = context.getElementById( match[3] );

						// Check parentNode to catch when Blackberry 4.6 returns
						// nodes that are no longer in the document #6963
						if ( elem && elem.parentNode ) {
							// Handle the case where IE and Opera return items
							// by name instead of ID
							if ( elem.id === match[3] ) {
								return makeArray( [ elem ], extra );
							}

						} else {
							return makeArray( [], extra );
						}
					}

					try {
						return makeArray( context.querySelectorAll(query), extra );
					} catch(qsaError) {}

				// qSA works strangely on Element-rooted queries
				// We can work around this by specifying an extra ID on the root
				// and working up from there (Thanks to Andrew Dupont for the technique)
				// IE 8 doesn't work on object elements
				} else if ( context.nodeType === 1 && context.nodeName.toLowerCase() !== "object" ) {
					var oldContext = context,
						old = context.getAttribute( "id" ),
						nid = old || id,
						hasParent = context.parentNode,
						relativeHierarchySelector = /^\s*[+~]/.test( query );

					if ( !old ) {
						context.setAttribute( "id", nid );
					} else {
						nid = nid.replace( /'/g, "\\$&" );
					}
					if ( relativeHierarchySelector && hasParent ) {
						context = context.parentNode;
					}

					try {
						if ( !relativeHierarchySelector || hasParent ) {
							return makeArray( context.querySelectorAll( "[id='" + nid + "'] " + query ), extra );
						}

					} catch(pseudoError) {
					} finally {
						if ( !old ) {
							oldContext.removeAttribute( "id" );
						}
					}
				}
			}

			return oldSizzle(query, context, extra, seed);
		};

		for ( var prop in oldSizzle ) {
			Sizzle[ prop ] = oldSizzle[ prop ];
		}

		// release memory in IE
		div = null;
	})();
}

(function(){
	var html = document.documentElement,
		matches = html.matchesSelector || html.mozMatchesSelector || html.webkitMatchesSelector || html.msMatchesSelector;

	if ( matches ) {
		// Check to see if it's possible to do matchesSelector
		// on a disconnected node (IE 9 fails this)
		var disconnectedMatch = !matches.call( document.createElement( "div" ), "div" ),
			pseudoWorks = false;

		try {
			// This should fail with an exception
			// Gecko does not error, returns false instead
			matches.call( document.documentElement, "[test!='']:sizzle" );

		} catch( pseudoError ) {
			pseudoWorks = true;
		}

		Sizzle.matchesSelector = function( node, expr ) {
			// Make sure that attribute selectors are quoted
			expr = expr.replace(/\=\s*([^'"\]]*)\s*\]/g, "='$1']");

			if ( !Sizzle.isXML( node ) ) {
				try {
					if ( pseudoWorks || !Expr.match.PSEUDO.test( expr ) && !/!=/.test( expr ) ) {
						var ret = matches.call( node, expr );

						// IE 9's matchesSelector returns false on disconnected nodes
						if ( ret || !disconnectedMatch ||
								// As well, disconnected nodes are said to be in a document
								// fragment in IE 9, so check for that
								node.document && node.document.nodeType !== 11 ) {
							return ret;
						}
					}
				} catch(e) {}
			}

			return Sizzle(expr, null, null, [node]).length > 0;
		};
	}
})();

(function(){
	var div = document.createElement("div");

	div.innerHTML = "<div class='test e'></div><div class='test'></div>";

	// Opera can't find a second classname (in 9.6)
	// Also, make sure that getElementsByClassName actually exists
	if ( !div.getElementsByClassName || div.getElementsByClassName("e").length === 0 ) {
		return;
	}

	// Safari caches class attributes, doesn't catch changes (in 3.2)
	div.lastChild.className = "e";

	if ( div.getElementsByClassName("e").length === 1 ) {
		return;
	}

	Expr.order.splice(1, 0, "CLASS");
	Expr.find.CLASS = function( match, context, isXML ) {
		if ( typeof context.getElementsByClassName !== "undefined" && !isXML ) {
			return context.getElementsByClassName(match[1]);
		}
	};

	// release memory in IE
	div = null;
})();

function dirNodeCheck( dir, cur, doneName, checkSet, nodeCheck, isXML ) {
	for ( var i = 0, l = checkSet.length; i < l; i++ ) {
		var elem = checkSet[i];

		if ( elem ) {
			var match = false;

			elem = elem[dir];

			while ( elem ) {
				if ( elem.sizcache === doneName ) {
					match = checkSet[elem.sizset];
					break;
				}

				if ( elem.nodeType === 1 && !isXML ){
					elem.sizcache = doneName;
					elem.sizset = i;
				}

				if ( elem.nodeName.toLowerCase() === cur ) {
					match = elem;
					break;
				}

				elem = elem[dir];
			}

			checkSet[i] = match;
		}
	}
}

function dirCheck( dir, cur, doneName, checkSet, nodeCheck, isXML ) {
	for ( var i = 0, l = checkSet.length; i < l; i++ ) {
		var elem = checkSet[i];

		if ( elem ) {
			var match = false;

			elem = elem[dir];

			while ( elem ) {
				if ( elem.sizcache === doneName ) {
					match = checkSet[elem.sizset];
					break;
				}

				if ( elem.nodeType === 1 ) {
					if ( !isXML ) {
						elem.sizcache = doneName;
						elem.sizset = i;
					}

					if ( typeof cur !== "string" ) {
						if ( elem === cur ) {
							match = true;
							break;
						}

					} else if ( Sizzle.filter( cur, [elem] ).length > 0 ) {
						match = elem;
						break;
					}
				}

				elem = elem[dir];
			}

			checkSet[i] = match;
		}
	}
}

if ( document.documentElement.contains ) {
	Sizzle.contains = function( a, b ) {
		return a !== b && (a.contains ? a.contains(b) : true);
	};

} else if ( document.documentElement.compareDocumentPosition ) {
	Sizzle.contains = function( a, b ) {
		return !!(a.compareDocumentPosition(b) & 16);
	};

} else {
	Sizzle.contains = function() {
		return false;
	};
}

Sizzle.isXML = function( elem ) {
	// documentElement is verified for cases where it doesn't yet exist
	// (such as loading iframes in IE - #4833)
	var documentElement = (elem ? elem.ownerDocument || elem : 0).documentElement;

	return documentElement ? documentElement.nodeName !== "HTML" : false;
};

var posProcess = function( selector, context ) {
	var match,
		tmpSet = [],
		later = "",
		root = context.nodeType ? [context] : context;

	// Position selectors must be done after the filter
	// And so must :not(positional) so we move all PSEUDOs to the end
	while ( (match = Expr.match.PSEUDO.exec( selector )) ) {
		later += match[0];
		selector = selector.replace( Expr.match.PSEUDO, "" );
	}

	selector = Expr.relative[selector] ? selector + "*" : selector;

	for ( var i = 0, l = root.length; i < l; i++ ) {
		Sizzle( selector, root[i], tmpSet );
	}

	return Sizzle.filter( later, tmpSet );
};

// EXPOSE

window.Sizzle = Sizzle;

})();


;(function(engine) {
  var extendElements = Prototype.Selector.extendElements;

  function select(selector, scope) {
    return extendElements(engine(selector, scope || document));
  }

  function match(element, selector) {
    return engine.matches(selector, [element]).length == 1;
  }

  Prototype.Selector.engine = engine;
  Prototype.Selector.select = select;
  Prototype.Selector.match = match;
})(Sizzle);

window.Sizzle = Prototype._original_property;
delete Prototype._original_property;

var Form = {
  reset: function(form) {
    form = $(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, accumulator, initial;

    if (options.hash) {
      initial = {};
      accumulator = function(result, key, value) {
        if (key in result) {
          if (!Object.isArray(result[key])) result[key] = [result[key]];
          result[key].push(value);
        } else result[key] = value;
        return result;
      };
    } else {
      initial = '';
      accumulator = function(result, key, value) {
        return result + (result ? '&' : '') + encodeURIComponent(key) + '=' + encodeURIComponent(value);
      }
    }

    return elements.inject(initial, function(result, element) {
      if (!element.disabled && element.name) {
        key = element.name; value = $(element).getValue();
        if (value != null && element.type != 'file' && (element.type != 'submit' || (!submitted &&
            submit !== false && (!submit || key == submit) && (submitted = true)))) {
          result = accumulator(result, key, value);
        }
      }
      return result;
    });
  }
};

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

  getElements: function(form) {
    var elements = $(form).getElementsByTagName('*'),
        element,
        arr = [ ],
        serializers = Form.Element.Serializers;
    for (var i = 0; element = elements[i]; i++) {
      arr.push(element);
    }
    return arr.inject([], function(elements, child) {
      if (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)$/i.test(element.tagName);
    });
  },

  focusFirstElement: function(form) {
    form = $(form);
    var element = form.findFirstElement();
    if (element) element.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)$/i.test(element.type))))
        element.select();
    } catch (e) { }
    return element;
  },

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

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

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

var Field = Form.Element;

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

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

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

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

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

  function select(element, value) {
    if (Object.isUndefined(value))
      return (element.type === 'select-one' ? selectOne : selectMany)(element);

    var opt, currentValue, single = !Object.isArray(value);
    for (var i = 0, length = element.length; i < length; i++) {
      opt = element.options[i];
      currentValue = this.optionValue(opt);
      if (single) {
        if (currentValue == value) {
          opt.selected = true;
          return;
        }
      }
      else opt.selected = value.include(currentValue);
    }
  }

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

  function selectMany(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(optionValue(opt));
    }
    return values;
  }

  function optionValue(opt) {
    return Element.hasAttribute(opt, 'value') ? opt.value : opt.text;
  }

  return {
    input:         input,
    inputSelector: inputSelector,
    textarea:      valueSelector,
    select:        select,
    selectOne:     selectOne,
    selectMany:    selectMany,
    optionValue:   optionValue,
    button:        valueSelector
  };
})();

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


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);
  }
});
(function() {

  var 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: {}
  };

  var docEl = document.documentElement;
  var MOUSEENTER_MOUSELEAVE_EVENTS_SUPPORTED = 'onmouseenter' in docEl
    && 'onmouseleave' in docEl;



  var isIELegacyEvent = function(event) { return false; };

  if (window.attachEvent) {
    if (window.addEventListener) {
      isIELegacyEvent = function(event) {
        return !(event instanceof window.Event);
      };
    } else {
      isIELegacyEvent = function(event) { return true; };
    }
  }

  var _isButton;

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

  var legacyButtonMap = { 0: 1, 1: 4, 2: 2 };
  function _isButtonForLegacyEvents(event, code) {
    return event.button === legacyButtonMap[code];
  }

  function _isButtonForWebKit(event, code) {
    switch (code) {
      case 0: return event.which == 1 && !event.metaKey;
      case 1: return event.which == 2 || (event.which == 1 && event.metaKey);
      case 2: return event.which == 3;
      default: return false;
    }
  }

  if (window.attachEvent) {
    if (!window.addEventListener) {
      _isButton = _isButtonForLegacyEvents;
    } else {
      _isButton = function(event, code) {
        return isIELegacyEvent(event) ? _isButtonForLegacyEvents(event, code) :
         _isButtonForDOMEvents(event, code);
      }
    }
  } else if (Prototype.Browser.WebKit) {
    _isButton = _isButtonForWebKit;
  } else {
    _isButton = _isButtonForDOMEvents;
  }

  function isLeftClick(event)   { return _isButton(event, 0) }

  function isMiddleClick(event) { return _isButton(event, 1) }

  function isRightClick(event)  { return _isButton(event, 2) }

  function element(event) {
    event = Event.extend(event);

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

    if (currentTarget && currentTarget.tagName) {
      if (type === 'load' || type === 'error' ||
        (type === 'click' && currentTarget.tagName.toLowerCase() === 'input'
          && currentTarget.type === 'radio'))
            node = currentTarget;
    }

    if (node.nodeType == Node.TEXT_NODE)
      node = node.parentNode;

    return Element.extend(node);
  }

  function findElement(event, expression) {
    var element = Event.element(event);

    if (!expression) return element;
    while (element) {
      if (Object.isElement(element) && Prototype.Selector.match(element, expression)) {
        return Element.extend(element);
      }
      element = element.parentNode;
    }
  }

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

  function pointerX(event) {
    var docElement = document.documentElement,
     body = document.body || { scrollLeft: 0 };

    return event.pageX || (event.clientX +
      (docElement.scrollLeft || body.scrollLeft) -
      (docElement.clientLeft || 0));
  }

  function pointerY(event) {
    var docElement = document.documentElement,
     body = document.body || { scrollTop: 0 };

    return  event.pageY || (event.clientY +
       (docElement.scrollTop || body.scrollTop) -
       (docElement.clientTop || 0));
  }


  function stop(event) {
    Event.extend(event);
    event.preventDefault();
    event.stopPropagation();

    event.stopped = true;
  }


  Event.Methods = {
    isLeftClick:   isLeftClick,
    isMiddleClick: isMiddleClick,
    isRightClick:  isRightClick,

    element:     element,
    findElement: findElement,

    pointer:  pointer,
    pointerX: pointerX,
    pointerY: pointerY,

    stop: stop
  };

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

  if (window.attachEvent) {
    function _relatedTarget(event) {
      var element;
      switch (event.type) {
        case 'mouseover':
        case 'mouseenter':
          element = event.fromElement;
          break;
        case 'mouseout':
        case 'mouseleave':
          element = event.toElement;
          break;
        default:
          return null;
      }
      return Element.extend(element);
    }

    var additionalMethods = {
      stopPropagation: function() { this.cancelBubble = true },
      preventDefault:  function() { this.returnValue = false },
      inspect: function() { return '[object Event]' }
    };

    Event.extend = function(event, element) {
      if (!event) return false;

      if (!isIELegacyEvent(event)) return event;

      if (event._extendedByPrototype) return event;
      event._extendedByPrototype = Prototype.emptyFunction;

      var pointer = Event.pointer(event);

      Object.extend(event, {
        target: event.srcElement || element,
        relatedTarget: _relatedTarget(event),
        pageX:  pointer.x,
        pageY:  pointer.y
      });

      Object.extend(event, methods);
      Object.extend(event, additionalMethods);

      return event;
    };
  } else {
    Event.extend = Prototype.K;
  }

  if (window.addEventListener) {
    Event.prototype = window.Event.prototype || document.createEvent('HTMLEvents').__proto__;
    Object.extend(Event.prototype, methods);
  }

  function _createResponder(element, eventName, handler) {
    var registry = Element.retrieve(element, 'prototype_event_registry');

    if (Object.isUndefined(registry)) {
      CACHE.push(element);
      registry = Element.retrieve(element, 'prototype_event_registry', $H());
    }

    var respondersForEvent = registry.get(eventName);
    if (Object.isUndefined(respondersForEvent)) {
      respondersForEvent = [];
      registry.set(eventName, respondersForEvent);
    }

    if (respondersForEvent.pluck('handler').include(handler)) return false;

    var responder;
    if (eventName.include(":")) {
      responder = function(event) {
        if (Object.isUndefined(event.eventName))
          return false;

        if (event.eventName !== eventName)
          return false;

        Event.extend(event, element);
        handler.call(element, event);
      };
    } else {
      if (!MOUSEENTER_MOUSELEAVE_EVENTS_SUPPORTED &&
       (eventName === "mouseenter" || eventName === "mouseleave")) {
        if (eventName === "mouseenter" || eventName === "mouseleave") {
          responder = function(event) {
            Event.extend(event, element);

            var parent = event.relatedTarget;
            while (parent && parent !== element) {
              try { parent = parent.parentNode; }
              catch(e) { parent = element; }
            }

            if (parent === element) return;

            handler.call(element, event);
          };
        }
      } else {
        responder = function(event) {
          Event.extend(event, element);
          handler.call(element, event);
        };
      }
    }

    responder.handler = handler;
    respondersForEvent.push(responder);
    return responder;
  }

  function _destroyCache() {
    for (var i = 0, length = CACHE.length; i < length; i++) {
      Event.stopObserving(CACHE[i]);
      CACHE[i] = null;
    }
  }

  var CACHE = [];

  if (Prototype.Browser.IE)
    window.attachEvent('onunload', _destroyCache);

  if (Prototype.Browser.WebKit)
    window.addEventListener('unload', Prototype.emptyFunction, false);


  var _getDOMEventName = Prototype.K,
      translations = { mouseenter: "mouseover", mouseleave: "mouseout" };

  if (!MOUSEENTER_MOUSELEAVE_EVENTS_SUPPORTED) {
    _getDOMEventName = function(eventName) {
      return (translations[eventName] || eventName);
    };
  }

  function observe(element, eventName, handler) {
    element = $(element);

    var responder = _createResponder(element, eventName, handler);

    if (!responder) return element;

    if (eventName.include(':')) {
      if (element.addEventListener)
        element.addEventListener("dataavailable", responder, false);
      else {
        element.attachEvent("ondataavailable", responder);
        element.attachEvent("onlosecapture", responder);
      }
    } else {
      var actualEventName = _getDOMEventName(eventName);

      if (element.addEventListener)
        element.addEventListener(actualEventName, responder, false);
      else
        element.attachEvent("on" + actualEventName, responder);
    }

    return element;
  }

  function stopObserving(element, eventName, handler) {
    element = $(element);

    var registry = Element.retrieve(element, 'prototype_event_registry');
    if (!registry) return element;

    if (!eventName) {
      registry.each( function(pair) {
        var eventName = pair.key;
        stopObserving(element, eventName);
      });
      return element;
    }

    var responders = registry.get(eventName);
    if (!responders) return element;

    if (!handler) {
      responders.each(function(r) {
        stopObserving(element, eventName, r.handler);
      });
      return element;
    }

    var i = responders.length, responder;
    while (i--) {
      if (responders[i].handler === handler) {
        responder = responders[i];
        break;
      }
    }
    if (!responder) return element;

    if (eventName.include(':')) {
      if (element.removeEventListener)
        element.removeEventListener("dataavailable", responder, false);
      else {
        element.detachEvent("ondataavailable", responder);
        element.detachEvent("onlosecapture", responder);
      }
    } else {
      var actualEventName = _getDOMEventName(eventName);
      if (element.removeEventListener)
        element.removeEventListener(actualEventName, responder, false);
      else
        element.detachEvent('on' + actualEventName, responder);
    }

    registry.set(eventName, responders.without(responder));

    return element;
  }

  function fire(element, eventName, memo, bubble) {
    element = $(element);

    if (Object.isUndefined(bubble))
      bubble = true;

    if (element == document && document.createEvent && !element.dispatchEvent)
      element = document.documentElement;

    var event;
    if (document.createEvent) {
      event = document.createEvent('HTMLEvents');
      event.initEvent('dataavailable', bubble, true);
    } else {
      event = document.createEventObject();
      event.eventType = bubble ? 'ondataavailable' : 'onlosecapture';
    }

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

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

    return Event.extend(event);
  }

  Event.Handler = Class.create({
    initialize: function(element, eventName, selector, callback) {
      this.element   = $(element);
      this.eventName = eventName;
      this.selector  = selector;
      this.callback  = callback;
      this.handler   = this.handleEvent.bind(this);
    },

    start: function() {
      Event.observe(this.element, this.eventName, this.handler);
      return this;
    },

    stop: function() {
      Event.stopObserving(this.element, this.eventName, this.handler);
      return this;
    },

    handleEvent: function(event) {
      var element = Event.findElement(event, this.selector);
      if (element) this.callback.call(this.element, event, element);
    }
  });

  function on(element, eventName, selector, callback) {
    element = $(element);
    if (Object.isFunction(selector) && Object.isUndefined(callback)) {
      callback = selector, selector = null;
    }

    return new Event.Handler(element, eventName, selector, callback).start();
  }

  Object.extend(Event, Event.Methods);

  Object.extend(Event, {
    fire:          fire,
    observe:       observe,
    stopObserving: stopObserving,
    on:            on
  });

  Element.addMethods({
    fire:          fire,

    observe:       observe,

    stopObserving: stopObserving,

    on:            on
  });

  Object.extend(document, {
    fire:          fire.methodize(),

    observe:       observe.methodize(),

    stopObserving: stopObserving.methodize(),

    on:            on.methodize(),

    loaded:        false
  });

  if (window.Event) Object.extend(window.Event, Event);
  else window.Event = Event;
})();

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

  var timer;

  function fireContentLoadedEvent() {
    if (document.loaded) return;
    if (timer) window.clearTimeout(timer);
    document.loaded = true;
    document.fire('dom:loaded');
  }

  function checkReadyState() {
    if (document.readyState === 'complete') {
      document.stopObserving('readystatechange', checkReadyState);
      fireContentLoadedEvent();
    }
  }

  function pollDoScroll() {
    try { document.documentElement.doScroll('left'); }
    catch(e) {
      timer = pollDoScroll.defer();
      return;
    }
    fireContentLoadedEvent();
  }

  if (document.addEventListener) {
    document.addEventListener('DOMContentLoaded', fireContentLoadedEvent, false);
  } else {
    document.observe('readystatechange', checkReadyState);
    if (window == top)
      timer = pollDoScroll.defer();
  }

  Event.observe(window, 'load', fireContentLoadedEvent);
})();

Element.addMethods();

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

var Position = {
  includeScrollOffsets: false,

  prepare: function() {
    this.deltaX =  window.pageXOffset
                || document.documentElement.scrollLeft
                || document.body.scrollLeft
                || 0;
    this.deltaY =  window.pageYOffset
                || document.documentElement.scrollTop
                || document.body.scrollTop
                || 0;
  },

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

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


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

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

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

    findElements: function(rootElement) {
      return Prototype.Selector.select(this.expression, rootElement);
    },

    match: function(element) {
      return Prototype.Selector.match(element, this.expression);
    },

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

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

  Object.extend(Selector, {
    matchElements: function(elements, expression) {
      var match = Prototype.Selector.match,
          results = [];

      for (var i = 0, length = elements.length; i < length; i++) {
        var element = elements[i];
        if (match(element, expression)) {
          results.push(Element.extend(element));
        }
      }
      return results;
    },

    findElement: function(elements, expression, index) {
      index = index || 0;
      var matchIndex = 0, element;
      for (var i = 0, length = elements.length; i < length; i++) {
        element = elements[i];
        if (Prototype.Selector.match(element, expression) && index === matchIndex++) {
          return Element.extend(element);
        }
      }
    },

    findChildElements: function(element, expressions) {
      var selector = expressions.toArray().join(', ');
      return Prototype.Selector.select(selector, element || document);
    }
  });
})();
/**
 * @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 2005 Clint Priest.
  *
  *  Unless required by applicable law or agreed to in writing, software distributed under the
  *  License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
  *  either express or implied. See the License for the specific language governing permissions
  *  and limitations under the License.
  **/

/***********************************************************************************************
*	Global Initialization
***********************************************************************************************/
navigator.msie = 0;
navigator.ff = 0;

if(navigator.userAgent.toLowerCase().indexOf('msie') != -1) {
	navigator.msie = 1;
} else if(navigator.userAgent.toLowerCase().indexOf('firefox') != -1) {
	navigator.ff = 1;
} else if(navigator.userAgent.toLowerCase().indexOf('safari') != -1) {
	navigator.safari = 1;
}

/* Catch any errors and send through the error reporting routines */
//window.onerror = function(Error) { pkAjax.ReportError(Error); return true; }

/**
*	Class used to dynamically extend the document based upon scan functions, this is abstracted
*		into its own class so that new document changes (from ajax requests) can also be extended
*		automatically.  Each function that is to scan an element tree should register itself here.
**/
pkExtend = {
	tExtenders : [ ],
	RegisterScanFunction: function(func, insertAt) {
		if(insertAt != undefined)
			this.tExtenders.splice(insertAt, 0, func);
		else
			this.tExtenders.push(func);
	},
	UnregisterScanFunction: function(func) { this.tExtenders = this.tExtenders.without(func); },
	Extend: function(htmElement) {
		if(htmElement.nodeType == 1) {
			for(var j=0;j<this.tExtenders.length;j++)
				this.tExtenders[j](htmElement);
		}
		return htmElement;
	}
}

/***********************************************************************************************
*	Pass in the class of the DIV Element to popup for upload progress and the URL to be used
*		for obtaining the HTML to show for progress, the UPLOAD_IDENTIFIER will be appened to URL
***********************************************************************************************/
function EnableUploadProgress(WindowClass, UpdateUrl) {
	Event.observe(window, 'load', function() {
		this._UpdateProgress = function(resultCode, hReturnData) {
			if(!this.htmlLayer) {
				this.htmlLayer = pkWindowMgr.CreateLayer();
				this.htmlUploadWnd = CreateElement('div', { _className: WindowClass }, { }, { appendChild: this.htmlLayer } );
				this.htmlLayer.htmlMask.SetVisibility('');
				this.htmlLayer.SetVisibility('visible');
			} else if(hReturnData.bytes_total == 0) {
				this.htmlLayer.remove();
				this.htmlLayer = undefined;
				return;
			}
			this.htmlUploadWnd.innerHTML = hReturnData.htmlProgress;
			this.SetEvent();
		}
		this.SetEvent = function() {
			setTimeout(pkAjax.Request.bind(pkAjax,UpdateUrl+this.UploadIdentifier, { onComplete: this._UpdateProgress.bind(this), bBackground: true} ), 750);
		}
		this._CheckSubmit = function(e) {
			var htmlForm = target.form;
			target = e.target ? e.target: e.srcElement;

			if( $A(htmlForm.getElementsByTagName('INPUT')).find( function(htmlInput) { if(htmlInput.type == "file" && htmlInput.value != "") return true; }) ) {
				this.UploadIdentifier = new Date().getTime();
				CreateElement('input', { _type: 'hidden', _name: 'UPLOAD_IDENTIFIER', _value: this.UploadIdentifier }, {}, { insertChild: htmlForm } );
				this.SetEvent();
			}
		}

		if(navigator.msie) {
			$A(document.getElementsByTagName('FORM')).each( function(htmlForm) {
				Event.observe(htmlForm, 'submit', this._CheckSubmit.bindAsEventListener(this) );
			}.bind(this) );
		} else {
			Event.observe(window, 'submit', this._CheckSubmit.bindAsEventListener(this) );
		}
	} );
}

/***********************************************************************************************
*	Mouse support routines
***********************************************************************************************/
pkMouse = {
	initialize: function() {
		Event.observe(window, 'mousemove', pkMouse._MouseMove.bindAsEventListener(pkMouse));
		if(window.addEventListener)
			window.addEventListener('click', pkMouse._Click.bindAsEventListener(pkMouse), true);
		else
			Event.observe(window, 'click', pkMouse._Click.bindAsEventListener(pkMouse));
	},
	_MouseMove: function(e) {
		this.CursorPos = { x: e.clientX, y: e.clientY };
		this.LastCursorTarget = Event.element(e);
	},
	_Click: function(e) {
		this.LastClickTarget = Event.element(e);
	},
	_GlobalCursorMove: function(e) {

		target = e.originalTarget ? e.originalTarget : e.srcElement;
		if(target.getStyle != undefined && target.getStyle('cursor') != this.GlobalCursor) {
			this.CursorStack[this.CursorStack.length] = {target:target, cursor:target.style.cursor};
			target.style.cursor = this.GlobalCursor;
		}
	},
	SetGlobalCursor: function(cursor) {
		this.ResetGlobalCursor();	// If its already going

		this.GlobalCursor = cursor;
		this.CursorStack = new Array;
		this._GlobalCursorMoveHandler = this._GlobalCursorMove.bindAsEventListener(this);

		if(this.LastCursorTarget != undefined) {
			this.CursorStack[this.CursorStack.length] = { target: this.LastCursorTarget, cursor: this.LastCursorTarget.style.cursor}
			this.LastCursorTarget.style.cursor = this.GlobalCursor;
		}

		Event.observe(document.body, 'mouseover', this._GlobalCursorMoveHandler);
	},
	ResetGlobalCursor: function() {
		if(this._GlobalCursorMoveHandler)
			Event.stopObserving(document.body, 'mouseover', this._GlobalCursorMoveHandler);
		if(this.CursorStack) {
			for(var j=0;j<this.CursorStack.length;j++)
				this.CursorStack[j].target.style.cursor = this.CursorStack[j].cursor;
		}
	}
}
Event.observe(window, 'load', pkMouse.initialize);

pkWindowMgr = {
	initialize: function() {
		this.myLayerDepth = 0;
		this.TopLayer = document.body;
	},
	CreateLayer: function() {
		var htmlLayer = CreateElement('div', { }, {
			visibility: 'hidden', top: '0px', left: '0px', width: '100%', height: '100%',
			position: 'fixed', zIndex: this.myLayerDepth+1000}, { appendChild: document.body } );

		htmlLayer.htmlMask = CreateElement('div', { }, {
			visibility: 'visible', display: 'none', top: '0px', left: '0px', width: '100%', height: '100%',
			position: 'fixed', zIndex: 0, backgroundColor: 'black', opacity: .3, filter: 'Alpha(Opacity=30)'}, { appendChild: htmlLayer } );

		this.myLayerDepth = Number(htmlLayer.style.zIndex);
		this.TopLayer = htmlLayer;
		return htmlLayer;
	},
	DestroyLayer: function(Layer) {
		try {Element.remove(Layer);} catch(e) { console.log('failed to destroy layer'); }
		delete Layer;
	},
	FindLayer: function(Element) {
		while(Element) {
			if(Element.htmlLayer)
				return Element.htmlLayer;
			if(Element == document.body)
				return document.body;
			Element = Element.parentNode;
		}
		return document.body;
	}
}

pkWindowMgr.Window = Class.create();
pkWindowMgr.Window.prototype = {
	initialize: function(Content, Width, Options) {
		pkWindowMgr.LastLaunchTarget = pkMouse.LastClickTarget;

		this.Options = Object.extend( { vAlign: .15, hAlign: .5, WindowClass: 'PopupForm', bMask: true }, Options);
		if(!this.Options.htmlLayer) {
			this.htmlLayer = pkWindowMgr.CreateLayer();
		} else {
			this.htmlLayer = this.Options.htmlLayer;
		}
		this.htmlWindow = $(document.createElement('div'));
		this.htmlWindow.htmlLayer = this.htmlLayer;
		this.htmlWindow.className = this.Options.WindowClass;

		this.htmlWindow.setStyle( { visibility: 'hidden', position: 'absolute', width: (Width != 'auto' ? Width+'px' : Width)} );
		this.htmlLayer.appendChild(this.htmlWindow);

		// Execute directly so (this) is this window for the contents scripts
		this.htmlWindow.innerHTML = '<div>'+Content.stripScripts()+'</div>';

		Content.extractScripts().each( function(script) {
			try {
				eval(script);
			} catch(e) {
				LogException('Error while processing script:\r\nScript:\r\n'+script+'---------------------------\r\n', e);
			}
		}.bind(this));

		pkExtend.Extend(this.htmlWindow);

		if(this.Options.bMask)
			this.htmlLayer.htmlMask.style.display = 'block';

		this.htmlWindow.setStyle( { top: Math.abs((this.htmlLayer.offsetHeight * this.Options.vAlign) - (this.htmlWindow.offsetHeight * this.Options.vAlign)) + 'px',
									left: (this.htmlLayer.getWidth() * this.Options.hAlign) - (this.htmlWindow.getWidth() / 2)+'px' } );
		this.htmlWindow.style.visibility = 'visible';

		try {
			var htmlHeader = this.htmlWindow.getElementsByTagName('h1')[0];
			htmlHeader.style.cursor = 'move';
			this._MouseMoveHandler = this._MouseMove.bindAsEventListener(this);
			this._MouseUpHandler = this._MouseUp.bindAsEventListener(this);

			Event.observe(htmlHeader, 'mousedown', this._TitleMouseDown.bind(this));
		} catch(e) { }
	},
	ShowWindow: function(bShow) {
		if(this.Options.htmlLayer)
			this.htmlWindow.style.visibility = bShow ? 'visible' : 'hidden';
		else
			this.htmlLayer.style.visibility = bShow ? 'visible' : 'hidden';
	},
	Destroy: function() {
		pkWindowMgr.DestroyLayer(this.htmlLayer);
	},
	_TitleMouseDown: function(e) {
		Event.stop(e);
		Event.observe(document.body, "mousemove", this._MouseMoveHandler);
		Event.observe(document.body, "mouseup", this._MouseUpHandler);
		this._MoveOffset = this.htmlWindow.cumulativeOffset();
		this._MoveOffset.left -= Event.pointerX(e);
		this._MoveOffset.top -= Event.pointerY(e);
	},
	_MouseUp: function(e) {
		Event.stop(e);
		Event.stopObserving(document.body, "mousemove", this._MouseMoveHandler);
		Event.stopObserving(document.body, "mouseup", this._MouseUpHandler);
	},
	_MouseMove: function(e) {
		Event.stop(e);
		this.htmlWindow.setStyle( { left: Event.pointerX(e) + this._MoveOffset.left+'px', top: Event.pointerY(e) + this._MoveOffset.top+'px' });
	}
}

pkIE = {
	ieFrameFix: function(MainElement) {
		if(navigator.msie) {
			MainElement.ieFrameFix = document.createElement('iframe');
			MainElement.ieFrameFix.style.position = MainElement.getStyle('position');
			MainElement.ieFrameFix.style.zIndex = MainElement.getStyle('zIndex') - 1;
			MainElement.ieFrameFix.style.top = MainElement.getStyle('top');
			MainElement.ieFrameFix.style.left = MainElement.getStyle('left');
			MainElement.ieFrameFix.style.width = MainElement.offsetWidth + 'px';
			MainElement.ieFrameFix.style.height = MainElement.offsetHeight + 'px';
			MainElement.ieFrameFix.Fixing = MainElement;

			MainElement.ieFrameFix.CheckSize = function(MainElement) {
				if(MainElement && MainElement.ieFrameFix) {
					MainElement.ieFrameFix.style.top = MainElement.getStyle('top');
					MainElement.ieFrameFix.style.left = MainElement.getStyle('left');
					MainElement.ieFrameFix.style.width = MainElement.offsetWidth + 'px';
					MainElement.ieFrameFix.style.height = MainElement.offsetHeight + 'px';
				}
			};
			Event.observe(MainElement, 'resize', MainElement.ieFrameFix.CheckSize.bind(MainElement));

			MainElement.parentNode.insertBefore(MainElement.ieFrameFix, MainElement);
		}
	}
}

Event.observe(window, 'load', pkWindowMgr.initialize.bind(pkWindowMgr));

/***********************************************************************************************
*	Utility
***********************************************************************************************/
function $Z(arg) {
	if(typeof arg == 'string')
		return document.getElementById(arg);
	return arg;
}

function $ATTR(node, set) {
	if(set == undefined)
		set = { };
	var tTemp = [ ] ;
	for(var j=0;j<node.attributes.length;j++)
		set[node.attributes[j].nodeName] = node.attributes[j].nodeValue;
	return set;
}

function CreateElement(Type, Params, Style, Operation) {
	var htmlElement = $(document.createElement(Type));
	$H(Params).each( function(pair) {
		if(pair.key.substr(0,1)=='_')
			eval('htmlElement.'+pair.key.substr(1,9999)+'= pair.value;');
		else
			htmlElement.setAttribute(pair.key, pair.value);
	});
	htmlElement.setStyle(Style);
	if(Operation && Operation.appendChild)
		Operation.appendChild.appendChild(htmlElement);
	else if (Operation && Operation.insertBefore)
		Operation.insertBefore.parentNode.insertBefore(htmlElement, Operation.insertBefore);
	else if (Operation && Operation.insertChild)
		Operation.insertChild.insertBefore(htmlElement, Operation.insertChild.firstChild);
	else if (Operation && Operation.insertAfter) {
		if(Operation.insertAfter.nextSibling)
			Operation.insertAfter.parentNode.insertBefore(htmlElement, Operation.insertAfter.nextSibling);
		else
			Operation.insertAfter.parentNode.appendChild(htmlElement);
	}
	return htmlElement;
}

function setCookie(name, value, expires, path, domain, secure) {
	if(!expires) {
		expires = new Date();
		expires.setDate(new Date().getDate() + 365);
	}
	document.cookie = name + "=" + escape(value) +
		((expires) ? "; expires=" + expires.toGMTString() : "" ) +
		((path) ? "; path=" + path : "; path=/") +
		((domain) ? "; domain=" + domain : "") +
		((secure) ? "; secure" : "");
}

function setPageCookie(name, value, expires) {
	var tParts = window.location.href.match(/(http|https):\/\/([^\/\?]+)([^\?]+)(.+)/)
	setCookie(name, value, expires, tParts[3], tParts[2]);
}

function getCookie(name) {
	var dc = document.cookie;
	var prefix = name + "=";
	var begin = dc.indexOf("; " + prefix);
	if (begin == -1) {
		begin = dc.indexOf(prefix);
		if (begin != 0) return null;
	} else {
		begin += 2;
	}
	var end = document.cookie.indexOf(";", begin);
	if (end == -1) {
		end = dc.length;
	}
	return unescape(dc.substring(begin + prefix.length, end));
}

function HTMLtoDOM(html) {
	var range=document.createRange();
	range.selectNode(document.body);
	return range.createContextualFragment(html).firstChild;
}

/***********************************************************************************************
*	mozError() - Outputs a message to the error console in FF
***********************************************************************************************/
function mozError() {
	try { console.log($A(arguments).join(' | ')); } catch(e) { }
	ShowErrorWindow(false /* Don't toggle, just create */);

	var htmlError = $(document.createElement('div'));
	htmlError.setStyle( {
		font:			'normal 12px Verdana',
		color:			'white',
		padding:		'10px 10px',
		borderBottom:	'1px solid white',
		margin:			'0px'
	} );
	htmlError.innerHTML = $A(arguments).join(' | ');
	window.htmlErrorWnd.appendChild(htmlError);
}

function mozHUD() {
	var hudName = $A(arguments).shift();
	if(!window.htmlHUD)
		window.htmlHUD = { slotsTaken: 0 };
	if(!window.htmlHUD[hudName]) {
		window.htmlHUD[hudName] = CreateElement('div', { }, {
			position: 'absolute', top: '0px', left: (window.htmlHUD.slotsTaken * 150)+'px', width: document.body.offsetWidth, zIndex: '999', backgroundColor: 'black', width: '150px',
			filter:		'alpha(Opacity=80)',	opacity:	'.8', color: 'white'
		}, { appendChild: document.body } );
		window.htmlHUD.slotsTaken++;
	}
	var htmlContent = '';
	$A(arguments).each( function(arg) {
		var content = '';
		switch(typeof arg) {
			case 'object':
				for(Name in arg)
					content += Name+': '+arg[Name]+'<BR>';
				break;
			case 'array':
				content = $A(arg).join(', ')+'<BR>';
				break;
			default:
				content = arg+'<BR>';
				break;
		}
		htmlContent += '<div style="float: left;">'+content+'</div>';
	} );
	window.htmlHUD[hudName].innerHTML = htmlContent + '<div style="clear: both;"></div>';
}

Event.observe(document, 'keydown', function(e) {
	if(e.keyCode  == 88 && e.altKey) {
		ShowErrorWindow(true);
		Event.stop(e);
	}
});

/* Monitor keyup events, if ever a keyup event occurs on an input[text,file], select, or textarea
	Fire an onchange event after a delay (500ms) */

Event.observe(window, 'keyup', function(e) {
	var htmElement = Event.element(e);
	var bFire = false;

	switch(htmElement.tagName) {
		case 'SELECT':
		case 'TEXTAREA':
			bFire = true;
			break;
		case 'INPUT':
			switch(htmElement.getAttribute('type')) {
				case 'file':
				case 'text':
					bFire = true;
					break;
			}
			break;
	}
	if(bFire) {
		clearTimeout(htmElement.ChangeEventTimerID);
		htmElement.ChangeEventTimerID = htmElement.fire.bind(htmElement, 'change').delay(.25);
	}
});

function ShowErrorWindow(bToggle) {
	if(!window.htmlErrorWnd) {
		var htmlDiv = $(document.createElement('div'));
		htmlDiv.setStyle( {
			backgroundColor:	'black',
			opacity:			'.8',
			filter:				'Alpha(Opacity=80)',
			display:			'none',
			position:			'absolute',
			bottom:				'0px',
			width:				'100%',
			height:				'450px',
			margin:				'0px',
			overflowY:			'scroll',
			overflowX:			'hidden'
		} );
		window.htmlErrorWnd = htmlDiv;
		Event.observe(document, 'load', function() {
			document.body.appendChild(window.htmlErrorWnd);
		}.bind(this));
	}

	if(bToggle)
		window.htmlErrorWnd.style.display = (window.htmlErrorWnd.style.display == 'none' ? '' : 'none');
}

pkExtend.RegisterScanFunction( function(eElement) {
	var tInputs = eElement.getElementsByTagName('input');
	$A(tInputs).each( function(eInput) {
		eInput = $(eInput);
		if(!eInput.autoControl) {
			switch(String(eInput.getAttribute('controltype')).toLowerCase()) {
				case 'datepicker':		eInput.autoControl = new pkDateSelect(eInput);	break;
				case 'editlist':		eInput.autoControl = new pkEditList(eInput); 	break;
			}
		}
	} );
	$A($(eElement).select('.CheckboxPanel')).each( function(htmContainer) {
		if(!htmContainer.autoControl)
			htmContainer.autoControl = new pkCheckboxPanel(htmContainer);
	});
	$A($(eElement).getElementsByTagName('textarea')).each( function(htmElement) {
		if(!htmElement.autoControl)
			htmElement.autoControl = new pkTextarea(htmElement);
	} );
	$A($(eElement).select('INPUT[type=text]')).each( function(Element) {
		if(!Element.getAttribute('JSClass'))
			Element.setAttribute('JSClass', 'pkTextInput');
	} );
	var JSClassElements = $(eElement).select('*[JSClass]') || [];
	if(eElement.getAttribute('JSClass') != undefined)
		JSClassElements.push(eElement);

	$A(JSClassElements).map( function(element) {
		var Class = element.getAttribute('jsclass');
		return { Class: Class, LoadPriority: eval(Class+'.prototype.LoadPriority') || 0, element: element };
	} ).sortBy( function(item) {
		return item.LoadPriority;
	}).each( function(item) {

		try { item.element.BecomeClass(eval(item.Class));	}
			catch(e) {
				if(console && console.error) {
					console.groupCollapsed('Unable to cause %o to become class %s: %o', item.element, item.Class, e);
					console.trace();
					console.groupEnd();
				}
				pkAjax.ReportError('Unable to cause element to become class '+item.Class+' for element: '+(Prototype.Browser.IE ? e.description : e.toString()), false);
			}
	} );
} );

/***********************************************************************************************
*	$S() - Selector function, optional changes, locates an element or set of elements from
*		a base location via a path string, optionally sets attributes/styles of selected
*	Format:
*		[atom]/[atom]/.../[atom]
*
*		[atom] 		= TAGNAME<attribs>[ranges]
*		TAGNAME		= Any valid tag name (TD, TR, etc)
*		<attribs>	= attrib,attrib,...,attrib
*		attrib		= Any name=value pair, the first character determines what type of attribute to match against:
*						$ - Attribute of element (via getAttribute)
*						_ - Attribute of object, such as (objXYZ.attrib)
*						default: Checks against the style of the object
*		[ranges]	= [range,range,...,range]
*		range		= Selects certain elements of the above type, if no parameter is specified
*						  then it selects all of that type
*							  Single number		Selects a single element
*							  Range (1-3) 		Selects a range of elements
*							  CSV (1,3,4)		Selects a set of elements
*
*	Examples:
*		$S(eTable,'tbody/tr[2]/td[2-5]', { _property: value, $attribute: value, style: value}');
*			Sets the given properties on 4 TD cell's, returns array of TD's
*		$S(eTable,'tbody/tr[0]/td[1,3]/img', { ... });
*			Sets the given properties on IMG elements that are children of td cells 1 & 3
***********************************************************************************************/

function $S(tBase, Selector, tProps) {
	if(!tBase.length)
		tBase = [ $(tBase) ];
	if(typeof Selector == 'string')
		Selector = Selector.split('/');

	var tParts = Selector.shift().match(/([\w*]+)(?:\<([^\>]+)\>){0,1}(?:\[([^\]]+)\]){0,1}/);
	tParts[1] = tParts[1].toUpperCase();
	if(tParts[2])
		var tAttribs = tParts[2].split(',');
	if(tParts[3])
		var tRanges = tParts[3].split(',');
	var tSelected = [ ];
	for(var k=0;k<tBase.length;k++) {
		var eBase = tBase[k];
		var tElems = [ ];
		if(tParts[1]) {
			if(eBase.childNodes) {
				for(var j=0;j<eBase.childNodes.length;j++) {
					if(eBase.childNodes[j].nodeType == 1 && (tParts[1] == '*' || eBase.childNodes[j].tagName == tParts[1]))
						tElems.push(eBase.childNodes[j]);
				}
			}
		}
		if(tRanges) {
			tRangeElems = tElems;	tElems = [ ];
			for(var j=0;j<tRanges.length;j++) {
				var tRange = tRanges[j].split('-');
				if(tRange.length == 1) {
					if(tRangeElems.length > tRange[0])
						tElems.push(tRangeElems[tRange[0]]);
				} else {
					for(var m=tRange[0]; m<=tRange[1];m++)
						if(tRangeElems.length > m)
							tElems.push(tRangeElems[m]);
				}
			}
		}
		if(tAttribs) {
			var tAttribElems = tElems; tElems = [ ];
			for(var i=0;i<tAttribElems.length;i++) {
				var bFailed = false;
				for(var j=0;j<tAttribs.length;j++) {
					var tAtoms = tAttribs[j].split('=');
					switch(tAtoms[0].substr(0,1)) {
						case '_':
							tAtoms[0] = tAtoms[0].substr(1,9999);
							if((tAtoms[1] && tAttribElems[i][tAtoms[0]] != tAtoms[1]) || (!tAtoms[1] && tAttribElems[i][tAtoms[0]] == undefined))
								bFailed = true;
							break;
						case '$':
							tAtoms[0] = tAtoms[0].substr(1,9999).toLowerCase();
							if((tAtoms[1] && tAttribElems[i].getAttribute(tAtoms[0]) != tAtoms[1]) || (!tAtoms[1] && tAttribElems[i].getAttribute(tAtoms[0]) == undefined))
								bFailed = true;
							break;
						default:
							if((tAtoms[1] && tAttribElems[i].style[tAtoms[0]] != tAtoms[1]) || (!tAtoms[1] && tAttribElems[i].style[tAtoms[0]] == undefined))
								bFailed = true;
							break;
					}
					if(bFailed)
						break;
				}
				if(!bFailed)
					tElems.push(tAttribElems[i]);
			}
		}
		tSelected = tSelected.concat(tElems);
	}
	if(Selector.length)
		return $S(tSelected, Selector, tProps);
	if(tProps) {
		for(var j=0;j<tSelected.length;j++) {
			if(tProps.length) {
				if(j < tProps.length)
					var Props = tProps[j];
				else
					var Props = tProps[0];
			} else
				var Props = tProps;
			for(var Name in Props) {
				if(Name == '__function')
					Props.__function(tSelected[j]);
				else {
					switch(Name.substr(0,1)) {
						case '_':	tSelected[j][Name.substr(1,9999)] = Props[Name];	break;
						case '$':	tSelected[j].setAttribute(Name.substr(1,9999), Props[Name]);	break;
						default:	tSelected[j].style[Name] = Props[Name];	break;
					}
				}
			}
		}
	}
	return tSelected;
}

Event.observe(window, 'load', function() { window.bLoaded = true; } );
function Initialize(func) {
	if(window.bLoaded)
		func();
	else
		Event.observe(window, 'load', func);
}
if(Prototype.Browser.IE)
	Event.observe(window, 'load', function() { pkExtend.Extend(document.body) });
else
	Event.observe(window, 'DOMContentLoaded', function() { pkExtend.Extend(document.body); });

function PointInRect(hPoint, hRect) {
	var pointX = hPoint.x,
		pointY = hPoint.y;
	var rectX1 = hRect.x || hRect.left,
		rectY1 = hRect.y || hRect.top,
		rectX2 = hRect.x2 || hRect.right,
		rectY2 = hRect.y2 || hRect.bottom;
	if(!rectX2 && hRect.width)
		rectX2 = rectX1 + hRect.width;
	if(!rectY2 && hRect.width)
		rectY2 = rectY1 + hRect.height;
	if( rectX1 < pointX && pointX < rectX2 && rectY1 < pointY && pointY < rectY2)
		return true;
	return false;
}

Logger = {
	Log: function() {
		this.CreateWindow();
		Msg = document.createElement('div');
		Msg.innerHTML = $A(arguments).join(', ');
		this.htmlWindow.appendChild(Msg);
		this.htmlWindow.scrollTop = 999999999;
	},
	CreateWindow: function() {
		if(!this.htmlWindow) {
			this.htmlWindow = document.createElement('div');
			this.htmlWindow.id = 'DebugLog';
			document.body.appendChild(this.htmlWindow);
		}
	}
}

pkPatterns = {
	/**
	* @desc Execute() - Executes an array of patterns against some data
	* @param string $Data - The data to work with
	* @param array $tPatterns - The patterns to work with, as follows:
	* 	{command}/{pattern}/{param}/{flags}
	* 		{command} - The command for this pattern, as follows:
	*
	* 			(s)ubstitute	- Substitutes instances of {pattern} with {param}
	* 				{flags}	  g - substitute all occurrances (default)
	*
	* 			(m)atch			- Data must match {pattern}, if not {param} is returned
	* 				{flags}	  d - Don't run the pattern if there is no data present.
	* 						  n - Negates the result, if pattern matches it will return {param}
	*
	* 		{pattern} 	- The perl compatible regular expression to work with
	* 		{param}		- The parameter to the command
	* @return mixed - Returns false upon everything working out fine, returns a string {param}
	* 	if any (m)atch parameters failed.  Note: If any pattern cannot be parse due to validity
	* 	the pattern will be skipped.
	*/
	Execute: function(Data, tPatterns) {
		for(var j=0;j<tPatterns.length;j++) {
			var tPattern = this.ParsePattern(tPatterns[j]);
			if(typeof tPattern == 'object') {
				switch(tPattern.Command) {
					case 's':
						Data = Data.replace(new RegExp(tPattern.Pattern, 'gi'), tPattern.Param);
						break;
					case 'm':
						if(Data || tPattern.Flags.indexOf('d') == -1) {
							if(Data.match(new RegExp(tPattern.Pattern, 'gi')) != null) {
								/* We've matched, normally we don't return anything, but if the (n)egate flag is present, return the {param} */
								if(tPattern.Flags.indexOf('n') != -1)
									return { Error: tPattern.Param, Data: Data };
							} else {
								/* We've failed the match, normally we return {param} unless the (n)egate flag is present */
								if(tPattern.Flags.indexOf('n') == -1)
									return { Error: tPattern.Param, Data: Data };
							}
						}
						break;
				}
			} else {
				/* Error in Pattern, ignore it */
			}
		}
		return { Data: Data };
	},
	/**
	* @desc ValidatePattern() - Checks the pattern for validity
	* @param string pkPattern - The pkPattern to check for validity
	* @return boolean Indication of validity
	*/
	ValidatePattern: function(pkPattern) { return this.ParsePattern(pkPattern, Command, Pattern, Param, Flags);	},
	/**
	* @desc ParsePattern() - Parses the pattern
	* @param string pkPattern - The pkPattern to parse into its parts
	* @return boolean Indication of success/failure
	*/
	ParsePattern: function(pkPattern) {
		pkPattern = pkPattern.replace('\\/', '##BACK##');
		var tParts = pkPattern.split('/');
		var Command = tParts[0].toLowerCase(), Pattern=tParts[1].replace('##BACK##','/'), Param=tParts[2], Flags=tParts[3];

		Flags = (Flags || '').toLowerCase();
		switch(Command) {
			case 's':
				if(Flags.match('/[^g0-9]+/'))
					return false;
				break;
			case 'm':
				if(Flags.match('/[^dn]+/'))
					return false;
				break;
			default:
				return false;
		}
		Pattern = Pattern;

		return { Command: Command, Pattern: Pattern, Param: Param, Flags: Flags };
	}
}

/* This snippet of code is here in case any console.log() statements are left in by accident, it will cause those messages to be ignored */
try {
	console.set = undefined;
} catch(e) {
	var console = {
		log: function() { /* Ignored */ }
	}
}

function rawurlencode(Url) {
	Url = escape(Url);
	return Url.replace(/(\+|\*|@|_|\/|\.)/g, function(str) {
		var hConvert = { '+':'2B', '*':'2A', '@':'40', '_':'5F', '/':'2F', '.':'2E' }
		return '%'+hConvert[str];
	} );
}

/*
 * Natural Sort algorithm for Javascript
 *  Version 0.3
 * Author: Jim Palmer (based on chunking idea from Dave Koelle)
 *  optimizations and safari fix by Mike Grier (mgrier.com)
 * Released under MIT license.
 */
function NaturalSort(a, b){
	// setup temp-scope variables for comparison evauluation
	var re = /(-?[0-9\.]+)/g,
		x = a.toString().toLowerCase() || '',
		y = b.toString().toLowerCase() || '',
		nC = String.fromCharCode(0),
		xN = x.replace( re, nC + '$1' + nC ).split(nC),
		yN = y.replace( re, nC + '$1' + nC ).split(nC),
		xD = (new Date(x)).getTime(),
		yD = xD ? (new Date(y)).getTime() : null;
	// natural sorting of dates
	if ( yD )
		if ( xD < yD ) return -1;
		else if ( xD > yD )	return 1;
	// natural sorting through split numeric strings and default strings
	for( var cLoc = 0, numS = Math.max(xN.length, yN.length); cLoc < numS; cLoc++ ) {
		oFxNcL = parseFloat(xN[cLoc]) || xN[cLoc] || ' ';
		oFyNcL = parseFloat(yN[cLoc]) || yN[cLoc] || ' ';
		if (oFxNcL < oFyNcL) return -1;
		else if (oFxNcL > oFyNcL) return 1;
	}
	return 0;
}

/*
 * Object that loads from and saves to localStorage
 */
var PersistorObject = Class.create( {
	initialize: function(key) {
		this._key = key;
		this.load(key);
	},

	save: function(key) {
		DataToSave = '';
		if(key && this._key != key) {
			this._key = key;
			this._original = false;
		}

		if(this._original) {
			ChangedValues = $H();

			$H(this).each(function(Item) {
				if(Item.key != '_original' && Object.toJSON(Item.value) != Object.toJSON(this._original.get(Item.key)))
					ChangedValues.set(Item.key, Item.value);
			}, this);

			//Pull an up to date copy of the current state of the object
			Temp = localStorage[this._key];
			if(Temp)
				Temp = JSON.parse(Temp);
			else
				Temp = {}

			//Update the temp state of the object with the values that have changed
			ChangedValues.each(function(Item) {
				if(!Object.isFunction(Item.value) && Item.key != '__proto__')
					Temp[Item.key] = Item.value;
			});

			DataToSave = Temp;
		} else {
			DataToSave = this;
		}

		this._original = $H(DataToSave);

		localStorage[this._key] = JSON.stringify(DataToSave);
	},

	load: function(key) {
		Temp = localStorage[key];

		if(Temp && Temp != 'undefined') {
			Temp = localStorage[key];

			//Avoiding reference "copy"
			this._original = $H(JSON.parse(Temp));
			Temp = $H(JSON.parse(Temp));

			Temp.each(function(Item) {
				if(Item.key != '_original')
					this[Item.key] = Item.value;
			}, this);
		}
	},

	remove: function() {
		delete localStorage[this._key];
	},

	IsDataLoaded: function() {
		return this._original ? true : false;
	}
});
/**
  *
  *  Copyright 2005 Clint Priest
  *
  *  Unless required by applicable law or agreed to in writing, software distributed under the
  *  License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
  *  either express or implied. See the License for the specific language governing permissions
  *  and limitations under the License.
  *
  *		pkExtensions.js - Extensions to elements
  *
  **/

pkElement_Methods = {
	SetPosition: function(element, position, top, left, height, width, zIndex) {
		var hStyle = { };
		if(position)	element.style.position = position;
		if(top)			element.style.top = top;
		if(left)		element.style.left = left;
		if(height)		element.style.height = height;
		if(width)		element.style.width = width;
		if(zIndex)		element.style.zIndex = zIndex;
	},

	SetVisibility: function(element) {
		var tArgs = $A(arguments); tArgs.shift();
		tArgs.each(
			function(arg) {
				switch(arg){
					case 'visible':
					case 'hidden':
						element.style.visibility = arg;
						if(element.ieFrameFix)
							element.ieFrameFix.style.visibility = arg;
						break;
					case true:
						element.style.display = '';
						if(element.ieFrameFix)
							element.ieFrameFix.style.display = '';
						break;
					case false:
						element.style.display = 'none';
						if(element.ieFrameFix)
							element.ieFrameFix.style.display = 'none';
						break;
					default:
						element.style.display = arg;
						if(element.ieFrameFix)
							element.ieFrameFix.style.display = arg;
						break;
				}
			}.bind(element)
		);
		if(element.ieFrameFix)
			element.ieFrameFix.CheckSize(element);
		return element;
	},

	SetValue: function(element, value) {
		if(element.tagName == 'SELECT' || element.tagName == 'INPUT') {
			element.value = value;
			element.fire('change');
		}
	},

	SetOpacity: function(element, opacity) {
		element = $Z(element);
		if(element) {
			if(opacity == undefined) {
				element.style.filter = '';
				element.style.opacity = '';
			} else {
				element.style.filter = 'alpha(Opacity='+Math.round(opacity*100)+')';
				element.style.opacity = opacity;
			}
		}
		return element;
	},
	clearChildren: function(element) {
		element = $Z(element);
		if(element)
			while(element.firstChild)
				element.removeChild(element.firstChild);
		},

		FindParentByTagName: function(element, TagName) {
		TagName = TagName.toUpperCase();
		while(element) {
			if(element.tagName == TagName)
				return element;
			element = element.parentNode;
		}
	},

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

	FindNextSibling: function(element, TagName) {
		TagName = TagName.toUpperCase();
		while(element.nextSibling) {
			if(element.nextSibling.tagName == TagName)
				return element.nextSibling;
			element = element.nextSibling;
		}
	},

	FilterElements: function(element, csvFilterTypes) {
		var tTypes = csvFilterTypes.split(',');
		for(var j=0;j<element.childNodes.length;j++) {
			if(element.childNodes[j].getAttribute) {
				var ElementFilterType = element.childNodes[j].getAttribute('FilterType');
				var bShow = false;
				for(var k=0;k<tTypes.length;k++) {
					if(ElementFilterType == tTypes[k]) {
						bShow = true;
						break;
					}
				}
				element.childNodes[j].style.display = (bShow ? '' : 'none');
			}
		}
	},

	AppendElements: function() {
		var tArgs = $A(arguments);
		var oElem = tArgs.shift();
		var TagName = tArgs.shift();
		tArgs.each(	function(hArg) {
			if(typeof hArg == 'string')
				hArg = { content: hArg };
			var oNew = document.createElement(TagName);
			for(property in hArg) {
				switch(property) {
					case 'content':
						oNew.innerHTML = hArg[property];
						break;
					default:
						oNew.setAttribute(property, hArg[property]);
						break;
				}
			}
			this.appendChild(oNew);
		}.bind(oElem) );
	},

	eachChildElement: function(element, tagName, userFunc) {
		tagName = tagName.toLowerCase();
		for(var j=0;j<element.childNodes.length;j++)
			if(element.childNodes[j].nodeType == 1 && element.childNodes[j].tagName.toLowerCase() == tagName)
				userFunc(element.childNodes[j]);
	},

	childElement: function(element, nIndex) {
		var nElementCount = -1;
		for(var j=0;j<element.childNodes.length;j++) {
			if(element.childNodes[j].nodeType == 1)
				if(++nElementCount == nIndex)
					return element.childNodes[j];
		}
	},
	/* Covers an element with a higher z-order white div (to dim the element) */
	WhiteOut: function(element, Overlay) {
		if(Overlay || Overlay == undefined) {
			var InsertLocation = element;
			if(element.tagName == 'TR')
				InsertLocation = element.down('TD');

			var htmOverlay = InsertLocation.insert( { top: '<div style="position: relative; width: 0px; height: 0px;"><div style="position: absolute; background-color: white; opacity: .7; z-index: 900;">&nbsp;</div></div>' } ).down();
			htmOverlay.down().clonePosition(element);
			element.WhiteOutElement = htmOverlay;
		} else if(element.WhiteOutElement) {
			element.WhiteOutElement.remove();
			delete element.WhiteOutElement;
		}
	},
	/** .WhiteOut()'s the element followed by FadeRemove()'s the elements after delay || 5000 */
	DestroyedEffect: function(element, delay, onComplete) {
		element.WhiteOut();
		setTimeout(function() {
			element.FadeRemove(onComplete);
		}, delay || 5000);
	},
	FadeRemoveAfter: function(element, delay, onComplete) {
		setTimeout(element.FadeRemove.bind(element, onComplete), delay || 5000);
	},
	FadeRemove: function(element, onComplete) {
		//Elimating Rico so no fade animation, to be replaced someday
		element.remove();
	},

	adoptParent: function(element, htmlParent) {
		element.priorParent = element.parentNode;
		element.priorNextSibling = element.nextSibling;
		element.parentNode.removeChild(element);
		htmlParent.appendChild(element);
	},

	abandonParent: function(element) {
		if(element.parentNode)
			element.parentNode.removeChild(element);
		if(element.priorParent) {
			if(element.priorNextSibling)
				element.priorParent.insertBefore(element, element.priorNextSibling);
			else
				element.priorParent.appendChild(element);
			element.priorParent = element.priorNextSibling = null;
		}
	},

	MoveUp: function(element) {
		var htmSibling = element.previousSibling;
		if(htmSibling) {
			element.remove();
			htmSibling.parentNode.insertBefore(element, htmSibling);
			return true;
		}
	},

	MoveDown: function(element) {
		var htmSibling = element.nextSibling;
		if(htmSibling) {
			element.remove();
			if(htmSibling.nextSibling)
				htmSibling.parentNode.insertBefore(element, htmSibling.nextSibling);
			else
				htmSibling.parentNode.appendChild(element);
			return true;
		}
	},
	CheckVisibilityChain: function(element) {
		while(element && element != document) {
			if(element.style.display == 'none' || element.style.visibility == 'hidden')
				return false;
			element = element.parentNode;
		}
		return true;
	},
	BecomeClass: function(element, BecomeClass) {
		if(element._JSClass != undefined)
			return element;

		var args = $A(arguments).slice(2);

		for(var i in BecomeClass)
			Object.cloneProp(BecomeClass, element, i);
		for(var i in BecomeClass.prototype)
			Object.cloneProp(BecomeClass.prototype, element, i);

		element.initialize.apply(element, args);
		element._JSClass = BecomeClass;
		return element;
	},
	FindParentObjectElement: function(element) {
		var ObjectElement = element.parentNode;
		while(ObjectElement && ObjectElement.getAttribute) {
			if(ObjectElement.getAttribute('objectid'))
				return ObjectElement;
			ObjectElement = ObjectElement.parentNode;
		}
		throw "Unable to locate ObjectID above pkAjaxStatusButton";
	},
	tabNext: function(Element) {
		var Context = $(Element.up('FORM') || document.body);
		var Inputs = Context.select('INPUT','SELECT','TEXTAREA');
		var IndexOf = Inputs.indexOf(Element);
		if(IndexOf >= 0) {
			IndexOf++;
			if(IndexOf >= Inputs.length)
				IndexOf = 0;
			Inputs[IndexOf].focus();
		} else
			Element.blur();
	},
	replace: function(Element, Html) {
		var Prev = Element.previous(), Parent = Element.up();
		pkElement_Methods._replace(Element, Html);
		var NewElement = Prev
			? Prev.next()
			: NewElement = Parent.down();
		pkExtend.Extend(NewElement);
		return NewElement;
	},
	update: function(Element, Html) {
		pkElement_Methods._update(Element, Html);
		$(Element).childElements().each( function(Node) {
			pkExtend.Extend(Node);
		} );
		return Element;
	},
	/** This function is just like fire except its delayed until other processing is completed.  This allows the
		fire event to be lazily called numerous times and have only one event fire */
	fireOnce: function(element, eventName, memo) {
		if(element.FireOnceTimerID && element.FireOnceTimerID[eventName])
			clearTimeout(element.FireOnceTimerID[eventName]);

		if(!element.FireOnceTimerID)
			element.FireOnceTimerID = { };
		element.FireOnceTimerID[eventName] = setTimeout(element.fire.bind(element, eventName, memo), 0);
	},
	fire: function(element, name, props) {
		props = props || { };
		/* If its a custom event, pass on to prototype */
		if(name.match(/:/) != undefined)
			return pkElement_Methods._fire.apply(this, arguments);

		var EventsMap = {
			'onchange': [ 'HTMLEvents', 'initEvent', { CanBubble: true, CanCancel: true } ],
			'change': 	[ 'HTMLEvents', 'initEvent', { CanBubble: true, CanCancel: true } ],
			'blur':		[ 'HTMLEvents', 'initEvent', { CanBubble: true, CanCancel: true } ],
			'focus':	[ 'HTMLEvents', 'initEvent', { CanBubble: true, CanCancel: true } ],
			'click': 	[ 'MouseEvents', 'initMouseEvent', { CanBubble: true, CanCancel: true, ClickCount: 1, screenX: 0, screenY: 0, clientX: 0, clientY: 0, ctrlKey: false, altKey: false, shiftKey: false, metaKey: false, button: 0, relatedTarget: null } ]
		};

		if(EventsMap[name] == undefined)
			throw 'pkElement_Methods.fire() does not know how to handle non-custom event of type: '+name;

		var event;

		if(document.createEvent) {
			event = document.createEvent(EventsMap[name][0]);
			var initArgs = [ name ];
			for (var x in EventsMap[name][2])
				initArgs.push(props[x] ? props[x] : EventsMap[name][2][x]);
			eval('event.'+EventsMap[name][1]+'.apply(event, initArgs)');
			element.dispatchEvent(event);
		} else {
			event = document.createEventObject();
			for(var x in EventsMap[name][2])
				event[x] = props[x] ? props[x] : EventsMap[name][2][x];
			event.type = name;
			element.fireEvent('on'+name, event);
		}
	}
}
pkElement_Methods._update = Element.update;
pkElement_Methods._replace = Element.replace;
pkElement_Methods._fire = Element.fire;

/**
*	Wrap the prototype core .insert .update .replace functionality to use our extender as well
* 		Disabled -- Prototype modified directly, this method doesn't work because at the point this
* 		is called, the nodes are not actually a part of the real DOM yet, they are in an anonymous
* 		element
*/
//Element._getContentFromAnonymousElement = Element._getContentFromAnonymousElement.wrap( function($super, tagName, html) {
//	var Nodes = $super(tagName, html);
//	$A(Nodes)
//		.findAll( function(Element) { return Element.nodeType == 1; } )
//		.each(pkExtend.Extend, pkExtend);
//	return Nodes;
//});

$A(['before','top','bottom','after']).each(function(Translation) {
	Element._insertionTranslations[Translation] = Element._insertionTranslations[Translation].wrap( function($super, element, node) {
		var Return = $super(element, node);
		pkExtend.Extend(node);
		return Return;
	} );
} );

/* Allows Array.without to be passed an array of elements to be without */
Array.prototype.without = Array.prototype.without.wrap( function() {
	var args = $A(arguments), Proceed = args.shift();
	if(args.length == 1 && Object.isArray(args[0]))
		return Proceed.apply(Proceed, args[0]);
	return Proceed.apply(Proceed, args);
} );

String.prototype.addslashes = function() {
	return this.replace(/(\"|\')/g, "\\$1");
}
String.prototype.stripslashes = function() {
	return this.replace(/\\(\"|\')/g, "$1");
}
String.prototype.escape = function() {
	var str = this.replace(/\"/g, '|dq|');
	return str.replace(/\'/g, '|sq|');
}
String.prototype.unescape = function() {
	var str = this.replace(/\|dq\|/g, '"');
	return str.replace(/\|sq\|/g, '\'');
}
String.prototype.striplinefeeds = function() {
	return this.replace(/(\r|\n)/g, '');
}
String.prototype.str_replace = function(search,replace,max) {
	max = max || 99999;
	var out = this;
	while(max > 0 && (nIndex = this.indexOf(search)) != -1) {
		out = out.substr(nIndex, search.length).concat(out.substr(nIndex+search.length, 99999));
		max--;
	}
	return out;
}
String.prototype.fastSearch = function(match) {
	var max=this.length - match.length;
	for(var j=0;j<=max;j++) {
		if(this[j] == match[0] && this.substr(j, match.length) == match)
			return 1;
	}
	return 0;
}

Function.prototype.after = function(f) {
	var __method=this;
	return function() {
		__method.apply(this, arguments);
		return f.apply(this, arguments);
	};
}

Function.prototype.before = function(f) {
	var __method=this;
	return function() {
		f.apply(this, arguments);
		return __method.apply(this, arguments);
	};
}

String.prototype.matchAll = function(tMatch) {
	for(var j=0;j<tMatch.length;j++)
		if(!this.match(tMatch[j]))
			return false;
	return true;
}

Element.addMethods(pkElement_Methods);

Object.cloneProp = function(from, to, prop) {
	if(!Object.copyGettersSetters(from, to, prop)) {
		if(from[prop] && from[prop] instanceof Array)
			to[prop] = from[prop].slice(0);
		else
			to[prop] = from[prop];
	}
}

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

/**
 * Prototype Overrides
 */

function FirstDefined() {
	for(var i=0; i<arguments.length;i++) {
		if(arguments[i] != undefined)
			return arguments[i];
	}
}

Form.serializeElements = function(elements, getHash) {
	var data = elements.inject({}, function(result, element) {
		if ((!element.disabled || element.CheckVisibilityChain()) && element.name) {
			var key = element.name, value = $(element).getValue();
			if (value != undefined) {
				if (result[key]) {
					if (result[key].constructor != Array) result[key] = [result[key]];
						result[key].push(value);
				}
				else result[key] = value;
			}
		}
		return result;
	});

	return getHash ? data : Hash.toQueryString(data);
}

var pkTemplate = Class.create( {
	syntax:		/(^|.|\r|\n)(\{(\w+)\})/,
	initialize: function(tmpl) {
		this.objTmpl = new Template(tmpl, this.syntax);
	},
	evaluate: function(vars) { return this.objTmpl.evaluate(vars); }
});

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

var $N = function(element) {
	return document.getElementsByName(element)[0];
}
/**
  *
  *  Copyright 2005 Clint Priest
  *
  *  Unless required by applicable law or agreed to in writing, software distributed under the
  *  License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
  *  either express or implied. See the License for the specific language governing permissions
  *  and limitations under the License.
  *
  *		pkCompatibility.js - Provides some routines for cross-browser compatibility
  *
  **/

Event.observe(window, 'keyup', function(e) {
	target = $(e.target ? e.target: e.srcElement);
	if(target.tagName.toLowerCase() == 'select')
		target.fire('onchange');
});


/* Makes IE8 accept .textContent */
if (navigator.userAgent.indexOf('IE') > 0 && Object.defineProperty && Object.getOwnPropertyDescriptor &&
	!Object.getOwnPropertyDescriptor(Element.prototype, "textContent").get)
  (function() {
	var innerText = Object.getOwnPropertyDescriptor(Element.prototype, "innerText");
	Object.defineProperty(Element.prototype, "textContent",
	  { // It won't work if you just drop in innerText.get
		// and innerText.set or the whole descriptor.
		get : function() {
		  return innerText.get.call(this)
		},
		set : function(x) {
		  return innerText.set.call(this, x)
		}
	  }
	);
  })();/**
  *
  *  Copyright 2005 Clint Priest
  *
  *  Unless required by applicable law or agreed to in writing, software distributed under the
  *  License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
  *  either express or implied. See the License for the specific language governing permissions
  *  and limitations under the License.
  *
  *		pkAjax.js - Ajax Routines & Support
  *
  **/

pkAjax = {
	ReportError: function(Error, ShowInConsole) {
		if((ShowInConsole || ShowInConsole == undefined) && console && console.error)
			console.error(Error);
		// This triggers an error and reports it in the servers error log, used to track any local javascript errors
		new Ajax.Request('/', { method: 'get', parameters: 'Command=LogError&Error='+escape(Error)+'&Location='+escape(window.location.href) } );
	},
	/**
	* Options = {
	*   onComplete: function(resultCode, hReturnData, responseXML);
	*   post: [ Post String ]
	*   bWaitIndicator: [ true | false ] - Default: true
	* }
	*/
	Request: function(Url, Options) {
		Options = Object.extend({ bWaitIndicator: true }, Options);
		Options.onException = function(request, exception) {
			pkMouse.ResetGlobalCursor();
			if(exception.name != 'NS_ERROR_NOT_AVAILABLE') {
				PopupMessage.Show('Request Failure', 'There was a failure during the request, this error has been logged and we will investigate the issue.');
				LogException('Exception while processing ajax request: '+request.url, exception);
			}
		}
		Options.__onComplete = Options.onComplete;
		Options.LastClickTarget = pkMouse.LastClickTarget;
		Options.LastCursorTarget = pkMouse.LastCursorTarget;

		if(!Options.bBackground || Options.bBackground == false)
			pkMouse.SetGlobalCursor('wait');
		if(typeof Url == 'string') {
			if(Options.post) {
				Options.postBody = Options.post;
				Options.method = 'post';
			} else {
				Options.method = 'get';
				if(Url.indexOf('?') != -1) {
					var tSplit = Url.split('?');
					Url = tSplit[0];
					Options.parameters = tSplit[1];
				} else {
					Options.parameters = Options.get;
				}
			}
			Options.asynchronous = true;
			Options.onComplete = this.onCompleteRequest.bind(this, Url, Options);

			new Ajax.Request(Url, Options );
		} else if(Url.nodeType && Url.nodeType == 1 && Url.tagName == 'FORM') {
			/* Using Form for Posting */
			var htmlForm = Url;
			htmlForm.target = '_'+new Date().getTime();
			var htmUploadFrame = CreateElement('iframe', {
					_htmlForm					: htmlForm,
					_name						: htmlForm.target
				}, {
					position					: 'absolute',
					top							: '-2000px',
					width						: '500px',
					height						: '500px',
					backgroundColor				: 'white'
				}, {
					appendChild					: document.body
			} );
			htmUploadFrame._FrameSubmitLoadedHandler = this._FrameSubmitLoaded.bind(this, htmUploadFrame, Options);
			Event.observe(htmUploadFrame, 'load', htmUploadFrame._FrameSubmitLoadedHandler);
			htmlForm.submit();
		}
	},
	_FrameSubmitLoaded: function(htmUploadFrame, Options) {
		Event.stopObserving(htmUploadFrame, 'load', htmUploadFrame._FrameSubmitLoadedHandler);
		try {
			if(navigator.msie) {
			} else {
				if(htmUploadFrame.contentDocument.contentType == "text/xml")
					var request = { responseXML: htmUploadFrame.contentDocument };
				else
					var request = { responseText: htmUploadFrame.contentDocument.body.innerHTML };
			}
			this.onCompleteRequest(htmUploadFrame.htmlForm.action, Options, request);
			$(htmUploadFrame).remove();
		} catch(e) {
			Options.onException(null, e);
		}
	},
	onCompleteRequest: function(Url, Options, request) {
		pkMouse.ResetGlobalCursor();
		pkMouse.LastClickTarget = Options.LastClickTarget;
		pkMouse.LastCursorTarget = Options.LastCursorTarget;

		if(request.responseXML && request.responseXML.childNodes.length) {
			if(request.responseXML.getElementsByTagName('ajax-response').length) {
				var tServerErrors = request.responseXML.getElementsByTagName('server_errors');
				if(tServerErrors.length)
					ServerErrors.Show(tServerErrors[0].firstChild.nodeValue);
				var hReturnData = { };
				$A(request.responseXML.getElementsByTagName('return_data')).each(
					function(xmlContent) {
						try{
							eval('hReturnData.'+xmlContent.getAttribute('Name')+'= '+(pkAjax.ProcessNodeValue(xmlContent.firstChild.nodeValue).striplinefeeds())+';');
						} catch(e) {
							LogException('Exception: ', e);
						}
					}
				)
				tMessages = request.responseXML.getElementsByTagName('message');
				$A(tMessages).each(
					function(xmlMessage) {
						pkAjax.QueueMessage(xmlMessage.getAttribute('Header'), pkAjax.ProcessNodeValue(xmlMessage.firstChild.nodeValue), xmlMessage.getAttribute('Target') || Options.htmlElement, xmlMessage.getAttribute('Arrow') );
					}
				)
				$A(request.responseXML.getElementsByTagName('object_status')).each( function(xmlMessage) {
					var StatusObject = $(document.body).select('*[ObjectID="'+xmlMessage.getAttribute('ObjectID')+'"]');
					if(StatusObject.length)
						$(StatusObject[0]).fire('pk:StatusChange', { Status: xmlMessage.getAttribute('Status') } );
				});
				if(Options.__onComplete) {
					var resultCode = 200;
					try {
						xmlResponse = request.responseXML.getElementsByTagName('response')[0];
						resultCode = Number(xmlResponse.getAttribute('resultCode'));
					} catch(e) { }
					Options.__onComplete(resultCode, hReturnData, request.responseXML);
				}
				pkAjax.ShowQueuedMessages();

				var Errors = $A(request.responseXML.getElementsByTagName('form_error')).inject( { }, function(Errors, xmlError) {
					Errors[xmlError.getAttribute('Name')] = pkAjax.ProcessNodeValue(xmlError.firstChild.nodeValue);
					return Errors;
				}.bind(this) );
				(Options.EventTarget || document.body).fire('pk:FormErrors', Errors);

				$A(request.responseXML.getElementsByTagName('object_html_update')).each( function(xmlMessage) {
					var Object = $(document.body).down('*[ObjectID="'+xmlMessage.getAttribute('ObjectID')+'"]');
					if(Object) {
						var Previous = Object.previous(), Next = Object.next(), Parent = Object.up();
						Object.replace(xmlMessage.firstChild.nodeValue);
						document.body.fire('pk:ObjectReplace', {
							original: 		Object,
							replacement: 	(Previous && Previous.next()) || (Next && Next.previous()) || (Parent.down())
						} );
					}
				});
				tContentUpdate = request.responseXML.getElementsByTagName('html_update');
				$A(tContentUpdate).each(
					function(xmlContent) {
						objUpdate = $(xmlContent.getAttribute('ElementID'));
						if(objUpdate)
							Element.update(objUpdate, pkAjax.ProcessNodeValue(xmlContent.firstChild.nodeValue));
					}
				);
				var RedirectNodes = request.responseXML.getElementsByTagName('redirect');
				if(RedirectNodes.length)
					window.location.href = RedirectNodes[0].firstChild.nodeValue;
			} else {
				pkAjax.ReportError('Invalid XML document returned (likely parsing error)\r'+request.responseText);
				PopupMessage.Show('Request Failure', 'There was a failure during the request, this error has been logged and we will investigate the issue.');
			}
		} else {
			if(Options.DirectUpdate)
				$(Options.DirectUpdate).update(request.responseText);
			else
				PopupMessage.Show('', request.responseText, '600');
		}
	},
	ProcessNodeValue: function(value) {
		return value.replace('_CDATA_END_TAG_PROXY_', ']]>');
	},
	QueueMessage: function(Header, Message, Target, Arrow) {
		if(!this.tQueuedMessages)
			this.tQueuedMessages = [ ];
		this.tQueuedMessages.push([Header,Message,Target,Arrow]);
	},
	ShowQueuedMessages: function() {
		try {
			$A(this.tQueuedMessages).each( function(tParams) {
				this.onMessage.apply(this, tParams);
			}.bind(this));
		} catch(e) { }
		this.tQueuedMessages = [ ];
	},
	onMessage: function(Header, Message, Target, Arrow) {
		if(Target) {
			try {
				PopupBalloon.Show(Header, Message, Target, Arrow || 'auto', 5000, false);
			} catch(e) {
				PopupMessage.Show(Header, Message, 600);
			}
		} else
			PopupMessage.Show(Header, Message, 600);
	}
}

pkAjax.Form = Class.create();
pkAjax.Form.prototype = {
	initialize: function(Url, Options) {
		this.Options = Object.extend( { WindowClass: 'PopupForm', vAlign: 0.2, hAlign: 0.5 }, Options);
		this.Url = Url;
		this.LaunchTarget = pkMouse.LastClickTarget;
		this._ExtendElements = this.ExtendElements.bind(this);
		pkExtend.RegisterScanFunction(this._ExtendElements);
		pkAjax.Request(Url, { onComplete: this._ReceiveHtml.bind(this) } );
	},
	_ReceiveHtml: function(resultCode, hReturnData, xmlDoc) {
		switch(resultCode) {
			case 200:
				if(hReturnData.Content) {
					this.Window = new pkWindowMgr.Window(hReturnData.Content, hReturnData.Width, { vAlign: this.Options.vAlign, hAlign: this.Options.hAlign, WindowClass: this.Options.WindowClass } );
					this.Window.ShowWindow(true);
					this.Window.objForm = this;

					this.Window.htmlWindow.observe('pk:FormErrors', this.onFormErrors.bindAsEventListener(this));

					var tForms = this.Window.htmlWindow.getElementsByTagName('form');
					if(tForms.length) {
						this.htmlForm = $(tForms[tForms.length-1]);	// The Last Form

						this.htmlForm.pkAjaxForm = this;

						/* Register Global Escape Handler */
						this._GlobalKeyHandler = this.GlobalKeyHandler.bindAsEventListener(this);
						Event.observe(window, 'keypress', this._GlobalKeyHandler);
						this.htmlForm.focusFirstElement();
					} else {
						pkAjax.ReportError('No <form> elements returned for Url: '+this.Url);
						PopupBalloon.Show('General Failure', 'The request was not able to be completed, this error has been logged and will be investigated by the tech team.', pkMouse.LastClickTarget, 'auto', 4000);
						this.Destroy();
					}
				}
				break;
			case 406:
				break;
			default:
				return this.Destroy();
		}
	},
	onFormErrors: function(e) {
		FormErrors.Clear();

		$H(e.memo).each( function(x) {
			FormErrors.ShowError(x.value, x.key, this.Window.htmlWindow);
		}, this );
	},
	ExtendElements: function(htmElement) {
		var tButtons = htmElement.getElementsByTagName('input');
		$A(tButtons).each( function(oInput) {
			if(oInput.type == 'button' && (Command=oInput.getAttribute('command'))) {
				Event.observe(oInput, 'click', this.onCommand.bind(this, Command, oInput));
				if(oInput.getAttribute('Default') != undefined)
					this.DefaultCommand = Command;
			}
		}, this);
	},
	GlobalKeyHandler: function(e) {
		switch(e.keyCode) {
			case 13:	// Enter
				if(this.DefaultCommand) {
					Event.stop(e);
					this.onCommand(this.DefaultCommand);
					return true;
				}
				break;
			case 27:
				Event.stop(e);
				this.onCommand('Cancel');
				return true;
		}
	},
	Destroy: function() {
		pkExtend.UnregisterScanFunction(this._ExtendElements);
		Event.stopObserving(window, 'keypress', this._GlobalKeyHandler);
		if(this.Options.OnDestroy)
			this.Options.OnDestroy();
		if(this.htmlForm)
			this.htmlForm.fire('blur');
		if(this.Window)
			this.Window.Destroy();
	},
	onCommand: function(Command, htmButton) {
		if(this.Options.onCommand && this.Options.onCommand.call(this, Command, htmButton) != false)
			return;
		switch(Command) {
			case 'Cancel':	return this.Destroy();
			case 'Apply':
				this.LastLaunchTarget = pkWindowMgr.LastLaunchTarget;
				pkWindowMgr.LastLaunchTarget = pkMouse.LastClickTarget;
				break;
		}

		if(this.Options.bUseFrameForSubmission) {
			pkAjax.Request(this.htmlForm, { onComplete: this._ReceiveSubmitResponse.bind(this, Command) } );
		} else {
			if(this.htmlForm.method == 'get')
				pkAjax.Request(this.htmlForm.action, { EventTarget: this.Window.htmlWindow, get: Form.serialize(this.htmlForm) + '&Command='+Command, onComplete: this._ReceiveSubmitResponse.bind(this, Command) } );
			else
				pkAjax.Request(this.htmlForm.action, { EventTarget: this.Window.htmlWindow, post: Form.serialize(this.htmlForm) + '&Command='+Command, onComplete: this._ReceiveSubmitResponse.bind(this, Command) } );
		}
	},
	onCommandCompleted: function(Command, hData) {
		switch(Command) {
			case 'Apply':
				pkWindowMgr.LastLaunchTarget = this.LastLaunchTarget;
				break;
		}
		try { this.Options.onCommandCompleted.call(this, Command, hData); } catch(e) { }
		try { this.htmlForm.onCommandCompleted.call(this, Command, hData); } catch(e) { }
	},
	_ReceiveSubmitResponse: function(Command, resultCode, hReturnData, xmlDoc) {
		switch(resultCode) {
			case 200:
				if(this.Options.OnSubmitSuccess)
					this.Options.OnSubmitSuccess(hReturnData, xmlDoc);
				if(this.htmlForm.OnSubmitSuccess)
					this.htmlForm.OnSubmitSuccess(hReturnData, xmlDoc);
				switch(hReturnData.UpdateMethod) {
					case 'LaunchTarget_ObjectID':
						var oElement = this.LaunchTarget;
						while(oElement && oElement.getAttribute) {
							if(oElement.getAttribute('ObjectID') == hReturnData.UpdateID) {
								var oInsert = new Insertion.Before(oElement, hReturnData.UpdateContent);
								$(oElement).remove();
								break;
							}
							oElement = oElement.parentNode;
						}
						break;
					case 'Update':
						var oElement = $(hReturnData.UpdateID);
						if(oElement)
							oElement.update(hReturnData.UpdateContent);
						break;
					case 'AppendChild':
						var oElement = $(hReturnData.UpdateID);
						if(oElement)
							new Insertion.Bottom(oElement, hReturnData.UpdateContent);
						break;
				}
				if(hReturnData.DestroyWindow || (hReturnData.DestroyWindow == undefined && Command != 'Apply'))
					this.Destroy();
				pkAjax.ShowQueuedMessages();
				this.onCommandCompleted(Command, hReturnData);
				if(hReturnData.Content && this.Options.UpdateContentID)
					try { $(this.Options.UpdateContentID).update(hReturnData.Content); } catch(e) { }
				break;
			case 406:
				break;
			default:
				pkAjax.ReportError('Ajax form returned a resultCode I am not prepared to handle ('+resultCode+')  Url: '+this.Url);
				PopupBalloon.Show('General Failure', 'The request was not able to be completed, this error has been logged and will be investigated by the tech team.', pkWindowMgr.LastLaunchTarget, 'auto', 4000);
				this.Destroy();
				break;
		}
	},
	FormError: function(htmlElement, Error) {
		try {
			this.Options.OnFormError(htmlElement, Error);
		} catch(e) {
			PopupTab.Show(Error, htmlElement);
		}
	}
}

pkExtend.RegisterScanFunction( function(htmElement) {
	$A(htmElement.getElementsByTagName('form')).each( function(htmForm) {
		if(htmForm.getAttribute('ajaxaction') != undefined) {
			Event.observe(htmForm, 'submit', function(e) {
				Event.stop(e);
				var Action = this.getAttribute('ajaxaction');

				var Options = {
					onComplete: function(resultCode, hReturnData) {
						if(this.onComplete)
							this.onComplete(resultCode, hReturnData);
					}.bind(this)
				};
				if(this.getAttribute('method') == 'post') {
					var Url = Action;
					Options.post = Form.serialize(this);
				} else {
					if(Action.indexOf('?') != -1)
						var Url = Action + '&' + Form.serialize(this);
					else
						var Url = Action + '?' + Form.serialize(this);
				}
				pkAjax.Request(Url, Options);
				return false;
			}.bindAsEventListener(htmForm));
		}
	});
} );

/***********************************************************************************************
*	LogException() - Formats an exception and sends it to the server
***********************************************************************************************/
function LogException(Error, Exception) {
	if(navigator.msie) {
		if(window.bUserIsAdmin && Logger.Log) {
			Logger.Log(Error+'\nException: '+Exception.message+' Desc: '+Exception.description);
			for(x in Exception)
				Logger.Log('Exception['+x+'] = '+Exception[x]);
		}
		return pkAjax.ReportError(Error+'\nException: '+Exception.message+' Desc: '+Exception.description);
	}
	pkAjax.ReportError(Error+'\nException: '+Exception.toString());
	try {	console.log(Error, Exception); } catch(e) {}
}
/**
  *
  *  Copyright 2005 Clint Priest
  *
  *  Unless required by applicable law or agreed to in writing, software distributed under the
  *  License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
  *  either express or implied. See the License for the specific language governing permissions
  *  and limitations under the License.
  *
  *		pkControls.js - Provides some nice control features
  *
  **/

pkBalloonMsg = Class.create();

pkBalloonMsg.prototype = {
	initialize: function(Class) {
		if(!pkWindowMgr.LastBalloonZIndex)
			pkWindowMgr.LastBalloonZIndex = 901;
		this._EscapeHandler = this.EscapeHandler.bind(this);
		this.OffsetContainer = CreateElement('div', { }, { position: 'relative', margin: '0px !important', padding: '0px !important', width: '0px', height: '0px' } );
		this.Balloon = CreateElement('div', { _className: Class }, { left: '100px', position: 'absolute' } );
		this.OffsetContainer.appendChild(this.Balloon);
		var Div = CreateElement('div', { _className: 'BalloonContainer' } );
		this.CloseButton = CreateElement('img', { src: '/res/img/BalloonMsg/icon.close.gif' }, { margin: '0px 5px 0px 4px', cursor: 'pointer', position: 'absolute', right: '1px', top: '5px', zIndex: '50' }, { appendChild: Div } );
		Event.observe(this.CloseButton, 'click', this._Hide.bind(this));
		this.Header = CreateElement('h1', {}, { }, { appendChild: Div  });
		this.Message = CreateElement('p', {}, { }, { appendChild: Div } );
		this.Balloon.appendChild(Div);
		this.ArrowTL = CreateElement('img', { src: '/res/img/BalloonMsg/arrow.topleft.gif' }, {
			top: '-3px', left: '-10px', width: '21px', height: '13px', position: 'absolute', visibility: 'hidden', zIndex: '1000' }, {appendChild: this.Balloon } );
		this.ArrowTR = CreateElement('img', { src: '/res/img/BalloonMsg/arrow.topright.gif' }, {
			top: '-3px', right: '-10px', width: '21px', height: '13px', position: 'absolute', visibility: 'hidden', zIndex: '1000' }, {appendChild: this.Balloon } );
		this.ArrowBR = CreateElement('img', { src: '/res/img/BalloonMsg/arrow.bottomright.gif'}, {
			bottom: (navigator.msie ? '-4px' : '-3px'), right: '-10px', width: '21px', height: '13px', position: 'absolute', visibility: 'hidden', zIndex: '1000'	}, {appendChild: this.Balloon } );
		this.ArrowBL = CreateElement('img', { src: '/res/img/BalloonMsg/arrow.bottomleft.gif'}, {
			bottom: (navigator.msie ? '-4px' : '-3px'), left: '-10px', width: '21px', height: '13px', position: 'absolute', visibility: 'hidden', zIndex: '1000'	}, {appendChild: this.Balloon } );
	},
	SetContent: function(Header, Contents, Width, Options) {
		Options = Options || { bShowClose: true }
		if(Header) {
			this.Header.innerHTML = Header;
			this.Header.show();
		} else
			this.Header.hide();
		if(typeof Contents == 'string') {
			this.Message.innerHTML = Contents;
		} else {
			this.Message.clearChildren();
			this.Message.appendChild(Contents);
		}
		if(Options.Class)
			this.Balloon.className = Options.Class;

		Options.bShowClose == false
			? this.CloseButton.hide()
			: this.CloseButton.show();
		this.Balloon.style.width = Width ? Width+'px' : '240px';
	},
	Show: function(Arrow, PositionElement, Options) {
		this._Hide();	// Ensures it has been hidden prior to re-showing
		if(PositionElement == 'LastClickTarget' || PositionElement == '_LastClickTarget_')
			PositionElement = pkMouse.LastClickTarget;
		if(PositionElement == 'LastLaunchTarget' || PositionElement == '_LastLaunchTarget_')
			PositionElement = pkWindowMgr.LastLaunchTarget;
		PositionElement = $(PositionElement) || document.body;


		var myLayer;
		if(Options.bShowMask) {
			myLayer = pkWindowMgr.CreateLayer();
			myLayer.htmlMask.setStyle( { display: 'block', visibility: 'visible' } );
			this.Layer = myLayer;
			Event.observe(window, 'keydown', this._EscapeHandler);
			myLayer.insert( { top: this.OffsetContainer } );
		} else {
			var InsertPosition = PositionElement.up('tr') || PositionElement;
			InsertPosition.insert( { before: this.OffsetContainer } );
		}
		this.OffsetContainer.style.zIndex = pkWindowMgr.LastBalloonZIndex++;

		this.Balloon.setStyle( { display: 'block', visibility: 'visible' } );
		if(!navigator.msie)
			this.Balloon.setStyle( { opacity: 1 } );
		this.SetPosition(Arrow, PositionElement, Number(Options.xOffset || 0), Number(Options.yOffset || 0));
		if(Options.nHideDelay != undefined && Options.nHideDelay > 0)
			setTimeout(	this.Hide.bind(this, true), Options.nHideDelay	);
	},
	SetPosition: function(Arrow, PositionElement, xPos, yPos) {
		PositionElement = $(PositionElement) || document.body;
		if(PositionElement) {
			var myArrow = Arrow;
			while(Arrow == 'auto') {
				switch(myArrow.toLowerCase()) {
					default:			myArrow = 'topLeft';		break;
					case 'topleft':		myArrow = 'topRight';		break;
					case 'topright': 	myArrow = 'bottomRight'; 	break;
					case 'bottomright':	myArrow = 'bottomLeft';		break;
					case 'bottomleft':	Arrow = 'topLeft';	break;
				}
				if(Arrow == 'auto') {
					pos = this.CalculatePosition(PositionElement, myArrow, xPos, yPos);
					if(pos.x > 0 && this.Balloon.offsetWidth + pos.x < document.body.offsetWidth
							&& this.Balloon.offsetHeight + pos.y > 0 && this.Balloon.offsetHeight + pos.y < document.body.offsetHeight) {
						Arrow = myArrow;	// Position Found
					}
				}
			}
			if(this.ArrowVisible)
				this.ArrowVisible.SetVisibility('hidden');
			switch(Arrow.toLowerCase()) {
				case 'topleft':		this.ArrowTL.SetVisibility('visible');	this.ArrowVisible = this.ArrowTL; break;
				case 'topright':	this.ArrowTR.SetVisibility('visible');	this.ArrowVisible = this.ArrowTR; break;
				case 'bottomleft':	this.ArrowBL.SetVisibility('visible');	this.ArrowVisible = this.ArrowBL; break;
				case 'bottomright':	this.ArrowBR.SetVisibility('visible');	this.ArrowVisible = this.ArrowBR; break;
			}

			pos = this.CalculatePosition(PositionElement, Arrow, xPos, yPos);
			this.Balloon.SetPosition('absolute', pos.y+'px', pos.x+'px');
			this.Balloon.SetVisibility('visible', true);
		} else {
			this._Hide();
			pkAjax.ReportError('BalloonMsg::SetPosition - Could not find reference to element '+PositionElement);
		}
	},
	CalculatePosition: function(PositionElement, Arrow, xPos, yPos) {
		switch(Arrow.toLowerCase()) {
			case 'topleft':		xOffset=9;	yOffset=3;		break;
			case 'topright': 	xOffset=-(PositionElement.offsetWidth+this.Balloon.offsetWidth+6);	yOffset=0;	 	break;
			case 'bottomright': xOffset=-(PositionElement.offsetWidth+this.Balloon.offsetWidth+6);	yOffset=-(PositionElement.offsetHeight+this.Balloon.offsetHeight-1);	 	break;
			case 'bottomleft': 	xOffset=5;	yOffset=-(PositionElement.offsetHeight+this.Balloon.offsetHeight-1);	 	break;
			default:	xOffset = 0; 	yOffset = 0;
		}

		var ContainerPos = this.OffsetContainer.positionedOffset();
		var PositionPos = PositionElement.positionedOffset();

		if(xPos > 0 && xPos < 1) {
			xPos *= PositionElement.offsetWidth;
			xPos += PositionPos.left;
			xPos -= this.Balloon.offsetWidth / 2;
		} else if(xPos > 0)
			xPos += PositionPos.left;
		else
			xPos += (PositionPos.left - ContainerPos.left) + (PositionElement.getWidth()/2);

		if(yPos > 0 && yPos < 1) {
			yPos *= PositionElement.offsetHeight;
			yPos += PositionPos.top;
			yPos -= this.Balloon.offsetHeight / 2;
		} else if(yPos > 0)
			yPos += PositionPos.top;
		else
			yPos += (PositionPos.top - ContainerPos.top) + (PositionElement.getHeight()/2);

		return { x: xPos+xOffset, y: yPos+yOffset}
	},
	_Hide: function() {
		if(this.OffsetContainer.parentNode) {
			Event.stopObserving(window, 'keydown', this._EscapeHandler);
			this.OffsetContainer.remove();
			if(this.Layer) {
				pkWindowMgr.DestroyLayer(this.Layer);
				this.Layer = undefined;
			}
		}
	},
	Hide: function() {
		this._Hide();

	},
	EscapeHandler: function(e) {
		if(e.keyCode == 27) { /* Escape */
			this._Hide();
			Event.stopObserving(window, 'keydown', this._EscapeHandler);
			Event.stop(e);
			return true;
		}
	},
	Visible: function() { return this.OffsetContainer.parentNode != null; }
}

PopupBalloon = {
	Show: function(Header, Message, Element, Arrow, AutoFadeTime, bShowClose) {
		var BalloonMsg = new pkBalloonMsg('Balloon');
		BalloonMsg.SetContent(Header,Message, 200, {bShowMask: false, bShowClose: bShowClose || false });
		BalloonMsg.Show(Arrow ? Arrow : 'auto', Element, { xOffset: 0, yOffset: 0, nHideDelay: AutoFadeTime || 5000 });
	}
}

PopupMessage = {
	Show: function(Header, Message, Width) {
		if(this.BalloonMsg == undefined)
			this.BalloonMsg = new pkBalloonMsg('Message');
		this.BalloonMsg.SetContent(Header, Message, Width || 500);
		this.BalloonMsg.Show('none', null, { xOffset: .5, yOffset: .2, bShowMask: true } );
	}
}


pkPopupTab = Class.create();
pkPopupTab.prototype = {
	Html: new Template('<div style="visibility: hidden;" class="PopupTab"><img src="/res/img/PopupTab/arrow.left.gif"><div>#{Message}</div>'),

	initialize: function(Message, PositionElement) {
		PositionElement = $(PositionElement);
		this.PopupTab = PositionElement.insert( { after: this.Html.evaluate( { Message: Message } ) } ).next();

		var Position = PositionElement.positionedOffset(),
			Dimensions = PositionElement.getDimensions();

		this.PopupTab.style.left = Position.left + Dimensions.width + 15 + 'px';
		this.PopupTab.style.top = Position.top + (Dimensions.height / 2) - (this.PopupTab.getHeight() / 2) + 'px';
		this.PopupTab.style.visibility = 'visible';
	},
	remove: function() { this.PopupTab.remove(); }
}

FormErrors = {
	tPopupTabs: $A([ ]),

	Clear: function() {
		this.tPopupTabs.invoke('remove');
		this.tPopupTabs = $A([ ]);
	},
	ShowError: function(Message, Name, BaseElement) {
		var Element = this.LocateElement(BaseElement || document.body, Name);

		/* See if this is a row based element, if so, special handling */
		if(Element.name && Element.name.match(/.+\[[-\d]+\]\[[\w\d]+\]$/) && ['TD','TH'].indexOf(Element.up().tagName) != -1) {
			if(Element.up('tr').down('DIV.PopupTab') != undefined)
				return;
			Element = Element.up('tr').childElements().last().down();
		}
		this.tPopupTabs.push(new pkPopupTab(Message, Element));
	},
	LocateElement: function(BaseElement, Name) {
		var htmElem = $(Name)
			|| BaseElement.down('*[Name="'+Name+'"]')
			|| BaseElement.select('input','select','textarea').find( function(htmElem) {
				if(htmElem.name && htmElem.type != 'hidden' && htmElem.name.substr(0, Name.length) == Name)
					return true;
				return false;
			});

		if(!htmElem.visible() || (htmElem.tagName == 'INPUT' && htmElem.type == 'hidden'))
			htmElem = htmElem.next() || htmElem.previous();
		if(htmElem.type == 'checkbox' && htmElem.up().tagName == 'LABEL')
			return htmElem.up().nextSiblings().last();
		return htmElem;
	}
}

/** Generates an InfoBar at the top of a browser */
var InfoBar = {
	tMessages: 			[ ],
	InfoBarClass:		'InfoBar',

	AddMessage: function(Class, Title, Details) {
		this.tMessages.push({ Class: Class, Title: Title, Details: Details} );
	},
	ShowMessage: function(Class, Title, Details) {
		this.AddMessage(Class, Title, Details);
		this.Show();
	},
	Hide: function() {
		this.htmBar.hide();
	},
	Show: function() {
		this.CreateBar();
		this.htmBar.update('');
		this.tMessages.each(function(Message) {
			this.htmBar.insert(this.CreateMessage(Message));
		}, this);
		this.htmBar.show();
	},
	CreateMessage: function(Message) {
		var Html = 		'<div class="' + Message.Class + '"><img class="Close" src="/res/img/icon.close.gif">' + Message.Title;
		if(Message.Details) {
			Html = Html + '<span>[ <a class="Details" href="#">Details</a> ]</span>';
			Html = Html + '<div style="display: none;">' + Message.Details + '</div>';
		}
		Html = Html + 	'</div>';
		return Html;
	},
	CreateBar: function() {
		if(this.htmBar)
			return;
		$(document.body).insert({ top: '<div style="display: none;" class="' + this.InfoBarClass + '"></div>' });
		this.htmBar = $(document.body).down('div');
		this.htmBar.observe('click', this.onClick.bindAsEventListener(this));
	},
	onClick: function(e) {
		var element = e.element();
		if(element.tagName == 'A' && element.up().next().tagName == 'DIV')
			element.up().next().toggle();
		if(element.hasClassName('Close'))
			element.up('div').remove();
	}
}

var pkInfiniteList = Class.create();

/***********************************************************************************************
*	pkInfiniteList() - This class is used to infinitely extend a table with rows of form elements
*		it will always have atleast one 'default' new row available.  It modifies the name
*		attributes of the elements changing the last numerical id, always producing another
*		negative number, expanding and contracting the list as needed.  Allows for multiple-row
*		entries.
***********************************************************************************************/
pkInfiniteList.prototype = {
	initialize: function(TableID, Options) {
		this.htmlTable = $(TableID);
		this.Options = Object.extend({ AutoExpand: true }, Options);

		if(this.htmlTable) {
			tRows = this.htmlTable.getElementsByTagName('tr');
			this.htmlVirginRow = $(tRows[tRows.length-1]);
			this.htmlTableBody = this.htmlVirginRow.parentNode;
			this.htmlVirginRow.remove();

			// Determine format of ObjectID (new vs old)
			var tMatch = this.htmlVirginRow.getAttribute('objectid').match(/^(\w+)\/([\d\-]+)$/);
			if(tMatch)
				this.ObjectClass = tMatch[1];

			// Locate the virgin row element id, replace the name of all controls with the *NEW_ID* parameter for easy replacement later
			this.GetRowElements(this.htmlVirginRow).each(
				function(element) {
					if(element.name) {
						var aParts = element.name.match(/([\s\w\d\-]+)/g);
						aParts.shift();
						for(j=aParts.length-1;j>=0;j--) {
							if(!isNaN(Number(aParts[j]))) {
								if(!this.NextNewRowID)
									this.NextNewRowID = aParts[j];
								aParts[j] = '*NEW_ID*';
								break;
							}
						}
						element.name = 'Data['+aParts.join('][')+']';
					}
					// Store the default value of the virgin row element
					switch(element.tagName) {
						case 'SELECT':	var defaultValue = element.selectedIndex;		break;
						case 'INPUT':
						case 'TEXTAREA':
							switch(element.type.toLowerCase()) {
								case 'checkbox':
									var defaultValue = element.checked;
									break;
								default:
									var defaultValue = element.value;
									break;
							}
							break;
					}
					element.setAttribute('defaultValue', defaultValue);
				}.bind(this)
			)
			if(this.Options.AutoExpand)
				this.CreateNewRow();
			if(this.htmlTableBody.select('tr').length == 0) {
				this.htmlTableBody.insert('<tr class="Empty"><td colspan="99">Empty List</td></tr>');
			}
		}
	},
	GetObjectID: function(htmRow) {
		var ObjectID = htmRow.getAttribute('objectid');
		if(ObjectID && this.ObjectClass)
			return ObjectID.replace(this.ObjectClass+'/', '');
		return ObjectID;
	},
	SetObjectID: function(htmRow, ObjectID) {
		if(this.ObjectClass)
			ObjectID = this.ObjectClass + '/' + ObjectID;
		htmRow.setAttribute('ObjectID', ObjectID);
	},
	CreateNewRow: function(NewRowID) {
		if(NewRowID == undefined) {
			// Find an available row id
			var tUsedObjectID = this.htmlTableBody.getElementsByTagName('tr').map( function(htmRow) {
				return this.GetObjectID(htmRow);
			}.bind(this) );
			do {
				this.NextNewRowID--;
			} while(tUsedObjectID.find(function(value) { return value == this.NextNewRowID; }.bind(this) ) != undefined );
			var NewRowID = this.NextNewRowID;
		}

		var htmlNewRow = $(this.htmlVirginRow.cloneNode(true));
		htmlNewRow.show();
		this.SetObjectID(htmlNewRow, NewRowID);
		// Setup new elements with the right name and hook in keydown/click events
		this.GetRowElements(htmlNewRow).each(
			function(element) {
				element.name = element.name.replace('*NEW_ID*', NewRowID);
				if(this.Options.AutoExpand) {
					Event.observe(element, 'click', this._InputChange.bind(this, htmlNewRow));
					Event.observe(element, 'keydown', this._InputChange.bind(this, htmlNewRow));
					Event.observe(element, 'change', this._InputChange.bind(this, htmlNewRow));
				}
			}.bind(this)
		)
		try { this.htmlTableBody.select('tr.Empty')[0].remove(); } catch(e) {};
		this.htmlTableBody.appendChild(htmlNewRow);
		if(pkExtend)
			pkExtend.Extend(htmlNewRow);
		return htmlNewRow;
	},
	_InputChange: function(htmlRow, event) {
		var bMatchesDefault = this.RowElementsMatchDefault(htmlRow);
		var bLastRow = htmlRow == htmlRow.parentNode.lastChild;
		if(!bMatchesDefault && bLastRow)
			this.CreateNewRow();
		else if(bMatchesDefault && !bLastRow)
			htmlRow.remove();
	},
	RowElementsMatchDefault: function(htmlRow) { return this._RowElementsMatchDefault(htmlRow); },
	_RowElementsMatchDefault: function(htmlRow) {
		// Returns true if any of the form elements do not match their default values
		return this.GetRowElements(htmlRow).find(
			function(element) {
				var defaultValue = element.getAttribute('defaultValue');
				switch(element.tagName){
					case 'SELECT':		var thisValue = element.selectedIndex;		break;
					case 'INPUT':
					case 'TEXTAREA':
						switch(element.type.toLowerCase()){
							case 'checkbox':	var thisValue = element.checked;	break;
							default:			var thisValue = element.value;		break;
						}
						break;
				}
				return String(thisValue) != defaultValue;
			}
		) == null;
	},
	GetRowElements: function(htmlRow) { return htmlRow.select('input','select','textarea'); }
}

var DropDownEdit = Class.create( {
	Lines: {
		get: function() { return parseInt(this.getAttribute('Lines')); }
	},
	SelectedItemID: {
		get: function() { return this.SelectedValue.value; },
		set: function(x) { this.SelectedValue.value = x; }
	},
	DataUrl: {
		get: function() { return this.getAttribute('DataUrl'); }
	},
	hCache: [ ],
	Items: [ ],

	initialize: function() {
		this.SelectedValue = this.insert( { after: '<input type="hidden" name="'+this.getAttribute('name')+'" value="'+this.getAttribute('SelectedItemID')+'">' }).next();
		this.removeAttribute('name', 'SelectedItemID');
		this.setAttribute('autocomplete', 'off');

		this.QueryValues(this.DataUrl);

		this.CreateDropDown();

		this.__KeyUp = this._KeyUp.bindAsEventListener(this);
		this.__KeyDown = this._KeyDown.bindAsEventListener(this);

		Event.observe(this, 'blur', this._Blur.bindAsEventListener(this));
		Event.observe(this, 'focus', this._Focus.bindAsEventListener(this));
	},
	CreateDropDown: function() {
		this.DropDown = document.body.insert( { bottom: '<div class="DropEdit" style="position: absolute; display: none;"></div>' }).childElements().last();
		pkIE.ieFrameFix(this.DropDown);

		for(j=0;j<this.Lines;j++)
			this.DropDown.insert( { bottom: '<div></div>' } );
		Event.observe(this.DropDown, 'mousedown', this._MouseDown.bindAsEventListener(this));
	},
	/* Queries the server for the data just once, fires an event when the data is available */
	QueryValues: function(QueryUrl) {
		var QueryData = DropDownEdit.QueryData || { };

		if(typeof QueryData[QueryUrl] == 'object') {
			this.SetValues(QueryData[QueryUrl]);
		} else {
			document.body.observe('pk:DropDownEdit.QueryDataAvailable', function(e) {
				if(e.memo.QueryUrl == QueryUrl)
					this.SetValues(e.memo.Items);
			}.bind(this));

			if(QueryData[QueryUrl] != 1) {
				QueryData[QueryUrl] = 1;
				pkAjax.Request(QueryUrl, {
					onComplete: function(resultCode, hReturnData) {
						$A(hReturnData.items).each( function(hItem) {
							hItem.Display = hItem.Display || hItem.Title;
							hItem.SearchTitle = (hItem.SearchTitle || hItem.Display).toLowerCase();
							hItem.Selectable = hItem.Selectable == undefined ? true : hItem.Selectable;
						} );
						QueryData[QueryUrl] = hReturnData;
						document.body.fire('pk:DropDownEdit.QueryDataAvailable', { QueryUrl: QueryUrl, Items: hReturnData.items } );
					}
				});
			}
			DropDownEdit.QueryData = QueryData;
		}
	},
	SetValues: function(Items) {
		// Called when the values have been retrieved from the url
		this.Items = Items;
		if(this.SelectedItemID) {
			var hItem = $A(this.Items).find(function(item) {
				if(item.ItemID == this.SelectedItemID)
					return true;
				return false;
			}.bind(this));
			if(hItem) {
				this.SetSelection(hItem);
				return;
			}
		}
		this.ClearSelection();
	},
	FindItemByItemID: function(ItemID) {
		return this.Items.find( function(Item) {
			return Item.ItemID == ItemID;
		} );
	},
	SetSelection: function(hItem) {
		this.value = hItem.Title;
		this.SelectedItemID = hItem.ItemID;
		this.SelectedItem = hItem;
		this.setStyle( { textDecoration: 'underline', color: hItem.Selectable ? 'blue' : 'red' } );
		this.fire('change');
	},
	ClearSelection: function() {
		this.value = this.SelectedItemID = '';
	},
	_Focus: function(e) {
		Event.observe(document.body, 'keyup', this.__KeyUp);
		Event.observe(document.body, 'keydown', this.__KeyDown);
		this.LastInputValue = undefined;
		this.SelectedItemID = '';
		this.setStyle( { textDecoration: '', color: 'black' } );
		this.select();
		this.DisplayResults();
	},
	_Blur: function(e) {
		Event.stopObserving(document.body, 'keyup', this.__KeyUp);
		Event.stopObserving(document.body, 'keydown', this.__KeyDown);
		this.Select(this.LastHilighted);
		this.setStyle( { textDecoration: 'underline', color: this.SelectedItemID ? 'blue' : 'red' } );
	},
	_KeyDown: function(e) {
		switch(e.keyCode) {
			case 13:	// DOM_VK_ENTER = 14 for some reason in FF
				this.Select(this.LastHilighted);
				break;
			case 9:		//	e.DOM_VK_TAB	IE Doesn't seem to have e.DOM_VK_TAB
				if(this.DisplayResultsTimerID == undefined)
					this.Select(this.LastHilighted);
				else
					this.SelectAfterUpdate = true;
				return;
			default:
				return;
		}
		Event.stop(e);
	},
	_KeyUp: function(e) {
		Event.stop(e);
		switch(e.keyCode) {
			case 38:	//	e.DOM_VK_UP		IE Doesn't seem to have e.DOM_VK_UP
				if(this.LastHilighted && this.LastHilighted.previous())
					this.Hilight(this.LastHilighted.previous());
				else
					this.Hilight(this.DropDown.childElements().last());
				break;
			case 40:	//	e.DOM_VK_DOWN	IE Doesn't seem to have e.DOM_VK_DOWN
				if(this.LastHilighted && this.LastHilighted.next())
					this.Hilight(this.LastHilighted.next())
				else
					this.Hilight(this.DropDown.childElements()[0]);
				break;
			default:
				clearTimeout(this.DisplayResultsTimerID);
				this.DisplayResultsTimerID = setTimeout(this.DisplayResults.bind(this), 50);
				return;
		}
		Event.stop(e);
	},
	DisplayResults: function() {
		this.DisplayResultsTimerID = undefined;

		if(this.value != this.LastInputValue) {
			this.LastInputValue = this.value;
			var tMatches = this.FindMatches(this.value);

			if(tMatches.length > 0) {
				this.DropDown.childElements().each( function(Element, index) {
					if(index < tMatches.length) {
						Element.innerHTML = tMatches[index].Display;
						Element.hItem = tMatches[index];
						Element.show();
					} else
						Element.hide();
				} );

				pos = this.positionedOffset();
				this.DropDown.setStyle( {
					top:			pos.top + this.offsetHeight +'px',
					left:			pos.left + 'px'
				});

				var Hilight = 0;
				// Find Closest Match for exact numbers
				if(!isNaN(parseInt(this.value))) {
					for(var j=0;j<tMatches.length;j++) {
						if(tMatches[j].SearchTitle.match(/\d+/) == this.value) {
							Hilight = j;
							break;
						}
					}
				}
				this.Hilight(this.DropDown.childElements()[Hilight]);

				this.DropDown.show();
			} else {
				this.DropDown.hide();
			}
		}
		if(this.SelectAfterUpdate == true)
			this.Select(this.LastHilighted);
		this.SelectAfterUpdate = false;
	},
	FindMatches: function(value) {
		if(value) {
			value = value.toLowerCase();
			if(!this.hCache[value]) {
				var tMatch = value.split(' ');
				var tMatches = [ ];
				for(var j=0;j<this.Items.length;j++) {
					var item = this.Items[j];
					var Matches=0;
					for(var k=0;k<tMatch.length;k++) {
						if(item.Selectable && item.SearchTitle.fastSearch(tMatch[k]))
							Matches++;
					}
					if(Matches == tMatch.length)
						tMatches.push(item);
				}
				this.hCache[value] = this.SortResults(tMatches);
			}
			return this.hCache[value];
		}
		return [ ];
	},
	_MouseDown: function(e) {
		var Selection = e.element();
		if(Selection.hItem) {
			this.Hilight(Selection);
			this.Select(Selection);
		}
	},
	SortResults: function(tMatches) { return tMatches; },
	Hilight: function(Selection) {
		if(this.LastHilighted != undefined)
			this.LastHilighted.removeClassName('Selected');
		this.LastHilighted = Selection;
		if(Selection)
			Selection.addClassName('Selected');
	},
	Select: function(Selection) {
		if(this.DropDown.visible()) {
			if(Selection.hItem)
				this.SetSelection(Selection.hItem);
			else
				this.ClearSelection();

			this.LastInputValue = this.value;
			this.DropDown.hide();
		}
	}
} );

pkDateSelectWindow = {
	hData: {
		DaysInMonth: [ 31,28,31,30,31,30,31,31,30,31,30,31 ],
		DayHeaders:  [ 'S','M','T','W','T','F','S' ],
		Months:		 [ 'January','February','March','April','May','June','July','August','September','October','November','December' ]
	},
	Show: function(sDate, Options) {
		this.Options = Object.extend({
			classWindow: 'pkDateSelectWindow'
		}, Options);
		if(!this.eDateWindow) {
			this._FocusHandler = this._Focus.bindAsEventListener(this);
			this._ClickHandler = this.Click.bindAsEventListener(this);
			this.eDateWindow = CreateElement('div', { }, { position: 'absolute', zOrder: '999' }, { } );
			new Insertion.Top(this.eDateWindow, '<table><tr><td><img></td><td colspan="5"></td><td><img></td></tr><tr><td></td><td></td><td></td><td></td><td></td><td></td><td></td></tr></table>');
			this.eTable = $S(this.eDateWindow, 'table')[0];
			eTableBody = this.eTable.childNodes[0];
			for(var j=0;j<6;j++)
				eTableBody.appendChild(eTableBody.childNodes[1].cloneNode(true));

			this.tRows = $S(eTableBody, 'tr');
			this.eHeader = $S(eTableBody, 'tr[0]/td[1]')[0];
			$S(eTableBody, 'tr[0,1]', { _className: 'Header' });
			$S(this.tRows[0], 'td[0,2]/img', [
				{ 	cursor: 'pointer',
					_src: '/res/img/icon.left_green_arrow.gif',
					__function: function(elem) {
						Event.observe(elem, 'mousedown', this._Previous.bind(this, false));
					}.bind(this) },
				{ 	cursor: 'pointer',
					_src: '/res/img/icon.right_green_arrow.gif',
					__function: function(elem) {
						Event.observe(elem, 'mousedown', this._Next.bind(this, false));
					}.bind(this) }
			] );
			$S(this.tRows[1], 'td').each( function(eCell, index) {
				eCell.innerHTML = this.hData.DayHeaders[index];
			}.bind(this) );

			this._MouseUpHandler = this._MouseUp.bind(this);
		} else {
			this.Hide();
			if(this.eDateWindow.parentNode)
				this.eDateWindow.remove();
		}
		Event.observe(document, 'focus', this._FocusHandler, true);
		Event.observe(document, 'click', this._ClickHandler, true);
		this.eDateWindow.className = this.Options.classWindow;
		this.SetDate(sDate);
		var pos = this.Options.eInput.cumulativeOffset();
		this.eDateWindow.setStyle( {
			top:	pos.top + 'px',
			left:	pos.left + this.Options.eInput.offsetWidth + 1 + 'px'
		} );
		pkWindowMgr.FindLayer(this.Options.eInput).appendChild(this.eDateWindow);
		return this._eDateWindow;
	},
	SetDate: function(sDate) {
		var oDate = new Date();
		if(arguments.length == 1 && Date.parse(arguments[0]))
			oDate.setTime(Date.parse(arguments[0]));
		else if(arguments.length == 3) {
			while(arguments[0] < 0) {
				arguments[0] += 12;
				arguments[2]--;
			}
			while(arguments[0] > 11) {
				arguments[0] -= 12;
				arguments[2]++;
			}
			oDate.setMonth(arguments[0]);
			oDate.setDate(arguments[1]);
			oDate.setFullYear(arguments[2]);
		}
		this.eHeader.innerHTML = this.hData.Months[oDate.getMonth()]+', '+oDate.getFullYear();
		this.nMonth = oDate.getMonth();		this.nDay = oDate.getDate();	this.nYear = oDate.getFullYear();
		var oToday = new Date();
		if(oToday.getMonth() == this.nMonth && oToday.getFullYear() == this.nYear)
			var nTodayDay = oToday.getDate();
		var tMonthInfo = this._MonthInfo(oDate.getMonth()+1, oDate.getFullYear());
		var nCell = 0;
		for(var row = 2; row < 8; row++) {
			for(var col = 0; col < 7; col++) {
				if(nCell >= tMonthInfo.FirstDay && nCell < tMonthInfo.FirstDay + tMonthInfo.MaxDays) {
					var innerHTML = nCell - tMonthInfo.FirstDay + 1;
					var className = 'Valid';
					if(nCell - tMonthInfo.FirstDay + 1 == this.nDay)
						className = className + ' Selected';
					if(nCell - tMonthInfo.FirstDay + 1 == nTodayDay)
						className = className + ' Today';
				} else {
					var innerHTML = '';
					var className = '';
				}
				this.tRows[row].childNodes[col].className = className;
				this.tRows[row].childNodes[col].innerHTML = innerHTML;
				nCell++;
			}
		}
	},
	_Previous: function(bTimeout) {
		if(this._MouseDown != true) {
			if(bTimeout == false) {
				this._MouseDown = true;
				this.nCount = 0;
				Event.observe(document, 'mouseup', this._MouseUpHandler);
			} else
				return;
		}
		this.SetDate(this.nMonth - (this.nCount < 24 ? 1 : 12), this.nDay, this.nYear);
		this.nCount++;
		setTimeout(this._Previous.bind(this, true), 500 / this.nCount);
	},
	_Next: function(bTimeout) {
		if(this._MouseDown != true) {
			if(bTimeout == false) {
				this._MouseDown = true;
				this.nCount = 0;
				Event.observe(document, 'mouseup', this._MouseUpHandler);
			} else
				return;
		}
		this.SetDate(this.nMonth + (this.nCount < 24 ? 1 : 12), this.nDay, this.nYear);
		this.nCount++;
		setTimeout(this._Next.bind(this, true), 500 / this.nCount);
	},
	_MouseUp: function() {
		this._MouseDown = false;
		Event.stopObserving(document, 'mouseup', this._MouseUpHandler);
	},
	Hide: function() {
		Event.stopObserving(document, 'click', this._ClickHandler, true);
		if(this.eDateWindow.parentNode)
			this.eDateWindow.remove();
	},
	_Focus: function(e) {
		target = $(e.target ? e.target: e.srcElement);
		if(this.Options.onBlur && target.nodeType == 1 && (target.tagName == 'INPUT' || target.tagName == 'SELECT'))
			this.Options.onBlur(target);
	},
	Click: function(e) {
		target = $(e.target ? e.target: e.srcElement);
		if(this.HitTest(target)) {
			if(target.className.search('Valid') >= 0) {
				if(this.Options.onSelectDate)
					this.Options.onSelectDate(this.nMonth+1, target.innerHTML, this.nYear);
			}
		} else {
			if(this.Options.onBlur)
				this.Options.onBlur(target);
		}
	},
	HitTest: function(htmElem) { return htmElem.childOf(this.eDateWindow) || htmElem == this.eDateWindow; },
	_MonthInfo: function(Month, Year) {
		var DaysInMonth = this.hData.DaysInMonth[Month-1];
		if(Month-1 == 1 && (2004 - Year) % 4 == 0)
			DaysInMonth++;
		var Day = 1;
		var a = Math.floor((14 - Month) / 12);
		var y = Year - a;
		var m = Month + 12 * a - 2;
		var d = (Day + y + Math.floor(y / 4) - Math.floor(y / 100) +
				 Math.floor(y / 400) + Math.floor((31 * m) / 12))  % 7;

		return { MaxDays: DaysInMonth, FirstDay: d };
	}
}

pkDateSelect = Class.create();

pkDateSelect.prototype = {
	initialize: function(InputID, Options) {
		this.Options = Object.extend( { srcButtonImage: '/res/img/icon.calendar.gif', classWindow: 'pkDateSelectWindow' }, Options);
		this.eInput = $(InputID);
		if(this.eInput) {
			this.eInput.parentNode.style.whiteSpace = 'nowrap';
			this.eButton = CreateElement('img', { _src: this.Options.srcButtonImage, style: 'vertical-align: top;' }, { cursor: 'pointer', marginLeft: '4px' }, { insertAfter: this.eInput} );
			Event.observe(this.eButton, 'click', this._ButtonClick.bindAsEventListener(this));
			Event.observe(this.eInput, 'focus', this.Show.bind(this, true));
		} else
			pkAjax.ReportError("pkDateSelect: Unable to locate input element ("+InputID+")");
	},
	_ButtonClick: function(e) {
		Event.stop(e);
		this.Show(true);
	},
	GetControl: function() { return this.eInput; },
	Show: function(bShow) {
		if(bShow) {
			pkDateSelectWindow.Show(this.eInput.value, {eInput: this.eInput, classWindow: this.Options.classWindow, onSelectDate: this.onSelectDate.bind(this), onBlur: this.onWindowBlur.bind(this) } );
		} else {
			pkDateSelectWindow.Hide();
		}
	},
	onWindowBlur: function(target) {
		if(target != this.eInput)
			this.Show(false);
	},
	onSelectDate: function(Month, Day, Year) {
		this.eInput.value = Number(Month).toPaddedString(2) + '/' + Number(Day).toPaddedString(2) + '/' + Number(Year).toPaddedString(2);
		this.Show(false);
		this.eInput.fire('change');
	}
}

pkTabBar = Class.create();

pkTabBar.prototype = {
	initialize: function(ElementID, hOptions) {
		this.htmlContainer = $(ElementID);
		this.hOptions = hOptions || { };
		this.tTabs = [ ];
		var nMaxHeight = 0;
		$A(this.htmlContainer.childNodes).each(function(htmlElement) {
			if(htmlElement.nodeType == 1 && htmlElement.tagName.toLowerCase() == 'div') {
				htmlElement = $(htmlElement);
				var hTab = {
					TabTitle	: htmlElement.getAttribute('tabtitle'),
					TabStyle	: htmlElement.getAttribute('tabstyle'),
					TabView		: htmlElement
				};
				htmlElement.addClassName('TabView');

				// Find maximum vertical height
				var nElemHeight = htmlElement.offsetHeight - (
					parseInt(htmlElement.getStyle('padding-top')) +
					parseInt(htmlElement.getStyle('padding-bottom')));

				if(nElemHeight > nMaxHeight)
					nMaxHeight = nElemHeight;

				htmlElement.style.display = 'none';
				this.tTabs.push(hTab);
			}
		}.bind(this));
		this.htmlTabBar = CreateElement('div', { _className: 'TabBar' }, { }, { insertChild: this.htmlContainer } );
		this.tTabs.each( function(hTab, nIndex) {
			hTab.htmlTab = CreateElement('div', { _className: 'TabButton' }, { }, { appendChild: this.htmlTabBar } );
			Event.observe(hTab.htmlTab, 'mousedown', this.Select.bind(this, nIndex));
			hTab.htmlTab.innerHTML = hTab.TabTitle;
			hTab.TabView.style.height = nMaxHeight + 'px';
		}.bind(this));
		this.htmlTabBar.style.height = this.tTabs[0].htmlTab.offsetHeight-1 + 'px';
		this.Select(0);
	},
	Select: function(Tab) {
		var hTab;
		if(isNaN(TabIndex = parseInt(Tab))) {
			Tab = Tab.toLowerCase();
			hTab = this.tTabs.find(function( hTab ) {
				if(hTab.TabTitle.toLowerCase() == Tab)
					return true;
			});
		} else
			hTab = this.tTabs[Tab];
		if(this.hTabSelected) {
			this.hTabSelected.htmlTab.removeClassName('Selected');
			this.hTabSelected.TabView.style.display = 'none';
			this.hTabSelected.TabView.fire('blur');
		}
		if(hTab) {
			hTab.htmlTab.addClassName('Selected');
			this.hTabSelected = hTab;
			hTab.TabView.style.display = '';
			this.hTabSelected.TabView.fire('focus');
		}
	}
}

/*	pkEditList takes a string, seperated by a character (default is semicolon), splits the string into its parts
 *		and presents a scrollable, editable list of those strings.  It will always keep the <INPUT> source up to
 *		date if specified.  You should observe the <INPUT> for 'change' events to monitor its activity
 */
var pkEditList = Class.create();
pkEditList.prototype = {
	AddItemText		: 'Add Item...',

	initialize: function(htmInput, Options) {
		this.Options = Object.extend( {
			Separator:		';',
			Class:			'EditList'
		}, Options);
		this.htmInput = $(htmInput);
		this.htmList = HTMLtoDOM('<div class="'+this.Options.Class+'"></div>');
		this.htmList.objControl = this;
		if(this.Options.Width)
			this.htmList.style.width = this.Options.Width + 'px';
		this.htmInput.parentNode.insertBefore(this.htmList, this.htmInput);
		this.htmInput.SetValue = this.SetValue.bind(this);

		this.htmInput.style.display = 'none';
		/* Install Convert To Control */
		Event.observe(this.htmInput, 'click', function(e) {
			if(e.altKey) {
				this.ShowControl(true);
				return Event.stop(e);
			}
		}.bind(this) );

		this.SetValue(this.htmInput.value);
		Event.observe(this.htmList, 'click', this.onClick.bindAsEventListener(this));

		this.htmHoverInput = CreateElement('input', { _type: 'text', _objControl: this }, { padding: '2px 0px 0px 4px', zIndex: 99, position: 'absolute', display: 'none', fontSize: this.htmList.getStyle('fontSize'), fontFamily: this.htmList.getStyle('fontFamily') }, { appendChild: this.htmList } );
		this.htmHoverInput.Close = function() {
			if(this.htmElement) {
				if(this.value == '')
					this.objControl.DeleteItem(this.htmElement);
				else {
					try {
						if(this.htmElement.next('div').down('span').hasClassName('NewItem'))
							var htmEditChain = this.htmElement.next('div');
					} catch(e) { }
				}
				this.htmElement = undefined;
				this.style.display = 'none';
				if(htmEditChain)
					this.objControl.EditItem(htmEditChain);
			}
		}
		Event.observe(this.htmHoverInput, 'blur', this.htmHoverInput.Close );
		Event.observe(this.htmHoverInput, 'change', function() {
			if(this.htmElement)
				this.objControl.SetItem(this.htmElement, this.value);
		}.bind(this.htmHoverInput) );
		Event.observe(this.htmHoverInput, 'keydown', function(e) {
			switch(e.keyCode) {
				case 13:	// Enter
					this.Close();
					Event.stop(e);
					return false;
			}
		}.bind(this.htmHoverInput) );
	},
	GetControl: function() {	return this.htmList;	},
	/* Creates a new list item */
	CreateItem: function(Item) {
		Item == ''
			? Item = '<img style="display: none;" src="/res/img/icon.close.12.gif"><span class="NewItem">'+this.AddItemText+'</span>'
			: Item = '<img src="/res/img/icon.close.12.gif"><span>'+Item+'</span>';
		var htmItem = CreateElement('div', { _innerHTML: Item }, { }, { appendChild: this.htmList } );
	},
	/* Delete the specifie list item */
	DeleteItem: function(htmElement) {	htmElement.remove(); this.UpdateControl(); },
	/* Set the value of the specified list item */
	SetItem: function(htmElement, Item) {
		htmElement.down('span').innerHTML = Item;
		if(htmElement.down('span').hasClassName('NewItem') && Item != this.AddItemText) {
			this.CreateItem('');
			htmElement.down('span').removeClassName('NewItem');
			htmElement.down('img').style.display = '';
		}
		this.UpdateControl();
	},
	/* Handle click events for the control */
	onClick: function(e) {
		var htmElement = Event.element(e);
		switch(htmElement.tagName) {
			case 'IMG':
				this.DeleteItem(htmElement.up());
				break;
			case 'SPAN':
				if(e.altKey && htmElement.hasClassName('NewItem')) {
					this.ShowControl(false);
					return Event.stop(e);
				}
				htmElement = htmElement.up();
			case 'DIV':
				if(htmElement != this.htmList)
					this.EditItem(htmElement);
				break;
		}
	},
	/* Show or hide the control (revealing or hiding the text field) */
	ShowControl: function(bShow) {
		if(bShow) {
			this.htmInput.style.display = 'none';
			this.htmList.style.display = '';
			this.SetValue(this.htmInput.value);
		} else {
			this.htmList.style.display = 'none';
			this.htmInput.style.display = '';
			this.htmInput.select();
			this.htmInput.focus();
		}
	},
	/* Starts an edit on the htmElement item */
	EditItem: function(htmElement) {
		this.htmHoverInput.value = htmElement.down('span').innerHTML.unescapeHTML();
		this.htmHoverInput.setStyle( {
			display		: '',
			top			: htmElement.offsetTop + 1 + 'px',
			width		: this.htmList.down('span').offsetWidth + 20 + 'px'
		} );
		this.htmHoverInput.htmElement = htmElement;
		this.htmHoverInput.focus();
		this.htmHoverInput.select();
	},
	/* Get the value of the list (string seperated by this.Options.Separator) */
	GetValue: function() {
		var x= $S(this.htmList, 'div/span')
			.pluck('innerHTML')
			.without(this.AddItemText)
			.map( function( item ) { return item.unescapeHTML().replace(this.Options.Separator, '\\'+this.Options.Separator);	}.bind(this) )
			.join(this.Options.Separator);
		return x;
	},
	/* Set the value, takes a string separated by this.Options.Separator) */
	SetValue: function(value) {
		$S(this.htmList, 'div').invoke('remove');
		value.split(/\s*;\s*/).each( function(Item) {
			if(Item != '')
				this.CreateItem(Item);
		}.bind(this) );
		this.CreateItem('');
		this.htmInput.value = this.GetValue();
		this.htmInput.fire('change');
	},
	/* Collects the items in the list and creates a seperated string, assigning the value to the input element */
	UpdateControl: function() {
		this.htmInput.value = this.GetValue();
		this.htmInput.fire('change');
	}
}

pkCheckboxPanel = Class.create();

pkCheckboxPanel.prototype = {
	initialize: function(htmContainer) {
		this.htmContainer = $(htmContainer);
		this.htmSummary = $($S(htmContainer, 'span[0]')[0]);
		this.htmPanel = $S(htmContainer, 'div[0]')[0];
		this.Options = {
			MaxLabelLength:		this.htmContainer.getAttribute('MaxLabelLength') || 40,
			StaticLabel:		this.htmContainer.getAttribute('StaticLabel')
		};

		if(this.htmPanel) {
			new Insertion.Bottom(this.htmPanel, '<div style="clear: both; float: none;">&nbsp;</div><input style="margin-top: 5px;" type="button" Action="ToggleAll" value="Select All">');
			this._ClickHandler = this._Click.bindAsEventListener(this);
			if(this.htmSummary.childNodes[0].nodeType != 3)
				this.htmSummary.insertBefore(document.createTextNode(' '), this.htmSummary.childNodes[0]);
			Event.observe(this.htmSummary, 'click', this._ClickButton.bindAsEventListener(this));
			this.UpdateLabel();
		} else {
			return pkAjax.ReportError('Unable to locate container for CheckboxPanel');
		}
	},
	_ClickButton: function(e) {
		if(!this.htmSummary.hasClassName('Down'))
			this.OpenPanel();
		else
			this.ClosePanel();
		Event.stop(e);
	},
	_Click: function(e) {
		var htmElement = Event.element(e);
		if(!htmElement.descendantOf(this.htmPanel) && htmElement != this.htmPanel && htmElement != this.htmSummary)
			this.ClosePanel();
		else if(htmElement.type == 'button') {
			if(htmElement.getAttribute('Action') == 'ToggleAll') {
				this.SelectAll(htmElement.value == 'Select All');
				htmElement.value = 	htmElement.value == 'Select All'
									? 'Unselect All'
									: 'Select All';
				this.UpdateLabel();
			}
		} else {
			this.UpdateLabel();
		}
	},
	SelectAll: function(bCheck) {
		var tSelected = this.htmPanel.select('input[type=checkbox]').each( function(item) {
			item.checked = bCheck;
		} );
	},
	OpenPanel: function() {
		this.htmSummary.addClassName('Down');
		this.htmPanel.SetVisibility('','visible').SetOpacity(.9);
		Event.observe(window, 'click', this._ClickHandler);
	},
	ClosePanel: function() {
		this.htmSummary.removeClassName('Down');
		this.htmPanel.SetVisibility('none','hidden');
		Event.stopObserving(window, 'click', this._ClickHandler);
		this.UpdateLabel();
	},
	UpdateLabel: function() {
		if(this.Options.StaticLabel) {
			this.htmSummary.addClassName('Dimmed');
			this.htmSummary.childNodes[0].nodeValue = this.Options.StaticLabel;
		} else {
			var tSelected = this.htmPanel.select('input[type=checkbox]')
				.reject( function(htmCheckbox) { return !htmCheckbox.checked; } )
				.map( function(htmCheckbox) { return htmCheckbox.parentNode.lastChild.nodeValue; } );
			if(tSelected.length) {
				this.htmSummary.removeClassName('Dimmed');
				var sLabel = tSelected.join(', ');
				if(sLabel.length > this.Options.MaxLabelLength)
					sLabel = sLabel.substr(0, this.Options.MaxLabelLength)+'...';
				this.htmSummary.childNodes[0].nodeValue = sLabel;
			} else {
				this.htmSummary.addClassName('Dimmed');
				this.htmSummary.childNodes[0].nodeValue = 'None Selected';
			}
		}
	}
}

/**
* 	pkTextarea extends standard <textarea> elements to include resizing capability
*/
var pkTextarea = Class.create();
pkTextarea.prototype = {
	initialize: function(htmElement) {
		this.htmElement = $(htmElement);
		this.htmElement.up().relativize();
		this.htmExpand = this.htmElement.next();
		if(!this.htmExpand || this.htmExpand.tagName != 'HR') {
			var Dimensions = this.htmElement.getDimensions();

			this.htmElement.insert( {after: '<hr align="left" style="height: 3px; cursor: s-resize; width: '+Dimensions.width+'px;">'});
			this.htmExpand = this.htmElement.next('hr');
		}

		var Offset = this.htmElement.positionedOffset();

		this._onMouseUpHandler = this.onMouseUp.bindAsEventListener(this);
		this._onMouseMoveHandler = this.onMouseMove.bindAsEventListener(this);
		Event.observe(this.htmExpand, 'mousedown', this.onMouseDown.bindAsEventListener(this));

//		var CookieHeight = getCookie('Prefs[TextareaHeight]['+this.htmElement.name+']');
//		if(!isNaN(parseInt(CookieHeight)))
//			this.SetHeight(parseInt(CookieHeight));
//		else
		this.SetHeight(parseInt(this.htmElement.getAttribute('rows'))*15);
		if(this.htmElement.getAttribute('previewurl')) {
			this.htmElement.insert( {before: '<input style="border: 1px outset black" type="button" value="Preview">'} );
			this.htmPreviewButton = this.htmElement.previous();
			this.htmPreviewButton.absolutize();
			this.htmPreviewButton.style.height = 'auto';
			this.htmPreviewButton.style.top = Offset.top - this.htmPreviewButton.getHeight() - 3 + 'px';
			this.htmPreviewButton.style.left = Offset.left + (this.htmElement.getWidth() - this.htmPreviewButton.getWidth()) + 'px';
			Event.observe(this.htmPreviewButton, 'click', this.TogglePreview.bind(this));

			this.htmElement.insert( {before: '<iframe frameborder="0" name="if'+Math.floor(Math.random()*99999)+'" style="border: 1px solid black; display: none;">'} );
			this.htmPreview = this.htmElement.previous();
			this.htmPreview.absolutize();
			this.htmPreview.clonePosition(this.htmElement, { setWidth: false, setHeight: false } );
			this.htmPreview.style.height = this.htmElement.getHeight() - 2 + 'px';
			this.htmPreview.style.width = this.htmElement.getWidth() + 'px';
			this.htmPreview.observe('load', this.onPreviewLoaded.bindAsEventListener(this));
		}
	},
	TogglePreview: function() {
		if(this.htmPreviewButton.style.border == '1px outset black') {
			this.htmPreviewButton.style.border = '1px inset black';
			this.htmElement.style.visibility = 'hidden';
			this.PostPreview();
		} else {
			this.htmPreviewButton.style.border = '1px outset black';
			this.htmElement.style.visibility = 'visible';
		}
		this.htmPreview.toggle();
	},
	PostPreview: function() {
		var FullMatch, Url, Params, HTML;

		[FullMatch, Url, Params] = this.htmElement.getAttribute('PreviewURL').match(/^(.+)\?(.+)$/);
		HTML = '<form style="display: none;" target="'+this.htmPreview.name+'" method="post" action="'+Url+'">';

		HTML = HTML + Params.split('&').inject("", function(acc, Pair) {
			[Name, Value] = Pair.split('=');
			return acc + '<INPUT TYPE="hidden" name="'+Name+'" value="'+Value+'">';
		}, this);
		HTML = HTML + '</form>';

		this.htmPreview.insert({ before: HTML });
		this.htmForm = this.htmPreview.previous();
		this.htmForm.down('INPUT[Value=""]').value = this.htmElement.value;
		this.htmForm.submit();
	},
	onPreviewLoaded: function(e) {
		if(this.htmForm)
			this.htmForm.remove();
	},
	onMouseDown: function(e) {
		Event.observe(window, 'mouseup', this._onMouseUpHandler);
		Event.observe(window, 'mousemove', this._onMouseMoveHandler);
		Event.stop(e);
		this.MouseDownY = e.clientY;
		this.MouseDownHeight = this.htmElement.offsetHeight;
	},
	onMouseMove: function(e) {
		this.SetHeight(e.clientY - this.MouseDownY + this.MouseDownHeight - 6);
		Event.stop(e);
	},
	onMouseUp: function(e) {
		Event.stopObserving(window, 'mousemove', this._onMouseMoveHandler);
		Event.stopObserving(window, 'mouseup', this._onMouseUpHandler);
		Event.stop(e);
	},
	SetHeight: function(Height) {
		if(Height < 15)
			Height = 15;
		this.htmExpand.style.top = Height - this.htmExpand.getHeight() + 5 + 'px';
		this.htmElement.style.height = Height+'px';
		this.htmElement.setStyle( { overflowX: ( Height < 30 ? 'hidden' : 'auto' ) } );
		if(this.htmPreview)
			this.htmPreview.style.height = Height+4+'px';
		setPageCookie('Prefs[TextareaHeight]['+this.htmElement.name+']', Height);
	},
	GetControl: function() {	return this.htmElement;	}
}

var pkTextInput = Class.create( {
	SimulatePlaceholder:	false,

	initialize: function() {
		this.observe('blur', this.onBlur.bindAsEventListener(this));
		this.observe('focus', this.onFocus.bindAsEventListener(this));
		/*
		if(!navigator.userAgent.match(/Firefox\/4/) && this.getAttribute('Placeholder')) {
			this.SimulatePlaceholder = true;
			this.Placeholder = this.insert( { before: '<div class="PlaceholderOverlay" style="visibility: hidden;"><div>'+this.getAttribute('Placeholder')+'</div></div>' } ).previous().down();
			this.Placeholder.absolutize();
			this.Placeholder.clonePosition(this);
			this.Placeholder.observe('click', function() { this.focus(); }.bind(this));
			this.SetPlaceholder();
		}*/
	},
	ClearPlaceholder: function() {
		if(this.SimulatePlaceholder && this.value == '')
			this.Placeholder.up().style.visibility = 'hidden';
	},
	SetPlaceholder: function() {
		if(this.SimulatePlaceholder && this.value == '')
			this.Placeholder.up().style.visibility = 'visible';
	},
	onFocus: function(e) { this.ClearPlaceholder(); },
	onBlur: function(e) { this.SetPlaceholder(); }
} );

var pkObject = Class.create({
	LoadPriority:				-99,	/* Load First */
	ObjectID: {
		get: function() 	{ return this.GetObjectID(); }
	},
	PrimaryID: {
		get: function()	{ return this.GetPrimaryID(); }
	},

	initialize: function() {
		if(!this.getAttribute('objectid'))
			throw 'pkObject elements must have an ObjectID';
	},
	GetObjectID: function() { return this.getAttribute('objectid'); },
	GetPrimaryID: function() { return this.getAttribute('objectid').replace(/^[^\/]+\//, ''); }
} );

var pkStatusObject = Class.create( pkObject, {
	Status: {
		get: function()		{ return this.GetStatus(); },
		set: function(x)	{ return this.SetStatus(x); }
	},
	UpdateStatusUrl:			'/?Command=SetStatus&AjaxCommand=SetStatus&Status={Status}&ObjectID={ObjectID}',
	StatusOptions:				'Enabled,Disabled,Deleted',
	initialize: function($super) {
		$super();
		this.UpdateStatusUrlTemplate = new pkTemplate(this.UpdateStatusUrl);
		this.StatusOptions = this.getAttribute('statusoptions') || this.StatusOptions;
		this.observe('pk:StatusChange', this.onStatusChange.bindAsEventListener(this));
	},
	ToggleStatus: function() {
		var Status, tToggle = this.StatusOptions.split(',');
		tToggle.each(function(Value, Index) {
			if(this.Status == Value) {
				Status = tToggle[Index+1];
				throw $break;
			}
		}, this);
		this.Status = Status || tToggle[0];
	},
	GetStatus: function() { return this.getAttribute('status'); },
	SetStatus: function(Status) {
		pkAjax.Request(this.UpdateStatusUrlTemplate.evaluate({ Status: Status, ObjectID: this.ObjectID }), {
			onComplete: function(ResultCode, hData) {
				pkAjax.ShowQueuedMessages();
				this.setAttribute('Status', hData.Status);
				this.FireStatusChanged();
			}.bind(this)
		});
	},
	onStatusChange: function(e) {
		this.setAttribute('Status', e.memo.Status);
	},
	FireStatusChanged: function() {
		if(this.getAttribute('Status') == 'Destroyed')
			this.DestroyedEffect();

		var StatusElement = this.down('.Status');
		if(StatusElement)
			StatusElement.innerHTML = this.getAttribute('Status');

		this.fire('pk:StatusChange', { Status: this.Status });
	}
} );

var pkButton = Class.create( {
	initialize: function($super) {
		if(!Prototype.Browser.IE && (this.type == '' || this.type == 'text' || this.type == undefined))
			this.type = 'button';
		this.observe('click', this.onClick.bind(this));
		if(Object.isFunction($super))
			$super();
	},
	onClick: function(e) { }
} );

var ToggleButton = Class.create(pkButton, {
	CheckedStyle: 	'border: 1px inset black;',
	UncheckedStyle: 'border: 1px outset black;',
	value: {
		get: function() {
			if(this.Checked)
				return this.getAttribute('Value') || 1;
			return this.getAttribute('UncheckedValue') || 0;
		}
	},
	initialize: function($super) {
		this.CreateHiddenElement();
		this.SetChecked(this.getAttribute('checked') == 1);
		this.observe('change', this.onChange.bindAsEventListener(this));
		$super();
	},
	CreateHiddenElement: function() {
		if(this.name) {
			var Container = this.up();
			var HiddenElement = $A(document.getElementsByName(this.name))
					.detect( function(Element) { return Element.type == 'hidden' && Element.up() == Container; } );
			if( !HiddenElement ) {
				this.insert( { after: '<input type="hidden" name="'+this.name+'">' } );
				HiddenElement = this.next();
			} else {
				HiddenElement.remove();
				this.insert( { after: HiddenElement } );
			}
			this.ValueElement = HiddenElement;
		}
	},
	onClick: function() {
		this.SetChecked(!this.Checked);
	},
	onChange: function(e) { },
	SetChecked: function(Checked) {
		this.Checked = Checked;
		if(this.ValueElement) {
			this.ValueElement.value = this.Checked ? '1' : '0';
			this.ValueElement.fire('change');
		}
		this.setStyle( this.Checked ? this.CheckedStyle : this.UncheckedStyle );
		this.fire('change');
	}
} );

/** RadioToggleButton differs from ToggleButton in that any other elements with the
* 	same name will become unchecked when one gets checked
*/
var RadioToggleButton = Class.create(ToggleButton, {
	SelectedValue: {
		get: function() { return this.ValueElement.value; }
	},
	initialize: function($super) {
		this.UncheckedSrc = this.src;
		this.CheckedSrc = this.getAttribute('CheckedSrc') || this.src;
		$super();
	},
	onClick: function($super, e) {
		if(this.Checked)
			return;
		$super(e);
	},
	SetChecked: function($super, Checked) {
		this.Checked = Checked;
		this.setStyle( this.Checked ? this.CheckedStyle : this.UncheckedStyle );
		if(this.Checked) {
			this.src = this.CheckedSrc;
			this.ValueElement.value = this.value;
			var Container = this.up();
			$A(document.getElementsByName(this.getAttribute('name')))
				.findAll( function(element) { return element.up() == Container && element != this && element.SetChecked != undefined; }, this)
				.invoke( 'SetChecked', false);
			this.ValueElement.fire('change');
		} else
			this.src = this.UncheckedSrc;
	}
} );

/** Acts as an anchor tag but with an ajax request and response */
var AjaxButton = Class.create(pkButton, {
	onClick: function(e) {
		var Message = this.getAttribute('ConfirmationMessage');
		if(!Message || confirm(Message))
			pkAjax.Request(this.getAttribute('href'), { onComplete: this.onComplete.bind(this) } );
	},
	onComplete: function(resultCode, hData) { }
});

/** Handles ajax click-to-switch status buttons */
var AjaxStatusButton = Class.create(pkButton, {
	initialize:	function($super) {
		this.ObjectElement = this.FindParentObjectElement();
		this.ObjectElement.observe('pk:StatusChange', this.onStatusChange.bindAsEventListener(this));
		$super();
		this.value = this.ObjectElement.Status;
	},
	onClick: function(e) { this.ObjectElement.ToggleStatus();	},
	onStatusChange: function(e) { this.value = e.memo.Status; }
} );

/** Acts as an anchor tag but with an ajax request and response */
var AjaxDeleteButton = Class.create(pkButton, {
	initialize:	function($super) {
		this.ObjectElement = this.FindParentObjectElement();
		this.ObjectElement.observe('pk:StatusChange', this.onStatusChange.bindAsEventListener(this));
		$super();
		this.value = 'Delete';
	},
	onClick: function(e) {
		if(e.ctrlKey && e.altKey && confirm('Are you sure you want to destroy this object?')) {
			this.ObjectElement.Status = 'Destroyed';
		} else
			this.ObjectElement.Status = 'Deleted';
	},
	onStatusChange: function(e) {
		if(e.memo.Status == 'Destroyed')
			this.ObjectElement.DestroyedEffect();
		else if(e.memo.Status == 'Deleted')
			this.hide();
		else
			this.show();
	}
} );
/** Ties a date range drop down to two date fields, from/to.  All three input parameters are the NAME of the field to use */

var QuickDateSelect = Class.create({
	initialize: function(QuickName, FromName, ToName) {
		try { this.htmQuick = $(QuickName) || document.getElementsByName(QuickName)[0]; } catch(e) { console.log('QuickDateSelect: Unable to locate quick field by name: '+QuickName); }
		try { this.htmFrom = $(FromName) || document.getElementsByName(FromName)[0]; } catch(e) { console.log('QuickDateSelect: Unable to locate from field by name: '+FromName); }
		try { this.htmTo = $(ToName) || document.getElementsByName(ToName)[0]; } catch(e) { console.log('QuickDateSelect: Unable to locate to field by name: '+ToName); }

		this.htmQuick.select('option').each( function(Element) {
			Element.RealValue = Element.value;
			Element.value = Element.innerHTML;
		} );

		this.htmFrom.observe('keyup', this.UpdateQuickSelect.bind(this));
		this.htmFrom.observe('change', this.UpdateQuickSelect.bind(this));
		this.htmTo.observe('keyup', this.UpdateQuickSelect.bind(this));
		this.htmTo.observe('change', this.UpdateQuickSelect.bind(this));
		this.htmQuick.observe('click', this.UpdateDateFields.bind(this));
		this.htmQuick.observe('keyup', this.UpdateDateFields.bind(this));
		this.UpdateDateFields();
	},
	UpdateQuickSelect: function() {
		var SearchValue = this.htmFrom.value+':'+this.htmTo.value;
		for(var i=0;i<this.htmQuick.options.length;i++) {
			if(this.htmQuick.options[i].value == SearchValue) {
				this.htmQuick.selectedIndex = i;
				return;
			}
		}
		this.htmQuick.selectedIndex = this.htmQuick.options.length-1;
	},
	UpdateDateFields: function() {
		var SelectedValue = this.htmQuick.options[this.htmQuick.selectedIndex].RealValue;

		if(SelectedValue != 'Custom') {
			var tPair = SelectedValue.split(':');
			this.htmFrom.value = tPair[0];
			this.htmTo.value = tPair[1];
		}
	}
});
/**
*	pkTable extends a TABLE element with numerous functions and ability
*/


/**
*	Data Table Columns
*/
var DataTableColumn = Class.create( {
	SortArrowHTML:	'<img style="display: none;">',
	DefaultSortDir: 0,
	ColumnName: {
		get: function() { return this.getAttribute('ColumnName') || this.down('SPAN').textContent; }
	},
	SortDir: {
		get: function() { return this.SortArrow.src.indexOf('up_arrow') != -1;},
		set: function(x) {
			if(x == null)
				this.SortArrow.hide();
			else {
				this.SortArrow.src = x
					? '/res/img/icon.black_up_arrow.gif'
					: '/res/img/icon.black_down_arrow.gif';
				this.SortArrow.show();
			}
		}
	},

	/* Returns the index into a data row that corresponds to this column */
	DataColumnIndex: {
		get:  function() {	return DataTableColumns.GetColumnIndex(this); }
	},

	initialize: function() {
		this.Table = this.up('TABLE');
		if(this.down('span') != undefined) {
			this.down('span').insert( { after: this.SortArrowHTML } );
		} else
			this.update('<span>'+this.innerHTML+'</span>'+this.SortArrowHTML);

		this.SortArrow = this.select('img').last();
		this.down('span').observe('click', this.onClick.bindAsEventListener(this));
	},
	onClick: function() {
		this.Table.SortByColumn(this.ColumnName, true /* FlipSort */);
	},
	SortElements: function(Rows) {
		var DataColumnIndex = this.DataColumnIndex;

		var CollatedElements = Rows.inject( { Ignore: [], Sort: [] }, function(CollatedElements, Element) {
			Element.hasClassName('SortIgnore')
				? CollatedElements.Ignore.push(Element)
				: CollatedElements.Sort.push(Element);
			return CollatedElements;
		} );

		var Sorted = CollatedElements.Sort
			.map( function(Row) {
				if(Row.SubRows)
					Row.SubRows = this.SortElements(Row.SubRows);
				var Element = Row.childElements()[DataColumnIndex];
				return { Row: Row, Criteria: this.GetContent(Element.UnformattedElement || Element) };
			}, this).sort(function(left, right) {
				return NaturalSort(left.Criteria,right.Criteria);
			}).pluck('Row');

		if(this.SortDir == 1)
			Sorted = Sorted.reverse();

		return Sorted.concat(CollatedElements.Ignore);
	},
	GetContent: function(Element) {
		if(Element.UnformattedElement && Element.UnformattedElement.textContent)
			return Element.UnformattedElement.textContent;
		return Element.textContent;
	},
	MatchesData: function(DataCells) { throw 'DataTableColumn.MatchesData() is abstract'; }
} );

/** TextColumn is the default column type for non-specific columns */
var TextColumn = Class.create( DataTableColumn, {
	DefaultSortDir: 0,
	initialize: function($super) {
		$super();
		this.addClassName('Text');
	},
	MatchesData: function(DataCells) { return true; }
} );

/** Matches any columns which are a m/d/y date format */
var InputColumn = Class.create( DataTableColumn, {
	DefaultSortDir: 0,
	initialize: function($super) {
		$super();
		this.addClassName('Input');
	},
	GetContent: function(Element) { return Element.down('select, input').value; },
	MatchesData: function(DataCells) {
		return DataCells.any(function(Cell) {
			return Cell.down('select,input') != undefined;
		}, this );
	}
} );

/** Matches any columns which are a m/d/y date format */
var DateColumn = Class.create( DataTableColumn, {
	DefaultSortDir: 1,
	DatePattern:	/(^\d+\/\d+\/\d+$|Never)/,
	initialize: function($super) {
		$super();
		this.addClassName('Date');
	},
	GetContent: function(Element) {
		var o = new Date(Element.getAttribute('Title') || Element.textContent);
		if(o.toString() == 'Invalid Date')
			return new Date(0,0,0);
		return o;
	},
	MatchesData: function(DataCells) {
		return DataCells.any(function(Cell) {
			return String(Cell.getAttribute('Title')).match(this.DatePattern) || Cell.textContent.match(this.DatePattern);
		}, this );
	}
} );

/** Matches any columns which contain an IMG.DateInfo element */
var DateInfoColumn = Class.create( DataTableColumn, {
	DefaultSortDir: 1,
	DatePattern:	/(^\d+\/\d+\/\d+$|Never)/,
	initialize: function($super) {
		$super();
		this.addClassName('DateInfo');
	},
	GetContent: function(Element) {
		var o = new Date(Element.down('IMG.DateInfo').getAttribute('Title'));
		if(o.toString() == 'Invalid Date')
			return new Date(0,0,0);
		return o;
	},
	MatchesData: function(DataCells) {
		return DataCells.any(function(Cell) {
			return Cell.down('IMG.DateInfo') != undefined;
		}, this );
	}
} );

/** Matches any columns which contain an IMG.DateInfo element */
var NumericalColumn = Class.create( DataTableColumn, {
	DefaultSortDir: 1,
	initialize: function($super) {
		$super();
		this.addClassName('Number');
	},
	GetContent: function(Element) {	return parseFloat(Element.textContent.replace(/[^\d.]+/g, '')); },
	MatchesData: function(DataCells) {
		return DataCells.any(function(Cell) {
			return !isNaN(parseFloat(Cell.textContent)) || Cell.textContent.match(/\$[\d,.]+/);
		}, this );
	}
} );


/**
*	DataTableColumns - Helper object for working with Data Table Columns, custom column classes
* 		should be registered using DataTableColumns.InsertClass
*/
var DataTableColumns = {
	ColumnClasses: [ InputColumn, DateInfoColumn, DateColumn, NumericalColumn, TextColumn ],

	GetColumnIndex: function(HeaderCell) {
		return HeaderCell.previousSiblings().inject(0, function(acc, Column) {
			return acc + parseInt(Column.getAttribute('colspan') || 1);
		});
	},
	GetColumnForElement: function(Element) {
		var DataTable = Element.up('table');
		var DataColumnIndex = Element.previousSiblings().length + 1;
		var Index = 0;
		return DataTable.HeaderRow.select('th').find( function(Column) {
			Index = Index + parseInt(Column.getAttribute('colspan') || 1);
			if(Index >= DataColumnIndex)
				return true;
		} );
	},
	CreateColumn: function(Table, HeaderCell) {
		var ColumnIndex = this.GetColumnIndex(HeaderCell);
		var DataCells = $A([ Table.DataElements[0].childElements()[ColumnIndex], Table.DataElements.last().childElements()[ColumnIndex] ]).compact();
		var ColumnClass =  this.ColumnClasses.find( function( Type ) {
			if(Type.prototype.MatchesData(DataCells))
				return true;
		});
		return HeaderCell.BecomeClass(ColumnClass);
	},
	InsertClass: function(Class) { DataTableColumns.ColumnClasses.unshift(Class); }
}

/*
*	Data Table Filters represent an object which can be queried to determine whether a set of rows
* 		should be shown or hidden.  This base class handles initialization and querying sub-classed
* 		object through the Matches() function.
*/
var DataTableFilter = Class.create( {
	LoadPriority:	100,	/* After DataTable has loaded */
	initialize: function($super, DataTable) {
		this.DataTable = DataTable || this.up('TABLE');
		this.DataTable.RegisterFilter(this);
		this.observe('change', this.onChange.bindAsEventListener(this));
		if(Object.isFunction($super))
			$super();
	},
	onChange: function($super, e) {
		this.DataTable.UpdateVisibility();
		if(Object.isFunction($super))
			$super(e);
	},
	GetMatches: function(DataElements) {
		return DataElements.findAll( function(Element) {
			return this.Matches(Element);
		}, this );
	},
	Matches: function(Element) { return true; },
	UpdatePosition: function() { },
	Reset: function() { this.fire('change'); }
});

/** Free Form Text Filter - Allows for wildcard text input filtering of data */
var FreeFilter = Class.create( DataTableFilter, {
	initialize: function($super, DataTable) {
		$super(DataTable);
		this.type = 'text';
		this.insert( { before: '<img src="/res/img/icon.filter.png" style="vertical-align: middle; margin-right: 5px;">' } );
		this.Icon = this.previous();
		this.Icon.observe('click', this.onClickIcon.bind(this));
	},
	onClickIcon: function() {
		this.value = '';
		this.fire('change');
	},
	onChange: function($super, e) {
		this.Icon.src = this.value == ''
			? '/res/img/icon.filter.png'
			: '/res/img/icon.filter.remove.png';
		$super(e);
	},
	Matches: function(Element) {
		if(this.value == '')
			return true;
		var Content = Element.innerHTML.toLowerCase();
		var FilterMatches = this.value.toLowerCase().split(' ').findAll( function(Value) {
			if(Value.substring(0, 1) == '-')
				return Content.indexOf(Value.substring(1, 99999)) == -1;
			return Content.indexOf(Value) != -1;
		} );
		if(FilterMatches.length == this.value.split(' ').length)
			return true;
		return false;
	},
	Reset: function($super) {
		this.value = '';
		$super();
	}
});

var ColumnSelector = Class.create( {
	initialize: function() {
		this.type = 'text';
		this.insert( { before: '<img src="/res/img/icon.filter.png" style="vertical-align: middle; margin-right: 5px;">' } );
		this.Icon = this.previous();
		this.Icon.observe('click', this.onClickIcon.bind(this));
	}
});

/** Combines a ToggleButton with a DataTableFilter */
var DataTableToggleButtonFilter = Class.create(ToggleButton, DataTableFilter.prototype);

/** Filters values based on a given ColumnName */
var DataTableColumnFilter = Class.create(DataTableFilter, {
	ColumnName:		'',
	FilterValues:	[ ],

	SetFilterValues: function(Values) {
		this.FilterValues = $A(Object.isArray(Values) ? Values : [ Values ]).invoke('toLowerCase');
		this.fire('change');
	},
	Matches: function(Element) {
		if(this.DataTable.Columns[this.ColumnName] && this.FilterValues.length > 0) {
			var DataColumnIndex = this.DataTable.Columns[this.ColumnName].DataColumnIndex;
			var Content = this.DataTable.Columns[this.ColumnName].GetContent(Element.childElements()[DataColumnIndex]).toLowerCase();

			return this.FilterValues.indexOf(Content) != -1;
		}
		return true;
	},
	Reset: function() { this.SetFilterValues( [ ] ); }
} );

/** Filters values based on an attribute for the Element or Row */
var DataTableAttributeFilter = Class.create(DataTableFilter, {
	AttributeName:	'',
	FilterValues:	[ ],
	Matches: function(Element) { return this.FilterValues.indexOf(Element.getAttribute(this.AttributeName)) != -1;	},
	Reset: function($super) {
		this.FilterValues = [ ];
		this.fire('change');
	}
} );

/** Class used with DataTableFocusFilter, this filters on a specific column and value, instantiated by DataTableFocusFilter */
var DataTableColumnFocusFilter = Class.create(DataTableColumnFilter, {
	Html:		'<img src="/res/img/icon.filter.remove.png" JSClass="DataTableColumnFocusFilter" class="AutoFilter" style="position: absolute; top: 0px; display: none;">',

	Create: function(DataTable, ColumnName, Values) {
		var Element = DataTable.down('td').insert({ top: this.prototype.Html }).down();
		Element.ColumnName = ColumnName;
		Element.SetFilterValues(Values);
		return Element;
	},
	initialize: function($super, DataTable) {
		this.observe('click', this.onClick.bindAsEventListener(this));
		$super(DataTable);
	},
	SetFilterValues: function($super, Values) {
		$super(Values);
		this.src = this.FilterValues.length > 0
			? '/res/img/icon.filter.remove.png'
			: '/res/img/icon.filter.png';
	},
	UpdatePosition: function() {
		var Column = this.DataTable.Columns[this.ColumnName];
		var Position = Column.cumulativeOffset(), Dims = Column.getDimensions();
		this.setStyle( {
			top: 		Position.top -8 + (Dims.height / 2) + 'px',
			left: 		Position.left - 16 + 'px',
			display: 	'block'
		} );
	},
	onClick: function(e) {
		e.stop();
		this.Reset();
	},
	Reset: function($super) {
		if(this.parentNode)
			this.remove();
		this.DataTable.UnregisterFilter(this);
	}
} );
DataTableColumnFocusFilter.Create = DataTableColumnFocusFilter.prototype.Create;

/** Plugin for DataTable which provides functionality to watch for ctrl-mousemove and ctrl-click on text cells
*		when activated it will create a DataTableColumnFocusFilter which filters the clicked column with the value clicked
*/
var DataTableFocusFilter = Class.create({
	initialize: function(DataTable) {
		this.DataTable = DataTable;

		this.DataTable.down('td').insert({ top: '<img src="/res/img/icon.filter.png" class="AutoFilter" style="position: absolute; top: 0px; display: none;" />' });
		this.FocusFilterIcon = $(this.DataTable.down('.AutoFilter'));

		this.DataTable.observe('mousemove', this.onEvent.bindAsEventListener(this, 'mousemove'));
		this.DataTable.observe('click', this.onEvent.bindAsEventListener(this, 'click'));
	},
	onEvent: function(e, event) {
		var Element = e.element();
		if(e.ctrlKey == true && this.DataTable.DataElements.indexOf(Element.up('tr')) != -1 && Element.tagName != 'A') {
			if(Element.tagName != 'TD')
				Element = Element.up('td');
			if(Element) {
				e.stop();
				var Column = DataTableColumns.GetColumnForElement(Element);

				if(Column.hasClassName('Text')) {
					switch(event) {
						case 'mousemove':
							return this.ShowAutoFilter(Element);
						case 'click':
							DataTableColumnFocusFilter.Create(this.DataTable, Column.ColumnName, Element.textContent);
							break;
					}
				}
			}
		}
		this.ShowAutoFilter(false);
	},
	ShowAutoFilter: function(Element) {
		if(!Element) {
			if(this.FocusFilterIcon.Element)
				delete this.FocusFilterIcon.Element;
			return this.FocusFilterIcon.hide();
		}

		if(this.FocusFilterIcon.Element != Element) {
			var Offset = Element.cumulativeOffset();
			var Dims = Element.getDimensions();

			this.FocusFilterIcon.setStyle( {
				top: Offset.top -8 + (Dims.height / 2) + 'px',
				left: Offset.left -16 + 'px'
			} ).show();
			this.FocusFilterIcon.Element = Element;
		}
	}
});

/**
*	DataTableFormatter - Implements functionality which can alter the format of the table
*/

var DataTableFormatter = Class.create({
	/** Styles, if populated, will alter the style of the Element with a .setStyle() */
	Styles: [
/**		{ backgroundColor: 'red' }, **/
	],
	Classes: [
/**		GroupClass0, ... */
	],

	initialize: function() {
		this.AppliedChanges = { };
	},
	/** Apply the formatter to the given table */
	Apply: function(DataTable, DataElements) {
		if(DataElements.length == 0)
			return;

		var Targets = this.GetTargets(DataTable, DataElements);
		this.InitializeGroups(Targets);

		Targets.each( function(Target) {
			var UnformattedElement = (Target.Element.UnformattedElement || Target.Element);

			if(!Target.Element.UnformattedElement)
				Target.Element.UnformattedElement = Target.Element.cloneNode(true);

			this.ActiveTarget = Target;
			this.ApplyGrouping(Target, this.DetermineGroup(Target));
		}, this );
		delete this.ActiveTarget;
	},
	/** Un-Apply the formatter to the given table */
	Reverse: function(DataTable, DataElements) {
		$H(this.AppliedChanges).each( function( pair ) {
			this.ReverseGrouping(pair.key);
		}, this);
	},
	/** Called to apply changes to the given element by the group number */
	ApplyGrouping: function(Target, GroupNum) {
		if(GroupNum < 0)
			return;

		this.ReverseGrouping(Target.Element);
		if(GroupNum >= 0 && this.Styles.length > GroupNum)
			this.ApplyStyle(Target.Element, this.Styles[GroupNum] );
		if(GroupNum >= 0 && this.Classes.length > GroupNum)
			this.ApplyClassName(Target.Element, this.Classes[GroupNum]);
	},
	/** Called to un-apply changes to the given element by the group number */
	ReverseGrouping: function(TargetElement) {
		if(this.AppliedChanges[TargetElement]) {
			this.AppliedChanges[TargetElement].reverse().each( function( Change ) {
				Change.Element.removeClassName(Change.ClassName);
				Change.Element.setStyle( Change.OriginalStyle );
			} );
			delete this.AppliedChanges[TargetElement];
		}
	},
	ApplyStyle: function(Element, Style) {
		var OriginalStyle = { };
		$H(Style).keys().each( function( StyleName) {
			OriginalStyle[StyleName] = Element.style[StyleName];
		} );

		var id = this.ActiveTarget.Element.identify();

		this.AppliedChanges[id] = (this.AppliedChanges[id] || [ ]);
		this.AppliedChanges[id].push( {
			Element: Element, OriginalStyle: OriginalStyle
		} );
		Element.setStyle(Style);
	},
	ApplyClassName: function(Element, ClassName) {
		var id = this.ActiveTarget.Element.identify();

		this.AppliedChanges[id] = (this.AppliedChanges[id] || [ ]);
		this.AppliedChanges[id].push( {
			Element: Element, ClassName: ClassName
		} );
		Element.addClassName(ClassName);
	},
	/* Takes a group of numerical targets and returns an object with High, Low and Range of the values */
	DetermineValueRange: function(Targets) {
		var Range = Targets
			.inject( { Low: 999999999999, High: -999999999999 }, function(Range, Target) {
				if(Target.Criteria < Range.Low)
					Range.Low = Target.Criteria;
				if(Target.Criteria > Range.High)
					Range.High = Target.Criteria;
				return Range;
			} );

		Range.Range = Range.High - Range.Low;
		return Range;
	},
	/** Called to translate the DataElements into an array of { Element: } objects, may contain additional data */
	GetTargets: function(DataTable, DataElements) {
		return DataElements
			.map( function(Row) {
				return { Element: Row };
			});
	},

	/** Called once to initialize the group of targets */
	InitializeGroups: function(Targets) { },
	/** Called to determine the group number for the given target */
	DetermineGroup: function(Target) { return -1; }
});

var DataTableColumnFormatter = Class.create(DataTableFormatter, {
	initialize: function($super, ColumnName) {
		this.ColumnName = ColumnName;
		$super();
	},
	GetTargets: function(DataTable, DataElements) {
		var Column = DataTable.Columns[this.ColumnName];
		if(!Column)
			return DataElements;

		var DataColumnIndex = Column.DataColumnIndex;

		return DataElements
			.map( function(Row) {
				if(Row.SubRows)
					this.Apply(DataTable, Row.SubRows);

				var Element = Row.childElements()[DataColumnIndex];
				return { Element: Element, Criteria: Column.GetContent(Element.UnformattedElement || Element) };
			}, this);
	}
} );

/**
*	Chooses groups based on hard values of data
*/
var DataTableRangeFormatter = Class.create(DataTableColumnFormatter, {
	Groups:	[
/**		{ Low: 0, High: 99 },	Data Values 0 to 99 **/
/**		{ Low: 99, High: 199 },	Data Values 99 to 199 **/
	],
	DetermineGroup: function(Target) {
		var Criteria = (typeof(Target) == 'object' ? Target.Criteria : Target);

		for(var i=0;i<this.Groups.length;i++) {
			if(Criteria >= this.Groups[i].Low && Criteria < this.Groups[i].High)
				return i;
		}
		return -1;
	}
} );

/*	Chooses groups based on values scaled to the range 0-1 */
var DataTableDataRangeFormatter = Class.create(DataTableColumnFormatter, {
	Groups:	[
/**		{ Low: 0.0, High: 0.3 },	0%  to 30% 	of Data Range **/
/**		{ Low: 0.3, High: 0.6 },	30% to 60% 	of Data Range **/
/**		{ Low: 0.6, High: 1.0 },	60% to 100%	of Data Range **/
	],
	InitializeGroups: function(Targets) {
		this.Range = this.DetermineValueRange(Targets);
	},
	DetermineGroup: function(Target) {
		var Value = (Target.Criteria - this.Range.Low) / (this.Range.High - this.Range.Low) || 0;

		for(var i=0;i<this.Groups.length;i++) {
			if(Value >= this.Groups[i].Low && Value <= this.Groups[i].High)
				return i;
		}
		return -1;
	}
} );

var DataTableQuantileFormatter = Class.create(DataTableColumnFormatter, {
	initialize: function($super, ColumnName, Divisions) {
		$super(ColumnName);
		this.Divisions = Divisions;
	},
	InitializeGroups: function(Targets) {

		var UniqueValues = Targets
			.map( function(Target) { return Target.Criteria; })
			.uniq()
			.sort(function(a, b) { return a == b ? 0 : a < b ? -1 : 1; });

		this.Quantiles = [ ];

		for(var i=1;i<=this.Divisions;i++)
			this.Quantiles.push( UniqueValues[Math.round(i*(UniqueValues.length/this.Divisions))-1] );
	},
	DetermineGroup: function(Target) {
		for(var i=0;i<this.Quantiles.length;i++) {
			if(Target.Criteria <= this.Quantiles[i])
				return i;
		}
		return 0;
	}
} );

var DataTablePreferences = Class.create({
	initialize: function(TableName) {
		this.PathName = window.location.pathname;
		this.Search = window.location.search
		this.TableTitle = TableName;
		TempUniqueData = new PersistorObject(this.TableTitle + this.PathName + this.Search);
		TempGenericData = new PersistorObject(this.TableTitle + this.PathName);

		if(TempUniqueData.IsDataLoaded()) {
			this.Type = 'Unique';
			this.Preferences = TempUniqueData;
		} else if(TempGenericData.IsDataLoaded()) {
			this.Type = 'Generic';
			this.Preferences = TempGenericData;
		} else {
			this.Type = 'New';
			this.Preferences = new PersistorObject();
		}
	},

	ResetPreferences: function(Type) {
		this.Preferences.remove();
		this.Preferences = new PersistorObject();
		this.Type = 'New';
	},

	GetColumnVisibility: function(ColumnName) {
		if(this.Preferences && this.Preferences.Visibility && typeof this.Preferences.Visibility[ColumnName] != 'undefined')
			return this.Preferences.Visibility[ColumnName];
		return undefined;
	},

	SetColumnVisibility: function(ColumnName, Visibility) {
		if(!this.Preferences.Visibility)
			this.Preferences.Visibility = { }
		this.Preferences.Visibility[ColumnName] = Visibility;
	},

	SetPreference: function(Name, Value) {
		this.Preferences[Name] = Value;
	},

	SaveForType: function(Type) {
		if(Type == this.Type) {
			this.Preferences.save();
		} else if(Type == 'Unique') {
			this.Preferences.save(this.TableTitle + this.PathName + this.Search);
			this.Type = 'Unique';
		} else if(Type == 'Generic') {
			this.Preferences.remove(); //Delete unique version
			this.Preferences.save(this.TableTitle + this.PathName);
			this.Type = 'Generic';
		}
	}
});


/**
* pkTable wraps a TABLE element and provides filtering and sorting capabilities
* 	Data elements must have an ObjectID attribute to be recognized as a data element
* 	Filters should have a FilterBy attribute specifying either:
* 		"Content": 		Filters by the content of the row, for free searching
* 		Anything Else:	Assumed to be an attribute of the ObjectID element,
* 							Values may be in CSV format such as Enabled,Disabled
*
* 	Honors colspan, Adds Class "Odd" to Odd Rows
*
*	Refactoring Ideas:
* 		Filter Classes (Handles Filtering)
* 		Header Cell Class (Handles Column DataType Stuff)
*/
var DataTable = Class.create({
	LoadPriority: 	99, /* Last */
	DataElements:	[ ],
	HeaderRow:		null,
	NoItemsHTML:	'<tr class="NoItems" style="display: none;"><td colspan="99">No Items To Show</td></tr>',
	SortColumnName:	'',
	SortDir:		0,
	SortColumn: {
		get:  function() { return this.Columns[this.SortColumnName]; }
	},
	Columns:		{ },
	Filters:		[ ],
	Formatters:		[ ],
	Plugins:		[ ],

	initialize: function() {
		this.Plugins.push(new DataTableFocusFilter(this));

		var Row = this.select('TR').last();
		if(Row.hasClassName('Footer'))
			Row = Row.previous();
		Row.insert( { after: this.NoItemsHTML } );
		this.NoItemsRow = Row.next();

		this.DataElements = this.select('*[ObjectID]','*[DataRow]');
		this.InitializeDataElements();
		this.UpdateVisibility();

		if(this.down('.Control h1')) {
			this.Title = this.down('.Control h1').innerHTML
			this.InsertColumnSelectors();
			Event.observe(window, 'click', this.RemoveColumnSelectors.bindAsEventListener());
			this.Preferences = new DataTablePreferences(this.Title);
			this.UpdateColumnVisibility();
		}
	},

	UpdateColumnVisibility: function() {
		TempColumns = $H(this.Columns);
		TempColumns.each(function(pair) {
			if(pair.key) {
				if(this.Preferences.GetColumnVisibility(pair.key) === false)
					this.HideColumn(this.Columns[pair.key]);
				else if(this.Preferences.GetColumnVisibility(pair.key))
					this.ShowColumn(this.Columns[pair.key]);
				else if(this.Columns[pair.key].hasAttribute('hide'))
					this.HideColumn(this.Columns[pair.key]);
				else
					this.ShowColumn(this.Columns[pair.key]);
			}

		}, this);
	},

	InsertColumnSelectors: function() {
		HeaderText = this.down('.Control h1');
		TempHTML = HeaderText.innerHTML;
		HeaderText.innerHTML = '';
		TempHTML += '<img width="7" height="4" border="0" style="position: relative; top: -3px;" src="/res/img/icon.black_down_triangle.gif" \/>';
		ColumnSelect = new Element('a', {
			href: '#',
			'class': 'ColumnSelect',
			'id': 'ColumnSelect' + this.readAttribute('id')
		}).update(TempHTML);
		$(ColumnSelect).observe('click', this.ColumnSelectAction.bind(this));
		HeaderText.insert(ColumnSelect);
	},

	RemoveColumnSelectors: function(event) {
		if(event && event.explicitOriginalTarget.type == "checkbox")
			return
		$$('.ColumnSelectorMenu').each(function(ColumnSelectorMenuItem) {
			$(ColumnSelectorMenuItem).remove();
		});
	},

	GenerateColumnMenu: function(Target) {
		count = 0;

		ColumnSelectorMenu = new Element('div', {
			'class':'ColumnSelectorMenu'
		});

		ColumnSelectorTableElement = new Element('table', {
			'class' : 'ColumnSelectorTable'
		}).setStyle({width: '100%'});

		ColumnSelectorTableBody = new Element('tbody');

		ColumnSelectorTableElement.insert(ColumnSelectorTableBody);

		$H(this.Columns).each(function(Column) {
			if(Column.key) {
				if(this.Columns[Column.key].getStyle('display') != 'none')
					ColumnSelectorTableBody.insert("<tr class='RowToggle'><td class='ElementState'><input type='checkbox' checked='checked' /></td><td class='ColumnName'>" + Column.key + "</td></tr>");
				else
					ColumnSelectorTableBody.insert("<tr class='RowToggle'><td class='ElementState'><input type='checkbox' /></td><td class='ColumnName'>" + Column.key + "</td></tr>");
			}
		}, this);

		UniqueValue = (this.Preferences.Type == 'Unique') ? 'X' : '';
		GenericValue = (this.Preferences.Type == 'Generic') ? 'X' : '';
		ColumnSelectorTableBody.insert("<tr class='SaveLayout RowSplit'><td class='ElementState'>" + UniqueValue + "</td><td class=''>Save For Page</td></tr>");
		ColumnSelectorTableBody.insert("<tr class='SaveLayoutForAll'><td class='ElementState'>" + GenericValue + "</td><td class=''>Save For All</td></tr>");
		ColumnSelectorTableBody.insert("<tr class='ResetLayout'><td class='ElementState'></td><td class=''>Reset Layout</td></tr>");


		ColumnSelectorMenu.insert(ColumnSelectorTableElement);

		Target.insert({after: ColumnSelectorMenu});

	},

	ColumnSelectAction: function(event) {
		event.preventDefault();
		event.stopPropagation();

		this.RemoveColumnSelectors();

		this.GenerateColumnMenu($(event.currentTarget));

		this.down('.SaveLayout').observe('click', function(event) {
			this.Preferences.SaveForType('Unique');
		}.bind(this));

		this.down('.SaveLayoutForAll').observe('click', function(event) {
			this.Preferences.SaveForType('Generic');
		}.bind(this));

		this.select('.RowToggle').each(function(Row) {
			Row.observe('click', function(e) {
				if(e.target.nodeName != 'INPUT')
					var action = $(e.currentTarget).down('input').checked;
				else
					var action = !$(e.currentTarget).down('input').checked;

				TargetColumn = $(e.currentTarget).down('.ColumnName').innerHTML;
				if(action) {
					this.Preferences.SetColumnVisibility(TargetColumn, false);
					this.HideColumn(this.Columns[TargetColumn]);
				} else {
					this.Preferences.SetColumnVisibility(TargetColumn, true);
					this.ShowColumn(this.Columns[TargetColumn]);
				}
			}.bind(this));
		}, this);

		this.down('.ResetLayout').observe('click', function(e) {
			this.Preferences.ResetPreferences();
			this.UpdateColumnVisibility();
		}.bind(this));
	},

	HideColumn: function(Node) {
		//See if it is already hidden
		if(Node.getStyle('display') == 'none')
			return;

		Node.setStyle({'display':'none'});

		this.DataElements.each(function(Row) {
			for(i = Node.ColumnIndexStart; i < Node.ColumnIndexEnd; i++)
				Row.childElements()[i].setStyle({'display':'none'});
		});

		this.HeaderRow.previousSiblings().each(function(sibling) {
			if(sibling.hasClassName('Control')) return;
			sibling.select('th').each(function(Cell) {
				if(Node.ColumnIndexStart >= Cell.ColumnIndexStart && Node.ColumnIndexEnd <= Cell.ColumnIndexEnd) {
					if(Cell.colSpan == (Node.colSpan))
						Cell.setStyle({'display':'table-cell'});

					Cell.colSpan -= Node.colSpan;
				}
			});
		});

	},

	ShowColumn: function(Node) {
		//See if it is already being displayed
		if(Node.getStyle('display') != 'none')
			return;

		Node.setStyle({'display':'table-cell'});

		this.DataElements.each(function(Row) {
			for(i = Node.ColumnIndexStart; i < Node.ColumnIndexEnd; i++)
				Row.childElements()[i].setStyle({'display':'table-cell'});
		});

		this.HeaderRow.previousSiblings().each(function(sibling) {
			if(sibling.hasClassName('Control')) return;
			sibling.select('th').each(function(Cell) {
				if(Node.ColumnIndexStart >= Cell.ColumnIndexStart && Node.ColumnIndexEnd <= Cell.ColumnIndexEnd) {
					Cell.colSpan += Node.colSpan;

					if(Cell.colSpan == (Node.colSpan))
						Cell.setStyle({'display':'table-cell'});
				}
			});
		});
	},
	InitializeDataElements: function() {
		if(this.DataElements.length && this.DataElements[0].getAttribute('RowLevel') != undefined) {
			var RowStack = [ ];
			this.DataElements = this.DataElements.select( function(Element) {
				var RowLevel = parseInt(Element.getAttribute('RowLevel') || 0);
				while(RowStack.length > RowLevel)
					RowStack.pop();
				RowStack.push(Element);
				if(RowLevel > 0) {
					var ParentRow = RowStack[RowStack.length-2];
					if(ParentRow.SubRows == undefined)
						ParentRow.SubRows = [ ];
					ParentRow.SubRows.push(Element);
				}

				return RowLevel == 0;
			}, this );
		}
		this.InitializeHeaderRow();
	},
	InitializeHeaderRow: function() {
		if(this.DataElements.length > 0)
			this.HeaderRow = this.DataElements[0].previous();
		else {
			this.HeaderRow = this.down('TR');
			while(this.HeaderRow != null && (this.HeaderRow.hasClassName('Control') || this.HeaderRow.hasClassName('Footer')))
				this.HeaderRow = this.HeaderRow.next();
		}

		if(this.HeaderRow && this.DataElements.length && !this.HeaderRow.hasClassName('Header')) {
			this.HeaderRow.addClassName('Header');
			this.Columns = this.HeaderRow.select('th')
				.inject( { }, function(Columns, HeaderCell) {
					DataTableColumns.CreateColumn(this, HeaderCell);
					Columns[HeaderCell.ColumnName] = HeaderCell;
					return Columns;
				}, this );

			/* Auto-Create <colgroup> elements matching TH[colspan] attributes, if none defined at start */
			if(this.CreateColumns == true || (this.CreateColumns == undefined && this.select('col').length == 0)) {
				this.select('col').invoke('remove');
				this.insert( {
					top: $H(this.Columns).values().inject( '', function(acc, Column) {
						return acc + '<col id="'+Column.ColumnName.replace(/ /g, '')+'" class="'+Column.className+'" span="'+(Column.getAttribute('colspan') || 1)+'" />';
					})
				});
				this.CreateColumns = true;
			}
			this.HeaderRow.previousSiblings()
				.reject( function(Row) { return Row.hasClassName('Control'); } )
				.invoke( 'addClassName', 'Header');

			this.HeaderRow.previousSiblings().each(function(sibling) {
				this.SetColumnIndexes(sibling);
			}, this);

			this.SetColumnIndexes(this.HeaderRow);


			count = 0;
			var Columns = [];
			for(i in this.Columns) {
				Columns[count] = i.replace(/ /g,'');
				count++;
			}
		}
	},

	SetColumnIndexes: function(node) {
		node.select('th').each(function(Cell) {
			Cell.ColumnIndexStart = 0;
			Cell.previousSiblings().each(function(prevSibling) {
				Cell.ColumnIndexStart += prevSibling.colSpan;
			});
			Cell.ColumnIndexEnd = Cell.ColumnIndexStart + Cell.colSpan;
		});
	},

	RegisterFilter: function(Filter) {
		this.Filters.push(Filter);
		this.UpdateVisibility();
	},
	UnregisterFilter: function(Filter) {
		this.Filters = this.Filters.without(Filter);
		this.UpdateVisibility();
	},
	ResetFilters: function() {
		this.Filters.invoke('Reset');
	},
	RegisterFormatter: function(Formatter) {
		if(this.Formatters.indexOf(Formatter) == -1) {
			this.Formatters.push(Formatter);
			Formatter.Apply(this, this.DataElements);
		}
	},
	UnregisterFormatter: function(Formatter) {
		this.Formatters = this.Formatters.without(Formatter);
		Formatter.Reverse(this, this.DataElements);
	},
	UpdateVisibility: function() {
		var MatchResults = this.Filters.inject( { Show: this.DataElements, Hide: [ ] },
			function(Results, Filter) {
				var FilterMatches = Filter.GetMatches(Results.Show);
				Results.Hide = Results.Show.without(FilterMatches).concat(Results.Hide);
				Results.Show = FilterMatches;
				return Results;
			}, this
		);
		MatchResults.Show.invoke('show');
		MatchResults.Hide.invoke('hide');

		this.ShowNoItems(MatchResults.Show.length == 0);
		this.Filters.invoke( 'UpdatePosition' );
	},
	ShowNoItems: function(Show) {
		Show
			? this.NoItemsRow.show()
			: this.NoItemsRow.hide();
	},
	SortByColumn: function(ColumnName, FlipSort) {
		ColumnName = ColumnName || this.SortColumnName || this.DefaultSortColumn;
		var SortColumn = this.Columns[ColumnName];
		if(!ColumnName || !SortColumn || this.DataElements.length == 0)
			return;

		if(this.SortColumnName != ColumnName) {
			if(this.Columns[this.SortColumnName])
				this.Columns[this.SortColumnName].SortDir = null;

			this.SortDir = SortColumn.DefaultSortDir;
		} else if(FlipSort == true) {
			this.SortDir = !this.SortDir;
		}
		this.SortColumnName = SortColumn.ColumnName;
		SortColumn.SortDir = this.SortDir;
		var SortedElements = this.SortColumn.SortElements(this.DataElements);

		var Range = document.createRange();
		Range.setStartBefore(this.DataElements[0]);
		Range.setEndAfter(this.DataElements.last());

		var Fragment = document.createDocumentFragment();

		var Index = 0;
		var AddElementsToFragment = function(Elements) {
			for(var i=0;i < Elements.length; i++) {
				Fragment.appendChild(Elements[i]);
				Index++ % 2 == 1
					? Elements[i].removeClassName('Odd')
					: Elements[i].addClassName('Odd');
				if(Elements[i].AttachedRows)
					AddElementsToFragment(Elements[i].AttachedRows);
				if(Elements[i].SubRows)
					AddElementsToFragment(Elements[i].SubRows);
			}
		}

		AddElementsToFragment(SortedElements);

		Range.deleteContents();
		Range.insertNode(Fragment);

		this.DataElements = SortedElements;
	},
	/** Adds sorted sub-rows to a given row, sorts if we're sorting */
	AddSubRows: function(Row, SubRows) {
		Row.insert( { after: SubRows } );

		this.RefreshFormatting();

		// Re-index the data elements
		this.DataElements = this.select('*[ObjectID]','*[DataRow]');
		this.InitializeDataElements();

		this.SortByColumn();
		this.UpdateVisibility();
		this.fire('data:changed');
	},
	AddRows: function(SubRows) { this.AddSubRows(this.HeaderRow, SubRows); },
	/** Assumes a new header row is present as well */
	ReplaceContent: function(Rows) {
		var Range = document.createRange();
		if(this.DataElements.length) {
			Range.setStartBefore(this.HeaderRow);
			Range.setEndAfter(this.DataElements.last());
		} else if(this.HeaderRow) {
			Range.setStartBefore(this.HeaderRow);
			Range.setEndAfter(this.HeaderRow);
		} else {
			var Row = this.down('TR');
			if(Row.hasClassName('Control'))
				Row = Row.next('TR');
			Range.setStartAfter(Row);
			Range.setEndAfter(Row);
		}

		var Fragment = Range.createContextualFragment(Rows);

		Range.deleteContents();
		Range.insertNode(Fragment);

		// Re-index the data elements
		this.DataElements = this.select('*[ObjectID]','*[DataRow]');
		this.InitializeDataElements();

		this.RefreshFormatting();
		this.SortByColumn();
		this.UpdateVisibility();
		this.fire('data:changed');
	},
	RefreshFormatting: function() {
		this.Formatters.each( function(Formatter) {
			Formatter.Reverse(this, this.DataElements);
			Formatter.Apply(this, this.DataElements);
		}, this);
	},
	onClickHeaderRow: function(e) {
		var HeaderCell = Event.element(e).up('th');
		if(HeaderCell) {
			HeaderCell.ShiftDown = e.shiftKey;

			this.SortByColumn(HeaderCell.ColumnName, true);
			Event.stop(e);
		}
	}
});



/**
* InputDataTable adds on the ability to have infinite rows of new input
* 	fields for an expanding editable record list, dependant on negative
* 	object primary ids meaning new objects, the last row is expected to
* 	be an empty template for new rows (VirginRow)
*/
var InputDataTable = Class.create(DataTable, {
	initialize: function($super) {
		this.VirginRow = this.select('TR[ObjectID]').reverse().find(function(Row) {
			try { this.NewObjectID = Row.getAttribute('objectid').match(/\w+\/(-\d+)/)[1]; return true; }
				catch(e) { return false; }
		}, this );
		this.InsertBefore = this.VirginRow.next();
		this.VirginRow.remove();
		this.VirginRow.select('input','select','textarea').each(function(element) {
			element.name = element.name.replace(this.NewObjectID, '**ID**');
		}, this);
		this.VirginRow.setAttribute('objectid', this.VirginRow.getAttribute('objectid').replace(this.NewObjectID, '**ID**'));

		this.AddNewRow();

		$super();

		Event.observe(this, 'keyup', this.onKeyUp.bindAsEventListener(this));
		window.addEventListener('focus', this.onFocus.bindAsEventListener(this), true);
	},
	AddNewRow: function() {
		var Row = this.VirginRow.cloneNode(true);
		Row.select('input','select','textarea').each(function(element) {
			element.name = element.name.replace('**ID**', this.NewObjectID);
		}, this);
		Row.setAttribute('objectid', Row.getAttribute('objectid').replace('**ID**', this.NewObjectID));
		Row.style.display = '';
		this.InsertBefore
			? this.InsertBefore.insert( { before: Row } )
			: this.insert( { bottom: Row } );
		this.DataElements.push(Row);

		this.NewObjectID--;
	},
	RowMatchesDefault: function(Row) {
		var RowElements = Row.select('input','select','textarea');
		var htmVirginElements = this.VirginRow.select('input','select','textarea');
		for(var i=0;i<RowElements.length;i++) {
			if(RowElements[i].value != htmVirginElements[i].value)
				return false;
		}
		return true;
	},
	onKeyUp: function(e) {
		var Row = Event.element(e).up('tr[ObjectID]');
		if(Row && Row.getAttribute('objectid').indexOf(this.NewObjectID+1) !== -1 && !this.RowMatchesDefault(Row))
			this.AddNewRow();
	},
	onFocus: function(e) {
		var element = Event.element(e);
		var FocusLeftLastRow = false;

		if(element.descendantOf && element.descendantOf(this)) {
			var Row = element.up('tr');
			if(Row != this.LastFocusRow)
				FocusLeftLastRow = true;
		} else
			FocusLeftLastRow = true;

		if(FocusLeftLastRow && this.LastFocusRow && !this.IsEstablishedObject(this.LastFocusRow) && this.RowMatchesDefault(this.LastFocusRow) && this.LastFocusRow.getAttribute('objectid').indexOf(this.NewObjectID+1) == -1) {
			this.DataElements = this.DataElements.without(this.LastFocusRow);
			this.LastFocusRow.remove();
		}

		if(Row && Row.descendantOf(this) && Row.getAttribute('objectid'))
			this.LastFocusRow = Row;

		if(element.descendantOf && element.descendantOf(this) && element.tagName == 'INPUT')
			element.select();
	},
	IsEstablishedObject: function(Row) { try { return Row.getAttribute('objectid').match(/\w+\/\d+/) != null;} catch(e) { return false; } },
	ShowNoItems: function(Show) { /* Never Show NoItems Row for Input Tables */ }
} );

var pkTable = Class.create(DataTable, {
	initialize: function($super) {	$super(); console.log('Warning, pkTable is depracated in favor of DataTable'); }
} );

var pkInputTable = Class.create(InputDataTable, {
	initialize: function($super) {	$super(); console.log('Warning, pkInputTable is depracated in favor of DataInputTable'); }
} );
/* This snippet of code is here in case any console.log() statements are left in by accident, it will cause those messages to be ignored */
try {
	console.set = undefined;
} catch(e) {
	var console = {
		log: function() { /* Ignored */ }
	}
}

Event.observe(document, 'keyup', function(e) {
	try {
		if(e.keyCode >= 33 && e.keyCode <= 126) {
			var htmElem = Event.element(e);
			
			if(htmElem.tagName == 'INPUT') {
				var MaxLen = htmElem.getAttribute('maxlength');
				if(MaxLen && htmElem.value.length >= MaxLen) {
					var tFormElems = htmElem.up('form').getElements();
					
					for(var j=0;j<tFormElems.length;j++) {
						if(tFormElems[j] == htmElem) {
							tFormElems[j+1].focus();
							break;
						}
					}
				}
			}
		}
	} catch(e) { }
} );


// Start Error Validator //
// Build Base Error Div Check

var xDecideOnFadeOut = function(event) {
	element=Event.element(event);
	if(!(event.type == "click" && element.type == "text" ))
		var FadeElem=element.up('td');	
	if(FadeElem && FadeElem.down('div.Error'))
		fadeOut(FadeElem.down("div.Error")) ;
}	
function checkIE6Fix(){
	Prototype.Browser.IE6=navigator.appVersion.indexOf('MSIE 6') >= 0 ? true : false;
	if(window.IE6WidthFix && Prototype.Browser.IE6)
					$("SiteContainer").style.width=parseInt($("SiteContainer").getStyle('width'))+(parseInt($("SiteContainer").getStyle('width'))*.0445)+"px"; 
	$("SiteContainer").style.display="block";
}

function checkError(){	
	$$('.Error').each( 
		function(htmElem){ 
			htmElem.onclick="fadeOut(this)";
			ParentDimensions=htmElem.up('tr').getDimensions();
			ParentDimensions.height=ParentDimensions.height-6;
			if(window.AltPosition){
				ParentDimensions.orgWidth=ParentDimensions.width;
				$WModifer = (htmElem.hasClassName('Left'))? 2.2: 1.25;
				$HModifer = (htmElem.hasClassName('Left'))? 2.2: 1.25;
				ParentDimensions.width=ParentDimensions.width/$WModifer;
				ParentDimensions.height=ParentDimensions.height/$HModifer;
				
			}
			ErrorMessage=htmElem.innerHTML;
			AreaSize=300;
			ContentSize=AreaSize-19;
			if(ErrorMessage.length > 1){	
				switch (htmElem.id){
					case "TOS":
						tMode="Right:"+ParentDimensions.orgWidth+"px;";
						ParentDimensions.height=ParentDimensions.height-12;
						Type="Left";
						break;
					
					default:
						switch (htmElem.id){
							case "Select":
								break;
							case "Radio":
								ParentDimensions.width=ParentDimensions.width;
								break;
							case "DOB":
								ParentDimensions.height=ParentDimensions.height-25;
								break;
							default:
								break;
						}
						if(htmElem.hasClassName('Left')){
							tMode="Left: "+ParentDimensions.width*1.10+"px;"
							ParentDimensions.height=ParentDimensions.height+5;
							Type="Left";
						}
						else{
							tMode="Right:-"+(ParentDimensions.width*.70)+"px;"
							ParentDimensions.height=ParentDimensions.height-5;
							Type="Right";
						}
						break;
				}	
				if(htmElem.id != "General")
				htmElem.update(	'<div class="ValArea'+Type+'" style="'+tMode+';width:'+AreaSize+'px;"><div class="ValContent'+Type+'" style="width:'+ContentSize+'px"><span class="ValContentX' + Type + '">X</span> 	<div class="ErrorText">'+ErrorMessage+'</div></div></div>');
			}
			
			htmElem.up().show;
			htmElem.show;
			htmElem.style.display="block";
		});

}

function fadeOut(htmElem){	
	if(htmElem) {
		stopAt=0.125;
		duration=200;
		amt = -(((htmElem.getStyle("opacity") || 1) - stopAt) / (duration / 25));
		htmElem.fadeInterval = setInterval(function(htmElem, amt, stopAt){
			htmElem.setOpacity(htmElem.getStyle("opacity") + amt);
			if(htmElem.getStyle("opacity") < stopAt){			
				htmElem.hide();
				htmElem.fadeInterval = clearInterval(htmElem.fadeInterval);
			}
		}.bind(this, htmElem, amt, stopAt), 25);
	}
}


// Determine if  Alt Method should load
if (window.AltErrorStyle == "Yes"){ 
	docType = (Prototype.Browser.IE) ? document : window ;	
	Event.observe(window, 'load', function(event) {	
		checkError();
	//	checkIE6Fix();
		Event.observe(document, 'keypress',  xDecideOnFadeOut);
		Event.observe(docType, 'click',  xDecideOnFadeOut);
		$$('FORM').invoke('observe', 'submit', function(e) {
			var htmForm = Event.element(e);
			if(htmForm.AlreadySubmitted == true) {
				Event.stop(e);
				alert('You have already submitted this form, please wait.');
				return false;
			}
			htmForm.AlreadySubmitted = true;
			if(window.PopupLinkID > 0)
				OpenWindow('/?Command=OpenSiteLink&LinkID='+window.PopupLinkID+'&PHPSESSID='+window.PHPSESSID,1,950,800);
		});
	});
}

function OpenWindow(Url, Name, Width, Height) {
	// If height or width aren't set, go with 80% of current document height
	if(!Width || !Height) {
		if (window.outerHeight) {	/* Most Browsers */
			Width = window.outerWidth; 
			Height = window.outerHeight; 
		} else { /* IE */
			Width = document.body.clientWidth; 
			Height = document.body.clientHeight;
		}
		Width = Width * .8;
		Height = Height * .8;
	}

	w=window.open(Url, Name, "width=" + Width + ", height=" + Height + "toolbar=0,location=no,status=0,menubar=0,scrollbars=1,resizable=1");
	w.blur();
	window.focus();
}

// End Window Pop //
// Start  Show/Hide Unsecure Debt Field on Tax//
function ShowHideUnsecureDebt() 
{
	var htmChecked = $A(document.getElementsByName('Data[PostToDebt]')).find( function(htmElem) { return htmElem.checked; } );
	if (htmChecked){
		switch (htmChecked.value){
			case "Yes":
				$$(".UnsecureDebtRow").invoke('show');
				break;
			case "1":
				$$(".UnsecureDebtRow").invoke('show');
				break;
			default : 
				$$(".UnsecureDebtRow").invoke('hide');
		}
	}
	else
	{
		$$(".UnsecureDebtRow").invoke('hide');
	}		
}
// End  Show/Hide Unsecure Debt Field on Tax//

// Start the Change Icon function for National Debt Solutions // 
function Change(e){
	var htmTarget = Event.element(e);
	
	var htmImage = Event.element(e).up('tr').down('img');
	if(htmImage) {
		if(htmTarget.value != '')
			htmImage.src = htmImage.src.replace("Base","Checked")
		else
			htmImage.src = htmImage.src.replace("Checked","Base");
	}
}
function DoLoad(){
	if($('form')) {
		$('form').select('INPUT','SELECT').each(function(htmInput) {
			htmInput.observe('change', Change.bindAsEventListener(this));
		});
	}
}
//End Function//

