OLD | NEW |
---|---|
(Empty) | |
1 "use strict"; | |
2 /* | |
tkent
2014/12/15 09:26:05
Use the 3-line header.
keishi
2014/12/16 03:53:24
Done.
| |
3 * Copyright (C) 2012 Google Inc. All rights reserved. | |
4 * | |
5 * Redistribution and use in source and binary forms, with or without | |
6 * modification, are permitted provided that the following conditions | |
7 * are met: | |
8 * 1. Redistributions of source code must retain the above copyright | |
9 * notice, this list of conditions and the following disclaimer. | |
10 * 2. Redistributions in binary form must reproduce the above copyright | |
11 * notice, this list of conditions and the following disclaimer in the | |
12 * documentation and/or other materials provided with the distribution. | |
13 * | |
14 * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' AND AN Y | |
15 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED | |
16 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE | |
17 * DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS BE LIABLE FOR AN Y | |
18 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES | |
19 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; | |
20 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND O N | |
21 * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | |
22 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS | |
23 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | |
24 */ | |
25 | |
26 var global = { | |
27 argumentsReceived: false, | |
28 params: null, | |
29 picker: null | |
30 }; | |
31 | |
32 /** | |
33 * @param {Event} event | |
34 */ | |
35 function handleMessage(event) { | |
36 window.removeEventListener("message", handleMessage, false); | |
37 initialize(JSON.parse(event.data)); | |
38 global.argumentsReceived = true; | |
39 } | |
40 | |
41 /** | |
42 * @param {!Object} args | |
43 */ | |
44 function initialize(args) { | |
45 global.params = args; | |
46 var main = $("main"); | |
47 main.innerHTML = ""; | |
48 global.picker = new ListPicker(main, args); | |
49 } | |
50 | |
51 function handleArgumentsTimeout() { | |
52 if (global.argumentsReceived) | |
53 return; | |
54 var args = { | |
55 }; | |
56 initialize(args); | |
57 } | |
58 | |
59 /** | |
60 * @constructor | |
61 * @param {!Element} element | |
62 * @param {!Object} config | |
63 */ | |
64 function ListPicker(element, config) { | |
65 Picker.call(this, element, config); | |
66 this._selectElement = createElement("select"); | |
67 this._element.appendChild(this._selectElement); | |
68 this._layout(); | |
69 this._selectElement.focus(); | |
70 this._selectElement.addEventListener("mouseover", this._handleMouseOver.bind (this), false); | |
71 this._selectElement.addEventListener("mouseup", this._handleMouseUp.bind(thi s), false); | |
72 this._selectElement.addEventListener("keydown", this._handleKeyDown.bind(thi s), false); | |
73 this._selectElement.addEventListener("input", this._handleInput.bind(this), false); | |
74 window.addEventListener("message", this._handleWindowMessage.bind(this), fal se); | |
75 window.addEventListener("mousemove", this._handleWindowMouseMove.bind(this), false); | |
76 window.addEventListener("mousemove", this._handleWindowMouseMove.bind(this), false); | |
77 this.lastMousePositionX = Infinity; | |
78 this.lastMousePositionY = Infinity; | |
79 | |
80 // Not sure why but we need to delay this call so that offsetHeight is | |
81 // accurate. We wait for the window to resize to work around an issue | |
82 // of immediate resize requests getting mixed up. | |
83 this._handleWindowDidHideBound = this._handleWindowDidHide.bind(this); | |
84 window.addEventListener("didHide", this._handleWindowDidHideBound, false); | |
85 hideWindow(); | |
86 } | |
87 ListPicker.prototype = Object.create(Picker.prototype); | |
88 | |
89 ListPicker.prototype._handleWindowDidHide = function() { | |
90 this._fixWindowSize(); | |
91 var selectedOption = this._selectElement.options[this._selectElement.selecte dIndex]; | |
92 selectedOption.scrollIntoView(false); | |
93 window.removeEventListener("didHide", this._handleWindowDidHideBound, false) ; | |
94 }; | |
95 | |
96 ListPicker.prototype._handleWindowMessage = function(event) { | |
97 eval(event.data); | |
98 if (window.updateData.type === "update") | |
99 this._update(window.updateData); | |
100 delete window.updateData; | |
101 }; | |
102 | |
103 ListPicker.prototype._handleWindowMouseMove = function (event) { | |
104 this.lastMousePositionX = event.clientX; | |
105 this.lastMousePositionY = event.clientY; | |
106 }; | |
107 | |
108 ListPicker.prototype._handleMouseOver = function(event) { | |
109 if (event.toElement.tagName !== "OPTION") | |
110 return; | |
111 var savedScrollTop = this._selectElement.scrollTop; | |
112 event.toElement.selected = true; | |
113 this._selectElement.scrollTop = savedScrollTop; | |
114 }; | |
115 | |
116 ListPicker.prototype._handleMouseUp = function(event) { | |
117 if (event.target.tagName !== "OPTION") | |
118 return; | |
119 window.pagePopupController.setValueAndClosePopup(0, this._selectElement.valu e); | |
120 }; | |
121 | |
122 ListPicker.prototype._handleInput = function(event) { | |
123 window.pagePopupController.setValue(this._selectElement.value); | |
124 }; | |
125 | |
126 ListPicker.prototype._handleKeyDown = function(event) { | |
127 var key = event.keyIdentifier; | |
128 if (key === "U+001B") { // ESC | |
129 window.pagePopupController.closePopup(); | |
130 event.preventDefault(); | |
131 } else if (key === "Enter") { | |
132 window.pagePopupController.setValueAndClosePopup(0, this._selectElement. value); | |
133 event.preventDefault(); | |
134 } else if (event.altKey && (key === "Down" || key === "Up")) { | |
135 // FIXME: We need to add a delay here because, if we do it immediately | |
136 // the key press event will be handled by HTMLSelectElement and this | |
137 // popup will be reopened. | |
138 setTimeout(function () { | |
139 window.pagePopupController.closePopup(); | |
140 }, 0); | |
141 event.preventDefault(); | |
142 } | |
143 }; | |
144 | |
145 ListPicker.prototype._fixWindowSize = function() { | |
146 this._selectElement.style.height = ""; | |
147 this._selectElement.size = 20; | |
148 var maxHeight = this._selectElement.offsetHeight; | |
149 this._selectElement.style.height = "0"; | |
150 var heightOutsideOfContent = this._selectElement.offsetHeight - this._select Element.clientHeight; | |
151 var desiredWindowHeight = this._selectElement.scrollHeight + heightOutsideOf Content; | |
152 this._selectElement.style.height = desiredWindowHeight + "px"; | |
153 // FIXME: scrollHeight returns floored value so we needed this check. | |
154 if (this._hasVerticalScrollbar()) | |
155 desiredWindowHeight += 1; | |
156 if (desiredWindowHeight > maxHeight) | |
157 desiredWindowHeight = maxHeight; | |
158 var desiredWindowWidth = Math.max(this._config.anchorRectInScreen.width, thi s._selectElement.offsetWidth); | |
159 var windowRect = adjustWindowRect(desiredWindowWidth, desiredWindowHeight, t his._selectElement.offsetWidth, 0); | |
160 this._selectElement.style.width = windowRect.width + "px"; | |
161 this._selectElement.style.height = windowRect.height + "px"; | |
162 this._element.style.height = windowRect.height + "px"; | |
163 setWindowRect(windowRect); | |
164 }; | |
165 | |
166 ListPicker.prototype._hasVerticalScrollbar = function() { | |
167 return this._selectElement.scrollWidth > this._selectElement.clientWidth; | |
168 }; | |
169 | |
170 ListPicker.prototype._listItemCount = function() { | |
171 return this._selectElement.querySelectorAll("option,optgroup,hr").length; | |
172 }; | |
173 | |
174 ListPicker.prototype._layout = function() { | |
175 for (var i = 0; i < this._config.children.length; ++i) { | |
176 this._selectElement.appendChild(this._createItemElement(this._config.chi ldren[i])); | |
177 } | |
178 this._selectElement.value = this._config.selectedIndex; | |
179 }; | |
180 | |
181 ListPicker.prototype._update = function(data) { | |
182 this._config.children = data.children; | |
183 var oldValue = this._selectElement.value; | |
184 while (this._selectElement.firstChild) { | |
185 this._selectElement.removeChild(this._selectElement.firstChild); | |
186 } | |
187 for (var i = 0; i < this._config.children.length; ++i) { | |
188 this._selectElement.appendChild(this._createItemElement(this._config.chi ldren[i])); | |
189 } | |
190 this._selectElement.value = this._config.selectedIndex; | |
191 var elementUnderMouse = document.elementFromPoint(this.lastMousePositionX, t his.lastMousePositionY); | |
192 var optionUnderMouse = elementUnderMouse && elementUnderMouse.closest("optio n"); | |
193 if (optionUnderMouse) | |
194 optionUnderMouse.selected = true; | |
195 else | |
196 this._selectElement.value = oldValue; | |
197 this._fixWindowSize(); | |
198 }; | |
199 | |
200 ListPicker.prototype._createItemElement = function(config) { | |
201 if (config.type === "option") { | |
202 var option = createElement("option"); | |
203 option.appendChild(document.createTextNode(config.label)); | |
204 option.value = config.value; | |
205 option.title = config.title; | |
206 option.disabled = config.disabled; | |
207 option.setAttribute("aria-label", config.ariaLabel); | |
208 this._applyItemStyle(option, config.style); | |
209 this._selectElement.appendChild(option); | |
210 return option; | |
211 } else if (config.type === "optgroup") { | |
212 var optgroup = createElement("optgroup"); | |
213 optgroup.label = config.label; | |
214 optgroup.title = config.title; | |
215 optgroup.disabled = config.disabled; | |
216 optgroup.setAttribute("aria-label", config.ariaLabel); | |
217 this._applyItemStyle(optgroup, config.style); | |
218 for (var i = 0; i < config.children.length; ++i) { | |
219 optgroup.appendChild(this._createItemElement(config.children[i])); | |
220 } | |
221 this._selectElement.appendChild(optgroup); | |
222 return optgroup; | |
223 } else if (config.type === "separator") { | |
224 var hr = createElement("hr"); | |
225 hr.title = config.title; | |
226 hr.disabled = config.disabled; | |
227 hr.setAttribute("aria-label", config.ariaLabel); | |
228 this._applyItemStyle(hr, config.style); | |
229 return hr; | |
230 } | |
231 }; | |
232 | |
233 ListPicker.prototype._applyItemStyle = function(element, styleConfig) { | |
234 element.style.color = styleConfig.color; | |
235 element.style.backgroundColor = styleConfig.backgroundColor; | |
236 element.style.fontSize = styleConfig.fontSize + "px"; | |
237 element.style.fontWeight = styleConfig.fontWeight; | |
238 element.style.fontFamily = styleConfig.fontFamily.join(","); | |
239 element.style.visibility = styleConfig.visibility; | |
240 element.style.display = styleConfig.display; | |
241 element.style.direction = styleConfig.direction; | |
242 element.style.unicodeBidi = styleConfig.unicodeBidi; | |
243 }; | |
244 | |
245 if (window.dialogArguments) { | |
246 initialize(dialogArguments); | |
247 } else { | |
248 window.addEventListener("message", handleMessage, false); | |
249 window.setTimeout(handleArgumentsTimeout, 1000); | |
250 } | |
OLD | NEW |