| OLD | NEW |
| (Empty) |
| 1 // Copyright (c) 2011 The Chromium Authors. All rights reserved. | |
| 2 // Use of this source code is governed by a BSD-style license that can be | |
| 3 // found in the LICENSE file. | |
| 4 | |
| 5 cr.define('options', function() { | |
| 6 const ArrayDataModel = cr.ui.ArrayDataModel; | |
| 7 const List = cr.ui.List; | |
| 8 const ListItem = cr.ui.ListItem; | |
| 9 | |
| 10 /** | |
| 11 * Creates a new autocomplete list item. | |
| 12 * @param {Object} pageInfo The page this item represents. | |
| 13 * @constructor | |
| 14 * @extends {cr.ui.ListItem} | |
| 15 */ | |
| 16 function AutocompleteListItem(pageInfo) { | |
| 17 var el = cr.doc.createElement('div'); | |
| 18 el.pageInfo_ = pageInfo; | |
| 19 AutocompleteListItem.decorate(el); | |
| 20 return el; | |
| 21 } | |
| 22 | |
| 23 /** | |
| 24 * Decorates an element as an autocomplete list item. | |
| 25 * @param {!HTMLElement} el The element to decorate. | |
| 26 */ | |
| 27 AutocompleteListItem.decorate = function(el) { | |
| 28 el.__proto__ = AutocompleteListItem.prototype; | |
| 29 el.decorate(); | |
| 30 }; | |
| 31 | |
| 32 AutocompleteListItem.prototype = { | |
| 33 __proto__: ListItem.prototype, | |
| 34 | |
| 35 /** @inheritDoc */ | |
| 36 decorate: function() { | |
| 37 ListItem.prototype.decorate.call(this); | |
| 38 | |
| 39 var title = this.pageInfo_['title']; | |
| 40 var url = this.pageInfo_['displayURL']; | |
| 41 var titleEl = this.ownerDocument.createElement('span'); | |
| 42 titleEl.className = 'title'; | |
| 43 titleEl.textContent = title || url; | |
| 44 this.appendChild(titleEl); | |
| 45 | |
| 46 if (title && title.length > 0 && url != title) { | |
| 47 var separatorEl = this.ownerDocument.createTextNode(' - '); | |
| 48 this.appendChild(separatorEl); | |
| 49 | |
| 50 var urlEl = this.ownerDocument.createElement('span'); | |
| 51 urlEl.className = 'url'; | |
| 52 urlEl.textContent = url; | |
| 53 this.appendChild(urlEl); | |
| 54 } | |
| 55 }, | |
| 56 }; | |
| 57 | |
| 58 /** | |
| 59 * Creates a new autocomplete list popup. | |
| 60 * @constructor | |
| 61 * @extends {cr.ui.List} | |
| 62 */ | |
| 63 var AutocompleteList = cr.ui.define('list'); | |
| 64 | |
| 65 AutocompleteList.prototype = { | |
| 66 __proto__: List.prototype, | |
| 67 | |
| 68 /** | |
| 69 * The text field the autocomplete popup is currently attached to, if any. | |
| 70 * @type {HTMLElement} | |
| 71 * @private | |
| 72 */ | |
| 73 targetInput_: null, | |
| 74 | |
| 75 /** | |
| 76 * Keydown event listener to attach to a text field. | |
| 77 * @type {Function} | |
| 78 * @private | |
| 79 */ | |
| 80 textFieldKeyHandler_: null, | |
| 81 | |
| 82 /** | |
| 83 * Input event listener to attach to a text field. | |
| 84 * @type {Function} | |
| 85 * @private | |
| 86 */ | |
| 87 textFieldInputHandler_: null, | |
| 88 | |
| 89 /** | |
| 90 * A function to call when new suggestions are needed. | |
| 91 * @type {Function} | |
| 92 * @private | |
| 93 */ | |
| 94 suggestionUpdateRequestCallback_: null, | |
| 95 | |
| 96 /** @inheritDoc */ | |
| 97 decorate: function() { | |
| 98 List.prototype.decorate.call(this); | |
| 99 this.classList.add('autocomplete-suggestions'); | |
| 100 this.selectionModel = new cr.ui.ListSingleSelectionModel; | |
| 101 | |
| 102 this.textFieldKeyHandler_ = this.handleAutocompleteKeydown_.bind(this); | |
| 103 var self = this; | |
| 104 this.textFieldInputHandler_ = function(e) { | |
| 105 if (self.suggestionUpdateRequestCallback_) | |
| 106 self.suggestionUpdateRequestCallback_(self.targetInput_.value); | |
| 107 }; | |
| 108 this.addEventListener('change', function(e) { | |
| 109 var input = self.targetInput; | |
| 110 if (!input || !self.selectedItem) | |
| 111 return; | |
| 112 input.value = self.selectedItem['url']; | |
| 113 // Programatically change the value won't trigger a change event, but | |
| 114 // clients are likely to want to know when changes happen, so fire one. | |
| 115 var changeEvent = document.createEvent('Event'); | |
| 116 changeEvent.initEvent('change', true, true); | |
| 117 input.dispatchEvent(changeEvent); | |
| 118 }); | |
| 119 // Start hidden; adding suggestions will unhide. | |
| 120 this.hidden = true; | |
| 121 }, | |
| 122 | |
| 123 /** @inheritDoc */ | |
| 124 createItem: function(pageInfo) { | |
| 125 return new AutocompleteListItem(pageInfo); | |
| 126 }, | |
| 127 | |
| 128 /** | |
| 129 * The suggestions to show. | |
| 130 * @type {Array} | |
| 131 */ | |
| 132 set suggestions(suggestions) { | |
| 133 this.dataModel = new ArrayDataModel(suggestions); | |
| 134 this.hidden = !this.targetInput_ || suggestions.length == 0; | |
| 135 }, | |
| 136 | |
| 137 /** | |
| 138 * A function to call when the attached input field's contents change. | |
| 139 * The function should take one string argument, which will be the text | |
| 140 * to autocomplete from. | |
| 141 * @type {Function} | |
| 142 */ | |
| 143 set suggestionUpdateRequestCallback(callback) { | |
| 144 this.suggestionUpdateRequestCallback_ = callback; | |
| 145 }, | |
| 146 | |
| 147 /** | |
| 148 * Attaches the popup to the given input element. Requires | |
| 149 * that the input be wrapped in a block-level container of the same width. | |
| 150 * @param {HTMLElement} input The input element to attach to. | |
| 151 */ | |
| 152 attachToInput: function(input) { | |
| 153 if (this.targetInput_ == input) | |
| 154 return; | |
| 155 | |
| 156 this.detach(); | |
| 157 this.targetInput_ = input; | |
| 158 this.style.width = input.getBoundingClientRect().width + 'px'; | |
| 159 this.hidden = false; // Necessary for positionPopupAroundElement to work. | |
| 160 cr.ui.positionPopupAroundElement(input, this, cr.ui.AnchorType.BELOW) | |
| 161 // Start hidden; when the data model gets results the list will show. | |
| 162 this.hidden = true; | |
| 163 | |
| 164 input.addEventListener('keydown', this.textFieldKeyHandler_, true); | |
| 165 input.addEventListener('input', this.textFieldInputHandler_); | |
| 166 }, | |
| 167 | |
| 168 /** | |
| 169 * Detaches the autocomplete popup from its current input element, if any. | |
| 170 */ | |
| 171 detach: function() { | |
| 172 var input = this.targetInput_ | |
| 173 if (!input) | |
| 174 return; | |
| 175 | |
| 176 input.removeEventListener('keydown', this.textFieldKeyHandler_); | |
| 177 input.removeEventListener('input', this.textFieldInputHandler_); | |
| 178 this.targetInput_ = null; | |
| 179 this.suggestions = []; | |
| 180 }, | |
| 181 | |
| 182 /** | |
| 183 * Makes sure that the suggestion list matches the width of the input it is. | |
| 184 * attached to. Should be called any time the input is resized. | |
| 185 */ | |
| 186 syncWidthToInput: function() { | |
| 187 var input = this.targetInput_ | |
| 188 if (input) | |
| 189 this.style.width = input.getBoundingClientRect().width + 'px'; | |
| 190 }, | |
| 191 | |
| 192 /** | |
| 193 * The text field the autocomplete popup is currently attached to, if any. | |
| 194 * @return {HTMLElement} | |
| 195 */ | |
| 196 get targetInput() { | |
| 197 return this.targetInput_; | |
| 198 }, | |
| 199 | |
| 200 /** | |
| 201 * Handles input field key events that should be interpreted as autocomplete | |
| 202 * commands. | |
| 203 * @param {Event} event The keydown event. | |
| 204 * @private | |
| 205 */ | |
| 206 handleAutocompleteKeydown_: function(event) { | |
| 207 if (this.hidden) | |
| 208 return; | |
| 209 var handled = false; | |
| 210 switch (event.keyIdentifier) { | |
| 211 case 'U+001B': // Esc | |
| 212 this.suggestions = []; | |
| 213 handled = true; | |
| 214 break; | |
| 215 case 'Enter': | |
| 216 var hadSelection = this.selectedItem != null; | |
| 217 this.suggestions = []; | |
| 218 // Only count the event as handled if a selection is being commited. | |
| 219 handled = hadSelection; | |
| 220 break; | |
| 221 case 'Up': | |
| 222 case 'Down': | |
| 223 this.dispatchEvent(event); | |
| 224 handled = true; | |
| 225 break; | |
| 226 } | |
| 227 // Don't let arrow keys affect the text field, or bubble up to, e.g., | |
| 228 // an enclosing list item. | |
| 229 if (handled) { | |
| 230 event.preventDefault(); | |
| 231 event.stopPropagation(); | |
| 232 } | |
| 233 }, | |
| 234 }; | |
| 235 | |
| 236 return { | |
| 237 AutocompleteList: AutocompleteList | |
| 238 }; | |
| 239 }); | |
| OLD | NEW |