/*  Prototype JavaScript framework, version 1.5.0_rc0
 *  (c) 2005 Sam Stephenson <sam@conio.net>
 *
 *  Prototype is freely distributable under the terms of an MIT-style license.
 *  For details, see the Prototype web site: http://prototype.conio.net/
 *
/*--------------------------------------------------------------------------*/

var Prototype = {
  Version: '1.5.0_rc0',
  ScriptFragment: '(?:<script.*?>)((\n|\r|.)*?)(?:<\/script>)',

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

var Class = {
  create: function() {
    return function() {
      this.initialize.apply(this, arguments);
    }
  }
};

var Abstract = new Object();

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

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

Function.prototype.bind = function() {
  var __method = this, args = $A(arguments), object = args.shift();
  return function() {
    return __method.apply(object, args.concat($A(arguments)));
  }
};

Function.prototype.bindAsEventListener = function(object) {
  var __method = this;
  return function(event) {
    return __method.call(object, event || window.event);
  }
};

Object.extend(Number.prototype, {
  toColorPart: function() {
    var digits = this.toString(16);
    if (this < 16) return '0' + digits;
    return digits;
  },

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

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

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

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

    return returnValue;
  }
};

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

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

    this.registerCallback();
  },

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

  onTimerEvent: function() {
    if (!this.currentlyExecuting) {
      try {
        this.currentlyExecuting = true;
        this.callback();
      } finally {
        this.currentlyExecuting = false;
      }
    }
  }
};
Object.extend(String.prototype, {
  gsub: function(pattern, replacement) {
    var result = '', source = this, match;
    replacement = arguments.callee.prepareReplacement(replacement);

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

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

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

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

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

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

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

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

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

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

  escapeHTML: function() {
    var div = document.createElement('div');
    var text = document.createTextNode(this);
    div.appendChild(text);
    return div.innerHTML;
  },

  unescapeHTML: function() {
    var div = document.createElement('div');
    div.innerHTML = this.stripTags();
    return div.childNodes[0] ? div.childNodes[0].nodeValue : '';
  },

  toQueryParams: function() {
    var pairs = this.match(/^\??(.*)$/)[1].split('&');
    return pairs.inject({}, function(params, pairString) {
      var pair = pairString.split('=');
      params[pair[0]] = pair[1];
      return params;
    });
  },

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

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

    var camelizedString = this.indexOf('-') == 0
      ? oStringList[0].charAt(0).toUpperCase() + oStringList[0].substring(1)
      : oStringList[0];

    for (var i = 1, len = oStringList.length; i < len; i++) {
      var s = oStringList[i];
      camelizedString += s.charAt(0).toUpperCase() + s.substring(1);
    }

    return camelizedString;
  },

  inspect: function() {
    return "'" + this.replace(/\\/g, '\\\\').replace(/'/g, '\\\'') + "'";
  }
});

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

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

