| OLD | NEW |
| (Empty) |
| 1 // Copyright (c) 2011 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. | |
| 4 | |
| 5 cr.define('options.search_engines', function() { | |
| 6 const InlineEditableItemList = options.InlineEditableItemList; | |
| 7 const InlineEditableItem = options.InlineEditableItem; | |
| 8 const ListSelectionController = cr.ui.ListSelectionController; | |
| 9 | |
| 10 /** | |
| 11 * Creates a new search engine list item. | |
| 12 * @param {Object} searchEnigne The search engine this represents. | |
| 13 * @constructor | |
| 14 * @extends {cr.ui.ListItem} | |
| 15 */ | |
| 16 function SearchEngineListItem(searchEngine) { | |
| 17 var el = cr.doc.createElement('div'); | |
| 18 el.searchEngine_ = searchEngine; | |
| 19 SearchEngineListItem.decorate(el); | |
| 20 return el; | |
| 21 } | |
| 22 | |
| 23 /** | |
| 24 * Decorates an element as a search engine list item. | |
| 25 * @param {!HTMLElement} el The element to decorate. | |
| 26 */ | |
| 27 SearchEngineListItem.decorate = function(el) { | |
| 28 el.__proto__ = SearchEngineListItem.prototype; | |
| 29 el.decorate(); | |
| 30 }; | |
| 31 | |
| 32 SearchEngineListItem.prototype = { | |
| 33 __proto__: InlineEditableItem.prototype, | |
| 34 | |
| 35 /** | |
| 36 * Input field for editing the engine name. | |
| 37 * @type {HTMLElement} | |
| 38 * @private | |
| 39 */ | |
| 40 nameField_: null, | |
| 41 | |
| 42 /** | |
| 43 * Input field for editing the engine keyword. | |
| 44 * @type {HTMLElement} | |
| 45 * @private | |
| 46 */ | |
| 47 keywordField_: null, | |
| 48 | |
| 49 /** | |
| 50 * Input field for editing the engine url. | |
| 51 * @type {HTMLElement} | |
| 52 * @private | |
| 53 */ | |
| 54 urlField_: null, | |
| 55 | |
| 56 /** | |
| 57 * Whether or not an input validation request is currently outstanding. | |
| 58 * @type {boolean} | |
| 59 * @private | |
| 60 */ | |
| 61 waitingForValidation_: false, | |
| 62 | |
| 63 /** | |
| 64 * Whether or not the current set of input is known to be valid. | |
| 65 * @type {boolean} | |
| 66 * @private | |
| 67 */ | |
| 68 currentlyValid_: false, | |
| 69 | |
| 70 /** @inheritDoc */ | |
| 71 decorate: function() { | |
| 72 InlineEditableItem.prototype.decorate.call(this); | |
| 73 | |
| 74 var engine = this.searchEngine_; | |
| 75 | |
| 76 if (engine['modelIndex'] == '-1') { | |
| 77 this.isPlaceholder = true; | |
| 78 engine['name'] = ''; | |
| 79 engine['keyword'] = ''; | |
| 80 engine['url'] = ''; | |
| 81 } | |
| 82 | |
| 83 this.currentlyValid_ = !this.isPlaceholder; | |
| 84 | |
| 85 if (engine['default']) | |
| 86 this.classList.add('default'); | |
| 87 | |
| 88 this.deletable = engine['canBeRemoved']; | |
| 89 | |
| 90 // Construct the name column. | |
| 91 var nameColEl = this.ownerDocument.createElement('div'); | |
| 92 nameColEl.className = 'name-column'; | |
| 93 nameColEl.classList.add('weakrtl'); | |
| 94 this.contentElement.appendChild(nameColEl); | |
| 95 | |
| 96 // Add the favicon. | |
| 97 var faviconDivEl = this.ownerDocument.createElement('div'); | |
| 98 faviconDivEl.className = 'favicon'; | |
| 99 var imgEl = this.ownerDocument.createElement('img'); | |
| 100 imgEl.src = 'chrome://favicon/iconurl/' + engine['iconURL']; | |
| 101 faviconDivEl.appendChild(imgEl); | |
| 102 nameColEl.appendChild(faviconDivEl); | |
| 103 | |
| 104 var nameEl = this.createEditableTextCell(engine['displayName']); | |
| 105 nameEl.classList.add('weakrtl'); | |
| 106 nameColEl.appendChild(nameEl); | |
| 107 | |
| 108 // Then the keyword column. | |
| 109 var keywordEl = this.createEditableTextCell(engine['keyword']); | |
| 110 keywordEl.className = 'keyword-column'; | |
| 111 keywordEl.classList.add('weakrtl'); | |
| 112 this.contentElement.appendChild(keywordEl); | |
| 113 | |
| 114 // And the URL column. | |
| 115 var urlEl = this.createEditableTextCell(engine['url']); | |
| 116 var urlWithButtonEl = this.ownerDocument.createElement('div'); | |
| 117 urlWithButtonEl.appendChild(urlEl); | |
| 118 urlWithButtonEl.className = 'url-column'; | |
| 119 urlWithButtonEl.classList.add('weakrtl'); | |
| 120 this.contentElement.appendChild(urlWithButtonEl); | |
| 121 // Add the Make Default button. Temporary until drag-and-drop re-ordering | |
| 122 // is implemented. When this is removed, remove the extra div above. | |
| 123 if (engine['canBeDefault']) { | |
| 124 var makeDefaultButtonEl = this.ownerDocument.createElement('button'); | |
| 125 makeDefaultButtonEl.className = 'raw-button custom-appearance'; | |
| 126 makeDefaultButtonEl.textContent = | |
| 127 templateData.makeDefaultSearchEngineButton; | |
| 128 makeDefaultButtonEl.onclick = function(e) { | |
| 129 chrome.send('managerSetDefaultSearchEngine', [engine['modelIndex']]); | |
| 130 }; | |
| 131 // Don't select the row when clicking the button. | |
| 132 makeDefaultButtonEl.onmousedown = function(e) { | |
| 133 e.stopPropagation(); | |
| 134 }; | |
| 135 urlWithButtonEl.appendChild(makeDefaultButtonEl); | |
| 136 } | |
| 137 | |
| 138 // Do final adjustment to the input fields. | |
| 139 this.nameField_ = nameEl.querySelector('input'); | |
| 140 // The editable field uses the raw name, not the display name. | |
| 141 this.nameField_.value = engine['name']; | |
| 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 // Listen for edit events. | |
| 163 if (engine['canBeEdited']) { | |
| 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 } else { | |
| 168 this.editable = false; | |
| 169 } | |
| 170 }, | |
| 171 | |
| 172 /** @inheritDoc */ | |
| 173 get currentInputIsValid() { | |
| 174 return !this.waitingForValidation_ && this.currentlyValid_; | |
| 175 }, | |
| 176 | |
| 177 /** @inheritDoc */ | |
| 178 get hasBeenEdited() { | |
| 179 var engine = this.searchEngine_; | |
| 180 return this.nameField_.value != engine['name'] || | |
| 181 this.keywordField_.value != engine['keyword'] || | |
| 182 this.urlField_.value != engine['url']; | |
| 183 }, | |
| 184 | |
| 185 /** | |
| 186 * Called when entering edit mode; starts an edit session in the model. | |
| 187 * @param {Event} e The edit event. | |
| 188 * @private | |
| 189 */ | |
| 190 onEditStarted_: function(e) { | |
| 191 var editIndex = this.searchEngine_['modelIndex']; | |
| 192 chrome.send('editSearchEngine', [String(editIndex)]); | |
| 193 this.startFieldValidation_(); | |
| 194 }, | |
| 195 | |
| 196 /** | |
| 197 * Called when committing an edit; updates the model. | |
| 198 * @param {Event} e The end event. | |
| 199 * @private | |
| 200 */ | |
| 201 onEditCommitted_: function(e) { | |
| 202 chrome.send('searchEngineEditCompleted', this.getInputFieldValues_()); | |
| 203 }, | |
| 204 | |
| 205 /** | |
| 206 * Called when cancelling an edit; informs the model and resets the control | |
| 207 * states. | |
| 208 * @param {Event} e The cancel event. | |
| 209 * @private | |
| 210 */ | |
| 211 onEditCancelled_: function() { | |
| 212 chrome.send('searchEngineEditCancelled'); | |
| 213 | |
| 214 // The name field has been automatically set to match the display name, | |
| 215 // but it should use the raw name instead. | |
| 216 this.nameField_.value = this.searchEngine_['name']; | |
| 217 this.currentlyValid_ = !this.isPlaceholder; | |
| 218 }, | |
| 219 | |
| 220 /** | |
| 221 * Returns the input field values as an array suitable for passing to | |
| 222 * chrome.send. The order of the array is important. | |
| 223 * @private | |
| 224 * @return {array} The current input field values. | |
| 225 */ | |
| 226 getInputFieldValues_: function() { | |
| 227 return [ this.nameField_.value, | |
| 228 this.keywordField_.value, | |
| 229 this.urlField_.value ]; | |
| 230 }, | |
| 231 | |
| 232 /** | |
| 233 * Begins the process of asynchronously validing the input fields. | |
| 234 * @private | |
| 235 */ | |
| 236 startFieldValidation_: function() { | |
| 237 this.waitingForValidation_ = true; | |
| 238 var args = this.getInputFieldValues_(); | |
| 239 args.push(this.searchEngine_['modelIndex']); | |
| 240 chrome.send('checkSearchEngineInfoValidity', args); | |
| 241 }, | |
| 242 | |
| 243 /** | |
| 244 * Callback for the completion of an input validition check. | |
| 245 * @param {Object} validity A dictionary of validitation results. | |
| 246 */ | |
| 247 validationComplete: function(validity) { | |
| 248 this.waitingForValidation_ = false; | |
| 249 // TODO(stuartmorgan): Implement the full validation UI with | |
| 250 // checkmark/exclamation mark icons and tooltips showing the errors. | |
| 251 if (validity['name']) { | |
| 252 this.nameField_.setCustomValidity(''); | |
| 253 } else { | |
| 254 this.nameField_.setCustomValidity( | |
| 255 templateData.editSearchEngineInvalidTitleToolTip); | |
| 256 } | |
| 257 | |
| 258 if (validity['keyword']) { | |
| 259 this.keywordField_.setCustomValidity(''); | |
| 260 } else { | |
| 261 this.keywordField_.setCustomValidity( | |
| 262 templateData.editSearchEngineInvalidKeywordToolTip); | |
| 263 } | |
| 264 | |
| 265 if (validity['url']) { | |
| 266 this.urlField_.setCustomValidity(''); | |
| 267 } else { | |
| 268 this.urlField_.setCustomValidity( | |
| 269 templateData.editSearchEngineInvalidURLToolTip); | |
| 270 } | |
| 271 | |
| 272 this.currentlyValid_ = validity['name'] && validity['keyword'] && | |
| 273 validity['url']; | |
| 274 }, | |
| 275 }; | |
| 276 | |
| 277 var SearchEngineList = cr.ui.define('list'); | |
| 278 | |
| 279 SearchEngineList.prototype = { | |
| 280 __proto__: InlineEditableItemList.prototype, | |
| 281 | |
| 282 /** @inheritDoc */ | |
| 283 createItem: function(searchEngine) { | |
| 284 return new SearchEngineListItem(searchEngine); | |
| 285 }, | |
| 286 | |
| 287 /** @inheritDoc */ | |
| 288 deleteItemAtIndex: function(index) { | |
| 289 var modelIndex = this.dataModel.item(index)['modelIndex'] | |
| 290 chrome.send('removeSearchEngine', [String(modelIndex)]); | |
| 291 }, | |
| 292 | |
| 293 /** | |
| 294 * Passes the results of an input validation check to the requesting row | |
| 295 * if it's still being edited. | |
| 296 * @param {number} modelIndex The model index of the item that was checked. | |
| 297 * @param {Object} validity A dictionary of validitation results. | |
| 298 */ | |
| 299 validationComplete: function(validity, modelIndex) { | |
| 300 // If it's not still being edited, it no longer matters. | |
| 301 var currentSelection = this.selectedItem; | |
| 302 if (!currentSelection) | |
| 303 return; | |
| 304 var listItem = this.getListItem(currentSelection); | |
| 305 if (listItem.editing && currentSelection['modelIndex'] == modelIndex) | |
| 306 listItem.validationComplete(validity); | |
| 307 }, | |
| 308 }; | |
| 309 | |
| 310 // Export | |
| 311 return { | |
| 312 SearchEngineList: SearchEngineList | |
| 313 }; | |
| 314 | |
| 315 }); | |
| 316 | |
| OLD | NEW |