Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(109)

Side by Side Diff: chrome/browser/resources/options/search_engine_manager_engine_list.js

Issue 6151004: DOMUI Prefs: Replace search engine edit overlay with inline editing. (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src
Patch Set: Address last comment Created 9 years, 11 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
OLDNEW
1 // Copyright (c) 2010 The Chromium Authors. All rights reserved. 1 // Copyright (c) 2010 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be 2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file. 3 // found in the LICENSE file.
4 4
5 cr.define('options.search_engines', function() { 5 cr.define('options.search_engines', function() {
6 const DeletableItem = options.DeletableItem; 6 const InlineEditableItemList = options.InlineEditableItemList;
7 const DeletableItemList = options.DeletableItemList; 7 const InlineEditableItem = options.InlineEditableItem;
8 const ListInlineHeaderSelectionController = 8 const ListInlineHeaderSelectionController =
9 options.ListInlineHeaderSelectionController; 9 options.ListInlineHeaderSelectionController;
10 10
11 /** 11 /**
12 * Creates a new search engine list item. 12 * Creates a new search engine list item.
13 * @param {Object} searchEnigne The search engine this represents. 13 * @param {Object} searchEnigne The search engine this represents.
14 * @constructor 14 * @constructor
15 * @extends {cr.ui.ListItem} 15 * @extends {cr.ui.ListItem}
16 */ 16 */
17 function SearchEngineListItem(searchEngine) { 17 function SearchEngineListItem(searchEngine) {
18 var el = cr.doc.createElement('div'); 18 var el = cr.doc.createElement('div');
19 el.searchEngine_ = searchEngine; 19 el.searchEngine_ = searchEngine;
20 SearchEngineListItem.decorate(el); 20 SearchEngineListItem.decorate(el);
21 return el; 21 return el;
22 } 22 }
23 23
24 /** 24 /**
25 * Decorates an element as a search engine list item. 25 * Decorates an element as a search engine list item.
26 * @param {!HTMLElement} el The element to decorate. 26 * @param {!HTMLElement} el The element to decorate.
27 */ 27 */
28 SearchEngineListItem.decorate = function(el) { 28 SearchEngineListItem.decorate = function(el) {
29 el.__proto__ = SearchEngineListItem.prototype; 29 el.__proto__ = SearchEngineListItem.prototype;
30 el.decorate(); 30 el.decorate();
31 }; 31 };
32 32
33 SearchEngineListItem.prototype = { 33 SearchEngineListItem.prototype = {
34 __proto__: DeletableItem.prototype, 34 __proto__: InlineEditableItem.prototype,
35
36 /**
37 * Input field for editing the engine name.
38 * @type {HTMLElement}
39 * @private
40 */
41 nameField_: null,
42
43 /**
44 * Input field for editing the engine keyword.
45 * @type {HTMLElement}
46 * @private
47 */
48 keywordField_: null,
49
50 /**
51 * Input field for editing the engine url.
52 * @type {HTMLElement}
53 * @private
54 */
55 urlField_: null,
56
57 /**
58 * Whether or not this is a placeholder for adding an engine.
59 * @type {boolean}
60 * @private
61 */
62 isPlaceholder_: false,
63
64 /**
65 * Whether or not an input validation request is currently outstanding.
66 * @type {boolean}
67 * @private
68 */
69 waitingForValidation_: false,
70
71 /**
72 * Whether or not the current set of input is known to be valid.
73 * @type {boolean}
74 * @private
75 */
76 currentlyValid_: false,
35 77
36 /** @inheritDoc */ 78 /** @inheritDoc */
37 decorate: function() { 79 decorate: function() {
38 DeletableItem.prototype.decorate.call(this); 80 InlineEditableItem.prototype.decorate.call(this);
39 81
40 var engine = this.searchEngine_; 82 var engine = this.searchEngine_;
41 83
42 if (engine['heading']) 84 if (engine['modelIndex'] == '-1') {
85 this.isPlaceholder_ = true;
86 engine['name'] = '';
87 engine['keyword'] = '';
88 engine['url'] = '';
89 }
90
91 this.currentlyValid_ = !this.isPlaceholder_;
92
93 if (engine['heading']) {
43 this.classList.add('heading'); 94 this.classList.add('heading');
44 else if (engine['default']) 95 this.editable = false;
96 } else if (engine['default']) {
45 this.classList.add('default'); 97 this.classList.add('default');
98 }
46 99
47 this.deletable = engine['canBeRemoved']; 100 this.deletable = engine['canBeRemoved'];
48 101
49 var nameEl = this.ownerDocument.createElement('div'); 102 var nameText = engine['name'];
50 nameEl.className = 'name'; 103 var keywordText = engine['keyword'];
104 var urlText = engine['url'];
51 if (engine['heading']) { 105 if (engine['heading']) {
52 nameEl.textContent = engine['heading']; 106 nameText = engine['heading'];
53 } else { 107 keywordText = localStrings.getString('searchEngineTableKeywordHeader');
54 nameEl.textContent = engine['name']; 108 urlText = localStrings.getString('searchEngineTableURLHeader');
55 nameEl.classList.add('favicon-cell'); 109 }
56 nameEl.style.backgroundImage = url('chrome://favicon/iconurl/' + 110
57 engine['iconURL']); 111 // Construct the name column.
58 } 112 var nameColEl = this.ownerDocument.createElement('div');
59 this.contentElement.appendChild(nameEl); 113 nameColEl.className = 'name-column';
60 114 this.contentElement.appendChild(nameColEl);
61 var keywordEl = this.ownerDocument.createElement('div'); 115
62 keywordEl.className = 'keyword'; 116 // For non-heading rows, start with a favicon.
63 keywordEl.textContent = engine['heading'] ? 117 if (!engine['heading']) {
64 localStrings.getString('searchEngineTableKeywordHeader') : 118 var faviconDivEl = this.ownerDocument.createElement('div');
65 engine['keyword']; 119 faviconDivEl.className = 'favicon';
120 var imgEl = this.ownerDocument.createElement('img');
121 imgEl.src = 'chrome://favicon/iconurl/' + engine['iconURL'];
122 faviconDivEl.appendChild(imgEl);
123 nameColEl.appendChild(faviconDivEl);
124 }
125
126 var nameEl = this.createEditableTextCell_(nameText);
127 nameColEl.appendChild(nameEl);
128
129 // Then the keyword column.
130 var keywordEl = this.createEditableTextCell_(keywordText);
131 keywordEl.className = 'keyword-column';
66 this.contentElement.appendChild(keywordEl); 132 this.contentElement.appendChild(keywordEl);
133
134 // And the URL column.
135 var urlEl = this.createEditableTextCell_(urlText);
136 urlEl.className = 'url-column';
137 this.contentElement.appendChild(urlEl);
138
139 // Do final adjustment to the input fields.
140 if (!engine['heading']) {
141 this.nameField_ = nameEl.querySelector('input');
142 this.keywordField_ = keywordEl.querySelector('input');
143 this.urlField_ = urlEl.querySelector('input');
144
145 if (engine['urlLocked'])
146 this.urlField_.disabled = true;
147
148 if (this.isPlaceholder_) {
149 this.nameField_.placeholder =
150 localStrings.getString('searchEngineTableNamePlaceholder');
151 this.keywordField_.placeholder =
152 localStrings.getString('searchEngineTableKeywordPlaceholder');
153 this.urlField_.placeholder =
154 localStrings.getString('searchEngineTableURLPlaceholder');
155 }
156
157 var fields = [ this.nameField_, this.keywordField_, this.urlField_ ];
158 for (var i = 0; i < fields.length; i++) {
159 fields[i].oninput = this.startFieldValidation_.bind(this);
160 }
161 }
162
163 // Listen for edit events.
164 this.addEventListener('edit', this.onEditStarted_.bind(this));
165 this.addEventListener('canceledit', this.onEditCancelled_.bind(this));
166 this.addEventListener('commitedit', this.onEditCommitted_.bind(this));
167 },
168
169 /**
170 * Returns a div containing an <input>, as well as static text if needed.
171 * @param {string} text The text of the cell.
172 * @return {HTMLElement} The HTML element for the cell.
173 * @private
174 */
175 createEditableTextCell_: function(text) {
176 var container = this.ownerDocument.createElement('div');
177
178 if (!this.isPlaceholder_) {
179 var textEl = this.ownerDocument.createElement('div');
180 textEl.className = 'static-text';
181 textEl.textContent = text;
182 textEl.setAttribute('editmode', false);
183 container.appendChild(textEl);
184 }
185
186 var inputEl = this.ownerDocument.createElement('input');
187 inputEl.type = 'text';
188 inputEl.value = text;
189 if (!this.isPlaceholder_) {
190 inputEl.setAttribute('editmode', true);
191 inputEl.staticVersion = textEl;
192 }
193 container.appendChild(inputEl);
194
195 return container;
196 },
197
198 /** @inheritDoc */
199 get initialFocusElement() {
200 return this.nameField_;
201 },
202
203 /** @inheritDoc */
204 get currentInputIsValid() {
205 return !this.waitingForValidation_ && this.currentlyValid_;
206 },
207
208 /** @inheritDoc */
209 hasBeenEdited: function(e) {
210 var engine = this.searchEngine_;
211 return this.nameField_.value != engine['name'] ||
212 this.keywordField_.value != engine['keyword'] ||
213 this.urlField_.value != engine['url'];
214 },
215
216 /**
217 * Called when entering edit mode; starts an edit session in the model.
218 * @param {Event} e The edit event.
219 * @private
220 */
221 onEditStarted_: function(e) {
222 var editIndex = this.searchEngine_['modelIndex'];
223 chrome.send('editSearchEngine', [String(editIndex)]);
224 },
225
226 /**
227 * Called when committing an edit; updates the model.
228 * @param {Event} e The end event.
229 * @private
230 */
231 onEditCommitted_: function(e) {
232 chrome.send('searchEngineEditCompleted', this.getInputFieldValues_());
233 // Update the static version immediately to prevent flickering before
234 // the model update callback updates the UI.
235 var editFields = [ this.nameField_, this.keywordField_, this.urlField_ ];
236 for (var i = 0; i < editFields.length; i++) {
237 var staticLabel = editFields[i].staticVersion;
238 if (staticLabel)
239 staticLabel.textContent = editFields[i].value;
240 }
241 },
242
243 /**
244 * Called when cancelling an edit; informs the model and resets the control
245 * states.
246 * @param {Event} e The cancel event.
247 * @private
248 */
249 onEditCancelled_: function() {
250 chrome.send('searchEngineEditCancelled');
251 var engine = this.searchEngine_;
252 this.nameField_.value = engine['name'];
253 this.keywordField_.value = engine['keyword'];
254 this.urlField_.value = engine['url'];
255
256 var editFields = [ this.nameField_, this.keywordField_, this.urlField_ ];
257 for (var i = 0; i < editFields.length; i++) {
258 editFields[i].classList.remove('invalid');
259 }
260 this.currentlyValid_ = !this.isPlaceholder_;
261 },
262
263 /**
264 * Returns the input field values as an array suitable for passing to
265 * chrome.send. The order of the array is important.
266 * @private
267 * @return {array} The current input field values.
268 */
269 getInputFieldValues_: function() {
270 return [ this.nameField_.value,
271 this.keywordField_.value,
272 this.urlField_.value ];
273 },
274
275 /**
276 * Begins the process of asynchronously validing the input fields.
277 * @private
278 */
279 startFieldValidation_: function() {
280 this.waitingForValidation_ = true;
281 var args = this.getInputFieldValues_();
282 args.push(this.searchEngine_['modelIndex']);
283 chrome.send('checkSearchEngineInfoValidity', args);
284 },
285
286 /**
287 * Callback for the completion of an input validition check.
288 * @param {Object} validity A dictionary of validitation results.
289 */
290 validationComplete: function(validity) {
291 this.waitingForValidation_ = false;
292 // TODO(stuartmorgan): Implement the full validation UI with
293 // checkmark/exclamation mark icons and tooltips.
294 if (validity['name'])
295 this.nameField_.classList.remove('invalid');
296 else
297 this.nameField_.classList.add('invalid');
298
299 if (validity['keyword'])
300 this.keywordField_.classList.remove('invalid');
301 else
302 this.keywordField_.classList.add('invalid');
303
304 if (validity['url'])
305 this.urlField_.classList.remove('invalid');
306 else
307 this.urlField_.classList.add('invalid');
308
309 this.currentlyValid_ = validity['name'] && validity['keyword'] &&
310 validity['url'];
67 }, 311 },
68 }; 312 };
69 313
70 var SearchEngineList = cr.ui.define('list'); 314 var SearchEngineList = cr.ui.define('list');
71 315
72 SearchEngineList.prototype = { 316 SearchEngineList.prototype = {
73 __proto__: DeletableItemList.prototype, 317 __proto__: InlineEditableItemList.prototype,
74 318
75 /** @inheritDoc */ 319 /** @inheritDoc */
76 createItem: function(searchEngine) { 320 createItem: function(searchEngine) {
77 return new SearchEngineListItem(searchEngine); 321 return new SearchEngineListItem(searchEngine);
78 }, 322 },
79 323
80 /** @inheritDoc */ 324 /** @inheritDoc */
81 createSelectionController: function(sm) { 325 createSelectionController: function(sm) {
82 return new ListInlineHeaderSelectionController(sm, this); 326 return new ListInlineHeaderSelectionController(sm, this);
83 }, 327 },
84 328
85 /** @inheritDoc */ 329 /** @inheritDoc */
86 deleteItemAtIndex: function(index) { 330 deleteItemAtIndex: function(index) {
87 var modelIndex = this.dataModel.item(index)['modelIndex'] 331 var modelIndex = this.dataModel.item(index)['modelIndex']
88 chrome.send('removeSearchEngine', [String(modelIndex)]); 332 chrome.send('removeSearchEngine', [String(modelIndex)]);
89 }, 333 },
90 334
91 /** 335 /**
92 * Returns true if the given item is selectable. 336 * Returns true if the given item is selectable.
93 * @param {number} index The index to check. 337 * @param {number} index The index to check.
94 */ 338 */
95 canSelectIndex: function(index) { 339 canSelectIndex: function(index) {
96 return !this.dataModel.item(index).hasOwnProperty('heading'); 340 return !this.dataModel.item(index).hasOwnProperty('heading');
97 }, 341 },
342
343 /**
344 * Passes the results of an input validation check to the requesting row
345 * if it's still being edited.
346 * @param {number} modelIndex The model index of the item that was checked.
347 * @param {Object} validity A dictionary of validitation results.
348 */
349 validationComplete: function(validity, modelIndex) {
350 // If it's not still being edited, it no longer matters.
351 var currentSelection = this.selectedItem;
352 var listItem = this.getListItem(currentSelection);
353 if (listItem.editing && currentSelection['modelIndex'] == modelIndex)
354 listItem.validationComplete(validity);
355 },
98 }; 356 };
99 357
100 // Export 358 // Export
101 return { 359 return {
102 SearchEngineList: SearchEngineList 360 SearchEngineList: SearchEngineList
103 }; 361 };
104 362
105 }); 363 });
106 364
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698