var Template = Class.create();
Template.Pattern = /(^|.|\r|\n)(#\{(.*?)\})/;
Template.prototype = {
  initialize: function(template, pattern) {
    this.template = template.toString();
    this.pattern  = pattern || Template.Pattern;
  },

  evaluate: function(object) {
    return this.template.gsub(this.pattern, function(match) {
      var before = match[1];
      if (before == '\\') return match[2];
      return before + (object[match[3]] || '').toString();
    });
  }
};

var $break    = new Object();
var $continue = new Object();

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

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

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

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

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

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

  grep: function(pattern, iterator) {
    var results = [];
    this.each(function(value, index) {
      var stringValue = value.toString();
      if (stringValue.match(pattern))
        results.push((iterator || Prototype.K)(value, index));
    })
    return results;
  },

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

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

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

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

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

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

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

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

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

  toArray: function() {
    return this.collect(Prototype.K);
  },

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

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

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

Object.extend(Enumerable, {
  map:     Enumerable.collect,
  find:    Enumerable.detect,
  select:  Enumerable.findAll,
  member:  Enumerable.include,
  entries: Enumerable.toArray
});
var $A = Array.from = function(iterable) {
  if (!iterable) return [];
  if (iterable.toArray) {
    return iterable.toArray();
  } else {
    var results = [];
    for (var i = 0; i < iterable.length; i++)
      results.push(iterable[i]);
    return results;
  }
};

Object.extend(Array.prototype, Enumerable);

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

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

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

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

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

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

  flatten: function() {
    return this.inject([], function(array, value) {
      return array.concat(value && value.constructor == Array ?
        value.flatten() : [value]);
    });
  },

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

  indexOf: function(object) {
    for (var i = 0; i < this.length; i++)
      if (this[i] == object) return i;
    return -1;
  },

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

  inspect: function() {
    return '[' + this.map(Object.inspect).join(', ') + ']';
  }
});
var Hash = {
  _each: function(iterator) {
    for (var key in this) {
      var value = this[key];
      if (typeof value == 'function') continue;

      var pair = [key, value];
      pair.key = key;
      pair.value = value;
      iterator(pair);
    }
  },

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

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

  merge: function(hash) {
    return $H(hash).inject($H(this), function(mergedHash, pair) {
      mergedHash[pair.key] = pair.value;
      return mergedHash;
    });
  },

  toQueryString: function() {
    return this.map(function(pair) {
      return pair.map(encodeURIComponent).join('=');
    }).join('&');
  },

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

function $H(object) {
  var hash = Object.extend({}, object || {});
  Object.extend(hash, Enumerable);
  Object.extend(hash, Hash);
  return hash;
};
ObjectRange = Class.create();
Object.extend(ObjectRange.prototype, Enumerable);
Object.extend(ObjectRange.prototype, {
  initialize: function(start, end, exclusive) {
    this.start = start;
    this.end = end;
    this.exclusive = exclusive;
  },

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

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

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

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

  activeRequestCount: 0
};

Ajax.Responders = {
  responders: [],

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

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

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

  dispatch: function(callback, request, transport, json) {
    this.each(function(responder) {
      if (responder[callback] && typeof responder[callback] == 'function') {
        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 = function() {};
Ajax.Base.prototype = {
  setOptions: function(options) {
    this.options = {
      method:       'post',
      asynchronous: true,
      contentType:  'application/x-www-form-urlencoded',
      parameters:   ''
    };
    Object.extend(this.options, options || {});
  },

  responseIsSuccess: function() {
    return this.transport.status == undefined
        || this.transport.status == 0
        || (this.transport.status >= 200 && this.transport.status < 300);
  },

  responseIsFailure: function() {
    return !this.responseIsSuccess();
  }
};

Ajax.Request = Class.create();
Ajax.Request.Events =
  ['Uninitialized', 'Loading', 'Loaded', 'Interactive', 'Complete'];

Ajax.Request.prototype = Object.extend(new Ajax.Base(), {
  initialize: function(url, options) {
    this.transport = Ajax.getTransport();
    this.setOptions(options);
    this.request(url);
  },

  request: function(url) {
    var parameters = this.options.parameters || '';
    if (parameters.length > 0) parameters += '&_=';

    try {
      this.url = url;
      if (this.options.method == 'get' && parameters.length > 0)
        this.url += (this.url.match(/\?/) ? '&' : '?') + parameters;

      Ajax.Responders.dispatch('onCreate', this, this.transport);

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

      if (this.options.asynchronous) {
        this.transport.onreadystatechange = this.onStateChange.bind(this);
        setTimeout((function() {this.respondToReadyState(1)}).bind(this), 10);
      }

      this.setRequestHeaders();

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

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

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

    if (this.options.method == 'post') {
      requestHeaders.push('Content-type', this.options.contentType);

      /* Force "Connection: close" for Mozilla browsers to work around
       * a bug where XMLHttpReqeuest sends an incorrect Content-length
       * header. See Mozilla Bugzilla #246651.
       */
      if (this.transport.overrideMimeType)
        requestHeaders.push('Connection', 'close');
    }

    if (this.options.requestHeaders)
      requestHeaders.push.apply(requestHeaders, this.options.requestHeaders);

    for (var i = 0; i < requestHeaders.length; i += 2)
      this.transport.setRequestHeader(requestHeaders[i], requestHeaders[i+1]);
  },

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

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

  evalJSON: function() {
    try {
      return eval('(' + this.header('X-JSON') + ')');
    } catch (e) {}
  },

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

  respondToReadyState: function(readyState) {
    var event = Ajax.Request.Events[readyState];
    var transport = this.transport, json = this.evalJSON();

    if (event == 'Complete') {
      try {
        (this.options['on' + this.transport.status]
         || this.options['on' + (this.responseIsSuccess() ? 'Success' : 'Failure')]
         || Prototype.emptyFunction)(transport, json);
      } catch (e) {
        this.dispatchException(e);
      }

      if ((this.header('Content-type') || '').match(/^text\/javascript/i))
        this.evalResponse();
    }

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

    /* Avoid memory leak in MSIE: clean up the oncomplete event handler */
    if (event == 'Complete')
      this.transport.onreadystatechange = Prototype.emptyFunction;
  },

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

Ajax.Updater = Class.create();

Object.extend(Object.extend(Ajax.Updater.prototype, Ajax.Request.prototype), {
  initialize: function(container, url, options) {
    this.containers = {
      success: container.success ? $(container.success) : $(container),
      failure: container.failure ? $(container.failure) :
        (container.success ? null : $(container))
    };

    this.transport = Ajax.getTransport();
    this.setOptions(options);

    var onComplete = this.options.onComplete || Prototype.emptyFunction;
    this.options.onComplete = (function(transport, object) {
      this.updateContent();
      onComplete(transport, object);
    }).bind(this);

    this.request(url);
  },

  updateContent: function() {
    var receiver = this.responseIsSuccess() ?
      this.containers.success : this.containers.failure;
    var response = this.transport.responseText;

    if (!this.options.evalScripts)
      response = response.stripScripts();

    if (receiver) {
      if (this.options.insertion) {
        new this.options.insertion(receiver, response);
      } else {
        Element.update(receiver, response);
      }
    }

    if (this.responseIsSuccess()) {
      if (this.onComplete)
        setTimeout(this.onComplete.bind(this), 10);
    }
  }
});

Ajax.PeriodicalUpdater = Class.create();
Ajax.PeriodicalUpdater.prototype = Object.extend(new Ajax.Base(), {
  initialize: function(container, url, options) {
    this.setOptions(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.onComplete = undefined;
    clearTimeout(this.timer);
    (this.onComplete || Prototype.emptyFunction).apply(this, arguments);
  },

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

      this.lastText = request.responseText;
    }
    this.timer = setTimeout(this.onTimerEvent.bind(this),
      this.decay * this.frequency * 1000);
  },

  onTimerEvent: function() {
    this.updater = new Ajax.Updater(this.container, this.url, this.options);
  }
});
function $() {
  var results = [], element;
  for (var i = 0; i < arguments.length; i++) {
    element = arguments[i];
    if (typeof element == 'string')
      element = document.getElementById(element);
    results.push(Element.extend(element));
  }
  return results.length < 2 ? results[0] : results;
};

document.getElementsByClassName = function(className, parentElement) {
  var children = ($(parentElement) || document.body).getElementsByTagName('*');
  return $A(children).inject([], function(elements, child) {
    if (child.className.match(new RegExp("(^|\\s)" + className + "(\\s|$)")))
      elements.push(Element.extend(child));
    return elements;
  });
};

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

if (!window.Element)
  var Element = new Object();

Element.extend = function(element) {
  if (!element) return;
  if (_nativeExtensions) return element;

  if (!element._extended && element.tagName && element != window) {
    var methods = Element.Methods, cache = Element.extend.cache;
    for (property in methods) {
      var value = methods[property];
      if (typeof value == 'function')
        element[property] = cache.findOrStore(value);
    }
  }

  element._extended = true;
  return element;
};

Element.extend.cache = {
  findOrStore: function(value) {
    return this[value] = this[value] || function() {
      return value.apply(null, [this].concat($A(arguments)));
    }
  }
};

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

  toggle: function() {
    for (var i = 0; i < arguments.length; i++) {
      var element = $(arguments[i]);
      Element[Element.visible(element) ? 'hide' : 'show'](element);
    }
  },

  hide: function() {
    for (var i = 0; i < arguments.length; i++) {
      var element = $(arguments[i]);
      element.style.display = 'none';
    }
  },

  show: function() {
    for (var i = 0; i < arguments.length; i++) {
      var element = $(arguments[i]);
      element.style.display = '';
    }
  },

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

  update: function(element, html) {
    $(element).innerHTML = html.stripScripts();
    setTimeout(function() {html.evalScripts()}, 10);
  },

  replace: function(element, html) {
    element = $(element);
    if (element.outerHTML) {
      element.outerHTML = html.stripScripts();
    } else {
      var range = element.ownerDocument.createRange();
      range.selectNodeContents(element);
      element.parentNode.replaceChild(
        range.createContextualFragment(html.stripScripts()), element);
    }
    setTimeout(function() {html.evalScripts()}, 10);
  },

  getHeight: function(element) {
    element = $(element);
    return element.offsetHeight;
  },

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

  hasClassName: function(element, className) {
    if (!(element = $(element))) return;
    return Element.classNames(element).include(className);
  },

  addClassName: function(element, className) {
    if (!(element = $(element))) return;
    return Element.classNames(element).add(className);
  },

  removeClassName: function(element, className) {
    if (!(element = $(element))) return;
    return Element.classNames(element).remove(className);
  },

  // removes whitespace-only text node children
  cleanWhitespace: function(element) {
    element = $(element);
    for (var i = 0; i < element.childNodes.length; i++) {
      var node = element.childNodes[i];
      if (node.nodeType == 3 && !/\S/.test(node.nodeValue))
        Element.remove(node);
    }
  },

  empty: function(element) {
    return $(element).innerHTML.match(/^\s*$/);
  },

  childOf: function(element, ancestor) {
    element = $(element), ancestor = $(ancestor);
    while (element = element.parentNode)
      if (element == ancestor) return true;
    return false;
  },

  scrollTo: function(element) {
    element = $(element);
    var x = element.x ? element.x : element.offsetLeft,
        y = element.y ? element.y : element.offsetTop;
    window.scrollTo(x, y);
  },

  getStyle: function(element, style) {
    element = $(element);
    var value = element.style[style.camelize()];
    if (!value) {
      if (document.defaultView && document.defaultView.getComputedStyle) {
        var css = document.defaultView.getComputedStyle(element, null);
        value = css ? css.getPropertyValue(style) : null;
      } else if (element.currentStyle) {
        value = element.currentStyle[style.camelize()];
      }
    }

    if (window.opera && ['left', 'top', 'right', 'bottom'].include(style))
      if (Element.getStyle(element, 'position') == 'static') value = 'auto';

    return value == 'auto' ? null : value;
  },

  setStyle: function(element, style) {
    element = $(element);
    for (var name in style)
      element.style[name.camelize()] = style[name];
  },

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

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

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

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

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

  undoClipping: function(element) {
    element = $(element);
    if (element._overflow) return;
    element.style.overflow = element._overflow;
    element._overflow = undefined;
  }
};

Object.extend(Element, Element.Methods);

var _nativeExtensions = false;

if(!HTMLElement && /Konqueror|Safari|KHTML/.test(navigator.userAgent)) {
  var HTMLElement = {};
  HTMLElement.prototype = document.createElement('div').__proto__;
}

Element.addMethods = function(methods) {
  Object.extend(Element.Methods, methods || {});

  if(typeof HTMLElement != 'undefined') {
    var methods = Element.Methods, cache = Element.extend.cache;
    for (property in methods) {
      var value = methods[property];
      if (typeof value == 'function')
        HTMLElement.prototype[property] = cache.findOrStore(value);
    }
    _nativeExtensions = true;
  }
};

Element.addMethods();

var Toggle = new Object();
Toggle.display = Element.toggle;

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

Abstract.Insertion = function(adjacency) {
  this.adjacency = adjacency;
};

Abstract.Insertion.prototype = {
  initialize: function(element, content) {
    this.element = $(element);
    this.content = content.stripScripts();

    if (this.adjacency && this.element.insertAdjacentHTML) {
      try {
        this.element.insertAdjacentHTML(this.adjacency, this.content);
      } catch (e) {
        var tagName = this.element.tagName.toLowerCase();
        if (tagName == 'tbody' || tagName == 'tr') {
          this.insertContent(this.contentFromAnonymousTable());
        } else {
          throw e;
        }
      }
    } else {
      this.range = this.element.ownerDocument.createRange();
      if (this.initializeRange) this.initializeRange();
      this.insertContent([this.range.createContextualFragment(this.content)]);
    }

    setTimeout(function() {content.evalScripts()}, 10);
  },

  contentFromAnonymousTable: function() {
    var div = document.createElement('div');
    div.innerHTML = '<table><tbody>' + this.content + '</tbody></table>';
    return $A(div.childNodes[0].childNodes[0].childNodes);
  }
};

var Insertion = new Object();

Insertion.Before = Class.create();
Insertion.Before.prototype = Object.extend(new Abstract.Insertion('beforeBegin'), {
  initializeRange: function() {
    this.range.setStartBefore(this.element);
  },

  insertContent: function(fragments) {
    fragments.each((function(fragment) {
      this.element.parentNode.insertBefore(fragment, this.element);
    }).bind(this));
  }
});

Insertion.Top = Class.create();
Insertion.Top.prototype = Object.extend(new Abstract.Insertion('afterBegin'), {
  initializeRange: function() {
    this.range.selectNodeContents(this.element);
    this.range.collapse(true);
  },

  insertContent: function(fragments) {
    fragments.reverse(false).each((function(fragment) {
      this.element.insertBefore(fragment, this.element.firstChild);
    }).bind(this));
  }
});

Insertion.Bottom = Class.create();
Insertion.Bottom.prototype = Object.extend(new Abstract.Insertion('beforeEnd'), {
  initializeRange: function() {
    this.range.selectNodeContents(this.element);
    this.range.collapse(this.element);
  },

  insertContent: function(fragments) {
    fragments.each((function(fragment) {
      this.element.appendChild(fragment);
    }).bind(this));
  }
});

Insertion.After = Class.create();
Insertion.After.prototype = Object.extend(new Abstract.Insertion('afterEnd'), {
  initializeRange: function() {
    this.range.setStartAfter(this.element);
  },

  insertContent: function(fragments) {
    fragments.each((function(fragment) {
      this.element.parentNode.insertBefore(fragment,
        this.element.nextSibling);
    }).bind(this));
  }
});

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

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(this.toArray().concat(classNameToAdd).join(' '));
  },

  remove: function(classNameToRemove) {
    if (!this.include(classNameToRemove)) return;
    this.set(this.select(function(className) {
      return className != classNameToRemove;
    }).join(' '));
  },

  toString: function() {
    return this.toArray().join(' ');
  }
};

Object.extend(Element.ClassNames.prototype, Enumerable);
var Selector = Class.create();
Selector.prototype = {
  initialize: function(expression) {
    this.params = {classNames: []};
    this.expression = expression.toString().strip();
    this.parseExpression();
    this.compileMatcher();
  },

  parseExpression: function() {
    function abort(message) { throw 'Parse error in selector: ' + message; }

    if (this.expression == '')  abort('empty expression');

    var params = this.params, expr = this.expression, match, modifier, clause, rest;
    while (match = expr.match(/^(.*)\[([a-z0-9_:-]+?)(?:([~\|!]?=)(?:"([^"]*)"|([^\]\s]*)))?\]$/i)) {
      params.attributes = params.attributes || [];
      params.attributes.push({name: match[2], operator: match[3], value: match[4] || match[5] || ''});
      expr = match[1];
    }

    if (expr == '*') return this.params.wildcard = true;

    while (match = expr.match(/^([^a-z0-9_-])?([a-z0-9_-]+)(.*)/i)) {
      modifier = match[1], clause = match[2], rest = match[3];
      switch (modifier) {
        case '#':       params.id = clause; break;
        case '.':       params.classNames.push(clause); break;
        case '':
        case undefined: params.tagName = clause.toUpperCase(); break;
        default:        abort(expr.inspect());
      }
      expr = rest;
    }

    if (expr.length > 0) abort(expr.inspect());
  },

  buildMatchExpression: function() {
    var params = this.params, conditions = [], clause;

    if (params.wildcard)
      conditions.push('true');
    if (clause = params.id)
      conditions.push('element.id == ' + clause.inspect());
    if (clause = params.tagName)
      conditions.push('element.tagName.toUpperCase() == ' + clause.inspect());
    if ((clause = params.classNames).length > 0)
      for (var i = 0; i < clause.length; i++)
        conditions.push('Element.hasClassName(element, ' + clause[i].inspect() + ')');
    if (clause = params.attributes) {
      clause.each(function(attribute) {
        var value = 'element.getAttribute(' + attribute.name.inspect() + ')';
        var splitValueBy = function(delimiter) {
          return value + ' && ' + value + '.split(' + delimiter.inspect() + ')';
        };

        switch (attribute.operator) {
          case '=':       conditions.push(value + ' == ' + attribute.value.inspect()); break;
          case '~=':      conditions.push(splitValueBy(' ') + '.include(' + attribute.value.inspect() + ')'); break;
          case '|=':      conditions.push(
                            splitValueBy('-') + '.first().toUpperCase() == ' + attribute.value.toUpperCase().inspect()
                          ); break;
          case '!=':      conditions.push(value + ' != ' + attribute.value.inspect()); break;
          case '':
          case undefined: conditions.push(value + ' != null'); break;
          default:        throw 'Unknown operator ' + attribute.operator + ' in selector';
        }
      });
    }

    return conditions.join(' && ');
  },

  compileMatcher: function() {
    this.match = new Function('element', 'if (!element.tagName) return false; \
      return ' + this.buildMatchExpression());
  },

  findElements: function(scope) {
    var element;

    if (element = $(this.params.id))
      if (this.match(element))
        if (!scope || Element.childOf(element, scope))
          return [element];

    scope = (scope || document).getElementsByTagName(this.params.tagName || '*');

    var results = [];
    for (var i = 0; i < scope.length; i++)
      if (this.match(element = scope[i]))
        results.push(Element.extend(element));

    return results;
  },

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

function $$() {
  return $A(arguments).map(function(expression) {
    return expression.strip().split(/\s+/).inject([null], function(results, expr) {
      var selector = new Selector(expr);
      return results.map(selector.findElements.bind(selector)).flatten();
    });
  }).flatten();
};
var Field = {
  clear: function() {
    for (var i = 0; i < arguments.length; i++)
      $(arguments[i]).value = '';
  },

  focus: function(element) {
    $(element).focus();
  },

  present: function() {
    for (var i = 0; i < arguments.length; i++)
      if ($(arguments[i]).value == '') return false;
    return true;
  },

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

  activate: function(element) {
    element = $(element);
    element.focus();
    if (element.select)
      element.select();
  }
};

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

var Form = {
  serialize: function(form) {
    var elements = Form.getElements($(form));
    var queryComponents = new Array();

    for (var i = 0; i < elements.length; i++) {
      var queryComponent = Form.Element.serialize(elements[i]);
      if (queryComponent)
        queryComponents.push(queryComponent);
    }

    return queryComponents.join('&');
  },

  getElements: function(form) {
    form = $(form);
    var elements = new Array();

    for (var tagName in Form.Element.Serializers) {
      var tagElements = form.getElementsByTagName(tagName);
      for (var j = 0; j < tagElements.length; j++)
        elements.push(tagElements[j]);
    }
    return elements;
  },

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

    if (!typeName && !name)
      return inputs;

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

    return matchingInputs;
  },

  disable: function(form) {
    var elements = Form.getElements(form);
    for (var i = 0; i < elements.length; i++) {
      var element = elements[i];
      element.blur();
      element.disabled = 'true';
    }
  },

  enable: function(form) {
    var elements = Form.getElements(form);
    for (var i = 0; i < elements.length; i++) {
      var element = elements[i];
      element.disabled = '';
    }
  },

  findFirstElement: function(form) {
    return Form.getElements(form).find(function(element) {
      return element.type != 'hidden' && !element.disabled &&
        ['input', 'select', 'textarea'].include(element.tagName.toLowerCase());
    });
  },

  focusFirstElement: function(form) {
    Field.activate(Form.findFirstElement(form));
  },

  reset: function(form) {
    $(form).reset();
  }
};

Form.Element = {
  serialize: function(element) {
    element = $(element);
    var method = element.tagName.toLowerCase();
    var parameter = Form.Element.Serializers[method](element);

    if (parameter) {
      var key = encodeURIComponent(parameter[0]);
      if (key.length == 0) return;

      if (parameter[1].constructor != Array)
        parameter[1] = [parameter[1]];

      return parameter[1].map(function(value) {
        return key + '=' + encodeURIComponent(value);
      }).join('&');
    }
  },

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

    if (parameter)
      return parameter[1];
  }
};

Form.Element.Serializers = {
  input: function(element) {
    switch (element.type.toLowerCase()) {
      case 'submit':
      case 'hidden':
      case 'password':
      case 'text':
        return Form.Element.Serializers.textarea(element);
      case 'checkbox':
      case 'radio':
        return Form.Element.Serializers.inputSelector(element);
    }
    return false;
  },

  inputSelector: function(element) {
    if (element.checked)
      return [element.name, element.value];
  },

  textarea: function(element) {
    return [element.name, element.value];
  },

  select: function(element) {
    return Form.Element.Serializers[element.type == 'select-one' ?
      'selectOne' : 'selectMany'](element);
  },

  selectOne: function(element) {
    var value = '', opt, index = element.selectedIndex;
    if (index >= 0) {
      opt = element.options[index];
      value = opt.value || opt.text;
    }
    return [element.name, value];
  },

  selectMany: function(element) {
    var value = [];
    for (var i = 0; i < element.length; i++) {
      var opt = element.options[i];
      if (opt.selected)
        value.push(opt.value || opt.text);
    }
    return [element.name, value];
  }
};

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

var $F = Form.Element.getValue;

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

Abstract.TimedObserver = function() {};
Abstract.TimedObserver.prototype = {
  initialize: function(element, frequency, callback) {
    this.frequency = frequency;
    this.element   = $(element);
    this.callback  = callback;

    this.lastValue = this.getValue();
    this.registerCallback();
  },

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

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

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

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

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

Abstract.EventObserver = function() {};
Abstract.EventObserver.prototype = {
  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() {
    var elements = Form.getElements(this.element);
    for (var i = 0; i < elements.length; i++)
      this.registerCallback(elements[i]);
  },

  registerCallback: function(element) {
    if (element.type) {
      switch (element.type.toLowerCase()) {
        case 'checkbox':
        case 'radio':
          Event.observe(element, 'click', this.onElementEvent.bind(this));
          break;
        case 'password':
        case 'text':
        case 'textarea':
        case 'select-one':
        case 'select-multiple':
          Event.observe(element, 'change', this.onElementEvent.bind(this));
          break;
      }
    }
  }
};

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

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

Object.extend(Event, {
  KEY_BACKSPACE: 8,
  KEY_TAB:       9,
  KEY_RETURN:   13,
  KEY_ESC:      27,
  KEY_LEFT:     37,
  KEY_UP:       38,
  KEY_RIGHT:    39,
  KEY_DOWN:     40,
  KEY_DELETE:   46,

  element: function(event) {
    return event.target || event.srcElement;
  },

  isLeftClick: function(event) {
    return (((event.which) && (event.which == 1)) ||
            ((event.button) && (event.button == 1)));
  },

  pointerX: function(event) {
    return event.pageX || (event.clientX +
      (document.documentElement.scrollLeft || document.body.scrollLeft));
  },

  pointerY: function(event) {
    return event.pageY || (event.clientY +
      (document.documentElement.scrollTop || document.body.scrollTop));
  },

  stop: function(event) {
    if (event.preventDefault) {
      event.preventDefault();
      event.stopPropagation();
    } else {
      event.returnValue = false;
      event.cancelBubble = true;
    }
  },

  // find the first node with the given tagName, starting from the
  // node the event was triggered on; traverses the DOM upwards
  findElement: function(event, tagName) {
    var element = Event.element(event);
    while (element.parentNode && (!element.tagName ||
        (element.tagName.toUpperCase() != tagName.toUpperCase())))
      element = element.parentNode;
    return element;
  },

  observers: false,

  _observeAndCache: function(element, name, observer, useCapture) {
    if (!this.observers) this.observers = [];
    if (element.addEventListener) {
      this.observers.push([element, name, observer, useCapture]);
      element.addEventListener(name, observer, useCapture);
    } else if (element.attachEvent) {
      this.observers.push([element, name, observer, useCapture]);
      element.attachEvent('on' + name, observer);
    }
  },

  unloadCache: function() {
    if (!Event.observers) return;
    for (var i = 0; i < Event.observers.length; i++) {
      Event.stopObserving.apply(this, Event.observers[i]);
      Event.observers[i][0] = null;
    }
    Event.observers = false;
  },

  observe: function(element, name, observer, useCapture) {
    var element = $(element);
    useCapture = useCapture || false;

    if (name == 'keypress' &&
        (navigator.appVersion.match(/Konqueror|Safari|KHTML/)
        || element.attachEvent))
      name = 'keydown';

    this._observeAndCache(element, name, observer, useCapture);
  },

  stopObserving: function(element, name, observer, useCapture) {
    var element = $(element);
    useCapture = useCapture || false;

    if (name == 'keypress' &&
        (navigator.appVersion.match(/Konqueror|Safari|KHTML/)
        || element.detachEvent))
      name = 'keydown';

    if (element.removeEventListener) {
      element.removeEventListener(name, observer, useCapture);
    } else if (element.detachEvent) {
      element.detachEvent('on' + name, observer);
    }
  }
});

/* prevent memory leaks in IE */
if (navigator.appVersion.match(/\bMSIE\b/))
  Event.observe(window, 'unload', Event.unloadCache, false);
var Position = {
  // set to true if needed, warning: firefox performance problems
  // NOT neeeded for page scrolling, only if draggable contained in
  // scrollable elements
  includeScrollOffsets: false,

  // must be called before calling withinIncludingScrolloffset, every time the
  // page is scrolled
  prepare: function() {
    this.deltaX =  window.pageXOffset
                || document.documentElement.scrollLeft
                || document.body.scrollLeft
                || 0;
    this.deltaY =  window.pageYOffset
                || document.documentElement.scrollTop
                || document.body.scrollTop
                || 0;
  },

  realOffset: function(element) {
    var valueT = 0, valueL = 0;
    do {
      valueT += element.scrollTop  || 0;
      valueL += element.scrollLeft || 0;
      element = element.parentNode;
    } while (element);
    return [valueL, valueT];
  },

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

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

  offsetParent: 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;
  },

  // caches x/y coordinate pair to use with overlap
  within: function(element, x, y) {
    if (this.includeScrollOffsets)
      return this.withinIncludingScrolloffsets(element, x, y);
    this.xcomp = x;
    this.ycomp = y;
    this.offset = this.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 = this.realOffset(element);

    this.xcomp = x + offsetcache[0] - this.deltaX;
    this.ycomp = y + offsetcache[1] - this.deltaY;
    this.offset = this.cumulativeOffset(element);

    return (this.ycomp >= this.offset[1] &&
            this.ycomp <  this.offset[1] + element.offsetHeight &&
            this.xcomp >= this.offset[0] &&
            this.xcomp <  this.offset[0] + element.offsetWidth);
  },

  // within must be called directly before
  overlap: function(mode, element) {
    if (!mode) return 0;
    if (mode == 'vertical')
      return ((this.offset[1] + element.offsetHeight) - this.ycomp) /
        element.offsetHeight;
    if (mode == 'horizontal')
      return ((this.offset[0] + element.offsetWidth) - this.xcomp) /
        element.offsetWidth;
  },

  clone: function(source, target) {
    source = $(source);
    target = $(target);
    target.style.position = 'absolute';
    var offsets = this.cumulativeOffset(source);
    target.style.top    = offsets[1] + 'px';
    target.style.left   = offsets[0] + 'px';
    target.style.width  = source.offsetWidth + 'px';
    target.style.height = source.offsetHeight + 'px';
  },

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

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

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

    } while (element = element.offsetParent);

    element = forElement;
    do {
      valueT -= element.scrollTop  || 0;
      valueL -= element.scrollLeft || 0;
    } while (element = element.parentNode);

    return [valueL, valueT];
  },

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

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

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

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

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

  absolutize: function(element) {
    element = $(element);
    if (element.style.position == 'absolute') return;
    Position.prepare();

    var offsets = Position.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';;
  },

  relativize: function(element) {
    element = $(element);
    if (element.style.position == 'relative') return;
    Position.prepare();

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

// Safari returns margins on body which is incorrect if the child is absolutely
// positioned.  For performance reasons, redefine Position.cumulativeOffset for
// KHTML/WebKit only.
if (/Konqueror|Safari|KHTML/.test(navigator.userAgent)) {
  Position.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 [valueL, valueT];
  };
}var ETechnik = {
	version: "3.0.0.0 Beta",
	copyright: "� 2005-2006 E-Technik Website Development (Pty) Ltd",
	trueFn: function() { return true; },
	falseFn: function() { return false; }
};
var Browser = {};
if(navigator.userAgent.indexOf("MSIE") != -1) {
	Browser.isIE = true;
}
if(navigator.userAgent.indexOf("MSIE 7") != -1) {
	Browser.isIE7 = true;
}
if(document.defaultView) {
	Browser.isMoz = true;
}

var Document = new Object();
Object.extend(Document, {
	loadCss: function(path, onload) {
		var link = document.createElement("link");
		link.href = path;
		link.rel = "stylesheet";
		link.type = "text/css";
		if (onload) Event.observe(link, "load", onload);
		document.body.appendChild(link);
	},
	loadScript: function(path, onload) {
		var script = document.createElement('script')
		script.type = "text/javascript";
		script.src = path;
		if (onload) Event.observe(script, "readystatechange", function(event) {
			if (Event.element(event).readyState == "loaded") this.onLoad();
		}.bindAsEventListener({ onLoad: onload }));
		document.body.appendChild(script);
	},
	getQueryVariable: function(variable) {
		var query = window.location.search.substring(1);
		var vars = query.split("&");
		for (var i=0; i < vars.length; i++) {
			var pair = vars[i].split("=");
			if (pair[0] == variable) {
				return pair[1];
			}
		}
		return "";
	},

	/* Code originally from http://www.quirksmode.org/js/cookies.html */
	createCookie: function(name, value, days) {
		if(days) {
			var date = new Date();
			date.setTime(date.getTime()+(days*24*60*60*1000));
			var expires = "; expires="+date.toGMTString();
		}
		else var expires = "";
		document.cookie = name + "=" + value + expires + "; path=/";
	},

	readCookie: function(name) {
		var nameEQ = name + "=";
		var ca = document.cookie.split(';');
		for(var i=0;i < ca.length;i++) {
			var c = ca[i];
			while (c.charAt(0)==' ') c = c.substring(1,c.length);
			if (c.indexOf(nameEQ) == 0) return c.substring(nameEQ.length,c.length);
		}
		return null;
	},

	eraseCookie: function(name) {
		createCookie(name,"",-1);
	}
});

var Styles = {};
Object.extend(Styles, {
	/* Stylesheet manipulation - addRule code based on http://www.thescripts.com/forum/thread98428.html, */
	/*   others from yui-ext 0.33 (C) Copyright(c) 2006, Jack Slocum. */
	cache: [],
	mustRefresh: true,
	addRule: function(selector, rule) {
		var styleSheet;
		if (document.styleSheets) {
			if (document.styleSheets.length === 0) {
				var styleElement;
				if (document.createElement && (styleElement = document.createElement('style'))) {
					styleElement.type = 'text/css';
					document.getElementsByTagName('head')[0].appendChild(styleElement);
					styleSheet = styleElement.sheet;
				}
			}
			if (document.styleSheets.length > 0) {
				styleSheet = document.styleSheets[document.styleSheets.length - 1];
				if (styleSheet.insertRule) {
					styleSheet.insertRule(selector + ' { ' + rule + ' }', styleSheet.cssRules.length);
				} else if (styleSheet.addRule) {
					styleSheet.addRule(selector, rule);
				}
			}
		}
		this.mustRefresh = true;
	},
	getRules: function(refresh){
		if(this.mustRefresh || refresh){
			this.cache = {};
			var ds = document.styleSheets;
			for(var i = 0, len = ds.length; i < len; i++) {
				try{
					var ss = ds[i];
					var ssRules = ss.cssRules || ss.rules;
					for(var j = ssRules.length - 1; j >= 0; --j){
						this.cache[ssRules[j].selectorText] = ssRules[j];
					}
				} catch(e) {} // try catch for cross domain access issue
			}
		}
		return this.cache;
	},
	getRule: function(selector, refresh){
		var rs = this.getRules(refresh);
		if(!(selector instanceof Array)) {
			return rs[selector];
		}
		for(var i = 0; i < selector.length; i++) {
			if(rs[selector[i]]){
				return rs[selector[i]];
			}
		}
		return null;
	},
	updateRule: function(selector, name, value) {
   		if(!(selector instanceof Array)) {
   			var rule = this.getRule(selector);
   			if(rule){
   				rule.style[name.camelize()] = value;
   				return true;
   			}
   		} else {
   			for(var i = 0; i < selector.length; i++) {
   				if(this.updateRule(selector[i], property, value)) {
   					return true;
   				}
   			}
   		}
   		return false;
	}
});

var Positioning = {
	positionedOffset: function(e) {
		var p = Position.positionedOffset(e);
		return new Point(p[0], p[1]);
	},
	cumulativeOffset: function(e) {
		var p = Position.cumulativeOffset(e);
		return new Point(p[0], p[1]);
	},
	realOffset: function(e) {
		var p = Position.realOffset(e);
		return new Point(p[0], p[1]);
	},
	scrolledOffset: function(e) {
		var pc = this.cumulativeOffset(e);
		var pr = this.realOffset(e.parentNode);
		return new Point(pc.x - pr.x, pc.y - pr.y);
	},
	isDisplayed: function(e) {
		var p = e;
		while(p && p != document.body) {
			if(p.style.display == "none") {
				return false;
			}
			p = p.parentNode;
		}
		return true;
	}
};

var Point = Class.create();
Point.prototype = {
	initialize: function(x, y) {
		this.x = x;
		this.y = y;
	},
	toString: function() {
		return "[" + this.x + ", " + this.y + "]";
	},
	clone: function() {
		return new Point(this.x, this.y);
	}
};

var Rect = Class.create();
Rect.prototype = {
	initialize: function(P, S, w, h) {
		if(w == null) 	{
			this.x = P.x; this.y = P.y; this.w = S.w; this.h = S.h;
		} else {
			this.x = P; this.y = S; this.w = w; this.h = h;
		}
	},
	isEqualTo: function(r, d) {
		if (!d) d = 0;
		return (Math.abs(r.x - this.x) <= d &&
				Math.abs(r.y - this.y) <= d &&
				Math.abs(r.w - this.w) <= d &&
				Math.abs(r.h - this.h) <= d);
	},
	getRight: function() {
		return this.x + this.w;
	},
	getBottom: function() {
		return this.y + this.h;
	},
	enlarge: function(t, l, r, b) {
		this.y -= t ? t : 0;
		this.x -= l ? l : 0;
		this.w += l ? l : 0 + r ? r : 0;
		this.h += t ? t : 0 + b ? b : 0;
	},
	toString: function() {
		return "[" + this.x + " " + this.y + " " + this.w + " " + this.h + "]";
	}
};

var Line = Class.create();
Line.prototype = {
	initialize: function(P0, P1) {
		this.P0 = P0;
		this.P1 = P1;
	}
};


Object.extend(Element, {
	//Originally from prototype. IE7 does not correctly measure elements if they contain padding and subelements and so on unless they
	//are displayed. So instead of making them invisible and measuring, make them visible off screen and measure them.
	//There may be problems with elements positioned by right and bottom.
	//TODO: Check right and bottom.
	getDimensions: function(element) {
		element = $(element);
		if (Element.getStyle(element, "display") != "none")
			return {width: element.offsetWidth, height: element.offsetHeight};

		// All *Width and *Height properties give 0 on elements with display none,
		// so enable the element temporarily
		var els = element.style;
		//var originalVisibility = els.visibility;
		var originalPosition = els.position;
		var originalTop = els.top;
		var originalLeft = els.left;
		var originalRight = els.right;
		var originalBottom = els.bottom;
		els.top = -4096;
		els.left = -4096;
		//els.visibility = "hidden";
		els.position = "absolute";
		els.display = "";
		var originalWidth = element.offsetWidth;
		var originalHeight = element.offsetHeight;
		els.display = "none";
		els.position = originalPosition;
		//els.visibility = originalVisibility;
		els.top = originalTop;
		els.left = originalLeft;
		els.right = originalRight;
		els.bottom = originalBottom;
		return {width: originalWidth, height: originalHeight};
	},

	//In IE6 style.width == offsetWidth
	//In Mozilla style.width == offsetWidth - style.padding - style.borderWidth
	//In IE7 style.width == offsetWidth - style.padding - style.borderWidth
	getSizePadding: function(e, oldIE) {
		if(Browser.isMoz) {
			var d = Element.getDimensions(e);
			var cs = document.defaultView.getComputedStyle(e, null);
			return { w: d.width - parseInt(cs.getPropertyValue("width")),
					 h: d.height - parseInt(cs.getPropertyValue("height")) };
		} else if(Browser.isIE7 || (Browser.isIE && oldIE)) {
			var bl = parseInt(e.currentStyle.borderLeftWidth);
			if(isNaN(bl)) { bl = 0; }
			var br = parseInt(e.currentStyle.borderRightWidth);
			if(isNaN(br)) { br = 0; }
			var bt = parseInt(e.currentStyle.borderTopWidth);
			if(isNaN(bt)) { bt = 0; }
			var bb = parseInt(e.currentStyle.borderBottomWidth);
			if(isNaN(bb)) { bb = 0; }
			return { w: parseInt(e.currentStyle.paddingLeft) + parseInt(e.currentStyle.paddingRight) + bl + br,
					h: parseInt(e.currentStyle.paddingTop) + parseInt(e.currentStyle.paddingBottom) + bt + bb };
		} else {
			return { w: 0, h: 0 };
		}
	},

	getPositionOffset: function(e) {
		if(Browser.isMoz) {
			var cs = document.defaultView.getComputedStyle(e, null);
			return { w: parseInt(cs.getPropertyValue("border-left-width")) * 2,
					 h: parseInt(cs.getPropertyValue("border-top-width")) * 2 };
		} else if(Browser.isIE) {
			var bl = parseInt(e.currentStyle.borderLeftWidth);
			if(isNaN(bl)) { bl = 0; }
			var bt = parseInt(e.currentStyle.borderTopWidth);
			if(isNaN(bt)) { bt = 0; }
			return { w: bl,	h: bt };
		} else {
			return { w: 0, h: 0 };
		}
	},

	getSizeOffset: function(e) {
		if(Browser.isMoz) {
			var cs = document.defaultView.getComputedStyle(e, null);
			return { w: parseInt(cs.getPropertyValue("border-right-width")) * 2,
					 h: parseInt(cs.getPropertyValue("border-bottom-width")) * 2 };
		} else if(Browser.isIE) {
			var br = parseInt(e.currentStyle.borderRightWidth);
			if(isNaN(br)) { br = 0; }
			var bb = parseInt(e.currentStyle.borderBottomWidth);
			if(isNaN(bb)) { bb = 0; }
			var bl = parseInt(e.currentStyle.borderLeftWidth);
			if(isNaN(bl)) { bl = 0; }
			var bt = parseInt(e.currentStyle.borderTopWidth);
			if(isNaN(bt)) { bt = 0; }
			return { w: bl + br, h: bt + bb };
		} else {
			return { w: 0, h: 0 };
		}
	},

	getInnerText: function(element, stopAtTag) {
		if (typeof element == "string") return element;
		if (typeof element == "undefined") { return element };
		if (!stopAtTag && element.innerText) return element.innerText;
		var innerText = "";
		var children = element.childNodes;
		if (stopAtTag) stopAtTag = stopAtTag.toUpperCase();
		for (var i = 0; i < children.length; i++) {
			if (stopAtTag && children[i].tagName && stopAtTag == children[i].tagName.toUpperCase()) return innerText;
			switch (children[i].nodeType) {
				case 1: //ELEMENT_NODE
					innerText += Element.getInnerText(children[i], stopAtTag);
					break;
				case 3:	//TEXT_NODE
					innerText += children[i].nodeValue;
					break;
			}
		}
		return innerText;
	},

	findParent: function(element, tagName) {
		var parent = $(element);
		while (parent.parentNode && (!parent.tagName ||
				(parent.tagName.toUpperCase() != tagName.toUpperCase())))
			parent = parent.parentNode;
		return parent;
	},

	findChild: function(element, tagName) {
		var children = element.getElementsByTagName(tagName);
		if(children.length > 0) {
			return children[0];
		}
		return null;
	},

	setTextSelectable: function(element, isSelectable) {
		if(isSelectable) {
			if(Browser.isIE) {
				Event.stopObserving(element, "selectstart", ETechnik.falseFn);
			} else if(Browser.isMoz) {
				element.style.MozUserSelect = "";
			}
		} else {
			if(Browser.isIE) {
				Event.observe(element, "selectstart", ETechnik.falseFn);
			} else if(Browser.isMoz) {
				element.style.MozUserSelect = "none";
			}
		}
	},

	fromHTML: function(HTML, tagName) {
		var div = document.createElement("DIV");
		if (tagName != null && new Array( "TR", "TD", "TBODY", "THEAD" ).indexOf(tagName.toUpperCase()) != -1) {
			Element.update(div, "<table>" + HTML + "</table>");
			return $A(div.getElementsByTagName(tagName))[0];
		}
		Element.update(div, HTML);
		return $A(div.getElementsByTagName(tagName))[0];
	}
});

//AspectOP from http://beppu.lbox.org/articles/2006/09/06/actsasaspect
function actsAsAspect(object) {
  object.yield = null;
  object.rv    = { };
  object.before  = function(method, f) {
    var original = eval("this." + method);
    this[method] = function() {
      f.apply(this, arguments);
      return original.apply(this, arguments);
    };
  };
  object.after   = function(method, f) {
    var original = eval("this." + method);
    this[method] = function() {
      this.rv[method] = original.apply(this, arguments);
      return f.apply(this, arguments);
    }
  };
  object.around  = function(method, f) {
    var original = eval("this." + method);
    this[method] = function() {
      this.yield = original;
      return f.apply(this, arguments);
    }
  };
}


//Class from http://twologic.com/projects/inheritance/
/*
  Class, version 2.2
  Copyright (c) 2006, Alex Arnell <alex@twologic.com>
  Licensed under the new BSD License. See end of file for full license terms.
*/

var Class = {
  extend: function(parent, def) {
    if (arguments.length == 1) def = parent, parent = null;
    var func = function() {
      if (!Class.extending) this.initialize.apply(this, arguments);
    };
    if (typeof(parent) == "function") {
      Class.extending = true;
      func.prototype = new parent();
      delete Class.extending;
    }
    var mixins = [];
    if (def && def.include) {
      if (def.include.reverse) {
        // methods defined in later mixins should override prior
        mixins = mixins.concat(def.include.reverse());
      } else {
        mixins.push(def.include);
      }
      delete def.include; // clean syntax sugar
    }
    if (def) Class.inherit(func.prototype, def);
    for (var i = 0; (mixin = mixins[i]); i++) {
      Class.mixin(func.prototype, mixin);
    }
    return func;
  },
  mixin: function (dest) {
    for (var i = 1; (src = arguments[i]); i++) {
      if (typeof(src) != "undefined" && src !== null) {
        for (var prop in src) {
          if (!dest[prop] && typeof(src[prop]) == "function") {
            // only mixin functions, if they don"t previously exist
            dest[prop] = src[prop];
          }
        }
      }
    }
    return dest;
  },
  inherit: function(dest, src, fname) {
    if (arguments.length == 3) {
      var ancestor = dest[fname], descendent = src[fname], method = descendent;
      descendent = function() {
        var ref = this.parent; this.parent = ancestor;
        var result = method.apply(this, arguments);
        ref ? this.parent = ref : delete this.parent;
        return result;
      };
      // mask the underlying method
      descendent.valueOf = function() { return method; };
      descendent.toString = function() { return method.toString(); };
      dest[fname] = descendent;
    } else {
      for (var prop in src) {
        if (dest[prop] && typeof(src[prop]) == "function") {
          Class.inherit(dest, src, prop);
        } else {
          dest[prop] = src[prop];
        }
      }
    }
    return dest;
  }
};

// finally remap Class.create for backward compatability with prototype
Class.create = function() {
  return Class.extend.apply(this, arguments);
};

//By Marc Heiligers again
Class.setAttributes = function(obj, attr, def) {
	if(def) {
		Object.extend(def, attr);
	} else {
		def = attr;
	}
	Object.extend(obj, def);
};

Object.extend(Event, {
	isAltPressed: function(event) {
		return event.altKey;
	},
	isCtrlPressed: function(event) {
		return event.ctrlKey;
	},
	isShiftPressed: function(event) {
		return event.shiftKey;
	}
});

Object.extend(String.prototype, {
	trim: function() {
		return this.replace(/^\s*|\s*$/g, "");
	},
	xmlDecode: function() {
		return this.replace(/\&\#39\;/g, "'");
	},
	startsWith: function(what) {
		return (this.substr(0, what.length) == what);
	},
	endsWith: function(what) {
		return (this.substr(this.length - what.length) == what);
	},
	removeLast: function(count) {
		return this.substr(0, this.length - count);
	},
	removeFirst: function(count) {
		return this.substr(count);
	}
});

if(!Array.prototype.add) {
	Array.prototype.add = function(item) {
		this[this.length] = item;
		return item;
	}
};

if(!Array.prototype.merge) {
	Array.prototype.merge = function(array) {
		for(var i = 0; i < array.length; ++i) {
			this[this.length] = array[i];
		}
		return this;
	}
}

if (!Array.prototype.push) {
   Array.prototype.add = function(item) {
	   this[this.length] = item;
	   return this.length - 1;
   }
};

/* Modified from Rico.js (http://openrico.org/) */
if (!Array.prototype.removeAt) {
   Array.prototype.removeAt = 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;
   };
};

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

//From PrototypeWindow window.js - originally from QuirksMode.org
window.getPageSize = function(){
  	var xScroll, yScroll;

  	if (window.innerHeight && window.scrollMaxY) {
  		xScroll = document.body.scrollWidth;
  		yScroll = window.innerHeight + window.scrollMaxY;
  	} else if (document.body.scrollHeight > document.body.offsetHeight){ // all but Explorer Mac
  		xScroll = document.body.scrollWidth;
  		yScroll = document.body.scrollHeight;
  	} else { // Explorer Mac...would also work in Explorer 6 Strict, Mozilla and Safari
  		xScroll = document.body.offsetWidth;
  		yScroll = document.body.offsetHeight;
  	}

  	var windowWidth, windowHeight;

  	if (self.innerHeight) {	// all except Explorer
  		windowWidth = self.innerWidth;
  		windowHeight = self.innerHeight;
  	} else if (document.documentElement && document.documentElement.clientHeight) { // Explorer 6 Strict Mode
  		windowWidth = document.documentElement.clientWidth;
  		windowHeight = document.documentElement.clientHeight;
  	} else if (document.body) { // other Explorers
  		windowWidth = document.body.clientWidth;
  		windowHeight = document.body.clientHeight;
  	}
  	var pageHeight, pageWidth;

  	// for small pages with total height less then height of the viewport
  	if(yScroll < windowHeight){
  		pageHeight = windowHeight;
  	} else {
  		pageHeight = yScroll;
  	}

  	// for small pages with total width less then width of the viewport
  	if(xScroll < windowWidth){
  		pageWidth = windowWidth;
  	} else {
  		pageWidth = xScroll;
  	}

  	return {pageWidth: pageWidth - 50, pageHeight: pageHeight - 50, windowWidth: windowWidth, windowHeight: windowHeight};
};

var Color = {};
Object.extend(Color, {
	//Expects either seperate r, g and b values or a comma seperated list r, g, b or rgb(r, g, b)
	rgbToHex: function(r, g, b) {
		if(r == null || (typeof(r) == "string" && (r.startsWith("#") || r.indexOf(",") == -1))) {
			return r;
		}
		if(typeof(r) == "string") {
			p = r.split("(");
			if(p.length == 1) {
				var i = 0;
			} else {
				var i = 1;
			}
			p1 = p[i].split(",");
			r = parseInt(p1[0]);
			g = parseInt(p1[1]);
			b = parseInt(p1[2]);
		}
		var r1 = this.byteToHex(Math.floor(r / 16));
		var r2 = this.byteToHex(r % 16);
		var g1 = this.byteToHex(Math.floor(g / 16));
		var g2 = this.byteToHex(g % 16);
		var b1 = this.byteToHex(Math.floor(b / 16));
		var b2 = this.byteToHex(b % 16);

		return "#" + r1 + r2 + g1 + g2 + b1 + b2;
	},

	hexToRgb: function(hex) {
		if(hex.startsWith("#")) {
			hex = hex.substr(1);
		}
		var r = this.hexToByte(hex.substr(0, 1)) * 16 + this.hexToByte(hex.substr(1, 1));
		var g = this.hexToByte(hex.substr(2, 1)) * 16 + this.hexToByte(hex.substr(3, 1));
		var b = this.hexToByte(hex.substr(4, 1)) * 16 + this.hexToByte(hex.substr(5, 1));
		return [ r, g, b ];
	},

	byteToHex: function(b) {
		switch(b) {
			case 10:
				return "A";
			case 11:
				return "B";
			case 12:
				return "C";
			case 13:
				return "D";
			case 14:
				return "E";
			case 15:
				return "F";
			default:
				return "" + b;
		}
	},

	hexToByte: function(hex) {
		switch(hex) {
			case "A":
				return 10;
			case "B":
				return 11;
			case "C":
				return 12;
			case "D":
				return 13;
			case "E":
				return 14;
			case "F":
				return 15;
			default:
				return parseInt(hex);
		}
	},

	//TODO: Improve for other cases
	tryGetColor: function(color) {
		if(color == null) {
			return null;
		}
		if(typeof(color) == "string") {
			color = color.trim();
			if(color == "" || color == "transparent" || color == "undefined" || color == "null") {
				return null;
			}
			if(color.startsWith("#") && color.length == 7) {
				return color;
			}
			if(color.startsWith("rgb") || color.indexOf(",") != -1) {
				return this.rgbToHex(color);
			}
			color = color.toLowerCase();
			var c = $H(this.colorNames).find(function(col) {
				if(color == col[0]) {
					return true;
				}
			});
			if(c) {
				return "#" + c[1];
			}
		}
		return null;
	},

	//Originally C# from http://www.codeproject.com/useritems/IdealTextColor.asp
	idealTextColor: function(bg) {
		if(bg == null) {
			return "#000000";
		}
		var color = this.hexToRgb(bg);
	    var threshold = 105;
	    var bgDelta = (color[0] * 0.299) + (color[1] * 0.587) + (color[2] * 0.114);
	    return (255 - bgDelta < threshold) ? "#000000" : "#FFFFFF";
	},

	//Originally from http://www.webreference.com/programming/javascript/mk/column3/index.html
	colorNames: {
		"aliceblue" : "F0F8FF",
		"darkslategray" : "2F4F4F",
		"lightsalmon" : "FFA07A",
		"palevioletred" : "DB7093",
		"antiquewhite" : "FAEBD7",
		"darkturquoise" : "00CED1",
		"lightseagreen" : "20B2AA",
		"papayawhip" : "FFEFD5",
		"aqua" : "00FFFF",
		"darkviolet" : "9400D3",
		"lightskyblue" : "87CEFA",
		"peachpuff" : "FFDAB9",
		"aquamarine" : "7FFFD4",
		"deeppink" : "FF1493",
		"lightslategray" : "778899",
		"peru" : "CD853F",
		"azure" : "F0FFFF",
		"deepskyblue" : "00BFFF",
		"lightsteelblue" : "B0C4DE",
		"pink" : "FFC0CB",
		"beige" : "F5F5DC",
		"dimgray" : "696969",
		"lightyellow" : "FFFFE0",
		"plum" : "DDA0DD",
		"bisque" : "FFE4C4",
		"dodgerblue" : "1E90FF",
		"lime" : "00FF00",
		"powderblue" : "B0E0E6",
		"black" : "000000",
		"firebrick" : "B22222",
		"limegreen" : "32CD32",
		"purple" : "800080",
		"blanchedalmond" : "FFEBCD",
		"floralwhite" : "FFFAF0",
		"linen" : "FAF0E6",
		"red" : "FF0000",
		"blue" : "0000FF",
		"forestgreen" : "228B22",
		"magenta" : "FF00FF",
		"rosybrown" : "BC8F8F",
		"blueviolet" : "8A2BE2",
		"fuchsia" : "FF00FF",
		"maroon" : "800000",
		"royalblue" : "4169E1",
		"brown" : "A52A2A",
		"gainsboro" : "DCDCDC",
		"mediumaquamarine" : "66CDAA",
		"saddlebrown" : "8B4513",
		"burlywood" : "DEB887",
		"ghostwhite" : "F8F8FF",
		"mediumblue" : "0000CD",
		"salmon" : "FA8072",
		"cadetblue" : "5F9EA0",
		"gold" : "FFD700",
		"mediumorchid" : "BA55D3",
		"sandybrown" : "F4A460",
		"chartreuse" : "7FFF00",
		"goldenrod" : "DAA520",
		"mediumpurple" : "9370DB",
		"seagreen" : "2E8B57",
		"chocolate" : "D2691E",
		"gray" : "808080",
		"mediumseagreen" : "3CB371",
		"seashell" : "FFF5EE",
		"coral" : "FF7F50",
		"green" : "008000",
		"mediumslateblue" : "7B68EE",
		"sienna" : "A0522D",
		"cornflowerblue" : "6495ED",
		"greenyellow" : "ADFF2F",
		"mediumspringgreen" : "00FA9A",
		"silver" : "C0C0C0",
		"cornsilk" : "FFF8DC",
		"honeydew" : "F0FFF0",
		"mediumturquoise" : "48D1CC",
		"skyblue" : "87CEEB",
		"crimson" : "DC143C",
		"hotpink" : "FF69B4",
		"mediumvioletred" : "C71585",
		"slateblue" : "6A5ACD",
		"cyan" : "00FFFF",
		"indianred" : "CD5C5C",
		"midnightblue" : "191970",
		"slategray" : "708090",
		"darkblue" : "00008B",
		"indigo" : "4B0082",
		"mintcream" : "F5FFFA",
		"snow" : "FFFAFA",
		"darkcyan" : "008B8B",
		"ivory" : "FFFFF0",
		"mistyrose" : "FFE4E1",
		"springgreen" : "00FF7F",
		"darkgoldenrod" : "B8860B",
		"khaki" : "F0E68C",
		"moccasin" : "FFE4B5",
		"steelblue" : "4682B4",
		"darkgray" : "A9A9A9",
		"lavender" : "E6E6FA",
		"navajowhite" : "FFDEAD",
		"tan" : "D2B48C",
		"darkgreen" : "006400",
		"lavenderblush" : "FFF0F5",
		"navy" : "000080",
		"teal" : "008080",
		"darkkhaki" : "BDB76B",
		"lawngreen" : "7CFC00",
		"oldlace" : "FDF5E6",
		"thistle" : "D8BFD8",
		"darkmagenta" : "8B008B",
		"lemonchiffon" : "FFFACD",
		"olive" : "808000",
		"tomato" : "FD6347",
		"darkolivegreen" : "556B2F",
		"lightblue" : "ADD8E6",
		"olivedrab" : "6B8E23",
		"turquoise" : "40E0D0",
		"darkorange" : "FF8C00",
		"lightcoral" : "F08080",
		"orange" : "FFA500",
		"violet" : "EE82EE",
		"darkorchid" : "9932CC",
		"lightcyan" : "E0FFFF",
		"orangered" : "FF4500",
		"wheat" : "F5DEB3",
		"darkred" : "8B0000",
		"lightgoldenrodyellow" : "FAFAD2",
		"orchid" : "DA70D6",
		"white" : "FFFFFF",
		"darksalmon" : "E9967A",
		"lightgreen" : "90EE90",
		"palegoldenrod" : "EEE8AA",
		"whitesmoke" : "F5F5F5",
		"darkseagreen" : "8FBC8F",
		"lightgrey" : "D3D3D3",
		"palegreen" : "98FB98",
		"yellow" : "FFFF00",
		"darkslateblue" : "483D8B",
		"lightpink" : "FFB6C1",
		"paleturquoise" : "AFEEEE",
		"yellowgreen" : "9ACD32"
	}
});

//These functions come from the Epoch calendar script by Nick Baicoianu
//They have been released under the LGPL license (see js/epoch_classes.js)
Date.prototype.getDayOfYear = function () //returns the day of the year for this date
{
	return parseInt((this.getTime() - new Date(this.getFullYear(),0,1).getTime())/86400000 + 1);
};
//-----------------------------------------------------------------------------
Date.prototype.getWeek = function () //returns the day of the year for this date
{
	return parseInt((this.getTime() - new Date(this.getFullYear(),0,1).getTime())/604800000 + 1);
};
//-----------------------------------------------------------------------------
Date.prototype.getUeDay = function () //returns the number of DAYS since the UNIX Epoch - good for comparing the date portion
{
	return parseInt(Math.floor((this.getTime() - this.getTimezoneOffset() * 60000)/86400000)); //must take into account the local timezone
};
//-----------------------------------------------------------------------------
//Modification by Marc Heiligers - make month and day names available globally.
Date.MonthNames = ['January','February','March','April','May','June','July','August','September','October','November','December','Jan','Feb','Mar','Apr','May','Jun','Jul','Aug','Sep','Oct','Nov','Dec'];
Date.DayNames = ['Sunday','Monday','Tuesday','Wednesday','Thursday','Friday','Saturday','Sun','Mon','Tue','Wed','Thu','Fri','Sat'];

Date.prototype.dateFormat = function(format)
{
	if(!format) { // the default date format to use - can be customized to the current locale
		format = 'd M Y';
	}
	LZ = function(x) {return(x < 0 || x > 9 ? '' : '0') + x};
	format = format + "";
	var result="";
	var i_format=0;
	var c="";
	var token="";
	var y=this.getFullYear().toString();
	var M=this.getMonth()+1;
	var d=this.getDate();
	var E=this.getDay();
	var H=this.getHours();
	var m=this.getMinutes();
	var s=this.getSeconds();
	var yyyy,yy,MMM,MM,dd,hh,h,mm,ss,ampm,HH,H,KK,K,kk,k;
	// Convert real this parts into formatted versions
	var value = new Object();
	//if (y.length < 4) {y=''+(y-0+1900);}
	value['Y'] = y.toString();
	value['y'] = y.substring(2);
	value['n'] = M;
	value['m'] = LZ(M);
	value['F'] = Date.MonthNames[M-1];
	value['M'] = Date.MonthNames[M+11];
	value['j'] = d;
	value['d'] = LZ(d);
	value['D'] = Date.DayNames[E+7];
	value['l'] = Date.DayNames[E];
	value['G'] = H;
	value['H'] = LZ(H);
	if (H==0) {value['g']=12;}
	else if (H>12){value['g']=H-12;}
	else {value['g']=H;}
	value['h']=LZ(value['g']);
	if (H > 11) {value['a']='pm'; value['A'] = 'PM';}
	else { value['a']='am'; value['A'] = 'AM';}
	value['i']=LZ(m);
	value['s']=LZ(s);
	//construct the result string
	while (i_format < format.length) {
		c=format.charAt(i_format);
		token="";
		while ((format.charAt(i_format)==c) && (i_format < format.length)) {
			token += format.charAt(i_format++);
			}
		if (value[token] != null) { result=result + value[token]; }
		else { result=result + token; }
		}
	return result;
};

//Additional methods by Marc Heiligers:
Date.prototype.addMilliseconds = function(ms) {
	return new Date(new Date().setTime(this.getTime() + (ms)));
};

Date.prototype.addHours = function(hours) {
	return this.addMilliseconds(hours * 3600000);
};

Date.prototype.addDays = function(days) {
	return this.addMilliseconds(days * 86400000);
};

Date.prototype.addYears = function(years) {
	return new Date(this.getFullYear() + years, this.getMonth(), this.getDate(), this.getHours(), this.getMinutes(), this.getSeconds(), this.getMilliseconds());
};

Date.prototype.isLeapYear = function() {
	var year = this.getYear();
	if (year % 4 == 0 && year % 400 != 0) {
		return true;
	}
	return false;
};

Date.prototype.daysInMonth = function() {
	var month = this.getMonth();
	if (month == 1) {
		if (this.isLeapYear()) {
			return 29;
		}
		return 28;
	}
	switch(month) {
		case 0:	case 2:	case 4:	case 6:	case 7:	case 9:	case 11: return 31;
		default: return 30;
	}
};

Date.prototype.getDateOnly = function() {
	return new Date(this.getFullYear(), this.getMonth(), this.getDate());
};

Date.prototype.getDayOf = function() {
	return new DateSpan(this.getDateOnly(), this.getDateOnly());
};

Date.prototype.getWeekOf = function() {
	var dayDiff = this.getDay() - 1;
	if (dayDiff < 0) dayDiff += 7;
	var start = this.addDays(-dayDiff).getDateOnly();
	return new DateSpan(start, start.addDays(6));
};

Date.prototype.getMonthOf = function() {
	return new DateSpan(
		new Date(this.getFullYear(), this.getMonth(), 1),
		new Date(this.getFullYear(), this.getMonth(), this.daysInMonth())
	);
};

Date.prototype.getQuarterOf = function() {
	var month = parseInt(this.getMonth() / 3) * 3;
	var endYear = this.getFullYear();
	var endMonth = month + 2;
	if (endMonth > 11) {
		year++;
		endMonth -= 12;
	}
	var endDate = new Date(endYear, endMonth, 1);
	return new DateSpan(
		new Date(this.getFullYear(), month, 1),
		new Date(endYear, endMonth, endDate.daysInMonth())
	);
};

Date.prototype.getYearOf = function() {
	return new DateSpan(
		new Date(this.getFullYear(), 0, 1),
		new Date(this.getFullYear(), 11, 31)
	);
};

Date.prototype.getPrevDayOf = function() {
	return this.addDays(-1).getDayOf();
};

Date.prototype.getNextDayOf = function() {
	return this.addDays(1).getDayOf();
};

Date.prototype.getPrevWeekOf = function() {
	return this.addDays(-7).getWeekOf();
};

Date.prototype.getNextWeekOf = function() {
	return this.addDays(7).getWeekOf();
};

Date.prototype.getPrevMonthOf = function() {
	return new Date(this.getFullYear(), this.getMonth(), 1).addDays(-1).getMonthOf();
};

Date.prototype.getNextMonthOf = function() {
	return new Date(this.getFullYear(), this.getMonth(), this.daysInMonth()).addDays(1).getMonthOf();
};

Date.prototype.getPrevQuarterOf = function() {
	var month = this.getMonth() - 3;
	var year = this.getFullYear();
	if (month < 0) {
		year--;
		month += 12;
	}
	return new Date(year, month, 1).getQuarterOf();
};

Date.prototype.getNextQuarterOf = function() {
	var month = this.getMonth() + 3;
	var year = this.getFullYear();
	if (month > 11) {
		year++;
		month -= 12;
	}
	return new Date(year, month, 1).getQuarterOf();
};

Date.prototype.getPrevYearOf = function() {
	return new Date(this.getFullYear() - 1, 0, 1).getYearOf();
};

Date.prototype.getNextYearOf = function() {
	return new Date(this.getFullYear() + 1, 0, 1).getYearOf();
};

var DateSpan = Class.create();
DateSpan.prototype = {
	initialize: function(start, end) {
		this.start = start;
		this.end = end;
	},
	intersects: function(period) {
		return (period.start <= this.end && period.end >= this.start);
	},
	contains: function(period) {
		return (period.start >= this.start && period.end <= this.end);
	},
	containsDate: function(date) {
		return (date >= this.start && date <= this.end);
	}
};
var ActiveElement = Class.create();
ActiveElement.prototype = {
	initialize: function(element, attributes) {
		this.element = $(element);
		Class.setAttributes(this, attributes, {
			elementClass: "activeNormal",
			elementHoverClass: "activeHover",
			elementActiveClass: "activeActive",
			elementSelectedClass: "activeSelected",
			elementDisabledClass: "activeDisabled"
		});

		if(!Element.hasClassName(this.element, this.elementClass)) {
			Element.addClassName(this.element, this.elementClass);
		}
		if((this.element.disabled || this.element.getAttribute("disabled") == "true") && !Element.hasClassName(this.element, this.elementDisabledClass)) {
			Element.addClassName(this.element, this.elementDisabledClass);
			this.element.prevDisabled = true;
		}
		if(this.element.getAttribute("selected") == "true" && !Element.hasClassName(element, this.elementSelectedClass)) {
			Element.addClassName(this.element, this.elementSelectedClass);
		}

		this._mouseOverListener = this.mouseOver.bindAsEventListener(this);
		this._mouseOutListener = this.mouseOut.bindAsEventListener(this);
		this._mouseDownListener = this.mouseDown.bindAsEventListener(this);
		this._mouseUpListener = this.mouseUp.bindAsEventListener(this);

		Event.observe(this.element, "mouseover", this._mouseOverListener);
		Event.observe(this.element, "mouseout", this._mouseOutListener);
		Event.observe(this.element, "mousedown", this._mouseDownListener);
		Event.observe(this.element, "mouseup", this._mouseUpListener);
	},

	mouseOver: function(event) {
		if(this.element && !this.element.disabled && this.element.getAttribute("disabled") != "true" && this.element.getAttribute("inactive") != "true") {
			Element.addClassName(this.element, this.elementHoverClass);
		}
	},

	mouseOut: function(event) {
		if(this.element) {
			Element.removeClassName(this.element, this.elementHoverClass);
			Element.removeClassName(this.element, this.elementActiveClass);
		}
	},

	mouseDown: function(event) {
		if(this.element && !this.element.disabled && this.element.getAttribute("disabled") != "true" && this.element.getAttribute("inactive") != "true") {
			Element.addClassName(this.element, this.elementActiveClass);
		}
	},

	mouseUp: function(event) {
		if(this.element && !this.element.disabled && this.element.getAttribute("disabled") != "true" && this.element.getAttribute("inactive") != "true") {
			Element.removeClassName(this.element, this.elementActiveClass);
		}
	},

	enable: function(enabled) {
		if(enabled === false) {
			this.disable();
			return;
		}
		Element.removeClassName(this.element, this.elementDisabledClass);
		this.element.disabled = false;
	},

	disable: function() {
		Element.addClassName(this.element, this.elementDisabledClass);
		Element.removeClassName(this.element, this.elementActiveClass);
		Element.removeClassName(this.element, this.elementHoverClass);
		this.element.prevDisabled = this.element.disabled;
		this.element.disabled = true;
	},

	select: function() {
		Element.addClassName(this.element, this.elementSelectedClass);
		this.element.setAttribute("selected", "true");
	},

	deselect: function() {
		Element.removeClassName(this.element, this.elementSelectedClass);
		this.element.setAttribute("selected", "false");
	},

	destroy: function() {
		Event.stopObserving(this.element, "mouseover", this._mouseOverListener);
		Event.stopObserving(this.element, "mouseout", this._mouseOutListener);
		Event.stopObserving(this.element, "mousedown", this._mouseDownListener);
		Event.stopObserving(this.element, "mouseup", this._mouseUpListener);
	}
};var ActivePanel = Class.create();
ActivePanel.prototype = {
	initialize: function(container, attributes) {
		this.container = $(container);
		Class.setAttributes(this, attributes, {
			elementTags: [ "A", "DIV", "SPAN", "BUTTON", "INPUT" ],
			elementClass: "activeNormal",
			elementHoverClass: "activeHover",
			elementActiveClass: "activeActive",
			elementSelectedClass: "activeSelected",
			elementDisabledClass: "activeDisabled",
			includeChildChildren: false
		});

		this.apply();

		this._mouseOverListener = this.mouseOver.bindAsEventListener(this);
		this._mouseOutListener = this.mouseOut.bindAsEventListener(this);
		this._mouseDownListener = this.mouseDown.bindAsEventListener(this);
		this._mouseUpListener = this.mouseUp.bindAsEventListener(this);

		Event.observe(this.container, "mouseover", this._mouseOverListener);
		Event.observe(this.container, "mouseout", this._mouseOutListener);
		Event.observe(this.container, "mousedown", this._mouseDownListener);
		Event.observe(this.container, "mouseup", this._mouseUpListener);
	},

	apply: function() {
		if(this.includeChildChildren) {
			this.children = [];
			this.elementTags.each(function(tag) {
				this.children.merge(this.container.getElementsByTagName(tag));
			}.bind(this))
		} else {
			this.children = $A(this.container.childNodes);
		}
		this.children.each(function(child) {
			if(this.elementTags.indexOf(child.nodeName) != -1 && child.getAttribute("inactive") != "true") {
				if(!Element.hasClassName(child, this.elementClass)) {
					Element.addClassName(child, this.elementClass);
				}
				if((child.disabled || child.getAttribute("disabled") == "true") && !Element.hasClassName(child, this.elementDisabledClass)) {
					Element.addClassName(child, this.elementDisabledClass);
					child.prevDisabled = true;
				}
				if(child.getAttribute("selected") == "true" && !Element.hasClassName(child, this.elementSelectedClass)) {
					Element.addClassName(child, this.elementSelectedClass);
				}
			}
		}.bind(this));
	},

	mouseOver: function(event) {
		var target = this._findTarget(Event.element(event));
		if(target && !target.disabled && target.getAttribute("disabled") != "true" && target.getAttribute("inactive") != "true") {
			Element.addClassName(target, this.elementHoverClass);
		}
	},

	mouseOut: function(event) {
		var target = this._findTarget(Event.element(event));
		if(target) {
			Element.removeClassName(target, this.elementHoverClass);
			Element.removeClassName(target, this.elementActiveClass);
		}
	},

	mouseDown: function(event) {
		var target = this._findTarget(Event.element(event));
		if(target && !target.disabled && target.getAttribute("disabled") != "true" && target.getAttribute("inactive") != "true") {
			Element.addClassName(target, this.elementActiveClass);
		}
	},

	mouseUp: function(event) {
		var target = this._findTarget(Event.element(event));
		if(target && !target.disabled && target.getAttribute("disabled") != "true" && target.getAttribute("inactive") != "true") {
			Element.removeClassName(target, this.elementActiveClass);
		}
	},

	_findTarget: function(target) {
		while(target && target.nodeName && this.elementTags.indexOf(target.nodeName) == -1) {
			target = target.parentNode;
			if(target == this.container) {
				return null;
			}
		}
		if(target && !this.includeChildChildren && target.parentNode != this.container) {
			return null;
		}
		return target;
	},

	getAt: function(index) {
		var counter = 0;
		return this.children.find(function(child) {
			if(this.elementTags.indexOf(child.nodeName) != -1 && child.getAttribute("inactive") != "true") {
				if(counter == index) {
					return true;
				}
				++counter;
			}
		}.bind(this));
	},

	get: function(element) {
		if(typeof(element) == "number") {
			return this.getAt(element);
		} else {
			return $(element);
		}
	},

	enable: function(element, enabled) {
		if(enabled === false) {
			this.disable(element);
			return;
		}
		element = this.get(element);
		Element.removeClassName(element, this.elementDisabledClass);
		element.disabled = false;
	},

	disable: function(element) {
		element = this.get(element);
		Element.addClassName(element, this.elementDisabledClass);
		Element.removeClassName(element, this.elementActiveClass);
		Element.removeClassName(element, this.elementHoverClass);
		element.prevDisabled = element.disabled;
		element.disabled = true;
	},

	enableAll: function() {
		this.children.each(function(child) {
			if(this.elementTags.indexOf(child.nodeName) != -1) {
				this.enable(child);
			}
		}.bind(this));
	},

	disableAll: function() {
		this.children.each(function(child) {
			if(this.elementTags.indexOf(child.nodeName) != -1) {
				this.disable(child);
			}
		}.bind(this));
	},

	restoreAll: function() {
		this.children.each(function(child) {
			if(this.elementTags.indexOf(child.nodeName) != -1 && !child.prevDisabled) {
				Element.removeClassName(child, this.elementDisabledClass);
				child.disabled = false;
			}
		}.bind(this));
	},

	select: function(element) {
		element = this.get(element);
		Element.addClassName(element, this.elementSelectedClass);
		element.setAttribute("selected", "true");
	},

	deselect: function(element) {
		element = this.get(element);
		Element.removeClassName(element, this.elementSelectedClass);
		element.setAttribute("selected", "false");
	},

	getSelected: function() {
		return this.children.select(function(child) {
			if(this.elementTags.indexOf(child.nodeName) != -1 && child.getAttribute("inactive") != "true" && child.getAttribute("selected") == "true") {
				return true;
			}
		}.bind(this));
	},

	deselectAll: function() {
		this.getSelected().each(function(element) {
			this.deselect(element);
		}.bind(this));
	},

	isSelected: function(element) {
		return this.get(element).getAttribute("selected") == "true";
	},

	destroy: function() {
		Event.stopObserving(this.container, "mouseover", this._mouseOverListener);
		Event.stopObserving(this.container, "mouseout", this._mouseOutListener);
		Event.stopObserving(this.container, "mousedown", this._mouseDownListener);
		Event.stopObserving(this.container, "mouseup", this._mouseUpListener);
	}
};/*
 * Toolbar 1.1
 * � 2005-2007 E-Technik Website Development (Pty) Ltd
 * Author:  Marc Heiligers (marc@e-technik.com)
 *
 * Requires prototype.js, core.js, activePanel.js
 */

Toolbar = Class.extend(ActivePanel, {
	initialize: function(container, attributes) {
		var attr = {
			elementTags: [ "BUTTON" ],
			elementClass: "toolbarNormal",
			elementHoverClass: "toolbarHover",
			elementtoolbarClass: "toolbarActive",
			elementSelectedClass: "toolbarSelected",
			elementDisabledClass: "toolbarDisabled",
			includeChildChildren: false
		};
		if(!attributes) {
			attributes = {};
		}
		Object.extend(attr, attributes);
		this.parent(container, attr);
	}
});PopupPanel = Class.create();
PopupPanel.prototype = {
	initialize: function(container, attributes) {
		this.container = $(container);
		var attr = {
			autoHide: true,
			autoHideTime: 200,
			//TODO: autoshow
			autoShow: false,
			maxHeight: -1,
			//TODO: add cursor as an ancher
			horizontalAnchor: "left", //farLeft, left, center, right, farRight, null
			verticalAnchor: "farBottom", //farTop, top, middle, bottom, farBottom, null
			element: null
		};
		if(!attributes) {
			attributes = {};
		}
		Object.extend(attr, attributes);
		Object.extend(this, attr);

		if(Browser.isIE) { // && !Browser.isIE7 - still covered by SVG
			this.iframe = document.body.appendChild(document.createElement("iframe"));
			Element.hide(this.iframe);
			this.iframe.style.position = "absolute";
			this.iframe.style.filter = "progid:DXImageTransform.Microsoft.Alpha(opacity=0)";
			//this.iframe.style.backgroundColor = "transparent";
			this.iframe.style.opacity = 0;
			this.iframe.style.zIndex = 1023;
		}

		Element.hide(this.container);
		this.container.style.position = "absolute";
		this.container.style.zIndex = 1024;
		document.body.appendChild(this.container);

		if(this.element) {
			this.element = $(this.element);
			if(this.autoHide) {
				this.__elementMouseOver = this.clearHide.bindAsEventListener(this);
				Event.observe(this.element, "mouseover", this.__elementMouseOver);
				this.__elementMouseOut = this.setHide.bindAsEventListener(this);
				Event.observe(this.element, "mouseout", this.__elementMouseOut);
			}
		}

		if(this.autoHide) {
			this.__mouseOver = this.clearHide.bindAsEventListener(this);
			Event.observe(this.container, "mouseover", this.__mouseOver);
			this.__mouseOut = this.setHide.bindAsEventListener(this);
			Event.observe(this.container, "mouseout", this.__mouseOut);
		}
	},

	show: function() {
		if(this.horizontalAnchor && this.verticalAnchor) {
			this.position();
		}
		if(this.iframe) {
			this._positionIFrame();
			Element.show(this.iframe);
		}
		Element.show(this.container);
	},

	hide: function() {
		Element.hide(this.container);
		if(this.iframe) {
			Element.hide(this.iframe);
		}
	},

	position: function() {
		if(!this.element) {
			return;
		}

		var eo = Element.getPositionOffset(this.element.offsetParent);
		var ed = Element.getDimensions(this.element);
		var ep = Positioning.scrolledOffset(this.element);
		var cd = Element.getDimensions(this.container);
		if(this.maxHeight != -1 && cd.height > this.maxHeight) {
			this.container.style.height = this.maxHeight + "px";
			//this.container.style.overflow = "hidden";
			cd = Element.getDimensions(this.container);
		}

		switch(this.horizontalAnchor) {
			case "farLeft": {
				var x = ep.x - cd.width;
				break;
			}
			case "left": {
				var x = ep.x;
				break;
			}
			case "center": {
				var x = ep.x + (ed.width - cd.width) / 2;
				break;
			}
			case "right": {
				var x = ep.x + ed.width - cd.width;
				break;
			}
			case "farRight": {
				var x = ep.x + ed.width;
				break;
			}
		}
		x += eo.w;

		switch(this.verticalAnchor) {
			case "farTop": {
				var y = ep.y - cd.height;
				break;
			}
			case "top": {
				var y = ep.y;
				break;
			}
			case "middle": {
				var y = ep.y + (ed.height - cd.height) / 2;
				break;
			}
			case "bottom": {
				var y = ep.y + ed.height - cd.height;
				break;
			}
			case "farBottom": {
				var y = ep.y + ed.height;
				break;
			}
		}
		y += eo.h;

		var p = window.getPageSize();
		//TODO: Calculate scroll offsets of page
		if(x + cd.width > p.windowWidth) {
			x -= x + cd.width - p.windowWidth;
		}
		if(x < 0) {
			x = 0;
		}
		if(y + cd.height > p.windowHeight) {
			y -= y + cd.height - p.windowHeight;
		}
		if(y < 0) {
			y = 0;
		}

		this.container.style.left = x + "px";
		this.container.style.top = y + "px";
	},

	_positionIFrame: function() {
		if(this.iframe) {
			var d = Element.getDimensions(this.container);
			var sp = Element.getSizeOffset(this.container);
			this.iframe.style.top = Element.getStyle(this.container, "top");
			this.iframe.style.left = Element.getStyle(this.container, "left");
			this.iframe.style.width = (d.width) + "px";
			this.iframe.style.height = (d.height) + "px";
		}
	},

	setHide: function() {
		this.clearHide();
		this.__hideTimeout = setTimeout(this.hide.bind(this), this.autoHideTime);
	},

	clearHide: function() {
		if(this.__hideTimeout) {
			clearTimeout(this.__hideTimeout);
		}
		this.__hideTimeout = null;
	},

	destroy: function() {
		if(this.autoHide) {
			if(this.element) {
				Event.stopObserving(this.element, "mouseover", this.__elementMouseOver);
				Event.stopObserving(this.element, "mouseout", this.__elementMouseOut);
			}
			Event.stopObserving(this.container, "mouseover", this.__mouseOver);
			Event.stopObserving(this.container, "mouseout", this.__mouseOut);
		}
		if(this.iframe) {
			document.body.removeChild(this.iframe);
		}
		this.clearHide();
	}
};
var ScrollPanel = Class.create();
ScrollPanel.prototype = {
	initialize: function(container, attributes) {
		this.container = $(container);
		Class.setAttributes(this, attributes, {
			onScroll: null,
			scrollerClass: "scroller",
			scrollerTopClass: "scrollerTop",
			scrollerLeftClass: "scrollerLeft",
			scrollerBottomClass: "scrollerBottom",
			scrollerRightClass: "scrollerRight",
			scrollerHoverClass: "scrollerHover",
			scrollerActiveClass: "scrollerActive",
			scrollerPosition: "inside",
			scrollerStepSize: 20,
			scrollInitialTime: 100,
			scrollAcceleration: 0.9,
			scrollHorizontal: true,
			scrollVertical: true
		});

		this.container.style.overflow = "hidden";

		this._scrollerMouseOverListener = this.scrollerMouseOver.bind(this);
		this._scrollerMouseOutListener = this.scrollerMouseOut.bind(this);
		this._scrollerMouseDownListener = this.scrollerMouseDown.bindAsEventListener(this);
		this._scrollerMouseUpListener = this.scrollerMouseUp.bindAsEventListener(this);

		this.scrollers = [];
		if(this.scrollHorizontal) {
			this.scrollerLeft = this.scrollers.add(this.__createScroller([ this.scrollerClass, this.scrollerLeftClass ], new Point(-1, 0)));
			this.scrollerRight = this.scrollers.add(this.__createScroller([ this.scrollerClass, this.scrollerRightClass ], new Point(1, 0)));
		}
		if(this.scrollVertical) {
			this.scrollerTop = this.scrollers.add(this.__createScroller([ this.scrollerClass, this.scrollerTopClass ], new Point(0, -1)));
			this.scrollerBottom = this.scrollers.add(this.__createScroller([ this.scrollerClass, this.scrollerBottomClass ], new Point(0, 1)));
		}

		this._mouseOverListener = this.mouseOver.bindAsEventListener(this);
		this._mouseOutListener = this.mouseOut.bindAsEventListener(this);

		Event.observe(this.container, "mouseover", this._mouseOverListener);
		Event.observe(this.container, "mouseout", this._mouseOutListener);
	},

	mouseOver: function(event) {
		this.clearHide();
		var p = Positioning.scrolledOffset(this.container);
		var sp = Element.getPositionOffset(this.container.parentNode);
		p.x += sp.w;
		p.y += sp.h;

		if(this.scrollHorizontal) {
			if(this.scrollerPosition == "inside") {
				this.scrollerLeft.style.left = p.x + "px";
			} else {
				var s = Element.getDimensions(this.scrollerLeft);
				this.scrollerLeft.style.left = (p.x - s.width) + "px";
			}
			this.scrollerLeft.style.top = p.y + "px";
			this.scrollerLeft.style.height = (this.container.offsetHeight - Element.getSizePadding(this.scrollerLeft).h) + "px";

			if(this.scrollerPosition == "inside") {
				var s = Element.getDimensions(this.scrollerRight);
				this.scrollerRight.style.left = (this.container.offsetWidth + p.x - s.width) + "px";
			} else {
				this.scrollerRight.style.left = (this.container.offsetWidth + p.x) + "px";
			}
			this.scrollerRight.style.top = p.y + "px";
			this.scrollerRight.style.height = (this.container.offsetHeight - Element.getSizePadding(this.scrollerRight).h) + "px";
		}

		if(this.scrollVertical) {
			if(this.scrollerPosition == "inside") {
				this.scrollerTop.style.top = p.y + "px";
			} else {
				var s = Element.getDimensions(this.scrollerTop);
				this.scrollerTop.style.top = (p.y - s.height) + "px";
			}
			this.scrollerTop.style.left = p.x + "px";
			this.scrollerTop.style.width = (this.container.offsetWidth - Element.getSizePadding(this.scrollerTop).w) + "px";

			if(this.scrollerPosition == "inside") {
				var s = Element.getDimensions(this.scrollerBottom);
				this.scrollerBottom.style.top = (this.container.offsetHeight + p.y - s.height) + "px";
			} else {
				this.scrollerBottom.style.top = (this.container.offsetHeight + p.y) + "px";
			}
			this.scrollerBottom.style.left = p.x + "px";
			this.scrollerBottom.style.width = (this.container.offsetWidth - Element.getSizePadding(this.scrollerBottom).w) + "px";
		}

		this.displayScrollers();
	},

	displayScrollers: function() {
		if(this.scrollHorizontal) {
			this.scrollerLeft.style.display = this.container.scrollLeft > 0 ? "" : "none";
			this.scrollerRight.style.display = this.container.scrollLeft < this.container.scrollWidth - this.container.offsetWidth ? "" : "none";
		}
		if(this.scrollVertical) {
			this.scrollerTop.style.display = this.container.scrollTop > 0 ? "" : "none";
			this.scrollerBottom.style.display = this.container.scrollTop < this.container.scrollHeight - this.container.offsetHeight ? "" : "none";
		}
		if(this.currentScroller && this.currentScroller.style.display == "none") {
			if(this.scrollerPosition != "inside") {
				this.setHide();
			}
			if(this.scrollerActiveClass) {
				Element.removeClassName(this.currentScroller, this.scrollerActiveClass);
			}
			if(this.scrollerHoverClass) {
				Element.removeClassName(this.currentScroller, this.scrollerHoverClass);
			}
			clearTimeout(this.scrollTimeout);
		}
	},

	mouseOut: function(event) {
		this.clearHide();
		if(this.scrollerOver) {
			return;
		}
		this.setHide();
	},

	setHide: function() {
		this.__hideTimeout = setTimeout(this.hideScrollers.bind(this), 200);
	},

	clearHide: function() {
		if(this.__hideTimeout) {
			clearTimeout(this.__hideTimeout);
			this.__hideTimeout = null;
		}
	},

	hideScrollers: function() {
		this.clearHide();
		this.scrollers.each(function(scroller) {
			Element.hide(scroller);
			if(this.scrollerActiveClass) {
				Element.removeClassName(scroller, this.scrollerActiveClass);
			}
			if(this.scrollerHoverClass) {
				Element.removeClassName(scroller, this.scrollerHoverClass);
			}
		}.bind(this));
		this.currentScroller = null;
	},

	__createScroller: function(classList, direction) {
		var scroller = document.createElement("div");
		scroller.style.position = "absolute";
		scroller.style.display = "none";
		scroller.style.zIndex = 2048;
		scroller.direction = direction;
		document.body.appendChild(scroller);
		classList.each(function(className) {
			if(className) {
				Element.addClassName(scroller, className);
			}
		}.bind(this));

		Event.observe(scroller, "mouseover", this._scrollerMouseOverListener);
		Event.observe(scroller, "mouseout", this._scrollerMouseOutListener);
		Event.observe(scroller, "mousedown", this._scrollerMouseDownListener);
		Event.observe(scroller, "mouseup", this._scrollerMouseUpListener);
		return scroller;
	},

	scrollerMouseOver: function(event) {
		var target = Event.element(event);
		this.scrollers.each(function(scroller) {
			if(target == scroller) {
				this.currentScroller = scroller;
				if(this.scrollerHoverClass) {
					Element.addClassName(this.currentScroller, this.scrollerHoverClass);
				}
			}
		}.bind(this));
		this.scrollerOver = true;
		this.clearHide();
	},

	scrollerMouseOut: function(event) {
		if(this.scrollerHoverClass) {
			Element.removeClassName(this.currentScroller, this.scrollerHoverClass);
		}
		this.scrollerOver = false;
		this.setHide();
	},

	scrollerMouseDown: function(event) {
		if(this.scrollerActiveClass) {
			Element.addClassName(this.currentScroller, this.scrollerActiveClass);
		}
		this.currentScrollTime = this.scrollInitialTime;
		this.scroll();
	},

	scrollerMouseUp: function(event) {
		if(this.scrollerActiveClass) {
			Element.removeClassName(this.currentScroller, this.scrollerActiveClass);
		}
		clearTimeout(this.scrollTimeout);
	},

	scroll: function() {
		clearTimeout(this.scrollTimeout);
		var x = this.container.scrollLeft + this.currentScroller.direction.x * this.scrollerStepSize;
		var y = this.container.scrollTop + this.currentScroller.direction.y * this.scrollerStepSize;
		if(x < 0) { x = 0; }
		if(x > this.container.scrollWidth - this.container.offsetWidth) { x = this.container.scrollWidth - this.container.offsetWidth; }
		if(y < 0) { y = 0; }
		if(y > this.container.scrollHeight - this.container.offsetHeight) { y = this.container.scrollHeight - this.container.offsetHeight; }
		this.container.scrollLeft = x;
		this.container.scrollTop = y;
		this.currentScrollTime = Math.ceil(this.currentScrollTime * this.scrollAcceleration);
		this.scrollTimeout = setTimeout(this.scroll.bind(this), this.currentScrollTime);
		this.displayScrollers();
		if(typeof(this.onScroll) == "function") {
			this.onScroll();
		}
	},

	scrollTo: function(child) {
		this.container.scrollLeft = child.offsetLeft - 2;
		this.container.scrollTop = child.offsetTop - 2;
	},

	destroy: function() {
		Event.stopObserving(this.container, "mouseover", this._mouseOverListener);
		Event.stopObserving(this.container, "mouseout", this._mouseOutListener);

		this.scrollers.each(function(scroller) {
			Event.stopObserving(scroller, "mouseover", this._scrollerMouseOverListener);
			Event.stopObserving(scroller, "mouseout", this._scrollerMouseOutListener);
			Event.stopObserving(scroller, "mousedown", this._scrollerMouseDownListener);
			Event.stopObserving(scroller, "mouseup", this._scrollerMouseUpListener);

			document.body.removeChild(scroller);
		}.bind(this));
	}
};
var Menu = Class.create();
Menu.prototype = {
	initialize: function(element, attributes) {
		this.element = $(element);
		this.attributes = {
			makeElementActive: true,
			/* ActiveElement attributes */
			elementClass: "activeNormal",
			elementHoverClass: "activeHover",
			elementActiveClass: "activeActive",
			elementSelectedClass: "activeSelected",
			elementDisabledClass: "activeDisabled",
			/* PopupPanel attributes */
			menuElement: null,
			menuClass: "menu",
			menuOverflow: "hidden",
			autoHide: true,
			autoHideTime: 200,
			autoShow: false,
			horizontalAnchor: "left", //farLeft, left, center, right, farRight, null
			verticalAnchor: "farBottom", //farTop, top, middle, bottom, farBottom, null
			maxHeight: -1,
			/* ActivePanel attributes */
			makeMenuActive: true,
			menuElementTags: [ "DIV" ],
			menuElementClass: "menuNormal",
			menuElementHoverClass: "menuHover",
			menuElementActiveClass: "menuActive",
			menuElementSelectedClass: "menuSelected",
			menuElementDisabledClass: "menuDisabled",
			menuIncludeChildChildren: false,
			/* ScrollPanel attributes */
			makeMenuScroll: false,
			scrollerClass: "scroller",
			scrollerTopClass: "scrollerTop",
			scrollerLeftClass: "scrollerLeft",
			scrollerBottomClass: "scrollerBottom",
			scrollerRightClass: "scrollerRight",
			scrollerHoverClass: "scrollerHover",
			scrollerActiveClass: "scrollerActive",
			scrollerPosition: "inside",
			scrollerStepSize: 20,
			scrollInitialTime: 100,
			scrollAcceleration: 0.9,
			scrollHorizontal: false,
			scrollVertical: true
		};
		Class.setAttributes(this, attributes, this.attributes);

		if(!this.menuElement) {
			this.menuElement = document.createElement("div");
			document.body.appendChild(this.menuElement);
		} else {
			this.menuElement = $(this.menuElement);
		}
		if(!Element.hasClassName(this.menuElement, this.menuClass)) {
			Element.addClassName(this.menuElement, this.menuClass);
		}

		if(this.makeElementActive) {
			this._activeElement = new ActiveElement(this.element, this.attributes);
		}

		if(this.makeMenuActive) {
			this._activePanel = new ActivePanel(this.menuElement, {
				elementTags: this.menuElementTags,
				elementClass: this.menuElementClass,
				elementHoverClass: this.menuElementHoverClass,
				elementActiveClass: this.menuElementActiveClass,
				elementSelectedClass: this.menuElementSelectedClass,
				elementDisabledClass: this.menuElementDisabledClass,
				includeChildChildren: this.menuIncludeChildChildren
			});
		}

		if(this.makeMenuScroll) {
			this._scrollPanel = new ScrollPanel(this.menuElement, this.attributes);
		}

		this._popupPanel = new PopupPanel(this.menuElement, {
			element: this.element,
			autoHide: this.autoHide,
			autoHideTime: this.autoHideTime,
			autoShow: this.autoShow,
			horizontalAnchor: this.horizontalAnchor,
			verticalAnchor: this.verticalAnchor,
			maxHeight: this.maxHeight
		});

		this._clickHandler = this.elementClick.bindAsEventListener(this);
		Event.observe(this.element, "click", this._clickHandler);

		this._clickCache = [];
	},

	beginUpdate: function() {
		this._updating = true;
	},

	endUpdate: function() {
		this._updating = false;
		if(this.makeMenuActive) {
			this._activePanel.apply();
		}
	},

	addItem: function(id, text, onClick, bindTo, isInactive) {
		var div = document.createElement("DIV");
		div.id = id;
		Element.update(div, text);
		if(isInactive) {
			div.setAttribute("inactive", "true");
		}
		if(typeof onClick == "function") {
			if(!bindTo) bindTo = div;
			var clickHandler = onClick.bindAsEventListener(bindTo);
			Event.observe(div, "click", clickHandler);
			this._clickCache.add({ d: div, c: clickHandler });
		}
		this.menuElement.appendChild(div);
		if(this.makeMenuActive && !this._updating) {
			this._activePanel.apply();
		}
		return div;
	},

	enableItem: function(item, enabled) {
		this._activePanel.enable(item, enabled);
	},

	disableItem: function(item) {
		this._activePanel.disable(item);
	},

	selectItem: function(item) {
		this._activePanel.select(item);
	},

	deselectItem: function(item) {
		this._activePanel.deselect(item);
	},

	deselectAllItems: function() {
		this._activePanel.deselectAll();
	},

	getSelectedItems: function() {
		return this._activePanel.getSelected();
	},

	show: function() {
		this.menuElement.style.overflowY = this.menuOverflow;
		this.menuElement.style.overflowX = "hidden";
		this._popupPanel.show();
	},

	hide: function() {
		this._popupPanel.hide();
	},

	elementClick: function(event) {
		this._popupPanel.show();
	},

	destroy: function() {
		if(this.makeElementActive) {
			this._activeElement.destroy();
		}

		if(this.makeMenuActive) {
			this._activePanel.destroy();
		}

		if(this.makeMenuScroll) {
			this._scrollPanel.destroy();
		}

		this._popupPanel.destroy();

		this._clickCache.each(function(c) {
			try {
				Event.stopObserving(c.d, c.c);
			} catch(ex) {}
		});
	}
};
/*
 * Accordian 1.1
 * � 2005-2007 E-Technik Website Development (Pty) Ltd
 * Author: Marc Heiligers (marc@e-technik.com)
 *
 * Requires: ActivePanel.js, prototype.js
 */

Accordian = Class.create();
Accordian.prototype = {
	initialize: function(container, attributes) {
		this.container = $(container);
		var attr = {
			panelClass: "accordianPanel",
			onPanelChanged: null,
			/* Active Panel */
			elementTags: [ "BUTTON" ],
			elementClass: "accordianButton",
			elementHoverClass: "accordianButtonHover",
			elementActiveClass: "accordianButtonActive",
			elementSelectedClass: "accordianButtonSelected",
			includeChildChildren: false
		};
		if(!attributes) {
			attributes = {};
		}
		Object.extend(attr, attributes);
		Object.extend(this, attr);

		this._buttonClickListener = this._buttonClick.bindAsEventListener(this);

		var panels = this._getPanels();
		panels.each(function(panel) {
			var button = document.createElement("button");
			button.hideFocus = true;
			button.panelId = panel.id;
			button.panel = panel;
			button.setAttribute("dock", "top");
			panel.button = button;
			panel.accordian = this;
			panel.style.overflow = "auto";
			panel.style.display = "none";
			panel.setAttribute("inactive", "true");
			panel.setAttribute("dock", "fill");
			var label = (panel.name ? panel.name : panel.id);
			new Insertion.Top(button, label);
			this.container.appendChild(button);
			Event.observe(button, "click", this._buttonClickListener);
			Element.addClassName(panel, this.panelClass);
		}.bind(this));
		panels.each(function(panel) {
			this.container.appendChild(panel);
		}.bind(this));

		this.activePanel = new ActivePanel(this.container, attr);
		this.layoutManager = new DockManager( { container: this.container } );

		this.show(panels[0].id);
	},

	destroy: function() {
		var panels = this._getPanels();
		panels.each(function(panel) {
			Event.observe(panel.button, "click", this._buttonClickListener);
		}.bind(this));

		this.activePanel.destroy();
		this.layoutManager.destroy();
	},

	show: function(id) {
		var panel = this.getPanel(id);
		if(panel) {
			if (this.currentPanel) {
				this.currentPanel.style.display = "none";
			}
			this.activePanel.deselectAll();
			panel.style.display = "";
			this.activePanel.select(panel.button);
			this.currentPanel = panel;
			var buttons = $A(this.container.getElementsByTagName("button"));
			var panelFound = false;
			buttons.each(function(button) {
				if(button.parentNode == this.container) {
					button.setAttribute("dock", (panelFound ? "bottom" : "top"));
					if(button.panel == panel) {
						panelFound = true;
					}
				}
			}.bind(this));
			this.layoutManager.performLayout();
			if(this.onPanelChanged) {
				this.onPanelChanged(panel);
			}
		}
	},

	getPanel: function(id) {
		var buttons = $A(this.container.getElementsByTagName("button"));
		return buttons.detect(function(button) {
			if (button.panelId == id) {
				return true;
			}
		}).panel;
	},

	getPanels: function() {
		var buttons = $A(this.container.getElementsByTagName("button"));
		var panels = new Array();
		buttons.each(function(button) {
			if (button.panelId) {
				panels.add(button.panel);
			}
		});
		return panels;
	},

	sizePanels: function() {
		var panels = this.getPanels();
		var buttonHeight = 0;
		panels.each(function(panel) {
			buttonHeight += panel.button.offsetHeight;
		}.bind(this));
		panels.each(function(panel) {
			panel.style.height = (this.container.clientHeight - buttonHeight) + "px";
		}.bind(this));
	},

	_buttonClick: function(event) {
		this.show(Event.element(event).panelId);
	},

	_getPanels: function() {
		var divs = $A(this.container.getElementsByTagName("div"));
		var panels = new Array();
		var container = this.container;
		divs.each(function(div) {
			if (div.parentNode == container) {
				panels.add(div);
			}
		});
		return panels;
	}
};DockManager = Class.create();
DockManager.prototype = {
	initialize: function() {
		Object.extend(this, {
			container: document.body,
			marginTop: 0,
			marginLeft: 0,
			marginBottom: 0,
			marginRight: 0,
			padding: 0,
			overflow: "hidden",
			timeout: 100,
			onLayoutPerformed: null,
			parent: null,
			useIE6BoxModel: true
		});
		if(arguments[0]) {
			Object.extend(this, arguments[0]);
		}
		this.container = $(this.container);
		this.container.layoutManager = this;

		this._managers = [];

		this.apply();

		this._oldOverflow = this.container.style.overflow;
		this.container.style.overflow = this.overflow;

		if(this.useIE6BoxModel) {
			this._applyIE6BoxModelToAll();
		}


		this._resizeTimeout = null;
		this._resizeIE7Hack = false;

		if(this.container.parentNode.layoutManager) {
			this.setParent(this.container.parentNode.layoutManager);
		} else {
			this._setParentEvents();
		}
	},

	addManager: function(child) {
		child.setParent(this);
		this._managers.add(child);
		this._resize();
		return child;
	},

	removeManager: function(child) {
		for(var i = 0; i < this._managers.length; ++i) {
			if(this._managers[i] == child) {
				this._managers[i] = null;
			}
		}
		this._managers = this._managers.compact();
	},

	setParent: function(parent) {
		if(this.parent == parent) {
			return;
		}

		if(this._resizeTimeout) {
			clearTimeout(this._resizeTimeout);
		}

		if(this.parent && parent != this.parent) {
			this.parent.removeManager(this);
		}

		if(this.parent) {
			if(this._resizeObserver) {
				Event.stopObserving(window, "resize", this._resizeObserver);
				this._resizeObserver = null;
			}
		}

		this.parent = parent;

		this._setParentEvents();
	},

	_setParentEvents: function() {
		if(!this.parent) {
			this._resizeObserver = this._resize.bindAsEventListener(this);
			Event.observe(window, "resize", this._resizeObserver);
			this._resize();
		} else {
			this.parent.addManager(this);
		}
	},

	destroy: function() {
		if(this._resizeObserver) {
			Event.stopObserving(window, "resize", this._resizeObserver);
			this._resizeObserver = null;
		}
		if(this.parent) {
			this.parent.removeManager(this);
			this.parent = null;
		}
		this._managers.each(function(manager) {
			manager.setParent(null);
		}.bind(this));
		this.container.style.overflow = this._oldOverflow;
		this.container.layoutManager = null;
	},

	performLayout: function(performChild) {
		this._resizeTimeout = null;
		if(this.container == document.body) {
			var t = window.getPageSize();
			var p = { width: t.windowWidth, height: t.windowHeight };
			var sp = { w: 0, h: 0 }
		} else if(this.container.parentNode == null) {
			return;
		} else {
			var p = Element.getDimensions(this.container);
			var sp = Element.getSizeOffset(this.container);
		}
		var r = new Rect(this.padding + this.marginLeft, this.padding + this.marginTop, p.width - this.padding * 2 - this.marginRight - this.marginLeft - sp.w, p.height - this.padding * 2 - this.marginBottom - this.marginTop - sp.h);
		this.dockables.each(function(dockable) {
			if(Positioning.isDisplayed(dockable)) {
				var d = Element.getDimensions(dockable);
				var sp = Element.getSizePadding(dockable);
				if(this.debug) { alert(dockable.tagName + " id=" + dockable.id + " r=" + r.toString() + " d=" + d.width + " " + d.height + " sp=" + sp.w + " " + sp.h); }
				switch(dockable.getAttribute("dock")) {
					case "top": {
						dockable.style.top = r.y + "px";
						dockable.style.left = r.x + "px";
						dockable.style.width = ((r.w - sp.w) > 0 ? (r.w - sp.w) : 0) + "px";
						r.enlarge(-d.height - this.padding, 0);
						break;
					}
					case "left": {
						dockable.style.top = r.y + "px";
						dockable.style.left = r.x + "px";
						dockable.style.height = ((r.h - sp.h) > 0 ? (r.h - sp.h) : 0) + "px";
						r.enlarge(0, -d.width - this.padding);
						if(this.isIE7) {
							r.enlarge(0, -this.marginLeft - this.marginRight);
						}
						break;
					}
					case "bottom": {
						dockable.style.top = (r.getBottom() - d.height) + "px";
						dockable.style.left = r.x + "px";
						dockable.style.width = ((r.w - sp.w) > 0 ? (r.w - sp.w) : 0) + "px";
						r.enlarge(0, 0, 0, -d.height - this.padding);
						break;
					}
					case "right": {
						dockable.style.top = r.y + "px";
						dockable.style.left = (r.getRight() - d.width) + "px";
						dockable.style.height = ((r.h - sp.h) > 0 ? (r.h - sp.h) : 0) + "px";
						r.enlarge(0, 0, -d.width - this.padding, 0);
						break;
					}
					case "fill": {
						dockable.style.top = r.y + "px";
						dockable.style.left = r.x + "px";
						dockable.style.width = ((r.w - sp.w) > 0 ? (r.w - sp.w) : 0) + "px";
						dockable.style.height = ((r.h - sp.h) > 0 ? (r.h - sp.h) : 0) + "px";
						r.enlarge(-r.h, -r.w);
						break;
					}
					default: {
						break;
					}
				}
				if(r.w < 0) { r.w = 0; }
				if(r.h < 0) { r.h = 0; }
			}
		}.bind(this));

		this._managers.each(function(manager) {
			try {
				manager.performLayout(true);
			} catch(ex) {}
		});

		if(typeof(this.onLayoutPerformed) == "function") {
			this.onLayoutPerformed();
		}

		//HACK: IE7 sometimes seems to need to have another go at this.
		//The first resize leaves a 20px or so blank space at the bottom (sometimes)
		if(Browser.isIE7 && !performChild) {
			if(this._resizeIE7Hack) {
				this._resizeIE7Hack = false;
			} else {
				this._resizeIE7Hack = true;
				this._resize();
			}
		}
	},

	apply: function() {
		this.dockables = [];
		var childNodes = $A(this.container.childNodes);
		childNodes.each(function(childNode) {
			if(childNode.getAttribute && childNode.getAttribute("dock")) {
				this.dockables.add(childNode);
				childNode.style.position = "absolute";
				if(childNode.layoutManager) {
					this.addManager(childNode.layoutManager);
				}
			}
		}.bind(this));
	},

	//Applies a pause before performing the layout.
	//Improves performance and also allows the user to delay "inner" dock managers to run after "outer" dock managers
	_resize: function() {
		if(this._resizeTimeout) {
			clearTimeout(this._resizeTimeout);
		}
		this._resizeTimeout = setTimeout(this.performLayout.bind(this), this.timeout);
	},

	//Applies the IE6 box model so that you don't have to apply it in CSS
	//Essentially, style.width == offsetWidth
	_applyIE6BoxModelToAll: function() {
		this.dockables.each(function(d) {
			this._applyIE6BoxModel(d);
		}.bind(this));
	},

	_applyIE6BoxModel: function(e) {
		if(document.defaultView || this.isIE7) {
			var d = Element.getDimensions(e);
			var sp = Element.getSizePadding(e);
			e.style.width = ((d.width - sp.w * 2) > 0 ? (d.width - sp.w * 2) : 0) + "px";
			e.style.height = ((d.height - sp.h * 2) > 0 ? (d.height - sp.h * 2) : 0) + "px";
		}
	}
};

var TabView = Class.create();
TabView.prototype = {
	initialize: function(container, attributes) {
		this.container = $(container);
		var attr = {
			headerClass: "tabHeader",
			panelClass: "tabPanel",
			headerDock: "top",
			defaultTab: 0,
			onTabChanged: null,
			/* ActivePanel */
			elementTags: [ "BUTTON" ],
			elementClass: "tabNormal",
			elementHoverClass: "tabHover",
			elementActiveClass: "tabActive",
			elementDisabledClass: "tabDisabled",
			elementSelectedClass: "tabSelected",
			/* ScrollPanel */
			onScroll: null,
			scrollerClass: "tabScroller",
			scrollerTopClass: "tabScrollerTop",
			scrollerLeftClass: "tabScrollerLeft",
			scrollerBottomClass: "tabScrollerBottom",
			scrollerRightClass: "tabScrollerRight",
			scrollerHoverClass: "tabScrollerHover",
			scrollerActiveClass: "tabScrollerActive",
			scrollerPosition: "inside",
			scrollerStepSize: 20,
			scrollInitialTime: 200,
			scrollAcceleration: 0.9,
			scrollHorizontal: true,
			scrollVertical: false
		};
		if(!attributes) {
			attributes = {};
		}
		Object.extend(attr, attributes);
		Object.extend(this, attr);

		this.header = document.createElement("div");
		this.header.setAttribute("dock", this.headerDock);
		Element.addClassName(this.header, this.headerClass);

		this.panels = [];
		var divs = $A(this.container.getElementsByTagName("DIV"));
		divs.each(function(panel) {
			if(panel.parentNode == this.container) {
				Element.addClassName(panel, this.panelClass);
				panel.tab = this.header.appendChild(document.createElement("button"));
				Element.update(panel.tab, panel.title ? panel.title : panel.id);
				panel.tab.setAttribute("title", panel.title);
				panel.setAttribute("title", "");
				panel.setAttribute("dock", "fill");
				panel.tab.panel = panel;
				Event.observe(panel.tab, "click", function() { this.showTab(panel) }.bind(this));
				Element.hide(panel);
				this.panels.add(panel);
			}
		}.bind(this));

		this.container.insertBefore(this.header, this.container.firstChild);
		this.activePanel = new ActivePanel(this.header, attr);
		this.scrollPanel = new ScrollPanel(this.header, attr);
		Element.setTextSelectable(this.header, false);

		this.showTab(this.defaultTab);
		this.layoutManager = new DockManager( { container: this.container } );
	},

	get: function(tab) {
		if(typeof(tab) == "number") {
			return this.panels[tab].tab;
		} else if(typeof(tab) == "string") {
			var panel = this.panels.find(function(panel) {
				if(panel.id == tab) {
					return true;
				}
			}.bind(this));
			if(panel) {
				return panel.tab;
			}
			return null;
		} else {
			return $(tab).tab;
		}
	},

	showTab: function(tab) {
		if(this.currentTab) {
			Element.hide(this.currentTab.panel);
			this.activePanel.deselect(this.currentTab);
			this.currentTab = null;
			this.currentPanel = null;
		}
		tab = this.get(tab);
		if(tab) {
			tab.blur();
			Element.show(tab.panel);
			this.currentTab = tab;
			this.currentPanel = tab.panel;
			this.activePanel.select(this.currentTab);
			if(this.layoutManager) {
				this.layoutManager.performLayout();
			}
			if(typeof this.onTabChanged == "function") {
				this.onTabChanged(tab);
			}
		}
	},

	enable: function(tab) {
		this.activePanel.enable(this.get(tab));
	},

	disable: function(tab) {
		this.activePanel.disable(this.get(tab));
	},

	enableAll: function() {
		this.activePanel.enableAll();
	},

	disableAll: function() {
		this.activePanel.disableAll();
	},

	hide: function(tab) {
		Element.hide(this.get(tab));
	},

	unhide: function(tab) {
		Element.show(this.get(tab));
	},

	destroy: function() {
		this.layoutManager.destroy();
		this.activePanel.destroy();
		this.scrollPanel.destroy();
	}
};/*
 * TreeView 1.1
 * � 2005-2007 E-Technik Website Development (Pty) Ltd
 * Author: Marc Heiligers (marc@e-technik.com)
 *
 * Requires: prototype.js, core.js
 */

TreeView = Class.create();
TreeView.prototype = {
	initialize: function(container, attributes) {
		this.container = $(container);
		Class.setAttributes(this, attributes, {
			nodeClass: "treeNode",
			nodeIconClass: "treeNodeIcon",
			nodeTextClass: "treeNodeText",
			nodeTextSelectedClass: "treeNodeTextSelected",
			nodeCollapsedIcon: "css/images/tree/collapsed.png",
			nodeExpandedIcon: "css/images/tree/expanded.png",
			nodeLeafIcon: "css/images/tree/leaf.png",
			nodeIconAlign: "absbottom",
			showRoot: true,
			onNodeSelected: null,
			onNodeDeselected: null,
			onNodeExpanded: null,
			onNodeCollapsed: null,
			onNodeDoubleClick: null
		});

		this.container.style.overflow = "auto";
		Element.setTextSelectable(this.container, false);
		this.list = Element.findChild(this.container, "UL");

		var nodes = $A(this.list.getElementsByTagName("LI"));
		nodes.each(this._applyNode.bind(this));
		this.root = nodes[0];
		this.root.tree = this;
		if(Browser.isMoz) {
			this.root.style.marginTop = "-10px";
		}
		if (this.showRoot) {
			this.root.style.marginLeft = "-40px";
		} else {
			var m = parseInt(Element.getStyle(this.root, "margin-left"));
			if(isNaN(m)) {
				m = 0;
			}
			this.root.style.marginLeft = (-80 - m) + "px";
			this.root.style.position = "relative";
			this.root.style.top = "-" + (this.root.childNodes[1].offsetHeight) + "px";
			this.root.childNodes[0].style.visibility = "hidden";
			this.root.childNodes[1].style.visibility = "hidden";
			this.expand(this.root);
		}
		this.selectedNode = null;

		this._clickListener = this._click.bindAsEventListener(this);
		this._dblclickListener = this._dblclick.bindAsEventListener(this);

		Event.observe(this.container, "click", this._clickListener);
		Event.observe(this.container, "dblclick", this._dblclickListener);
	},

	select: function(node) {
		if(node.getAttribute("unselectable") == "true") {
			return;
		}
		this.deselect(this.selectedNode);
		this.selectedNode = node;
		Element.addClassName(this.selectedNode.childNodes[1], this.nodeTextSelectedClass);
		if(typeof(this.onNodeSelected) == "function") {
			this.onNodeSelected(node, this);
		}
	},

	deselect: function(node) {
		if(!node) return;
		Element.removeClassName(node.childNodes[1], this.nodeTextSelectedClass);
		if(node == this.selectedNode) this.selectedNode = null;
		if(typeof(this.onNodeDeselected) == "function") {
			this.onNodeDeselected(node, this);
		}
	},

	expand: function(node) {
		var ul = Element.findChild(node, "UL");
		if(ul) {
			ul.style.display = "";
			node.setAttribute("expanded", "true");
			this.setIcon(node, this.nodeExpandedIcon);
			if(typeof(this.onNodeExpanded) == "function") {
				this.onNodeExpanded(node, this);
			}
		}
	},

	expandAll: function() {
		this.expand(this.root);
		$A(this.root.getElementsByTagName("LI")).each(function(node) {
			this.expand(node);
		}.bind(this))
	},

	collapse: function(node) {
		var ul = Element.findChild(node, "UL");
		if(ul) {
			ul.style.display = "none";
			node.setAttribute("expanded", "false");
			this.setIcon(node, this.nodeCollapsedIcon);
			if(typeof(this.onNodeCollapsed) == "function") {
				this.onNodeCollapsed(node, this);
			}
		}
	},

	collapseAll: function() {
		this.collapse(this.root);
		$A(this.root.getElementsByTagName("LI")).each(function(node) {
			this.collapse(node);
		}.bind(this))
	},

	expandUp: function(node) {
		var current = node;
		while(current = this.getParentNode(current)) {
			this.expand(current);
		}
	},

	setIcon: function(node, url, w, h) {
		node.childNodes[0].src = url;
		if(w) {
			node.childNodes[0].width = w;
		}
		if(h) {
			node.childNodes[0].height = h;
		}
	},

	setCaption: function(node, caption) {
		Element.update(node.childNodes[1], caption);
		node.title = Element.getInnerText(node.childNodes[1]);
	},

	addNode: function(parent, node) {
		var children = parent.getElementsByTagName("UL");
		var childList = null;
		if (children.length > 0) {
			childList = children[0];
		} else {
			childList = parent.appendChild(document.createElement("UL"));
		}
		childList.appendChild(node);
		this._applyNode(node);
		if(parent.getAttribute("expanded") == "true") {
			this.expand(parent);
		} else {
			this.collapse(parent);
		}
		return node;
	},

	addHTMLNode: function(parent, html) {
		var node = Element.fromHTML(html, "LI");
		return this.addNode(parent, node);
	},

	removeNode: function(node) {
		node.parentNode.removeChild(node);
	},

	clear: function() {
		var nodes = this.getChildNodes(this.root);
		for(var i = 0; i < nodes.length; ++i) {
			this.removeNode(nodes[i]);
		}
		this.deselect(this.root);
		this.selectedNode = null;
	},

	getChildNodes: function(parent) {
		var children = parent.getElementsByTagName("UL");
		var childNodes = [];
		if(children.length > 0) {
			var childList = children[0];
			for(var i = 0; i < childList.childNodes.length; ++i) {
				if (childList.childNodes[i].tagName == "LI") {
					childNodes.add(childList.childNodes[i]);
				}
			}
		}
		return childNodes;
	},

	getParentNode: function(node) {
		if(!node || node == this.root) {
			return null;
		} else {
			var nodeParent = node.parentNode;
			if(nodeParent) {
				return Element.findParent(nodeParent, "LI");
			}
		}
	},

	getPathNodes: function(node) {
		var n = node;
		var p = [ n ];
		while(n = this.getParentNode(n)) {
			p.push(n);
		}
		p.reverse();
		return p;
	},

	destroy: function() {
		Event.stopObserving(this.container, "click", this._clickListener);
		Event.stopObserving(this.container, "dblclick", this._dblclickListener);
	},

	_click: function(event) {
		var target = Event.element(event);
		var node = Event.findElement(event, "LI");
		if(node.childNodes[0] == target) {
			if(node.getAttribute("expanded") == "true") {
				this.collapse(node);
			} else {
				this.expand(node);
			}
		} else if(this.container != target) {
			this.select(node);
		}
	},

	_dblclick: function(event) {
		var target = Event.element(event);
		var node = Event.findElement(event, "LI");
		if(node.childNodes[0] == target) {
			return;
		}
		if(node.getAttribute("expanded") == "true") {
			this.collapse(node);
		} else {
			this.expand(node);
		}
	},

	_applyNode: function(node) {
		Element.addClassName(node, this.nodeClass);
		var span = document.createElement("span");
		Element.addClassName(span, this.nodeTextClass);
		var nodeContent = new Array();
		for(var i = 0; i < node.childNodes.length; ++i) {
			if (node.childNodes[i].tagName && node.childNodes[i].tagName.toLowerCase() == "ul") break;
			nodeContent.add(node.childNodes[i]);
		}
		node.replaceChild(span, node.childNodes[0]);
		for(var i = 0; i < nodeContent.length; ++i) {
			span.appendChild(nodeContent[i]);
		}
		span.title = Element.getInnerText(span);
		var children = node.getElementsByTagName("ul");
		if (children.length > 0) {
			new Insertion.Top(node, "<img align='" + this.nodeIconAlign + "' class='" + this.nodeIconClass + "' src='" + this.nodeCollapsedIcon + "' style='cursor:pointer;cursor:hand' />");
			children[0].style.display = "none";
		}
		else {
			new Insertion.Top(node, "<img align='" + this.nodeIconAlign + "' class='" + this.nodeIconClass + "' src='" + this.nodeLeafIcon + "' style='cursor:pointer;cursor:hand' />");
		}
		node.setAttribute("expanded", "false");
		node.tree = this;
	}
};
ListView = Class.create();
ListView.prototype = {
	initialize: function(container, attributes) {
		this.container = $(container);
		this.id = this.container.id;
		var attr = {
			headerAscClass: "listHeaderAsc",
			headerDescClass: "listHeaderDesc",
			rowClass: "listRow",
			rowSelectedClass: "listRowSelected",
			applyListClasses: false,
			allowSelect: true,
			multipleSelect: true,
			allowSort: true,
			sortCol: 0,
			sortAsc: true,
			headerSizeTimeout: 500,
			sortSymbolWidth: 16,
			rowHeight: 20,
			columnSizing: true,
			minColumnWidth: 50,
			onRowClick: null,
			onRowDoubleClick: null,
			onCellClick: null,
			onRowSelected: null,
			onRowDeselected: null,
			enabled: true,
			/*Active Panel for Header*/
			elementTags: [ "TD", "TH" ],
			elementClass: "listHeaderNormal",
			elementHoverClass: "listHeaderHover",
			elementActiveClass: "listHeaderActive",
			elementDisabledClass: "listHeaderDisabled",
			applyActivePanelClasses: true
		};
		if(!attributes) {
			attributes = {};
		}
		Object.extend(attr, attributes);
		Object.extend(this, attr);

		//Mash the DOM
		this.headerContainer = document.createElement("DIV");
		this.headerContainer.style.overflow = "hidden";
		this.headerContainer.setAttribute("dock", "top");
		this.headerScrollOffset = this.headerContainer.appendChild(document.createElement("DIV"));
		this.headerScrollOffset.style.position = "absolute";
		this.headerScrollOffset.style.left = "5000px";
		this.headerScrollOffset.style.top = "0px";
		this.headerScrollOffset.style.width = "10px";
		this.headerScrollOffset.style.height = "10px";
		this.headerScrollOffset.style.background = "transparent";

		this.bodyContainer = document.createElement("DIV");
		this.bodyContainer.style.overflow = "auto";
		this.bodyContainer.setAttribute("dock", "fill");

		this.bodyTable = Element.findChild(this.container, "TABLE");

		this.header = Element.findChild(this.container, "THEAD");

		this.headerTable = document.createElement("TABLE");
		this.headerTable.appendChild(this.header);
		this.headerTable.cellPadding = this.bodyTable.cellPadding;
		this.headerTable.cellSpacing = this.bodyTable.cellSpacing;
		this.headerTable.border = this.bodyTable.border;
		this.headerTable.width = "";
		this.bodyTable.width = "";
		this.headerTable.style.width = "0px";
		this.bodyTable.style.width = "0px";

		this.headerContainer.appendChild(this.headerTable);
		this.bodyContainer.appendChild(this.bodyTable);

		this.container.appendChild(this.headerContainer);
		this.container.appendChild(this.bodyContainer);

		//Get the column width information
		this.headerWidths = [];
		this.headerPixelWidth = 0;
		this.headerPercWidth = 0;
		this.columnCells = [];
		this.headerCells = $A(this.headerTable.rows[0].cells);
		this.headerCells.each(function(cell, i) {
			var w = cell.getAttribute("width");
			if(!w || w == 0) {
				w = 1;
				this.headerPercWidth += 1;
			} else if (w.endsWith("%")) {
				w = parseInt(w) / 100;
				this.headerPercWidth += w;
			} else {
				w = parseInt(w);
				this.headerPixelWidth += w;
			}
			this.headerWidths[i] = w;
		}.bind(this));

		this.headerCells.each(function(cell, i) {
			var ruleId = "." + this.id + "_col" + i;
			if(Styles.getRule(ruleId)) {
				Styles.updateRule(ruleId, "width", this.minColumnWidth + "px");
				Styles.updateRule(ruleId, "height", this.rowHeight + "px");
			} else {
				Styles.addRule(ruleId, "width:" + this.minColumnWidth + "px;height:" + this.rowHeight + "px;white-space:nowrap;overflow:hidden;");
			}
			this._refitCell(cell, i);
		}.bind(this));

		$A(this.bodyTable.rows).each(function(row) {
			$A(row.cells).each(function(cell, i) {
				this._refitCell(cell, i);
			}.bind(this));
		}.bind(this));

		this.layoutManager = new DockManager({ container: this.container, onLayoutPerformed: this.resizeColumns.bind(this) });
		this.headerPanel = new ActivePanel(this.headerTable.rows[0], attr)
		if(!this.allowSort) {
			this.headerPanel.disableAll();
		}

		this.selectedRows = [];
		this.selectedRow = null;

		this.setSortCol();

		Element.setTextSelectable(this.bodyTable, false);
		Element.setTextSelectable(this.headerTable, false);

		this._scrollListener = this.scroll.bindAsEventListener(this);
		this._clickListener = this.click.bindAsEventListener(this);
		this._dblClickListener = this._dblClick.bindAsEventListener(this);
		this._headerClickListener = this._headerClick.bindAsEventListener(this)

		Event.observe(this.bodyContainer, "scroll", this._scrollListener);
		Event.observe(this.bodyContainer, "click", this._clickListener);
		Event.observe(this.bodyContainer, "dblclick", this._dblClickListener);
		Event.observe(this.headerContainer, "click", this._headerClickListener);

		setTimeout(function() { this.layoutManager.performLayout() }.bind(this), 200);
	},

	_refitCell: function(cell, index) {
		if(!this.columnCells[index]) {
			var div = document.createElement("DIV");
			div.className = this.id + "_col" + index;
			if(cell.getAttribute("align")) {
				div.style.textAlign = cell.getAttribute("align");
			}
			this.columnCells[index] = div;
		}
		var div = this.columnCells[index].cloneNode(false);
		while(cell.firstChild) {
			div.appendChild(cell.firstChild);
		}
		cell.appendChild(div);
	},

	getSubItem: function(row, index) {
		return row.cells[index].firstChild;
	},

	setSubItem: function(row, index, html) {
		Element.update(row.cells[index].firstChild, html);
	},

	resizeColumns: function() {
		if(!this.columnSizing) {
			return;
		}
		if(!Positioning.isDisplayed(this.container)) {
			return;
		}
		var width = this.bodyContainer.clientWidth; //Element.getDimensions(this.container).width - Element.getSizePadding(this.container).w;
		this.headerContainer.style.width = width + "px";
		var availWidth = width - this.headerPixelWidth;
		var sw = Element.getSizePadding(this.headerCells[0], true).w;
		this.headerCells.each(function(cell, i) {
			var w = this.headerWidths[i];
			if(w <= 1) {
				w = parseInt(availWidth * w / this.headerPercWidth);
				if(w < this.minColumnWidth) {
					w = this.minColumnWidth;
				}
			}
			Styles.updateRule("." + this.id + "_col" + i, "width", (w - sw) + "px");
		}.bind(this));
		this.scroll(null);
	},

	scroll: function(event) {
		this.headerContainer.scrollLeft = this.bodyContainer.scrollLeft;
	},

	click: function(event) {
		if(!this.enabled) {
			return;
		}
		var cell = Event.findElement(event, "TD");
		if(cell) {
			if(this.allowSelect) {
				if((!Event.isCtrlPressed(event) && !Event.isShiftPressed(event)) || !this.multipleSelect) {
					this.clearSelection();
				}
				if(Event.isShiftPressed(event) && this.selectedRow) {
					var startIndex = this.indexOf(this.selectedRow);
					var endIndex = this.indexOf(cell.parentNode);
					if(startIndex > endIndex) {
						var t = startIndex;
						startIndex = endIndex;
						endIndex = t;
					}
					if(startIndex == endIndex) {
						this.toggleSelection(cell.parentNode);
					} else {
						for(var i = startIndex; i <= endIndex; ++i) {
							this.select(this.bodyTable.rows[i]);
						}
					}
				} else {
					this.toggleSelection(cell.parentNode);
				}
			}

			if(typeof(this.onCellClick) == "function") {
				this.onCellClick(cell, event);
			}
			if(typeof(this.onRowClick) == "function") {
				this.onRowClick(cell.parentNode, event);
			}
		}
	},

	_dblClick: function(event) {
		if(!this.enabled) {
			return;
		}
		var cell = Event.findElement(event, "TD");
		if(cell) {
			if(typeof(this.onRowDoubleClick) == "function") {
				this.onRowDoubleClick(cell.parentNode, event);
			}
		}
	},

	_headerClick: function(event) {
		if(!this.allowSort || !this.enabled) {
			return;
		}
		var header = Event.findElement(event, "TD");
		if(!header) {
			return;
		}
		var col = $A(this.headerTable.rows[0].cells).indexOf(header);
		var asc = this.sortAsc;
		if(col == this.sortCol) {
			asc = !asc
		}
		this.setSortCol(col, asc);
		this.sort();
	},

	sort: function() {
		if(this.sortCol < 0 || this.sortCol >= this.headerTable.rows[0].cells.length) {
			this.sortCol = 0;
		}
		var header = this.headerTable.rows[0].cells[this.sortCol];
		var sortAlgo = this._sortAlpha;
		switch(header.getAttribute("sortas")) {
			case "int":
				sortAlgo = this._sortInt;
				break;
			case "double":
				sortAlgo = this._sortDouble;
				break;
			case "date":
				sortAlgo = this._sortDate;
				break;
		}
		var rows = $A(this.bodyTable.rows);
		rows.sort(sortAlgo.bind(this));
		rows.each(function(row) {
			this.bodyTable.tBodies[0].appendChild(row);
		}.bind(this));
	},

	clear: function() {
		this.clearSelection();
		this.getRows().each(function(row) {
			this.removeRow(row);
		}.bind(this));
	},

	toggleSelection: function(row) {
		if(this.selectedRows.indexOf(row) == -1) {
			this.select(row);
		} else {
			this.deselect(row);
		}
	},

	select: function(row) {
		if(!row || !row.cells) {
			return;
		}
		if(this.selectedRows.indexOf(row) == -1) {
			this.addRowClass(row, this.rowSelectedClass);
			this.selectedRows.add(row);
			this.selectedRow = row;
			if(typeof(this.onRowSelected) == "function") {
				this.onRowSelected(row);
			}
		}
	},

	deselect: function(row) {
		if(this.selectedRows.indexOf(row) != -1) {
			this.removeRowClass(row, this.rowSelectedClass);
			this.selectedRows.remove(row);
			if(this.selectedRow == row) {
				this.selectedRow = null;
			}
			if(typeof(this.onRowDeselected) == "function") {
				this.onRowDeselected(row);
			}
		}
	},

	clearSelection: function() {
		this.selectedRows.each(function(row) {
			this.removeRowClass(row, this.rowSelectedClass);
		}.bind(this));
		this.selectedRows = [];
		this.selectedRow = null;
	},

	getSelectedRows: function() {
		return this.selectedRows;
	},

	indexOf: function(row) {
		return $A(this.bodyTable.rows).indexOf(row);
	},

	setSortCol: function(col, asc) {
		if(col == null) {
			col = this.sortCol;
		}
		if(col < 0 || col > this.headerTable.rows[0].cells.length) {
			col = -1;
		}
		if(asc == null) {
			asc = this.sortAsc;
		}
		if(this.sortCol != -1) {
			Element.removeClassName(this.headerTable.rows[0].cells[this.sortCol], this.headerAscClass);
			Element.removeClassName(this.headerTable.rows[0].cells[this.sortCol], this.headerDescClass);
		}
		this.sortCol = col;
		this.sortAsc = asc;
		if(this.sortCol != -1) {
			Element.addClassName(this.headerTable.rows[0].cells[this.sortCol], this.sortAsc ? this.headerAscClass : this.headerDescClass);
		}
	},

	addRow: function(row) {
		if(row.constructor == Array) {
			while(row.length < this.headerTable.rows[0].cells.length) {
				row.add("");
			}
			var tr = document.createElement("TR");
			var cells = [];
			for(var i = 0; i < this.headerTable.rows[0].cells.length; ++i) {
				var td = tr.appendChild(document.createElement("TD"));
				if(typeof(row[i]) == "string" || typeof(row[i]) == "number") {
					Element.update(td, row[i].toString());
				} else {
					td.appendChild(row[i]);
				}
				cells.add(td);
			}
		} else if(typeof(row) == "string") {
			var tr = Element.fromHTML(row, "TR");
			var cells = $A(tr.cells);
		} else {
			var tr = row;
			var cells = $A(tr.cells);
		}
		$A(cells).each(function(cell, i) {
			this._refitCell(cell, i);
		}.bind(this));
		this.bodyTable.tBodies[0].appendChild(tr);
		this.addRowClass(tr, this.rowClass);
		return tr;
	},

	removeRow: function(row) {
		if(typeof(row) == "number") {
			var tr = this.bodyTable.rows[row];
		} else {
			var tr = row;
		}
		this.deselect(tr);
		this.bodyTable.tBodies[0].removeChild(tr);
		return tr;
	},

	getRows: function(){
		return $A(this.bodyTable.rows);
	},

	addRowClass: function(row, className) {
		$A(row.cells).each(function(cell) {
			Element.addClassName(cell, className);
		});
	},

	removeRowClass: function(row, className) {
		$A(row.cells).each(function(cell) {
			Element.removeClassName(cell, className);
		});
	},

	disable: function() {
		this.enabled = false;
		this.headerPanel.disableAll();
	},

	enable: function() {
		this.enabled = true;
		this.headerPanel.enableAll();
	},

	destroy: function() {
		this.layoutManager.destroy();
		this.headerPanel.destroy();

		Event.stopObserving(this.bodyContainer, "scroll", this._scrollListener);
		Event.stopObserving(this.bodyContainer, "click", this._clickListener);
		Event.stopObserving(this.bodyContainer, "dblclick", this._dblClickListener);
		Event.stopObserving(this.headerContainer, "click", this._headerClickListener);
	},

	_sortAlpha: function(a, b) {
	    aa = Element.getInnerText(a.cells[this.sortCol]);
	    bb = Element.getInnerText(b.cells[this.sortCol]);
	    if (("" + aa).trim() == "") return (this.sortAsc ? 1 : -1);
	    if (("" + bb).trim() == "") return (this.sortAsc ? -1 : 1);
	    if (aa==bb) return 0;
	    if (aa.toLowerCase()<bb.toLowerCase()) return (this.sortAsc ? -1 : 1);
	    return (this.sortAsc ? 1 : -1);
	},

	_sortInt: function(a, b) {
		var listView = Element.findParent(a, "div").listView;
	    aa = Element.getInnerText(a.cells[this.sortCol]);
	    bb = Element.getInnerText(b.cells[this.sortCol]);
	    if (("" + aa).trim() == "") return (this.sortAsc ? 1 : -1);
	    if (("" + bb).trim() == "") return (this.sortAsc ? -1 : 1);
	    try { aa = parseInt(aa) } catch(ex) { }
	    try { bb = parseInt(bb) } catch(ex) { }
	    if (aa == bb) return 0;
	    if (aa < bb) return (this.sortAsc ? -1 : 1);
	    return (this.sortAsc ? 1 : -1);
	},

	_sortDouble: function(a, b) {
		var listView = Element.findParent(a, "div").listView;
	    aa = Element.getInnerText(a.cells[this.sortCol]);
	    bb = Element.getInnerText(b.cells[this.sortCol]);
	    if (("" + aa).trim() == "") return (this.sortAsc ? 1 : -1);
	    if (("" + bb).trim() == "") return (this.sortAsc ? -1 : 1);
	    try { aa = parseFloat(aa) } catch(ex) { }
	    try { bb = parseFloat(bb) } catch(ex) { }
	    if (aa == bb) return 0;
	    if (aa < bb) return (this.sortAsc ? -1 : 1);
	    return (this.sortAsc ? 1 : -1);
	},

	_sortDate: function(a, b) { //TODO: Date sorting
		var listView = Element.findParent(a, "div").listView;
	    aa = Element.getInnerText(a.cells[this.sortCol]);
	    bb = Element.getInnerText(b.cells[this.sortCol]);
	    if (("" + aa).trim() == "") return (this.sortAsc ? 1 : -1);
	    if (("" + bb).trim() == "") return (this.sortAsc ? -1 : 1);
	    aa = new Date(aa);
	    bb = new Date(bb);
	    if (aa==bb) return 0;
	    if (aa<bb) return (this.sortAsc ? -1 : 1);
	    return (this.sortAsc ? 1 : -1);
	}
};/*
 * DatePicker 1.1
 * � 2005-2007 E-Technik Website Development (Pty) Ltd
 * Author: Marc Heiligers (marc@e-technik.com)
 *
 * Requires: prototype.js, core.js, activePanel.js, scrollPanel.js, popupPanel.js
 */

DatePicker = Class.create();
DatePicker.prototype = {
	initialize: function(textField, attributes) {
		var attr = {
			containerClass: "datePickerContainer",
			tableClass: "datePickerTable",
			prevClass: "datePickerPrev",
			nextClass: "datePickerNext",
			monthClass: "datePickerMonth",
			yearClass: "datePickerYear",
			dayNameClass: "datePickerDayName",
			dayClass: "datePickerDay",
			outsideDayClass: "datePickerDayOutside",
			todayClass: "datePickerToday",
			blankCellClass: "datePickerBlank",
			currentClass: "datePickerCurrent",
			minYear: 1960,
			maxYear: 2050,
			dateFormat: "d M Y",
			dateButton: null,
			date: null, //The actual date
			prevSymbol: "",
			nextSymbol: "",
			nullDate: "",
			/* PopupPanel */
			autoHide: false,
			autoHideTime: 200,
			autoShow: false,
			horizontalAnchor: "left",
			verticalAnchor: "farBottom",
			element: null, // Should always be either the same as dateButton or textField
			/* Active Panel & Popup Month and Year Selectors */
			elementTags: [ "DIV", "TD" ],
			selectContainerClass: "datePickerSelectContainer",
			elementClass: "datePickerSelect",
			elementHoverClass: "datePickerSelectHover",
			elementActiveClass: "datePickerSelectActive",
			elementSelectedClass: "datePickerSelectSelected",
			includeChildChildren: true,
			/* ScrollPanel */
			scrollerClass: "datePickerScroller",
			scrollerTopClass: "datePickerScrollerTop",
			scrollerLeftClass: "datePickerScrollerLeft",
			scrollerBottomClass: "datePickerScrollerBottom",
			scrollerRightClass: "datePickerScrollerRight",
			scrollerHoverClass: "datePickerScrollerHover",
			scrollerActiveClass: "datePickerScrollerActive",
			scrollerPosition: "inside",
			scrollerStepSize: 30,
			scrollInitialTime: 100,
			scrollAcceleration: 0.9,
			scrollHorizontal: false,
			scrollVertical: true,
			/* Events */
			onDateSelected: null
		};
		if(!attributes) {
			attributes = {};
		}
		Object.extend(attr, attributes);
		Object.extend(this, attr);
		this.textField = $(textField);
		this.dateButton = $(this.dateButton);

		this.setDate(this.date);

		this.container = document.body.appendChild(document.createElement("DIV"));
		this.container.setAttribute("inactive", "true");
		Element.addClassName(this.container, this.containerClass);
		if(!attr.element) {
			if(this.dateButton) {
				attr.element = this.dateButton;
			} else {
				attr.element = this.textField;
			}
		}

		this._mouseDownListener = this._mouseDown.bind(this);
		this._monthClickListener = this._monthClick.bindAsEventListener(this);

		this.popup = new PopupPanel(this.container, attr);
		this.activePanel = new ActivePanel(this.container, attr);
		Event.observe(this.container, "mousedown", this._mouseDownListener);
		Element.setTextSelectable(this.container, false);

		this.monthContainer = document.body.appendChild(document.createElement("DIV"));
		this.monthContainer.setAttribute("inactive", "true");
		Element.addClassName(this.monthContainer, this.selectContainerClass);
		var s = "";
		for(i = 0; i < 12; ++i) {
			s += "<div month='" + i + "'>" + Date.MonthNames[i] + "</div>";
		}
		Element.update(this.monthContainer, s);
		this.monthPopup = new PopupPanel(this.monthContainer, attr);
		this.monthActivePanel = new ActivePanel(this.monthContainer, attr);
		this.monthScrollPanel = new ScrollPanel(this.monthContainer, attr);
		this.monthScrollPanel.onScroll = this._scroll.bind(this);
		Event.observe(this.monthContainer, "click", this._monthClickListener);

		this._yearClickListener = this._yearClick.bindAsEventListener(this);

		this.yearContainer = document.body.appendChild(document.createElement("DIV"));
		this.yearContainer.setAttribute("inactive", "true");
		Element.addClassName(this.yearContainer, this.selectContainerClass);
		var s = "";
		for(i = this.minYear; i <= this.maxYear; ++i) {
			s += "<div year='" + i + "'>" + i + "</div>";
		}
		Element.update(this.yearContainer, s);
		this.yearPopup = new PopupPanel(this.yearContainer, attr);
		this.yearActivePanel = new ActivePanel(this.yearContainer, attr);
		this.yearScrollPanel = new ScrollPanel(this.yearContainer, attr);
		this.yearScrollPanel.onScroll = this._scroll.bind(this);
		Event.observe(this.yearContainer, "click", this._yearClickListener);

		this._showListener = this._show.bindAsEventListener(this);
		this._blurListener = this._blur.bind(this);
		this._clickListener = this._click.bindAsEventListener(this);

		if(this.dateButton) {
			Event.observe(this.dateButton, "click", this._showListener);
			Event.observe(this.dateButton, "blur", this._blurListener);
		}
		if(this.textField && this.textField.type != "hidden") {
			Event.observe(this.textField, "focus", this._showListener);
			Event.observe(this.textField, "blur", this._blurListener);
			Event.observe(this.textField, "click", this._showListener);
		}
		Event.observe(this.popup.container, "click", this._clickListener);

		this.setDate(this.date);
	},

	setDate: function(date) {
		if(!date) {
			this.date = null;
			if(this.textField) {
				this.textField.value = this.nullDate;
			}
		} else {
			this.date = new Date(date);
			if(this.textField) {
				this.textField.value = this.date.dateFormat(this.dateFormat);
			}
		}
	},

	destroy: function() {
		this.monthPopup.destroy();
		this.monthActivePanel.destroy();
		this.monthScrollPanel.destroy();
		Event.stopObserving(this.monthContainer, "click", this._monthClickListener);
		document.body.removeChild(this.monthContainer);


		this.yearPopup.destroy();
		this.yearActivePanel.destroy();
		this.yearScrollPanel.destroy();
		Event.stopObserving(this.yearContainer, "click", this._yearClickListener);
		document.body.removeChild(this.yearContainer);

		if(this.dateButton) {
			Event.stopObserving(this.dateButton, "click", this._showListener);
			Event.stopObserving(this.dateButton, "blur", this._blurListener);
		}
		if(this.textField && this.textField.type != "hidden") {
			Event.stopObserving(this.textField, "focus", this._showListener);
			Event.stopObserving(this.textField, "blur", this._blurListener);
			Event.stopObserving(this.textField, "click", this._showListener);
		}
		Event.stopObserving(this.popup.container, "click", this._clickListener);

		this.popup.destroy();
		this.activePanel.destroy();
		Event.stopObserving(this.container, "mousedown", this._mouseDownListener);
		document.body.removeChild(this.container);
	},

	_show: function(event) {
		if(this.__noBlur) {
			this.__noBlur = false;
			return;
		}
		Element.update(this.container, this._createDateTable(this.date));
		this.activePanel.apply();
		this.__currentDate = this.date;
		this.popup.show(event);
	},

	_blur: function() {
		setTimeout(this._doBlur.bind(this), 200);
	},

	_doBlur: function() {
		if(this.__noBlur) {
			this.textField.focus();
			return;
		}
		if(this.textField) {
			var date = new Date(this.textField.value);
			if(date && date.dateFormat(this.dateFormat).indexOf("NaN") == -1) {
				this.setDate(date);
			} else {
				this.setDate(this.date);
			}
		}
		this.popup.hide();
		this.monthPopup.hide();
		this.yearPopup.hide();
	},

	_click: function(event) {
		if(Positioning.isDisplayed(this.monthContainer)) {
			this.__noBlur = true;
			this.monthPopup.hide();
			return;
		}
		if(Positioning.isDisplayed(this.yearContainer)) {
			this.__noBlur = true;
			this.yearPopup.hide();
			return;
		}
		var target = Event.element(event);
		if(Element.hasClassName(target, this.dayClass) || Element.hasClassName(target, this.outsideDayClass)) {
			this.setDate(target.getAttribute("date"));
			this.popup.hide();
			if(this.onDateSelected) {
				this.onDateSelected(this.date);
			}
		} else if(Element.hasClassName(target, this.todayClass)) {
			this.setDate(new Date());
			this.popup.hide();
			if(this.onDateSelected) {
				this.onDateSelected(this.date);
			}
		} else if(Element.hasClassName(target, this.currentClass)) {
			this.setDate(this.date);
			this.popup.hide();
			if(this.onDateSelected) {
				this.onDateSelected(this.date);
			}
		} else if(Element.hasClassName(target, this.prevClass)) {
			this.__noBlur = true;
			this.__currentDate = this.__currentDate.getMonthOf().start.addDays(-1);
			Element.update(this.container, this._createDateTable(this.__currentDate));
			this.activePanel.apply();
		} else if(Element.hasClassName(target, this.nextClass)) {
			this.__noBlur = true;
			this.__currentDate = this.__currentDate.getMonthOf().end.addDays(1);
			Element.update(this.container, this._createDateTable(this.__currentDate));
			this.activePanel.apply();
		} else if(Element.hasClassName(target, this.monthClass)) {
			this.__noBlur = true;
			this.monthPopup.element = target;
			this.monthPopup.show();
			this.monthScrollPanel.scrollTo(this.monthContainer.childNodes[this.__currentDate.getMonth()]);
			this.monthActivePanel.deselectAll();
			this.monthActivePanel.select(this.monthContainer.childNodes[this.__currentDate.getMonth()]);
		} else if(Element.hasClassName(target, this.yearClass)) {
			this.__noBlur = true;
			this.yearPopup.element = target;
			this.yearPopup.show();

			var yearIndex = this.__currentDate.getFullYear() - this.minYear;
			var scrollIndex = yearIndex - parseInt(this.yearPopup.container.offsetHeight / this.yearContainer.childNodes[0].offsetHeight / 2);
			this.yearActivePanel.deselectAll();
			if(scrollIndex < 0) {
				this.yearScrollPanel.scrollTo(this.yearContainer.childNodes[0]);
			} else if(scrollIndex >= this.yearContainer.childNodes.length) {
				this.yearScrollPanel.scrollTo(this.yearContainer.childNodes[this.yearContainer.childNodes.length - 1]);
			} else {
				this.yearScrollPanel.scrollTo(this.yearContainer.childNodes[scrollIndex]);
			}
			if(yearIndex >= 0 && yearIndex < this.yearContainer.childNodes.length) {
				this.yearActivePanel.select(this.yearContainer.childNodes[yearIndex]);
			}
		} else {
			this.__noBlur = true;
		}
	},

	_mouseDown: function() {
		this.__noBlur = true;
	},

	_scroll: function() {
		this.__noBlur = true;
	},

	_monthClick: function(event) {
		this.__noBlur = true;
		var target = Event.element(event);
		var month = target.getAttribute("month");
		if(month) {
			this.__currentDate = new Date(this.__currentDate.getFullYear(), parseInt(month), 1);
			Element.update(this.container, this._createDateTable(this.__currentDate));
			this.activePanel.apply();
			this.monthPopup.hide();
		}
	},

	_yearClick: function(event) {
		this.__noBlur = true;
		var target = Event.element(event);
		var year = target.getAttribute("year");
		if(year) {
			this.__currentDate = new Date(parseInt(year), this.__currentDate.getMonth(), 1);
			Element.update(this.container, this._createDateTable(this.__currentDate));
			this.activePanel.apply();
			this.yearPopup.hide();
		}
	},

	_createDateTable: function(date) {
		if(!date) {
			date = new Date();
		}
		var month = date.getMonthOf();
		var start = month.start.addDays(-month.start.getDay());
		var s = "<table cellpadding='0' cellspacing='0' border='0' inactive='true' class='" + this.tableClass + "'>" +
				"<tr><td class='" + this.prevClass + "'>" + this.prevSymbol + "</td>" + //&#AB;
				"<td class='" + this.monthClass + "' colspan='3' align='right'>" + month.start.dateFormat("F") + "</td>" +
				"<td class='" + this.yearClass + "' colspan='2'>" + month.start.dateFormat("Y") + "</td>" +
				"<td class='" + this.nextClass + "'>" + this.nextSymbol + "</td></tr>" + //&#BB;
				"<tr><td inactive='true' class='" + this.dayNameClass + "'>S</td>" +
				"<td inactive='true' class='" + this.dayNameClass + "'>M</td>" +
				"<td inactive='true' class='" + this.dayNameClass + "'>T</td>" +
				"<td inactive='true' class='" + this.dayNameClass + "'>W</td>" +
				"<td inactive='true' class='" + this.dayNameClass + "'>T</td>" +
				"<td inactive='true' class='" + this.dayNameClass + "'>F</td>" +
				"<td inactive='true' class='" + this.dayNameClass + "'>S</td></tr>";
		for(var i = 0; i < 6; ++i) {
			s += "<tr>";
			for(var ii = 0; ii < 7; ++ii) {
				s += "<td date='" + start.dateFormat("d M Y") + "' " + (this.date ? start.getTime() == this.date.getTime() ? "selected='true'" : "" : "") + "class='" + (start < month.start || start > month.end ? this.outsideDayClass : this.dayClass) + "'>" + start.getDate() + "</td>";
				start = start.addDays(1);
			}
			s += "</tr>";
		}
		s += "<tr><td date='" + (new Date()).dateFormat("d M Y") + "' class='" + this.todayClass + "' colspan='3'>Today</td>" +
				"<td inactive='true' class='" + this.blankCellClass + "'></td>" +
				"<td date='" + (this.date ? this.date.dateFormat("d M Y") : "") + "' class='" + this.currentClass + "' colspan='3' align='right'>Current</td></tr></table>";
		return s;
	}
};/*
 * CalendarView 1.1
 * � 2005-2007 E-Technik Website Development (Pty) Ltd
 * Author: Marc Heiligers (marc@e-technik.com)
 */

CalendarView = Class.create();
CalendarView.prototype = {
	initialize: function(container) {
		this.container = $(container);

		Object.extend(this, {
			viewMode: null,
			selectedDate: null,

			dateFormat: "d M Y",
			weekLayout: CalendarView.LayoutVertical,
			yearLayout: CalendarView.LayoutHorizontal,

			dayViewTableClass: "dayViewTable",
			dayViewTimeColumnClass: "dayViewTimeColumn",
			dayViewDateHeaderClass: "dayViewDateHeader",
			dayViewBusinessHoursClass: "dayViewBusinessHours",
			dayViewAfterHoursClass: "dayViewAfterHours",

			weekViewTableClass: "weekViewTable",
			weekViewDayHeaderClass: "weekViewDayHeader",
			weekViewDayBodyClass: "weekViewDayBody",
			weekViewWeekendDayBodyClass: "weekViewWeekendDayBody",

			monthViewTableClass: "monthViewTable",
			monthViewDayHeaderClass: "monthViewDayHeader",
			monthViewDayClass: "monthViewDay",
			monthViewOutsideDayClass: "monthViewOutsideDay",

			yearViewTableClass: "yearViewTable",
			yearViewDayHeaderClass: "yearViewDayHeader",
			yearViewDayBodyClass: "yearViewDayBody",
			yearViewDayOutsideBodyClass: "yearViewDayOutsideBody",
			yearViewMonthColumnClass: "yearViewMonthColumn",

			eventClass: "calendarViewEvent",
			eventEmptyClass: "calendarViewEventEmpty",
			eventOverdueClass: "calendarViewEventOverdue",
			eventFutureClass: "calendarViewEventFuture",

			events: [],
			eventText: true,
			eventTooltip: true,

			click: null,
			doubleClick: null,
			rendered: null,
			eventsRendered: null,

			padding: 0,
			marginLeft: 0,
			marginRight: 0,
			marginTop: 0,
			marginBottom: 0,
			overflow: "auto"
		});
		if(arguments[1]) {
			Object.extend(this, arguments[1]);
		}

		Element.setTextSelectable(this.container, false);

		this.setViewMode(this.viewMode);
		this.setDate(this.selectedDate);
		this.firstDay = null;
		this.lastDay = null;

		this.fillers = [];

		this._clickListener = this._click.bindAsEventListener(this);
		this._dblclickListener = this._dblclick.bindAsEventListener(this);
		Event.observe(this.container, "click", this._clickListener);
		Event.observe(this.container, "dblclick", this._dblclickListener);
		this.calendarTable = null;

		this.layoutManager = new DockManager({ container: this.container, overflow: this.overflow, padding: this.padding,
			marginLeft: this.marginLeft, marginRight: this.marginRight, marginTop: this.marginTop, marginBottom: this.marginBottom });
	},

	destroy: function() {
		Event.stopObserving(this.container, "click", this._clickListener);
		Event.stopObserving(this.container, "dblclick", this._dblclickListener);
		this.layoutManager.destroy();
	},

	_click: function(event) {
		var element = Event.element(event);
		if(this.click) {
			this.click(element, event);
		}
	},

	_dblclick: function(event) {
		var element = Event.element(event);
		if(this.doubleClick) {
			this.doubleClick(element, event);
		}
	},

	setDate: function(selectedDate, render) {
		this.selectedDate = (selectedDate ? selectedDate.getDateOnly() : (new Date()).getDateOnly());
		if(render) {
			this.render();
		}
	},

	setViewMode: function(viewMode, render) {
		this.viewMode = (viewMode ? viewMode : CalendarView.ViewMonth);
		if(render) {
			this.render();
		}
	},

	getPeriod: function() {
		switch(this.viewMode) {
			case CalendarView.ViewDay:
				return this.selectedDate.getDayOf();
			case CalendarView.ViewWeek:
				return this.selectedDate.getWeekOf();
			case CalendarView.ViewMonth:
				return this.selectedDate.getMonthOf();
			case CalendarView.ViewYear:
				return this.selectedDate.getYearOf();
		}
	},

	clearEvents: function() {
		this.removeEvents();
		this.events = [];
	},

	removeEvents: function() {
		this.events.each(function(event) {
			event.divs.each(function(div) {
				try {
					div.parentNode.removeChild(div);
					if(div.parentNode.childNodes.length == 0) {
						div.parentNode.update("<br />");
					}
				} catch(ex) {}
				event.divs = [];
			});
		});
		this.fillers.each(function(filler) {
			try {
				filler.parentNode.removeChild(filler);
				if(filler.parentNode.childNodes.length == 0) {
					filler.parentNode.update("<br />");
				}
			} catch(ex) {}
		});
		this.fillers = [];
		if(this.days && this.viewMode == CalendarView.ViewYear && this.yearLayout != CalendarView.LayoutVertical) {
			this.days.each(function(cell) {
				cell.update("<br />");
			});
		}

	},

	addEvent: function(event) {
		this.events.add(event);
	},

	_showEvent: function(event) {
		//Get cells
		var current = event.period.start.getDateOnly();
		var cells = [];
		var endTime = event.period.end.getDateOnly();
		while(current <= endTime) {
			var cell = this.getDayCell(current);
			if(cell) {
				cells.add(cell);
			}
			current = current.addDays(1);
		}

		if(cells.length == 0) {
			return;
		}

		//Get max events in cells
		var count = 0;
		cells.each(function(cell) {
			var eventCount = cell.getAttribute("eventCount")
			if(eventCount > count) {
				count = eventCount;
			}
			if(this.viewMode == CalendarView.ViewYear && this.yearLayout != CalendarView.LayoutVertical && eventCount == 0) {
				cell.update("");
			}
		}.bind(this));

		//Add Fillers
		var fill = document.createElement("div");
		fill.className = this.eventEmptyClass;

		cells.each(function(cell) {
			var eventCount = cell.getAttribute("eventCount");
			for(var i = eventCount; i < count; ++i) {
				var actual = fill.cloneNode();
				cell.appendChild(actual);
				this.fillers.add(actual);
			}
			cell.setAttribute("eventCount", count + 1);
		}.bind(this));

		//Add Events
		var div = document.createElement("div");
		var title = event.title;
		if(event.dateTitles) {
			title = null;
		}
		if(this.eventText && title) {
			new Insertion.Top(div, title);
		}
		if(this.eventTooltip && title) {
			div.title = title.replace(/<br \/>/ig,"\n").replace(/(<([^>]+)>)/ig,"");
		}
		div.style.backgroundColor = event.color;
		div.className = this.eventClass;
		if(event.className) {
			Element.addClassName(div, event.className);
		}

		var today = new Date().getDateOnly();
		cells.each(function(cell) {
			var actual = div.cloneNode();
			cell.appendChild(actual);
			actual.event = event;
			if(cell.date > event.due) {
				Element.addClassName(actual, this.eventOverdueClass);
			}
			if(cell.date > today) {
				Element.addClassName(actual, this.eventFutureClass);
			}
			if(event.dateTitles) {
				var title = event.dateTitles[cell.date];
				if(this.eventText && title) {
					new Insertion.Top(actual, title);
				}
				if(this.eventTooltip && title) {
					actual.title = title.replace(/<br \/>/ig,"\n").replace(/(<([^>]+)>)/ig,"");
				}
			}
			event.divs.add(actual);
		}.bind(this));
	},

	showEvents: function() {
		if(this.calendarTable) {
			$A(this.calendarTable.getElementsByTagName("TD")).each(function(cell) {
				cell.setAttribute("eventCount", 0)
			});
		}
		this.removeEvents();
		for(var i = 0; i < this.events.length; ++i) {
			this._showEvent(this.events[i]);
		}
		if(this.eventsRendered) {
			this.eventsRendered(this);
		}
	},

	getDayCell: function(date) {
		var actualDate = date.getDateOnly().getTime();
		for(var i = 0; i < this.days.length; ++i) {
			if(this.days[i].date.getTime() == actualDate) {
				return this.days[i];
			}
		}
		return null;
	},

	render: function() {
		this.removeEvents();
		var content = document.createTextNode("A Render error has occurred (ViewMode " + this.viewMode + ")");
		switch(this.viewMode) {
			case CalendarView.ViewDay:
				content = this._renderDay();
				break;
			case CalendarView.ViewWeek:
				if(this.weekLayout == CalendarView.LayoutVertical) {
					content = this._renderWeekVertical();
				} else {
					content = this._renderWeek();
				}
				break;
			case CalendarView.ViewMonth:
				content = this._renderMonth();
				break;
			case CalendarView.ViewYear:
				if(this.yearLayout == CalendarView.LayoutVertical) {
					content = this._renderYearVertical();
				} else {
					content = this._renderYear();
				}
				break;
		}
		this._clearContainer();
		this.container.appendChild(content);
		this.calendarTable = content;
		this.layoutManager.apply();
		this.layoutManager.performLayout();
		if(this.rendered) {
			this.rendered(this);
		}
	},

	_renderDay: function() {
		var table = document.createElement("table");
		table.cellPadding = "0";
		table.cellSpacing = "0";
		table.border = "0";
		table.className = this.dayViewTableClass;
		var tbody = table.appendChild(document.createElement("tbody"));
		var tr = tbody.appendChild(document.createElement("tr"));

		var td = this._makeCell(tr, this.selectedDate.dateFormat(), "center", "middle", this.dayViewDateHeaderClass);
		td.colSpan = 2;
		td.date = new Date(this.selectedDate);
		this.firstDay = this.selectedDate;
		this.lastDay = this.selectedDate;
		td.tag = "day";

		var time = new Date(this.selectedDate);
		var style = this.dayViewAfterHoursClass;
		for(var i = 0; i < 24; ++i) {
			tr = tbody.appendChild(document.createElement("tr"));
			var td = this._makeCell(tr, time.dateFormat("H:i"), "center", "middle", this.dayViewTimeColumnClass);
			td.date = new Date(time);
			td.tag = "timeColumn";
			td = this._makeCell(tr, " ", "left", "top", style);
			td.date = new Date(time);
			td.tag = "timeSlot";
			time = time.addHours(1);
			if(i > 7 && i < 17) {
				style = this.dayViewBusinessHoursClass;
			} else {
				style = this.dayViewAfterHoursClass;
			}
		}

		return table;
	},

	_renderWeek: function() {
		var table = document.createElement("table");
		table.width = "100%";
		table.cellPadding = "0";
		table.cellSpacing = "0";
		table.border = "0";
		table.className = this.weekViewTableClass;
		var tbody = table.appendChild(document.createElement("tbody"));
		var tr = tbody.appendChild(document.createElement("tr"));

		var week = this.selectedDate.getWeekOf();
		var currentDay = week.start;
		this.firstDay = week.start;
		this.lastDay = week.end;
		this.days = [];

		var style = this.weekViewDayBodyClass;
		for(var i = 0; i < 4; ++i) {
			tr = tbody.appendChild(document.createElement("tr"));
			var td = this._makeCell(tr, currentDay.dateFormat("l, d F Y"), "right", "middle", this.weekViewDayHeaderClass);
			td.date = new Date(currentDay);
			td.tag = "dayHeader";

			if(i < 3) {
				td = this._makeCell(tr, currentDay.addDays(1).dateFormat("l, d M Y"), "right", "middle", this.weekViewDayHeaderClass);
				td.date = currentDay.addDays(1);
				td.tag = "dayHeader";
			}

			if(i == 2) {
				style = this.weekViewWeekendDayBodyClass;
			}

			tr = tbody.appendChild(document.createElement("tr"));
			td = this._makeCell(tr, " ", "left", "top", style);
			td.date = new Date(currentDay);
			td.tag = "dayBody";
			if(i == 2) {
				td.rowSpan = 3;
			}
			this.days.add(td);

			if(i < 3) {
				td = this._makeCell(tr, " ", "left", "top", style);
				td.date = currentDay.addDays(1);
				td.tag = "dayBody";
				this.days.add(td);
			}

			currentDay = currentDay.addDays(2);
		}

		this.showEvents();
		return table;
	},

	_renderWeekVertical: function() {
		var table = document.createElement("table");
		table.width = "100%";
		table.cellPadding = "0";
		table.cellSpacing = "0";
		table.border = "0";
		table.className = this.weekViewTableClass;
		var tbody = table.appendChild(document.createElement("tbody"));
		var tr = tbody.appendChild(document.createElement("tr"));

		var week = this.selectedDate.getWeekOf();
		var currentDay = week.start;
		this.firstDay = week.start;
		this.lastDay = week.end;
		this.days = [];

		for(var i = 0; i < 7; ++i) {
			tr = tbody.appendChild(document.createElement("tr"));
			var td = this._makeCell(tr, currentDay.dateFormat("l, d F Y"), "center", "middle", this.weekViewDayHeaderClass);
			td.date = new Date(currentDay);
			td.tag = "dayHeader";

			tr = tbody.appendChild(document.createElement("tr"));
			td = this._makeCell(tr, " ", "left", "top", this.weekViewDayBodyClass);
			td.date = new Date(currentDay);
			td.tag = "dayBody";
			this.days.add(td);

			currentDay = currentDay.addDays(1);
		}

		this.showEvents();
		return table;
	},

	_renderMonth: function() {
		var table = document.createElement("table");
		table.width = "100%";
		table.cellPadding = "0";
		table.cellSpacing = "0";
		table.border = "0";
		table.className = this.monthViewTableClass;
		table.setAttribute("dock", "fill");

		var month = this.selectedDate.getMonthOf();
		var currentDay = month.start.addDays(-month.start.getDay());
		this.firstDay = currentDay;
		this.days = [];

		var thead = table.appendChild(document.createElement("thead"));
		var tr = thead.appendChild(document.createElement("tr"));
		var td = tr.appendChild(this._makeCell(tr, month.start.dateFormat("F Y"), "center", "middle", this.monthViewDayHeaderClass));
		td.colSpan = 7;
		var tr = thead.appendChild(document.createElement("tr"));
		tr.appendChild(this._makeCell(tr, currentDay.dateFormat("l"), "center", "middle", this.monthViewDayHeaderClass));
		tr.appendChild(this._makeCell(tr, currentDay.addDays(1).dateFormat("l"), "center", "middle", this.monthViewDayHeaderClass));
		tr.appendChild(this._makeCell(tr, currentDay.addDays(2).dateFormat("l"), "center", "middle", this.monthViewDayHeaderClass));
		tr.appendChild(this._makeCell(tr, currentDay.addDays(3).dateFormat("l"), "center", "middle", this.monthViewDayHeaderClass));
		tr.appendChild(this._makeCell(tr, currentDay.addDays(4).dateFormat("l"), "center", "middle", this.monthViewDayHeaderClass));
		tr.appendChild(this._makeCell(tr, currentDay.addDays(5).dateFormat("l"), "center", "middle", this.monthViewDayHeaderClass));
		tr.appendChild(this._makeCell(tr, currentDay.addDays(6).dateFormat("l"), "center", "middle", this.monthViewDayHeaderClass));

		var tbody = table.appendChild(document.createElement("tbody"));
		for(var i = 0; i < 6; ++i) {
			var tr = tbody.appendChild(document.createElement("tr"));
			var day = currentDay;
			for(var ii = 0; ii < 7; ++ii) {
				var className = this.monthViewDayClass;
				if(day < month.start || day > month.end) {
					className = this.monthViewOutsideDayClass;
				}
				var td = this._makeCell(tr, "<div style='text-align:right'>" + day.getDate().toString() + "</div>", "middle", "top", className)
				/*if(day.getTime() == this.selectedDate.getTime()) {
					className = this.daySelectedClass;
					this._selectedDateTd = td;
				}*/
				td.date = new Date(day);
				td.tag = "dayBody";
				day = day.addDays(1);
				this.days.add(td);
			}
			currentDay = currentDay.addDays(7);
		}
		this.lastDay = currentDay.addDays(-1);

		this.showEvents();
		return table;
	},

	_renderYear: function() {
		var table = document.createElement("table");
		table.cellPadding = "0";
		table.cellSpacing = "0";
		table.border = "0";
		table.className = this.yearViewTableClass;

		var year = this.selectedDate.getYearOf();
		this.firstDay = year.start;
		this.lastDay = year.end;
		this.days = [];

		var thead = table.appendChild(document.createElement("thead"));
		var tr = thead.appendChild(document.createElement("tr"));
		tr.appendChild(this._makeCell(tr, year.start.dateFormat("Y"), "center", "middle", this.yearViewDayHeaderClass));
		for(var i = 1; i < 32; ++i) {
			tr.appendChild(this._makeCell(tr, i.toString(), "center", "middle", this.yearViewDayHeaderClass));
		}

		var tbody = table.appendChild(document.createElement("tbody"));
		var month = year.start.getMonthOf();
		for(var i = 0; i < 12; ++i) {
			var tr = tbody.appendChild(document.createElement("tr"));
			tr.appendChild(this._makeCell(tr, month.start.dateFormat("F"), "center", "top", this.yearViewMonthColumnClass));
			for(var ii = 1; ii < 32; ++ii) {
				var className = this.yearViewDayBodyClass;
				if(ii > month.start.daysInMonth()) {
					className = this.yearViewDayOutsideBodyClass;
				}
				var td = tr.appendChild(this._makeCell(tr, "<br />", "center", "top", className));
				if(ii <= month.start.daysInMonth()) {
					td.date = new Date(month.start.addDays(ii - 1));
					td.tag = "dayBody";
					this.days.add(td);
				}
			}
			month = month.end.addDays(1).getMonthOf();
		}

		this.showEvents();
		return table;
	},

	_renderYearVertical: function() {
		var table = document.createElement("table");
		table.cellPadding = "0";
		table.cellSpacing = "0";
		table.border = "0";
		table.className = this.yearViewTableClass;

		var year = this.selectedDate.getYearOf();
		this.firstDay = year.start;
		this.lastDay = year.end;
		this.days = [];

		var thead = table.appendChild(document.createElement("thead"));
		var tr = thead.appendChild(document.createElement("tr"));
		tr.appendChild(this._makeCell(tr, year.start.dateFormat("Y"), "center", "middle", this.yearViewDayHeaderClass));
		var month = year.start.getMonthOf();
		for(var i = 0; i < 12; ++i) {
			tr.appendChild(this._makeCell(tr, month.start.dateFormat("M"), "center", "middle", this.yearViewMonthColumnClass));
			month = month.end.addDays(1).getMonthOf();
		}

		var tbody = table.appendChild(document.createElement("tbody"));
		for(var i = 1; i < 32; ++i) {
			month = year.start.getMonthOf();
			var tr = tbody.appendChild(document.createElement("tr"));
			tr.appendChild(this._makeCell(tr, i.toString(), "center", "middle", this.yearViewDayHeaderClass));
			for(var ii = 0; ii < 12; ++ii) {
				var className = this.yearViewDayBodyClass;
				if(i > month.start.daysInMonth()) {
					className = this.yearViewDayOutsideBodyClass;
				}
				var td = tr.appendChild(this._makeCell(tr, "&nbsp;", "center", "middle", className));
				if(i <= month.start.daysInMonth()) {
					td.date = new Date(month.start.addDays(i - 1));
					td.tag = "dayBody";
					this.days.add(td);
				}
				month = month.end.addDays(1).getMonthOf();
			}
		}

		this.showEvents();
		return table;
	},

	_makeCell: function(tr, content, align, vAlign, className, id) {
		var td = document.createElement("td");
		tr.appendChild(td);
		if(content) {
			if(typeof(content) == "string") {
				Element.update(td, content);
			} else {
				td.appendChild(content);
			}
		}
		if(align) {
			td.align = align;
		}
		if(vAlign) {
			td.vAlign = vAlign;
		}
		if(className) {
			td.className = className;
		}
		if(id) {
			td.id = id;
		}
		return td;
	},

	_clearContainer: function() {
		for(var i = this.container.childNodes.length - 1; i >= 0; --i) {
			this.container.removeChild(this.container.childNodes[i]);
		}
	},

	_getDateCell: function(date, parent, tag) {
		for(var i = 0; i < parent.childNodes.length; ++i) {
			if(parent.childNodes[i].date && parent.childNodes[i].date.getTime() == date.getTime()) {
				if(tag) {
					if(parent.childNodes[i].tag && tag == parent.childNodes[i].tag) {
						return parent.childNodes[i];
					}
				} else {
					return parent.childNodes[i];
				}
			}
			var cell = this._getDateCell(date, parent.childNodes[i], tag);
			if(cell) {
				return cell;
			}
		}
	}
};

CalendarView.ViewDay = 1;
CalendarView.ViewWeek = 2;
CalendarView.ViewMonth = 3;
CalendarView.ViewYear = 4;

CalendarView.LayoutHorizontal = 0;
CalendarView.LayoutVertical = 1;
CalendarView.LayoutTwoColumn = 2;

CalendarView.Event  = Class.create();
CalendarView.Event.prototype = {
	initialize: function(period, title, color, className, due) {
		this.period = period;
		if(due != null) {
			this.due = due;
		} else {
			this.due = period.end;
		}
		this.title = title;
		this.color = color;
		this.className = className;
		this.divs = [];
	},
	addDateTitle: function(date, title, seperator) {
		if(!this.dateTitles) {
			this.dateTitles = [];
		}
		if(this.dateTitles[date.getDateOnly()]) {
			if(seperator) {
				this.dateTitles[date.getDateOnly()] += seperator + title;
			} else {
				this.dateTitles[date.getDateOnly()] = title;
			}
		} else {
			this.dateTitles[date.getDateOnly()] = title;
		}
	}
};ColorPicker = Class.create();
ColorPicker.prototype = {
	initialize: function(attributes) {
		var attr = {
			containerClass: "colorPickerContainer",
			tableClass: "colorPickerTable",
			swatchColorClass: "colorPickerSwatch",
			swatchTransparentClass: "colorPickerTransparent",
			swatchCurrentClass: "colorPickerCurrent",
			colorSwatch: null,
			textField: null,
			color: null, //The actual color, a hex representation
			showTransparent: true,
			/* PopupPanel */
			autoHide: false,
			autoHideTime: 300,
			autoShow: false,
			horizontalAnchor: "left", //farLeft, left, center, right, farRight, null
			verticalAnchor: "farBottom", //farTop, top, middle, bottom, farBottom, null
			element: null // Should always be either the same as colorSwatch or textField
		};
		if(!attributes) {
			attributes = {};
		}
		Object.extend(attr, attributes);
		Object.extend(this, attr);
		this.colorSwatch = $(this.colorSwatch);
		this.textField = $(this.textField);
		if(this.textField != null) {
			if(this.colorSwatch == null) {
				this.colorSwatch = this.textField;
			}
			if(this.element == null) {
				this.element = this.textField;
				attr.element = this.element;
			}
			if(this.color == null) {
				this.color = $F(this.textField);
			}
		}

		this.container = document.body.appendChild(document.createElement("DIV"));
		Element.addClassName(this.container, this.containerClass);
		Element.update(this.container, this._createColorTable());
		this.popup = new PopupPanel(this.container, attr);

		this._showEventListener = this.popup.show.bindAsEventListener(this.popup);
		this._blurEventListener = this._blurred.bind(this);
		this._clickEventListener = this._click.bindAsEventListener(this);

		if(this.colorSwatch) {
			Event.observe(this.colorSwatch, "click", this._showEventListener);
			Event.observe(this.colorSwatch, "blur", this._blurEventListener);
		}
		if(this.textField && this.textField.type != "hidden") {
			Event.observe(this.textField, "focus", this._showEventListener);
			Event.observe(this.textField, "blur", this._blurEventListener);
		}
		Event.observe(this.popup.container, "click", this._clickEventListener);

		this.setColor(this.color);
	},

	setColor: function(color) {
		this.color = Color.tryGetColor(color);
		if(this.textField) {
			if(this.color) {
				this.textField.value = this.color;
			} else {
				this.textField.value = "transparent";
			}
		}
		if(this.colorSwatch) {
			if(color) {
				this.colorSwatch.style.borderColor = "black";
				this.colorSwatch.style.backgroundColor = this.color;
			} else {
				this.colorSwatch.style.borderColor = "#99AADD";
				this.colorSwatch.style.backgroundColor = "#CCCCFF";
			}
		}
		if(this.colorSwatch == this.textField) {
			this.textField.style.color = Color.idealTextColor(this.color);
		}
	},

	destroy: function() {
		if(this.colorSwatch) {
			Event.stopObserving(this.colorSwatch, "click", this._showEventListener);
			Event.stopObserving(this.colorSwatch, "blur", this._blurEventListener);
		}
		if(this.textField && this.textField.type != "hidden") {
			Event.stopObserving(this.textField, "focus", this._showEventListener);
			Event.stopObserving(this.textField, "blur", this._blurEventListener);
		}
		Event.stopObserving(this.popup.container, "click", this._clickEventListener);
		this.popup.destroy();
		document.body.removeChild(this.container);
	},

	_blurred: function() {
		if(this.textField) {
			var color = Color.tryGetColor(this.textField.value);
			if(color) {
				this.setColor(color);
			} else {
				this.setColor(this.color);
			}
		}
		setTimeout(this.popup.hide.bind(this.popup), this.autoHideTime);
	},

	_click: function(event) {
		var target = Event.element(event);
		if(Element.hasClassName(target, this.swatchColorClass)) {
			this.setColor(target.style.backgroundColor);
			this.popup.hide();
		} else if(Element.hasClassName(target, this.swatchTransparentClass)) {
			this.setColor(null);
			this.popup.hide();
		} else if(Element.hasClassName(target, this.swatchCurrentClass)) {
			this.popup.hide();
		}
	},

	_createColorTable: function() {
		var s="<table cellpadding='0' cellspacing='0' border='0' class='" + this.tableClass + "'>";
		var r = 0, g = 0, b = 0;
		for (y = 0; y < 12; y++) {
			s += "<tr>";
			for (x = 0; x < 18; x++) {
				s += "<td style='background:rgb("+r+","+g+","+b+")' class='" + this.swatchColorClass + "'></td>";
				b += 51;
				if (b > 255) {
					b = 0;
					g += 51;
				}
				if (g > 255) {
					b = 0;
					g = 0;
					r += 51;
				}
			}
			s += "</tr>";
		}
		if(this.showTransparent) {
			return s + "<tr><td class='" + this.swatchTransparentClass + "' colspan='9'>Transparent</td><td colspan='9' class='" + this.swatchCurrentClass + "'>Current</td></tr></table>";
		} else {
			return s + "<tr><td colspan='18' class='" + this.swatchCurrentClass + "'>Current</td></tr></table>";
		}
	}
};/*
 * Xml 1.1
 * � 2005-2007 E-Technik Website Development (Pty) Ltd
 * Author:  Marc Heiligers (marc@e-technik.com)
 *
 * Requires prototype.js
 */

Xml = {
	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,
	createDocument: function(rootNode, nameSpace) {
		return Try.these(
			function() {
				var xmlDocument = new ActiveXObject('Msxml2.XMLDOM');
				if (rootNode) {
					var xmlRoot = Xml.createElementNS(xmlDocument, rootNode, nameSpace);
					xmlDocument.documentElement = xmlRoot;
				}
				return xmlDocument;
			},
			function() {
				var xmlDocument = new ActiveXObject('Microsoft.XMLDOM');
				if (rootNode) {
					var xmlRoot = Xml.createElementNS(xmlDocument, rootNode, nameSpace);
					xmlDocument.documentElement = xmlRoot;
				}
				return xmlDocument;
			},
			function() {
				return document.implementation.createDocument(nameSpace, rootNode, null);
			}
		) || false;
	},
	createDocumentFromString: function(xmlString) {
		if(window.ActiveXObject) {
			var xml = this.createDocument();
			xml.loadXML(xmlString);
		} else {
			var parser = new DOMParser();
			var xml = parser.parseFromString(xmlString, "text/xml");
  		}
		return xml;
	},
	createElementNS: function(xmlDocument, name, nameSpace) {
		return Try.these(
			function() {
				if (nameSpace) {
					return xmlDocument.createNode(Xml.ELEMENT_NODE, name, nameSpace);
				} else {
					return xmlDocument.createNode(Xml.ELEMENT_NODE, name, null);
				}
			},
			function() {
				if (nameSpace) {
					return xmlDocument.createElementNS(nameSpace, name);
				} else {
					return xmlDocument.createElement(name);
				}
			}
		) || false;
	},
	createAttributeNS: function(xmlDocument, name, nameSpace, value) {
		var xmlAttribute = Try.these(
			function() {
				return xmlDocument.createNode(Xml.ATTRIBUTE_NODE, name, nameSpace)
			},
			function() {
				return xmlDocument.createAttributeNS( nameSpace, name)
			}
		) || false;
		xmlAttribute.nodeValue = value;
		return xmlAttribute;
	},
	//To add attributes use node.setAttributeNode(attr)
	createAttribute: function(xmlDocument, name, value) {
		var xmlAttribute = Try.these(
			function() {
				return xmlDocument.createNode(Xml.ATTRIBUTE_NODE, name)
			},
			function() {
				return xmlDocument.createAttribute(name)
			}
		) || false;
		xmlAttribute.nodeValue = value;
		return xmlAttribute;
	},
	createText: function(xmlDocument, value) {
		var xmlNode = Try.these(
			function() {
				return xmlDocument.createTextNode(value)
			}
		) || false;
		return xmlNode;
	},
	createCDATA: function(xmlDocument, value) {
		var node = Try.these(function() {
			return xmlDocument.createCDATASection(value)
			}
		) || false;
		return node;
	},
	createNodeFrom: function(xmlDocument, name, nameSpace, value) {
		var xmlElement = this.createElementNS(xmlDocument, name, nameSpace);
		for(v in value) {
			var xmlAttribute = Xml.createAttribute(xmlDocument, v, value[v]);
			xmlElement.setAttributeNode(xmlAttribute);
		}
		return xmlElement;
	}
};

Xml.Form = {
	createDocument: function(form, nullMessage) {
		var xmlDocument = Xml.createDocument("form");
		if(Xml.Form.mergeDocument(xmlDocument, form, nullMessage)) {
			return xmlDocument;
		}
		return null;
	},

	mergeDocument: function(xmlDocument, form, nullMessage) {
		if(!nullMessage) nullMessage = "Not all required fields have been entered.";
		var elements = Form.getElements(form);
		for(var i = 0; i < elements.length; ++i) {
			var element = elements[i];
			if (element.id) {
				var name = element.id;
				if(element.type.toLowerCase() == "radio") {
					if(!element.checked) {
						continue;
					}
					name = element.name;
				}
				var value = $F(element);

				if(element.type.toLowerCase() == "checkbox") {
					if(!element.checked) {
						continue;
					}
				}
				
				if(element.type.toLowerCase() == "textarea") {
					value = element.innerHTML;
				}
				
				if(element.required == "true" && (!value || ("" + value).trim() == "")) {
					alert(nullMessage);
					element.focus();
					return null;
				}
				var xmlElement = Xml.createElementNS(xmlDocument, name);
				var xmlText = Xml.createText(xmlDocument, value);
				xmlElement.appendChild(xmlText);
				xmlDocument.documentElement.appendChild(xmlElement);
			}
		};
		return xmlDocument;
	}
};

Xml.List = {
	createDocument: function(tagName) {
		var xmlDocument = Xml.createDocument("list");
		for(var i = 1; i < arguments.length; ++i) {
			Xml.List.addElement(xmlDocument, tagName, arguments[i]);
		}
		return xmlDocument;
	},
	addElement: function(xmlDocument, tagName, value) {
		var xmlElement = Xml.createElementNS(xmlDocument, tagName);
		var xmlText = Xml.createText(xmlDocument, value);
		xmlElement.appendChild(xmlText);
		xmlDocument.documentElement.appendChild(xmlElement);
	}
};/*
 * Dialog 1.1
 * � 2005-2007 E-Technik Website Development (Pty) Ltd
 * Author:  Marc Heiligers (marc@e-technik.com)
 *
 * Requires core.js
 */

//These class only work in IE.
//TODO: Write dialog handlers for Mozilla
DialogController = Class.create();
DialogController.prototype = {
	initialize: function() {
		this.screenDx = window.screenLeft - parseInt(window.dialogLeft);
		this.screenDy = window.screenTop - parseInt(window.dialogTop);
	},
	resizeTo: function(w, h) {
		var x = window.screenLeft;
		var y = window.screenTop;
		window.dialogWidth = w + "px";
		window.dialogHeight = h + "px";
		window.dialogLeft = x - this.screenDx + "px";
		window.dialogTop = y - this.screenDy + "px";
	},
	resizeBy: function(dw, dh) {
		var x = window.screenLeft;
		var y = window.screenTop;
		window.dialogWidth = parseInt(window.dialogWidth) + dw + "px";
		window.dialogHeight = parseInt(window.dialogHeight) + dh + "px";
		window.dialogLeft = x - this.screenDx + "px";
		window.dialogTop = y - this.screenDy + "px";
	},
	moveTo: function(x, y) {
		window.dialogLeft = x + "px";
		window.dialogTop = y + "px";
	},
	moveBy: function(dx, dy) {
		window.dialogLeft = window.screenLeft - this.screenDx + dx + "px";
		window.dialogTop = window.screenTop - this.screenDy + dy + "px";
	},
	getLeft: function() {
		return window.screenLeft - this.screenDx;
	},
	getTop: function() {
		return window.screenTop - this.screenDy;
	},
	getWidth: function() {
		return parseInt(window.dialogWidth);
	},
	getHeight: function() {
		return parseInt(window.dialogHeight);
	},
	getWindowRect: function() {
		return new Rect(this.getLeft(), this.getTop(), this.getWidth(), this.getHeight());
	}
};

DialogHandler = Class.create();
DialogHandler.prototype = {
	initialize: function(defaultRect, minimizedRect) {
		this.window = window;
		this.document = document;
		this.defaultRect = defaultRect;
		this.minimizedRect = minimizedRect;
		this.currentRect = defaultRect;
		this.dialog = null;
		this.dialogArguments = null;
		Event.observe(window, "unload", this.close.bindAsEventListener(this));
	},
	showModal: function(url, dialogArguments, center, resizable, scroll) {
		this.close();
		if(!dialogArguments) dialogArguments = new Object();
		dialogArguments.dialogHandler = this;
		this.dialog = showModalDialog(url, dialogArguments, "center:" + (center ? "yes" : "no;dialogTop:" + this.currentRect.y + "px;dialogLeft:" + this.currentRect.x + "px") + ";dialogWidth:" + this.currentRect.w + "px;dialogHeight:" + this.currentRect.h + "px;help:no;resizable:" + (resizable ? "yes;" : "no") + ";scroll:" + (scroll ? "yes;" : "no") + ";status:yes;");
		this.dialogArguments = dialogArguments;
		return this.dialog;
	},
	showModeless: function(url, dialogArguments, center, resizable, scroll) {
		this.close();
		if(!dialogArguments) dialogArguments = new Object();
		dialogArguments.dialogHandler = this;
		this.dialog = showModelessDialog(url, dialogArguments, "center:" + (center ? "yes" : "no;dialogTop:" + this.currentRect.y + "px;dialogLeft:" + this.currentRect.x + "px") + ";dialogWidth:" + this.currentRect.w + "px;dialogHeight:" + this.currentRect.h + "px;help:no;resizable:" + (resizable ? "yes;" : "no") + ";scroll:" + (scroll ? "yes;" : "no") + ";status:yes;");
		this.dialogArguments = dialogArguments;
		return this.dialog;
	},
	close: function() {
		try { this.dialog.unload(); } catch(ex) {}
		try { this.dialog.close(); } catch(ex) {}
	},
	setCurrentRect: function(r) {
		this.currentRect = new Rect(r.x, r.y, r.w, r.h);
	}
};
// Copyright (c) 2005 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us)
//
// Permission is hereby granted, free of charge, to any person obtaining
// a copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to
// the following conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

var Scriptaculous = {
  Version: '1.6.1',
  require: function(libraryName) {
    // inserting via DOM fails in Safari 2.0, so brute force approach
    document.write('<script type="text/javascript" src="'+libraryName+'"></script>');
  },
  load: function() {
    if((typeof Prototype=='undefined') ||
       (typeof Element == 'undefined') ||
       (typeof Element.Methods=='undefined') ||
       parseFloat(Prototype.Version.split(".")[0] + "." +
                  Prototype.Version.split(".")[1]) < 1.5)
       throw("script.aculo.us requires the Prototype JavaScript framework >= 1.5.0");

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

//Removed due to all scripts being included in compressed file
//Scriptaculous.load();// Copyright (c) 2005 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us)
//
// See scriptaculous.js for full license.

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

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

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

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

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

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

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

     return element;
  },
  _text: function(text) {
     return document.createTextNode(text);
  },
  _attributes: function(attributes) {
    var attrs = [];
    for(attribute in attributes)
      attrs.push((attribute=='className' ? 'class' : attribute) +
          '="' + attributes[attribute].toString().escapeHTML() + '"');
    return attrs.join(" ");
  },
  _children: function(element, children) {
    if(typeof children=='object') { // array can hold nodes and text
      children.flatten().each( function(e) {
        if(typeof e=='object')
          element.appendChild(e)
        else
          if(Builder._isStringOrNumber(e))
            element.appendChild(Builder._text(e));
      });
    } else
      if(Builder._isStringOrNumber(children))
         element.appendChild(Builder._text(children));
  },
  _isStringOrNumber: function(param) {
    return(typeof param=='string' || typeof param=='number');
  }
};// Copyright (c) 2005 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us)
//           (c) 2005 Ivan Krstic (http://blogs.law.harvard.edu/ivan)
//           (c) 2005 Jon Tirsen (http://www.tirsen.com)
// Contributors:
//  Richard Livsey
//  Rahul Bhargava
//  Rob Wills
//
// See scriptaculous.js for full license.

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

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

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

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

    if (typeof(this.options.tokens) == 'string')
      this.options.tokens = new Array(this.options.tokens);

    this.observer = null;

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

    Element.hide(this.update);

    Event.observe(this.element, "blur", this.onBlur.bindAsEventListener(this));
    Event.observe(this.element, "keypress", this.onKeyPress.bindAsEventListener(this));
  },

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

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

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

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

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

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

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

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

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

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

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

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

  render: function() {
    if(this.entryCount > 0) {
      for (var i = 0; i < this.entryCount; i++)
        this.index==i ?
          Element.addClassName(this.getEntry(i),"selected") :
          Element.removeClassName(this.getEntry(i),"selected");

      if(this.hasFocus) {
        this.show();
        this.active = true;
      }
    } else {
      this.active = false;
      this.hide();
    }
  },

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

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

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

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

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

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

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

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

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

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

      this.stopIndicator();

      this.index = 0;
      this.render();
    }
  },

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

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

  getToken: function() {
    var tokenPos = this.findLastToken();
    if (tokenPos != -1)
      var ret = this.element.value.substr(tokenPos + 1).replace(/^\s+/,'').replace(/\s+$/,'');
    else
      var ret = this.element.value;

    return /\n/.test(ret) ? '' : ret;
  },

  findLastToken: function() {
    var lastTokenPos = -1;

    for (var i=0; i<this.options.tokens.length; i++) {
      var thisTokenPos = this.element.value.lastIndexOf(this.options.tokens[i]);
      if (thisTokenPos > lastTokenPos)
        lastTokenPos = thisTokenPos;
    }
    return lastTokenPos;
  }
};

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

  getUpdatedChoices: function() {
    entry = encodeURIComponent(this.options.paramName) + '=' +
      encodeURIComponent(this.getToken());

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

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

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

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

});

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

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

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

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

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

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

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

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

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

// AJAX in-place editor
//
// see documentation on http://wiki.script.aculo.us/scriptaculous/show/Ajax.InPlaceEditor

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

Ajax.InPlaceEditor = Class.create();
Ajax.InPlaceEditor.defaultHighlightColor = "#FFFF99";
Ajax.InPlaceEditor.prototype = {
  initialize: function(element, url, options) {
    this.url = url;
    this.element = $(element);

    this.options = Object.extend({
      okButton: true,
      okText: "ok",
      cancelLink: true,
      cancelText: "cancel",
      savingText: "Saving...",
      clickToEditText: "Click to edit",
      okText: "ok",
      rows: 1,
      onComplete: function(transport, element) {
        new Effect.Highlight(element, {startcolor: this.options.highlightcolor});
      },
      onFailure: function(transport) {
        alert("Error communicating with the server: " + transport.responseText.stripTags());
      },
      callback: function(form) {
        return Form.serialize(form);
      },
      handleLineBreaks: true,
      loadingText: 'Loading...',
      savingClassName: 'inplaceeditor-saving',
      loadingClassName: 'inplaceeditor-loading',
      formClassName: 'inplaceeditor-form',
      highlightcolor: Ajax.InPlaceEditor.defaultHighlightColor,
      highlightendcolor: "#FFFFFF",
      externalControl: null,
      submitOnBlur: false,
      ajaxOptions: {},
      evalScripts: false
    }, options || {});

    if(!this.options.formId && this.element.id) {
      this.options.formId = this.element.id + "-inplaceeditor";
      if ($(this.options.formId)) {
        // there's already a form with that name, don't specify an id
        this.options.formId = null;
      }
    }

    if (this.options.externalControl) {
      this.options.externalControl = $(this.options.externalControl);
    }

    this.originalBackground = Element.getStyle(this.element, 'background-color');
    if (!this.originalBackground) {
      this.originalBackground = "transparent";
    }

    this.element.title = this.options.clickToEditText;

    this.onclickListener = this.enterEditMode.bindAsEventListener(this);
    this.mouseoverListener = this.enterHover.bindAsEventListener(this);
    this.mouseoutListener = this.leaveHover.bindAsEventListener(this);
    Event.observe(this.element, 'click', this.onclickListener);
    Event.observe(this.element, 'mouseover', this.mouseoverListener);
    Event.observe(this.element, 'mouseout', this.mouseoutListener);
    if (this.options.externalControl) {
      Event.observe(this.options.externalControl, 'click', this.onclickListener);
      Event.observe(this.options.externalControl, 'mouseover', this.mouseoverListener);
      Event.observe(this.options.externalControl, 'mouseout', this.mouseoutListener);
    }
  },
  enterEditMode: function(evt) {
    if (this.saving) return;
    if (this.editing) return;
    this.editing = true;
    this.onEnterEditMode();
    if (this.options.externalControl) {
      Element.hide(this.options.externalControl);
    }
    Element.hide(this.element);
    this.createForm();
    this.element.parentNode.insertBefore(this.form, this.element);
    Field.scrollFreeActivate(this.editField);
    // stop the event to avoid a page refresh in Safari
    if (evt) {
      Event.stop(evt);
    }
    return false;
  },
  createForm: function() {
    this.form = document.createElement("form");
    this.form.id = this.options.formId;
    Element.addClassName(this.form, this.options.formClassName)
    this.form.onsubmit = this.onSubmit.bind(this);

    this.createEditField();

    if (this.options.textarea) {
      var br = document.createElement("br");
      this.form.appendChild(br);
    }

    if (this.options.okButton) {
      okButton = document.createElement("input");
      okButton.type = "submit";
      okButton.value = this.options.okText;
      okButton.className = 'editor_ok_button';
      this.form.appendChild(okButton);
    }

    if (this.options.cancelLink) {
      cancelLink = document.createElement("a");
      cancelLink.href = "#";
      cancelLink.appendChild(document.createTextNode(this.options.cancelText));
      cancelLink.onclick = this.onclickCancel.bind(this);
      cancelLink.className = 'editor_cancel';
      this.form.appendChild(cancelLink);
    }
  },
  hasHTMLLineBreaks: function(string) {
    if (!this.options.handleLineBreaks) return false;
    return string.match(/<br/i) || string.match(/<p>/i);
  },
  convertHTMLLineBreaks: function(string) {
    return string.replace(/<br>/gi, "\n").replace(/<br\/>/gi, "\n").replace(/<\/p>/gi, "\n").replace(/<p>/gi, "");
  },
  createEditField: function() {
    var text;
    if(this.options.loadTextURL) {
      text = this.options.loadingText;
    } else {
      text = this.getText();
    }

    var obj = this;

    if (this.options.rows == 1 && !this.hasHTMLLineBreaks(text)) {
      this.options.textarea = false;
      var textField = document.createElement("input");
      textField.obj = this;
      textField.type = "text";
      textField.name = "value";
      textField.value = text;
      textField.style.backgroundColor = this.options.highlightcolor;
      textField.className = 'editor_field';
      var size = this.options.size || this.options.cols || 0;
      if (size != 0) textField.size = size;
      if (this.options.submitOnBlur)
        textField.onblur = this.onSubmit.bind(this);
      this.editField = textField;
    } else {
      this.options.textarea = true;
      var textArea = document.createElement("textarea");
      textArea.obj = this;
      textArea.name = "value";
      textArea.value = this.convertHTMLLineBreaks(text);
      textArea.rows = this.options.rows;
      textArea.cols = this.options.cols || 40;
      textArea.className = 'editor_field';
      if (this.options.submitOnBlur)
        textArea.onblur = this.onSubmit.bind(this);
      this.editField = textArea;
    }

    if(this.options.loadTextURL) {
      this.loadExternalText();
    }
    this.form.appendChild(this.editField);
  },
  getText: function() {
    return this.element.innerHTML;
  },
  loadExternalText: function() {
    Element.addClassName(this.form, this.options.loadingClassName);
    this.editField.disabled = true;
    new Ajax.Request(
      this.options.loadTextURL,
      Object.extend({
        asynchronous: true,
        onComplete: this.onLoadedExternalText.bind(this)
      }, this.options.ajaxOptions)
    );
  },
  onLoadedExternalText: function(transport) {
    Element.removeClassName(this.form, this.options.loadingClassName);
    this.editField.disabled = false;
    this.editField.value = transport.responseText.stripTags();
  },
  onclickCancel: function() {
    this.onComplete();
    this.leaveEditMode();
    return false;
  },
  onFailure: function(transport) {
    this.options.onFailure(transport);
    if (this.oldInnerHTML) {
      this.element.innerHTML = this.oldInnerHTML;
      this.oldInnerHTML = null;
    }
    return false;
  },
  onSubmit: function() {
    // onLoading resets these so we need to save them away for the Ajax call
    var form = this.form;
    var value = this.editField.value;

    // do this first, sometimes the ajax call returns before we get a chance to switch on Saving...
    // which means this will actually switch on Saving... *after* we've left edit mode causing Saving...
    // to be displayed indefinitely
    this.onLoading();

    if (this.options.evalScripts) {
      new Ajax.Request(
        this.url, Object.extend({
          parameters: this.options.callback(form, value),
          onComplete: this.onComplete.bind(this),
          onFailure: this.onFailure.bind(this),
          asynchronous:true,
          evalScripts:true
        }, this.options.ajaxOptions));
    } else  {
      new Ajax.Updater(
        { success: this.element,
          // don't update on failure (this could be an option)
          failure: null },
        this.url, Object.extend({
          parameters: this.options.callback(form, value),
          onComplete: this.onComplete.bind(this),
          onFailure: this.onFailure.bind(this)
        }, this.options.ajaxOptions));
    }
    // stop the event to avoid a page refresh in Safari
    if (arguments.length > 1) {
      Event.stop(arguments[0]);
    }
    return false;
  },
  onLoading: function() {
    this.saving = true;
    this.removeForm();
    this.leaveHover();
    this.showSaving();
  },
  showSaving: function() {
    this.oldInnerHTML = this.element.innerHTML;
    this.element.innerHTML = this.options.savingText;
    Element.addClassName(this.element, this.options.savingClassName);
    this.element.style.backgroundColor = this.originalBackground;
    Element.show(this.element);
  },
  removeForm: function() {
    if(this.form) {
      if (this.form.parentNode) Element.remove(this.form);
      this.form = null;
    }
  },
  enterHover: function() {
    if (this.saving) return;
    this.element.style.backgroundColor = this.options.highlightcolor;
    if (this.effect) {
      this.effect.cancel();
    }
    Element.addClassName(this.element, this.options.hoverClassName)
  },
  leaveHover: function() {
    if (this.options.backgroundColor) {
      this.element.style.backgroundColor = this.oldBackground;
    }
    Element.removeClassName(this.element, this.options.hoverClassName)
    if (this.saving) return;
    this.effect = new Effect.Highlight(this.element, {
      startcolor: this.options.highlightcolor,
      endcolor: this.options.highlightendcolor,
      restorecolor: this.originalBackground
    });
  },
  leaveEditMode: function() {
    Element.removeClassName(this.element, this.options.savingClassName);
    this.removeForm();
    this.leaveHover();
    this.element.style.backgroundColor = this.originalBackground;
    Element.show(this.element);
    if (this.options.externalControl) {
      Element.show(this.options.externalControl);
    }
    this.editing = false;
    this.saving = false;
    this.oldInnerHTML = null;
    this.onLeaveEditMode();
  },
  onComplete: function(transport) {
    this.leaveEditMode();
    this.options.onComplete.bind(this)(transport, this.element);
  },
  onEnterEditMode: function() {},
  onLeaveEditMode: function() {},
  dispose: function() {
    if (this.oldInnerHTML) {
      this.element.innerHTML = this.oldInnerHTML;
    }
    this.leaveEditMode();
    Event.stopObserving(this.element, 'click', this.onclickListener);
    Event.stopObserving(this.element, 'mouseover', this.mouseoverListener);
    Event.stopObserving(this.element, 'mouseout', this.mouseoutListener);
    if (this.options.externalControl) {
      Event.stopObserving(this.options.externalControl, 'click', this.onclickListener);
      Event.stopObserving(this.options.externalControl, 'mouseover', this.mouseoverListener);
      Event.stopObserving(this.options.externalControl, 'mouseout', this.mouseoutListener);
    }
  }
};

Ajax.InPlaceCollectionEditor = Class.create();
Object.extend(Ajax.InPlaceCollectionEditor.prototype, Ajax.InPlaceEditor.prototype);
Object.extend(Ajax.InPlaceCollectionEditor.prototype, {
  createEditField: function() {
    if (!this.cached_selectTag) {
      var selectTag = document.createElement("select");
      var collection = this.options.collection || [];
      var optionTag;
      collection.each(function(e,i) {
        optionTag = document.createElement("option");
        optionTag.value = (e instanceof Array) ? e[0] : e;
        if(this.options.value==optionTag.value) optionTag.selected = true;
        optionTag.appendChild(document.createTextNode((e instanceof Array) ? e[1] : e));
        selectTag.appendChild(optionTag);
      }.bind(this));
      this.cached_selectTag = selectTag;
    }

    this.editField = this.cached_selectTag;
    if(this.options.loadTextURL) this.loadExternalText();
    this.form.appendChild(this.editField);
    this.options.callback = function(form, value) {
      return "value=" + encodeURIComponent(value);
    }
  }
});

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

Form.Element.DelayedObserver = Class.create();
Form.Element.DelayedObserver.prototype = {
  initialize: function(element, delay, callback) {
    this.delay     = delay || 0.5;
    this.element   = $(element);
    this.callback  = callback;
    this.timer     = null;
    this.lastValue = $F(this.element);
    Event.observe(this.element,'keyup',this.delayedListener.bindAsEventListener(this));
  },
  delayedListener: function(event) {
    if(this.lastValue == $F(this.element)) return;
    if(this.timer) clearTimeout(this.timer);
    this.timer = setTimeout(this.onTimerEvent.bind(this), this.delay * 1000);
    this.lastValue = $F(this.element);
  },
  onTimerEvent: function() {
    this.timer = null;
    this.callback(this.element, $F(this.element));
  }
};
// Copyright (c) 2005 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us)
//           (c) 2005 Sammi Williams (http://www.oriontransfer.co.nz, sammi@oriontransfer.co.nz)
//
// See scriptaculous.js for full license.

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

var Droppables = {
  drops: [],

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

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

    // cache containers
    if(options.containment) {
      options._containers = [];
      var containment = options.containment;
      if((typeof containment == 'object') &&
        (containment.constructor == Array)) {
        containment.each( function(c) { options._containers.push($(c)) });
      } else {
        options._containers.push($(containment));
      }
    }

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

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

    this.drops.push(options);
  },

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

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

    return deepest;
  },

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

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

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

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

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

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

    if(affected.length>0) {
      drop = Droppables.findDeepestChild(affected);
      Position.within(drop.element, point[0], point[1]);
      if(drop.onHover)
        drop.onHover(element, drop.element, Position.overlap(drop.overlap, drop.element));

      Droppables.activate(drop);
    }
  },

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

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

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

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

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

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

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

  activate: function(draggable) {
    window.focus(); // allows keypress events if window isn't currently focused, fails for Safari
    this.activeDraggable = draggable;
  },

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

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

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

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

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

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

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

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

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

