Wed, 31 Dec 2014 06:55:50 +0100
Added tag UPSTREAM_283F7C6 for changeset ca08bd8f51b2
michael@0 | 1 | /* This Source Code Form is subject to the terms of the Mozilla Public |
michael@0 | 2 | * License, v. 2.0. If a copy of the MPL was not distributed with this |
michael@0 | 3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ |
michael@0 | 4 | |
michael@0 | 5 | "use strict"; |
michael@0 | 6 | |
michael@0 | 7 | /** |
michael@0 | 8 | * SelectHelperUI: Provides an interface for making a choice in a list. |
michael@0 | 9 | * Supports simultaneous selection of choices and group headers. |
michael@0 | 10 | */ |
michael@0 | 11 | var SelectHelperUI = { |
michael@0 | 12 | _list: null, |
michael@0 | 13 | |
michael@0 | 14 | get _container() { |
michael@0 | 15 | delete this._container; |
michael@0 | 16 | return this._container = document.getElementById("select-container"); |
michael@0 | 17 | }, |
michael@0 | 18 | |
michael@0 | 19 | get _listbox() { |
michael@0 | 20 | delete this._listbox; |
michael@0 | 21 | return this._listbox = document.getElementById("select-commands"); |
michael@0 | 22 | }, |
michael@0 | 23 | |
michael@0 | 24 | get _menuPopup() { |
michael@0 | 25 | let popup = document.getElementById("select-popup"); |
michael@0 | 26 | delete this._menuPopup; |
michael@0 | 27 | return this._menuPopup = new MenuPopup(this._container, popup); |
michael@0 | 28 | }, |
michael@0 | 29 | |
michael@0 | 30 | show: function selectHelperShow(aList, aTitle, aRect) { |
michael@0 | 31 | if (this._list) { |
michael@0 | 32 | this.reset(); |
michael@0 | 33 | } |
michael@0 | 34 | |
michael@0 | 35 | this._list = aList; |
michael@0 | 36 | |
michael@0 | 37 | this._listbox.setAttribute("seltype", aList.multiple ? "multiple" : "single"); |
michael@0 | 38 | |
michael@0 | 39 | let firstSelected = null; |
michael@0 | 40 | |
michael@0 | 41 | // Using a fragment prevent us to hang on huge list |
michael@0 | 42 | let fragment = document.createDocumentFragment(); |
michael@0 | 43 | let choices = aList.choices; |
michael@0 | 44 | let selectedItems = []; |
michael@0 | 45 | for (let i = 0; i < choices.length; i++) { |
michael@0 | 46 | let choice = choices[i]; |
michael@0 | 47 | let item = document.createElement("richlistitem"); |
michael@0 | 48 | let label = document.createElement("label"); |
michael@0 | 49 | |
michael@0 | 50 | item.setAttribute("class", "option-command listitem-iconic"); |
michael@0 | 51 | item.setAttribute("flex", "1"); |
michael@0 | 52 | item.setAttribute("crop", "center"); |
michael@0 | 53 | label.setAttribute("value", choice.text); |
michael@0 | 54 | item.appendChild(label); |
michael@0 | 55 | item.setAttribute("oldstate", "false"); |
michael@0 | 56 | choice.disabled ? item.setAttribute("disabled", "true") |
michael@0 | 57 | : item.removeAttribute("disabled"); |
michael@0 | 58 | |
michael@0 | 59 | fragment.appendChild(item); |
michael@0 | 60 | |
michael@0 | 61 | if (choice.group) { |
michael@0 | 62 | item.classList.add("optgroup"); |
michael@0 | 63 | continue; |
michael@0 | 64 | } |
michael@0 | 65 | |
michael@0 | 66 | item.optionIndex = choice.optionIndex; |
michael@0 | 67 | item.choiceIndex = i; |
michael@0 | 68 | |
michael@0 | 69 | if (choice.inGroup) { |
michael@0 | 70 | item.classList.add("in-optgroup"); |
michael@0 | 71 | } |
michael@0 | 72 | |
michael@0 | 73 | if (choice.selected) { |
michael@0 | 74 | firstSelected = firstSelected || item; |
michael@0 | 75 | selectedItems.push(item); |
michael@0 | 76 | } |
michael@0 | 77 | } |
michael@0 | 78 | this._listbox.appendChild(fragment); |
michael@0 | 79 | |
michael@0 | 80 | this._container.addEventListener("click", this, false); |
michael@0 | 81 | window.addEventListener("MozPrecisePointer", this, false); |
michael@0 | 82 | this._menuPopup.show(this._positionOptions(aRect)); |
michael@0 | 83 | |
michael@0 | 84 | // Setup pre-selected items. Note, this has to happen after show. |
michael@0 | 85 | this._listbox.clearSelection(); |
michael@0 | 86 | for (let item of selectedItems) { |
michael@0 | 87 | this._listbox.addItemToSelection(item); |
michael@0 | 88 | item.setAttribute("oldstate", "true"); |
michael@0 | 89 | } |
michael@0 | 90 | this._listbox.ensureElementIsVisible(firstSelected); |
michael@0 | 91 | }, |
michael@0 | 92 | |
michael@0 | 93 | reset: function selectHelperReset() { |
michael@0 | 94 | this._updateControl(); |
michael@0 | 95 | while (this._listbox.hasChildNodes()) |
michael@0 | 96 | this._listbox.removeChild(this._listbox.lastChild); |
michael@0 | 97 | this._list = null; |
michael@0 | 98 | }, |
michael@0 | 99 | |
michael@0 | 100 | hide: function selectHelperHide() { |
michael@0 | 101 | if (!this._list) |
michael@0 | 102 | return; |
michael@0 | 103 | |
michael@0 | 104 | this._container.removeEventListener("click", this, false); |
michael@0 | 105 | window.removeEventListener("MozPrecisePointer", this, false); |
michael@0 | 106 | this._menuPopup.hide(); |
michael@0 | 107 | this.reset(); |
michael@0 | 108 | }, |
michael@0 | 109 | |
michael@0 | 110 | _positionOptions: function _positionOptions(aRect) { |
michael@0 | 111 | let browser = Browser.selectedBrowser; |
michael@0 | 112 | let p0 = browser.ptBrowserToClient(aRect.left, aRect.top); |
michael@0 | 113 | let p1 = browser.ptBrowserToClient(aRect.right, aRect.bottom); |
michael@0 | 114 | |
michael@0 | 115 | return { |
michael@0 | 116 | xPos: p0.x, |
michael@0 | 117 | yPos: p1.y, |
michael@0 | 118 | bottomAligned: false, |
michael@0 | 119 | leftAligned: true |
michael@0 | 120 | }; |
michael@0 | 121 | }, |
michael@0 | 122 | |
michael@0 | 123 | _updateControl: function _selectHelperUpdateControl() { |
michael@0 | 124 | Browser.selectedBrowser.messageManager.sendAsyncMessage("FormAssist:ChoiceChange", { }); |
michael@0 | 125 | }, |
michael@0 | 126 | |
michael@0 | 127 | handleEvent: function selectHelperHandleEvent(aEvent) { |
michael@0 | 128 | switch (aEvent.type) { |
michael@0 | 129 | case "MozPrecisePointer": |
michael@0 | 130 | this.hide(); |
michael@0 | 131 | break; |
michael@0 | 132 | case "click": |
michael@0 | 133 | let item = aEvent.target; |
michael@0 | 134 | if (item && item.hasOwnProperty("optionIndex")) { |
michael@0 | 135 | if (this._list.multiple) { |
michael@0 | 136 | // item.selected is always true here since that's how richlistbox handles |
michael@0 | 137 | // mouse click events. We track our own state so that we can toggle here |
michael@0 | 138 | // on click events. Iniial 'oldstate' values are setup in show above. |
michael@0 | 139 | if (item.getAttribute("oldstate") == "true") { |
michael@0 | 140 | item.setAttribute("oldstate", "false"); |
michael@0 | 141 | } else { |
michael@0 | 142 | item.setAttribute("oldstate", "true"); |
michael@0 | 143 | } |
michael@0 | 144 | // Fix up selected items - richlistbox will clear selection on click events |
michael@0 | 145 | // so we need to set selection on the items the user has previously chosen. |
michael@0 | 146 | this._listbox.clearSelection(); |
michael@0 | 147 | for (let node of this._listbox.childNodes) { |
michael@0 | 148 | if (node.getAttribute("oldstate") == "true") { |
michael@0 | 149 | this._listbox.addItemToSelection(node); |
michael@0 | 150 | } |
michael@0 | 151 | } |
michael@0 | 152 | } |
michael@0 | 153 | // Let the form element know we've added or removed a selected item. |
michael@0 | 154 | this.onSelect(item.optionIndex, item.selected); |
michael@0 | 155 | } |
michael@0 | 156 | break; |
michael@0 | 157 | } |
michael@0 | 158 | }, |
michael@0 | 159 | |
michael@0 | 160 | onSelect: function selectHelperOnSelect(aIndex, aSelected) { |
michael@0 | 161 | Browser.selectedBrowser.messageManager.sendAsyncMessage("FormAssist:ChoiceSelect", { |
michael@0 | 162 | index: aIndex, |
michael@0 | 163 | selected: aSelected |
michael@0 | 164 | }); |
michael@0 | 165 | if (!this._list.multiple) { |
michael@0 | 166 | this.hide(); |
michael@0 | 167 | } |
michael@0 | 168 | } |
michael@0 | 169 | }; |