var theDoc=document, theAll=(theDoc.all)?theDoc.all:null;
function ShowHideLayer(name, state) {
	if (theAll==null) return;
	if (arguments.length<2) state=theAll[name].style.display=="none";
	theAll[name].style.display=(state)?"block":"none";
}
function ButtonClick(but, name, desc1, desc2) {
	var state=but.value==desc1;
	but.value=(state)?desc2:desc1;
	ShowHideLayer(name,!state);
}
function addShowHideButton(name, desc1, desc2, state) {
	if (theAll==null) return;
	theDoc.write('<form><input type="button" value="'+(state?desc1:desc2)+'" onclick="ButtonClick(this, \''+name+'\',\''+desc1+'\',\''+desc2+'\');" /></form>');
}	

/*  Prototype JavaScript framework, version 1.3.1
 *  (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.3.1',
  emptyFunction: function() {}
}

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.prototype.extend = function(object) {
  return Object.extend.apply(this, [this, object]);
}

Function.prototype.bind = function(object) {
  var __method = this;
  return function() {
    __method.apply(object, arguments);
  }
}

Function.prototype.bindAsEventListener = function(object) {
  var __method = this;
  return function(event) {
    __method.call(object, event || window.event);
  }
}

Number.prototype.toColorPart = function() {
  var digits = this.toString(16);
  if (this < 16) return '0' + digits;
  return digits;
}

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;
}

if (!Array.prototype.push) {
  Array.prototype.push = function() {
		var startLength = this.length;
		for (var i = 0; i < arguments.length; i++)
      this[startLength + i] = arguments[i];
	  return this.length;
  }
}

if (!Function.prototype.apply) {
  // Based on code from http://www.youngpup.net/
  Function.prototype.apply = function(object, parameters) {
    var parameterStrings = new Array();
    if (!object)     object = window;
    if (!parameters) parameters = new Array();
    
    for (var i = 0; i < parameters.length; i++)
      parameterStrings[i] = 'parameters[' + i + ']';
    
    object.__apply__ = this;
    var result = eval('object.__apply__(' + 
      parameterStrings.join(', ') + ')');
    object.__apply__ = null;
    
    return result;
  }
}

String.prototype.extend({
  stripTags: function() {
    return this.replace(/<\/?[^>]+>/gi, '');
  },

  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].nodeValue;
  }
});

var Ajax = {
  getTransport: function() {
    return Try.these(
      function() {return new ActiveXObject('Msxml2.XMLHTTP')},
      function() {return new ActiveXObject('Microsoft.XMLHTTP')},
      function() {return new XMLHttpRequest()}
    ) || false;
  }
}

Ajax.Base = function() {};
Ajax.Base.prototype = {
  setOptions: function(options) {
    this.options = {
      method:       'post',
      asynchronous: true,
      parameters:   ''
    }.extend(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 = (new Ajax.Base()).extend({
  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 {
      if (this.options.method == 'get')
        url += '?' + parameters;

      this.transport.open(this.options.method, 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) {
    }
  },

  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);
  },

  respondToReadyState: function(readyState) {
    var event = Ajax.Request.Events[readyState];

    if (event == 'Complete')
      (this.options['on' + this.transport.status]
       || this.options['on' + (this.responseIsSuccess() ? 'Success' : 'Failure')]
       || Prototype.emptyFunction)(this.transport);

    (this.options['on' + event] || Prototype.emptyFunction)(this.transport);

    /* Avoid memory leak in MSIE: clean up the oncomplete event handler */
    if (event == 'Complete')
      this.transport.onreadystatechange = Prototype.emptyFunction;
  }
});

Ajax.Updater = Class.create();
Ajax.Updater.ScriptFragment = '(?:<script.*?>)((\n|.)*?)(?:<\/script>)';

Ajax.Updater.prototype.extend(Ajax.Request.prototype).extend({
  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() {
      this.updateContent();
      onComplete(this.transport);
    }).bind(this);

    this.request(url);
  },

  updateContent: function() {
    var receiver = this.responseIsSuccess() ?
      this.containers.success : this.containers.failure;

    var match    = new RegExp(Ajax.Updater.ScriptFragment, 'img');
    var response = this.transport.responseText.replace(match, '');
    var scripts  = this.transport.responseText.match(match);

    if (receiver) {
      if (this.options.insertion) {
        new this.options.insertion(receiver, response);
      } else {
        receiver.innerHTML = response;
      }
    }

    if (this.responseIsSuccess()) {
      if (this.onComplete)
        setTimeout((function() {this.onComplete(
          this.transport)}).bind(this), 10);
    }

    if (this.options.evalScripts && scripts) {
      match = new RegExp(Ajax.Updater.ScriptFragment, 'im');
      setTimeout((function() {
        for (var i = 0; i < scripts.length; i++)
          eval(scripts[i].match(match)[1]);
      }).bind(this), 10);
    }
  }
});

Ajax.PeriodicalUpdater = Class.create();
Ajax.PeriodicalUpdater.prototype = (new Ajax.Base()).extend({
  initialize: function(container, url, options) {
    this.setOptions(options);
    this.onComplete = this.options.onComplete;

    this.frequency = (this.options.frequency || 2);
    this.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 || Ajax.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) {
  var children = document.getElementsByTagName('*') || document.all;
  var elements = new Array();
  
  for (var i = 0; i < children.length; i++) {
    var child = children[i];
    var classNames = child.className.split(' ');
    for (var j = 0; j < classNames.length; j++) {
      if (classNames[j] == className) {
        elements.push(child);
        break;
      }
    }
  }
  
  return elements;
}

/*--------------------------------------------------------------------------*/

if (!window.Element) {
  var Element = new Object();
}

Object.extend(Element, {
  toggle: function() {
    for (var i = 0; i < arguments.length; i++) {
      var element = $(arguments[i]);
      element.style.display = 
        (element.style.display == 'none' ? '' : 'none');
    }
  },

  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);
  },
   
  getHeight: function(element) {
    element = $(element);
    return element.offsetHeight; 
  },

  hasClassName: function(element, className) {
    element = $(element);
    if (!element)
      return;
    var a = element.className.split(' ');
    for (var i = 0; i < a.length; i++) {
      if (a[i] == className)
        return true;
    }
    return false;
  },

  addClassName: function(element, className) {
    element = $(element);
    Element.removeClassName(element, className);
    element.className += ' ' + className;
  },

  removeClassName: function(element, className) {
    element = $(element);
    if (!element)
      return;
    var newClassName = '';
    var a = element.className.split(' ');
    for (var i = 0; i < a.length; i++) {
      if (a[i] != className) {
        if (i > 0)
          newClassName += ' ';
        newClassName += a[i];
      }
    }
    element.className = newClassName;
  },
  
  // removes whitespace-only text node children
  cleanWhitespace: function(element) {
    var 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);
    }
  }
});

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;
    
    if (this.adjacency && this.element.insertAdjacentHTML) {
      this.element.insertAdjacentHTML(this.adjacency, this.content);
    } else {
      this.range = this.element.ownerDocument.createRange();
      if (this.initializeRange) this.initializeRange();
      this.fragment = this.range.createContextualFragment(this.content);
      this.insertContent();
    }
  }
}