var Draggable = Class.create();
Draggable.prototype = {
  initialize: function(element) {
    var options = Object.extend({
      handle: false,
      starteffect: function(element) {
        element._opacity = Element.getOpacity(element);
        new Effect.Opacity(element, {duration:0.2, from:element._opacity, to:0.7});
      },
      reverteffect: function(element, top_offset, left_offset) {
        var dur = Math.sqrt(Math.abs(top_offset^2)+Math.abs(left_offset^2))*0.02;
        element._revert = new Effect.Move(element, { x: -left_offset, y: -top_offset, duration: dur});
      },
      endeffect: function(element) {
        var toOpacity = typeof element._opacity == 'number' ? element._opacity : 1.0
        new Effect.Opacity(element, {duration:0.2, from:0.7, to:toOpacity});
      },
      zindex: 1000,
      revert: false,
      scroll: false,
      scrollSensitivity: 20,
      scrollSpeed: 15,
      snap: false   // false, or xy or [x,y] or function(x,y){ return [x,y] }
    }, arguments[1] || {});

    this.element = $(element);

    if(options.handle && (typeof options.handle == 'string')) {
      var h = Element.childrenWithClassName(this.element, options.handle, true);
      if(h.length>0) this.handle = h[0];
    }
    if(!this.handle) this.handle = $(options.handle);
    if(!this.handle) this.handle = this.element;

    if(options.scroll && !options.scroll.scrollTo && !options.scroll.outerHTML)
      options.scroll = $(options.scroll);

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

    this.delta    = this.currentDelta();
    this.options  = options;
    this.dragging = false;

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

    Draggables.register(this);
  },

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

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

  initDrag: function(event) {
    if(Event.isLeftClick(event)) {
      // abort on form elements, fixes a Firefox issue
      var src = Event.element(event);
      if(src.tagName && (
        src.tagName=='INPUT' ||
        src.tagName=='SELECT' ||
        src.tagName=='OPTION' ||
        src.tagName=='BUTTON' ||
        src.tagName=='TEXTAREA')) return;

      if(this.element._revert) {
        this.element._revert.cancel();
        this.element._revert = null;
      }

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

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

  startDrag: function(event) {
    this.dragging = true;

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

    if(this.options.ghosting) {
      this._clone = this.element.cloneNode(true);
      Position.absolutize(this.element);
      this.element.parentNode.insertBefore(this._clone, this.element);
    }

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

    Draggables.notify('onStart', this, event);
    if(this.options.starteffect) this.options.starteffect(this.element);
  },

  updateDrag: function(event, pointer) {
    if(!this.dragging) this.startDrag(event);
    Position.prepare();
    Droppables.show(pointer, this.element);
    Draggables.notify('onDrag', this, event);
    this.draw(pointer);
    if(this.options.change) this.options.change(this);

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

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

    // fix AppleWebKit rendering
    if(navigator.appVersion.indexOf('AppleWebKit')>0) window.scrollBy(0,0);

    Event.stop(event);
  },

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

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

    if(success) Droppables.fire(event, this.element);
    Draggables.notify('onEnd', this, event);

    var revert = this.options.revert;
    if(revert && typeof revert == 'function') revert = revert(this.element);

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

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

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

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

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

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

  draw: function(point) {
    var pos = Position.cumulativeOffset(this.element);
    var d = this.currentDelta();
    pos[0] -= d[0]; pos[1] -= d[1];

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

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

    if(this.options.snap) {
      if(typeof this.options.snap == 'function') {
        p = this.options.snap(p[0],p[1],this);
      } else {
      if(this.options.snap instanceof Array) {
        p = p.map( function(v, i) {
          return Math.round(v/this.options.snap[i])*this.options.snap[i] }.bind(this))
      } else {
        p = p.map( function(v) {
          return Math.round(v/this.options.snap)*this.options.snap }.bind(this))
      }
    }}

    var style = this.element.style;
    if((!this.options.constraint) || (this.options.constraint=='horizontal'))
      style.left = p[0] + "px";
    if((!this.options.constraint) || (this.options.constraint=='vertical'))
      style.top  = p[1] + "px";
    if(style.visibility=="hidden") style.visibility = ""; // fix gecko rendering
  },

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

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

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

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

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

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

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

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

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

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

var Sortable = {
  sortables: {},

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

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

  destroy: function(element){
    var s = Sortable.options(element);

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

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

  create: function(element) {
    element = $(element);
    var options = Object.extend({
      element:     element,
      tag:         'li',       // assumes li children, override with tag: 'tagname'
      dropOnEmpty: false,
      tree:        false,
      treeTag:     'ul',
      overlap:     'vertical', // one of 'vertical', 'horizontal'
      constraint:  'vertical', // one of 'vertical', 'horizontal', false
      containment: element,    // also takes array of elements (or id's); or false
      handle:      false,      // or a CSS class
      only:        false,
      hoverclass:  null,
      ghosting:    false,
      scroll:      false,
      scrollSensitivity: 20,
      scrollSpeed: 15,
      format:      /^[^_]*_(.*)$/,
      onChange:    Prototype.emptyFunction,
      onUpdate:    Prototype.emptyFunction
    }, arguments[1] || {});

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

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

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

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

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

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

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

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

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

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

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

    (this.findElements(element, options) || []).each( function(e) {
      // handles are per-draggable
      var handle = options.handle ?
        Element.childrenWithClassName(e, options.handle)[0] : e;
      options.draggables.push(
        new Draggable(e, Object.extend(options_for_draggable, { handle: handle })));
      Droppables.add(e, options_for_droppable);
      if(options.tree) e.treeNode = element;
      options.droppables.push(e);
    });

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

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

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

  },

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

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

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

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

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

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

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

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

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

      dropon.insertBefore(element, child);

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

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

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

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

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

    Element.show(Sortable._marker);
  },

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

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

      if (!match) continue;

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

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

      parent.children.push (child);
    }

    return parent;
  },

  /* Finds the first element of the given tag type within a parent element.
    Used for finding the first LI[ST] within a L[IST]I[TEM].*/
  _findChildrenElement: function (element, containerTag) {
    if (element && element.hasChildNodes)
      for (var i = 0; i < element.childNodes.length; ++i)
        if (element.childNodes[i].tagName == containerTag)
          return element.childNodes[i];

    return null;
  },

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

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

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

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

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

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

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

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

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

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

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

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

  if (child.parentNode == element) return true;

  return Element.isParent(child.parentNode, element);
};

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

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

Element.offsetSize = function (element, type) {
  if (type == 'vertical' || type == 'height')
    return element.offsetHeight;
  else
    return element.offsetWidth;
};// Copyright (c) 2005 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us)
// Contributors:
//  Justin Palmer (http://encytemedia.com/)
//  Mark Pilgrim (http://diveintomark.org/)
//  Martin Bialasinki
//
// See scriptaculous.js for full license.

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

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

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

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

Element.setContentZoom = function(element, percent) {
  element = $(element);
  Element.setStyle(element, {fontSize: (percent/100) + 'em'});
  if(navigator.appVersion.indexOf('AppleWebKit')>0) window.scrollBy(0,0);
};

Element.getOpacity = function(element){
  var opacity;
  if (opacity = Element.getStyle(element, 'opacity'))
    return parseFloat(opacity);
  if (opacity = (Element.getStyle(element, 'filter') || '').match(/alpha\(opacity=(.*)\)/))
    if(opacity[1]) return parseFloat(opacity[1]) / 100;
  return 1.0;
};

Element.setOpacity = function(element, value){
  element= $(element);
  if (value == 1){
    Element.setStyle(element, { opacity:
      (/Gecko/.test(navigator.userAgent) && !/Konqueror|Safari|KHTML/.test(navigator.userAgent)) ?
      0.999999 : null });
    if(/MSIE/.test(navigator.userAgent))
      Element.setStyle(element, {filter: Element.getStyle(element,'filter').replace(/alpha\([^\)]*\)/gi,'')});
  } else {
    if(value < 0.00001) value = 0;
    Element.setStyle(element, {opacity: value});
    if(/MSIE/.test(navigator.userAgent))
     Element.setStyle(element,
       { filter: Element.getStyle(element,'filter').replace(/alpha\([^\)]*\)/gi,'') +
                 'alpha(opacity='+value*100+')' });
  }
};

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

