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 cr.define('options.search_engines', function() {
6 /** @const */ var ControlledSettingIndicator =
7 options.ControlledSettingIndicator;
8 /** @const */ var InlineEditableItemList = options.InlineEditableItemList;
9 /** @const */ var InlineEditableItem = options.InlineEditableItem;
10 /** @const */ var ListSelectionController = cr.ui.ListSelectionController;
13 * Creates a new search engine list item.
14 * @param {Object} searchEnigne The search engine this represents.
16 * @extends {cr.ui.ListItem}
18 function SearchEngineListItem(searchEngine) {
19 var el = cr.doc.createElement('div');
20 el.searchEngine_ = searchEngine;
21 SearchEngineListItem.decorate(el);
26 * Decorates an element as a search engine list item.
27 * @param {!HTMLElement} el The element to decorate.
29 SearchEngineListItem.decorate = function(el) {
30 el.__proto__ = SearchEngineListItem.prototype;
34 SearchEngineListItem.prototype = {
35 __proto__: InlineEditableItem.prototype,
38 * Input field for editing the engine name.
45 * Input field for editing the engine keyword.
52 * Input field for editing the engine url.
59 * Whether or not an input validation request is currently outstanding.
63 waitingForValidation_: false,
66 * Whether or not the current set of input is known to be valid.
70 currentlyValid_: false,
73 decorate: function() {
74 InlineEditableItem.prototype.decorate.call(this);
76 var engine = this.searchEngine_;
78 if (engine.modelIndex == '-1') {
79 this.isPlaceholder = true;
85 this.currentlyValid_ = !this.isPlaceholder;
88 this.classList.add('default');
90 this.deletable = engine.canBeRemoved;
92 // Construct the name column.
93 var nameColEl = this.ownerDocument.createElement('div');
94 nameColEl.className = 'name-column';
95 nameColEl.classList.add('weakrtl');
96 this.contentElement.appendChild(nameColEl);
99 var faviconDivEl = this.ownerDocument.createElement('div');
100 faviconDivEl.className = 'favicon';
101 if (!this.isPlaceholder) {
102 faviconDivEl.style.backgroundImage = imageset(
103 'chrome://favicon/size/16@scalefactorx/iconurl/' + engine.iconURL);
105 nameColEl.appendChild(faviconDivEl);
107 var nameEl = this.createEditableTextCell(engine.displayName);
108 nameEl.classList.add('weakrtl');
109 nameColEl.appendChild(nameEl);
111 // Then the keyword column.
112 var keywordEl = this.createEditableTextCell(engine.keyword);
113 keywordEl.className = 'keyword-column';
114 keywordEl.classList.add('weakrtl');
115 this.contentElement.appendChild(keywordEl);
117 // And the URL column.
118 var urlEl = this.createEditableTextCell(engine.url);
119 // Extensions should not display a URL column.
120 if (!engine.isExtension) {
121 var urlWithButtonEl = this.ownerDocument.createElement('div');
122 urlWithButtonEl.appendChild(urlEl);
123 urlWithButtonEl.className = 'url-column';
124 urlWithButtonEl.classList.add('weakrtl');
125 this.contentElement.appendChild(urlWithButtonEl);
126 // Add the Make Default button. Temporary until drag-and-drop
127 // re-ordering is implemented. When this is removed, remove the extra
129 if (engine.canBeDefault) {
130 var makeDefaultButtonEl = this.ownerDocument.createElement('button');
131 makeDefaultButtonEl.className =
132 'custom-appearance list-inline-button';
133 makeDefaultButtonEl.textContent =
134 loadTimeData.getString('makeDefaultSearchEngineButton');
135 makeDefaultButtonEl.onclick = function(e) {
136 chrome.send('managerSetDefaultSearchEngine', [engine.modelIndex]);
138 makeDefaultButtonEl.onmousedown = function(e) {
139 // Don't select the row when clicking the button.
141 // Don't focus on the button.
144 urlWithButtonEl.appendChild(makeDefaultButtonEl);
148 // Do final adjustment to the input fields.
149 this.nameField_ = nameEl.querySelector('input');
150 // The editable field uses the raw name, not the display name.
151 this.nameField_.value = engine.name;
152 this.keywordField_ = keywordEl.querySelector('input');
153 this.urlField_ = urlEl.querySelector('input');
155 if (engine.urlLocked)
156 this.urlField_.disabled = true;
158 if (engine.isExtension)
159 this.nameField_.disabled = true;
161 if (this.isPlaceholder) {
162 this.nameField_.placeholder =
163 loadTimeData.getString('searchEngineTableNamePlaceholder');
164 this.keywordField_.placeholder =
165 loadTimeData.getString('searchEngineTableKeywordPlaceholder');
166 this.urlField_.placeholder =
167 loadTimeData.getString('searchEngineTableURLPlaceholder');
170 var fields = [this.nameField_, this.keywordField_, this.urlField_];
171 for (var i = 0; i < fields.length; i++) {
172 fields[i].oninput = this.startFieldValidation_.bind(this);
175 // Listen for edit events.
176 if (engine.canBeEdited) {
177 this.addEventListener('edit', this.onEditStarted_.bind(this));
178 this.addEventListener('canceledit', this.onEditCancelled_.bind(this));
179 this.addEventListener('commitedit', this.onEditCommitted_.bind(this));
181 this.editable = false;
182 this.querySelector('.row-delete-button').hidden = true;
183 var indicator = ControlledSettingIndicator();
184 indicator.setAttribute('setting', 'search-engine');
185 // Create a synthetic pref change event decorated as
186 // CoreOptionsHandler::CreateValueForPref() does.
187 var event = new Event(this.contentType);
188 if (engine.extension) {
189 event.value = { controlledBy: 'extension' };
190 // TODO(mad): add id, name, and icon once we solved the issue with the
191 // search engine manager in http://crbug.com/314507.
193 event.value = { controlledBy: 'policy' };
195 indicator.handlePrefChange(event);
196 this.appendChild(indicator);
201 get currentInputIsValid() {
202 return !this.waitingForValidation_ && this.currentlyValid_;
206 get hasBeenEdited() {
207 var engine = this.searchEngine_;
208 return this.nameField_.value != engine.name ||
209 this.keywordField_.value != engine.keyword ||
210 this.urlField_.value != engine.url;
214 * Called when entering edit mode; starts an edit session in the model.
215 * @param {Event} e The edit event.
218 onEditStarted_: function(e) {
219 var editIndex = this.searchEngine_.modelIndex;
220 chrome.send('editSearchEngine', [String(editIndex)]);
221 this.startFieldValidation_();
225 * Called when committing an edit; updates the model.
226 * @param {Event} e The end event.
229 onEditCommitted_: function(e) {
230 chrome.send('searchEngineEditCompleted', this.getInputFieldValues_());
234 * Called when cancelling an edit; informs the model and resets the control
236 * @param {Event} e The cancel event.
239 onEditCancelled_: function() {
240 chrome.send('searchEngineEditCancelled');
242 // The name field has been automatically set to match the display name,
243 // but it should use the raw name instead.
244 this.nameField_.value = this.searchEngine_.name;
245 this.currentlyValid_ = !this.isPlaceholder;
249 * Returns the input field values as an array suitable for passing to
250 * chrome.send. The order of the array is important.
252 * @return {array} The current input field values.
254 getInputFieldValues_: function() {
255 return [this.nameField_.value,
256 this.keywordField_.value,
257 this.urlField_.value];
261 * Begins the process of asynchronously validing the input fields.
264 startFieldValidation_: function() {
265 this.waitingForValidation_ = true;
266 var args = this.getInputFieldValues_();
267 args.push(this.searchEngine_.modelIndex);
268 chrome.send('checkSearchEngineInfoValidity', args);
272 * Callback for the completion of an input validition check.
273 * @param {Object} validity A dictionary of validitation results.
275 validationComplete: function(validity) {
276 this.waitingForValidation_ = false;
277 // TODO(stuartmorgan): Implement the full validation UI with
278 // checkmark/exclamation mark icons and tooltips showing the errors.
280 this.nameField_.setCustomValidity('');
282 this.nameField_.setCustomValidity(
283 loadTimeData.getString('editSearchEngineInvalidTitleToolTip'));
286 if (validity.keyword) {
287 this.keywordField_.setCustomValidity('');
289 this.keywordField_.setCustomValidity(
290 loadTimeData.getString('editSearchEngineInvalidKeywordToolTip'));
294 this.urlField_.setCustomValidity('');
296 this.urlField_.setCustomValidity(
297 loadTimeData.getString('editSearchEngineInvalidURLToolTip'));
300 this.currentlyValid_ = validity.name && validity.keyword && validity.url;
304 var SearchEngineList = cr.ui.define('list');
306 SearchEngineList.prototype = {
307 __proto__: InlineEditableItemList.prototype,
310 createItem: function(searchEngine) {
311 return new SearchEngineListItem(searchEngine);
315 deleteItemAtIndex: function(index) {
316 var modelIndex = this.dataModel.item(index).modelIndex;
317 chrome.send('removeSearchEngine', [String(modelIndex)]);
321 * Passes the results of an input validation check to the requesting row
322 * if it's still being edited.
323 * @param {number} modelIndex The model index of the item that was checked.
324 * @param {Object} validity A dictionary of validitation results.
326 validationComplete: function(validity, modelIndex) {
327 // If it's not still being edited, it no longer matters.
328 var currentSelection = this.selectedItem;
329 if (!currentSelection)
331 var listItem = this.getListItem(currentSelection);
332 if (listItem.editing && currentSelection.modelIndex == modelIndex)
333 listItem.validationComplete(validity);
339 SearchEngineList: SearchEngineList