search_engine_manager_engine_list.js revision 72a454cd3513ac24fbdd0e0cb9ad70b86a99b801
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 3// found in the LICENSE file. 4 5cr.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 this is a placeholder for adding an engine. 58 * @type {boolean} 59 * @private 60 */ 61 isPlaceholder_: false, 62 63 /** 64 * Whether or not an input validation request is currently outstanding. 65 * @type {boolean} 66 * @private 67 */ 68 waitingForValidation_: false, 69 70 /** 71 * Whether or not the current set of input is known to be valid. 72 * @type {boolean} 73 * @private 74 */ 75 currentlyValid_: false, 76 77 /** @inheritDoc */ 78 decorate: function() { 79 InlineEditableItem.prototype.decorate.call(this); 80 81 var engine = this.searchEngine_; 82 83 if (engine['modelIndex'] == '-1') { 84 this.isPlaceholder_ = true; 85 engine['name'] = ''; 86 engine['keyword'] = ''; 87 engine['url'] = ''; 88 } 89 90 this.currentlyValid_ = !this.isPlaceholder_; 91 92 if (engine['default']) 93 this.classList.add('default'); 94 95 this.deletable = engine['canBeRemoved']; 96 97 // Construct the name column. 98 var nameColEl = this.ownerDocument.createElement('div'); 99 nameColEl.className = 'name-column'; 100 this.contentElement.appendChild(nameColEl); 101 102 // Add the favicon. 103 var faviconDivEl = this.ownerDocument.createElement('div'); 104 faviconDivEl.className = 'favicon'; 105 var imgEl = this.ownerDocument.createElement('img'); 106 imgEl.src = 'chrome://favicon/iconurl/' + engine['iconURL']; 107 faviconDivEl.appendChild(imgEl); 108 nameColEl.appendChild(faviconDivEl); 109 110 var nameEl = this.createEditableTextCell(engine['name'], 111 this.isPlaceholder_); 112 nameColEl.appendChild(nameEl); 113 114 // Then the keyword column. 115 var keywordEl = this.createEditableTextCell(engine['keyword'], 116 this.isPlaceholder_); 117 keywordEl.className = 'keyword-column'; 118 this.contentElement.appendChild(keywordEl); 119 120 // And the URL column. 121 var urlEl = this.createEditableTextCell(engine['url'], 122 this.isPlaceholder_); 123 var urlWithButtonEl = this.ownerDocument.createElement('div'); 124 urlWithButtonEl.appendChild(urlEl); 125 urlWithButtonEl.className = 'url-column'; 126 this.contentElement.appendChild(urlWithButtonEl); 127 // Add the Make Default button. Temporary until drag-and-drop re-ordering 128 // is implemented. When this is removed, remove the extra div above. 129 if (engine['canBeDefault']) { 130 var makeDefaultButtonEl = this.ownerDocument.createElement('button'); 131 makeDefaultButtonEl.className = "raw-button"; 132 makeDefaultButtonEl.textContent = 133 templateData.makeDefaultSearchEngineButton; 134 makeDefaultButtonEl.onclick = function(e) { 135 chrome.send('managerSetDefaultSearchEngine', [engine['modelIndex']]); 136 }; 137 // Don't select the row when clicking the button. 138 makeDefaultButtonEl.onmousedown = function(e) { 139 e.stopPropagation(); 140 }; 141 urlWithButtonEl.appendChild(makeDefaultButtonEl); 142 } 143 144 // Do final adjustment to the input fields. 145 this.nameField_ = nameEl.querySelector('input'); 146 this.keywordField_ = keywordEl.querySelector('input'); 147 this.urlField_ = urlEl.querySelector('input'); 148 149 if (engine['urlLocked']) 150 this.urlField_.disabled = true; 151 152 if (this.isPlaceholder_) { 153 this.nameField_.placeholder = 154 localStrings.getString('searchEngineTableNamePlaceholder'); 155 this.keywordField_.placeholder = 156 localStrings.getString('searchEngineTableKeywordPlaceholder'); 157 this.urlField_.placeholder = 158 localStrings.getString('searchEngineTableURLPlaceholder'); 159 } 160 161 var fields = [ this.nameField_, this.keywordField_, this.urlField_ ]; 162 for (var i = 0; i < fields.length; i++) { 163 fields[i].oninput = this.startFieldValidation_.bind(this); 164 } 165 166 // Listen for edit events. 167 this.addEventListener('edit', this.onEditStarted_.bind(this)); 168 this.addEventListener('canceledit', this.onEditCancelled_.bind(this)); 169 this.addEventListener('commitedit', this.onEditCommitted_.bind(this)); 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 if (this.isPlaceholder_) { 215 var engine = this.searchEngine_; 216 this.nameField_.value = ''; 217 this.keywordField_.value = ''; 218 this.urlField_.value = ''; 219 } 220 this.currentlyValid_ = !this.isPlaceholder_; 221 }, 222 223 /** 224 * Returns the input field values as an array suitable for passing to 225 * chrome.send. The order of the array is important. 226 * @private 227 * @return {array} The current input field values. 228 */ 229 getInputFieldValues_: function() { 230 return [ this.nameField_.value, 231 this.keywordField_.value, 232 this.urlField_.value ]; 233 }, 234 235 /** 236 * Begins the process of asynchronously validing the input fields. 237 * @private 238 */ 239 startFieldValidation_: function() { 240 this.waitingForValidation_ = true; 241 var args = this.getInputFieldValues_(); 242 args.push(this.searchEngine_['modelIndex']); 243 chrome.send('checkSearchEngineInfoValidity', args); 244 }, 245 246 /** 247 * Callback for the completion of an input validition check. 248 * @param {Object} validity A dictionary of validitation results. 249 */ 250 validationComplete: function(validity) { 251 this.waitingForValidation_ = false; 252 // TODO(stuartmorgan): Implement the full validation UI with 253 // checkmark/exclamation mark icons and tooltips showing the errors. 254 if (validity['name']) { 255 this.nameField_.setCustomValidity(''); 256 } else { 257 this.nameField_.setCustomValidity( 258 templateData.editSearchEngineInvalidTitleToolTip); 259 } 260 261 if (validity['keyword']) { 262 this.keywordField_.setCustomValidity(''); 263 } else { 264 this.keywordField_.setCustomValidity( 265 templateData.editSearchEngineInvalidKeywordToolTip); 266 } 267 268 if (validity['url']) { 269 this.urlField_.setCustomValidity(''); 270 } else { 271 this.urlField_.setCustomValidity( 272 templateData.editSearchEngineInvalidURLToolTip); 273 } 274 275 this.currentlyValid_ = validity['name'] && validity['keyword'] && 276 validity['url']; 277 }, 278 }; 279 280 var SearchEngineList = cr.ui.define('list'); 281 282 SearchEngineList.prototype = { 283 __proto__: InlineEditableItemList.prototype, 284 285 /** @inheritDoc */ 286 createItem: function(searchEngine) { 287 return new SearchEngineListItem(searchEngine); 288 }, 289 290 /** @inheritDoc */ 291 deleteItemAtIndex: function(index) { 292 var modelIndex = this.dataModel.item(index)['modelIndex'] 293 chrome.send('removeSearchEngine', [String(modelIndex)]); 294 }, 295 296 /** 297 * Passes the results of an input validation check to the requesting row 298 * if it's still being edited. 299 * @param {number} modelIndex The model index of the item that was checked. 300 * @param {Object} validity A dictionary of validitation results. 301 */ 302 validationComplete: function(validity, modelIndex) { 303 // If it's not still being edited, it no longer matters. 304 var currentSelection = this.selectedItem; 305 if (!currentSelection) 306 return; 307 var listItem = this.getListItem(currentSelection); 308 if (listItem.editing && currentSelection['modelIndex'] == modelIndex) 309 listItem.validationComplete(validity); 310 }, 311 }; 312 313 // Export 314 return { 315 SearchEngineList: SearchEngineList 316 }; 317 318}); 319 320