Element.childrenWithClassName = function(element, className, findFirst) {
  var classNameRegExp = new RegExp("(^|\\s)" + className + "(\\s|$)");
  var results = $A($(element).getElementsByTagName('*'))[findFirst ? 'detect' : 'select']( function(c) {
    return (c.className && c.className.match(classNameRegExp));
  });
  if(!results) results = [];
  return results;
};

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

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

Array.prototype.call = function() {
  var args = arguments;
  this.each(function(f){ f.apply(this, args) });
};

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

var Effect = {
  tagifyText: function(element) {
    var tagifyStyle = 'position:relative';
    if(/MSIE/.test(navigator.userAgent)) tagifyStyle += ';zoom:1';
    element = $(element);
    $A(element.childNodes).each( function(child) {
      if(child.nodeType==3) {
        child.nodeValue.toArray().each( function(character) {
          element.insertBefore(
            Builder.node('span',{style: tagifyStyle},
              character == ' ' ? String.fromCharCode(160) : character),
              child);
        });
        Element.remove(child);
      }
    });
  },
  multiple: function(element, effect) {
    var elements;
    if(((typeof element == 'object') ||
        (typeof element == 'function')) &&
       (element.length))
      elements = element;
    else
      elements = $(element).childNodes;

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

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

var Effect2 = Effect; // deprecated

/* ------------- transitions ------------- */

Effect.Transitions = {};

Effect.Transitions.linear = function(pos) {
  return pos;
};
Effect.Transitions.sinoidal = function(pos) {
  return (-Math.cos(pos*Math.PI)/2) + 0.5;
};
Effect.Transitions.reverse  = function(pos) {
  return 1-pos;
};
Effect.Transitions.flicker = function(pos) {
  return ((-Math.cos(pos*Math.PI)/4) + 0.75) + Math.random()/4;
};
Effect.Transitions.wobble = function(pos) {
  return (-Math.cos(pos*Math.PI*(9*pos))/2) + 0.5;
};
Effect.Transitions.pulse = function(pos) {
  return (Math.floor(pos*10) % 2 == 0 ?
    (pos*10-Math.floor(pos*10)) : 1-(pos*10-Math.floor(pos*10)));
};
Effect.Transitions.none = function(pos) {
  return 0;
};
Effect.Transitions.full = function(pos) {
  return 1;
};

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

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

    var position = (typeof effect.options.queue == 'string') ?
      effect.options.queue : effect.options.queue.position;

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

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

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

    if(!this.interval)
      this.interval = setInterval(this.loop.bind(this), 40);
  },
  remove: function(effect) {
    this.effects = this.effects.reject(function(e) { return e==effect });
    if(this.effects.length == 0) {
      clearInterval(this.interval);
      this.interval = null;
    }
  },
  loop: function() {
    var timePos = new Date().getTime();
    this.effects.invoke('loop', timePos);
  }
});