var Insertion = new Object();

Insertion.Before = Class.create();
Insertion.Before.prototype = (new Abstract.Insertion('beforeBegin')).extend({
  initializeRange: function() {
    this.range.setStartBefore(this.element);
  },
  
  insertContent: function() {
    this.element.parentNode.insertBefore(this.fragment, this.element);
  }
});

Insertion.Top = Class.create();
Insertion.Top.prototype = (new Abstract.Insertion('afterBegin')).extend({
  initializeRange: function() {
    this.range.selectNodeContents(this.element);
    this.range.collapse(true);
  },
  
  insertContent: function() {  
    this.element.insertBefore(this.fragment, this.element.firstChild);
  }
});

Insertion.Bottom = Class.create();
Insertion.Bottom.prototype = (new Abstract.Insertion('beforeEnd')).extend({
  initializeRange: function() {
    this.range.selectNodeContents(this.element);
    this.range.collapse(this.element);
  },
  
  insertContent: function() {
    this.element.appendChild(this.fragment);
  }
});

Insertion.After = Class.create();
Insertion.After.prototype = (new Abstract.Insertion('afterEnd')).extend({
  initializeRange: function() {
    this.range.setStartAfter(this.element);
  },
  
  insertContent: function() {
    this.element.parentNode.insertBefore(this.fragment, 
      this.element.nextSibling);
  }
});

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).focus();
    $(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) {
    var 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) {
    var 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 = '';
    }
  },

  focusFirstElement: function(form) {
    var form = $(form);
    var elements = Form.getElements(form);
    for (var i = 0; i < elements.length; i++) {
      var element = elements[i];
      if (element.type != 'hidden' && !element.disabled) {
        Field.activate(element);
        break;
      }
    }
  },

  reset: function(form) {
    $(form).reset();
  }
}

Form.Element = {
  serialize: function(element) {
    var element = $(element);
    var method = element.tagName.toLowerCase();
    var parameter = Form.Element.Serializers[method](element);
    
    if (parameter)
      return encodeURIComponent(parameter[0]) + '=' + 
        encodeURIComponent(parameter[1]);                   
  },
  
  getValue: function(element) {
    var 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) {
    var value = '';
    if (element.type == 'select-one') {
      var index = element.selectedIndex;
      if (index >= 0)
        value = element.options[index].value || element.options[index].text;
    } else {
      value = new Array();
      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 = (new Abstract.TimedObserver()).extend({
  getValue: function() {
    return Form.Element.getValue(this.element);
  }
});

Form.Observer = Class.create();
Form.Observer.prototype = (new Abstract.TimedObserver()).extend({
  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':
          element.target = this;
          element.prev_onclick = element.onclick || Prototype.emptyFunction;
          element.onclick = function() {
            this.prev_onclick(); 
            this.target.onElementEvent();
          }
          break;
        case 'password':
        case 'text':
        case 'textarea':
        case 'select-one':
        case 'select-multiple':
          element.target = this;
          element.prev_onchange = element.onchange || Prototype.emptyFunction;
          element.onchange = function() {
            this.prev_onchange(); 
            this.target.onElementEvent();
          }
          break;
      }
    }    
  }
}

Form.Element.EventObserver = Class.create();
Form.Element.EventObserver.prototype = (new Abstract.EventObserver()).extend({
  getValue: function() {
    return Form.Element.getValue(this.element);
  }
});

Form.EventObserver = Class.create();
Form.EventObserver.prototype = (new Abstract.EventObserver()).extend({
  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;
    }
  },

  // 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.indexOf('AppleWebKit') > 0) 
        || 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.indexOf('AppleWebKit') > 0) 
        || 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];
  },

  // 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';
  }
}

/**
 * Copyright 2005 Darren L. Spurgeon
 * 
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 * http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

var AjaxJspTag = {
  Version: '1.1.5'
}

var isSafari = false;
var isMoz = false;
var isIE = false;

if (navigator.userAgent.indexOf("Safari") > 0) {
  isSafari = true;
  isMoz = false;
  isIE = false;
}
else if (navigator.product == "Gecko") {
  isSafari = false;
  isMoz = true;
  isIE = false;
} else {
  isSafari = false;
  isMoz = false;
  isIE = true;
}

AJAX_METHOD_UPDATER = "updater";
AJAX_METHOD_REQUEST = "request";

AJAX_DEFAULT_PARAMETER = "ajaxParameter";

AJAX_PORTLET_MAX = 1;
AJAX_PORTLET_MIN = 2;
AJAX_PORTLET_CLOSE = 3;


/* ---------------------------------------------------------------------- */
/* Example File From "_JavaScript and DHTML Cookbook"
   Published by O'Reilly & Associates
   Copyright 2003 Danny Goodman
*/

// utility function to retrieve a future expiration date in proper format;
// pass three integer parameters for the number of days, hours,
// and minutes from now you want the cookie to expire; all three
// parameters required, so use zeros where appropriate
function getExpDate(days, hours, minutes) {
  var expDate = new Date();
  if (typeof days == "number" && typeof hours == "number" && typeof hours == "number") {
    expDate.setDate(expDate.getDate() + parseInt(days));
    expDate.setHours(expDate.getHours() + parseInt(hours));
    expDate.setMinutes(expDate.getMinutes() + parseInt(minutes));
    return expDate.toGMTString();
  }
}

// utility function called by getCookie()
function getCookieVal(offset) {
  var endstr = document.cookie.indexOf (";", offset);
  if (endstr == -1) {
    endstr = document.cookie.length;
  }
  return unescape(document.cookie.substring(offset, endstr));
}

