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 * isExtension: 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
;
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
);
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
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.
164 // Don't focus on the button.
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));
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
};
217 event
.value
= { controlledBy
: 'policy' };
219 indicator
.handlePrefChange(event
);
220 this.appendChild(indicator
);
225 get currentInputIsValid() {
226 return !this.waitingForValidation_
&& this.currentlyValid_
;
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.
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.
253 onEditCommitted_: function(e
) {
254 chrome
.send('searchEngineEditCompleted', this.getInputFieldValues_());
258 * Called when cancelling an edit; informs the model and resets the control
260 * @param {Event} e The cancel event.
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.
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.
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.
304 this.nameField_
.setCustomValidity('');
306 this.nameField_
.setCustomValidity(
307 loadTimeData
.getString('editSearchEngineInvalidTitleToolTip'));
310 if (validity
.keyword
) {
311 this.keywordField_
.setCustomValidity('');
313 this.keywordField_
.setCustomValidity(
314 loadTimeData
.getString('editSearchEngineInvalidKeywordToolTip'));
318 this.urlField_
.setCustomValidity('');
320 this.urlField_
.setCustomValidity(
321 loadTimeData
.getString('editSearchEngineInvalidURLToolTip'));
324 this.currentlyValid_
= validity
.name
&& validity
.keyword
&& validity
.url
;
330 * @extends {options.InlineEditableItemList}
332 var SearchEngineList
= cr
.ui
.define('list');
334 SearchEngineList
.prototype = {
335 __proto__
: InlineEditableItemList
.prototype,
339 * @param {SearchEngine} searchEngine
341 createItem: function(searchEngine
) {
342 return new SearchEngineListItem(searchEngine
);
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
)
362 var listItem
= this.getListItem(currentSelection
);
363 if (listItem
.editing
&& currentSelection
.modelIndex
== modelIndex
)
364 listItem
.validationComplete(validity
);
370 SearchEngineList
: SearchEngineList