Effect.Queues = {
  instances: $H(),
  get: function(queueName) {
    if(typeof queueName != 'string') return queueName;

    if(!this.instances[queueName])
      this.instances[queueName] = new Effect.ScopedQueue();

    return this.instances[queueName];
  }
};
Effect.Queue = Effect.Queues.get('global');

Effect.DefaultOptions = {
  transition: Effect.Transitions.sinoidal,
  duration:   1.0,   // seconds
  fps:        25.0,  // max. 25fps due to Effect.Queue implementation
  sync:       false, // true for combining
  from:       0.0,
  to:         1.0,
  delay:      0.0,
  queue:      'parallel'
};

Effect.Base = function() {};
Effect.Base.prototype = {
  position: null,
  start: function(options) {
    this.options      = Object.extend(Object.extend({},Effect.DefaultOptions), options || {});
    this.currentFrame = 0;
    this.state        = 'idle';
    this.startOn      = this.options.delay*1000;
    this.finishOn     = this.startOn + (this.options.duration*1000);
    this.event('beforeStart');
    if(!this.options.sync)
      Effect.Queues.get(typeof this.options.queue == 'string' ?
        'global' : this.options.queue.scope).add(this);
  },
  loop: function(timePos) {
    if(timePos >= this.startOn) {
      if(timePos >= this.finishOn) {
        this.render(1.0);
        this.cancel();
        this.event('beforeFinish');
        if(this.finish) this.finish();
        this.event('afterFinish');
        return;
      }
      var pos   = (timePos - this.startOn) / (this.finishOn - this.startOn);
      var frame = Math.round(pos * this.options.fps * this.options.duration);
      if(frame > this.currentFrame) {
        this.render(pos);
        this.currentFrame = frame;
      }
    }
  },
  render: function(pos) {
    if(this.state == 'idle') {
      this.state = 'running';
      this.event('beforeSetup');
      if(this.setup) this.setup();
      this.event('afterSetup');
    }
    if(this.state == 'running') {
      if(this.options.transition) pos = this.options.transition(pos);
      pos *= (this.options.to-this.options.from);
      pos += this.options.from;
      this.position = pos;
      this.event('beforeUpdate');
      if(this.update) this.update(pos);
      this.event('afterUpdate');
    }
  },
  cancel: function() {
    if(!this.options.sync)
      Effect.Queues.get(typeof this.options.queue == 'string' ?
        'global' : this.options.queue.scope).remove(this);
    this.state = 'finished';
  },
  event: function(eventName) {
    if(this.options[eventName + 'Internal']) this.options[eventName + 'Internal'](this);
    if(this.options[eventName]) this.options[eventName](this);
  },
  inspect: function() {
    return '#<Effect:' + $H(this).inspect() + ',options:' + $H(this.options).inspect() + '>';
  }
};

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

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