// primary function to retrieve cookie by name
function getCookie(name) {
  var arg = name + "=";
  var alen = arg.length;
  var clen = document.cookie.length;
  var i = 0;
  while (i < clen) {
    var j = i + alen;
    if (document.cookie.substring(i, j) == arg) {
      return getCookieVal(j);
    }
    i = document.cookie.indexOf(" ", i) + 1;
    if (i == 0) break;
  }
  return null;
}

// store cookie value with optional details as needed
function setCookie(name, value, expires, path, domain, secure) {
  document.cookie = name + "=" + escape (value) +
    ((expires) ? "; expires=" + expires : "") +
    ((path) ? "; path=" + path : "") +
    ((domain) ? "; domain=" + domain : "") +
    ((secure) ? "; secure" : "");
}

// remove the cookie by setting ancient expiration date
function deleteCookie(name,path,domain) {
  if (getCookie(name)) {
    document.cookie = name + "=" +
      ((path) ? "; path=" + path : "") +
      ((domain) ? "; domain=" + domain : "") +
      "; expires=Thu, 01-Jan-70 00:00:01 GMT";
  }
}
/* ---------------------------------------------------------------------- */
/* End Copyright 2003 Danny Goodman */


/* ---------------------------------------------------------------------- */
/* UTILITY FUNCTIONS
 */

/**
 * Type Detection
 */
function isAlien(a) {
  return isObject(a) && typeof a.constructor != 'function';
}

function isArray(a) {
  return isObject(a) && a.constructor == Array;
}

function isBoolean(a) {
  return typeof a == 'boolean';
}

function isEmpty(o) {
  var i, v;
  if (isObject(o)) {
    for (i in o) {
      v = o[i];
      if (isUndefined(v) && isFunction(v)) {
        return false;
      }
    }
  }
  return true;
}

function isFunction(a) {
  return typeof a == 'function';
}

function isNull(a) {
  return typeof a == 'object' && !a;
}

function isNumber(a) {
  return typeof a == 'number' && isFinite(a);
}

function isObject(a) {
  return (a && typeof a == 'object') || isFunction(a);
}

function isString(a) {
  return typeof a == 'string';
}

function isUndefined(a) {
  return typeof a == 'undefined';
}

function getPropertyByName(node, nodeName) {
  var arr = new Array();
  var items = node.getElementsByTagName("name");
  for (var i=0; i<items.length; i++) {
    if (nodeName == items[i].firstChild.nodeValue) {
      arr[0] = items[i].firstChild.nodeValue;
      arr[1] = items[i].parentNode.getElementsByTagName("value")[0].firstChild.nodeValue;
      return arr;
    }
  }
  return null;
}

function getValueForNode(node, nodeName) {
  var arr = getPropertyByName(node, nodeName);
  return arr != null ? arr[1] : null;
}

function evalBoolean(value, defaultValue) {
  if (!isNull(value) && isString(value)) {
    return ("true" == value.toLowerCase() || "yes" == value.toLowerCase()) ? "true" : "false";
  } else {
    return defaultValue == true ? "true" : "false";
  }
}

/*
 * Extract querystring from a URL
 */
function extractQueryString(url) {
  var ret = (url.indexOf('?') >= 0 && url.indexOf('?') < (url.length-1))
    ? url.substr(url.indexOf('?')+1)
    : '';
  return ret;
}

/*
 * Trim the querystring from a URL
 */
function trimQueryString(url) {
  var ret = url.indexOf('?') >= 0
    ? url.substring(0, url.indexOf('?'))
    : url;
  return ret;
}

/*
 *
 */
function delimitQueryString(qs) {
  var ret = '';
  if (qs.length > 0) {
    var params = qs.split('&');
    for (var i=0; i<params.length; i++) {
      if (i > 0) ret += ',';
      ret += params[i];
    }
  }
  return ret;
}

function getElementsByClassName(node, className) {
  var children = node.getElementsByTagName('*');
  var elements = new Array();

  for (var i = 0; i < children.length; i++) {
    var child = children[i];
    var classNames = child.className.split(' ');
    for (var j = 0; j < classNames.length; j++) {
      if (classNames[j] == className) {
        elements.push(child);
        break;
      }
    }
  }
  return elements;
}

/*
 * Add multiple onLoad Events
 */
function addOnLoadEvent(func) {
  var oldonload = window.onload;
  if (typeof window.onload != 'function') {
    window.onload = func;
  } else {
    window.onload = function() {
      oldonload();
      func();
    }
  }
}

function getElementY(element){
  var targetTop = 0;
  if (element.offsetParent) {
    while (element.offsetParent) {
      targetTop += element.offsetTop;
      element = element.offsetParent;
    }
  } else if (element.y) {
    targetTop += element.y;
  }
  return targetTop;
}

function getElementX(element){
  var targetLeft = 0;
  if (element.offsetParent) {
    while (element.offsetParent) {
      targetLeft += element.offsetLeft;
      element = element.offsetParent;
    }
  } else if (element.x) {
    targetLeft += element.yx;
  }
  return targetLeft;
}

function findNextElementByTagName(element, tagName) {
  var children = element.getElementsByTagName('*');
  for (var i = 0; i < children.length; i++) {
    var child = children[i];
    if (child.tagName.toLowerCase() == tagName.toLowerCase())
      return child;
  }
  return null;
}


/**
 * Returns true if an element has a specified class name
 */
function hasClass(node, className) {
  if (node.className == className) {
    return true;
  }
  var reg = new RegExp('(^| )'+ className +'($| )')
  if (reg.test(node.className)) {
    return true;
  }
  return false;
}

/**
 * Emulate PHP's ereg_replace function in javascript
 */
function eregReplace(search, replace, subject) {
  return subject.replace(new RegExp(search,'g'), replace);
}

function arrayToParameterString(array) {
  var str = '';
  for (var i=0; i<array.length; i++) {
    // escape parameter values
    var pair = array[i].split('=');
    if (pair.length == 2) {
      str += (i >= 0 ? '&' : '') + pair[0] + '=' + escape(pair[1]);
    } else {
      str += (i >= 0 ? '&' : '') + array[i];
    }
  }
  return str;
}

function replaceWithValue(sourceString, regExp, element) {
  var retString = "";
  switch (element.type) {
    case 'checkbox':
    case 'radio':
    case 'text':
    case 'textarea':
    case 'password':
    case 'hidden':
    case 'select-one':
    case 'select-multiple':
      retString = sourceString.replace(regExp, $F(element));
      break;
    default:
      retString = sourceString.replace(regExp, element.innerHTML);
      break;
  }
  return retString;
}

