OLD | NEW |
(Empty) | |
| 1 "use strict"; |
| 2 // Copyright (c) 2014 The Chromium Authors. All rights reserved. |
| 3 // Use of this source code is governed by a BSD-style license that can be |
| 4 // found in the LICENSE file. |
| 5 |
| 6 var global = { |
| 7 argumentsReceived: false, |
| 8 params: null, |
| 9 picker: null |
| 10 }; |
| 11 |
| 12 /** |
| 13 * @param {Event} event |
| 14 */ |
| 15 function handleMessage(event) { |
| 16 window.removeEventListener("message", handleMessage, false); |
| 17 initialize(JSON.parse(event.data)); |
| 18 global.argumentsReceived = true; |
| 19 } |
| 20 |
| 21 /** |
| 22 * @param {!Object} args |
| 23 */ |
| 24 function initialize(args) { |
| 25 global.params = args; |
| 26 var main = $("main"); |
| 27 main.innerHTML = ""; |
| 28 global.picker = new ListPicker(main, args); |
| 29 } |
| 30 |
| 31 function handleArgumentsTimeout() { |
| 32 if (global.argumentsReceived) |
| 33 return; |
| 34 initialize({}); |
| 35 } |
| 36 |
| 37 /** |
| 38 * @constructor |
| 39 * @param {!Element} element |
| 40 * @param {!Object} config |
| 41 */ |
| 42 function ListPicker(element, config) { |
| 43 Picker.call(this, element, config); |
| 44 this._selectElement = createElement("select"); |
| 45 this._element.appendChild(this._selectElement); |
| 46 this._layout(); |
| 47 this._selectElement.focus(); |
| 48 this._selectElement.addEventListener("mouseover", this._handleMouseOver.bind
(this), false); |
| 49 this._selectElement.addEventListener("mouseup", this._handleMouseUp.bind(thi
s), false); |
| 50 this._selectElement.addEventListener("keydown", this._handleKeyDown.bind(thi
s), false); |
| 51 this._selectElement.addEventListener("change", this._handleChange.bind(this)
, false); |
| 52 window.addEventListener("message", this._handleWindowMessage.bind(this), fal
se); |
| 53 window.addEventListener("mousemove", this._handleWindowMouseMove.bind(this),
false); |
| 54 this.lastMousePositionX = Infinity; |
| 55 this.lastMousePositionY = Infinity; |
| 56 |
| 57 // Not sure why but we need to delay this call so that offsetHeight is |
| 58 // accurate. We wait for the window to resize to work around an issue |
| 59 // of immediate resize requests getting mixed up. |
| 60 this._handleWindowDidHideBound = this._handleWindowDidHide.bind(this); |
| 61 window.addEventListener("didHide", this._handleWindowDidHideBound, false); |
| 62 hideWindow(); |
| 63 } |
| 64 ListPicker.prototype = Object.create(Picker.prototype); |
| 65 |
| 66 ListPicker.prototype._handleWindowDidHide = function() { |
| 67 this._fixWindowSize(); |
| 68 var selectedOption = this._selectElement.options[this._selectElement.selecte
dIndex]; |
| 69 selectedOption.scrollIntoView(false); |
| 70 window.removeEventListener("didHide", this._handleWindowDidHideBound, false)
; |
| 71 }; |
| 72 |
| 73 ListPicker.prototype._handleWindowMessage = function(event) { |
| 74 eval(event.data); |
| 75 if (window.updateData.type === "update") |
| 76 this._update(window.updateData); |
| 77 delete window.updateData; |
| 78 }; |
| 79 |
| 80 ListPicker.prototype._handleWindowMouseMove = function (event) { |
| 81 this.lastMousePositionX = event.clientX; |
| 82 this.lastMousePositionY = event.clientY; |
| 83 }; |
| 84 |
| 85 ListPicker.prototype._handleMouseOver = function(event) { |
| 86 if (event.toElement.tagName !== "OPTION") |
| 87 return; |
| 88 var savedScrollTop = this._selectElement.scrollTop; |
| 89 event.toElement.selected = true; |
| 90 this._selectElement.scrollTop = savedScrollTop; |
| 91 }; |
| 92 |
| 93 ListPicker.prototype._handleMouseUp = function(event) { |
| 94 if (event.target.tagName !== "OPTION") |
| 95 return; |
| 96 window.pagePopupController.setValueAndClosePopup(0, this._selectElement.valu
e); |
| 97 }; |
| 98 |
| 99 ListPicker.prototype._handleChange = function(event) { |
| 100 window.pagePopupController.setValue(this._selectElement.value); |
| 101 }; |
| 102 |
| 103 ListPicker.prototype._handleKeyDown = function(event) { |
| 104 var key = event.keyIdentifier; |
| 105 if (key === "U+001B") { // ESC |
| 106 window.pagePopupController.closePopup(); |
| 107 event.preventDefault(); |
| 108 } else if (key === "U+0009" /* TAB */ || key === "Enter") { |
| 109 window.pagePopupController.setValueAndClosePopup(0, this._selectElement.
value); |
| 110 event.preventDefault(); |
| 111 } else if (event.altKey && (key === "Down" || key === "Up")) { |
| 112 // We need to add a delay here because, if we do it immediately the key |
| 113 // press event will be handled by HTMLSelectElement and this popup will |
| 114 // be reopened. |
| 115 setTimeout(function () { |
| 116 window.pagePopupController.closePopup(); |
| 117 }, 0); |
| 118 event.preventDefault(); |
| 119 } else { |
| 120 // After a key press, we need to call setValue to reflect the selection |
| 121 // to the owner element. We can handle most cases with the change |
| 122 // event. But we need to call setValue even when the selection hasn't |
| 123 // changed. So we call it here too. setValue will be called twice for |
| 124 // some key presses but it won't matter. |
| 125 window.pagePopupController.setValue(this._selectElement.value); |
| 126 } |
| 127 }; |
| 128 |
| 129 ListPicker.prototype._fixWindowSize = function() { |
| 130 this._selectElement.style.height = ""; |
| 131 this._selectElement.size = 20; |
| 132 var maxHeight = this._selectElement.offsetHeight; |
| 133 this._selectElement.style.height = "0"; |
| 134 var heightOutsideOfContent = this._selectElement.offsetHeight - this._select
Element.clientHeight; |
| 135 var desiredWindowHeight = this._selectElement.scrollHeight + heightOutsideOf
Content; |
| 136 this._selectElement.style.height = desiredWindowHeight + "px"; |
| 137 // scrollHeight returns floored value so we needed this check. |
| 138 if (this._hasVerticalScrollbar()) |
| 139 desiredWindowHeight += 1; |
| 140 if (desiredWindowHeight > maxHeight) |
| 141 desiredWindowHeight = maxHeight; |
| 142 var desiredWindowWidth = Math.max(this._config.anchorRectInScreen.width, thi
s._selectElement.offsetWidth); |
| 143 var windowRect = adjustWindowRect(desiredWindowWidth, desiredWindowHeight, t
his._selectElement.offsetWidth, 0); |
| 144 this._selectElement.style.width = windowRect.width + "px"; |
| 145 this._selectElement.style.height = windowRect.height + "px"; |
| 146 this._element.style.height = windowRect.height + "px"; |
| 147 setWindowRect(windowRect); |
| 148 }; |
| 149 |
| 150 ListPicker.prototype._hasVerticalScrollbar = function() { |
| 151 return this._selectElement.scrollWidth > this._selectElement.clientWidth; |
| 152 }; |
| 153 |
| 154 ListPicker.prototype._listItemCount = function() { |
| 155 return this._selectElement.querySelectorAll("option,optgroup,hr").length; |
| 156 }; |
| 157 |
| 158 ListPicker.prototype._layout = function() { |
| 159 for (var i = 0; i < this._config.children.length; ++i) { |
| 160 this._selectElement.appendChild(this._createItemElement(this._config.chi
ldren[i])); |
| 161 } |
| 162 this._selectElement.value = this._config.selectedIndex; |
| 163 }; |
| 164 |
| 165 ListPicker.prototype._update = function(data) { |
| 166 this._config.children = data.children; |
| 167 var oldValue = this._selectElement.value; |
| 168 while (this._selectElement.firstChild) { |
| 169 this._selectElement.removeChild(this._selectElement.firstChild); |
| 170 } |
| 171 for (var i = 0; i < this._config.children.length; ++i) { |
| 172 this._selectElement.appendChild(this._createItemElement(this._config.chi
ldren[i])); |
| 173 } |
| 174 this._selectElement.value = this._config.selectedIndex; |
| 175 var elementUnderMouse = document.elementFromPoint(this.lastMousePositionX, t
his.lastMousePositionY); |
| 176 var optionUnderMouse = elementUnderMouse && elementUnderMouse.closest("optio
n"); |
| 177 if (optionUnderMouse) |
| 178 optionUnderMouse.selected = true; |
| 179 else |
| 180 this._selectElement.value = oldValue; |
| 181 this._fixWindowSize(); |
| 182 }; |
| 183 |
| 184 ListPicker.prototype._createItemElement = function(config) { |
| 185 if (config.type === "option") { |
| 186 var option = createElement("option"); |
| 187 option.appendChild(document.createTextNode(config.label)); |
| 188 option.value = config.value; |
| 189 option.title = config.title; |
| 190 option.disabled = config.disabled; |
| 191 option.setAttribute("aria-label", config.ariaLabel); |
| 192 this._applyItemStyle(option, config.style); |
| 193 this._selectElement.appendChild(option); |
| 194 return option; |
| 195 } else if (config.type === "optgroup") { |
| 196 var optgroup = createElement("optgroup"); |
| 197 optgroup.label = config.label; |
| 198 optgroup.title = config.title; |
| 199 optgroup.disabled = config.disabled; |
| 200 optgroup.setAttribute("aria-label", config.ariaLabel); |
| 201 this._applyItemStyle(optgroup, config.style); |
| 202 for (var i = 0; i < config.children.length; ++i) { |
| 203 optgroup.appendChild(this._createItemElement(config.children[i])); |
| 204 } |
| 205 this._selectElement.appendChild(optgroup); |
| 206 return optgroup; |
| 207 } else if (config.type === "separator") { |
| 208 var hr = createElement("hr"); |
| 209 hr.title = config.title; |
| 210 hr.disabled = config.disabled; |
| 211 hr.setAttribute("aria-label", config.ariaLabel); |
| 212 this._applyItemStyle(hr, config.style); |
| 213 return hr; |
| 214 } |
| 215 }; |
| 216 |
| 217 ListPicker.prototype._applyItemStyle = function(element, styleConfig) { |
| 218 element.style.color = styleConfig.color; |
| 219 element.style.backgroundColor = styleConfig.backgroundColor; |
| 220 element.style.fontSize = styleConfig.fontSize + "px"; |
| 221 element.style.fontWeight = styleConfig.fontWeight; |
| 222 element.style.fontFamily = styleConfig.fontFamily.join(","); |
| 223 element.style.visibility = styleConfig.visibility; |
| 224 element.style.display = styleConfig.display; |
| 225 element.style.direction = styleConfig.direction; |
| 226 element.style.unicodeBidi = styleConfig.unicodeBidi; |
| 227 }; |
| 228 |
| 229 if (window.dialogArguments) { |
| 230 initialize(dialogArguments); |
| 231 } else { |
| 232 window.addEventListener("message", handleMessage, false); |
| 233 window.setTimeout(handleArgumentsTimeout, 1000); |
| 234 } |
OLD | NEW |