Effect.Move = Class.create();
Object.extend(Object.extend(Effect.Move.prototype, Effect.Base.prototype), {
  initialize: function(element) {
    this.element = $(element);
    var options = Object.extend({
      x:    0,
      y:    0,
      mode: 'relative'
    }, arguments[1] || {});
    this.start(options);
  },
  setup: function() {
    // Bug in Opera: Opera returns the "real" position of a static element or
    // relative element that does not have top/left explicitly set.
    // ==> Always set top and left for position relative elements in your stylesheets
    // (to 0 if you do not need them)
    this.element.makePositioned();
    this.originalLeft = parseFloat(this.element.getStyle('left') || '0');
    this.originalTop  = parseFloat(this.element.getStyle('top')  || '0');
    if(this.options.mode == 'absolute') {
      // absolute movement, so we need to calc deltaX and deltaY
      this.options.x = this.options.x - this.originalLeft;
      this.options.y = this.options.y - this.originalTop;
    }
  },
  update: function(position) {
    this.element.setStyle({
      left: this.options.x  * position + this.originalLeft + 'px',
      top:  this.options.y  * position + this.originalTop  + 'px'
    });
  }
});

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

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

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

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

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

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

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

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

Effect.ScrollTo = Class.create();
Object.extend(Object.extend(Effect.ScrollTo.prototype, Effect.Base.prototype), {
  initialize: function(element) {
    this.element = $(element);
    this.start(arguments[1] || {});
  },
  setup: function() {
    Position.prepare();
    var offsets = Position.cumulativeOffset(this.element);
    if(this.options.offset) offsets[1] += this.options.offset;
    var max = window.innerHeight ?
      window.height - window.innerHeight :
      document.body.scrollHeight -
        (document.documentElement.clientHeight ?
          document.documentElement.clientHeight : document.body.clientHeight);
    this.scrollStart = Position.deltaY;
    this.delta = (offsets[1] > max ? max : offsets[1]) - this.scrollStart;
  },
  update: function(position) {
    Position.prepare();
    window.scrollTo(Position.deltaX,
      this.scrollStart + (position*this.delta));
  }
});

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

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

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

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

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

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

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

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