function decodeHtml(sourceString) {
  var retString = sourceString.replace(/&amp;/, "&");
  retString = retString.replace(/&lt;/, "<");
  retString = retString.replace(/&gt;/, ">");
  return retString;
}


/* ---------------------------------------------------------------------- */
/* AJAXJSPTAG.BASE
 */

AjaxJspTag.Base = function() {};
AjaxJspTag.Base.prototype = {

  resolveParameters: function() {
    // Strip URL of querystring and append it to parameters
    var qs = delimitQueryString(extractQueryString(this.baseUrl));
    if (qs != null && qs.length > 0) {
      if (this.options.parameters) {
        this.options.parameters += ',' + qs;
      } else {
        this.options.parameters = qs;
      }
    }
    this.baseUrl = trimQueryString(this.baseUrl);
  },

  setAjaxOptions: function(ajaxOptions) {
    this.ajaxOptions = {
      asynchronous: true,
      method: 'get',
      evalScripts: true,
      onComplete: this.onRequestComplete.bind(this)
    }.extend(ajaxOptions || {});
  },

  sendRequest: function() {
    new Ajax.Request(this.baseUrl, this.ajaxOptions);
  },

  sendUpdateRequest: function(target) {
    this.ajaxUpdater = new Ajax.Updater(target, this.baseUrl, this.ajaxOptions);
  },

  sendPeriodicalUpdateRequest: function(target) {
    this.ajaxPeriodicalUpdater =
      new Ajax.PeriodicalUpdater(target, this.baseUrl, this.ajaxOptions);
  },

  onRequestComplete: function(request) {
    if (request != null && request.status == 200) {
      var xmlDoc = request.responseXML;
      if (this.options.ajaxMethod != AJAX_METHOD_UPDATER
          && this.isEmptyResponse(request.responseXML)) {
        if (this.options.emptyFunction) {
          this.options.emptyFunction(request);
        }
      } else if (this.options.ajaxMethod != AJAX_METHOD_UPDATER
                 && this.isErrorResponse(request.responseXML)) {
        this.options.errorFunction(request.responseXML);
      } else {
        this.handlerFunction(request.responseXML);
      }
    } else {
      if (this.options.errorFunction) {
        this.options.errorFunction(request);
      }
    }
  },

  isEmptyResponse: function(xml) {
    var root = xml.documentElement;
    if (root.getElementsByTagName("response").length == 0
        || root.getElementsByTagName("response")[0].getElementsByTagName("item").length == 0) {
      return true;
    }
    return false;
  },

  isErrorResponse: function(xml) {
    var root = xml.documentElement;
    if (root.nodeName == "parsererror"
        || root.getElementsByTagName("error").length == 1
        || root.getElementsByTagName("response").length == 0) {
      return true;
    }
    return false;
  },

  buildParameterString: function(parameterList) {
    var ajaxParameters = parameterList || '';
    var re = new RegExp("(\\{[^,]*\\})", 'g'); // should retrieve each {} group
    var results = ajaxParameters.match(re);
    if (results != null) {
      for (var r=0; r<results.length; r++) {
        var nre = new RegExp(results[r], 'g');
        var field = $(results[r].substring(1, results[r].length-1));
        ajaxParameters = replaceWithValue(ajaxParameters, nre, field);
      }
    }
    return ajaxParameters;
  },

  attachBehaviors: function(element, event, listener, obj) {
    if (isArray(element)) {
      for (var i=0; i<element.length; i++) {
        eval("element[i].on"+event+" = listener.bindAsEventListener(obj)");
      }
    } else {
      eval("element.on"+event+" = listener.bindAsEventListener(obj)");
    }
  }

}


/* ---------------------------------------------------------------------- */
/* SELECT TAG
 */

AjaxJspTag.Select = Class.create();
AjaxJspTag.Select.prototype = (new AjaxJspTag.Base()).extend({

  initialize: function(url, options) {
    this.baseUrl = url;
    this.setOptions(options);
    this.attachBehaviors(this.options.sourceElem, this.options.eventType, this.sourceElemChanged, this);
  },

  setOptions: function(options) {
    this.options = {
      sourceElem: $(options.source),
      targetElem: $(options.target),
      eventType: options.eventType ? options.eventType : "change"
    }.extend(options || {});
  },

  populateSelect: function(xml) {
    var root = xml.documentElement;

    // reset field
    this.options.targetElem.options.length = 0;
    this.options.targetElem.disabled = false;

    // grab list of options
    var respNode = root.getElementsByTagName("response")[0];
    var items = respNode.getElementsByTagName("item");
    for (var i=0; i<items.length; i++) {
      if (items[i].getElementsByTagName("name")[0].firstChild==null) {
        this.options.targetElem.options[i] = new Option("","");
      } else {
        var name = items[i].getElementsByTagName("name")[0].firstChild.nodeValue;
        var value = items[i].getElementsByTagName("value")[0].firstChild.nodeValue;
        this.options.targetElem.options[i] = new Option(name, value);
      }
    }
  },

  sourceElemChanged: function(e) {
    this.resolveParameters();
    this.setAjaxOptions({
      parameters: arrayToParameterString(this.buildParameterString(this.options.parameters).split(','))
    });
    this.sendRequest();
  },

  handlerFunction: function(xml) {
    this.populateSelect(xml);
    if (this.options.postFunction) {
      this.options.postFunction(xml);
    }
  }

});


/* ---------------------------------------------------------------------- */
/* TOGGLE TAG
 */

AjaxJspTag.Toggle = Class.create();
AjaxJspTag.Toggle.prototype = (new AjaxJspTag.Base()).extend({

  initialize: function(url, options) {
    this.baseUrl = url;
    this.setOptions(options);
    this.attachBehaviors(this.options.imageElem, this.options.eventType, this.imageElemClicked, this);
  },

  setOptions: function(options) {
    this.options = {
      imageElem: $(options.image),
      stateElem: $(options.state),
      stateXmlName: options.stateXmlName ? options.stateXmlName : "toggleState",
      eventType: options.eventType ? options.eventType : "click"
    }.extend(options || {});
  },

  toggleImage: function(xml) {
    var root = xml.documentElement;

    // set state
    var respNode = root.getElementsByTagName("response")[0];
    var toggleState = getValueForNode(respNode, this.options.stateXmlName);
    if (this.options.stateElem) {
      this.options.stateElem.value = toggleState;
    }

    // set image
    var patternRegExp = /\{0\}/;
    this.options.imageElem.src = this.options.imagePattern.replace(patternRegExp, toggleState);
  },

  imageElemClicked: function(e) {
    this.resolveParameters();
    this.setAjaxOptions({
      parameters: this.options.parameters ?
        arrayToParameterString(this.buildParameterString(this.options.parameters).split(','))
        : ''
    });
    this.sendRequest();
  },

  handlerFunction: function(xml) {
    this.toggleImage(xml);
    if (this.options.postFunction) {
      this.options.postFunction(xml);
    }
  }

});


