summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorstuartmorgan@chromium.org <stuartmorgan@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2011-01-12 00:40:09 +0000
committerstuartmorgan@chromium.org <stuartmorgan@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2011-01-12 00:40:09 +0000
commit9394fced7d8c03bb4b81941c99155bb775b68d04 (patch)
tree39b8d802c8b0cda5395ee3d7678d55045f755b2f
parentcba920c94cb99e0c7b6a0dd06b124902699a0aee (diff)
downloadchromium_src-9394fced7d8c03bb4b81941c99155bb775b68d04.zip
chromium_src-9394fced7d8c03bb4b81941c99155bb775b68d04.tar.gz
chromium_src-9394fced7d8c03bb4b81941c99155bb775b68d04.tar.bz2
DOMUI Prefs: Replace search engine edit overlay with inline editing.
Validation feedback still needs polish, but this is completely functional, so I'll leave that for a follow-up. This introduces a new shared class for editable lists; we should migrate the existing editable lists to it, expanding as necessary, but I didn't want to make this patch too unwieldly. BUG=63825,61742 TEST=Search engines should be editable and addable inline in DOMUI prefs. Review URL: http://codereview.chromium.org/6151004 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@71121 0039d316-1c4b-4281-b951-d872f2087c98
-rw-r--r--chrome/app/generated_resources.grd13
-rw-r--r--chrome/browser/dom_ui/options/search_engine_manager_handler.cc46
-rw-r--r--chrome/browser/resources/options/edit_search_engine_overlay.css36
-rw-r--r--chrome/browser/resources/options/edit_search_engine_overlay.html40
-rw-r--r--chrome/browser/resources/options/edit_search_engine_overlay.js158
-rw-r--r--chrome/browser/resources/options/inline_editable_list.js210
-rw-r--r--chrome/browser/resources/options/options.html4
-rw-r--r--chrome/browser/resources/options/options.js2
-rw-r--r--chrome/browser/resources/options/options_page.css2
-rw-r--r--chrome/browser/resources/options/search_engine_manager.css62
-rw-r--r--chrome/browser/resources/options/search_engine_manager.html4
-rw-r--r--chrome/browser/resources/options/search_engine_manager.js48
-rw-r--r--chrome/browser/resources/options/search_engine_manager_engine_list.js300
-rw-r--r--chrome/browser/resources/shared/js/cr/ui/list_single_selection_model.js1
14 files changed, 594 insertions, 332 deletions
diff --git a/chrome/app/generated_resources.grd b/chrome/app/generated_resources.grd
index fe58bc3..e8df9bb 100644
--- a/chrome/app/generated_resources.grd
+++ b/chrome/app/generated_resources.grd
@@ -1133,6 +1133,10 @@ each locale. -->
desc="Title of the description column in the search engines editor">
Name
</message>
+ <message name="IDS_SEARCH_ENGINES_EDITOR_URL_COLUMN"
+ desc="Title of the URL column in the search engines editor">
+ URL
+ </message>
<message name="IDS_SEARCH_ENGINES_EDITOR_DESCRIPTION_LABEL"
desc="Prefix before the search engine description text field">
Name:
@@ -6893,6 +6897,15 @@ Keep your key file in a safe place. You will need it to create new versions of y
desc="The label of a link that brings up the keyword editor (aka search engine editor)">
Manage
</message>
+ <message name="IDS_SEARCH_ENGINE_ADD_NEW_NAME_PLACEHOLDER" desc="Placeholder text for name before the user adds a new search engine" >
+ Add a new search engine
+ </message>
+ <message name="IDS_SEARCH_ENGINE_ADD_NEW_KEYWORD_PLACEHOLDER" desc="Placeholder text for keyword before the user adds a new search engine" >
+ Keyword
+ </message>
+ <message name="IDS_SEARCH_ENGINE_ADD_NEW_URL_PLACEHOLDER" desc="Placeholder text for URL before the user adds a new search engine" >
+ URL with %s in place of query
+ </message>
<if expr="not pp_ifdef('use_titlecase') or os != 'linux2'">
<message name="IDS_OPTIONS_DEFAULTBROWSER_GROUP_NAME" desc="The title of the default browser group">
diff --git a/chrome/browser/dom_ui/options/search_engine_manager_handler.cc b/chrome/browser/dom_ui/options/search_engine_manager_handler.cc
index 78c8cd3..1c211f6 100644
--- a/chrome/browser/dom_ui/options/search_engine_manager_handler.cc
+++ b/chrome/browser/dom_ui/options/search_engine_manager_handler.cc
@@ -54,34 +54,22 @@ void SearchEngineManagerHandler::GetLocalizedValues(
l10n_util::GetStringUTF16(IDS_SEARCH_ENGINES_EDITOR_DESCRIPTION_COLUMN));
localized_strings->SetString("searchEngineTableKeywordHeader",
l10n_util::GetStringUTF16(IDS_SEARCH_ENGINES_EDITOR_KEYWORD_COLUMN));
- localized_strings->SetString("addSearchEngineButton",
- l10n_util::GetStringUTF16(IDS_SEARCH_ENGINES_EDITOR_NEW_BUTTON));
- localized_strings->SetString("editSearchEngineButton",
+ localized_strings->SetString("searchEngineTableURLHeader",
l10n_util::GetStringUTF16(IDS_SEARCH_ENGINES_EDITOR_EDIT_BUTTON));
localized_strings->SetString("makeDefaultSearchEngineButton",
l10n_util::GetStringUTF16(IDS_SEARCH_ENGINES_EDITOR_MAKE_DEFAULT_BUTTON));
- // Overlay strings.
- localized_strings->SetString("editSearchEngineTitle",
- l10n_util::GetStringUTF16(IDS_SEARCH_ENGINES_EDITOR_EDIT_WINDOW_TITLE));
- localized_strings->SetString("editSearchEngineNameLabel",
- l10n_util::GetStringUTF16(IDS_SEARCH_ENGINES_EDITOR_DESCRIPTION_LABEL));
- localized_strings->SetString("editSearchEngineKeywordLabel",
- l10n_util::GetStringUTF16(IDS_SEARCH_ENGINES_EDITOR_KEYWORD_LABEL));
- localized_strings->SetString("editSearchEngineURLLabel",
- l10n_util::GetStringUTF16(IDS_SEARCH_ENGINES_EDITOR_URL_LABEL));
+ localized_strings->SetString("searchEngineTableNamePlaceholder",
+ l10n_util::GetStringUTF16(IDS_SEARCH_ENGINE_ADD_NEW_NAME_PLACEHOLDER));
+ localized_strings->SetString("searchEngineTableKeywordPlaceholder",
+ l10n_util::GetStringUTF16(IDS_SEARCH_ENGINE_ADD_NEW_KEYWORD_PLACEHOLDER));
+ localized_strings->SetString("searchEngineTableURLPlaceholder",
+ l10n_util::GetStringUTF16(IDS_SEARCH_ENGINE_ADD_NEW_URL_PLACEHOLDER));
localized_strings->SetString("editSearchEngineInvalidTitleToolTip",
l10n_util::GetStringUTF16(IDS_SEARCH_ENGINES_INVALID_TITLE_TT));
localized_strings->SetString("editSearchEngineInvalidKeywordToolTip",
l10n_util::GetStringUTF16(IDS_SEARCH_ENGINES_INVALID_KEYWORD_TT));
localized_strings->SetString("editSearchEngineInvalidURLToolTip",
l10n_util::GetStringUTF16(IDS_SEARCH_ENGINES_INVALID_URL_TT));
- localized_strings->SetString("editSearchEngineURLExplanation",
- l10n_util::GetStringUTF16(
- IDS_SEARCH_ENGINES_EDITOR_URL_DESCRIPTION_LABEL));
- localized_strings->SetString("editSearchEngineOkayButton",
- l10n_util::GetStringUTF16(IDS_OK));
- localized_strings->SetString("editSearchEngineCancelButton",
- l10n_util::GetStringUTF16(IDS_CANCEL));
}
void SearchEngineManagerHandler::RegisterMessages() {
@@ -170,6 +158,8 @@ DictionaryValue* SearchEngineManagerHandler::CreateDictionaryForEngine(
dict->SetString("keyword", table_model->GetText(
index, IDS_SEARCH_ENGINES_EDITOR_KEYWORD_COLUMN));
const TemplateURL* template_url = list_controller_->GetTemplateURL(index);
+ dict->SetString("url", WideToUTF16Hack(template_url->url()->DisplayURL()));
+ dict->SetBoolean("urlLocked", template_url->prepopulate_id() > 0);
GURL icon_url = template_url->GetFavIconURL();
if (icon_url.is_valid())
dict->SetString("iconURL", icon_url.spec());
@@ -225,17 +215,6 @@ void SearchEngineManagerHandler::EditSearchEngine(const ListValue* args) {
edit_url = list_controller_->GetTemplateURL(index);
edit_controller_.reset(
new EditSearchEngineController(edit_url, this, dom_ui_->GetProfile()));
-
- if (edit_url) {
- DictionaryValue engine_details;
- engine_details.SetString("name", WideToUTF16Hack(edit_url->short_name()));
- engine_details.SetString("keyword", WideToUTF16Hack(edit_url->keyword()));
- engine_details.SetString("url",
- WideToUTF16Hack(edit_url->url()->DisplayURL()));
- engine_details.SetBoolean("urlLocked", edit_url->prepopulate_id() > 0);
- dom_ui_->CallJavascriptFunction(L"EditSearchEngineOverlay.setEditDetails",
- engine_details);
- }
}
void SearchEngineManagerHandler::OnEditedKeyword(
@@ -259,9 +238,11 @@ void SearchEngineManagerHandler::CheckSearchEngineInfoValidity(
string16 name;
string16 keyword;
std::string url;
+ std::string modelIndex;
if (!args->GetString(ENGINE_NAME, &name) ||
!args->GetString(ENGINE_KEYWORD, &keyword) ||
- !args->GetString(ENGINE_URL, &url)) {
+ !args->GetString(ENGINE_URL, &url) ||
+ !args->GetString(3, &modelIndex)) {
NOTREACHED();
return;
}
@@ -270,8 +251,9 @@ void SearchEngineManagerHandler::CheckSearchEngineInfoValidity(
validity.SetBoolean("name", edit_controller_->IsTitleValid(name));
validity.SetBoolean("keyword", edit_controller_->IsKeywordValid(keyword));
validity.SetBoolean("url", edit_controller_->IsURLValid(url));
+ StringValue indexValue(modelIndex);
dom_ui_->CallJavascriptFunction(
- L"EditSearchEngineOverlay.validityCheckCallback", validity);
+ L"SearchEngineManager.validityCheckCallback", validity, indexValue);
}
void SearchEngineManagerHandler::EditCancelled(const ListValue* args) {
diff --git a/chrome/browser/resources/options/edit_search_engine_overlay.css b/chrome/browser/resources/options/edit_search_engine_overlay.css
deleted file mode 100644
index fad104e..0000000
--- a/chrome/browser/resources/options/edit_search_engine_overlay.css
+++ /dev/null
@@ -1,36 +0,0 @@
-#editSearchEngineOverlay {
- width: 500px;
-}
-
-#editSearchEngineOverlay table label {
- text-align: end;
-}
-
-#editSearchEngineOverlay table,
-#editSearchEngineOverlay table td:nth-child(2) {
- width: 100%;
-}
-
-#editSearchEngineOverlay table input {
- width: 100%;
- box-sizing: border-box;
-}
-
-#editSearchEngineOverlay .action-area {
- margin-top: 2ex;
-}
-
-.valid-badge, .alert-badge {
- width: 22px;
- height: 21px;
- background-position: 50% 1px;
- background-repeat: no-repeat;
-}
-
-.valid-badge {
- background-image: url("../../../../app/resources/input_good.png");
-}
-
-.alert-badge {
- background-image: url("../../../app/theme/input_alert.png");
-}
diff --git a/chrome/browser/resources/options/edit_search_engine_overlay.html b/chrome/browser/resources/options/edit_search_engine_overlay.html
deleted file mode 100644
index 19c425a..0000000
--- a/chrome/browser/resources/options/edit_search_engine_overlay.html
+++ /dev/null
@@ -1,40 +0,0 @@
-<div class="page hidden" id="editSearchEngineOverlay">
- <h1 i18n-content="editSearchEngineTitle"></h1>
-
- <form id="editSearchEngineForm">
- <table>
- <tr>
- <td><label for="editSearchEngineName"><span
- i18n-content="editSearchEngineNameLabel"></span></label></td>
- <td><input type="text" id="editSearchEngineName"></td>
- <td><div id="editSearchEngineNameValidity"
- class="alert-badge">&nbsp;</div></td>
- </tr>
- <tr>
- <td><label for="editSearchEngineKeyword"><span
- i18n-content="editSearchEngineKeywordLabel"></span></label></td>
- <td><input type="text" id="editSearchEngineKeyword"></td>
- <td><div id="editSearchEngineKeywordValidity"
- class="alert-badge">&nbsp;</div></td>
- </tr>
- <tr>
- <td><label for="editSearchEngineURL"><span
- i18n-content="editSearchEngineURLLabel"></span></label></td>
- <td><input type="url" id="editSearchEngineURL"></td>
- <td><div id="editSearchEngineURLValidity"
- class="alert-badge">&nbsp;</div></td>
- </tr>
- <tr>
- <td></td>
- <td><span i18n-content="editSearchEngineURLExplanation"><span></td>
- <td></td>
- </table>
-
- <div class="action-area button-strip">
- <button type="reset"
- i18n-content="editSearchEngineCancelButton"></button>
- <button type="submit" id="editSearchEngineOkayButton" disabled
- i18n-content="editSearchEngineOkayButton"></button>
- </div>
- </form>
-</div>
diff --git a/chrome/browser/resources/options/edit_search_engine_overlay.js b/chrome/browser/resources/options/edit_search_engine_overlay.js
deleted file mode 100644
index a4f72b5..0000000
--- a/chrome/browser/resources/options/edit_search_engine_overlay.js
+++ /dev/null
@@ -1,158 +0,0 @@
-// Copyright (c) 2010 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 OptionsPage = options.OptionsPage;
-
- /**
- * EditSearchEngineOverlay class
- * Encapsulated handling of the 'Edit Search Engine' overlay page.
- * @class
- * @constructor
- */
- function EditSearchEngineOverlay() {
- OptionsPage.call(this, 'editSearchEngineOverlay',
- templateData.editSearchEngineTitle,
- 'editSearchEngineOverlay');
- }
-
- cr.addSingletonGetter(EditSearchEngineOverlay);
-
- EditSearchEngineOverlay.prototype = {
- __proto__: OptionsPage.prototype,
-
- /**
- * Initializes the page.
- */
- initializePage: function() {
- OptionsPage.prototype.initializePage.call(this);
-
- var self = this;
- var editForm = $('editSearchEngineForm');
- editForm.onreset = function(e) {
- chrome.send('searchEngineEditCancelled');
- self.dismissOverlay_();
- };
- editForm.onsubmit = function(e) {
- chrome.send('searchEngineEditCompleted', self.getInputFieldValues_());
- self.dismissOverlay_();
- return false;
- };
- var fieldIDs = ['editSearchEngineName',
- 'editSearchEngineKeyword',
- 'editSearchEngineURL'];
- for (var i = 0; i < fieldIDs.length; i++) {
- var field = $(fieldIDs[i]);
- field.oninput = this.validateFields_.bind(this);
- field.onkeydown = function(e) {
- if (e.keyCode == 27) // Esc
- editForm.reset();
- };
- }
- },
-
- /**
- * Clears any uncommited input, and dismisses the overlay.
- * @private
- */
- dismissOverlay_: function() {
- this.setEditDetails_();
- OptionsPage.clearOverlays();
- },
-
- /**
- * Fills the text fields from the given search engine.
- * @private
- */
- setEditDetails_: function(engineDetails) {
- if (engineDetails) {
- $('editSearchEngineName').value = engineDetails['name'];
- $('editSearchEngineKeyword').value = engineDetails['keyword'];
- var urlField = $('editSearchEngineURL');
- urlField.value = engineDetails['url'];
- urlField.disabled = engineDetails['urlLocked'];
- this.validateFields_();
- } else {
- $('editSearchEngineName').value = '';
- $('editSearchEngineKeyword').value = '';
- $('editSearchEngineURL').value = '';
- var invalid = { name: false, keyword: false, url: false };
- this.updateValidityWithResults_(invalid);
- }
- },
-
- /**
- * Starts the process of asynchronously validating the user input. Results
- * will be reported to updateValidityWithResults_.
- * @private
- */
- validateFields_: function() {
- chrome.send('checkSearchEngineInfoValidity', this.getInputFieldValues_());
- },
-
- /**
- * Sets the validation images and the enabled state of the Add button based
- * on the current values of the text fields.
- * @private
- * @param {Object} The dictionary of validity states.
- */
- updateValidityWithResults_: function(validity) {
- this.setBadgeValidity_($('editSearchEngineNameValidity'),
- validity['name'],
- 'editSearchEngineInvalidTitleToolTip');
- this.setBadgeValidity_($('editSearchEngineKeywordValidity'),
- validity['keyword'],
- 'editSearchEngineInvalidKeywordToolTip');
- this.setBadgeValidity_($('editSearchEngineURLValidity'),
- validity['url'],
- 'editSearchEngineInvalidURLToolTip');
- $('editSearchEngineOkayButton').disabled =
- !(validity['name'] && validity['keyword'] && validity['url']);
- },
-
- /**
- * Updates the state of the given validity indicator badge.
- * @private
- * @param {HTMLElement} The badge element to adjust.
- * @param {boolean} Whether or not the badge should be set to the valid
- * state.
- * @param {string} The tooltip string id for the invalid state.
- */
- setBadgeValidity_: function(element, isValid, tooltip_id) {
- if (isValid) {
- element.className = 'valid-badge';
- element.title = '';
- } else {
- element.className = 'alert-badge';
- element.title = localStrings.getString(tooltip_id);
- }
- },
-
- /**
- * Returns the input field values as an array suitable for passing to
- * chrome.send. The order of the array is important.
- * @private
- * @return {array} The current input field values.
- */
- getInputFieldValues_: function() {
- return [ $('editSearchEngineName').value,
- $('editSearchEngineKeyword').value,
- $('editSearchEngineURL').value ];
- }
- };
-
- EditSearchEngineOverlay.setEditDetails = function(engineDetails) {
- EditSearchEngineOverlay.getInstance().setEditDetails_(engineDetails);
- };
-
- EditSearchEngineOverlay.validityCheckCallback = function(validity) {
- EditSearchEngineOverlay.getInstance().updateValidityWithResults_(validity);
- };
-
- // Export
- return {
- EditSearchEngineOverlay: EditSearchEngineOverlay
- };
-
-});
diff --git a/chrome/browser/resources/options/inline_editable_list.js b/chrome/browser/resources/options/inline_editable_list.js
new file mode 100644
index 0000000..ec0f4a4
--- /dev/null
+++ b/chrome/browser/resources/options/inline_editable_list.js
@@ -0,0 +1,210 @@
+// Copyright (c) 2010 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 DeletableItem = options.DeletableItem;
+ const DeletableItemList = options.DeletableItemList;
+
+ /**
+ * Creates a new list item with support for inline editing.
+ * @constructor
+ * @extends {options.DeletableListItem}
+ */
+ function InlineEditableItem() {
+ var el = cr.doc.createElement('div');
+ InlineEditableItem.decorate(el);
+ return el;
+ }
+
+ /**
+ * Decorates an element as a inline-editable list item. Note that this is
+ * a subclass of DeletableItem.
+ * @param {!HTMLElement} el The element to decorate.
+ */
+ InlineEditableItem.decorate = function(el) {
+ el.__proto__ = InlineEditableItem.prototype;
+ el.decorate();
+ };
+
+ InlineEditableItem.prototype = {
+ __proto__: DeletableItem.prototype,
+
+ /**
+ * Whether or not this item can be edited.
+ * @type {boolean}
+ * @private
+ */
+ editable_: true,
+
+ /**
+ * Whether or not the current edit should be considered cancelled, rather
+ * than committed, when editing ends.
+ * @type {boolean}
+ * @private
+ */
+ editCancelled_: true,
+
+ /** @inheritDoc */
+ decorate: function() {
+ DeletableItem.prototype.decorate.call(this);
+
+ this.addEventListener('keydown', this.handleKeyDown_.bind(this));
+ },
+
+ /** @inheritDoc */
+ selectionChanged: function() {
+ if (this.editable)
+ this.editing = this.selected;
+ },
+
+ /**
+ * Whether the user is currently editing the list item.
+ * @type {boolean}
+ */
+ get editing() {
+ return this.hasAttribute('editing');
+ },
+ set editing(editing) {
+ if (this.editing == editing)
+ return;
+
+ if (editing)
+ this.setAttribute('editing', '');
+ else
+ this.removeAttribute('editing');
+
+ if (editing) {
+ this.editCancelled_ = false;
+
+ cr.dispatchSimpleEvent(this, 'edit', true);
+
+ var focusElement = this.initialFocusElement;
+ // When this is called in response to the selectedChange event,
+ // the list grabs focus immediately afterwards. Thus we must delay
+ // our focus grab.
+ if (focusElement) {
+ window.setTimeout(function() {
+ focusElement.focus();
+ focusElement.select();
+ }, 50);
+ }
+ } else {
+ if (!this.editCancelled_ && this.hasBeenEdited() &&
+ this.currentInputIsValid) {
+ cr.dispatchSimpleEvent(this, 'commitedit', true);
+ } else {
+ cr.dispatchSimpleEvent(this, 'canceledit', true);
+ }
+ }
+ },
+
+ /**
+ * Whether the item is editable.
+ * @type {boolean}
+ */
+ get editable() {
+ return this.editable_;
+ },
+ set editable(editable) {
+ this.editable_ = editable;
+ if (!editable)
+ this.editing = false;
+ },
+
+ /**
+ * The HTML element that should have focus initially when editing starts.
+ * Should be overriden by subclasses.
+ * @type {HTMLElement}
+ */
+ get initialFocusElement() {
+ return null;
+ },
+
+ /**
+ * Whether the input in currently valid to submit. If this returns false
+ * when editing would be submitted, either editing will not be ended,
+ * or it will be cancelled, depending on the context.
+ * Can be overrided by subclasses to perform input validation.
+ */
+ get currentInputIsValid() {
+ return true;
+ },
+
+ /**
+ * Returns true if the item has been changed by an edit.
+ * Can be overrided by subclasses to return false when nothing has changed
+ * to avoid unnecessary commits.
+ */
+ hasBeenEdited: function() {
+ return true;
+ },
+
+ /**
+ * Called a key is pressed. Handles committing and cancelling edits.
+ * @param {Event} e The key down event.
+ * @private
+ */
+ handleKeyDown_: function(e) {
+ if (!this.editing)
+ return;
+
+ var endEdit = false;
+ switch (e.keyIdentifier) {
+ case 'U+001B': // Esc
+ this.editCancelled_ = true;
+ endEdit = true;
+ break;
+ case 'Enter':
+ if (this.currentInputIsValid)
+ endEdit = true;
+ break;
+ }
+
+ if (endEdit) {
+ // Blurring will trigger the edit to end; see InlineEditableItemList.
+ this.ownerDocument.activeElement.blur();
+ // Make sure that handled keys aren't passed on and double-handled.
+ // (e.g., esc shouldn't both cancel an edit and close a subpage)
+ e.stopPropagation();
+ }
+ },
+ };
+
+ var InlineEditableItemList = cr.ui.define('list');
+
+ InlineEditableItemList.prototype = {
+ __proto__: DeletableItemList.prototype,
+
+ /** @inheritDoc */
+ decorate: function() {
+ DeletableItemList.prototype.decorate.call(this);
+ this.addEventListener('blur', this.handleBlur_.bind(this), true);
+ },
+
+ /**
+ * Called when an element in the list is blurred. Removes selection (thus
+ * ending edit) if focus moves outside the list.
+ * @param {Event} e The blur event.
+ * @private
+ */
+ handleBlur_: function(e) {
+ // When the blur event happens we do not know who is getting focus so we
+ // delay this a bit until we know if the new focus node is outside the
+ // list.
+ var list = this;
+ var doc = e.target.ownerDocument;
+ window.setTimeout(function() {
+ var activeElement = doc.activeElement;
+ if (!list.contains(activeElement))
+ list.selectionModel.unselectAll();
+ }, 50);
+ },
+ };
+
+ // Export
+ return {
+ InlineEditableItem: InlineEditableItem,
+ InlineEditableItemList: InlineEditableItemList,
+ };
+});
diff --git a/chrome/browser/resources/options/options.html b/chrome/browser/resources/options/options.html
index 2885d51..2a17cc2 100644
--- a/chrome/browser/resources/options/options.html
+++ b/chrome/browser/resources/options/options.html
@@ -22,7 +22,6 @@
<link rel="stylesheet" href="clear_browser_data.css">
<link rel="stylesheet" href="content_settings.css">
<link rel="stylesheet" href="cookies_view.css">
-<link rel="stylesheet" href="edit_search_engine_overlay.css">
<link rel="stylesheet" href="password_manager.css">
<link rel="stylesheet" href="password_manager_list.css">
<link rel="stylesheet" href="personal_options.css">
@@ -60,6 +59,7 @@
<script src="preferences.js"></script>
<script src="pref_ui.js"></script>
<script src="deletable_item_list.js"></script>
+<script src="inline_editable_list.js"></script>
<script src="list_inline_header_selection_controller.js"></script>
<script src="options_page.js"></script>
<if expr="pp_ifdef('chromeos')">
@@ -116,7 +116,6 @@
<script src="content_settings_ui.js"></script>
<script src="cookies_tree.js"></script>
<script src="cookies_view.js"></script>
-<script src="edit_search_engine_overlay.js"></script>
<script src="font_settings.js"></script>
<script src="font_settings_ui.js"></script>
<script src="import_data_overlay.js"></script>
@@ -139,7 +138,6 @@
<include src="alert_overlay.html">
<include src="autofill_edit_address_overlay.html">
<include src="autofill_edit_creditcard_overlay.html">
- <include src="edit_search_engine_overlay.html">
<include src="import_data_overlay.html">
<include src="instant_confirm_overlay.html">
<if expr="pp_ifdef('chromeos')">
diff --git a/chrome/browser/resources/options/options.js b/chrome/browser/resources/options/options.js
index d7734703..c5fdf07 100644
--- a/chrome/browser/resources/options/options.js
+++ b/chrome/browser/resources/options/options.js
@@ -14,7 +14,6 @@ var ContentSettings = options.ContentSettings;
var ContentSettingsExceptionsArea =
options.contentSettings.ContentSettingsExceptionsArea;
var CookiesView = options.CookiesView;
-var EditSearchEngineOverlay = options.EditSearchEngineOverlay;
var FontSettings = options.FontSettings;
var ImportDataOverlay = options.ImportDataOverlay;
var InstantConfirmOverlay = options.InstantConfirmOverlay;
@@ -124,7 +123,6 @@ function load() {
OptionsPage.registerOverlay(AlertOverlay.getInstance());
OptionsPage.registerOverlay(AutoFillEditAddressOverlay.getInstance());
OptionsPage.registerOverlay(AutoFillEditCreditCardOverlay.getInstance());
- OptionsPage.registerOverlay(EditSearchEngineOverlay.getInstance());
OptionsPage.registerOverlay(ImportDataOverlay.getInstance());
OptionsPage.registerOverlay(InstantConfirmOverlay.getInstance());
diff --git a/chrome/browser/resources/options/options_page.css b/chrome/browser/resources/options/options_page.css
index 6e04d75..ce6fd74 100644
--- a/chrome/browser/resources/options/options_page.css
+++ b/chrome/browser/resources/options/options_page.css
@@ -417,7 +417,9 @@ list .deletable-item {
}
list .deletable-item > :first-child {
+ -webkit-box-align: center;
-webkit-box-flex: 1;
+ -webkit-padding-end: 3px;
display: -webkit-box;
}
diff --git a/chrome/browser/resources/options/search_engine_manager.css b/chrome/browser/resources/options/search_engine_manager.css
index 1eac1c4..0a0523c 100644
--- a/chrome/browser/resources/options/search_engine_manager.css
+++ b/chrome/browser/resources/options/search_engine_manager.css
@@ -18,7 +18,7 @@
border-top: 1px solid #d9d9d9;
}
-#searchEngineList .heading .name {
+#searchEngineList .heading .name-column {
font-weight: bold;
}
@@ -31,24 +31,72 @@
display: -webkit-box;
}
-#searchEngineList .name {
+#searchEngineList .favicon {
+ padding: 1px 7px 0px 7px;
+ height: 16px;
+}
+
+#searchEngineList .name-column {
box-sizing: border-box;
- width: 50%;
+ width: 37%;
}
-#searchEngineList .keyword {
- -webkit-box-flex: 1;
+#searchEngineList .keyword-column {
+ width: 26%;
}
-#searchEngineList > div:not(.heading) .keyword {
+#searchEngineList .url-column {
+ width: 37%;
+}
+
+#searchEngineList > div:not(.heading) .keyword-column,
+#searchEngineList > div:not(.heading) .url-column {
color: #666666;
}
+#searchEngineList .name-column,
+#searchEngineList .keyword-column,
+#searchEngineList .url-column {
+ -webkit-padding-end: 1ex;
+}
+
#searchEngineList .default {
font-weight: bold;
}
-#searchEngineList .name, #searchEngineList .keyword {
+#searchEngineList .default .url-column {
+ font-weight: normal;
+}
+
+#searchEngineList .name-column {
+ display: -webkit-box;
+}
+
+#searchEngineList .name-column :last-child {
+ -webkit-box-flex: 1;
+}
+
+#searchEngineList input {
+ box-sizing: border-box;
+ margin: 0;
+ width: 100%;
+}
+
+#searchEngineList .static-text {
overflow: hidden;
text-overflow: ellipsis;
+ white-space: nowrap;
+}
+
+#searchEngineList > :not([editing]) [editmode=true] {
+ display: none;
+}
+
+#searchEngineList > [editing] [editmode=false] {
+ display: none;
+}
+
+#searchEngineList input.invalid {
+ /* TODO(stuartmorgan): Replace with actual badging */
+ background-color: pink;
}
diff --git a/chrome/browser/resources/options/search_engine_manager.html b/chrome/browser/resources/options/search_engine_manager.html
index 58d1ab1..4d8946e 100644
--- a/chrome/browser/resources/options/search_engine_manager.html
+++ b/chrome/browser/resources/options/search_engine_manager.html
@@ -5,10 +5,6 @@
<list id="searchEngineList"></list>
</div>
<div>
- <div><button id="addSearchEngineButton"
- i18n-content="addSearchEngineButton"></div>
- <div><button id="editSearchEngineButton" disabled
- i18n-content="editSearchEngineButton"></div>
<div><button id="makeDefaultSearchEngineButton" disabled
i18n-content="makeDefaultSearchEngineButton"></div>
</div>
diff --git a/chrome/browser/resources/options/search_engine_manager.js b/chrome/browser/resources/options/search_engine_manager.js
index 7a56bad..9a99f57 100644
--- a/chrome/browser/resources/options/search_engine_manager.js
+++ b/chrome/browser/resources/options/search_engine_manager.js
@@ -37,23 +37,19 @@ cr.define('options', function() {
this.selectionChanged_.bind(this));
var self = this;
- $('addSearchEngineButton').onclick = function(event) {
- chrome.send('editSearchEngine', ["-1"]);
- OptionsPage.showOverlay('editSearchEngineOverlay');
- };
- $('editSearchEngineButton').onclick = function(event) {
- chrome.send('editSearchEngine', [self.selectedModelIndex_]);
- OptionsPage.showOverlay('editSearchEngineOverlay');
+ // This is a temporary hack to allow the "Make Default" button to
+ // continue working despite the new list behavior of removing selection
+ // on focus loss.
+ // Once drag-and-drop is supported, so items can be moved into the default
+ // section, this button will go away entirely.
+ $('makeDefaultSearchEngineButton').onmousedown = function(event) {
+ self.pendingDefaultEngine_ = self.list_.selectedItem;
};
$('makeDefaultSearchEngineButton').onclick = function(event) {
chrome.send('managerSetDefaultSearchEngine',
- [self.selectedModelIndex_]);
+ [self.pendingDefaultEngine_['modelIndex']]);
+ self.pendingDefaultEngine_ = null;
};
-
- // Remove Windows-style accelerators from button labels.
- // TODO(stuartmorgan): Remove this once the strings are updated.
- $('addSearchEngineButton').textContent =
- localStrings.getStringWithoutAccelerator('addSearchEngineButton');
},
/**
@@ -62,16 +58,11 @@ cr.define('options', function() {
* @param {Array} engineList List of available search engines.
*/
updateSearchEngineList_: function(engineList) {
- this.list_.dataModel = new ArrayDataModel(engineList);
- },
-
- /**
- * Returns the currently selected list item's underlying model index.
- * @private
- */
- get selectedModelIndex_() {
- var listIndex = this.list_.selectionModel.selectedIndex;
- return this.list_.dataModel.item(listIndex)['modelIndex'];
+ var model = new ArrayDataModel(engineList);
+ model.push({
+ 'modelIndex': '-1'
+ });
+ this.list_.dataModel = model;
},
/**
@@ -80,11 +71,7 @@ cr.define('options', function() {
* @param {!cr.Event} e Event with change info.
*/
selectionChanged_: function(e) {
- var selectedIndex = this.list_.selectionModel.selectedIndex;
- var engine = selectedIndex != -1 ?
- this.list_.dataModel.item(selectedIndex) : null;
-
- $('editSearchEngineButton').disabled = engine == null;
+ var engine = this.list_.selectedItem || this.pendingDefaultEngine_;
$('makeDefaultSearchEngineButton').disabled =
!(engine && engine['canBeDefault']);
},
@@ -94,6 +81,11 @@ cr.define('options', function() {
SearchEngineManager.getInstance().updateSearchEngineList_(engineList);
};
+ SearchEngineManager.validityCheckCallback = function(validity, modelIndex) {
+ SearchEngineManager.getInstance().list_.validationComplete(validity,
+ modelIndex);
+ };
+
// Export
return {
SearchEngineManager: SearchEngineManager
diff --git a/chrome/browser/resources/options/search_engine_manager_engine_list.js b/chrome/browser/resources/options/search_engine_manager_engine_list.js
index 1e7d93e..b375337 100644
--- a/chrome/browser/resources/options/search_engine_manager_engine_list.js
+++ b/chrome/browser/resources/options/search_engine_manager_engine_list.js
@@ -3,8 +3,8 @@
// found in the LICENSE file.
cr.define('options.search_engines', function() {
- const DeletableItem = options.DeletableItem;
- const DeletableItemList = options.DeletableItemList;
+ const InlineEditableItemList = options.InlineEditableItemList;
+ const InlineEditableItem = options.InlineEditableItem;
const ListInlineHeaderSelectionController =
options.ListInlineHeaderSelectionController;
@@ -31,46 +31,290 @@ cr.define('options.search_engines', function() {
};
SearchEngineListItem.prototype = {
- __proto__: DeletableItem.prototype,
+ __proto__: InlineEditableItem.prototype,
+
+ /**
+ * Input field for editing the engine name.
+ * @type {HTMLElement}
+ * @private
+ */
+ nameField_: null,
+
+ /**
+ * Input field for editing the engine keyword.
+ * @type {HTMLElement}
+ * @private
+ */
+ keywordField_: null,
+
+ /**
+ * Input field for editing the engine url.
+ * @type {HTMLElement}
+ * @private
+ */
+ urlField_: null,
+
+ /**
+ * Whether or not this is a placeholder for adding an engine.
+ * @type {boolean}
+ * @private
+ */
+ isPlaceholder_: false,
+
+ /**
+ * Whether or not an input validation request is currently outstanding.
+ * @type {boolean}
+ * @private
+ */
+ waitingForValidation_: false,
+
+ /**
+ * Whether or not the current set of input is known to be valid.
+ * @type {boolean}
+ * @private
+ */
+ currentlyValid_: false,
/** @inheritDoc */
decorate: function() {
- DeletableItem.prototype.decorate.call(this);
+ InlineEditableItem.prototype.decorate.call(this);
var engine = this.searchEngine_;
- if (engine['heading'])
+ if (engine['modelIndex'] == '-1') {
+ this.isPlaceholder_ = true;
+ engine['name'] = '';
+ engine['keyword'] = '';
+ engine['url'] = '';
+ }
+
+ this.currentlyValid_ = !this.isPlaceholder_;
+
+ if (engine['heading']) {
this.classList.add('heading');
- else if (engine['default'])
+ this.editable = false;
+ } else if (engine['default']) {
this.classList.add('default');
+ }
this.deletable = engine['canBeRemoved'];
- var nameEl = this.ownerDocument.createElement('div');
- nameEl.className = 'name';
+ var nameText = engine['name'];
+ var keywordText = engine['keyword'];
+ var urlText = engine['url'];
if (engine['heading']) {
- nameEl.textContent = engine['heading'];
- } else {
- nameEl.textContent = engine['name'];
- nameEl.classList.add('favicon-cell');
- nameEl.style.backgroundImage = url('chrome://favicon/iconurl/' +
- engine['iconURL']);
+ nameText = engine['heading'];
+ keywordText = localStrings.getString('searchEngineTableKeywordHeader');
+ urlText = localStrings.getString('searchEngineTableURLHeader');
+ }
+
+ // Construct the name column.
+ var nameColEl = this.ownerDocument.createElement('div');
+ nameColEl.className = 'name-column';
+ this.contentElement.appendChild(nameColEl);
+
+ // For non-heading rows, start with a favicon.
+ if (!engine['heading']) {
+ var faviconDivEl = this.ownerDocument.createElement('div');
+ faviconDivEl.className = 'favicon';
+ var imgEl = this.ownerDocument.createElement('img');
+ imgEl.src = 'chrome://favicon/iconurl/' + engine['iconURL'];
+ faviconDivEl.appendChild(imgEl);
+ nameColEl.appendChild(faviconDivEl);
}
- this.contentElement.appendChild(nameEl);
- var keywordEl = this.ownerDocument.createElement('div');
- keywordEl.className = 'keyword';
- keywordEl.textContent = engine['heading'] ?
- localStrings.getString('searchEngineTableKeywordHeader') :
- engine['keyword'];
+ var nameEl = this.createEditableTextCell_(nameText);
+ nameColEl.appendChild(nameEl);
+
+ // Then the keyword column.
+ var keywordEl = this.createEditableTextCell_(keywordText);
+ keywordEl.className = 'keyword-column';
this.contentElement.appendChild(keywordEl);
+
+ // And the URL column.
+ var urlEl = this.createEditableTextCell_(urlText);
+ urlEl.className = 'url-column';
+ this.contentElement.appendChild(urlEl);
+
+ // Do final adjustment to the input fields.
+ if (!engine['heading']) {
+ this.nameField_ = nameEl.querySelector('input');
+ this.keywordField_ = keywordEl.querySelector('input');
+ this.urlField_ = urlEl.querySelector('input');
+
+ if (engine['urlLocked'])
+ this.urlField_.disabled = true;
+
+ if (this.isPlaceholder_) {
+ this.nameField_.placeholder =
+ localStrings.getString('searchEngineTableNamePlaceholder');
+ this.keywordField_.placeholder =
+ localStrings.getString('searchEngineTableKeywordPlaceholder');
+ this.urlField_.placeholder =
+ localStrings.getString('searchEngineTableURLPlaceholder');
+ }
+
+ var fields = [ this.nameField_, this.keywordField_, this.urlField_ ];
+ for (var i = 0; i < fields.length; i++) {
+ fields[i].oninput = this.startFieldValidation_.bind(this);
+ }
+ }
+
+ // Listen for edit events.
+ this.addEventListener('edit', this.onEditStarted_.bind(this));
+ this.addEventListener('canceledit', this.onEditCancelled_.bind(this));
+ this.addEventListener('commitedit', this.onEditCommitted_.bind(this));
+ },
+
+ /**
+ * Returns a div containing an <input>, as well as static text if needed.
+ * @param {string} text The text of the cell.
+ * @return {HTMLElement} The HTML element for the cell.
+ * @private
+ */
+ createEditableTextCell_: function(text) {
+ var container = this.ownerDocument.createElement('div');
+
+ if (!this.isPlaceholder_) {
+ var textEl = this.ownerDocument.createElement('div');
+ textEl.className = 'static-text';
+ textEl.textContent = text;
+ textEl.setAttribute('editmode', false);
+ container.appendChild(textEl);
+ }
+
+ var inputEl = this.ownerDocument.createElement('input');
+ inputEl.type = 'text';
+ inputEl.value = text;
+ if (!this.isPlaceholder_) {
+ inputEl.setAttribute('editmode', true);
+ inputEl.staticVersion = textEl;
+ }
+ container.appendChild(inputEl);
+
+ return container;
+ },
+
+ /** @inheritDoc */
+ get initialFocusElement() {
+ return this.nameField_;
+ },
+
+ /** @inheritDoc */
+ get currentInputIsValid() {
+ return !this.waitingForValidation_ && this.currentlyValid_;
+ },
+
+ /** @inheritDoc */
+ hasBeenEdited: function(e) {
+ var engine = this.searchEngine_;
+ return this.nameField_.value != engine['name'] ||
+ this.keywordField_.value != engine['keyword'] ||
+ this.urlField_.value != engine['url'];
+ },
+
+ /**
+ * Called when entering edit mode; starts an edit session in the model.
+ * @param {Event} e The edit event.
+ * @private
+ */
+ onEditStarted_: function(e) {
+ var editIndex = this.searchEngine_['modelIndex'];
+ chrome.send('editSearchEngine', [String(editIndex)]);
+ },
+
+ /**
+ * Called when committing an edit; updates the model.
+ * @param {Event} e The end event.
+ * @private
+ */
+ onEditCommitted_: function(e) {
+ chrome.send('searchEngineEditCompleted', this.getInputFieldValues_());
+ // Update the static version immediately to prevent flickering before
+ // the model update callback updates the UI.
+ var editFields = [ this.nameField_, this.keywordField_, this.urlField_ ];
+ for (var i = 0; i < editFields.length; i++) {
+ var staticLabel = editFields[i].staticVersion;
+ if (staticLabel)
+ staticLabel.textContent = editFields[i].value;
+ }
+ },
+
+ /**
+ * Called when cancelling an edit; informs the model and resets the control
+ * states.
+ * @param {Event} e The cancel event.
+ * @private
+ */
+ onEditCancelled_: function() {
+ chrome.send('searchEngineEditCancelled');
+ var engine = this.searchEngine_;
+ this.nameField_.value = engine['name'];
+ this.keywordField_.value = engine['keyword'];
+ this.urlField_.value = engine['url'];
+
+ var editFields = [ this.nameField_, this.keywordField_, this.urlField_ ];
+ for (var i = 0; i < editFields.length; i++) {
+ editFields[i].classList.remove('invalid');
+ }
+ this.currentlyValid_ = !this.isPlaceholder_;
+ },
+
+ /**
+ * Returns the input field values as an array suitable for passing to
+ * chrome.send. The order of the array is important.
+ * @private
+ * @return {array} The current input field values.
+ */
+ getInputFieldValues_: function() {
+ return [ this.nameField_.value,
+ this.keywordField_.value,
+ this.urlField_.value ];
+ },
+
+ /**
+ * Begins the process of asynchronously validing the input fields.
+ * @private
+ */
+ startFieldValidation_: function() {
+ this.waitingForValidation_ = true;
+ var args = this.getInputFieldValues_();
+ args.push(this.searchEngine_['modelIndex']);
+ chrome.send('checkSearchEngineInfoValidity', args);
+ },
+
+ /**
+ * Callback for the completion of an input validition check.
+ * @param {Object} validity A dictionary of validitation results.
+ */
+ validationComplete: function(validity) {
+ this.waitingForValidation_ = false;
+ // TODO(stuartmorgan): Implement the full validation UI with
+ // checkmark/exclamation mark icons and tooltips.
+ if (validity['name'])
+ this.nameField_.classList.remove('invalid');
+ else
+ this.nameField_.classList.add('invalid');
+
+ if (validity['keyword'])
+ this.keywordField_.classList.remove('invalid');
+ else
+ this.keywordField_.classList.add('invalid');
+
+ if (validity['url'])
+ this.urlField_.classList.remove('invalid');
+ else
+ this.urlField_.classList.add('invalid');
+
+ this.currentlyValid_ = validity['name'] && validity['keyword'] &&
+ validity['url'];
},
};
var SearchEngineList = cr.ui.define('list');
SearchEngineList.prototype = {
- __proto__: DeletableItemList.prototype,
+ __proto__: InlineEditableItemList.prototype,
/** @inheritDoc */
createItem: function(searchEngine) {
@@ -95,6 +339,20 @@ cr.define('options.search_engines', function() {
canSelectIndex: function(index) {
return !this.dataModel.item(index).hasOwnProperty('heading');
},
+
+ /**
+ * Passes the results of an input validation check to the requesting row
+ * if it's still being edited.
+ * @param {number} modelIndex The model index of the item that was checked.
+ * @param {Object} validity A dictionary of validitation results.
+ */
+ validationComplete: function(validity, modelIndex) {
+ // If it's not still being edited, it no longer matters.
+ var currentSelection = this.selectedItem;
+ var listItem = this.getListItem(currentSelection);
+ if (listItem.editing && currentSelection['modelIndex'] == modelIndex)
+ listItem.validationComplete(validity);
+ },
};
// Export
diff --git a/chrome/browser/resources/shared/js/cr/ui/list_single_selection_model.js b/chrome/browser/resources/shared/js/cr/ui/list_single_selection_model.js
index f68818c..ff3c8c6 100644
--- a/chrome/browser/resources/shared/js/cr/ui/list_single_selection_model.js
+++ b/chrome/browser/resources/shared/js/cr/ui/list_single_selection_model.js
@@ -140,7 +140,6 @@ cr.define('cr.ui', function() {
if (this.selectedIndexBefore_ != this.selectedIndex_) {
var e = new Event('change');
var indexes = [this.selectedIndexBefore_, this.selectedIndex_];
- indexes.sort();
e.changes = indexes.filter(function(index) {
return index != -1;
}).map(function(index) {