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

var Prototype = {
  Version: '1.6.1',

  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.*Safari/.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');
	  var form = document.createElement('form');
	  var 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() {
  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; i < properties.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;
	var properties = Object.keys(source);

	if (!Object.keys({ toString: true }).length) {
	  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().first() == "$super") {
			var method = value;
			value = (function(m) {
			  return function() { return ancestor[m].apply(this, arguments); };
			})(property).wrap(method);

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

	return this;
  }

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

  var _toString = Object.prototype.toString;

  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(object) {
	var type = typeof object;
	switch (type) {
	  case 'undefined':
	  case 'function':
	  case 'unknown': return;
	  case 'boolean': return object.toString();
	}

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

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

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

  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 toQueryString(object) {
	return $H(object).toQueryString();
  }

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

  function keys(object) {
	var results = [];
	for (var property in object)
	  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) == "[object Array]";
  }


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

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

  function isString(object) {
	return _toString.call(object) == "[object String]";
  }

  function isNumber(object) {
	return _toString.call(object) == "[object Number]";
  }

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

  extend(Object, {
	extend:        			extend,
	inspect:       			inspect,
	toJSON:        			toJSON,
	toQueryString: 			toQueryString,
	toHTML:        			toHTML,
	keys:          			keys,
	values:        			values,
	clone:         			clone,
	isElement:     			isElement,
	isArray:       			isArray,
	isHash:        			isHash,
	isFunction:    			isFunction,
	isString:      			isString,
	isNumber:     			isNumber,
	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
  }
})());


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


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

  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');
	var 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());
		var value = pair.length > 1 ? pair.join('=') : pair[0];
		if (value != undefined) value = decodeURIComponent(value);

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

  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() {
	var parts = this.split('-'), len = parts.length;
	if (len == 1) return parts[0];

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

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

	return camelized;
  }

  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 toJSON() {
	return this.inspect(true);
  }

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

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

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

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

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

  function endsWith(pattern) {
	var d = this.length - pattern.length;
	return d >= 0 && this.lastIndexOf(pattern) === 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 ? 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,
	toJSON:         toJSON,
	unfilterJSON:   unfilterJSON,
	isJSON:         isJSON,
	evalJSON:       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];
	  var 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) {
	for (var i = 0, length = this.length; i < length; i++)
	  iterator(this[i]);
  }
  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 : this.toArray())._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 toJSON() {
	var results = [];
	this.each(function(object) {
	  var value = Object.toJSON(object);
	  if (!Object.isUndefined(value)) results.push(value);
	});
	return '[' + results.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,
	toJSON:    toJSON
  });

  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))
		  return results.concat(values.map(toQueryPair.curry(key)));
	  } 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 toJSON() {
	return Object.toJSON(this.toObject());
  }

  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:                 toJSON,
	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 toJSON() {
	return isFinite(this) ? this.toString() : 'null';
  }

  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,
	toJSON:         toJSON,
	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.isString(this.options.parameters))
	  this.options.parameters = this.options.parameters.toQueryParams();
	else if (Object.isHash(this.options.parameters))
	  this.options.parameters = this.options.parameters.toObject();
  }
});
Ajax.Request = Class.create(Ajax.Base, {
  _complete: false,

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

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

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

	this.parameters = params;

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

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

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

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

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

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

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

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

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

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

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

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

	if (typeof this.options.requestHeaders == 'object') {
	  var extras = this.options.requestHeaders;

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

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

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

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

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

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

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

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

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

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

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

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

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

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








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

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

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

  status:      0,

  statusText: '',

  getStatus: Ajax.Request.prototype.getStatus,

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

  getHeader: Ajax.Request.prototype.getHeader,

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

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

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

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

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

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

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

	$super(url, options);
  },

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

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

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

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

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

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

	this.start();
  },

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

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

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

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

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



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

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

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

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

if (!Node.ELEMENT_NODE) {
  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) {

  var SETATTRIBUTE_IGNORES_NAME = (function(){
	var elForm = document.createElement("form");
	var elInput = document.createElement("input");
	var root = document.documentElement;
	elInput.setAttribute("name", "test");
	elForm.appendChild(elInput);
	root.appendChild(elForm);
	var isBuggy = elForm.elements
	  ? (typeof elForm.elements.test == "undefined")
	  : null;
	root.removeChild(elForm);
	elForm = elInput = null;
	return isBuggy;
  })();

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

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

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

	  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 (SELECT_ELEMENT_INNERHTML_BUGGY || TABLE_ELEMENT_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 {
		  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();
	  var value = (element[property] || '').toString();
	  if (value) result += ' ' + attribute + '=' + value.inspect(true);
	});
	return result + '>';
  },

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

  ancestors: function(element) {
	return Element.recursivelyCollect(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) {
	if (!(element = $(element).firstChild)) return [];
	while (element && element.nodeType != 1) element = element.nextSibling;
	if (element) return [element].concat($(element).nextSiblings());
	return [];
  },

  previousSiblings: function(element) {
	return Element.recursivelyCollect(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) {
	if (Object.isString(selector))
	  selector = new Selector(selector);
	return selector.match($(element));
  },

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

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

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

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


  select: function(element) {
	var args = Array.prototype.slice.call(arguments, 1);
	return Selector.findChildElements(element, args);
  },

  adjacent: function(element) {
	var args = Array.prototype.slice.call(arguments, 1);
	return Selector.findChildElements(element.parentNode, args).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;
  },

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

	var els = element.style;
	var originalVisibility = els.visibility;
	var originalPosition = els.position;
	var originalDisplay = els.display;
	els.visibility = 'hidden';
	if (originalPosition != 'fixed') // Switching fixed to absolute causes issues in Safari
	  els.position = 'absolute';
	els.display = 'block';
	var originalWidth = element.clientWidth;
	var originalHeight = element.clientHeight;
	els.display = originalDisplay;
	els.position = originalPosition;
	els.visibility = originalVisibility;
	return {width: originalWidth, height: originalHeight};
  },

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

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

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

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

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

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

  absolutize: function(element) {
	element = $(element);
	if (Element.getStyle(element, 'position') == 'absolute') return element;

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

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

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

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

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

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

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

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

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

	return $(document.body);
  },

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

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

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

	} while (element = element.offsetParent);

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

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

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

	source = $(source);
	var p = Element.viewportOffset(source);

	element = $(element);
	var delta = [0, 0];
	var parent = null;
	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 'left': case 'top': case 'right': case 'bottom':
		  if (proceed(element, 'position') === 'static') return null;
		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.getOffsetParent = Element.Methods.getOffsetParent.wrap(
	function(proceed, element) {
	  element = $(element);
	  try { element.offsetParent }
	  catch(e) { return $(document.body) }
	  var position = element.getStyle('position');
	  if (position !== 'static') return proceed(element);
	  element.setStyle({ position: 'relative' });
	  var value = proceed(element);
	  element.setStyle({ position: position });
	  return value;
	}
  );

  $w('positionedOffset viewportOffset').each(function(method) {
	Element.Methods[method] = Element.Methods[method].wrap(
	  function(proceed, element) {
		element = $(element);
		try { element.offsetParent }
		catch(e) { return Element._returnOffset(0,0) }
		var position = element.getStyle('position');
		if (position !== 'static') return proceed(element);
		var offsetParent = element.getOffsetParent();
		if (offsetParent && offsetParent.getStyle('position') === 'fixed')
		  offsetParent.setStyle({ zoom: 1 });
		element.setStyle({ position: 'relative' });
		var value = proceed(element);
		element.setStyle({ position: position });
		return value;
	  }
	);
  });

  Element.Methods.cumulativeOffset = Element.Methods.cumulativeOffset.wrap(
	function(proceed, element) {
	  try { element.offsetParent }
	  catch(e) { return Element._returnOffset(0,0) }
	  return proceed(element);
	}
  );

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

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

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

  Element.Methods.setOpacity = function(element, value) {
	function stripAlpha(filter){
	  return filter.replace(/alpha\([^\)]*\)/gi,'');
	}
	element = $(element);
	var currentStyle = element.currentStyle;
	if ((currentStyle && !currentStyle.hasLayout) ||
	  (!currentStyle && element.style.zoom == 'normal'))
		element.style.zoom = 1;

	var filter = element.getStyle('filter'), style = element.style;
	if (value == 1 || value === '') {
	  (filter = stripAlpha(filter)) ?
		style.filter = filter : style.removeAttribute('filter');
	  return element;
	} else if (value < 0.00001) value = 0;
	style.filter = stripAlpha(filter) +
	  'alpha(opacity=' + (value * 100) + ')';
	return element;
  };

  Element._attributeTranslations = (function(){

	var classProp = 'className';
	var forProp = 'for';

	var 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');
			el.onclick = Prototype.emptyFunction;
			var value = el.getAttribute('onclick');
			var f;

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

  Element.Methods.cumulativeOffset = function(element) {
	var valueT = 0, valueL = 0;
	do {
	  valueT += element.offsetTop  || 0;
	  valueL += element.offsetLeft || 0;
	  if (element.offsetParent == document.body)
		if (Element.getStyle(element, 'position') == 'absolute') break;

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

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

if ('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();
	  var fragments = Element._getContentFromAnonymousElement(tagName, content.stripScripts());
	  parent.removeChild(element);
	  if (nextSibling)
		fragments.each(function(node) { parent.insertBefore(node, nextSibling) });
	  else
		fragments.each(function(node) { parent.appendChild(node) });
	}
	else element.outerHTML = content.stripScripts();

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

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

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

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

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

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

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

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

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

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

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

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

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

	var element = document.createElement(tagName);
	var 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[0];
	}

	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);
  }
});
/* Portions of the Selector class are derived from Jack Slocum's DomQuery,
 * part of YUI-Ext version 0.40, distributed under the terms of an MIT-style
 * license.  Please see http://www.yui-ext.com/ for more information. */

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

	if (this.shouldUseSelectorsAPI()) {
	  this.mode = 'selectorsAPI';
	} else if (this.shouldUseXPath()) {
	  this.mode = 'xpath';
	  this.compileXPathMatcher();
	} else {
	  this.mode = "normal";
	  this.compileMatcher();
	}

  },

  shouldUseXPath: (function() {

	var IS_DESCENDANT_SELECTOR_BUGGY = (function(){
	  var isBuggy = false;
	  if (document.evaluate && window.XPathResult) {
		var el = document.createElement('div');
		el.innerHTML = '<ul><li></li></ul><div><ul><li></li></ul></div>';

		var xpath = ".//*[local-name()='ul' or local-name()='UL']" +
		  "//*[local-name()='li' or local-name()='LI']";

		var result = document.evaluate(xpath, el, null,
		  XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null);

		isBuggy = (result.snapshotLength !== 2);
		el = null;
	  }
	  return isBuggy;
	})();

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

	  var e = this.expression;

	  if (Prototype.Browser.WebKit &&
	   (e.include("-of-type") || e.include(":empty")))
		return false;

	  if ((/(\[[\w-]*?:|:checked)/).test(e))
		return false;

	  if (IS_DESCENDANT_SELECTOR_BUGGY) return false;

	  return true;
	}

  })(),

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

	if (Selector.CASE_INSENSITIVE_CLASS_NAMES) return false;

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

	try {
	  Selector._div.querySelector(this.expression);
	} catch(e) {
	  return false;
	}

	return true;
  },

  compileMatcher: function() {
	var e = this.expression, ps = Selector.patterns, h = Selector.handlers,
		c = Selector.criteria, le, p, m, len = ps.length, name;

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

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

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

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

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

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

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

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

  findElements: function(root) {
	root = root || document;
	var e = this.expression, results;

	switch (this.mode) {
	  case 'selectorsAPI':
		if (root !== document) {
		  var oldId = root.id, id = $(root).identify();
		  id = id.replace(/([\.:])/g, "\\$1");
		  e = "#" + id + " " + e;
		}

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

		return results;
	  case 'xpath':
		return document._getElementsByXPath(this.xpath, root);
	  default:
	   return this.matcher(root);
	}
  },

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

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

	while (e && le !== e && (/\S/).test(e)) {
	  le = e;
	  for (var i = 0; i<len; i++) {
		p = ps[i].re;
		name = ps[i].name;
		if (m = e.match(p)) {
		  if (as[name]) {
			this.tokens.push([name, Object.clone(m)]);
			e = e.replace(m[0], '');
		  } else {
			return this.findElements(document).include(element);
		  }
		}
	  }
	}

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

	return match;
  },

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

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

if (Prototype.BrowserFeatures.SelectorsAPI &&
 document.compatMode === 'BackCompat') {
  Selector.CASE_INSENSITIVE_CLASS_NAMES = (function(){
	var div = document.createElement('div'),
	 span = document.createElement('span');

	div.id = "prototype_test_id";
	span.className = 'Test';
	div.appendChild(span);
	var isIgnored = (div.querySelector('#prototype_test_id .test') !== null);
	div = span = null;
	return isIgnored;
  })();
}

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

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

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

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

  patterns: [
	{ name: 'laterSibling', re: /^\s*~\s*/ },
	{ name: 'child',        re: /^\s*>\s*/ },
	{ name: 'adjacent',     re: /^\s*\+\s*/ },
	{ name: 'descendant',   re: /^\s/ },

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

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

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

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

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

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

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

	mark: function(nodes) {
	  var _true = Prototype.emptyFunction;
	  for (var i = 0, node; node = nodes[i]; i++)
		node._countedByPrototype = _true;
	  return nodes;
	},

	unmark: (function(){

	  var PROPERTIES_ATTRIBUTES_MAP = (function(){
		var el = document.createElement('div'),
			isBuggy = false,
			propName = '_countedByPrototype',
			value = 'x'
		el[propName] = value;
		isBuggy = (el.getAttribute(propName) === value);
		el = null;
		return isBuggy;
	  })();

	  return PROPERTIES_ATTRIBUTES_MAP ?
		function(nodes) {
		  for (var i = 0, node; node = nodes[i]; i++)
			node.removeAttribute('_countedByPrototype');
		  return nodes;
		} :
		function(nodes) {
		  for (var i = 0, node; node = nodes[i]; i++)
			node._countedByPrototype = void 0;
		  return nodes;
		}
	})(),

	index: function(parentNode, reverse, ofType) {
	  parentNode._countedByPrototype = Prototype.emptyFunction;
	  if (reverse) {
		for (var nodes = parentNode.childNodes, i = nodes.length - 1, j = 1; i >= 0; i--) {
		  var node = nodes[i];
		  if (node.nodeType == 1 && (!ofType || node._countedByPrototype)) node.nodeIndex = j++;
		}
	  } else {
		for (var i = 0, j = 1, nodes = parentNode.childNodes; node = nodes[i]; i++)
		  if (node.nodeType == 1 && (!ofType || node._countedByPrototype)) node.nodeIndex = j++;
	  }
	},

	unique: function(nodes) {
	  if (nodes.length == 0) return nodes;
	  var results = [], n;
	  for (var i = 0, l = nodes.length; i < l; i++)
		if (typeof (n = nodes[i])._countedByPrototype == 'undefined') {
		  n._countedByPrototype = Prototype.emptyFunction;
		  results.push(Element.extend(n));
		}
	  return Selector.handlers.unmark(results);
	},

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

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

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

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

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

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

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

	id: function(nodes, root, id, combinator) {
	  var targetNode = $(id), h = Selector.handlers;

	  if (root == document) {
		if (!targetNode) return [];
		if (!nodes) return [targetNode];
	  } else {
		if (!root.sourceIndex || root.sourceIndex < 1) {
		  var nodes = root.getElementsByTagName('*');
		  for (var j = 0, node; node = nodes[j]; j++) {
			if (node.id === id) return [node];
		  }
		}
	  }

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

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

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

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

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

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

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

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

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

	'empty': function(nodes, value, root) {
	  for (var i = 0, results = [], node; node = nodes[i]; i++) {
		if (node.tagName == '!' || node.firstChild) continue;
		results.push(node);
	  }
	  return results;
	},

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

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

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

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

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

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

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

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

  findChildElements: function(element, expressions) {
	expressions = Selector.split(expressions.join(','));
	var results = [], h = Selector.handlers;
	for (var i = 0, l = expressions.length, selector; i < l; i++) {
	  selector = new Selector(expressions[i].strip());
	  h.concat(results, selector.findElements(element));
	}
	return (l > 1) ? h.unique(results) : results;
  }
});