/* ---------------------------------------------------------------------- */
/* UPDATE FIELD TAG
 */

AjaxJspTag.UpdateField = Class.create();
AjaxJspTag.UpdateField.prototype = (new AjaxJspTag.Base()).extend({

  initialize: function(url, options) {
    this.baseUrl = url;
    this.setOptions(options);
    this.attachBehaviors(this.options.actionElem, this.options.eventType, this.actionElemClicked, this);
  },

  setOptions: function(options) {
    this.options = {
      sourceElem: $(options.source),
      actionElem: $(options.action),
      eventType: options.eventType ? options.eventType : "click"
    }.extend(options || {});
  },

  updateField: function(xml) {
    var root = xml.documentElement;
    var respNode = root.getElementsByTagName("response")[0];

    var items = respNode.getElementsByTagName("item");
    var targetArray = this.options.target.split(",");
    if (items.length > 0) {
      for (var i=0; i<targetArray.length; i++) {
        var value = getValueForNode(respNode, targetArray[i]);
        var field = $(targetArray[i]);
        if (value != null && field != null
            && (field.type == "text"
                || field.type == "textarea"
                || field.type == "hidden")) {
          field.value = value;
        }
      }
    }
  },

  actionElemClicked: function(e) {
    this.resolveParameters();
    this.setAjaxOptions({
      parameters: arrayToParameterString(this.buildParameterString(this.options.parameters).split(','))
    });
    this.sendRequest();
  },

  handlerFunction: function(xml) {
    this.updateField(xml);
    if (this.options.postFunction) {
      this.options.postFunction(xml);
    }
  }

});


/* ---------------------------------------------------------------------- */
/* AUTOCOMPLETE TAG
 */

