Chromium Code Reviews| 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 |