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