AjaxJspTag.Autocomplete = Class.create();
AjaxJspTag.Autocomplete.prototype = (new AjaxJspTag.Base()).extend({

  initialize: function(url, options) {
    this.baseUrl = url;
    this.setOptions(options);
    this.suggestList = new Array();
    this.currentIndex = 0;
    this.createPopup();
    this.createIframe();
    this.options.sourceElem.setAttribute("autocomplete", "off");
    this.attachBehaviors(this.options.sourceElem, this.options.eventType, this.sourceElemChanged, this);
  },

  setOptions: function(options) {
    this.options = {
      sourceElem: $(options.source),
      targetElem: $(options.target),
      eventType: options.eventType ? options.eventType : "keyup",
      appendValue: evalBoolean(options.appendValue),
      appendSeparator: options.appendSeparator || " ",
      forceSelection: evalBoolean(options.forceSelection)
    }.extend(options || {});

    // Autocomplete is unique in that we may have a progress icon
    // So, we need to hook in the resetProgressStyle function on an empty result
    var self = this;
    this.options.emptyFunction = function() {
      self.handleEmptyResult();
      if (options.emptyFunction) options.emptyFunction();
    }

    this.popupElem = "ajaxAutocompletePopup";
  },

  autocomplete: function() {
    var root = this.xml.documentElement;

    // clear contents of container (i.e., DIV tag)
    $(this.popupElem).innerHTML = "";

    var ul = document.createElement("ul");
    items = root.getElementsByTagName("item");
    for (var i=0; i<items.length; i++) {
      var name = items[i].getElementsByTagName("name")[0].firstChild.nodeValue;
      var value = items[i].getElementsByTagName("value")[0].firstChild.nodeValue;
      var li = document.createElement("li");
      var liIdAttr = document.createAttribute("id");
      li.setAttribute("id", value);
      var liText = document.createTextNode(name);
      li.appendChild(liText);
      ul.appendChild(li);
    }
    $(this.popupElem).appendChild(ul);
    Element.show(this.popupElem);

    this.setSelected();
  },

  sourceElemChanged: function(e) {
    var key = 0;
    if (e.keyCode) { key = e.keyCode; }
    else if (typeof(e.which)!= 'undefined') { key = e.which; }
    var fieldLength = $F(this.options.sourceElem).length;

    //up arrow
    if (key == 38) {
      if (this.currentIndex > 0) {
        this.suggestList[this.currentIndex].className = '';
        this.currentIndex--;
        this.suggestList[this.currentIndex].className = 'selected';
        this.suggestList[this.currentIndex].scrollIntoView(false);
      }

    //down arrow
    } else if (key == 40) {
      if(this.currentIndex < this.suggestList.length - 1) {
        this.suggestList[this.currentIndex].className = '';
        this.currentIndex++;
        this.suggestList[this.currentIndex].className = 'selected';
        this.suggestList[this.currentIndex].scrollIntoView(false);
      }

    //enter
    } else if (key == 13 && $(this.popupElem).style.display != 'none') {
      this.fillField(this.suggestList[this.currentIndex]);
      Event.stop(e);
      this.executePostFunction();

    //escape
    } else if (key == 27) {
      this.hidePopup();
      Event.stop(e);

    } else {
      if (fieldLength < this.options.minimumCharacters) {
        this.hidePopup();
      } else if (key != 37 && key != 39 && key != 17 && key != 18) {
        this.resolveParameters();
        this.setAjaxOptions({
          parameters: arrayToParameterString(this.buildParameterString(this.options.parameters).split(','))
        });
        this.setProgressStyle();
        this.sendRequest();
      }
    }
  },

  fillField: function(selection) {
    this.options.sourceElem.value = decodeHtml(selection.innerHTML);
    if (this.options.appendValue == "false") {
      this.options.targetElem.value = selection.getAttribute("id");
    } else {
      if (this.options.targetElem.value.length > 0)
        this.options.targetElem.value += this.options.appendSeparator;
      this.options.targetElem.value += selection.getAttribute("id");
    }
    Element.hide(this.popupElem);
    this.hidePopup();
  },

  handleEmptyResult: function(e) {
    this.resetProgressStyle();
    if (this.options.forceSelection == "true") {
      // remove last character typed
      this.options.sourceElem.value = this.options.sourceElem.value.substr(0, this.options.sourceElem.value.length-1);
    } else {
      // simply hide the popup
      this.hidePopup();
    }
  },

  handlerFunction: function(xml) {
    this.xml = xml;
    this.resetProgressStyle();
    this.autocomplete();
  },

  executePostFunction: function() {
    if (this.options.postFunction) {
      this.options.postFunction(this.xml);
    }
  },

  createPopup: function() {
    new Insertion.Top(
      document.getElementsByTagName("body")[0],
        "<div id=\""+this.popupElem+"\" class=\""+this.options.className+"\"></div>");
  },

  hidePopup: function() {
    Element.hide(this.popupElem);
    $('layerCover').style.display = "none";
  },

  setProgressStyle: function() {
    if (this.options.progressStyle != null) {
      Element.addClassName(this.options.sourceElem, this.options.progressStyle);
    }
  },

  resetProgressStyle: function() {
    if (this.options.progressStyle != null) {
      Element.removeClassName(this.options.sourceElem, this.options.progressStyle);
    }
  },

  setSelected: function() {
    this.currentIndex = 0;
    this.suggestList = $(this.popupElem).getElementsByTagName("li");
    if ((this.suggestList.length > 1)
       || (this.suggestList.length == 1
           && this.suggestList[0].innerHTML != $F(this.options.sourceElem))) {
      this.setPopupStyles();
      for (var i = 0; i < this.suggestList.length; i++) {
        this.suggestList[i].index = i;
        this.addOptionHandlers(this.suggestList[i]);
      }
      this.suggestList[0].className = 'selected';
    } else {
      Element.hide(this.popupElem);
    }
    return null;
  },

  setPopupStyles: function() {
    var maxHeight;
    if (isIE) {
      maxHeight = 200;
    } else {
      maxHeight = window.outerHeight/3;
    }

    if ($(this.popupElem).offsetHeight < maxHeight) {
      $(this.popupElem).style.overflow = 'hidden';
    } else if (isMoz) {
      $(this.popupElem).style.maxHeight = maxHeight + 'px';
      $(this.popupElem).style.overflow = '-moz-scrollbars-vertical';
    } else {
      $(this.popupElem).style.height = maxHeight + 'px';
      $(this.popupElem).style.overflowY = 'auto';
    }
    $(this.popupElem).scrollTop = 0;
    Element.show(this.popupElem);

    $(this.popupElem).style.top = (getElementY(this.options.sourceElem)+this.options.sourceElem.offsetHeight+2) + "px";
    $(this.popupElem).style.left = getElementX(this.options.sourceElem) + "px";
    if (isIE) {
      $(this.popupElem).style.width = this.options.sourceElem.offsetWidth + "px";
    } else {
      $(this.popupElem).style.minWidth = this.options.sourceElem.offsetWidth + "px";
    }
    $(this.popupElem).style.overflow = "visible";

    // do iframe
    $('layerCover').style.top = (getElementY(this.options.sourceElem)+this.options.sourceElem.offsetHeight+2) + "px";
    $('layerCover').style.left = getElementX(this.options.sourceElem) + "px";
    $('layerCover').style.width = $(this.popupElem).offsetWidth;
    $('layerCover').style.height = $(this.popupElem).offsetHeight;
    $('layerCover').style.display = "block";
    $('layerCover').style.zIndex = 10;
    $(this.popupElem).style.zIndex = 20;
  },

  createIframe: function() {
    new Insertion.Before($(this.popupElem), "<iframe id='layerCover' style='display: none; position: absolute; top: 0; left: 0;' src='about:blank' frameborder='0' scrolling='no'></iframe>");
  },

  handleClick: function(e) {
    this.fillField(Event.element(e));
    this.executePostFunction();
  },

  handleOver: function(e) {
    this.suggestList[this.currentIndex].className = '';
    this.currentIndex = Event.element(e).index;
    this.suggestList[this.currentIndex].className = 'selected';
  },

  addOptionHandlers: function(option) {
    option.onclick = this.handleClick.bindAsEventListener(this);
    option.onmouseover = this.handleOver.bindAsEventListener(this);
  }

});


/* ---------------------------------------------------------------------- */
/* CALLOUT TAG
 */

