/*  prototype javascript framework, version 1.4.0
 *  (c) 2005 sam stephenson <sam@conio.net>
 *
 *  this file is automatically generated. when sending patches, please diff
 *  against the source tree, available from the prototype darcs repository.
 *
 *  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.4.0',
  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 (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;
      }
    }
  }
}

/*--------------------------------------------------------------------------*/

function $() {
  var elements = new array();

  for (var i = 0; i < arguments.length; i++) {
    var element = arguments[i];
    if (typeof element == 'string')
      element = document.getelementbyid(element);

    if (arguments.length == 1)
      return element;

    elements.push(element);
  }

  return elements;
}
object.extend(string.prototype, {
  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(eval);
  },

  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('\\', '\\\\').replace("'", '\\\'') + "'";
  }
});

string.prototype.parsequery = string.prototype.toqueryparams;

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 (value >= (result || value))
        result = value;
    });
    return result;
  },

  min: function(iterator) {
    var result;
    this.each(function(value, index) {
      value = (iterator || prototype.k)(value, index);
      if (value <= (result || value))
        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) {
      iterator(value = collections.pluck(index));
      return value;
    });
  },

  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);

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.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();
  },

  shift: function() {
    var result = this[0];
    for (var i = 0; i < this.length - 1; i++)
      this[i] = this[i + 1];
    this.length--;
    return result;
  },

  inspect: function() {
    return '[' + this.map(object.inspect).join(', ') + ']';
  }
});
var hash = {
  _each: function(iterator) {
    for (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 activexobject('msxml2.xmlhttp')},
      function() {return new activexobject('microsoft.xmlhttp')},
      function() {return new xmlhttprequest()}
    ) || 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,
      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];

    if (this.options.method == 'post') {
      requestheaders.push('content-type',
        'application/x-www-form-urlencoded');

      /* 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);
  }
});
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(child);
    return elements;
  });
}

/*--------------------------------------------------------------------------*/

if (!window.element) {
  var element = new object();
}

object.extend(element, {
  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);
  },

  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*$/);
  },

  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 (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 = '';
    var originalwidth = element.clientwidth;
    var originalheight = element.clientheight;
    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;
  }
});

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) {
        if (this.element.tagname.tolowercase() == 'tbody') {
          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 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 (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;
      if (!value && !('value' in opt))
        value = opt.text;
    }
    return [element.name, value];
  },

  selectmany: function(element) {
    var value = new array();
    for (var i = 0; i < element.length; i++) {
      var opt = element.options[i];
      if (opt.selected) {
        var optvalue = opt.value;
        if (!optvalue && !('value' in opt))
          optvalue = opt.text;
        value.push(optvalue);
      }
    }
    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 */
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];
  }
}







