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 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 this.contentElement.appendChild(nameColEl); 94 95 // Add the favicon. 96 var faviconDivEl = this.ownerDocument.createElement('div'); 97 faviconDivEl.className = 'favicon'; 98 var imgEl = this.ownerDocument.createElement('img'); 99 imgEl.src = 'chrome://favicon/iconurl/' + engine['iconURL']; 100 faviconDivEl.appendChild(imgEl); 101 nameColEl.appendChild(faviconDivEl); 102 103 var nameEl = this.createEditableTextCell(engine['displayName']); 104 nameColEl.appendChild(nameEl); 105 106 // Then the keyword column. 107 var keywordEl = this.createEditableTextCell(engine['keyword']); 108 keywordEl.className = 'keyword-column'; 109 this.contentElement.appendChild(keywordEl); 110 111 // And the URL column. 112 var urlEl = this.createEditableTextCell(engine['url']); 113 var urlWithButtonEl = this.ownerDocument.createElement('div'); 114 urlWithButtonEl.appendChild(urlEl); 115 urlWithButtonEl.className = 'url-column'; 116 this.contentElement.appendChild(urlWithButtonEl); 117 // Add the Make Default button. Temporary until drag-and-drop re-ordering 118 // is implemented. When this is removed, remove the extra div above. 119 if (engine['canBeDefault']) { 120 var makeDefaultButtonEl = this.ownerDocument.createElement('button'); 121 makeDefaultButtonEl.className = "raw-button"; 122 makeDefaultButtonEl.textContent = 123 templateData.makeDefaultSearchEngineButton; 124 makeDefaultButtonEl.onclick = function(e) { 125 chrome.send('managerSetDefaultSearchEngine', [engine['modelIndex']]); 126 }; 127 // Don't select the row when clicking the button. 128 makeDefaultButtonEl.onmousedown = function(e) { 129 e.stopPropagation(); 130 }; 131 urlWithButtonEl.appendChild(makeDefaultButtonEl); 132 } 133 134 // Do final adjustment to the input fields. 135 this.nameField_ = nameEl.querySelector('input'); 136 // The editable field uses the raw name, not the display name. 137 this.nameField_.value = engine['name']; 138 this.keywordField_ = keywordEl.querySelector('input'); 139 this.urlField_ = urlEl.querySelector('input'); 140 141 if (engine['urlLocked']) 142 this.urlField_.disabled = true; 143 144 if (this.isPlaceholder) { 145 this.nameField_.placeholder = 146 localStrings.getString('searchEngineTableNamePlaceholder'); 147 this.keywordField_.placeholder = 148 localStrings.getString('searchEngineTableKeywordPlaceholder'); 149 this.urlField_.placeholder = 150 localStrings.getString('searchEngineTableURLPlaceholder'); 151 } 152 153 var fields = [ this.nameField_, this.keywordField_, this.urlField_ ]; 154 for (var i = 0; i < fields.length; i++) { 155 fields[i].oninput = this.startFieldValidation_.bind(this); 156 } 157 158 // Listen for edit events. 159 this.addEventListener('edit', this.onEditStarted_.bind(this)); 160 this.addEventListener('canceledit', this.onEditCancelled_.bind(this)); 161 this.addEventListener('commitedit', this.onEditCommitted_.bind(this)); 162 }, 163 164 /** @inheritDoc */ 165 get currentInputIsValid() { 166 return !this.waitingForValidation_ && this.currentlyValid_; 167 }, 168 169 /** @inheritDoc */ 170 get hasBeenEdited() { 171 var engine = this.searchEngine_; 172 return this.nameField_.value != engine['name'] || 173 this.keywordField_.value != engine['keyword'] || 174 this.urlField_.value != engine['url']; 175 }, 176 177 /** 178 * Called when entering edit mode; starts an edit session in the model. 179 * @param {Event} e The edit event. 180 * @private 181 */ 182 onEditStarted_: function(e) { 183 var editIndex = this.searchEngine_['modelIndex']; 184 chrome.send('editSearchEngine', [String(editIndex)]); 185 this.startFieldValidation_(); 186 }, 187 188 /** 189 * Called when committing an edit; updates the model. 190 * @param {Event} e The end event. 191 * @private 192 */ 193 onEditCommitted_: function(e) { 194 chrome.send('searchEngineEditCompleted', this.getInputFieldValues_()); 195 }, 196 197 /** 198 * Called when cancelling an edit; informs the model and resets the control 199 * states. 200 * @param {Event} e The cancel event. 201 * @private 202 */ 203 onEditCancelled_: function() { 204 chrome.send('searchEngineEditCancelled'); 205 206 // The name field has been automatically set to match the display name, 207 // but it should use the raw name instead. 208 this.nameField_.value = this.searchEngine_['name']; 209 this.currentlyValid_ = !this.isPlaceholder; 210 }, 211 212 /** 213 * Returns the input field values as an array suitable for passing to 214 * chrome.send. The order of the array is important. 215 * @private 216 * @return {array} The current input field values. 217 */ 218 getInputFieldValues_: function() { 219 return [ this.nameField_.value, 220 this.keywordField_.value, 221 this.urlField_.value ]; 222 }, 223 224 /** 225 * Begins the process of asynchronously validing the input fields. 226 * @private 227 */ 228 startFieldValidation_: function() { 229 this.waitingForValidation_ = true; 230 var args = this.getInputFieldValues_(); 231 args.push(this.searchEngine_['modelIndex']); 232 chrome.send('checkSearchEngineInfoValidity', args); 233 }, 234 235 /** 236 * Callback for the completion of an input validition check. 237 * @param {Object} validity A dictionary of validitation results. 238 */ 239 validationComplete: function(validity) { 240 this.waitingForValidation_ = false; 241 // TODO(stuartmorgan): Implement the full validation UI with 242 // checkmark/exclamation mark icons and tooltips showing the errors. 243 if (validity['name']) { 244 this.nameField_.setCustomValidity(''); 245 } else { 246 this.nameField_.setCustomValidity( 247 templateData.editSearchEngineInvalidTitleToolTip); 248 } 249 250 if (validity['keyword']) { 251 this.keywordField_.setCustomValidity(''); 252 } else { 253 this.keywordField_.setCustomValidity( 254 templateData.editSearchEngineInvalidKeywordToolTip); 255 } 256 257 if (validity['url']) { 258 this.urlField_.setCustomValidity(''); 259 } else { 260 this.urlField_.setCustomValidity( 261 templateData.editSearchEngineInvalidURLToolTip); 262 } 263 264 this.currentlyValid_ = validity['name'] && validity['keyword'] && 265 validity['url']; 266 }, 267 }; 268 269 var SearchEngineList = cr.ui.define('list'); 270 271 SearchEngineList.prototype = { 272 __proto__: InlineEditableItemList.prototype, 273 274 /** @inheritDoc */ 275 createItem: function(searchEngine) { 276 return new SearchEngineListItem(searchEngine); 277 }, 278 279 /** @inheritDoc */ 280 deleteItemAtIndex: function(index) { 281 var modelIndex = this.dataModel.item(index)['modelIndex'] 282 chrome.send('removeSearchEngine', [String(modelIndex)]); 283 }, 284 285 /** 286 * Passes the results of an input validation check to the requesting row 287 * if it's still being edited. 288 * @param {number} modelIndex The model index of the item that was checked. 289 * @param {Object} validity A dictionary of validitation results. 290 */ 291 validationComplete: function(validity, modelIndex) { 292 // If it's not still being edited, it no longer matters. 293 var currentSelection = this.selectedItem; 294 if (!currentSelection) 295 return; 296 var listItem = this.getListItem(currentSelection); 297 if (listItem.editing && currentSelection['modelIndex'] == modelIndex) 298 listItem.validationComplete(validity); 299 }, 300 }; 301 302 // Export 303 return { 304 SearchEngineList: SearchEngineList 305 }; 306 307}); 308 309