Move Webstore URL concepts to //extensions and out
[chromium-blink-merge.git] / chrome / browser / resources / options / search_engine_manager_engine_list.js
blob4fe36511f337284ba059b8c4b2744bbc47531418
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.
5 /**
6 * @typedef {{canBeDefault: boolean,
7 * canBeEdited: boolean,
8 * canBeRemoved: boolean,
9 * default: boolean,
10 * displayName: string,
11 * extension: (Object|undefined),
12 * iconURL: (string|undefined),
13 * isExtension: boolean,
14 * keyword: string,
15 * modelIndex: string,
16 * name: string,
17 * url: string,
18 * urlLocked: boolean}}
19 * @see chrome/browser/ui/webui/options/search_engine_manager_handler.cc
21 var SearchEngine;
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;
30 /**
31 * Creates a new search engine list item.
32 * @param {SearchEngine} searchEngine The search engine this represents.
33 * @constructor
34 * @extends {options.InlineEditableItem}
36 function SearchEngineListItem(searchEngine) {
37 var el = cr.doc.createElement('div');
38 el.searchEngine_ = searchEngine;
39 SearchEngineListItem.decorate(el);
40 return el;
43 /**
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;
49 el.decorate();
52 SearchEngineListItem.prototype = {
53 __proto__: InlineEditableItem.prototype,
55 /**
56 * Input field for editing the engine name.
57 * @type {HTMLElement}
58 * @private
60 nameField_: null,
62 /**
63 * Input field for editing the engine keyword.
64 * @type {HTMLElement}
65 * @private
67 keywordField_: null,
69 /**
70 * Input field for editing the engine url.
71 * @type {HTMLElement}
72 * @private
74 urlField_: null,
76 /**
77 * Whether or not an input validation request is currently outstanding.
78 * @type {boolean}
79 * @private
81 waitingForValidation_: false,
83 /**
84 * Whether or not the current set of input is known to be valid.
85 * @type {boolean}
86 * @private
88 currentlyValid_: false,
90 /**
91 * @type {?SearchEngine}
93 searchEngine_: null,
95 /** @override */
96 decorate: function() {
97 InlineEditableItem.prototype.decorate.call(this);
99 var engine = this.searchEngine_;
101 if (engine.modelIndex == '-1') {
102 this.isPlaceholder = true;
103 engine.name = '';
104 engine.keyword = '';
105 engine.url = '';
108 this.currentlyValid_ = !this.isPlaceholder;
110 if (engine.default)
111 this.classList.add('default');
113 this.deletable = engine.canBeRemoved;
115 // Construct the name column.
116 var nameColEl = this.ownerDocument.createElement('div');
117 nameColEl.className = 'name-column';
118 nameColEl.classList.add('weakrtl');
119 this.contentElement.appendChild(nameColEl);
121 // Add the favicon.
122 var faviconDivEl = this.ownerDocument.createElement('div');
123 faviconDivEl.className = 'favicon';
124 if (!this.isPlaceholder) {
125 faviconDivEl.style.backgroundImage = imageset(
126 'chrome://favicon/size/16@scalefactorx/iconurl/' + engine.iconURL);
128 nameColEl.appendChild(faviconDivEl);
130 var nameEl = this.createEditableTextCell(engine.displayName);
131 nameEl.classList.add('weakrtl');
132 nameColEl.appendChild(nameEl);
134 // Then the keyword column.
135 var keywordEl = this.createEditableTextCell(engine.keyword);
136 keywordEl.className = 'keyword-column';
137 keywordEl.classList.add('weakrtl');
138 this.contentElement.appendChild(keywordEl);
140 // And the URL column.
141 var urlEl = this.createEditableTextCell(engine.url);
142 // Extensions should not display a URL column.
143 if (!engine.isExtension) {
144 var urlWithButtonEl = this.ownerDocument.createElement('div');
145 urlWithButtonEl.appendChild(urlEl);
146 urlWithButtonEl.className = 'url-column';
147 urlWithButtonEl.classList.add('weakrtl');
148 this.contentElement.appendChild(urlWithButtonEl);
149 // Add the Make Default button. Temporary until drag-and-drop
150 // re-ordering is implemented. When this is removed, remove the extra
151 // div above.
152 if (engine.canBeDefault) {
153 var makeDefaultButtonEl = this.ownerDocument.createElement('button');
154 makeDefaultButtonEl.className =
155 'custom-appearance list-inline-button';
156 makeDefaultButtonEl.textContent =
157 loadTimeData.getString('makeDefaultSearchEngineButton');
158 makeDefaultButtonEl.onclick = function(e) {
159 chrome.send('managerSetDefaultSearchEngine', [engine.modelIndex]);
161 makeDefaultButtonEl.onmousedown = function(e) {
162 // Don't select the row when clicking the button.
163 e.stopPropagation();
164 // Don't focus on the button.
165 e.preventDefault();
167 urlWithButtonEl.appendChild(makeDefaultButtonEl);
171 // Do final adjustment to the input fields.
172 this.nameField_ = /** @type {HTMLElement} */(
173 nameEl.querySelector('input'));
174 // The editable field uses the raw name, not the display name.
175 this.nameField_.value = engine.name;
176 this.keywordField_ = /** @type {HTMLElement} */(
177 keywordEl.querySelector('input'));
178 this.urlField_ = /** @type {HTMLElement} */(urlEl.querySelector('input'));
180 if (engine.urlLocked)
181 this.urlField_.disabled = true;
183 if (engine.isExtension)
184 this.nameField_.disabled = true;
186 if (this.isPlaceholder) {
187 this.nameField_.placeholder =
188 loadTimeData.getString('searchEngineTableNamePlaceholder');
189 this.keywordField_.placeholder =
190 loadTimeData.getString('searchEngineTableKeywordPlaceholder');
191 this.urlField_.placeholder =
192 loadTimeData.getString('searchEngineTableURLPlaceholder');
195 var fields = [this.nameField_, this.keywordField_, this.urlField_];
196 for (var i = 0; i < fields.length; i++) {
197 fields[i].oninput = this.startFieldValidation_.bind(this);
200 // Listen for edit events.
201 if (engine.canBeEdited) {
202 this.addEventListener('edit', this.onEditStarted_.bind(this));
203 this.addEventListener('canceledit', this.onEditCancelled_.bind(this));
204 this.addEventListener('commitedit', this.onEditCommitted_.bind(this));
205 } else {
206 this.editable = false;
207 this.querySelector('.row-delete-button').hidden = true;
208 var indicator = new ControlledSettingIndicator();
209 indicator.setAttribute('setting', 'search-engine');
210 // Create a synthetic pref change event decorated as
211 // CoreOptionsHandler::CreateValueForPref() does.
212 var event = new Event(this.contentType);
213 if (engine.extension) {
214 event.value = { controlledBy: 'extension',
215 extension: engine.extension };
216 } else {
217 event.value = { controlledBy: 'policy' };
219 indicator.handlePrefChange(event);
220 this.appendChild(indicator);
224 /** @override */
225 get currentInputIsValid() {
226 return !this.waitingForValidation_ && this.currentlyValid_;
229 /** @override */
230 get hasBeenEdited() {
231 var engine = this.searchEngine_;
232 return this.nameField_.value != engine.name ||
233 this.keywordField_.value != engine.keyword ||
234 this.urlField_.value != engine.url;
238 * Called when entering edit mode; starts an edit session in the model.
239 * @param {Event} e The edit event.
240 * @private
242 onEditStarted_: function(e) {
243 var editIndex = this.searchEngine_.modelIndex;
244 chrome.send('editSearchEngine', [String(editIndex)]);
245 this.startFieldValidation_();
249 * Called when committing an edit; updates the model.
250 * @param {Event} e The end event.
251 * @private
253 onEditCommitted_: function(e) {
254 chrome.send('searchEngineEditCompleted', this.getInputFieldValues_());
258 * Called when cancelling an edit; informs the model and resets the control
259 * states.
260 * @param {Event} e The cancel event.
261 * @private
263 onEditCancelled_: function(e) {
264 chrome.send('searchEngineEditCancelled');
266 // The name field has been automatically set to match the display name,
267 // but it should use the raw name instead.
268 this.nameField_.value = this.searchEngine_.name;
269 this.currentlyValid_ = !this.isPlaceholder;
273 * Returns the input field values as an array suitable for passing to
274 * chrome.send. The order of the array is important.
275 * @private
276 * @return {Array} The current input field values.
278 getInputFieldValues_: function() {
279 return [this.nameField_.value,
280 this.keywordField_.value,
281 this.urlField_.value];
285 * Begins the process of asynchronously validing the input fields.
286 * @private
288 startFieldValidation_: function() {
289 this.waitingForValidation_ = true;
290 var args = this.getInputFieldValues_();
291 args.push(this.searchEngine_.modelIndex);
292 chrome.send('checkSearchEngineInfoValidity', args);
296 * Callback for the completion of an input validition check.
297 * @param {Object} validity A dictionary of validitation results.
299 validationComplete: function(validity) {
300 this.waitingForValidation_ = false;
301 // TODO(stuartmorgan): Implement the full validation UI with
302 // checkmark/exclamation mark icons and tooltips showing the errors.
303 if (validity.name) {
304 this.nameField_.setCustomValidity('');
305 } else {
306 this.nameField_.setCustomValidity(
307 loadTimeData.getString('editSearchEngineInvalidTitleToolTip'));
310 if (validity.keyword) {
311 this.keywordField_.setCustomValidity('');
312 } else {
313 this.keywordField_.setCustomValidity(
314 loadTimeData.getString('editSearchEngineInvalidKeywordToolTip'));
317 if (validity.url) {
318 this.urlField_.setCustomValidity('');
319 } else {
320 this.urlField_.setCustomValidity(
321 loadTimeData.getString('editSearchEngineInvalidURLToolTip'));
324 this.currentlyValid_ = validity.name && validity.keyword && validity.url;
329 * @constructor
330 * @extends {options.InlineEditableItemList}
332 var SearchEngineList = cr.ui.define('list');
334 SearchEngineList.prototype = {
335 __proto__: InlineEditableItemList.prototype,
338 * @override
339 * @param {SearchEngine} searchEngine
341 createItem: function(searchEngine) {
342 return new SearchEngineListItem(searchEngine);
345 /** @override */
346 deleteItemAtIndex: function(index) {
347 var modelIndex = this.dataModel.item(index).modelIndex;
348 chrome.send('removeSearchEngine', [String(modelIndex)]);
352 * Passes the results of an input validation check to the requesting row
353 * if it's still being edited.
354 * @param {number} modelIndex The model index of the item that was checked.
355 * @param {Object} validity A dictionary of validitation results.
357 validationComplete: function(validity, modelIndex) {
358 // If it's not still being edited, it no longer matters.
359 var currentSelection = this.selectedItem;
360 if (!currentSelection)
361 return;
362 var listItem = this.getListItem(currentSelection);
363 if (listItem.editing && currentSelection.modelIndex == modelIndex)
364 listItem.validationComplete(validity);
368 // Export
369 return {
370 SearchEngineList: SearchEngineList