Index: chrome/browser/resources/options2/autocomplete_list.js |
diff --git a/chrome/browser/resources/options2/autocomplete_list.js b/chrome/browser/resources/options2/autocomplete_list.js |
new file mode 100644 |
index 0000000000000000000000000000000000000000..2aeb195cb5bd053998e584d11faa2daf2621a98f |
--- /dev/null |
+++ b/chrome/browser/resources/options2/autocomplete_list.js |
@@ -0,0 +1,239 @@ |
+// Copyright (c) 2011 The Chromium Authors. All rights reserved. |
+// Use of this source code is governed by a BSD-style license that can be |
+// found in the LICENSE file. |
+ |
+cr.define('options', function() { |
+ const ArrayDataModel = cr.ui.ArrayDataModel; |
+ const List = cr.ui.List; |
+ const ListItem = cr.ui.ListItem; |
+ |
+ /** |
+ * Creates a new autocomplete list item. |
+ * @param {Object} pageInfo The page this item represents. |
+ * @constructor |
+ * @extends {cr.ui.ListItem} |
+ */ |
+ function AutocompleteListItem(pageInfo) { |
+ var el = cr.doc.createElement('div'); |
+ el.pageInfo_ = pageInfo; |
+ AutocompleteListItem.decorate(el); |
+ return el; |
+ } |
+ |
+ /** |
+ * Decorates an element as an autocomplete list item. |
+ * @param {!HTMLElement} el The element to decorate. |
+ */ |
+ AutocompleteListItem.decorate = function(el) { |
+ el.__proto__ = AutocompleteListItem.prototype; |
+ el.decorate(); |
+ }; |
+ |
+ AutocompleteListItem.prototype = { |
+ __proto__: ListItem.prototype, |
+ |
+ /** @inheritDoc */ |
+ decorate: function() { |
+ ListItem.prototype.decorate.call(this); |
+ |
+ var title = this.pageInfo_['title']; |
+ var url = this.pageInfo_['displayURL']; |
+ var titleEl = this.ownerDocument.createElement('span'); |
+ titleEl.className = 'title'; |
+ titleEl.textContent = title || url; |
+ this.appendChild(titleEl); |
+ |
+ if (title && title.length > 0 && url != title) { |
+ var separatorEl = this.ownerDocument.createTextNode(' - '); |
+ this.appendChild(separatorEl); |
+ |
+ var urlEl = this.ownerDocument.createElement('span'); |
+ urlEl.className = 'url'; |
+ urlEl.textContent = url; |
+ this.appendChild(urlEl); |
+ } |
+ }, |
+ }; |
+ |
+ /** |
+ * Creates a new autocomplete list popup. |
+ * @constructor |
+ * @extends {cr.ui.List} |
+ */ |
+ var AutocompleteList = cr.ui.define('list'); |
+ |
+ AutocompleteList.prototype = { |
+ __proto__: List.prototype, |
+ |
+ /** |
+ * The text field the autocomplete popup is currently attached to, if any. |
+ * @type {HTMLElement} |
+ * @private |
+ */ |
+ targetInput_: null, |
+ |
+ /** |
+ * Keydown event listener to attach to a text field. |
+ * @type {Function} |
+ * @private |
+ */ |
+ textFieldKeyHandler_: null, |
+ |
+ /** |
+ * Input event listener to attach to a text field. |
+ * @type {Function} |
+ * @private |
+ */ |
+ textFieldInputHandler_: null, |
+ |
+ /** |
+ * A function to call when new suggestions are needed. |
+ * @type {Function} |
+ * @private |
+ */ |
+ suggestionUpdateRequestCallback_: null, |
+ |
+ /** @inheritDoc */ |
+ decorate: function() { |
+ List.prototype.decorate.call(this); |
+ this.classList.add('autocomplete-suggestions'); |
+ this.selectionModel = new cr.ui.ListSingleSelectionModel; |
+ |
+ this.textFieldKeyHandler_ = this.handleAutocompleteKeydown_.bind(this); |
+ var self = this; |
+ this.textFieldInputHandler_ = function(e) { |
+ if (self.suggestionUpdateRequestCallback_) |
+ self.suggestionUpdateRequestCallback_(self.targetInput_.value); |
+ }; |
+ this.addEventListener('change', function(e) { |
+ var input = self.targetInput; |
+ if (!input || !self.selectedItem) |
+ return; |
+ input.value = self.selectedItem['url']; |
+ // Programatically change the value won't trigger a change event, but |
+ // clients are likely to want to know when changes happen, so fire one. |
+ var changeEvent = document.createEvent('Event'); |
+ changeEvent.initEvent('change', true, true); |
+ input.dispatchEvent(changeEvent); |
+ }); |
+ // Start hidden; adding suggestions will unhide. |
+ this.hidden = true; |
+ }, |
+ |
+ /** @inheritDoc */ |
+ createItem: function(pageInfo) { |
+ return new AutocompleteListItem(pageInfo); |
+ }, |
+ |
+ /** |
+ * The suggestions to show. |
+ * @type {Array} |
+ */ |
+ set suggestions(suggestions) { |
+ this.dataModel = new ArrayDataModel(suggestions); |
+ this.hidden = !this.targetInput_ || suggestions.length == 0; |
+ }, |
+ |
+ /** |
+ * A function to call when the attached input field's contents change. |
+ * The function should take one string argument, which will be the text |
+ * to autocomplete from. |
+ * @type {Function} |
+ */ |
+ set suggestionUpdateRequestCallback(callback) { |
+ this.suggestionUpdateRequestCallback_ = callback; |
+ }, |
+ |
+ /** |
+ * Attaches the popup to the given input element. Requires |
+ * that the input be wrapped in a block-level container of the same width. |
+ * @param {HTMLElement} input The input element to attach to. |
+ */ |
+ attachToInput: function(input) { |
+ if (this.targetInput_ == input) |
+ return; |
+ |
+ this.detach(); |
+ this.targetInput_ = input; |
+ this.style.width = input.getBoundingClientRect().width + 'px'; |
+ this.hidden = false; // Necessary for positionPopupAroundElement to work. |
+ cr.ui.positionPopupAroundElement(input, this, cr.ui.AnchorType.BELOW) |
+ // Start hidden; when the data model gets results the list will show. |
+ this.hidden = true; |
+ |
+ input.addEventListener('keydown', this.textFieldKeyHandler_, true); |
+ input.addEventListener('input', this.textFieldInputHandler_); |
+ }, |
+ |
+ /** |
+ * Detaches the autocomplete popup from its current input element, if any. |
+ */ |
+ detach: function() { |
+ var input = this.targetInput_ |
+ if (!input) |
+ return; |
+ |
+ input.removeEventListener('keydown', this.textFieldKeyHandler_); |
+ input.removeEventListener('input', this.textFieldInputHandler_); |
+ this.targetInput_ = null; |
+ this.suggestions = []; |
+ }, |
+ |
+ /** |
+ * Makes sure that the suggestion list matches the width of the input it is. |
+ * attached to. Should be called any time the input is resized. |
+ */ |
+ syncWidthToInput: function() { |
+ var input = this.targetInput_ |
+ if (input) |
+ this.style.width = input.getBoundingClientRect().width + 'px'; |
+ }, |
+ |
+ /** |
+ * The text field the autocomplete popup is currently attached to, if any. |
+ * @return {HTMLElement} |
+ */ |
+ get targetInput() { |
+ return this.targetInput_; |
+ }, |
+ |
+ /** |
+ * Handles input field key events that should be interpreted as autocomplete |
+ * commands. |
+ * @param {Event} event The keydown event. |
+ * @private |
+ */ |
+ handleAutocompleteKeydown_: function(event) { |
+ if (this.hidden) |
+ return; |
+ var handled = false; |
+ switch (event.keyIdentifier) { |
+ case 'U+001B': // Esc |
+ this.suggestions = []; |
+ handled = true; |
+ break; |
+ case 'Enter': |
+ var hadSelection = this.selectedItem != null; |
+ this.suggestions = []; |
+ // Only count the event as handled if a selection is being commited. |
+ handled = hadSelection; |
+ break; |
+ case 'Up': |
+ case 'Down': |
+ this.dispatchEvent(event); |
+ handled = true; |
+ break; |
+ } |
+ // Don't let arrow keys affect the text field, or bubble up to, e.g., |
+ // an enclosing list item. |
+ if (handled) { |
+ event.preventDefault(); |
+ event.stopPropagation(); |
+ } |
+ }, |
+ }; |
+ |
+ return { |
+ AutocompleteList: AutocompleteList |
+ }; |
+}); |