Effect.Shake = function(element) {
  element = $(element);
  var oldStyle = {
    top: element.getStyle('top'),
    left: element.getStyle('left') };
    return new Effect.Move(element,
      { x:  20, y: 0, duration: 0.05, afterFinishInternal: function(effect) {
    new Effect.Move(effect.element,
      { x: -40, y: 0, duration: 0.1,  afterFinishInternal: function(effect) {
    new Effect.Move(effect.element,
      { x:  40, y: 0, duration: 0.1,  afterFinishInternal: function(effect) {
    new Effect.Move(effect.element,
      { x: -40, y: 0, duration: 0.1,  afterFinishInternal: function(effect) {
    new Effect.Move(effect.element,
      { x:  40, y: 0, duration: 0.1,  afterFinishInternal: function(effect) {
    new Effect.Move(effect.element,
      { x: -20, y: 0, duration: 0.05, afterFinishInternal: function(effect) {
        effect.element.undoPositioned();
        effect.element.setStyle(oldStyle);
  }}) }}) }}) }}) }}) }});
};

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

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

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

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

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

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

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

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

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

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

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

Effect.Pulsate = function(element) {
  element = $(element);
  var options    = arguments[1] || {};
  var oldOpacity = element.getInlineOpacity();
  var transition = options.transition || Effect.Transitions.sinoidal;
  var reverser   = function(pos){ return transition(1-Effect.Transitions.pulse(pos)) };
  reverser.bind(transition);
  return new Effect.Opacity(element,
    Object.extend(Object.extend({  duration: 3.0, from: 0,
      afterFinishInternal: function(effect) { effect.element.setStyle({opacity: oldOpacity}); }
    }, options), {transition: reverser}));
};

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

['setOpacity','getOpacity','getInlineOpacity','forceRerendering','setContentZoom',
 'collectTextNodes','collectTextNodesIgnoreClass','childrenWithClassName'].each(
  function(f) { Element.Methods[f] = Element[f]; }
);

Element.Methods.visualEffect = function(element, effect, options) {
  s = effect.gsub(/_/, '-').camelize();
  effect_class = s.charAt(0).toUpperCase() + s.substring(1);
  new Effect[effect_class](element, options);
  return $(element);
};

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

if(!Control) var Control = {};
Control.Slider = Class.create();

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

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

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

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

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

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

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

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

    this.trackLength = this.maximumOffset() - this.minimumOffset();
    this.handleLength = this.isVertical() ? this.handles[0].offsetHeight : this.handles[0].offsetWidth;

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

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

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

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

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

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

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

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

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

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

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

          this.activeHandle    = handle;
          this.activeHandleIdx = this.handles.indexOf(this.activeHandle);
          this.updateStyles();

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

