text: /*
   * The MIT License
   * 
   * Copyright (c) 2004-2009, Sun Microsystems, Inc., Kohsuke Kawaguchi
   * 
   * Permission is hereby granted, free of charge, to any person obtaining a copy
   * of this software and associated documentation files (the "Software"), to deal
   * in the Software without restriction, including without limitation the rights
   * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
   * copies of the Software, and to permit persons to whom the Software is
   * furnished to do so, subject to the following conditions:
   * 
   * The above copyright notice and this permission notice shall be included in
   * all copies or substantial portions of the Software.
   * 
   * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
   * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
   * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
   * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
   * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
   * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
   * THE SOFTWARE.
   */
  
  if (typeof UTILITIES_VERSION == "undefined" || UTILITIES_VERSION < 0.1) {
  	alert("A suitable version of the Utilities class is not available");
  }
  
  COMBOBOX_VERSION = 0.1;
  
  /*
  Sample CSS for the combobox
  	.comboBoxList {
  		padding: 0px;
  		border: 1px solid #999;
  		background-color: #f7f7ff;
  		overflow: visible;
  	}
  	.comboBoxItem {
  		margin: 0px;
  		padding: 2px 5px;
  		background-color: inherit;
  		cursor: default;
  	}
  	.comboBoxSelectedItem {
  		background-color: #ddf;
  	}
  */
  
  /**
   * I create a new combobox, using the specified text input field and
   * population callback.  The item list is styled with three CSS
   * classes: comboBoxList, comboBoxItem, and comboBoxSelectedItem, which
   * are for the containing DIV, the individial item DIVs, and for the
   * currently selected item DIV.  Note that the selected item has both
   * the item and selectedItem classes applied.  Sample CSS is available
   * in a comment at the top of the implementation file.
   * 
   * The 'config' argument allows passing of additional parameters that
   * further govern the behaviour of the combo box.  Supported parameters
   * are listed here:
   * 	allowMultipleValues - whether the form field should allow multiple
   *		values to be provided.  Each individual value will get it's own
   *		separate dropdown with, so a field value such as "dog,ca" would
   *		operate as if the value were just "ca" (i.e. just "ca" would be
   *		passed to the callback, and a selection choice would only
   *		replace the "ca").  Defaults to false.
   *	valueDelimiter - if allowMultipleValues is set to true, this is the
   *		character used to delimit the values.  Defaults to a comma.
   *
   * @param id The ID of the text field the combobox is based around.
   * @param callback The function to call when the typed value changes.
   *		The function will be passed the current value of the field, and
   *		must return an array of values to display in the dropdown.
   * @param config additional config parameters, as explained above.
   * @param config An object containing configuration parameters for the
   *		instance.
   */
  function ComboBox(id, callback, config) {
  	var self = this;
  	// instance variables
  	this.config = config || new Object();
  	this.callback = callback;
  	this.availableItems = new Array();
  	this.selectedItemIndex = -1;
  	this.id = id;
  	this.field = document.getElementById(id);
  	if (typeof this.field == "undefined")
  		alert("You have specified an invalid id for the field you want to turn into a combo box");
  	this.dropdown = document.createElement("div");
  	this.isDropdownShowing = false;
  	
  	// configure the dropdown div
  	this.dropdown.className = "comboBoxList";
  	document.body.appendChild(this.dropdown);
  	this.dropdown.style.position = 'absolute';
      this.moveDropdown();
      this.hideDropdown();
  	
  	// initialize the field
  	this.field.comboBox = this;
  	this.field.oldValue = this.field.value;
  	this.field.onkeyup = ComboBox.onKeyUp;
  	this.field.moveCaretToEnd = function() {
  		if (this.createTextRange) {
  			var range = this.createTextRange();
  			range.collapse(false);
  			range.select();
  		} else if (this.setSelectionRange) {
  			this.focus();
  			var length = this.value.length;
  			this.setSelectionRange(length, length);
  		}
  	}
  	this.field.form.oldonsubmit = this.field.form.onsubmit;
  	this.field.onfocus = function() {
  		this.form.onsubmit = function() {
  			if (this.oldonsubmit) this.oldonsubmit();
  			return ! self.isDropdownShowing;
  		};
  	}
  	this.field.onblur = function() {
  		var cb = this.comboBox;
  		this.hideTimeout = setTimeout(function() { cb.hideDropdown(); }, 100);
  		this.form.onsubmit = function() {
  			if (this.oldonsubmit) this.oldonsubmit();
  			return true;
  		};
  	}
  	
  	// privileged methods
  	this.getConfigParam = function(name, defVal) {
  		return self.config[name] || defVal;
  	}
  }
  
  
  
  /**
   * I am the onKeyDown listener that gets installed on the input field
   * that is the core of the ComboBox.  I handle action operations.
   *
   * @param e The event object on Mozilla browsers, null on IE
   */
  ComboBox.onKeyDown = function(e) {
  	if (!e) e = window.event;
  	var capture = function() {
  		e.cancelBubble = true;
  		if (e.stopPropagation) e.stopPropagation();
  	}
  	switch (e.keyCode) {
  		case 13: // enter
  		case 9: // tab
  			this.comboBox.chooseSelection();
  			capture();
  			return false;
  		case 27: // escape
  			this.comboBox.hideDropdown();
  			capture();
  		case 38: // up arrow
  			this.comboBox.selectPrevious();
  			capture();
  			break;
  		case 40: // down arrow
  			this.comboBox.selectNext();
  			capture();
  			break;
  	}
  }
  
  
  
  /**
   * I am the onKeyUp listener that gets installed on the input field
   * that is the core of the ComboBox.  I handle value-change operations.
   *
   * @param e The event object on Mozilla browsers, null on IE
   */
  ComboBox.onKeyUp = function(e) {
  	if (!e) e = window.event;
  	var capture = function() {
  		e.cancelBubble = true;
  		if (e.stopPropagation) e.stopPropagation();
  	}
  	switch (e.keyCode) {
  		case 38: // up arrow
  		case 40: // down arrow
  			this.moveCaretToEnd();
  			capture();
  			break;
  		default:
  			if (this.value != this.oldValue) {
  				this.comboBox.valueChanged();
  				this.oldValue = this.value;
  			}
  			capture();
  	}
  }
  
  
  
  /**
   * I am called by the onKeyUp listener when the entered value changes,
   * and am responsible for invoking the application callback function
   * and repopulating the dropdown, if appropriate.  
   */
  ComboBox.prototype.valueChanged = function() {
  	var value = this.field.value;
  	if (this.getConfigParam("allowMultipleValues", false)) {
  		value = value.split(this.getConfigParam("valueDelimiter", ","));
  		value = value[value.length - 1].replace(/^ +/, "").replace(/ +$/, "");
  	}
  	var a = this.callback(value, this);
  	if (typeof a == "undefined") // to catch null returns
  		return;
  	this.setItems(a);
  }
  
  
  
  /**
   * I can be called at any time with a new set of items to display in
   * the dropdown.
   *
   * @param items The array of items that should be used for the dropdown
   *		values.
   */
  ComboBox.prototype.setItems = function(items) {
  	if (typeof items != "object") {
  		alert("setItems wasn't passed a valid array: " + typeof a);
  		return;
  	}
  	this.availableItems = items;
  	this.populateDropdown();
  }
  
  
  
  /**
   * I am called to repopulate the dropdown.  There should never be a
   * need to invoke me externally.
   */
  ComboBox.prototype.populateDropdown = function() {
  	if (this.availableItems.length > 0) {
  		Utilities.removeChildren(this.dropdown);
  		for (var i = 0; i < this.availableItems.length; i++) {
  			var item = document.createElement("div");
  			item.className = "comboBoxItem";
  			item.innerHTML = this.availableItems[i];
  			item.id = "item_" + this.availableItems[i];
  			item.comboBox = this;
  			item.comboBoxIndex = i;
  			item.onmouseover = function() {this.comboBox.select(this.comboBoxIndex);};
  			item.onclick = function() {this.comboBox.choose(this.comboBoxIndex);};
  			this.dropdown.appendChild(item);
  		}
  		this.selectedItemIndex = 0;
  		this.updateSelection();
  		this.showDropdown();
  	} else {
  		this.selectedItemIndex = -1;
  		this.hideDropdown();
  	}
  }
  
  
  
  /**
   * I am called by a mouse listener on the dropdown items to choose a
   * specific item straight away.
   *
   * @param index The index of the item to choose
   */
  ComboBox.prototype.choose = function(index) {
  	if (this.select(index))
  		this.chooseSelection();
  }
  
  
  
  /**
   * I am called by the onKeyUp listener to indicate that the user wants
   * to use the current selection as the new value of the field.
   */
  ComboBox.prototype.chooseSelection = function() {
  	var i = this.selectedItemIndex;
  	var a = this.availableItems;
  	if (i >= 0 && i < a.length) {
  		var valueToAdd = a[i].replace(/<[^>]+>/g, "");
  		if (this.getConfigParam("allowMultipleValues", false)) {
  			var currentValue = "";
  			var delim = this.getConfigParam("valueDelimiter", ",");
  			values = this.field.value.split(delim);
  			for (var j = 0; j < values.length - 1; j++) {
  				currentValue = Utilities.listAppend(currentValue, values[j], delim);
  			}
  			this.field.value = Utilities.listAppend(currentValue, valueToAdd, delim);
  		} else {
  			this.field.value = valueToAdd;
  		}
  
  		this.field.oldValue = this.field.value;
  		this.field.focus();
  		this.field.moveCaretToEnd();
  		this.hideDropdown();
  	}
  }
  
  
  
  /**
   * I am called by a mouse listener on the dropdown items to select a
   * specific item straight away.
   *
   * @param index The index of the item to select
   * @return whether the selection happened (the index was valid)
   */
  ComboBox.prototype.select = function(index) {
  	if (index < 0 || index >= this.availableItems.length)
  		return false;
  	this.selectedItemIndex = index;
  	this.updateSelection();
  	return true;
  }
  
  
  
  /**
   * I am called by the onKeyUp listener to indicate that the user wants
   * to select the next option in the dropdown.
   */
  ComboBox.prototype.selectNext = function() {
  	if (this.selectedItemIndex >= this.availableItems.length - 1)
  		return false;
  	this.selectedItemIndex++;
  	this.updateSelection();
  	return true;
  }
  
  
  
  /**
   * I am called by the onKeyUp listener to indicate that the user wants
   * to select the previous option in the dropdown.
   */
  ComboBox.prototype.selectPrevious = function() {
  	if (this.selectedItemIndex <= 0)
  		return false;
  	this.selectedItemIndex--;
  	this.updateSelection();
  }
  
  
  
  /**
   * I show the dropdown DIV.
   */
  ComboBox.prototype.showDropdown = function() {
      this.moveDropdown();
      clearTimeout(this.field.hideTimeout);
  	this.dropdown.style.display = 'block';
  	this.field.onkeydown = ComboBox.onKeyDown;
  	this.isDropdownShowing = true;
  }
  
  
  
  /**
   * I hide the dropdown DIV.
   */
  ComboBox.prototype.hideDropdown = function() {
  	var self = this;
  	setTimeout(function() {self.isDropdownShowing = false;}, 100);
  	this.field.onkeydown = null;
  	this.dropdown.style.display = 'none';
  }
  
  ComboBox.prototype.moveDropdown = function() {
      var offsets = Utilities.getOffsets(this.field);
      this.dropdown.style.top = offsets.y + (this.field.offsetHeight ? this.field.offsetHeight : 22) + "px";
      this.dropdown.style.left = offsets.x + "px";
      this.dropdown.style.width = (this.field.offsetWidth ? this.field.offsetWidth : 100) + "px"
  }
  
  
  
  /**
   * I update the dropdown so that the display reflects the internally
   * selected item,
   */
  ComboBox.prototype.updateSelection = function() {
  	for (var i = 0; i < this.dropdown.childNodes.length; i++) {
  		if (i == this.selectedItemIndex) {
  			this.dropdown.childNodes[i].className += " comboBoxSelectedItem";
  		} else {
  			this.dropdown.childNodes[i].className = this.dropdown.childNodes[i].className.replace(/ *comboBoxSelectedItem */g, "");
  		}
  	}
  }