1 // Copyright (c) 2012 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.
6 * @typedef {{canBeDefault: boolean,
7 * canBeEdited: boolean,
8 * canBeRemoved: boolean,
10 * displayName: string,
11 * extension: (Object|undefined),
12 * iconURL: (string|undefined),
13 * isOmniboxExtension: boolean,
18 * urlLocked: boolean}}
19 * @see chrome/browser/ui/webui/options/search_engine_manager_handler.cc
23 cr.define('options.search_engines', function() {
24 /** @const */ var ControlledSettingIndicator =
25 options.ControlledSettingIndicator;
26 /** @const */ var InlineEditableItemList = options.InlineEditableItemList;
27 /** @const */ var InlineEditableItem = options.InlineEditableItem;
28 /** @const */ var ListSelectionController = cr.ui.ListSelectionController;
31 * Creates a new search engine list item.
32 * @param {SearchEngine} searchEngine The search engine this represents.
34 * @extends {options.InlineEditableItem}
36 function SearchEngineListItem(searchEngine) {
37 var el = cr.doc.createElement('div');
38 el.searchEngine_ = searchEngine;
39 SearchEngineListItem.decorate(el);
44 * Decorates an element as a search engine list item.
45 * @param {!HTMLElement} el The element to decorate.
47 SearchEngineListItem.decorate = function(el) {
48 el.__proto__ = SearchEngineListItem.prototype;
52 SearchEngineListItem.prototype = {
53 __proto__: InlineEditableItem.prototype,
56 * Input field for editing the engine name.
63 * Input field for editing the engine keyword.
70 * Input field for editing the engine url.
77 * Whether or not an input validation request is currently outstanding.
81 waitingForValidation_: false,
84 * Whether or not the current set of input is known to be valid.
88 currentlyValid_: false,
91 * @type {?SearchEngine}
96 decorate: function() {
97 InlineEditableItem.prototype.decorate.call(this);
99 var engine = this.searchEngine_;
101 if (engine.modelIndex == '-1') {
102 this.isPlaceholder = true;
108 this.currentlyValid_ = !this.isPlaceholder;
111 this.classList.add('default');
113 this.deletable = engine.canBeRemoved;
114 this.closeButtonFocusAllowed = true;
116 // Construct the name column.
117 var nameColEl = this.ownerDocument.createElement('div');
118 nameColEl.className = 'name-column';
119 nameColEl.classList.add('weakrtl');
120 this.contentElement.appendChild(nameColEl);
123 var faviconDivEl = this.ownerDocument.createElement('div');
124 faviconDivEl.className = 'favicon';
125 if (!this.isPlaceholder) {
126 faviconDivEl.style.backgroundImage = imageset(
127 'chrome://favicon/size/16@scalefactorx/iconurl/' + engine.iconURL);
129 nameColEl.appendChild(faviconDivEl);
131 var nameEl = this.createEditableTextCell(engine.displayName);
132 nameEl.classList.add('weakrtl');
133 nameColEl.appendChild(nameEl);
135 // Then the keyword column.
136 var keywordEl = this.createEditableTextCell(engine.keyword);
137 keywordEl.className = 'keyword-column';
138 keywordEl.classList.add('weakrtl');
139 this.contentElement.appendChild(keywordEl);
141 // And the URL column.
142 var urlEl = this.createEditableTextCell(engine.url);
143 var makeDefaultButtonEl = null;
144 // Extensions should not display a URL column.
145 if (!engine.isOmniboxExtension) {
146 var urlWithButtonEl = this.ownerDocument.createElement('div');
147 urlWithButtonEl.appendChild(urlEl);
148 urlWithButtonEl.className = 'url-column';
149 urlWithButtonEl.classList.add('weakrtl');
150 this.contentElement.appendChild(urlWithButtonEl);
151 // Add the Make Default button. Temporary until drag-and-drop
152 // re-ordering is implemented. When this is removed, remove the extra
154 if (engine.canBeDefault) {
155 makeDefaultButtonEl = this.ownerDocument.createElement('button');
156 makeDefaultButtonEl.className =
157 'custom-appearance list-inline-button';
158 makeDefaultButtonEl.textContent =
159 loadTimeData.getString('makeDefaultSearchEngineButton');
160 makeDefaultButtonEl.onclick = function(e) {
161 chrome.send('managerSetDefaultSearchEngine', [engine.modelIndex]);
163 makeDefaultButtonEl.onmousedown = function(e) {
164 // Don't select the row when clicking the button.
166 // Don't focus on the button.
169 urlWithButtonEl.appendChild(makeDefaultButtonEl);
173 // Do final adjustment to the input fields.
174 this.nameField_ = /** @type {HTMLElement} */(
175 nameEl.querySelector('input'));
176 // The editable field uses the raw name, not the display name.
177 this.nameField_.value = engine.name;
178 this.keywordField_ = /** @type {HTMLElement} */(
179 keywordEl.querySelector('input'));
180 this.urlField_ = /** @type {HTMLElement} */(urlEl.querySelector('input'));
182 if (engine.urlLocked)
183 this.urlField_.disabled = true;
185 if (this.isPlaceholder) {
186 this.nameField_.placeholder =
187 loadTimeData.getString('searchEngineTableNamePlaceholder');
188 this.keywordField_.placeholder =
189 loadTimeData.getString('searchEngineTableKeywordPlaceholder');
190 this.urlField_.placeholder =
191 loadTimeData.getString('searchEngineTableURLPlaceholder');
194 this.setFocusableColumnIndex(this.nameField_, 0);
195 this.setFocusableColumnIndex(this.keywordField_, 1);
196 this.setFocusableColumnIndex(this.urlField_, 2);
197 this.setFocusableColumnIndex(makeDefaultButtonEl, 3);
198 this.setFocusableColumnIndex(this.closeButtonElement, 4);
200 var fields = [this.nameField_, this.keywordField_, this.urlField_];
201 for (var i = 0; i < fields.length; i++) {
202 fields[i].oninput = this.startFieldValidation_.bind(this);
205 // Listen for edit events.
206 if (engine.canBeEdited) {
207 this.addEventListener('edit', this.onEditStarted_.bind(this));
208 this.addEventListener('canceledit', this.onEditCancelled_.bind(this));
209 this.addEventListener('commitedit', this.onEditCommitted_.bind(this));
211 this.editable = false;
212 this.querySelector('.row-delete-button').hidden = true;
213 var indicator = new ControlledSettingIndicator();
214 indicator.setAttribute('setting', 'search-engine');
215 // Create a synthetic pref change event decorated as
216 // CoreOptionsHandler::CreateValueForPref() does.
217 var event = new Event(this.contentType);
218 if (engine.extension) {
219 event.value = { controlledBy: 'extension',
220 extension: engine.extension };
222 event.value = { controlledBy: 'policy' };
224 indicator.handlePrefChange(event);
225 this.appendChild(indicator);
230 get currentInputIsValid() {
231 return !this.waitingForValidation_ && this.currentlyValid_;
235 get hasBeenEdited() {
236 var engine = this.searchEngine_;
237 return this.nameField_.value != engine.name ||
238 this.keywordField_.value != engine.keyword ||
239 this.urlField_.value != engine.url;
243 * Called when entering edit mode; starts an edit session in the model.
244 * @param {Event} e The edit event.
247 onEditStarted_: function(e) {
248 var editIndex = this.searchEngine_.modelIndex;
249 chrome.send('editSearchEngine', [String(editIndex)]);
250 this.startFieldValidation_();
254 * Called when committing an edit; updates the model.
255 * @param {Event} e The end event.
258 onEditCommitted_: function(e) {
259 chrome.send('searchEngineEditCompleted', this.getInputFieldValues_());
263 * Called when cancelling an edit; informs the model and resets the control
265 * @param {Event} e The cancel event.
268 onEditCancelled_: function(e) {
269 chrome.send('searchEngineEditCancelled');
271 // The name field has been automatically set to match the display name,
272 // but it should use the raw name instead.
273 this.nameField_.value = this.searchEngine_.name;
274 this.currentlyValid_ = !this.isPlaceholder;
278 * Returns the input field values as an array suitable for passing to
279 * chrome.send. The order of the array is important.
281 * @return {Array} The current input field values.
283 getInputFieldValues_: function() {
284 return [this.nameField_.value,
285 this.keywordField_.value,
286 this.urlField_.value];
290 * Begins the process of asynchronously validing the input fields.
293 startFieldValidation_: function() {
294 this.waitingForValidation_ = true;
295 var args = this.getInputFieldValues_();
296 args.push(this.searchEngine_.modelIndex);
297 chrome.send('checkSearchEngineInfoValidity', args);
301 * Callback for the completion of an input validition check.
302 * @param {Object} validity A dictionary of validitation results.
304 validationComplete: function(validity) {
305 this.waitingForValidation_ = false;
306 // TODO(stuartmorgan): Implement the full validation UI with
307 // checkmark/exclamation mark icons and tooltips showing the errors.
309 this.nameField_.setCustomValidity('');
311 this.nameField_.setCustomValidity(
312 loadTimeData.getString('editSearchEngineInvalidTitleToolTip'));
315 if (validity.keyword) {
316 this.keywordField_.setCustomValidity('');
318 this.keywordField_.setCustomValidity(
319 loadTimeData.getString('editSearchEngineInvalidKeywordToolTip'));
323 this.urlField_.setCustomValidity('');
325 this.urlField_.setCustomValidity(
326 loadTimeData.getString('editSearchEngineInvalidURLToolTip'));
329 this.currentlyValid_ = validity.name && validity.keyword && validity.url;
335 * @extends {options.InlineEditableItemList}
337 var SearchEngineList = cr.ui.define('list');
339 SearchEngineList.prototype = {
340 __proto__: InlineEditableItemList.prototype,
344 * @param {SearchEngine} searchEngine
346 createItem: function(searchEngine) {
347 return new SearchEngineListItem(searchEngine);
351 deleteItemAtIndex: function(index) {
352 var modelIndex = this.dataModel.item(index).modelIndex;
353 chrome.send('removeSearchEngine', [String(modelIndex)]);
357 * Passes the results of an input validation check to the requesting row
358 * if it's still being edited.
359 * @param {number} modelIndex The model index of the item that was checked.
360 * @param {Object} validity A dictionary of validitation results.
362 validationComplete: function(validity, modelIndex) {
363 // If it's not still being edited, it no longer matters.
364 var currentSelection = this.selectedItem;
365 if (!currentSelection)
367 var listItem = this.getListItem(currentSelection);
368 if (listItem.editing && currentSelection.modelIndex == modelIndex)
369 listItem.validationComplete(validity);
375 SearchEngineList: SearchEngineList