Chromium Code Reviews| OLD | NEW |
|---|---|
| 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 // Finally, 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 | |
| 164 /** | |
| 165 * Returns a div containing an <input>, as well as static text if needed. | |
| 166 * @param {string} text The text of the cell. | |
| 167 * @return {HTMLElement} The HTML element for the cell. | |
| 168 * @private | |
| 169 */ | |
| 170 createEditableTextCell_: function(text) { | |
| 171 var container = this.ownerDocument.createElement('div'); | |
| 172 | |
| 173 if (!this.isPlaceholder_) { | |
| 174 var textEl = this.ownerDocument.createElement('div'); | |
| 175 textEl.className = 'static-text'; | |
| 176 textEl.textContent = text; | |
| 177 textEl.setAttribute('editmode', false); | |
| 178 container.appendChild(textEl); | |
| 179 } | |
| 180 | |
| 181 var inputEl = this.ownerDocument.createElement('input'); | |
| 182 inputEl.type = 'text'; | |
| 183 inputEl.value = text; | |
| 184 if (!this.isPlaceholder_) { | |
| 185 inputEl.setAttribute('editmode', true); | |
| 186 inputEl.staticVersion = textEl; | |
| 187 } | |
| 188 container.appendChild(inputEl); | |
| 189 | |
| 190 return container; | |
| 191 }, | |
| 192 | |
| 193 /** @inheritDoc */ | |
| 194 initialFocusElement: function() { | |
| 195 return this.nameField_; | |
| 196 }, | |
| 197 | |
| 198 /** @inheritDoc */ | |
| 199 currentInputIsValid: function() { | |
| 200 return (!this.waitingForValidation_ && this.currentlyValid_) | |
|
Evan Stade
2011/01/11 21:06:01
no (), need trailing ;
stuartmorgan
2011/01/11 23:18:41
Done.
| |
| 201 }, | |
| 202 | |
| 203 /** @inheritDoc */ | |
| 204 hasBeenEdited: function() { | |
| 205 var engine = this.searchEngine_; | |
| 206 return (this.nameField_.value != engine['name'] || | |
|
Evan Stade
2011/01/11 21:06:01
ditto
stuartmorgan
2011/01/11 23:18:41
Done.
| |
| 207 this.keywordField_.value != engine['keyword'] || | |
| 208 this.urlField_.value != engine['url']) | |
| 209 }, | |
| 210 | |
| 211 /** @inheritDoc */ | |
| 212 onEditStarted: function() { | |
| 213 var editIndex = this.searchEngine_['modelIndex'] | |
|
Evan Stade
2011/01/11 21:06:01
;
stuartmorgan
2011/01/11 23:18:41
Done.
| |
| 214 chrome.send('editSearchEngine', [String(editIndex)]); | |
| 215 }, | |
| 216 | |
| 217 /** @inheritDoc */ | |
| 218 onEditCommitted: function() { | |
| 219 chrome.send('searchEngineEditCompleted', this.getInputFieldValues_()); | |
| 220 // Update the static version immediately to prevent flickering before | |
| 221 // the model update callback updates the UI. | |
| 222 var editFields = [ this.nameField_, this.keywordField_, this.urlField_ ]; | |
| 223 for (var i = 0; i < editFields.length; i++) { | |
| 224 var staticLabel = editFields[i].staticVersion; | |
| 225 if (staticLabel) | |
| 226 staticLabel.textContent = editFields[i].value; | |
| 227 } | |
| 228 }, | |
| 229 | |
| 230 /** @inheritDoc */ | |
| 231 onEditCancelled: function() { | |
| 232 chrome.send('searchEngineEditCancelled'); | |
| 233 var engine = this.searchEngine_; | |
| 234 this.nameField_.value = engine['name']; | |
| 235 this.keywordField_.value = engine['keyword']; | |
| 236 this.urlField_.value = engine['url']; | |
| 237 | |
| 238 var editFields = [ this.nameField_, this.keywordField_, this.urlField_ ]; | |
| 239 for (var i = 0; i < editFields.length; i++) { | |
| 240 editFields[i].classList.remove('invalid'); | |
| 241 } | |
| 242 this.currentlyValid_ = !this.isPlaceholder_; | |
| 243 }, | |
| 244 | |
| 245 /** | |
| 246 * Returns the input field values as an array suitable for passing to | |
| 247 * chrome.send. The order of the array is important. | |
| 248 * @private | |
| 249 * @return {array} The current input field values. | |
| 250 */ | |
| 251 getInputFieldValues_: function() { | |
| 252 return [ this.nameField_.value, | |
| 253 this.keywordField_.value, | |
| 254 this.urlField_.value ]; | |
| 255 }, | |
| 256 | |
| 257 /** | |
| 258 * Begins the process of asynchronously validing the input fields. | |
| 259 * @private | |
| 260 */ | |
| 261 startFieldValidation_: function() { | |
| 262 this.waitingForValidation_ = true; | |
| 263 var args = this.getInputFieldValues_(); | |
| 264 args.push(this.searchEngine_['modelIndex']); | |
| 265 chrome.send('checkSearchEngineInfoValidity', args); | |
| 266 }, | |
| 267 | |
| 268 /** | |
| 269 * Callback for the completion of an input validition check. | |
| 270 * @param {Object} validity A dictionary of validitation results. | |
| 271 */ | |
| 272 validationComplete: function(validity) { | |
| 273 this.waitingForValidation_ = false; | |
| 274 // TODO(stuartmorgan): Implement the full validation UI with | |
| 275 // checkmark/exclamation mark icons and tooltips. | |
| 276 if (validity['name']) | |
| 277 this.nameField_.classList.remove('invalid'); | |
| 278 else | |
| 279 this.nameField_.classList.add('invalid'); | |
|
Evan Stade
2011/01/11 21:06:01
nit: can you put a blank line after each else clau
stuartmorgan
2011/01/11 23:18:41
Done.
| |
| 280 if (validity['keyword']) | |
| 281 this.keywordField_.classList.remove('invalid'); | |
| 282 else | |
| 283 this.keywordField_.classList.add('invalid'); | |
| 284 if (validity['url']) | |
| 285 this.urlField_.classList.remove('invalid'); | |
| 286 else | |
| 287 this.urlField_.classList.add('invalid'); | |
| 288 | |
| 289 this.currentlyValid_ = validity['name'] && validity['keyword'] && | |
| 290 validity['url']; | |
| 67 }, | 291 }, |
| 68 }; | 292 }; |
| 69 | 293 |
| 70 var SearchEngineList = cr.ui.define('list'); | 294 var SearchEngineList = cr.ui.define('list'); |
| 71 | 295 |
| 72 SearchEngineList.prototype = { | 296 SearchEngineList.prototype = { |
| 73 __proto__: DeletableItemList.prototype, | 297 __proto__: InlineEditableItemList.prototype, |
| 74 | 298 |
| 75 /** @inheritDoc */ | 299 /** @inheritDoc */ |
| 76 createItem: function(searchEngine) { | 300 createItem: function(searchEngine) { |
| 77 return new SearchEngineListItem(searchEngine); | 301 return new SearchEngineListItem(searchEngine); |
| 78 }, | 302 }, |
| 79 | 303 |
| 80 /** @inheritDoc */ | 304 /** @inheritDoc */ |
| 81 createSelectionController: function(sm) { | 305 createSelectionController: function(sm) { |
| 82 return new ListInlineHeaderSelectionController(sm, this); | 306 return new ListInlineHeaderSelectionController(sm, this); |
| 83 }, | 307 }, |
| 84 | 308 |
| 85 /** @inheritDoc */ | 309 /** @inheritDoc */ |
| 86 deleteItemAtIndex: function(index) { | 310 deleteItemAtIndex: function(index) { |
| 87 var modelIndex = this.dataModel.item(index)['modelIndex'] | 311 var modelIndex = this.dataModel.item(index)['modelIndex'] |
| 88 chrome.send('removeSearchEngine', [String(modelIndex)]); | 312 chrome.send('removeSearchEngine', [String(modelIndex)]); |
| 89 }, | 313 }, |
| 90 | 314 |
| 91 /** | 315 /** |
| 92 * Returns true if the given item is selectable. | 316 * Returns true if the given item is selectable. |
| 93 * @param {number} index The index to check. | 317 * @param {number} index The index to check. |
| 94 */ | 318 */ |
| 95 canSelectIndex: function(index) { | 319 canSelectIndex: function(index) { |
| 96 return !this.dataModel.item(index).hasOwnProperty('heading'); | 320 return !this.dataModel.item(index).hasOwnProperty('heading'); |
| 97 }, | 321 }, |
| 322 | |
| 323 /** | |
| 324 * Passes the results of an input validation check to the requesting row | |
| 325 * if it's still being edited. | |
| 326 * @param {number} modelIndex The model index of the item that was checked. | |
| 327 * @param {Object} validity A dictionary of validitation results. | |
| 328 */ | |
| 329 validationComplete: function(validity, modelIndex) { | |
| 330 // If it's not still being edited, it no longer matters. | |
| 331 var currentSelection = this.selectedItem; | |
| 332 var listItem = this.getListItem(currentSelection); | |
| 333 if (listItem.editing && currentSelection['modelIndex'] == modelIndex) | |
| 334 listItem.validationComplete(validity); | |
| 335 }, | |
| 98 }; | 336 }; |
| 99 | 337 |
| 100 // Export | 338 // Export |
| 101 return { | 339 return { |
| 102 SearchEngineList: SearchEngineList | 340 SearchEngineList: SearchEngineList |
| 103 }; | 341 }; |
| 104 | 342 |
| 105 }); | 343 }); |
| 106 | 344 |
| OLD | NEW |