AjaxJspTag.Callout = Class.create();
AjaxJspTag.Callout.prototype = (new AjaxJspTag.Base()).extend({

  initialize: function(url, options) {
    this.baseUrl = url;
    this.setOptions(options);
    this.attachBehaviors(this.options.sourceElementList,
                         this.options.eventType,
                         this.sourceElemClicked,
                         this);
    this.createContainer();
    this.targetElem = this.constructBox();
    this.activeElem = null;
  },

  setOptions: function(options) {
    var list;
    if (options.sourceClass) {
      list = document.getElementsByClassName(options.sourceClass);
    } else {
      list = new Array();
      list.push($(options.source));
    }
    this.options = {
      sourceElementList: list,
      classNamePrefix: options.classNamePrefix ? options.classNamePrefix : "callout",
      eventType: options.eventType ? options.eventType : "click"
    }.extend(options || {});

    if (options.timeout) {
      if (Number(options.timeout) > 250)
        this.options.timeout = Number(options.timeout)
      else
        this.options.timeout = 250;
    }

    if (options.title) {
      this.options.useTitleBar = "true";
    } else if (options.useTitleBar) {
      this.options.useTitleBar = evalBoolean(options.useTitleBar);
    } else {
      this.options.useTitleBar = "false";
    }

    if (!options.boxPosition) this.options.boxPosition = "top right";
    this.calloutContainer = "calloutContainer";
    this.calloutParameter = AJAX_DEFAULT_PARAMETER;
  },

  callout: function(xml) {
    var root = xml.documentElement;

    var items = root.getElementsByTagName("item");
    if (items.length > 0) {
      var name = items[0].getElementsByTagName("name")[0].firstChild.nodeValue;
      var value = items[0].getElementsByTagName("value")[0].firstChild.nodeValue;
      // fill text (if present)
      if (this.options.useTitleBar == "true") {
        if (!this.options.title) {
          this.targetElem.childNodes[1].innerHTML = name;
        } else {
          this.targetElem.childNodes[1].innerHTML = this.options.title;
        }
        this.targetElem.childNodes[2].innerHTML = value;
      } else {
        this.targetElem.childNodes[1].innerHTML = value;
      }

      this.targetElem.style.overflow = "visible";

      // bring box to front
      this.targetElem.style.display = "block";
      this.targetElem.style.visibility = "visible";

      // move box to new location
      this.moveBox(this.activeElem, this.targetElem);

      // hook events
      this.targetElem.childNodes[0].onclick = this.handleCloseClick.bindAsEventListener(this);
      window.onclick = this.checkBoxPosition.bindAsEventListener(this); // when clicked outside callout
      if (this.options.timeout) {
        if (this.timer)
          clearTimeout(this.timer);
        this.timer = setTimeout(this.handleHover.bind(this), this.options.timeout);
      }
    }
  },

  sourceElemClicked: function(e) {
    // replace unique parameter 'ajaxCallout' with inner content of containing element
    // ONLY IF we are using the class as the identifier; otherwise, treat it like a field
    this.activeElem = Event.element(e);
    this.resolveParameters();
    var ajaxParameters = this.options.parameters || '';
    var re = new RegExp("(\\{"+this.calloutParameter+"\\})", 'g');
    ajaxParameters = replaceWithValue(ajaxParameters, re, this.activeElem);
    // set the rest of the parameters generically
    this.setAjaxOptions({
      parameters: arrayToParameterString(this.buildParameterString(ajaxParameters).split(','))
    });
    this.sendRequest();
  },

  handlerFunction: function(xml) {
    this.callout(xml);
    if (this.options.postFunction) {
      this.options.postFunction(this.activeElem);
    }
  },

  createContainer: function() {
    new Insertion.Top(
      document.getElementsByTagName("body")[0],
        "<div id=\""+this.calloutContainer+"\" style=\"position: absolute; top: 0; left: 0\"></div>");
  },

  constructBox: function() {
    // create base
    var eBox = document.createElement("div");
    eBox.className = this.options.classNamePrefix+"Box";
    eBox.setAttribute("style", "position: absolute; top: 0; left: 0");
    document.documentElement.appendChild(eBox);

    // add elements
    var eClose = document.createElement("div");
    eClose.className = this.options.classNamePrefix+"Close";
    eClose.appendChild(document.createTextNode("X"));
    eBox.appendChild(eClose);

    if (this.options.useTitleBar == "true") {
      var eTitle = document.createElement("div");
      eTitle.className = this.options.classNamePrefix+"Title";
      eBox.appendChild(eTitle);
    }

    var eContent = document.createElement("div");
    eContent.className = this.options.classNamePrefix+"Content";
    eBox.appendChild(eContent);

    eBox.style.display = "none";
    $(this.calloutContainer).appendChild(eBox);
    return eBox;
  },

  moveBox: function(anchor, box) {
    box.style.position = "absolute";
    var posXY = Position.cumulativeOffset(anchor);

    if (this.options.boxPosition.indexOf("top") >= 0) {
      box.style.top = (posXY[1] - (box.offsetHeight) - 10) + "px";
    } else {
      box.style.top = (posXY[1] + (anchor.offsetHeight) + 10) + "px";
    }
    if (this.options.boxPosition.indexOf("right") >= 0) {
      box.style.left = (posXY[0] + 10) + "px";
    } else {
      box.style.left = (posXY[0] - (box.offsetWidth) - 10) + "px";
    }

    // Check for off-screen position
    if (box.offsetLeft < 0) {
      box.style.left = 0;
    }
    if (box.offsetTop < 0) {
      box.style.top = 0;
    }
  },

  handleCloseClick: function(e) {
    clearTimeout(this.timer);
    if (window.captureEvents) {
      window.releaseEvents(Event.MOUSEMOVE);
    }
    window.onmousemove = null;
    this.targetElem.style.display = "none";
  },

  checkBoxPosition: function(e) {
    var outsideContainer = false;
    var outsideSource = false;

    // evaluate if cursor is over box
    var clickX = e.clientX; // cursor X position
    var clickY = e.clientY; // cursor Y position
    var boundX1 = this.targetElem.offsetLeft;
    var boundX2 = boundX1 + this.targetElem.offsetWidth;
    var boundY1 = this.targetElem.offsetTop;
    var boundY2 = boundY1 + this.targetElem.offsetHeight;
    if (clickX < boundX1 || clickX > boundX2 || clickY < boundY1 || clickY > boundY2) {
      outsideContainer = true;
    }

    // evaluate if cursor is over source
    boundX1 = this.activeElem.offsetLeft;
    boundX2 = boundX1 + this.activeElem.offsetWidth;
    boundY1 = this.activeElem.offsetTop;
    boundY2 = boundY1 + this.activeElem.offsetHeight;

    if (clickX < boundX1 || clickX > boundX2 || clickY < boundY1 || clickY > boundY2) {
      outsideSource = true;
    }
    if (outsideContainer && outsideSource) {
      this.handleCloseClick();
    }
  },

  handleHover: function(e) {
    if (window.captureEvents) {
      window.captureEvents(Event.MOUSEMOVE);
    }
    window.onmousemove = this.checkBoxPosition.bindAsEventListener(this);
  }

});


/* ---------------------------------------------------------------------- */
/* HTML CONTENT TAG
 */

AjaxJspTag.HtmlContent = Class.create();
AjaxJspTag.HtmlContent.prototype = (new AjaxJspTag.Base()).extend({

  initialize: function(url, options) {
    this.baseUrl = url;
    this.setOptions(options);
    this.attachBehaviors(this.options.sourceElementList,
                         this.options.eventType,
                         this.sourceElemClicked,
                         this);
  },

  setOptions: function(options) {
    var list;
    if (options.sourceClass) {
      list = document.getElementsByClassName(options.sourceClass);
    } else {
      list = new Array();
      list.push($(options.source));
    }
    this.options = {
      sourceElementList: list,
      targetElem: $(options.target),
      eventType: options.eventType ? options.eventType : "click",
      ajaxMethod: AJAX_METHOD_UPDATER
    }.extend(options || {});

    this.contentParameter = AJAX_DEFAULT_PARAMETER;
  },

  sourceElemClicked: function(e) {
    this.resolveParameters();

    var ajaxParameters = this.options.parameters || '';
    if (this.options.sourceClass) {
      this.activeElem = Event.element(e);
      var re = new RegExp("(\\{"+this.contentParameter+"\\})", 'g');
      ajaxParameters = replaceWithValue(ajaxParameters, re, this.activeElem);
    }
    this.setAjaxOptions({
      parameters: arrayToParameterString(this.buildParameterString(ajaxParameters).split(','))
    });

    this.sendUpdateRequest(this.options.target);
  },

  handlerFunction: function(xml) {
    if (this.options.postFunction) {
      this.options.postFunction(xml);
    }
  }

});


