Home | History | Annotate | Download | only in options
      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 
      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       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