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