/* ---------------------------------------------------------------------- */
/* TAB PANEL TAG
 */

AjaxJspTag.TabPanel = Class.create();
AjaxJspTag.TabPanel.prototype = (new AjaxJspTag.Base()).extend({

  initialize: function(url, options) {
    this.baseUrl = url;
    this.setOptions(options);
    this.execute();
  },

  setOptions: function(options) {
    this.options = {
      ajaxMethod: AJAX_METHOD_UPDATER
    }.extend(options || {});
  },

  execute: function() {
    this.resolveParameters();

    this.setAjaxOptions({
      parameters: arrayToParameterString(this.buildParameterString(this.options.parameters).split(','))
    });

    this.sendUpdateRequest(this.options.target);

    $(this.options.currentStyleId).id = '';
    this.options.source.id = this.options.currentStyleId;
  },

  handlerFunction: function(xml) {
    if (this.options.postFunction) {
      this.options.postFunction(xml);
    }
  }

});


/* ---------------------------------------------------------------------- */
/* PORTLET TAG
 */

AjaxJspTag.Portlet = Class.create();
AjaxJspTag.Portlet.prototype = (new AjaxJspTag.Base()).extend({

  initialize: function(url, options) {
    this.baseUrl = url;
    this.setOptions(options);
    if (this.options.executeOnLoad == "true") {
      this.execute();
    }
    if (this.preserveState) this.checkCookie();

    // Attach events to icons if defined
    if (this.options.imageClose) {
      this.attachBehaviors(this.options.closeElement, "click", this.closePortlet, this);
    }
    if (this.options.imageRefresh) {
      this.attachBehaviors(this.options.refreshElement, "click", this.refreshPortlet, this);
    }
    if (this.options.imageMaximize && this.options.imageMinimize) {
      this.attachBehaviors(this.options.toggleElement, "click", this.togglePortlet, this);
    }
  },

  checkCookie: function() {
    // Check cookie for save state
    var cVal = getCookie("AjaxJspTag.Portlet."+this.options.source);
    if (cVal != null) {
      if (cVal == AJAX_PORTLET_MIN) {
        this.togglePortlet();
      } else if (cVal == AJAX_PORTLET_CLOSE) {
        this.closePortlet();
      }
    }
  },

  setOptions: function(options) {
    this.options = {
      ajaxMethod: AJAX_METHOD_UPDATER,
      targetElement: options.classNamePrefix+"Content",
      closeElement: getElementsByClassName($(options.source), (options.classNamePrefix+"Close"))[0],
      refreshElement: getElementsByClassName($(options.source), (options.classNamePrefix+"Refresh"))[0],
      toggleElement: getElementsByClassName($(options.source), (options.classNamePrefix+"Size"))[0],
      executeOnLoad: evalBoolean(options.executeOnLoad, true),
      preserveState: evalBoolean(options.preserveState),
      expireDays: options.expireDays || "0",
      expireHours: options.expireHours || "0",
      expireMinutes: options.expireMinutes || "0",
      isMaximized: true
    }.extend(options || {});

    if (parseInt(this.options.expireDays) > 0
        || parseInt(this.options.expireHours) > 0
        || parseInt(this.options.expireMinutes) > 0) {
      this.preserveState = true;
      this.options.expireDate = getExpDate(
        parseInt(this.options.expireDays),
        parseInt(this.options.expireHours),
        parseInt(this.options.expireMinutes));
    }

    this.autoRefreshSet = false;
  },

  execute: function() {
    this.resolveParameters();

    this.setAjaxOptions({
      frequency: this.options.refreshPeriod ? (this.options.refreshPeriod) : null,
      parameters: arrayToParameterString(this.buildParameterString(this.options.parameters).split(','))
    });

    if (this.options.refreshPeriod && this.autoRefreshSet == false) {
      this.sendPeriodicalUpdateRequest(
          getElementsByClassName($(this.options.source), this.options.targetElement)[0]);
      this.autoRefreshSet = true;
    } else {
      this.sendUpdateRequest(
          getElementsByClassName($(this.options.source), this.options.targetElement)[0]);
    }
  },

  stopAutoRefresh: function() {
    // stop auto-update if present
    if (this.ajaxPeriodicalUpdater != null
        && this.options.refreshPeriod
        && this.autoRefreshSet == true) {
      this.ajaxPeriodicalUpdater.stop();
    }
  },

  startAutoRefresh: function() {
    // stop auto-update if present
    if (this.ajaxPeriodicalUpdater != null && this.options.refreshPeriod) {
      this.ajaxPeriodicalUpdater.start();
    }
  },

  refreshPortlet: function(e) {
    // clear existing updater
    this.stopAutoRefresh();
    if (this.ajaxPeriodicalUpdater != null) {
      this.startAutoRefresh();
    } else {
      this.execute();
    }
  },

  closePortlet: function(e) {
    this.stopAutoRefresh();
    Element.remove(this.options.source);
    // Save state in cookie
    if (this.preserveState) {
      setCookie("AjaxJspTag.Portlet."+this.options.source,
        AJAX_PORTLET_CLOSE,
        this.options.expireDate);
    }
  },

  togglePortlet: function(e) {
    Element.toggle(getElementsByClassName($(this.options.source), this.options.targetElement)[0]);
    var image = this.options.toggleElement;
    if (this.options.isMaximized) {
      image.src = this.options.imageMaximize;
      this.stopAutoRefresh();
    } else {
      image.src = this.options.imageMinimize;
      this.startAutoRefresh();
    }
    this.options.isMaximized = !this.options.isMaximized;
    // Save state in cookie
    if (this.preserveState) {
      setCookie("AjaxJspTag.Portlet."+this.options.source,
        (this.options.isMaximized == true ? AJAX_PORTLET_MAX : AJAX_PORTLET_MIN),
        this.options.expireDate);
    }
  },

  handlerFunction: function(xml) {
    if (this.options.postFunction) {
      this.options.postFunction(xml);
    }
  }

});