if (Prototype.Browser.IE) {
  Object.extend(Selector.handlers, {
	concat: function(a, b) {
	  for (var i = 0, node; node = b[i]; i++)
		if (node.tagName !== "!") a.push(node);
	  return a;
	}
  });
}

function $$() {
  return Selector.findChildElements(document, $A(arguments));
}

var Form = {
  reset: function(form) {
	form = $(form);
	form.reset();
	return form;
  },

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

	var data = elements.inject({ }, function(result, element) {
	  if (!element.disabled && element.name) {
		key = element.name; value = $(element).getValue();
		if (value != null && element.type != 'file' && (element.type != 'submit' || (!submitted &&
			submit !== false && (!submit || key == submit) && (submitted = true)))) {
		  if (key in result) {
			if (!Object.isArray(result[key])) result[key] = [result[key]];
			result[key].push(value);
		  }
		  else result[key] = value;
		}
	  }
	  return result;
	});

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

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

  getElements: function(form) {
	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);
	form.findFirstElement().activate();
	return form;
  },

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

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

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

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

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

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


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

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

Form.Element.Methods = {

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

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

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

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

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

  activate: function(element) {
	element = $(element);
	try {
	  element.focus();
	  if (element.select && (element.tagName.toLowerCase() != 'input' ||
		  !(/^(?:button|reset|submit)$/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 = {
  input: function(element, value) {
	switch (element.type.toLowerCase()) {
	  case 'checkbox':
	  case 'radio':
		return Form.Element.Serializers.inputSelector(element, value);
	  default:
		return Form.Element.Serializers.textarea(element, value);
	}
  },

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

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

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

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

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

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

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

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


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

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

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

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

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

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

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

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

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

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

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

Form.EventObserver = Class.create(Abstract.EventObserver, {
  getValue: function() {
	return Form.serialize(this.element);
  }
});
(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 _isButton;
  if (Prototype.Browser.IE) {
	var buttonMap = { 0: 1, 1: 4, 2: 2 };
	_isButton = function(event, code) {
	  return event.button === buttonMap[code];
	};
  } else if (Prototype.Browser.WebKit) {
	_isButton = function(event, code) {
	  switch (code) {
		case 0: return event.which == 1 && !event.metaKey;
		case 1: return event.which == 1 && event.metaKey;
		default: return false;
	  }
	};
  } else {
	_isButton = function(event, code) {
	  return event.which ? (event.which === code + 1) : (event.button === code);
	};
  }

  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;
	var elements = [element].concat(element.ancestors());
	return Selector.findElement(elements, expression, 0);
  }

  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 (Prototype.Browser.IE) {
	function _relatedTarget(event) {
	  var element;
	  switch (event.type) {
		case 'mouseover': element = event.fromElement; break;
		case 'mouseout':  element = event.toElement;   break;
		default: return null;
	  }
	  return Element.extend(element);
	}

	Object.extend(methods, {
	  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 (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
	  });

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

  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;

  if (!MOUSEENTER_MOUSELEAVE_EVENTS_SUPPORTED) {
	_getDOMEventName = function(eventName) {
	  var translations = { mouseenter: "mouseover", mouseleave: "mouseout" };
	  return eventName in translations ? 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("onfilterchange", 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 (Object.isUndefined(registry)) return element;

	if (eventName && !handler) {
	  var responders = registry.get(eventName);

	  if (Object.isUndefined(responders)) return element;

	  responders.each( function(r) {
		Element.stopObserving(element, eventName, r.handler);
	  });
	  return element;
	} else if (!eventName) {
	  registry.each( function(pair) {
		var eventName = pair.key, responders = pair.value;

		responders.each( function(r) {
		  Element.stopObserving(element, eventName, r.handler);
		});
	  });
	  return element;
	}

	var responders = registry.get(eventName);

	if (!responders) return;

	var responder = responders.find( function(r) { return r.handler === handler; });
	if (!responder) return element;

	var actualEventName = _getDOMEventName(eventName);

	if (eventName.include(':')) {
	  if (element.removeEventListener)
		element.removeEventListener("dataavailable", responder, false);
	  else {
		element.detachEvent("ondataavailable", responder);
		element.detachEvent("onfilterchange",  responder);
	  }
	} else {
	  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', true, true);
	} else {
	  event = document.createEventObject();
	  event.eventType = bubble ? 'ondataavailable' : 'onfilterchange';
	}

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

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

	return Event.extend(event);
  }


  Object.extend(Event, Event.Methods);

  Object.extend(Event, {
	fire:          fire,
	observe:       observe,
	stopObserving: stopObserving
  });

  Element.addMethods({
	fire:          fire,

	observe:       observe,

	stopObserving: stopObserving
  });

  Object.extend(document, {
	fire:          fire.methodize(),

	observe:       observe.methodize(),

	stopObserving: stopObserving.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);

/*--------------------------------------------------------------------------*/
/**
  *
  *  Copyright 2005 Sabre Airline Solutions
  *
  *  Licensed under the Apache License, Version 2.0 (the "License"); you may not use this
  *  file except in compliance with the License. You may obtain a copy of the License at
  *
  *         http://www.apache.org/licenses/LICENSE-2.0
  *
  *  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.
  **/


//-------------------- rico.js
var Rico = {
  Version: '1.1.2',
  prototypeVersion: parseFloat(Prototype.Version.split(".")[0] + "." + Prototype.Version.split(".")[1])
}

if((typeof Prototype=='undefined') || Rico.prototypeVersion < 1.3)
	  throw("Rico requires the Prototype JavaScript framework >= 1.3");

Rico.ArrayExtensions = new Array();

if (Array.prototype.push) {
   Rico.ArrayExtensions[ Rico.ArrayExtensions.length ] = Array.prototype.push;
}

if (!Array.prototype.remove) {
   Array.prototype.remove = function(dx) {
	  if( isNaN(dx) || dx > this.length )
		 return false;
	  for( var i=0,n=0; i<this.length; i++ )
		 if( i != dx )
			this[n++]=this[i];
	  this.length-=1;
   };
  Rico.ArrayExtensions[ Rico.ArrayExtensions.length ] = Array.prototype.remove;
}

if (!Array.prototype.removeItem) {
   Array.prototype.removeItem = function(item) {
	  for ( var i = 0 ; i < this.length ; i++ )
		 if ( this[i] == item ) {
			this.remove(i);
			break;
		 }
   };
  Rico.ArrayExtensions[ Rico.ArrayExtensions.length ] = Array.prototype.removeItem;
}

if (!Array.prototype.indices) {
   Array.prototype.indices = function() {
	  var indexArray = new Array();
	  for ( index in this ) {
		 var ignoreThis = false;
		 for ( var i = 0 ; i < Rico.ArrayExtensions.length ; i++ ) {
			if ( this[index] == Rico.ArrayExtensions[i] ) {
			   ignoreThis = true;
			   break;
			}
		 }
		 if ( !ignoreThis )
			indexArray[ indexArray.length ] = index;
	  }
	  return indexArray;
   }
  Rico.ArrayExtensions[ Rico.ArrayExtensions.length ] = Array.prototype.indices;
}

// Create the loadXML method and xml getter for Mozilla
if ( window.DOMParser &&
	  window.XMLSerializer &&
	  window.Node && Node.prototype && Node.prototype.__defineGetter__ ) {

   if (!Document.prototype.loadXML) {
	  Document.prototype.loadXML = function (s) {
		 var doc2 = (new DOMParser()).parseFromString(s, "text/xml");
		 while (this.hasChildNodes())
			this.removeChild(this.lastChild);

		 for (var i = 0; i < doc2.childNodes.length; i++) {
			this.appendChild(this.importNode(doc2.childNodes[i], true));
		 }
	  };
	}

	Document.prototype.__defineGetter__( "xml",
	   function () {
		   return (new XMLSerializer()).serializeToString(this);
	   }
	 );
}

document.getElementsByTagAndClassName = function(tagName, className) {
  if ( tagName == null )
	 tagName = '*';

  var children = document.getElementsByTagName(tagName) || document.all;
  var elements = new Array();

  if ( className == null )
	return children;

  for (var i = 0; i < children.length; i++) {
	var child = children[i];
	var classNames = child.className.split(' ');
	for (var j = 0; j < classNames.length; j++) {
	  if (classNames[j] == className) {
		elements.push(child);
		break;
	  }
	}
  }

  return elements;
}


//-------------------- ricoAccordion.js
Rico.Accordion = Class.create();

Rico.Accordion.prototype = {

   initialize: function(container, options) {
	  this.container            = $(container);
	  this.lastExpandedTab      = null;
	  this.accordionTabs        = new Array();
	  this.setOptions(options);
	  this._attachBehaviors();
	  if(!container) return;

	  this.container.style.borderBottom = '1px solid ' + this.options.borderColor;
	  // validate onloadShowTab
	   if (this.options.onLoadShowTab >= this.accordionTabs.length)
		this.options.onLoadShowTab = 0;

	  // set the initial visual state...
	  for ( var i=0 ; i < this.accordionTabs.length ; i++ )
	  {
		if (i != this.options.onLoadShowTab){
		 this.accordionTabs[i].collapse();
		 this.accordionTabs[i].content.style.display = 'none';
		}
	  }
	  this.lastExpandedTab = this.accordionTabs[this.options.onLoadShowTab];
	  if (this.options.panelHeight == 'auto'){
		  var tabToCheck = (this.options.onloadShowTab === 0)? 1 : 0;
		  var titleBarSize = parseInt(RicoUtil.getElementsComputedStyle(this.accordionTabs[tabToCheck].titleBar, 'height'));
		  if (isNaN(titleBarSize))
			titleBarSize = this.accordionTabs[tabToCheck].titleBar.offsetHeight;
		  
		  var totalTitleBarSize = this.accordionTabs.length * titleBarSize;
		  var parentHeight = parseInt(RicoUtil.getElementsComputedStyle(this.container.parentNode, 'height'));
		  if (isNaN(parentHeight))
			parentHeight = this.container.parentNode.offsetHeight;
		  
		  this.options.panelHeight = parentHeight - totalTitleBarSize-2;
	  }
	  
	  this.lastExpandedTab.content.style.height = this.options.panelHeight + "px";
	  this.lastExpandedTab.showExpanded();
	  this.lastExpandedTab.titleBar.style.fontWeight = this.options.expandedFontWeight;

   },

   setOptions: function(options) {
	  this.options = {
		 expandedBg          : '#63699c',
		 hoverBg             : '#63699c',
		 collapsedBg         : '#6b79a5',
		 expandedTextColor   : '#ffffff',
		 expandedFontWeight  : 'bold',
		 hoverTextColor      : '#ffffff',
		 collapsedTextColor  : '#ced7ef',
		 collapsedFontWeight : 'normal',
		 hoverTextColor      : '#ffffff',
		 borderColor         : '#1f669b',
		 panelHeight         : 200,
		 onHideTab           : null,
		 onShowTab           : null,
		 onLoadShowTab       : 0
	  }
	  Object.extend(this.options, options || {});
   },

   showTabByIndex: function( anIndex, animate ) {
	  var doAnimate = arguments.length == 1 ? true : animate;
	  this.showTab( this.accordionTabs[anIndex], doAnimate );
   },

   showTab: function( accordionTab, animate ) {
	 if ( this.lastExpandedTab == accordionTab )
		return;

	  var doAnimate = arguments.length == 1 ? true : animate;

	  if ( this.options.onHideTab )
		 this.options.onHideTab(this.lastExpandedTab);

	  this.lastExpandedTab.showCollapsed(); 
	  var accordion = this;
	  var lastExpandedTab = this.lastExpandedTab;

	  this.lastExpandedTab.content.style.height = (this.options.panelHeight - 1) + 'px';
	  accordionTab.content.style.display = '';

	  accordionTab.titleBar.style.fontWeight = this.options.expandedFontWeight;

	  if ( doAnimate ) {
		 new Rico.Effect.AccordionSize( this.lastExpandedTab.content,
								   accordionTab.content,
								   1,
								   this.options.panelHeight,
								   100, 10,
								   { complete: function() {accordion.showTabDone(lastExpandedTab)} } );
		 this.lastExpandedTab = accordionTab;
	  }
	  else {
		 this.lastExpandedTab.content.style.height = "1px";
		 accordionTab.content.style.height = this.options.panelHeight + "px";
		 this.lastExpandedTab = accordionTab;
		 this.showTabDone(lastExpandedTab);
	  }
   },

   showTabDone: function(collapsedTab) {
	  collapsedTab.content.style.display = 'none';
	  this.lastExpandedTab.showExpanded();
	  if ( this.options.onShowTab )
		 this.options.onShowTab(this.lastExpandedTab);
   },

   _attachBehaviors: function() {
	  var panels = this._getDirectChildrenByTag(this.container, 'DIV');
	  for ( var i = 0 ; i < panels.length ; i++ ) {

		 var tabChildren = this._getDirectChildrenByTag(panels[i],'DIV');
		 if ( tabChildren.length != 2 )
			continue; // unexpected

		 var tabTitleBar   = tabChildren[0];
		 var tabContentBox = tabChildren[1];
		 this.accordionTabs.push( new Rico.Accordion.Tab(this,tabTitleBar,tabContentBox) );
	  }
   },

   _getDirectChildrenByTag: function(e, tagName) {
	  var kids = new Array();
	  var allKids = e.childNodes;
	  for( var i = 0 ; i < allKids.length ; i++ )
		 if ( allKids[i] && allKids[i].tagName && allKids[i].tagName == tagName )
			kids.push(allKids[i]);
	  return kids;
   }

};

Rico.Accordion.Tab = Class.create();

Rico.Accordion.Tab.prototype = {

   initialize: function(accordion, titleBar, content) {
	  this.accordion = accordion;
	  this.titleBar  = titleBar;
	  this.content   = content;
	  this._attachBehaviors();
   },

   collapse: function() {
	  this.showCollapsed();
	  this.content.style.height = "1px";
   },

   showCollapsed: function() {
	  this.expanded = false;
	  this.titleBar.style.backgroundColor = this.accordion.options.collapsedBg;
	  this.titleBar.style.color           = this.accordion.options.collapsedTextColor;
	  this.titleBar.style.fontWeight      = this.accordion.options.collapsedFontWeight;
	  this.content.style.overflow = "hidden";
   },

   showExpanded: function() {
	  this.expanded = true;
	  this.titleBar.style.backgroundColor = this.accordion.options.expandedBg;
	  this.titleBar.style.color           = this.accordion.options.expandedTextColor;
	  this.content.style.overflow         = "auto";
   },

   titleBarClicked: function(e) {
	  if ( this.accordion.lastExpandedTab == this )
		 return;
	  this.accordion.showTab(this);
   },

   hover: function(e) {
		this.titleBar.style.backgroundColor = this.accordion.options.hoverBg;
		this.titleBar.style.color           = this.accordion.options.hoverTextColor;
   },

   unhover: function(e) {
	  if ( this.expanded ) {
		 this.titleBar.style.backgroundColor = this.accordion.options.expandedBg;
		 this.titleBar.style.color           = this.accordion.options.expandedTextColor;
	  }
	  else {
		 this.titleBar.style.backgroundColor = this.accordion.options.collapsedBg;
		 this.titleBar.style.color           = this.accordion.options.collapsedTextColor;
	  }
   },

   _attachBehaviors: function() {
	  this.content.style.border = "1px solid " + this.accordion.options.borderColor;
	  this.content.style.borderTopWidth    = "0px";
	  this.content.style.borderBottomWidth = "0px";
	  this.content.style.margin            = "0px";

	  this.titleBar.onclick     = this.titleBarClicked.bindAsEventListener(this);
	  this.titleBar.onmouseover = this.hover.bindAsEventListener(this);
	  this.titleBar.onmouseout  = this.unhover.bindAsEventListener(this);
   }

};


//-------------------- ricoAjaxEngine.js
Rico.AjaxEngine = Class.create();

Rico.AjaxEngine.prototype = {

   initialize: function() {
	  this.ajaxElements = new Array();
	  this.ajaxObjects  = new Array();
	  this.requestURLS  = new Array();
	  this.options = {};
   },

   registerAjaxElement: function( anId, anElement ) {
	  if ( !anElement )
		 anElement = $(anId);
	  this.ajaxElements[anId] = anElement;
   },

   registerAjaxObject: function( anId, anObject ) {
	  this.ajaxObjects[anId] = anObject;
   },

   registerRequest: function (requestLogicalName, requestURL) {
	  this.requestURLS[requestLogicalName] = requestURL;
   },

   sendRequest: function(requestName, options) {
	  // Allow for backwards Compatibility
	  if ( arguments.length >= 2 )
	   if (typeof arguments[1] == 'string')
		 options = {parameters: this._createQueryString(arguments, 1)};
	  this.sendRequestWithData(requestName, null, options);
   },

   sendRequestWithData: function(requestName, xmlDocument, options) {
	  var requestURL = this.requestURLS[requestName];
	  if ( requestURL == null )
		 return;

	  // Allow for backwards Compatibility
	  if ( arguments.length >= 3 )
		if (typeof arguments[2] == 'string')
		  options.parameters = this._createQueryString(arguments, 2);

	  new Ajax.Request(requestURL, this._requestOptions(options,xmlDocument));
   },

   sendRequestAndUpdate: function(requestName,container,options) {
	  // Allow for backwards Compatibility
	  if ( arguments.length >= 3 )
		if (typeof arguments[2] == 'string')
		  options.parameters = this._createQueryString(arguments, 2);

	  this.sendRequestWithDataAndUpdate(requestName, null, container, options);
   },

   sendRequestWithDataAndUpdate: function(requestName,xmlDocument,container,options) {
	  var requestURL = this.requestURLS[requestName];
	  if ( requestURL == null )
		 return;

	  // Allow for backwards Compatibility
	  if ( arguments.length >= 4 )
		if (typeof arguments[3] == 'string')
		  options.parameters = this._createQueryString(arguments, 3);

	  var updaterOptions = this._requestOptions(options,xmlDocument);

	  new Ajax.Updater(container, requestURL, updaterOptions);
   },

   // Private -- not part of intended engine API --------------------------------------------------------------------

   _requestOptions: function(options,xmlDoc) {
	  var requestHeaders = ['X-Rico-Version', Rico.Version ];
	  var sendMethod = 'post';
	  if ( xmlDoc == null )
		if (Rico.prototypeVersion < 1.4)
		requestHeaders.push( 'Content-type', 'text/xml' );
	  else
		  sendMethod = 'get';
	  (!options) ? options = {} : '';

	  if (!options._RicoOptionsProcessed){
	  // Check and keep any user onComplete functions
		if (options.onComplete)
			 options.onRicoComplete = options.onComplete;
		// Fix onComplete
		if (options.overrideOnComplete)
		  options.onComplete = options.overrideOnComplete;
		else
		  options.onComplete = this._onRequestComplete.bind(this);
		options._RicoOptionsProcessed = true;
	  }

	 // Set the default options and extend with any user options
	 this.options = {
					 requestHeaders: requestHeaders,
					 parameters:     options.parameters,
					 postBody:       xmlDoc,
					 method:         sendMethod,
					 onComplete:     options.onComplete
					};
	 // Set any user options:
	 Object.extend(this.options, options);
	 return this.options;
   },

   _createQueryString: function( theArgs, offset ) {
	  var queryString = ""
	  for ( var i = offset ; i < theArgs.length ; i++ ) {
		  if ( i != offset )
			queryString += "&";

		  var anArg = theArgs[i];

		  if ( anArg.name != undefined && anArg.value != undefined ) {
			queryString += anArg.name +  "=" + escape(anArg.value);
		  }
		  else {
			 var ePos  = anArg.indexOf('=');
			 var argName  = anArg.substring( 0, ePos );
			 var argValue = anArg.substring( ePos + 1 );
			 queryString += argName + "=" + escape(argValue);
		  }
	  }
	  return queryString;
   },

   _onRequestComplete : function(request) {
	  if(!request)
		  return;
	  // User can set an onFailure option - which will be called by prototype
	  if (request.status != 200)
		return;

	  var response = request.responseXML.getElementsByTagName("ajax-response");
	  if (response == null || response.length != 1)
		 return;
	  this._processAjaxResponse( response[0].childNodes );
	  
	  // Check if user has set a onComplete function
	  var onRicoComplete = this.options.onRicoComplete;
	  if (onRicoComplete != null)
		  onRicoComplete();
   },

   _processAjaxResponse: function( xmlResponseElements ) {
	  for ( var i = 0 ; i < xmlResponseElements.length ; i++ ) {
		 var responseElement = xmlResponseElements[i];

		 // only process nodes of type element.....
		 if ( responseElement.nodeType != 1 )
			continue;

		 var responseType = responseElement.getAttribute("type");
		 var responseId   = responseElement.getAttribute("id");

		 if ( responseType == "object" )
			this._processAjaxObjectUpdate( this.ajaxObjects[ responseId ], responseElement );
		 else if ( responseType == "element" )
			this._processAjaxElementUpdate( this.ajaxElements[ responseId ], responseElement );
		 else
			alert('unrecognized AjaxResponse type : ' + responseType );
	  }
   },

   _processAjaxObjectUpdate: function( ajaxObject, responseElement ) {
	  ajaxObject.ajaxUpdate( responseElement );
   },

   _processAjaxElementUpdate: function( ajaxElement, responseElement ) {
	  ajaxElement.innerHTML = RicoUtil.getContentAsString(responseElement);
   }

}

var ajaxEngine = new Rico.AjaxEngine();


//-------------------- ricoColor.js
Rico.Color = Class.create();

Rico.Color.prototype = {

   initialize: function(red, green, blue) {
	  this.rgb = { r: red, g : green, b : blue };
   },

   setRed: function(r) {
	  this.rgb.r = r;
   },

   setGreen: function(g) {
	  this.rgb.g = g;
   },

   setBlue: function(b) {
	  this.rgb.b = b;
   },

   setHue: function(h) {

	  // get an HSB model, and set the new hue...
	  var hsb = this.asHSB();
	  hsb.h = h;

	  // convert back to RGB...
	  this.rgb = Rico.Color.HSBtoRGB(hsb.h, hsb.s, hsb.b);
   },

   setSaturation: function(s) {
	  // get an HSB model, and set the new hue...
	  var hsb = this.asHSB();
	  hsb.s = s;

	  // convert back to RGB and set values...
	  this.rgb = Rico.Color.HSBtoRGB(hsb.h, hsb.s, hsb.b);
   },

   setBrightness: function(b) {
	  // get an HSB model, and set the new hue...
	  var hsb = this.asHSB();
	  hsb.b = b;

	  // convert back to RGB and set values...
	  this.rgb = Rico.Color.HSBtoRGB( hsb.h, hsb.s, hsb.b );
   },

   darken: function(percent) {
	  var hsb  = this.asHSB();
	  this.rgb = Rico.Color.HSBtoRGB(hsb.h, hsb.s, Math.max(hsb.b - percent,0));
   },

   brighten: function(percent) {
	  var hsb  = this.asHSB();
	  this.rgb = Rico.Color.HSBtoRGB(hsb.h, hsb.s, Math.min(hsb.b + percent,1));
   },

   blend: function(other) {
	  this.rgb.r = Math.floor((this.rgb.r + other.rgb.r)/2);
	  this.rgb.g = Math.floor((this.rgb.g + other.rgb.g)/2);
	  this.rgb.b = Math.floor((this.rgb.b + other.rgb.b)/2);
   },

   isBright: function() {
	  var hsb = this.asHSB();
	  return this.asHSB().b > 0.5;
   },

   isDark: function() {
	  return ! this.isBright();
   },

   asRGB: function() {
	  return "rgb(" + this.rgb.r + "," + this.rgb.g + "," + this.rgb.b + ")";
   },

   asHex: function() {
	  return "#" + this.rgb.r.toColorPart() + this.rgb.g.toColorPart() + this.rgb.b.toColorPart();
   },

   asHSB: function() {
	  return Rico.Color.RGBtoHSB(this.rgb.r, this.rgb.g, this.rgb.b);
   },

   toString: function() {
	  return this.asHex();
   }

};

Rico.Color.createFromHex = function(hexCode) {
  if(hexCode.length==4) {
	var shortHexCode = hexCode; 
	var hexCode = '#';
	for(var i=1;i<4;i++) hexCode += (shortHexCode.charAt(i) + 
shortHexCode.charAt(i));
  }
   if ( hexCode.indexOf('#') == 0 )
	  hexCode = hexCode.substring(1);
   var red   = hexCode.substring(0,2);
   var green = hexCode.substring(2,4);
   var blue  = hexCode.substring(4,6);
   return new Rico.Color( parseInt(red,16), parseInt(green,16), parseInt(blue,16) );
}

/**
 * Factory method for creating a color from the background of
 * an HTML element.
 */
Rico.Color.createColorFromBackground = function(elem) {

   var actualColor = RicoUtil.getElementsComputedStyle($(elem), "backgroundColor", "background-color");

   if ( actualColor == "transparent" && elem.parentNode )
	  return Rico.Color.createColorFromBackground(elem.parentNode);

   if ( actualColor == null )
	  return new Rico.Color(255,255,255);

   if ( actualColor.indexOf("rgb(") == 0 ) {
	  var colors = actualColor.substring(4, actualColor.length - 1 );
	  var colorArray = colors.split(",");
	  return new Rico.Color( parseInt( colorArray[0] ),
							parseInt( colorArray[1] ),
							parseInt( colorArray[2] )  );

   }
   else if ( actualColor.indexOf("#") == 0 ) {
	  return Rico.Color.createFromHex(actualColor);
   }
   else
	  return new Rico.Color(255,255,255);
}

Rico.Color.HSBtoRGB = function(hue, saturation, brightness) {

   var red   = 0;
	var green = 0;
	var blue  = 0;

   if (saturation == 0) {
	  red = parseInt(brightness * 255.0 + 0.5);
	   green = red;
	   blue = red;
	}
	else {
	  var h = (hue - Math.floor(hue)) * 6.0;
	  var f = h - Math.floor(h);
	  var p = brightness * (1.0 - saturation);
	  var q = brightness * (1.0 - saturation * f);
	  var t = brightness * (1.0 - (saturation * (1.0 - f)));

	  switch (parseInt(h)) {
		 case 0:
			red   = (brightness * 255.0 + 0.5);
			green = (t * 255.0 + 0.5);
			blue  = (p * 255.0 + 0.5);
			break;
		 case 1:
			red   = (q * 255.0 + 0.5);
			green = (brightness * 255.0 + 0.5);
			blue  = (p * 255.0 + 0.5);
			break;
		 case 2:
			red   = (p * 255.0 + 0.5);
			green = (brightness * 255.0 + 0.5);
			blue  = (t * 255.0 + 0.5);
			break;
		 case 3:
			red   = (p * 255.0 + 0.5);
			green = (q * 255.0 + 0.5);
			blue  = (brightness * 255.0 + 0.5);
			break;
		 case 4:
			red   = (t * 255.0 + 0.5);
			green = (p * 255.0 + 0.5);
			blue  = (brightness * 255.0 + 0.5);
			break;
		  case 5:
			red   = (brightness * 255.0 + 0.5);
			green = (p * 255.0 + 0.5);
			blue  = (q * 255.0 + 0.5);
			break;
		}
	}

   return { r : parseInt(red), g : parseInt(green) , b : parseInt(blue) };
}

Rico.Color.RGBtoHSB = function(r, g, b) {

   var hue;
   var saturation;
   var brightness;

   var cmax = (r > g) ? r : g;
   if (b > cmax)
	  cmax = b;

   var cmin = (r < g) ? r : g;
   if (b < cmin)
	  cmin = b;

   brightness = cmax / 255.0;
   if (cmax != 0)
	  saturation = (cmax - cmin)/cmax;
   else
	  saturation = 0;

   if (saturation == 0)
	  hue = 0;
   else {
	  var redc   = (cmax - r)/(cmax - cmin);
		var greenc = (cmax - g)/(cmax - cmin);
		var bluec  = (cmax - b)/(cmax - cmin);

		if (r == cmax)
		   hue = bluec - greenc;
		else if (g == cmax)
		   hue = 2.0 + redc - bluec;
	  else
		   hue = 4.0 + greenc - redc;

		hue = hue / 6.0;
		if (hue < 0)
		   hue = hue + 1.0;
   }

   return { h : hue, s : saturation, b : brightness };
}


//-------------------- ricoCorner.js
Rico.Corner = {

   round: function(e, options) {
	  var e = $(e);
	  this._setOptions(options);

	  var color = this.options.color;
	  if ( this.options.color == "fromElement" )
		 color = this._background(e);

	  var bgColor = this.options.bgColor;
	  if ( this.options.bgColor == "fromParent" )
		 bgColor = this._background(e.offsetParent);

	  this._roundCornersImpl(e, color, bgColor);
   },

   _roundCornersImpl: function(e, color, bgColor) {
	  if(this.options.border)
		 this._renderBorder(e,color, bgColor);
	  if(this._isTopRounded())
		 this._roundTopCorners(e,color,bgColor);
	  if(this._isBottomRounded())
		 this._roundBottomCorners(e,color,bgColor);
   },

   _renderBorder: function(el,color, bgColor) {
	  var borderValue = "1px solid " + this._borderColor(color, bgColor);
	  var borderL = "border-left: "  + borderValue;
	  var borderR = "border-right: " + borderValue;
	  var style   = "style='" + borderL + ";" + borderR +  "'";
	  el.innerHTML = "<div " + style + ">" + el.innerHTML + "</div>"
   },

   _roundTopCorners: function(el, color, bgColor) {
	  var corner = this._createCorner(bgColor);
	  for(var i=0 ; i < this.options.numSlices ; i++ )
		 corner.appendChild(this._createCornerSlice(color,bgColor,i,"top"));
	  el.style.paddingTop = 0;
	  el.insertBefore(corner,el.firstChild);
   },

   _roundBottomCorners: function(el, color, bgColor) {
	  var corner = this._createCorner(bgColor);
	  for(var i=(this.options.numSlices-1) ; i >= 0 ; i-- )
		 corner.appendChild(this._createCornerSlice(color,bgColor,i,"bottom"));
	  el.style.paddingBottom = 0;
	  el.appendChild(corner);
   },

   _createCorner: function(bgColor) {
	  var corner = document.createElement("div");
	  corner.style.backgroundColor = (this._isTransparent() ? "transparent" : bgColor);
	  return corner;
   },

   _createCornerSlice: function(color,bgColor, n, position) {
	  var slice = document.createElement("span");

	  var inStyle = slice.style;
	  inStyle.backgroundColor = color;
	  inStyle.display  = "block";
	  inStyle.height   = "1px";
	  inStyle.overflow = "hidden";
	  inStyle.fontSize = "1px";

	  var borderColor = this._borderColor(color,bgColor);
	  if ( this.options.border && n == 0 ) {
		 inStyle.borderTopStyle    = "solid";
		 inStyle.borderTopWidth    = "1px";
		 inStyle.borderLeftWidth   = "0px";
		 inStyle.borderRightWidth  = "0px";
		 inStyle.borderBottomWidth = "0px";
		 inStyle.height			   = "0px";	// assumes css compliant box model
		 inStyle.borderColor       = borderColor;
	  }
	  else if(borderColor) {
		 inStyle.borderColor = borderColor;
		 inStyle.borderStyle = "solid";
		 inStyle.borderWidth = "0px 1px";
	  }

	  if ( !this.options.compact && (n == (this.options.numSlices-1)) )
		 inStyle.height = "2px";

	  this._setMargin(slice, n, position);
	  this._setBorder(slice, n, position);
	  return slice;
   },

   _setOptions: function(options) {
	  this.options = {
		 corners : "all",
		 color   : "fromElement",
		 bgColor : "fromParent",
		 blend   : true,
		 border  : false,
		 compact : false
	  }
	  Object.extend(this.options, options || {});

	  this.options.numSlices = this.options.compact ? 2 : 4;
	  if ( this._isTransparent() )
		 this.options.blend = false;
   },

   _whichSideTop: function() {
	  if ( this._hasString(this.options.corners, "all", "top") )
		 return "";

	  if ( this.options.corners.indexOf("tl") >= 0 && this.options.corners.indexOf("tr") >= 0 )
		 return "";

	  if (this.options.corners.indexOf("tl") >= 0)
		 return "left";
	  else if (this.options.corners.indexOf("tr") >= 0)
		  return "right";
	  return "";
   },

   _whichSideBottom: function() {
	  if ( this._hasString(this.options.corners, "all", "bottom") )
		 return "";

	  if ( this.options.corners.indexOf("bl")>=0 && this.options.corners.indexOf("br")>=0 )
		 return "";

	  if(this.options.corners.indexOf("bl") >=0)
		 return "left";
	  else if(this.options.corners.indexOf("br")>=0)
		 return "right";
	  return "";
   },

   _borderColor : function(color,bgColor) {
	  if ( color == "transparent" )
		 return bgColor;
	  else if ( this.options.border )
		 return this.options.border;
	  else if ( this.options.blend )
		 return this._blend( bgColor, color );
	  else
		 return "";
   },


   _setMargin: function(el, n, corners) {
	  var marginSize = this._marginSize(n);
	  var whichSide = corners == "top" ? this._whichSideTop() : this._whichSideBottom();

	  if ( whichSide == "left" ) {
		 el.style.marginLeft = marginSize + "px"; el.style.marginRight = "0px";
	  }
	  else if ( whichSide == "right" ) {
		 el.style.marginRight = marginSize + "px"; el.style.marginLeft  = "0px";
	  }
	  else {
		 el.style.marginLeft = marginSize + "px"; el.style.marginRight = marginSize + "px";
	  }
   },

   _setBorder: function(el,n,corners) {
	  var borderSize = this._borderSize(n);
	  var whichSide = corners == "top" ? this._whichSideTop() : this._whichSideBottom();
	  if ( whichSide == "left" ) {
		 el.style.borderLeftWidth = borderSize + "px"; el.style.borderRightWidth = "0px";
	  }
	  else if ( whichSide == "right" ) {
		 el.style.borderRightWidth = borderSize + "px"; el.style.borderLeftWidth  = "0px";
	  }
	  else {
		 el.style.borderLeftWidth = borderSize + "px"; el.style.borderRightWidth = borderSize + "px";
	  }
	  if (this.options.border != false)
		el.style.borderLeftWidth = borderSize + "px"; el.style.borderRightWidth = borderSize + "px";
   },

   _marginSize: function(n) {
	  if ( this._isTransparent() )
		 return 0;

	  var marginSizes          = [ 5, 3, 2, 1 ];
	  var blendedMarginSizes   = [ 3, 2, 1, 0 ];
	  var compactMarginSizes   = [ 2, 1 ];
	  var smBlendedMarginSizes = [ 1, 0 ];

	  if ( this.options.compact && this.options.blend )
		 return smBlendedMarginSizes[n];
	  else if ( this.options.compact )
		 return compactMarginSizes[n];
	  else if ( this.options.blend )
		 return blendedMarginSizes[n];
	  else
		 return marginSizes[n];
   },

   _borderSize: function(n) {
	  var transparentBorderSizes = [ 5, 3, 2, 1 ];
	  var blendedBorderSizes     = [ 2, 1, 1, 1 ];
	  var compactBorderSizes     = [ 1, 0 ];
	  var actualBorderSizes      = [ 0, 2, 0, 0 ];

	  if ( this.options.compact && (this.options.blend || this._isTransparent()) )
		 return 1;
	  else if ( this.options.compact )
		 return compactBorderSizes[n];
	  else if ( this.options.blend )
		 return blendedBorderSizes[n];
	  else if ( this.options.border )
		 return actualBorderSizes[n];
	  else if ( this._isTransparent() )
		 return transparentBorderSizes[n];
	  return 0;
   },

   _hasString: function(str) { for(var i=1 ; i<arguments.length ; i++) if (str.indexOf(arguments[i]) >= 0) return true; return false; },
   _blend: function(c1, c2) { var cc1 = Rico.Color.createFromHex(c1); cc1.blend(Rico.Color.createFromHex(c2)); return cc1; },
   _background: function(el) { try { return Rico.Color.createColorFromBackground(el).asHex(); } catch(err) { return "#ffffff"; } },
   _isTransparent: function() { return this.options.color == "transparent"; },
   _isTopRounded: function() { return this._hasString(this.options.corners, "all", "top", "tl", "tr"); },
   _isBottomRounded: function() { return this._hasString(this.options.corners, "all", "bottom", "bl", "br"); },
   _hasSingleTextChild: function(el) { return el.childNodes.length == 1 && el.childNodes[0].nodeType == 3; }
}


//-------------------- ricoDragAndDrop.js
Rico.DragAndDrop = Class.create();

Rico.DragAndDrop.prototype = {

   initialize: function() {
	  this.dropZones                = new Array();
	  this.draggables               = new Array();
	  this.currentDragObjects       = new Array();
	  this.dragElement              = null;
	  this.lastSelectedDraggable    = null;
	  this.currentDragObjectVisible = false;
	  this.interestedInMotionEvents = false;
	  this._mouseDown = this._mouseDownHandler.bindAsEventListener(this);
	  this._mouseMove = this._mouseMoveHandler.bindAsEventListener(this);
	  this._mouseUp = this._mouseUpHandler.bindAsEventListener(this);
   },

   registerDropZone: function(aDropZone) {
	  this.dropZones[ this.dropZones.length ] = aDropZone;
   },

   deregisterDropZone: function(aDropZone) {
	  var newDropZones = new Array();
	  var j = 0;
	  for ( var i = 0 ; i < this.dropZones.length ; i++ ) {
		 if ( this.dropZones[i] != aDropZone )
			newDropZones[j++] = this.dropZones[i];
	  }

	  this.dropZones = newDropZones;
   },

   clearDropZones: function() {
	  this.dropZones = new Array();
   },

   registerDraggable: function( aDraggable ) {
	  this.draggables[ this.draggables.length ] = aDraggable;
	  this._addMouseDownHandler( aDraggable );
   },

   clearSelection: function() {
	  for ( var i = 0 ; i < this.currentDragObjects.length ; i++ )
		 this.currentDragObjects[i].deselect();
	  this.currentDragObjects = new Array();
	  this.lastSelectedDraggable = null;
   },

   hasSelection: function() {
	  return this.currentDragObjects.length > 0;
   },

   setStartDragFromElement: function( e, mouseDownElement ) {
	  this.origPos = RicoUtil.toDocumentPosition(mouseDownElement);
	  this.startx = e.screenX - this.origPos.x
	  this.starty = e.screenY - this.origPos.y
	  //this.startComponentX = e.layerX ? e.layerX : e.offsetX;
	  //this.startComponentY = e.layerY ? e.layerY : e.offsetY;
	  //this.adjustedForDraggableSize = false;

	  this.interestedInMotionEvents = this.hasSelection();
	  this._terminateEvent(e);
   },

   updateSelection: function( draggable, extendSelection ) {
	  if ( ! extendSelection )
		 this.clearSelection();

	  if ( draggable.isSelected() ) {
		 this.currentDragObjects.removeItem(draggable);
		 draggable.deselect();
		 if ( draggable == this.lastSelectedDraggable )
			this.lastSelectedDraggable = null;
	  }
	  else {
		 this.currentDragObjects[ this.currentDragObjects.length ] = draggable;
		 draggable.select();
		 this.lastSelectedDraggable = draggable;
	  }
   },

   _mouseDownHandler: function(e) {
	  if ( arguments.length == 0 )
		 e = event;

	  // if not button 1 ignore it...
	  var nsEvent = e.which != undefined;
	  if ( (nsEvent && e.which != 1) || (!nsEvent && e.button != 1))
		 return;

	  var eventTarget      = e.target ? e.target : e.srcElement;
	  var draggableObject  = eventTarget.draggable;

	  var candidate = eventTarget;
	  while (draggableObject == null && candidate.parentNode) {
		 candidate = candidate.parentNode;
		 draggableObject = candidate.draggable;
	  }
   
	  if ( draggableObject == null )
		 return;

	  this.updateSelection( draggableObject, e.ctrlKey );

	  // clear the drop zones postion cache...
	  if ( this.hasSelection() )
		 for ( var i = 0 ; i < this.dropZones.length ; i++ )
			this.dropZones[i].clearPositionCache();

	  this.setStartDragFromElement( e, draggableObject.getMouseDownHTMLElement() );
   },


   _mouseMoveHandler: function(e) {
	  var nsEvent = e.which != undefined;
	  if ( !this.interestedInMotionEvents ) {
		 //this._terminateEvent(e);
		 return;
	  }

	  if ( ! this.hasSelection() )
		 return;

	  if ( ! this.currentDragObjectVisible )
		 this._startDrag(e);

	  if ( !this.activatedDropZones )
		 this._activateRegisteredDropZones();

	  //if ( !this.adjustedForDraggableSize )
	  //   this._adjustForDraggableSize(e);

	  this._updateDraggableLocation(e);
	  this._updateDropZonesHover(e);

	  this._terminateEvent(e);
   },

   _makeDraggableObjectVisible: function(e)
   {
	  if ( !this.hasSelection() )
		 return;

	  var dragElement;
	  if ( this.currentDragObjects.length > 1 )
		 dragElement = this.currentDragObjects[0].getMultiObjectDragGUI(this.currentDragObjects);
	  else
		 dragElement = this.currentDragObjects[0].getSingleObjectDragGUI();

	  // go ahead and absolute position it...
	  if ( RicoUtil.getElementsComputedStyle(dragElement, "position")  != "absolute" )
		 dragElement.style.position = "absolute";

	  // need to parent him into the document...
	  if ( dragElement.parentNode == null || dragElement.parentNode.nodeType == 11 )
		 document.body.appendChild(dragElement);

	  this.dragElement = dragElement;
	  this._updateDraggableLocation(e);

	  this.currentDragObjectVisible = true;
   },

   /**
   _adjustForDraggableSize: function(e) {
	  var dragElementWidth  = this.dragElement.offsetWidth;
	  var dragElementHeight = this.dragElement.offsetHeight;
	  if ( this.startComponentX > dragElementWidth )
		 this.startx -= this.startComponentX - dragElementWidth + 2;
	  if ( e.offsetY ) {
		 if ( this.startComponentY > dragElementHeight )
			this.starty -= this.startComponentY - dragElementHeight + 2;
	  }
	  this.adjustedForDraggableSize = true;
   },
   **/

   _leftOffset: function(e) {
	   return e.offsetX ? document.body.scrollLeft : 0
	},

   _topOffset: function(e) {
	   return e.offsetY ? document.body.scrollTop:0
	},

		
   _updateDraggableLocation: function(e) {
	  var dragObjectStyle = this.dragElement.style;
	  dragObjectStyle.left = (e.screenX + this._leftOffset(e) - this.startx) + "px"
	  dragObjectStyle.top  = (e.screenY + this._topOffset(e) - this.starty) + "px";
   },

   _updateDropZonesHover: function(e) {
	  var n = this.dropZones.length;
	  for ( var i = 0 ; i < n ; i++ ) {
		 if ( ! this._mousePointInDropZone( e, this.dropZones[i] ) )
			this.dropZones[i].hideHover();
	  }

	  for ( var i = 0 ; i < n ; i++ ) {
		 if ( this._mousePointInDropZone( e, this.dropZones[i] ) ) {
			if ( this.dropZones[i].canAccept(this.currentDragObjects) )
			   this.dropZones[i].showHover();
		 }
	  }
   },

   _startDrag: function(e) {
	  for ( var i = 0 ; i < this.currentDragObjects.length ; i++ )
		 this.currentDragObjects[i].startDrag();

	  this._makeDraggableObjectVisible(e);
   },

   _mouseUpHandler: function(e) {
	  if ( ! this.hasSelection() )
		 return;

	  var nsEvent = e.which != undefined;
	  if ( (nsEvent && e.which != 1) || (!nsEvent && e.button != 1))
		 return;

	  this.interestedInMotionEvents = false;

	  if ( this.dragElement == null ) {
		 this._terminateEvent(e);
		 return;
	  }

	  if ( this._placeDraggableInDropZone(e) )
		 this._completeDropOperation(e);
	  else {
		 this._terminateEvent(e);
		 new Rico.Effect.Position( this.dragElement,
							  this.origPos.x,
							  this.origPos.y,
							  200,
							  20,
							  { complete : this._doCancelDragProcessing.bind(this) } );
	  }

	 Event.stopObserving(document.body, "mousemove", this._mouseMove);
	 Event.stopObserving(document.body, "mouseup",  this._mouseUp);
   },

   _retTrue: function () {
	  return true;
   },

   _completeDropOperation: function(e) {
	  if ( this.dragElement != this.currentDragObjects[0].getMouseDownHTMLElement() ) {
		 if ( this.dragElement.parentNode != null )
			this.dragElement.parentNode.removeChild(this.dragElement);
	  }

	  this._deactivateRegisteredDropZones();
	  this._endDrag();
	  this.clearSelection();
	  this.dragElement = null;
	  this.currentDragObjectVisible = false;
	  this._terminateEvent(e);
   },

   _doCancelDragProcessing: function() {
	  this._cancelDrag();

		if ( this.dragElement != this.currentDragObjects[0].getMouseDownHTMLElement() && this.dragElement)
		   if ( this.dragElement.parentNode != null )
			  this.dragElement.parentNode.removeChild(this.dragElement);


	  this._deactivateRegisteredDropZones();
	  this.dragElement = null;
	  this.currentDragObjectVisible = false;
   },

   _placeDraggableInDropZone: function(e) {
	  var foundDropZone = false;
	  var n = this.dropZones.length;
	  for ( var i = 0 ; i < n ; i++ ) {
		 if ( this._mousePointInDropZone( e, this.dropZones[i] ) ) {
			if ( this.dropZones[i].canAccept(this.currentDragObjects) ) {
			   this.dropZones[i].hideHover();
			   this.dropZones[i].accept(this.currentDragObjects);
			   foundDropZone = true;
			   break;
			}
		 }
	  }

	  return foundDropZone;
   },

   _cancelDrag: function() {
	  for ( var i = 0 ; i < this.currentDragObjects.length ; i++ )
		 this.currentDragObjects[i].cancelDrag();
   },

   _endDrag: function() {
	  for ( var i = 0 ; i < this.currentDragObjects.length ; i++ )
		 this.currentDragObjects[i].endDrag();
   },

   _mousePointInDropZone: function( e, dropZone ) {

	  var absoluteRect = dropZone.getAbsoluteRect();
	  
	  return e.clientX  > absoluteRect.left + this._leftOffset(e) &&
			 e.clientX  < absoluteRect.right + this._leftOffset(e) &&
			 e.clientY  > absoluteRect.top + this._topOffset(e)   &&
			 e.clientY  < absoluteRect.bottom + this._topOffset(e);
   },

   _addMouseDownHandler: function( aDraggable )
   {
	   htmlElement  = aDraggable.getMouseDownHTMLElement();
	  if ( htmlElement  != null ) { 
		 htmlElement.draggable = aDraggable;
		 Event.observe(htmlElement , "mousedown", this._onmousedown.bindAsEventListener(this));
		 Event.observe(htmlElement, "mousedown", this._mouseDown);
	  }
   },

   _activateRegisteredDropZones: function() {
	  var n = this.dropZones.length;
	  for ( var i = 0 ; i < n ; i++ ) {
		 var dropZone = this.dropZones[i];
		 if ( dropZone.canAccept(this.currentDragObjects) )
			dropZone.activate();
	  }

	  this.activatedDropZones = true;
   },

   _deactivateRegisteredDropZones: function() {
	  var n = this.dropZones.length;
	  for ( var i = 0 ; i < n ; i++ )
		 this.dropZones[i].deactivate();
	  this.activatedDropZones = false;
   },

   _onmousedown: function () {
	 Event.observe(document.body, "mousemove", this._mouseMove);
	 Event.observe(document.body, "mouseup",  this._mouseUp);
   },

   _terminateEvent: function(e) {
	  if ( e.stopPropagation != undefined )
		 e.stopPropagation();
	  else if ( e.cancelBubble != undefined )
		 e.cancelBubble = true;

	  if ( e.preventDefault != undefined )
		 e.preventDefault();
	  else
		 e.returnValue = false;
   },


	   initializeEventHandlers: function() {
		  if ( typeof document.implementation != "undefined" &&
			 document.implementation.hasFeature("HTML",   "1.0") &&
			 document.implementation.hasFeature("Events", "2.0") &&
			 document.implementation.hasFeature("CSS",    "2.0") ) {
			 Event.observe(document, 'mouseup', this._mouseUpHandler.bindAsEventListener(this), false);
			 Event.observe(document, 'mousemove', this._mouseUpHandler.bindAsEventListener(this), false);
		  }
		  else {
			 document.attachEvent( "onmouseup",   this._mouseUpHandler.bindAsEventListener(this) );
			 document.attachEvent( "onmousemove", this._mouseMoveHandler.bindAsEventListener(this) );
		  }
	   }
	}

	var dndMgr = new Rico.DragAndDrop();
	dndMgr.initializeEventHandlers();


//-------------------- ricoDraggable.js
Rico.Draggable = Class.create();

Rico.Draggable.prototype = {

   initialize: function( type, htmlElement ) {
	  this.type          = type;
	  this.htmlElement   = $(htmlElement);
	  this.selected      = false;
   },

   /**
	*   Returns the HTML element that should have a mouse down event
	*   added to it in order to initiate a drag operation
	*
	**/
   getMouseDownHTMLElement: function() {
	  return this.htmlElement;
   },

   select: function() {
	  this.selected = true;

	  if ( this.showingSelected )
		 return;

	  var htmlElement = this.getMouseDownHTMLElement();

	  var color = Rico.Color.createColorFromBackground(htmlElement);
	  color.isBright() ? color.darken(0.033) : color.brighten(0.033);

	  this.saveBackground = RicoUtil.getElementsComputedStyle(htmlElement, "backgroundColor", "background-color");
	  htmlElement.style.backgroundColor = color.asHex();
	  this.showingSelected = true;
   },

   deselect: function() {
	  this.selected = false;
	  if ( !this.showingSelected )
		 return;

	  var htmlElement = this.getMouseDownHTMLElement();

	  htmlElement.style.backgroundColor = this.saveBackground;
	  this.showingSelected = false;
   },

   isSelected: function() {
	  return this.selected;
   },

   startDrag: function() {
   },

   cancelDrag: function() {
   },

   endDrag: function() {
   },

   getSingleObjectDragGUI: function() {
	  return this.htmlElement;
   },

   getMultiObjectDragGUI: function( draggables ) {
	  return this.htmlElement;
   },

   getDroppedGUI: function() {
	  return this.htmlElement;
   },

   toString: function() {
	  return this.type + ":" + this.htmlElement + ":";
   }

}


//-------------------- ricoDropzone.js
Rico.Dropzone = Class.create();

Rico.Dropzone.prototype = {

   initialize: function( htmlElement ) {
	  this.htmlElement  = $(htmlElement);
	  this.absoluteRect = null;
   },

   getHTMLElement: function() {
	  return this.htmlElement;
   },

   clearPositionCache: function() {
	  this.absoluteRect = null;
   },

   getAbsoluteRect: function() {
	  if ( this.absoluteRect == null ) {
		 var htmlElement = this.getHTMLElement();
		 var pos = RicoUtil.toViewportPosition(htmlElement);

		 this.absoluteRect = {
			top:    pos.y,
			left:   pos.x,
			bottom: pos.y + htmlElement.offsetHeight,
			right:  pos.x + htmlElement.offsetWidth
		 };
	  }
	  return this.absoluteRect;
   },

   activate: function() {
	  var htmlElement = this.getHTMLElement();
	  if (htmlElement == null  || this.showingActive)
		 return;

	  this.showingActive = true;
	  this.saveBackgroundColor = htmlElement.style.backgroundColor;

	  var fallbackColor = "#ffea84";
	  var currentColor = Rico.Color.createColorFromBackground(htmlElement);
	  if ( currentColor == null )
		 htmlElement.style.backgroundColor = fallbackColor;
	  else {
		 currentColor.isBright() ? currentColor.darken(0.2) : currentColor.brighten(0.2);
		 htmlElement.style.backgroundColor = currentColor.asHex();
	  }
   },

   deactivate: function() {
	  var htmlElement = this.getHTMLElement();
	  if (htmlElement == null || !this.showingActive)
		 return;

	  htmlElement.style.backgroundColor = this.saveBackgroundColor;
	  this.showingActive = false;
	  this.saveBackgroundColor = null;
   },

   showHover: function() {
	  var htmlElement = this.getHTMLElement();
	  if ( htmlElement == null || this.showingHover )
		 return;

	  this.saveBorderWidth = htmlElement.style.borderWidth;
	  this.saveBorderStyle = htmlElement.style.borderStyle;
	  this.saveBorderColor = htmlElement.style.borderColor;

	  this.showingHover = true;
	  htmlElement.style.borderWidth = "1px";
	  htmlElement.style.borderStyle = "solid";
	  //htmlElement.style.borderColor = "#ff9900";
	  htmlElement.style.borderColor = "#ffff00";
   },

   hideHover: function() {
	  var htmlElement = this.getHTMLElement();
	  if ( htmlElement == null || !this.showingHover )
		 return;

	  htmlElement.style.borderWidth = this.saveBorderWidth;
	  htmlElement.style.borderStyle = this.saveBorderStyle;
	  htmlElement.style.borderColor = this.saveBorderColor;
	  this.showingHover = false;
   },

   canAccept: function(draggableObjects) {
	  return true;
   },

   accept: function(draggableObjects) {
	  var htmlElement = this.getHTMLElement();
	  if ( htmlElement == null )
		 return;

	  n = draggableObjects.length;
	  for ( var i = 0 ; i < n ; i++ )
	  {
		 var theGUI = draggableObjects[i].getDroppedGUI();
		 if ( RicoUtil.getElementsComputedStyle( theGUI, "position" ) == "absolute" )
		 {
			theGUI.style.position = "static";
			theGUI.style.top = "";
			theGUI.style.top = "";
		 }
		 htmlElement.appendChild(theGUI);
	  }
   }
}


//-------------------- ricoEffects.js

Rico.Effect = {};

Rico.Effect.SizeAndPosition = Class.create();
Rico.Effect.SizeAndPosition.prototype = {

   initialize: function(element, x, y, w, h, duration, steps, options) {
	  this.element = $(element);
	  this.x = x;
	  this.y = y;
	  this.w = w;
	  this.h = h;
	  this.duration = duration;
	  this.steps    = steps;
	  this.options  = arguments[7] || {};

	  this.sizeAndPosition();
   },

   sizeAndPosition: function() {
	  if (this.isFinished()) {
		 if(this.options.complete) this.options.complete(this);
		 return;
	  }

	  if (this.timer)
		 clearTimeout(this.timer);

	  var stepDuration = Math.round(this.duration/this.steps) ;

	  // Get original values: x,y = top left corner;  w,h = width height
	  var currentX = this.element.offsetLeft;
	  var currentY = this.element.offsetTop;
	  var currentW = this.element.offsetWidth;
	  var currentH = this.element.offsetHeight;

	  // If values not set, or zero, we do not modify them, and take original as final as well
	  this.x = (this.x) ? this.x : currentX;
	  this.y = (this.y) ? this.y : currentY;
	  this.w = (this.w) ? this.w : currentW;
	  this.h = (this.h) ? this.h : currentH;

	  // how much do we need to modify our values for each step?
	  var difX = this.steps >  0 ? (this.x - currentX)/this.steps : 0;
	  var difY = this.steps >  0 ? (this.y - currentY)/this.steps : 0;
	  var difW = this.steps >  0 ? (this.w - currentW)/this.steps : 0;
	  var difH = this.steps >  0 ? (this.h - currentH)/this.steps : 0;

	  this.moveBy(difX, difY);
	  this.resizeBy(difW, difH);

	  this.duration -= stepDuration;
	  this.steps--;

	  this.timer = setTimeout(this.sizeAndPosition.bind(this), stepDuration);
   },

   isFinished: function() {
	  return this.steps <= 0;
   },

   moveBy: function( difX, difY ) {
	  var currentLeft = this.element.offsetLeft;
	  var currentTop  = this.element.offsetTop;
	  var intDifX     = parseInt(difX);
	  var intDifY     = parseInt(difY);

	  var style = this.element.style;
	  if ( intDifX != 0 )
		 style.left = (currentLeft + intDifX) + "px";
	  if ( intDifY != 0 )
		 style.top  = (currentTop + intDifY) + "px";
   },

   resizeBy: function( difW, difH ) {
	  var currentWidth  = this.element.offsetWidth;
	  var currentHeight = this.element.offsetHeight;
	  var intDifW       = parseInt(difW);
	  var intDifH       = parseInt(difH);

	  var style = this.element.style;
	  if ( intDifW != 0 )
		 style.width   = (currentWidth  + intDifW) + "px";
	  if ( intDifH != 0 )
		 style.height  = (currentHeight + intDifH) + "px";
   }
}

Rico.Effect.Size = Class.create();
Rico.Effect.Size.prototype = {

   initialize: function(element, w, h, duration, steps, options) {
	  new Rico.Effect.SizeAndPosition(element, null, null, w, h, duration, steps, options);
  }
}

Rico.Effect.Position = Class.create();
Rico.Effect.Position.prototype = {

   initialize: function(element, x, y, duration, steps, options) {
	  new Rico.Effect.SizeAndPosition(element, x, y, null, null, duration, steps, options);
  }
}

Rico.Effect.Round = Class.create();
Rico.Effect.Round.prototype = {

   initialize: function(tagName, className, options) {
	  var elements = document.getElementsByTagAndClassName(tagName,className);
	  for ( var i = 0 ; i < elements.length ; i++ )
		 Rico.Corner.round( elements[i], options );
   }
};

Rico.Effect.FadeTo = Class.create();
Rico.Effect.FadeTo.prototype = {

   initialize: function( element, opacity, duration, steps, options) {
	  this.element  = $(element);
	  this.opacity  = opacity;
	  this.duration = duration;
	  this.steps    = steps;
	  this.options  = arguments[4] || {};
	  this.fadeTo();
   },

   fadeTo: function() {
	  if (this.isFinished()) {
		this.element.ricoOpacity = undefined;
		 if(this.options.complete) this.options.complete(this);
		 return;
	  }

	  if (this.timer)
		 clearTimeout(this.timer);

	  var stepDuration = Math.round(this.duration/this.steps) ;
	  var currentOpacity = this.getElementOpacity();
	  var delta = this.steps > 0 ? (this.opacity - currentOpacity)/this.steps : 0;

	  this.changeOpacityBy(delta);
	  this.duration -= stepDuration;
	  this.steps--;

	  this.timer = setTimeout(this.fadeTo.bind(this), stepDuration);
   },

   changeOpacityBy: function(v) {
	  var currentOpacity = this.getElementOpacity();
	  var newOpacity = Math.max(0, Math.min(currentOpacity+v, 1));
	  this.element.ricoOpacity = newOpacity;

	  this.element.style.filter = "alpha(Opacity="+Math.round(newOpacity*100)+")";
	  this.element.style.opacity = newOpacity; /*//*/;
   },

   isFinished: function() {
	  return this.steps <= 0;
   },

   getElementOpacity: function() {
	  if ( this.element.ricoOpacity == undefined ) {
		 var opacity = RicoUtil.getElementsComputedStyle(this.element, 'opacity');
		 this.element.ricoOpacity = opacity != undefined ? opacity : 1.0;
	  }
	  return parseFloat(this.element.ricoOpacity);
   }
}

Rico.Effect.AccordionSize = Class.create();

Rico.Effect.AccordionSize.prototype = {

   initialize: function(e1, e2, start, end, duration, steps, options) {
	  this.e1       = $(e1);
	  this.e2       = $(e2);
	  this.start    = start;
	  this.end      = end;
	  this.duration = duration;
	  this.steps    = steps;
	  this.options  = arguments[6] || {};

	  this.accordionSize();
   },

   accordionSize: function() {

	  if (this.isFinished()) {
		 // just in case there are round errors or such...
		 this.e1.style.height = this.start + "px";
		 this.e2.style.height = this.end + "px";

		 if(this.options.complete)
			this.options.complete(this);
		 return;
	  }

	  if (this.timer)
		 clearTimeout(this.timer);

	  var stepDuration = Math.round(this.duration/this.steps) ;

	  var diff = this.steps > 0 ? (parseInt(this.e1.offsetHeight) - this.start)/this.steps : 0;
	  this.resizeBy(diff);

	  this.duration -= stepDuration;
	  this.steps--;

	  this.timer = setTimeout(this.accordionSize.bind(this), stepDuration);
   },

   isFinished: function() {
	  return this.steps <= 0;
   },

   resizeBy: function(diff) {
	  var h1Height = this.e1.offsetHeight;
	  var h2Height = this.e2.offsetHeight;
	  var intDiff = parseInt(diff);
	  if ( diff != 0 ) {
		 this.e1.style.height = (h1Height - intDiff) + "px";
		 this.e2.style.height = (h2Height + intDiff) + "px";
	  }
   }

};


//-------------------- ricoLiveGrid.js
// Rico.LiveGridMetaData -----------------------------------------------------

Rico.LiveGridMetaData = Class.create();

Rico.LiveGridMetaData.prototype = {

   initialize: function( pageSize, totalRows, columnCount, options ) {
	  this.pageSize  = pageSize;
	  this.totalRows = totalRows;
	  this.setOptions(options);
	  this.ArrowHeight = 16;
	  this.columnCount = columnCount;
   },

   setOptions: function(options) {
	  this.options = {
		 largeBufferSize    : 7.0,   // 7 pages
		 nearLimitFactor    : 0.2    // 20% of buffer
	  };
	  Object.extend(this.options, options || {});
   },

   getPageSize: function() {
	  return this.pageSize;
   },

   getTotalRows: function() {
	  return this.totalRows;
   },

   setTotalRows: function(n) {
	  this.totalRows = n;
   },

   getLargeBufferSize: function() {
	  return parseInt(this.options.largeBufferSize * this.pageSize);
   },

   getLimitTolerance: function() {
	  return parseInt(this.getLargeBufferSize() * this.options.nearLimitFactor);
   }
};

// Rico.LiveGridScroller -----------------------------------------------------

Rico.LiveGridScroller = Class.create();

Rico.LiveGridScroller.prototype = {

   initialize: function(liveGrid, viewPort) {
	  this.isIE = navigator.userAgent.toLowerCase().indexOf("msie") >= 0;
	  this.liveGrid = liveGrid;
	  this.metaData = liveGrid.metaData;
	  this.createScrollBar();
	  this.scrollTimeout = null;
	  this.lastScrollPos = 0;
	  this.viewPort = viewPort;
	  this.rows = new Array();
   },

   isUnPlugged: function() {
	  return this.scrollerDiv.onscroll == null;
   },

   plugin: function() {
	  this.scrollerDiv.onscroll = this.handleScroll.bindAsEventListener(this);
   },

   unplug: function() {
	  this.scrollerDiv.onscroll = null;
   },

   sizeIEHeaderHack: function() {
	  if ( !this.isIE ) return;
	  var headerTable = $(this.liveGrid.tableId + "_header");
	  if ( headerTable )
		 headerTable.rows[0].cells[0].style.width =
			(headerTable.rows[0].cells[0].offsetWidth + 1) + "px";
   },

   createScrollBar: function() {
	  var visibleHeight = this.liveGrid.viewPort.visibleHeight();
	  // create the outer div...
	  this.scrollerDiv  = document.createElement("div");
	  var scrollerStyle = this.scrollerDiv.style;
	  scrollerStyle.borderRight = this.liveGrid.options.scrollerBorderRight;
	  scrollerStyle.position    = "relative";
	  scrollerStyle.left        = this.isIE ? "-6px" : "-3px";
	  scrollerStyle.width       = "19px";
	  scrollerStyle.height      = visibleHeight + "px";
	  scrollerStyle.overflow    = "auto";

	  // create the inner div...
	  this.heightDiv = document.createElement("div");
	  this.heightDiv.style.width  = "1px";

	  this.heightDiv.style.height = parseInt(visibleHeight *
						this.metaData.getTotalRows()/this.metaData.getPageSize()) + "px" ;
	  this.scrollerDiv.appendChild(this.heightDiv);
	  this.scrollerDiv.onscroll = this.handleScroll.bindAsEventListener(this);

	 var table = this.liveGrid.table;
	 table.parentNode.parentNode.insertBefore( this.scrollerDiv, table.parentNode.nextSibling );
	  var eventName = this.isIE ? "mousewheel" : "DOMMouseScroll";
	  Event.observe(table, eventName, 
					function(evt) {
					   if (evt.wheelDelta>=0 || evt.detail < 0) //wheel-up
						  this.scrollerDiv.scrollTop -= (2*this.viewPort.rowHeight);
					   else
						  this.scrollerDiv.scrollTop += (2*this.viewPort.rowHeight);
					   this.handleScroll(false);
					}.bindAsEventListener(this), 
					false);
	 },

   updateSize: function() {
	  var table = this.liveGrid.table;
	  var visibleHeight = this.viewPort.visibleHeight();
	  this.heightDiv.style.height = parseInt(visibleHeight *
								  this.metaData.getTotalRows()/this.metaData.getPageSize()) + "px";
   },

   rowToPixel: function(rowOffset) {
	  return (rowOffset / this.metaData.getTotalRows()) * this.heightDiv.offsetHeight
   },
   
   moveScroll: function(rowOffset) {
	  this.scrollerDiv.scrollTop = this.rowToPixel(rowOffset);
	  if ( this.metaData.options.onscroll )
		 this.metaData.options.onscroll( this.liveGrid, rowOffset );
   },

   handleScroll: function() {
	 if ( this.scrollTimeout )
		 clearTimeout( this.scrollTimeout );

	var scrollDiff = this.lastScrollPos-this.scrollerDiv.scrollTop;
	if (scrollDiff != 0.00) {
	   var r = this.scrollerDiv.scrollTop % this.viewPort.rowHeight;
	   if (r != 0) {
		  this.unplug();
		  if ( scrollDiff < 0 ) {
			 this.scrollerDiv.scrollTop += (this.viewPort.rowHeight-r);
		  } else {
			 this.scrollerDiv.scrollTop -= r;
		  }
		  this.plugin();
	   }
	}
	var contentOffset = parseInt(this.scrollerDiv.scrollTop / this.viewPort.rowHeight);
	this.liveGrid.requestContentRefresh(contentOffset);
	this.viewPort.scrollTo(this.scrollerDiv.scrollTop);

	if ( this.metaData.options.onscroll )
	   this.metaData.options.onscroll( this.liveGrid, contentOffset );

	this.scrollTimeout = setTimeout(this.scrollIdle.bind(this), 1200 );
	this.lastScrollPos = this.scrollerDiv.scrollTop;

   },

   scrollIdle: function() {
	  if ( this.metaData.options.onscrollidle )
		 this.metaData.options.onscrollidle();
   }
};

// Rico.LiveGridBuffer -----------------------------------------------------

Rico.LiveGridBuffer = Class.create();

Rico.LiveGridBuffer.prototype = {

   initialize: function(metaData, viewPort) {
	  this.startPos = 0;
	  this.size     = 0;
	  this.metaData = metaData;
	  this.rows     = new Array();
	  this.updateInProgress = false;
	  this.viewPort = viewPort;
	  this.maxBufferSize = metaData.getLargeBufferSize() * 2;
	  this.maxFetchSize = metaData.getLargeBufferSize();
	  this.lastOffset = 0;
   },

   getBlankRow: function() {
	  if (!this.blankRow ) {
		 this.blankRow = new Array();
		 for ( var i=0; i < this.metaData.columnCount ; i++ ) 
			this.blankRow[i] = "&nbsp;";
	 }
	 return this.blankRow;
   },

   loadRows: function(ajaxResponse) {
	  var rowsElement = ajaxResponse.getElementsByTagName('rows')[0];
	  this.updateUI = rowsElement.getAttribute("update_ui") == "true"
	  var newRows = new Array()
	  var trs = rowsElement.getElementsByTagName("tr");
	  for ( var i=0 ; i < trs.length; i++ ) {
		 var row = newRows[i] = new Array(); 
		 var cells = trs[i].getElementsByTagName("td");
		 for ( var j=0; j < cells.length ; j++ ) {
			var cell = cells[j];
			var convertSpaces = cell.getAttribute("convert_spaces") == "true";
			var cellContent = RicoUtil.getContentAsString(cell);
			row[j] = convertSpaces ? this.convertSpaces(cellContent) : cellContent;
			if (!row[j]) 
			   row[j] = '&nbsp;';
		 }
	  }
	  return newRows;
   },
	  
   update: function(ajaxResponse, start) {
	 var newRows = this.loadRows(ajaxResponse);
	  if (this.rows.length == 0) { // initial load
		 this.rows = newRows;
		 this.size = this.rows.length;
		 this.startPos = start;
		 return;
	  }
	  if (start > this.startPos) { //appending
		 if (this.startPos + this.rows.length < start) {
			this.rows =  newRows;
			this.startPos = start;//
		 } else {
			  this.rows = this.rows.concat( newRows.slice(0, newRows.length));
			if (this.rows.length > this.maxBufferSize) {
			   var fullSize = this.rows.length;
			   this.rows = this.rows.slice(this.rows.length - this.maxBufferSize, this.rows.length)
			   this.startPos = this.startPos +  (fullSize - this.rows.length);
			}
		 }
	  } else { //prepending
		 if (start + newRows.length < this.startPos) {
			this.rows =  newRows;
		 } else {
			this.rows = newRows.slice(0, this.startPos).concat(this.rows);
			if (this.rows.length > this.maxBufferSize) 
			   this.rows = this.rows.slice(0, this.maxBufferSize)
		 }
		 this.startPos =  start;
	  }
	  this.size = this.rows.length;
   },
   
   clear: function() {
	  this.rows = new Array();
	  this.startPos = 0;
	  this.size = 0;
   },

   isOverlapping: function(start, size) {
	  return ((start < this.endPos()) && (this.startPos < start + size)) || (this.endPos() == 0)
   },

   isInRange: function(position) {
	  return (position >= this.startPos) && (position + this.metaData.getPageSize() <= this.endPos()); 
			 //&& this.size()  != 0;
   },

   isNearingTopLimit: function(position) {
	  return position - this.startPos < this.metaData.getLimitTolerance();
   },

   endPos: function() {
	  return this.startPos + this.rows.length;
   },
   
   isNearingBottomLimit: function(position) {
	  return this.endPos() - (position + this.metaData.getPageSize()) < this.metaData.getLimitTolerance();
   },

   isAtTop: function() {
	  return this.startPos == 0;
   },

   isAtBottom: function() {
	  return this.endPos() == this.metaData.getTotalRows();
   },

   isNearingLimit: function(position) {
	  return ( !this.isAtTop()    && this.isNearingTopLimit(position)) ||
			 ( !this.isAtBottom() && this.isNearingBottomLimit(position) )
   },

   getFetchSize: function(offset) {
	  var adjustedOffset = this.getFetchOffset(offset);
	  var adjustedSize = 0;
	  if (adjustedOffset >= this.startPos) { //apending
		 var endFetchOffset = this.maxFetchSize  + adjustedOffset;
		 if (endFetchOffset > this.metaData.totalRows)
			endFetchOffset = this.metaData.totalRows;
		 adjustedSize = endFetchOffset - adjustedOffset;  
			if(adjustedOffset == 0 && adjustedSize < this.maxFetchSize){
			   adjustedSize = this.maxFetchSize;
			}
	  } else {//prepending
		 var adjustedSize = this.startPos - adjustedOffset;
		 if (adjustedSize > this.maxFetchSize)
			adjustedSize = this.maxFetchSize;
	  }
	  return adjustedSize;
   }, 

   getFetchOffset: function(offset) {
	  var adjustedOffset = offset;
	  if (offset > this.startPos)  //apending
		 adjustedOffset = (offset > this.endPos()) ? offset :  this.endPos(); 
	  else { //prepending
		 if (offset + this.maxFetchSize >= this.startPos) {
			var adjustedOffset = this.startPos - this.maxFetchSize;
			if (adjustedOffset < 0)
			   adjustedOffset = 0;
		 }
	  }
	  this.lastOffset = adjustedOffset;
	  return adjustedOffset;
   },

   getRows: function(start, count) {
	  var begPos = start - this.startPos
	  var endPos = begPos + count

	  // er? need more data...
	  if ( endPos > this.size )
		 endPos = this.size

	  var results = new Array()
	  var index = 0;
	  for ( var i=begPos ; i < endPos; i++ ) {
		 results[index++] = this.rows[i]
	  }
	  return results
   },

   convertSpaces: function(s) {
	  return s.split(" ").join("&nbsp;");
   }

};


//Rico.GridViewPort --------------------------------------------------
Rico.GridViewPort = Class.create();

Rico.GridViewPort.prototype = {

   initialize: function(table, rowHeight, visibleRows, buffer, liveGrid) {
	  this.lastDisplayedStartPos = 0;
	  this.div = table.parentNode;
	  this.table = table
	  this.rowHeight = rowHeight;
	  this.div.style.height = (this.rowHeight * visibleRows) + "px";
	  this.div.style.overflow = "hidden";
	  this.buffer = buffer;
	  this.liveGrid = liveGrid;
	  this.visibleRows = visibleRows + 1;
	  this.lastPixelOffset = 0;
	  this.startPos = 0;
   },

   populateRow: function(htmlRow, row) {
	  for (var j=0; j < row.length; j++) {
		 htmlRow.cells[j].innerHTML = row[j]
	  }
   },
   
   bufferChanged: function() {
	  this.refreshContents( parseInt(this.lastPixelOffset / this.rowHeight));
   },
   
   clearRows: function() {
	  if (!this.isBlank) {
		 this.liveGrid.table.className = this.liveGrid.options.loadingClass;
		 for (var i=0; i < this.visibleRows; i++)
			this.populateRow(this.table.rows[i], this.buffer.getBlankRow());
		 this.isBlank = true;
	  }
   },
   
   clearContents: function() {   
	  this.clearRows();
	  this.scrollTo(0);
	  this.startPos = 0;
	  this.lastStartPos = -1;   
   },
   
   refreshContents: function(startPos) {
	  if (startPos == this.lastRowPos && !this.isPartialBlank && !this.isBlank) {
		 return;
	  }
	  if ((startPos + this.visibleRows < this.buffer.startPos)  
		  || (this.buffer.startPos + this.buffer.size < startPos) 
		  || (this.buffer.size == 0)) {
		 this.clearRows();
		 return;
	  }
	  this.isBlank = false;
	  var viewPrecedesBuffer = this.buffer.startPos > startPos
	  var contentStartPos = viewPrecedesBuffer ? this.buffer.startPos: startPos; 
	  var contentEndPos = (this.buffer.startPos + this.buffer.size < startPos + this.visibleRows) 
								 ? this.buffer.startPos + this.buffer.size
								 : startPos + this.visibleRows;
	  var rowSize = contentEndPos - contentStartPos;
	  var rows = this.buffer.getRows(contentStartPos, rowSize ); 
	  var blankSize = this.visibleRows - rowSize;
	  var blankOffset = viewPrecedesBuffer ? 0: rowSize;
	  var contentOffset = viewPrecedesBuffer ? blankSize: 0;

	  for (var i=0; i < rows.length; i++) {//initialize what we have
		this.populateRow(this.table.rows[i + contentOffset], rows[i]);
	  }
	  for (var i=0; i < blankSize; i++) {// blank out the rest 
		this.populateRow(this.table.rows[i + blankOffset], this.buffer.getBlankRow());
	  }
	  this.isPartialBlank = blankSize > 0;
	  this.lastRowPos = startPos;

	   this.liveGrid.table.className = this.liveGrid.options.tableClass;
	   // Check if user has set a onRefreshComplete function
	   var onRefreshComplete = this.liveGrid.options.onRefreshComplete;
	   if (onRefreshComplete != null)
		   onRefreshComplete();
   },

   scrollTo: function(pixelOffset) {      
	  if (this.lastPixelOffset == pixelOffset)
		 return;

	  this.refreshContents(parseInt(pixelOffset / this.rowHeight))
	  this.div.scrollTop = pixelOffset % this.rowHeight        
	  
	  this.lastPixelOffset = pixelOffset;
   },
   
   visibleHeight: function() {
	  return parseInt(RicoUtil.getElementsComputedStyle(this.div, 'height'));
   }

};


Rico.LiveGridRequest = Class.create();
Rico.LiveGridRequest.prototype = {
   initialize: function( requestOffset, options ) {
	  this.requestOffset = requestOffset;
   }
};

// Rico.LiveGrid -----------------------------------------------------

Rico.LiveGrid = Class.create();

Rico.LiveGrid.prototype = {

   initialize: function( tableId, visibleRows, totalRows, url, options, ajaxOptions ) {

	 this.options = {
				tableClass:           $(tableId).className,
				loadingClass:         $(tableId).className,
				scrollerBorderRight: '1px solid #ababab',
				bufferTimeout:        20000,
				sortAscendImg:        'images/sort_asc.gif',
				sortDescendImg:       'images/sort_desc.gif',
				sortImageWidth:       9,
				sortImageHeight:      5,
				ajaxSortURLParms:     [],
				onRefreshComplete:    null,
				requestParameters:    null,
				inlineStyles:         true
				};
	  Object.extend(this.options, options || {});

	  this.ajaxOptions = {parameters: null};
	  Object.extend(this.ajaxOptions, ajaxOptions || {});

	  this.tableId     = tableId; 
	  this.table       = $(tableId);

	  this.addLiveGridHtml();

	  var columnCount  = this.table.rows[0].cells.length;
	  this.metaData    = new Rico.LiveGridMetaData(visibleRows, totalRows, columnCount, options);
	  this.buffer      = new Rico.LiveGridBuffer(this.metaData);

	  var rowCount = this.table.rows.length;
	  this.viewPort =  new Rico.GridViewPort(this.table, 
											this.table.offsetHeight/rowCount,
											visibleRows,
											this.buffer, this);
	  this.scroller    = new Rico.LiveGridScroller(this,this.viewPort);
	  this.options.sortHandler = this.sortHandler.bind(this);

	  if ( $(tableId + '_header') )
		 this.sort = new Rico.LiveGridSort(tableId + '_header', this.options)

	  this.processingRequest = null;
	  this.unprocessedRequest = null;

	  this.initAjax(url);
	  if ( this.options.prefetchBuffer || this.options.prefetchOffset > 0) {
		 var offset = 0;
		 if (this.options.offset ) {
			offset = this.options.offset;            
			this.scroller.moveScroll(offset);
			this.viewPort.scrollTo(this.scroller.rowToPixel(offset));            
		 }
		 if (this.options.sortCol) {
			 this.sortCol = options.sortCol;
			 this.sortDir = options.sortDir;
		 }
		 this.requestContentRefresh(offset);
	  }
   },

   addLiveGridHtml: function() {
	 // Check to see if need to create a header table.
	 if (this.table.getElementsByTagName("thead").length > 0){
	   // Create Table this.tableId+'_header'
	   var tableHeader = this.table.cloneNode(true);
	   tableHeader.setAttribute('id', this.tableId+'_header');
	   tableHeader.setAttribute('class', this.table.className+'_header');

	   // Clean up and insert
	   for( var i = 0; i < tableHeader.tBodies.length; i++ ) 
	   tableHeader.removeChild(tableHeader.tBodies[i]);
	   this.table.deleteTHead();
	   this.table.parentNode.insertBefore(tableHeader,this.table);
	 }

	new Insertion.Before(this.table, "<div id='"+this.tableId+"_container'></div>");
	this.table.previousSibling.appendChild(this.table);
	new Insertion.Before(this.table,"<div id='"+this.tableId+"_viewport' style='float:left;'></div>");
	this.table.previousSibling.appendChild(this.table);
   },


   resetContents: function() {
	  this.scroller.moveScroll(0);
	  this.buffer.clear();
	  this.viewPort.clearContents();
   },
   
   sortHandler: function(column) {
	   if(!column) return ;
	  this.sortCol = column.name;
	  this.sortDir = column.currentSort;

	  this.resetContents();
	  this.requestContentRefresh(0) 
   },

   adjustRowSize: function() {
	  
	},
	
   setTotalRows: function( newTotalRows ) {
	  this.resetContents();
	  this.metaData.setTotalRows(newTotalRows);
	  this.scroller.updateSize();
   },

   initAjax: function(url) {
	  ajaxEngine.registerRequest( this.tableId + '_request', url );
	  ajaxEngine.registerAjaxObject( this.tableId + '_updater', this );
   },

   invokeAjax: function() {
   },

   handleTimedOut: function() {
	  //server did not respond in 4 seconds... assume that there could have been
	  //an error or something, and allow requests to be processed again...
	  this.processingRequest = null;
	  this.processQueuedRequest();
   },

   fetchBuffer: function(offset) {
	  if ( this.buffer.isInRange(offset) &&
		 !this.buffer.isNearingLimit(offset)) {
		 return;
		 }
	  if (this.processingRequest) {
		  this.unprocessedRequest = new Rico.LiveGridRequest(offset);
		 return;
	  }
	  var bufferStartPos = this.buffer.getFetchOffset(offset);
	  this.processingRequest = new Rico.LiveGridRequest(offset);
	  this.processingRequest.bufferOffset = bufferStartPos;   
	  var fetchSize = this.buffer.getFetchSize(offset);
	  var partialLoaded = false;
	  
	  var queryString
	  if (this.options.requestParameters)
		 queryString = this._createQueryString(this.options.requestParameters, 0);

		queryString = (queryString == null) ? '' : queryString+'&';
		queryString  = queryString+'id='+this.tableId+'&page_size='+fetchSize+'&offset='+bufferStartPos;
		if (this.sortCol)
			queryString = queryString+'&sort_col='+escape(this.sortCol)+'&sort_dir='+this.sortDir;

		this.ajaxOptions.parameters = queryString;

	   ajaxEngine.sendRequest( this.tableId + '_request', this.ajaxOptions );

	   this.timeoutHandler = setTimeout( this.handleTimedOut.bind(this), this.options.bufferTimeout);

   },

   setRequestParams: function() {
	  this.options.requestParameters = [];
	  for ( var i=0 ; i < arguments.length ; i++ )
		 this.options.requestParameters[i] = arguments[i];
   },

   requestContentRefresh: function(contentOffset) {
	  this.fetchBuffer(contentOffset);
   },

   ajaxUpdate: function(ajaxResponse) {
	  try {
		 clearTimeout( this.timeoutHandler );
		 this.buffer.update(ajaxResponse,this.processingRequest.bufferOffset);
		 this.viewPort.bufferChanged();
	  }
	  catch(err) {}
	  finally {this.processingRequest = null; }
	  this.processQueuedRequest();
   },

   _createQueryString: function( theArgs, offset ) {
	  var queryString = ""
	  if (!theArgs)
		  return queryString;

	  for ( var i = offset ; i < theArgs.length ; i++ ) {
		  if ( i != offset )
			queryString += "&";

		  var anArg = theArgs[i];

		  if ( anArg.name != undefined && anArg.value != undefined ) {
			queryString += anArg.name +  "=" + escape(anArg.value);
		  }
		  else {
			 var ePos  = anArg.indexOf('=');
			 var argName  = anArg.substring( 0, ePos );
			 var argValue = anArg.substring( ePos + 1 );
			 queryString += argName + "=" + escape(argValue);
		  }
	  }
	  return queryString;
   },

   processQueuedRequest: function() {
	  if (this.unprocessedRequest != null) {
		 this.requestContentRefresh(this.unprocessedRequest.requestOffset);
		 this.unprocessedRequest = null
	  }
   }
};


//-------------------- ricoLiveGridSort.js
Rico.LiveGridSort = Class.create();

Rico.LiveGridSort.prototype = {

   initialize: function(headerTableId, options) {
	  this.headerTableId = headerTableId;
	  this.headerTable   = $(headerTableId);
	  this.options = options;
	  this.setOptions();
	  this.applySortBehavior();

	  if ( this.options.sortCol ) {
		 this.setSortUI( this.options.sortCol, this.options.sortDir );
	  }
   },

   setSortUI: function( columnName, sortDirection ) {
	  var cols = this.options.columns;
	  for ( var i = 0 ; i < cols.length ; i++ ) {
		 if ( cols[i].name == columnName ) {
			this.setColumnSort(i, sortDirection);
			break;
		 }
	  }
   },

   setOptions: function() {
	  // preload the images...
	  new Image().src = this.options.sortAscendImg;
	  new Image().src = this.options.sortDescendImg;

	  this.sort = this.options.sortHandler;
	  if ( !this.options.columns )
		 this.options.columns = this.introspectForColumnInfo();
	  else {
		 // allow client to pass { columns: [ ["a", true], ["b", false] ] }
		 // and convert to an array of Rico.TableColumn objs...
		 this.options.columns = this.convertToTableColumns(this.options.columns);
	  }
   },

   applySortBehavior: function() {
	  var headerRow   = this.headerTable.rows[0];
	  var headerCells = headerRow.cells;
	  for ( var i = 0 ; i < headerCells.length ; i++ ) {
		 this.addSortBehaviorToColumn( i, headerCells[i] );
	  }
   },

   addSortBehaviorToColumn: function( n, cell ) {
	  if ( this.options.columns[n].isSortable() ) {
		 cell.id            = this.headerTableId + '_' + n;
		 cell.style.cursor  = 'pointer';
		 cell.onclick       = this.headerCellClicked.bindAsEventListener(this);
		 cell.innerHTML     = cell.innerHTML + '<span id="' + this.headerTableId + '_img_' + n + '">'
						   + '&nbsp;&nbsp;&nbsp;</span>';
	  }
   },

   // event handler....
   headerCellClicked: function(evt) {
	  var eventTarget = evt.target ? evt.target : evt.srcElement;
	  var cellId = eventTarget.id;
	  var columnNumber = parseInt(cellId.substring( cellId.lastIndexOf('_') + 1 ));
	  var sortedColumnIndex = this.getSortedColumnIndex();
	  if ( sortedColumnIndex != -1 ) {
		 if ( sortedColumnIndex != columnNumber ) {
			this.removeColumnSort(sortedColumnIndex);
			this.setColumnSort(columnNumber, Rico.TableColumn.SORT_ASC);
		 }
		 else
			this.toggleColumnSort(sortedColumnIndex);
	  }
	  else
		 this.setColumnSort(columnNumber, Rico.TableColumn.SORT_ASC);

	  if (this.options.sortHandler) {
		 this.options.sortHandler(this.options.columns[columnNumber]);
	  }
   },

   removeColumnSort: function(n) {
	  this.options.columns[n].setUnsorted();
	  this.setSortImage(n);
   },

   setColumnSort: function(n, direction) {
	if(isNaN(n)) return ;
	  this.options.columns[n].setSorted(direction);
	  this.setSortImage(n);
   },

   toggleColumnSort: function(n) {
	  this.options.columns[n].toggleSort();
	  this.setSortImage(n);
   },

   setSortImage: function(n) {
	  var sortDirection = this.options.columns[n].getSortDirection();

	  var sortImageSpan = $( this.headerTableId + '_img_' + n );
	  if ( sortDirection == Rico.TableColumn.UNSORTED )
		 sortImageSpan.innerHTML = '&nbsp;&nbsp;';
	  else if ( sortDirection == Rico.TableColumn.SORT_ASC )
		 sortImageSpan.innerHTML = '&nbsp;&nbsp;<img width="'  + this.options.sortImageWidth    + '" ' +
													 'height="'+ this.options.sortImageHeight   + '" ' +
													 'src="'   + this.options.sortAscendImg + '"/>';
	  else if ( sortDirection == Rico.TableColumn.SORT_DESC )
		 sortImageSpan.innerHTML = '&nbsp;&nbsp;<img width="'  + this.options.sortImageWidth    + '" ' +
													 'height="'+ this.options.sortImageHeight   + '" ' +
													 'src="'   + this.options.sortDescendImg + '"/>';
   },

   getSortedColumnIndex: function() {
	  var cols = this.options.columns;
	  for ( var i = 0 ; i < cols.length ; i++ ) {
		 if ( cols[i].isSorted() )
			return i;
	  }

	  return -1;
   },

   introspectForColumnInfo: function() {
	  var columns = new Array();
	  var headerRow   = this.headerTable.rows[0];
	  var headerCells = headerRow.cells;
	  for ( var i = 0 ; i < headerCells.length ; i++ )
		 columns.push( new Rico.TableColumn( this.deriveColumnNameFromCell(headerCells[i],i), true ) );
	  return columns;
   },

   convertToTableColumns: function(cols) {
	  var columns = new Array();
	  for ( var i = 0 ; i < cols.length ; i++ )
		 columns.push( new Rico.TableColumn( cols[i][0], cols[i][1] ) );
	  return columns;
   },

   deriveColumnNameFromCell: function(cell,columnNumber) {
	  var cellContent = cell.innerText != undefined ? cell.innerText : cell.textContent;
	  return cellContent ? cellContent.toLowerCase().split(' ').join('_') : "col_" + columnNumber;
   }
};

Rico.TableColumn = Class.create();

Rico.TableColumn.UNSORTED  = 0;
Rico.TableColumn.SORT_ASC  = "ASC";
Rico.TableColumn.SORT_DESC = "DESC";

Rico.TableColumn.prototype = {
   initialize: function(name, sortable) {
	  this.name        = name;
	  this.sortable    = sortable;
	  this.currentSort = Rico.TableColumn.UNSORTED;
   },

   isSortable: function() {
	  return this.sortable;
   },

   isSorted: function() {
	  return this.currentSort != Rico.TableColumn.UNSORTED;
   },

   getSortDirection: function() {
	  return this.currentSort;
   },

   toggleSort: function() {
	  if ( this.currentSort == Rico.TableColumn.UNSORTED || this.currentSort == Rico.TableColumn.SORT_DESC )
		 this.currentSort = Rico.TableColumn.SORT_ASC;
	  else if ( this.currentSort == Rico.TableColumn.SORT_ASC )
		 this.currentSort = Rico.TableColumn.SORT_DESC;
   },

   setUnsorted: function(direction) {
	  this.setSorted(Rico.TableColumn.UNSORTED);
   },

   setSorted: function(direction) {
	  // direction must by one of Rico.TableColumn.UNSORTED, .SORT_ASC, or .SORT_DESC...
	  this.currentSort = direction;
   }

};


//-------------------- ricoUtil.js
var RicoUtil = {

   getElementsComputedStyle: function ( htmlElement, cssProperty, mozillaEquivalentCSS) {
	  if ( arguments.length == 2 )
		 mozillaEquivalentCSS = cssProperty;

	  var el = $(htmlElement);
	  if ( el.currentStyle )
		 return el.currentStyle[cssProperty];
	  else if(document && document.defaultView && document.defaultView.getComputedStyle) {
		 tmp = document.defaultView.getComputedStyle(el, null);
		 if(tmp)
			 return document.defaultView.getComputedStyle(el, null).getPropertyValue(mozillaEquivalentCSS);
		 return '';
	  }
	  else if(window.getComputedStyle) 
		 return window.getComputedStyle(el, null).getPropertyValue(mozillaEquivalentCSS);
   },

   createXmlDocument : function() {
	  if (document.implementation && document.implementation.createDocument) {
		 var doc = document.implementation.createDocument("", "", null);

		 if (doc.readyState == null) {
			doc.readyState = 1;
			doc.addEventListener("load", function () {
			   doc.readyState = 4;
			   if (typeof doc.onreadystatechange == "function")
				  doc.onreadystatechange();
			}, false);
		 }

		 return doc;
	  }

	  if (window.ActiveXObject)
		  return Try.these(
			function() { return new ActiveXObject('MSXML2.DomDocument')   },
			function() { return new ActiveXObject('Microsoft.DomDocument')},
			function() { return new ActiveXObject('MSXML.DomDocument')    },
			function() { return new ActiveXObject('MSXML3.DomDocument')   }
		  ) || false;

	  return null;
   },

   getContentAsString: function( parentNode ) {
	  return parentNode.xml != undefined ? 
		 this._getContentAsStringIE(parentNode) :
		 this._getContentAsStringMozilla(parentNode);
   },

  _getContentAsStringIE: function(parentNode) {
	 var contentStr = "";
	 for ( var i = 0 ; i < parentNode.childNodes.length ; i++ ) {
		 var n = parentNode.childNodes[i];
		 if (n.nodeType == 4) {
			 contentStr += n.nodeValue;
		 }
		 else {
		   contentStr += n.xml;
	   }
	 }
	 return contentStr;
  },

  _getContentAsStringMozilla: function(parentNode) {
	 var xmlSerializer = new XMLSerializer();
	 var contentStr = "";
	 for ( var i = 0 ; i < parentNode.childNodes.length ; i++ ) {
		  var n = parentNode.childNodes[i];
		  if (n.nodeType == 4) { // CDATA node
			  contentStr += n.nodeValue;
		  }
		  else {
			contentStr += xmlSerializer.serializeToString(n);
		}
	 }
	 return contentStr;
  },

   toViewportPosition: function(element) {
	  return this._toAbsolute(element,true);
   },

   toDocumentPosition: function(element) {
	  return this._toAbsolute(element,false);
   },

   /**
	*  Compute the elements position in terms of the window viewport
	*  so that it can be compared to the position of the mouse (dnd)
	*  This is additions of all the offsetTop,offsetLeft values up the
	*  offsetParent hierarchy, ...taking into account any scrollTop,
	*  scrollLeft values along the way...
	*
	* IE has a bug reporting a correct offsetLeft of elements within a
	* a relatively positioned parent!!!
	**/
   _toAbsolute: function(element,accountForDocScroll) {
	   if ( navigator.userAgent.toLowerCase().indexOf("msie") == -1 )
		 return this._toAbsoluteMozilla(element,accountForDocScroll);

	  var x = 0;
	  var y = 0;
	  var parent = element;
	  while ( parent ) {

		 var borderXOffset = 0;
		 var borderYOffset = 0;
		 if ( parent != element ) {
			var borderXOffset = parseInt(this.getElementsComputedStyle(parent, "borderLeftWidth" ));
			var borderYOffset = parseInt(this.getElementsComputedStyle(parent, "borderTopWidth" ));
			borderXOffset = isNaN(borderXOffset) ? 0 : borderXOffset;
			borderYOffset = isNaN(borderYOffset) ? 0 : borderYOffset;
		 }

		 x += parent.offsetLeft - parent.scrollLeft + borderXOffset;
		 y += parent.offsetTop - parent.scrollTop + borderYOffset;
		 parent = parent.offsetParent;
	  }

	  if (!accountForDocScroll ) {
		 x += this.docScrollLeft();
		 y += this.docScrollTop();
	  }

	  return { x:x, y:y };
   },

   /**
	*  Mozilla did not report all of the parents up the hierarchy via the
	*  offsetParent property that IE did.  So for the calculation of the
	*  offsets we use the offsetParent property, but for the calculation of
	*  the scrollTop/scrollLeft adjustments we navigate up via the parentNode
	*  property instead so as to get the scroll offsets...
	*
	**/
   _toAbsoluteMozilla: function(element,accountForDocScroll) {
	  var x = 0;
	  var y = 0;
	  var parent = element;
	  while ( parent ) {
		 x += parent.offsetLeft;
		 y += parent.offsetTop;
		 parent = parent.offsetParent;
	  }

	  parent = element;
	  while ( parent &&
			  parent != document.body &&
			  parent != document.documentElement ) {
		 if ( parent.scrollLeft  )
			x -= parent.scrollLeft;
		 if ( parent.scrollTop )
			y -= parent.scrollTop;
		 parent = parent.parentNode;
	  }

	  if ( accountForDocScroll ) {
		 x -= this.docScrollLeft();
		 y -= this.docScrollTop();
	  }

	  return { x:x, y:y };
   },

   docScrollLeft: function() {
	  if ( window.pageXOffset )
		 return window.pageXOffset;
	  else if ( document.documentElement && document.documentElement.scrollLeft )
		 return document.documentElement.scrollLeft;
	  else if ( document.body )
		 return document.body.scrollLeft;
	  else
		 return 0;
   },

   docScrollTop: function() {
	  if ( window.pageYOffset )
		 return window.pageYOffset;
	  else if ( document.documentElement && document.documentElement.scrollTop )
		 return document.documentElement.scrollTop;
	  else if ( document.body )
		 return document.body.scrollTop;
	  else
		 return 0;
   }

};
/**
 * @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.style.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 = RicoUtil.toDocumentPosition(this.htmlWindow);
		this._MoveOffset.x -= Event.pointerX(e);
		this._MoveOffset.y -= 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.x+'px', top: Event.pointerY(e) + this._MoveOffset.y+'px' });
	}
}

pkIE = {
	ieFrameFix: function(MainElement) {
		if(navigator.msie) {
			MainElement.ieFrameFix = document.createElement('iframe');
			MainElement.ieFrameFix.style.position = RicoUtil.getElementsComputedStyle(MainElement, 'position');
			MainElement.ieFrameFix.style.zIndex = RicoUtil.getElementsComputedStyle(MainElement, 'zIndex') - 1;
			MainElement.ieFrameFix.style.top = RicoUtil.getElementsComputedStyle(MainElement, 'top');
			MainElement.ieFrameFix.style.left = RicoUtil.getElementsComputedStyle(MainElement, '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 = RicoUtil.getElementsComputedStyle(MainElement, 'top');
					MainElement.ieFrameFix.style.left = RicoUtil.getElementsComputedStyle(MainElement, '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 : "/") +
		((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.fireEvent.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);
	} );
	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: %s', item.element, item.Class, e.toString());
					console.trace();
					console.groupEnd();
				}
				pkAjax.ReportError('Unable to cause element to become class '+item.Class+' for element: '+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;
}
/**
  *
  *  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.fireEvent('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) {
		if(!navigator.msie && Rico && Rico.Effect.FadeTo) {
			new Rico.Effect.FadeTo(element, 0, 500, 10, {
				complete: function(element) {
					element.remove();
					Try.these( onComplete );
				}.bind(this, element)
			} );
		} else {
			element.remove();
		}
	},

	FadeTo: function(element, opacity, duration, steps, options) {
		new Rico.Effect.FadeTo(element, opacity || 0, duration || 500, steps || 10, options);
	},

	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;
	},
	/** 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);
	}
}
pkElement_Methods._replace = Element.replace;

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

if(document.createEvent) {
	pkElement_Methods.fireEvent = function(element, name, props) {
		if(name.substr(0,2)=='on')
			name = name.substr(2);
		switch(name) {
			case 'click':
				var objEvent = document.createEvent('MouseEvents');
				objEvent.initMouseEvent('click', true, true, window, 0, 0, 0, 0, 0, false, false, false, false, 0, null);
				break;
			default:
				if(navigator.safari)
					var objEvent = document.createEvent('UIEvents');
				else
					var objEvent = document.createEvent('Event');
				objEvent.initEvent(name, true, true);
				break;
		}
		if(props)
			Object.extend(objEvent, props);
		element.dispatchEvent(objEvent);
	}
}


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] 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.fireEvent('onchange');
});


/* Makes IE8 accept .textContent */
if (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.fireEvent('onblur');
		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);

		Rico.Corner.round(this.Balloon, { border: 'black', color: '#fdffe1', bgColor: 'transparent' } );
		var Div = this.Balloon.getElementsByTagName('div')[1];

		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.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() {
		if(navigator.msie)
			this._Hide();
		else {
			new Rico.Effect.FadeTo(this.OffsetContainer, 0, 500, 10,	{ complete: this._Hide.bind(this) } );
		}
	},
	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) {};
		if(this.Options.FadeNewRow && !navigator.ie) {
			htmlNewRow.SetOpacity(0);
			this.htmlTableBody.appendChild(htmlNewRow);
			new Rico.Effect.FadeTo(htmlNewRow, 1.0, 250, 10);
		} else
			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.fireEvent('onchange');
	},
	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 = RicoUtil.toDocumentPosition(this.Options.eInput);
		this.eDateWindow.setStyle( {
			top:	pos.y + 'px',
			left:	pos.x + 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.fireEvent('onchange');
	}
}

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(RicoUtil.getElementsComputedStyle(htmlElement, 'paddingTop', 'padding-top')) +
					parseInt(RicoUtil.getElementsComputedStyle(htmlElement, 'paddingBottom', '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.fireEvent('onblur');
		}
		if(hTab) {
			hTab.htmlTab.addClassName('Selected');
			this.hTabSelected = hTab;
			hTab.TabView.style.display = '';
			this.hTabSelected.TabView.fireEvent('onfocus');
		}
	}
}

/*	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.fireEvent('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.fireEvent('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 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 = '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);
		$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);
	},
	SetChecked: function(Checked) {
		this.Checked = Checked;
		if(this.ValueElement) {
			this.ValueElement.value = this.Checked ? '1' : '0';
			this.ValueElement.fireEvent('change');
		}
		this.setStyle( this.Checked ? this.CheckedStyle : this.UncheckedStyle );
		this.fireEvent('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.fireEvent('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) { 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) {
		this.DataTable = this.up('TABLE');
		this.DataTable.RegisterFilter(this);
		this.observe('change', this.onChange.bindAsEventListener(this));
		if(Object.isFunction($super))
			$super();
	},
	onChange: function(e) { this.DataTable.UpdateVisibility(); },
	GetMatches: function(DataElements) {
		return DataElements.findAll( function(Element) {
			return this.Matches(Element);
		}, this );
	},
	Matches: function(Element) { return true; },
	UpdatePosition: function() { },
	Reset: function() { this.fireEvent('change'); }
});

/** Free Form Text Filter - Allows for wildcard text input filtering of data */
var FreeFilter = Class.create( DataTableFilter, {
	initialize: function($super) {
		$super();
		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.fireEvent('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();
	}
});

/** 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.fireEvent('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.fireEvent('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.insert({ top: this.prototype.Html }).down();
		Element.ColumnName = ColumnName;
		Element.SetFilterValues(Values);
		return Element;
	},
	initialize: function($super) {
		this.observe('click', this.onClick.bindAsEventListener(this));
		$super();
	},
	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.insert({ top: '<img src="/res/img/icon.filter.png" class="AutoFilter" style="position: absolute; top: 0px; display: none;">' });
		this.FocusFilterIcon = $(this.DataTable.down());

		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() {},
	/** 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.ApplyGrouping(Target.Element, this.DetermineGroup(Target));
		}, this );
	},
	/** Un-Apply the formatter to the given table */
	Reverse: function(DataTable, DataElements) {
		this.GetTargets(DataTable, DataElements)
			.each( function(Target) {
				if(Target.Element.SubRows)
					this.Reverse(DataTable, Target.Element.SubRows);
				this.ReverseGrouping(Target.Element);
			}, this);
	},
	/** Called to apply changes to the given element by the group number */
	ApplyGrouping: function(Element, GroupNum) {
		if(GroupNum < 0)
			return;
		this.ReverseGrouping(Element);
		if(GroupNum >= 0 && this.Styles.length > GroupNum) {
			Element.AppliedStyles = Element.AppliedStyles || [ ];
			Element.AppliedStyles[this] = GroupNum;
			Element.setStyle( this.Styles[GroupNum] );
		}
		if(GroupNum >= 0 && this.Classes.length > GroupNum) {
			if(this.AppliedClass)
				Element.removeClassName(this.AppliedClass);

			this.AppliedClass = this.Classes[GroupNum];
			Element.addClassName(this.Classes[GroupNum]);
		}
	},
	/** Called to un-apply changes to the given element by the group number */
	ReverseGrouping: function(Element) {
		if(Element.AppliedStyles && Element.AppliedStyles[this] != undefined) {
			var AppliedStyle = Object.clone(this.Styles[Element.AppliedStyles[this]]);
			delete Element.AppliedStyles[this];
			for(var i in AppliedStyle)
				AppliedStyle[i] = Element.UnformattedElement.getStyle(i);
			Element.setStyle(AppliedStyle);
		}
		if(this.AppliedClass) {
			Element.removeClassName(this.AppliedClass);
			delete this.AppliedClass;
		}
	},
	/* 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) {
		for(var i=0;i<this.Groups.length;i++) {
			if(Target.Criteria >= this.Groups[i].Low && Target.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) {

		this.Range = this.DetermineValueRange(Targets);

		this.Quantiles = [ ];
		for(var i=1;i<=this.Divisions;i++)
			this.Quantiles.push((Math.round(((i*this.Range.Range)/this.Divisions)*100)/100)+this.Range.Low);
	},
	DetermineGroup: function(Target) {
		for(var i=0;i<this.Quantiles.length;i++) {
			if(Target.Criteria <= this.Quantiles[i])
				return i;
		}
		return 0;
	}
} );

/**
* 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();
	},
	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('colgroup').length == 0)) {
				this.select('colgroup').invoke('remove');
				this.insert( {
					top: $H(this.Columns).values().inject( '', function(acc, Column) {
						return acc + '<colgroup class="'+Column.className+'" span="'+(Column.getAttribute('colspan') || 1)+'" />';
					})
				});
				this.CreateColumns = true;
			}
			this.HeaderRow.previousSiblings()
				.reject( function(Row) { return Row.hasClassName('Control'); } )
				.invoke( 'addClassName', 'Header');
		}
	},
	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;
			}
		);
		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;
		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;
		}
		SortColumn.SortDir = this.SortDir;
		this.SortColumnName = SortColumn.ColumnName;

		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.Formatters.each( function(Formatter) {
			Formatter.Apply(this, Row.SubRows);
		}, this);

		// Re-index the data elements
		this.DataElements = this.select('*[ObjectID]','*[DataRow]');
		this.InitializeDataElements();

		this.SortByColumn();
		this.UpdateVisibility();
	},
	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.Formatters.each( function(Formatter) {
			Formatter.Apply(this, this.DataElements);
		}, this);

		this.SortByColumn();
		this.UpdateVisibility();
	},
	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**'));

		$super();

		Event.observe(this, 'keyup', this.onKeyUp.bindAsEventListener(this));
		window.addEventListener('focus', this.onFocus.bindAsEventListener(this), true);

		this.AddNewRow();
	},
	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//
