mirror of https://github.com/jenkinsci/jenkins.git
376 lines
10 KiB
JavaScript
376 lines
10 KiB
JavaScript
|
|
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, "");
|
|
}
|
|
}
|
|
} |