зеркало из https://github.com/mozilla/gecko-dev.git
280 строки
7.8 KiB
JavaScript
280 строки
7.8 KiB
JavaScript
/**
|
|
* SelectHelperUI: Provides an interface for making a choice in a list.
|
|
* Supports simultaneous selection of choices and group headers.
|
|
*/
|
|
var SelectHelperUI = {
|
|
_list: null,
|
|
_selectedIndexes: null,
|
|
|
|
get _panel() {
|
|
delete this._panel;
|
|
return this._panel = document.getElementById("select-container");
|
|
},
|
|
|
|
get _textbox() {
|
|
delete this._textbox;
|
|
return this._textbox = document.getElementById("select-helper-textbox");
|
|
},
|
|
|
|
show: function selectHelperShow(aList) {
|
|
this.showFilter = false;
|
|
this._textbox.blur();
|
|
this._list = aList;
|
|
|
|
this._container = document.getElementById("select-list");
|
|
this._container.setAttribute("multiple", aList.multiple ? "true" : "false");
|
|
|
|
this._selectedIndexes = this._getSelectedIndexes();
|
|
let firstSelected = null;
|
|
|
|
// Using a fragment prevent us to hang on huge list
|
|
let fragment = document.createDocumentFragment();
|
|
let choices = aList.choices;
|
|
for (let i = 0; i < choices.length; i++) {
|
|
let choice = choices[i];
|
|
let item = document.createElement("option");
|
|
item.className = "chrome-select-option";
|
|
item.setAttribute("label", choice.text);
|
|
choice.disabled ? item.setAttribute("disabled", choice.disabled)
|
|
: item.removeAttribute("disabled");
|
|
fragment.appendChild(item);
|
|
|
|
if (choice.group) {
|
|
item.classList.add("optgroup");
|
|
continue;
|
|
}
|
|
|
|
item.optionIndex = choice.optionIndex;
|
|
item.choiceIndex = i;
|
|
|
|
if (choice.inGroup)
|
|
item.classList.add("in-optgroup");
|
|
|
|
if (choice.selected) {
|
|
item.setAttribute("selected", "true");
|
|
firstSelected = firstSelected || item;
|
|
}
|
|
}
|
|
this._container.appendChild(fragment);
|
|
|
|
this._panel.hidden = false;
|
|
this._panel.height = this._panel.getBoundingClientRect().height;
|
|
|
|
if (!this._docked)
|
|
BrowserUI.pushPopup(this, this._panel);
|
|
|
|
this._scrollElementIntoView(firstSelected);
|
|
|
|
this._container.addEventListener("click", this, false);
|
|
this._panel.addEventListener("overflow", this, true);
|
|
},
|
|
|
|
_showFilter: false,
|
|
get showFilter() {
|
|
return this._showFilter;
|
|
},
|
|
|
|
set showFilter(val) {
|
|
this._showFilter = val;
|
|
if (!this._panel.hidden)
|
|
this._textbox.hidden = !val;
|
|
},
|
|
|
|
dock: function selectHelperDock(aContainer) {
|
|
aContainer.insertBefore(this._panel, aContainer.lastChild);
|
|
this.resize();
|
|
this._docked = true;
|
|
},
|
|
|
|
undock: function selectHelperUndock() {
|
|
let rootNode = Elements.stack;
|
|
rootNode.insertBefore(this._panel, rootNode.lastChild);
|
|
this._panel.style.maxHeight = "";
|
|
this._docked = false;
|
|
},
|
|
|
|
reset: function selectHelperReset() {
|
|
this._updateControl();
|
|
let empty = this._container.cloneNode(false);
|
|
this._container.parentNode.replaceChild(empty, this._container);
|
|
this._container = empty;
|
|
this._list = null;
|
|
this._selectedIndexes = null;
|
|
this._panel.height = "";
|
|
this._textbox.value = "";
|
|
},
|
|
|
|
resize: function selectHelperResize() {
|
|
this._panel.style.maxHeight = (window.innerHeight / 1.8) + "px";
|
|
},
|
|
|
|
hide: function selectHelperResize() {
|
|
if (!this._list)
|
|
return;
|
|
|
|
this.showFilter = false;
|
|
this._container.removeEventListener("click", this, false);
|
|
this._panel.removeEventListener("overflow", this, true);
|
|
|
|
this._panel.hidden = true;
|
|
|
|
if (this._docked)
|
|
this.undock();
|
|
else
|
|
BrowserUI.popPopup(this);
|
|
|
|
this.reset();
|
|
},
|
|
|
|
filter: function selectHelperFilter(aValue) {
|
|
let reg = new RegExp(aValue, "gi");
|
|
let options = this._container.childNodes;
|
|
for (let i = 0; i < options.length; i++) {
|
|
let option = options[i];
|
|
option.getAttribute("label").match(reg) ? option.removeAttribute("filtered")
|
|
: option.setAttribute("filtered", "true");
|
|
}
|
|
},
|
|
|
|
unselectAll: function selectHelperUnselectAll() {
|
|
if (!this._list)
|
|
return;
|
|
|
|
let choices = this._list.choices;
|
|
this._forEachOption(function(aItem, aIndex) {
|
|
aItem.selected = false;
|
|
choices[aIndex].selected = false;
|
|
});
|
|
},
|
|
|
|
selectByIndex: function selectHelperSelectByIndex(aIndex) {
|
|
if (!this._list)
|
|
return;
|
|
|
|
let choices = this._list.choices;
|
|
for (let i = 0; i < this._container.childNodes.length; i++) {
|
|
let option = this._container.childNodes[i];
|
|
if (option.optionIndex == aIndex) {
|
|
option.selected = true;
|
|
this._choices[i].selected = true;
|
|
this._scrollElementIntoView(option);
|
|
break;
|
|
}
|
|
}
|
|
},
|
|
|
|
_getSelectedIndexes: function _selectHelperGetSelectedIndexes() {
|
|
let indexes = [];
|
|
if (!this._list)
|
|
return indexes;
|
|
|
|
let choices = this._list.choices;
|
|
let choiceLength = choices.length;
|
|
for (let i = 0; i < choiceLength; i++) {
|
|
let choice = choices[i];
|
|
if (choice.selected)
|
|
indexes.push(choice.optionIndex);
|
|
}
|
|
return indexes;
|
|
},
|
|
|
|
_scrollElementIntoView: function _selectHelperScrollElementIntoView(aElement) {
|
|
if (!aElement)
|
|
return;
|
|
|
|
let index = -1;
|
|
this._forEachOption(
|
|
function(aItem, aIndex) {
|
|
if (aElement.optionIndex == aItem.optionIndex)
|
|
index = aIndex;
|
|
}
|
|
);
|
|
|
|
if (index == -1)
|
|
return;
|
|
|
|
let scrollBoxObject = this._container.boxObject.QueryInterface(Ci.nsIScrollBoxObject);
|
|
let itemHeight = aElement.getBoundingClientRect().height;
|
|
let visibleItemsCount = this._container.boxObject.height / itemHeight;
|
|
if ((index + 1) > visibleItemsCount) {
|
|
let delta = Math.ceil(visibleItemsCount / 2);
|
|
scrollBoxObject.scrollTo(0, ((index + 1) - delta) * itemHeight);
|
|
}
|
|
else {
|
|
scrollBoxObject.scrollTo(0, 0);
|
|
}
|
|
},
|
|
|
|
_forEachOption: function _selectHelperForEachOption(aCallback) {
|
|
let children = this._container.children;
|
|
for (let i = 0; i < children.length; i++) {
|
|
let item = children[i];
|
|
if (!item.hasOwnProperty("optionIndex"))
|
|
continue;
|
|
aCallback(item, i);
|
|
}
|
|
},
|
|
|
|
_updateControl: function _selectHelperUpdateControl() {
|
|
let currentSelectedIndexes = this._getSelectedIndexes();
|
|
|
|
let isIdentical = (this._selectedIndexes && this._selectedIndexes.length == currentSelectedIndexes.length);
|
|
if (isIdentical) {
|
|
for (let i = 0; i < currentSelectedIndexes.length; i++) {
|
|
if (currentSelectedIndexes[i] != this._selectedIndexes[i]) {
|
|
isIdentical = false;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (isIdentical)
|
|
return;
|
|
|
|
Browser.selectedBrowser.messageManager.sendAsyncMessage("FormAssist:ChoiceChange", { });
|
|
},
|
|
|
|
handleEvent: function selectHelperHandleEvent(aEvent) {
|
|
switch (aEvent.type) {
|
|
case "click":
|
|
let item = aEvent.target;
|
|
if (item && item.hasOwnProperty("optionIndex")) {
|
|
if (this._list.multiple) {
|
|
// Toggle the item state
|
|
item.selected = !item.selected;
|
|
}
|
|
else {
|
|
this.unselectAll();
|
|
|
|
// Select the new one and update the control
|
|
item.selected = true;
|
|
}
|
|
this.onSelect(item.optionIndex, item.selected, !this._list.multiple);
|
|
}
|
|
break;
|
|
case "overflow":
|
|
if (!this._textbox.value)
|
|
this.showFilter = true;
|
|
break;
|
|
}
|
|
},
|
|
|
|
onSelect: function selectHelperOnSelect(aIndex, aSelected, aClearAll) {
|
|
let json = {
|
|
index: aIndex,
|
|
selected: aSelected,
|
|
clearAll: aClearAll
|
|
};
|
|
Browser.selectedBrowser.messageManager.sendAsyncMessage("FormAssist:ChoiceSelect", json);
|
|
|
|
// http://www.whatwg.org/specs/web-apps/current-work/multipage/the-button-element.html#the-select-element
|
|
if (!this._list.multiple) {
|
|
this._updateControl();
|
|
// Update the selectedIndex so the field will fire a new change event if
|
|
// needed
|
|
this._selectedIndexes = [aIndex];
|
|
}
|
|
|
|
}
|
|
};
|