Revert of Add button to add new FSP services to Files app. (patchset #8 id:140001...
[chromium-blink-merge.git] / chrome / browser / resources / options / autofill_options_list.js
blob004f62131cdb569ed1fb1dacf8fe0e3bc6dcd118
1 // Copyright (c) 2012 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.
5 /**
6  * @typedef {{
7  *   guid: string,
8  *   label: string,
9  *   sublabel: string,
10  *   isLocal: boolean,
11  *   isCached: boolean
12  * }}
13  * @see chrome/browser/ui/webui/options/autofill_options_handler.cc
14  */
15 var AutofillEntityMetadata;
17 cr.define('options.autofillOptions', function() {
18   /** @const */ var DeletableItem = options.DeletableItem;
19   /** @const */ var DeletableItemList = options.DeletableItemList;
20   /** @const */ var InlineEditableItem = options.InlineEditableItem;
21   /** @const */ var InlineEditableItemList = options.InlineEditableItemList;
23   /**
24    * @return {!HTMLButtonElement}
25    */
26   function AutofillEditProfileButton(edit) {
27     var editButtonEl = /** @type {HTMLButtonElement} */(
28         document.createElement('button'));
29     editButtonEl.className =
30         'list-inline-button hide-until-hover custom-appearance';
31     editButtonEl.textContent =
32         loadTimeData.getString('autofillEditProfileButton');
33     editButtonEl.onclick = edit;
35     editButtonEl.onmousedown = function(e) {
36       // Don't select the row when clicking the button.
37       e.stopPropagation();
38       // Don't focus on the button when clicking it.
39       e.preventDefault();
40     };
42     return editButtonEl;
43   }
45   /** @return {!Element} */
46   function CreateGoogleAccountLabel() {
47     var label = document.createElement('div');
48     label.className = 'deemphasized hides-on-hover';
49     label.textContent = loadTimeData.getString('autofillFromGoogleAccount');
50     return label;
51   }
53   /**
54    * Creates a new address list item.
55    * @constructor
56    * @param {AutofillEntityMetadata} metadata Details about an address profile.
57    * @extends {options.DeletableItem}
58    * @see chrome/browser/ui/webui/options/autofill_options_handler.cc
59    */
60   function AddressListItem(metadata) {
61     var el = cr.doc.createElement('div');
62     el.__proto__ = AddressListItem.prototype;
63     /** @private */
64     el.metadata_ = metadata;
65     el.decorate();
67     return el;
68   }
70   AddressListItem.prototype = {
71     __proto__: DeletableItem.prototype,
73     /** @override */
74     decorate: function() {
75       DeletableItem.prototype.decorate.call(this);
77       var label = this.ownerDocument.createElement('div');
78       label.className = 'autofill-list-item';
79       label.textContent = this.metadata_.label;
80       this.contentElement.appendChild(label);
82       var sublabel = this.ownerDocument.createElement('div');
83       sublabel.className = 'deemphasized';
84       sublabel.textContent = this.metadata_.sublabel;
85       this.contentElement.appendChild(sublabel);
87       if (!this.metadata_.isLocal) {
88         this.deletable = false;
89         this.contentElement.appendChild(CreateGoogleAccountLabel());
90       }
92       // The 'Edit' button.
93       var metadata = this.metadata_;
94       var editButtonEl = AutofillEditProfileButton(
95           AddressListItem.prototype.loadAddressEditor.bind(this));
96       this.contentElement.appendChild(editButtonEl);
97     },
99     /**
100      * For local Autofill data, this function causes the AutofillOptionsHandler
101      * to call showEditAddressOverlay(). For Wallet data, the user is
102      * redirected to the Wallet web interface.
103      */
104     loadAddressEditor: function() {
105       if (this.metadata_.isLocal)
106         chrome.send('loadAddressEditor', [this.metadata_.guid]);
107       else
108         window.open(loadTimeData.getString('manageWalletAddressesUrl'));
109     },
110   };
112   /**
113    * Creates a new credit card list item.
114    * @param {AutofillEntityMetadata} metadata Details about a credit card.
115    * @constructor
116    * @extends {options.DeletableItem}
117    */
118   function CreditCardListItem(metadata) {
119     var el = cr.doc.createElement('div');
120     el.__proto__ = CreditCardListItem.prototype;
121     /** @private */
122     el.metadata_ = metadata;
123     el.decorate();
125     return el;
126   }
128   CreditCardListItem.prototype = {
129     __proto__: DeletableItem.prototype,
131     /** @override */
132     decorate: function() {
133       DeletableItem.prototype.decorate.call(this);
135       var label = this.ownerDocument.createElement('div');
136       label.className = 'autofill-list-item';
137       label.textContent = this.metadata_.label;
138       this.contentElement.appendChild(label);
140       var sublabel = this.ownerDocument.createElement('div');
141       sublabel.className = 'deemphasized';
142       sublabel.textContent = this.metadata_.sublabel;
143       this.contentElement.appendChild(sublabel);
145       if (!this.metadata_.isLocal) {
146         this.deletable = false;
147         this.contentElement.appendChild(CreateGoogleAccountLabel());
148       }
150       var guid = this.metadata_.guid;
151       if (this.metadata_.isCached) {
152         var localCopyText = this.ownerDocument.createElement('span');
153         localCopyText.className = 'hide-until-hover deemphasized';
154         localCopyText.textContent =
155             loadTimeData.getString('autofillDescribeLocalCopy');
156         this.contentElement.appendChild(localCopyText);
158         var clearLocalCopyButton = AutofillEditProfileButton(
159             function() { chrome.send('clearLocalCardCopy', [guid]); });
160         clearLocalCopyButton.textContent =
161             loadTimeData.getString('autofillClearLocalCopyButton');
162         this.contentElement.appendChild(clearLocalCopyButton);
163       }
165       // The 'Edit' button.
166       var metadata = this.metadata_;
167       var editButtonEl = AutofillEditProfileButton(
168           CreditCardListItem.prototype.loadCreditCardEditor.bind(this));
169       this.contentElement.appendChild(editButtonEl);
170     },
172     /**
173      * For local Autofill data, this function causes the AutofillOptionsHandler
174      * to call showEditCreditCardOverlay(). For Wallet data, the user is
175      * redirected to the Wallet web interface.
176      */
177     loadCreditCardEditor: function() {
178       if (this.metadata_.isLocal)
179         chrome.send('loadCreditCardEditor', [this.metadata_.guid]);
180       else
181         window.open(loadTimeData.getString('manageWalletPaymentMethodsUrl'));
182     },
183   };
185   /**
186    * Creates a new value list item.
187    * @param {options.autofillOptions.AutofillValuesList} list The parent list of
188    *     this item.
189    * @param {string} entry A string value.
190    * @constructor
191    * @extends {options.InlineEditableItem}
192    */
193   function ValuesListItem(list, entry) {
194     var el = cr.doc.createElement('div');
195     el.list = list;
196     el.value = entry ? entry : '';
197     el.__proto__ = ValuesListItem.prototype;
198     el.decorate();
200     return el;
201   }
203   ValuesListItem.prototype = {
204     __proto__: InlineEditableItem.prototype,
206     /** @override */
207     decorate: function() {
208       InlineEditableItem.prototype.decorate.call(this);
210       // Note: This must be set prior to calling |createEditableTextCell|.
211       this.isPlaceholder = !this.value;
213       // The stored value.
214       var cell = this.createEditableTextCell(String(this.value));
215       this.contentElement.appendChild(cell);
216       this.input = cell.querySelector('input');
218       if (this.isPlaceholder) {
219         this.input.placeholder = this.list.getAttribute('placeholder');
220         this.deletable = false;
221       }
223       this.addEventListener('commitedit', this.onEditCommitted_);
224       this.closeButtonFocusAllowed = true;
225       this.setFocusableColumnIndex(this.input, 0);
226       this.setFocusableColumnIndex(this.closeButtonElement, 1);
227     },
229     /**
230      * @return {Array} This item's value.
231      * @protected
232      */
233     value_: function() {
234       return this.input.value;
235     },
237     /**
238      * @param {*} value The value to test.
239      * @return {boolean} True if the given value is non-empty.
240      * @protected
241      */
242     valueIsNonEmpty_: function(value) {
243       return !!value;
244     },
246     /**
247      * @return {boolean} True if value1 is logically equal to value2.
248      */
249     valuesAreEqual_: function(value1, value2) {
250       return value1 === value2;
251     },
253     /**
254      * Clears the item's value.
255      * @protected
256      */
257     clearValue_: function() {
258       this.input.value = '';
259     },
261     /**
262      * Called when committing an edit.
263      * If this is an "Add ..." item, committing a non-empty value adds that
264      * value to the end of the values list, but also leaves this "Add ..." item
265      * in place.
266      * @param {Event} e The end event.
267      * @private
268      */
269     onEditCommitted_: function(e) {
270       var value = this.value_();
271       var i = this.list.items.indexOf(this);
272       if (i < this.list.dataModel.length &&
273           this.valuesAreEqual_(value, this.list.dataModel.item(i))) {
274         return;
275       }
277       var entries = this.list.dataModel.slice();
278       if (this.valueIsNonEmpty_(value) &&
279           !entries.some(this.valuesAreEqual_.bind(this, value))) {
280         // Update with new value.
281         if (this.isPlaceholder) {
282           // It is important that updateIndex is done before validateAndSave.
283           // Otherwise we can not be sure about AddRow index.
284           this.list.ignoreChangeEvents(function() {
285             this.list.dataModel.updateIndex(i);
286           }.bind(this));
287           this.list.validateAndSave(i, 0, value);
288         } else {
289           this.list.validateAndSave(i, 1, value);
290         }
291       } else {
292         // Reject empty values and duplicates.
293         if (!this.isPlaceholder) {
294           this.list.ignoreChangeEvents(function() {
295             this.list.dataModel.splice(i, 1);
296           }.bind(this));
297           this.list.selectIndexWithoutFocusing(i);
298         } else {
299           this.clearValue_();
300         }
301       }
302     },
303   };
305   /**
306    * Creates a new name value list item.
307    * @param {options.autofillOptions.AutofillNameValuesList} list The parent
308    *     list of this item.
309    * @param {Array<string>} entry An array of [first, middle, last] names.
310    * @constructor
311    * @extends {options.autofillOptions.ValuesListItem}
312    */
313   function NameListItem(list, entry) {
314     var el = cr.doc.createElement('div');
315     el.list = list;
316     el.first = entry ? entry[0] : '';
317     el.middle = entry ? entry[1] : '';
318     el.last = entry ? entry[2] : '';
319     el.__proto__ = NameListItem.prototype;
320     el.decorate();
322     return el;
323   }
325   NameListItem.prototype = {
326     __proto__: ValuesListItem.prototype,
328     /** @override */
329     decorate: function() {
330       InlineEditableItem.prototype.decorate.call(this);
332       // Note: This must be set prior to calling |createEditableTextCell|.
333       this.isPlaceholder = !this.first && !this.middle && !this.last;
335       // The stored value.
336       // For the simulated static "input element" to display correctly, the
337       // value must not be empty.  We use a space to force the UI to render
338       // correctly when the value is logically empty.
339       var cell = this.createEditableTextCell(this.first);
340       this.contentElement.appendChild(cell);
341       this.firstNameInput = cell.querySelector('input');
343       cell = this.createEditableTextCell(this.middle);
344       this.contentElement.appendChild(cell);
345       this.middleNameInput = cell.querySelector('input');
347       cell = this.createEditableTextCell(this.last);
348       this.contentElement.appendChild(cell);
349       this.lastNameInput = cell.querySelector('input');
351       if (this.isPlaceholder) {
352         this.firstNameInput.placeholder =
353             loadTimeData.getString('autofillAddFirstNamePlaceholder');
354         this.middleNameInput.placeholder =
355             loadTimeData.getString('autofillAddMiddleNamePlaceholder');
356         this.lastNameInput.placeholder =
357             loadTimeData.getString('autofillAddLastNamePlaceholder');
358         this.deletable = false;
359       }
361       this.addEventListener('commitedit', this.onEditCommitted_);
362     },
364     /** @override */
365     value_: function() {
366       return [this.firstNameInput.value,
367               this.middleNameInput.value,
368               this.lastNameInput.value];
369     },
371     /** @override */
372     valueIsNonEmpty_: function(value) {
373       return value[0] || value[1] || value[2];
374     },
376     /** @override */
377     valuesAreEqual_: function(value1, value2) {
378       // First, check for null values.
379       if (!value1 || !value2)
380         return value1 == value2;
382       return value1[0] === value2[0] &&
383              value1[1] === value2[1] &&
384              value1[2] === value2[2];
385     },
387     /** @override */
388     clearValue_: function() {
389       this.firstNameInput.value = '';
390       this.middleNameInput.value = '';
391       this.lastNameInput.value = '';
392     },
393   };
395   /**
396    * Base class for shared implementation between address and credit card lists.
397    * @constructor
398    * @extends {options.DeletableItemList}
399    */
400   var AutofillProfileList = cr.ui.define('list');
402   AutofillProfileList.prototype = {
403     __proto__: DeletableItemList.prototype,
405     decorate: function() {
406       DeletableItemList.prototype.decorate.call(this);
408       this.addEventListener('blur', this.onBlur_);
409     },
411     /**
412      * When the list loses focus, unselect all items in the list.
413      * @private
414      */
415     onBlur_: function() {
416       this.selectionModel.unselectAll();
417     },
418   };
420   /**
421    * Create a new address list.
422    * @constructor
423    * @extends {options.autofillOptions.AutofillProfileList}
424    */
425   var AutofillAddressList = cr.ui.define('list');
427   AutofillAddressList.prototype = {
428     __proto__: AutofillProfileList.prototype,
430     decorate: function() {
431       AutofillProfileList.prototype.decorate.call(this);
432     },
434     /** @override */
435     activateItemAtIndex: function(index) {
436       this.getListItemByIndex(index).loadAddressEditor();
437     },
439     /**
440      * @override
441      * @param {AutofillEntityMetadata} metadata
442      */
443     createItem: function(metadata) {
444       return new AddressListItem(metadata);
445     },
447     /** @override */
448     deleteItemAtIndex: function(index) {
449       AutofillOptions.removeData(this.dataModel.item(index).guid,
450                                  'Options_AutofillAddressDeleted');
451     },
452   };
454   /**
455    * Create a new credit card list.
456    * @constructor
457    * @extends {options.DeletableItemList}
458    */
459   var AutofillCreditCardList = cr.ui.define('list');
461   AutofillCreditCardList.prototype = {
462     __proto__: AutofillProfileList.prototype,
464     decorate: function() {
465       AutofillProfileList.prototype.decorate.call(this);
466     },
468     /** @override */
469     activateItemAtIndex: function(index) {
470       this.getListItemByIndex(index).loadCreditCardEditor();
471     },
473     /**
474      * @override
475      * @param {AutofillEntityMetadata} metadata
476      */
477     createItem: function(metadata) {
478       return new CreditCardListItem(metadata);
479     },
481     /** @override */
482     deleteItemAtIndex: function(index) {
483       AutofillOptions.removeData(this.dataModel.item(index).guid,
484                                  'Options_AutofillCreditCardDeleted');
485     },
486   };
488   /**
489    * Create a new value list.
490    * @constructor
491    * @extends {options.InlineEditableItemList}
492    */
493   var AutofillValuesList = cr.ui.define('list');
495   AutofillValuesList.prototype = {
496     __proto__: InlineEditableItemList.prototype,
498     /**
499      * @override
500      * @param {string} entry
501      */
502     createItem: function(entry) {
503       return new ValuesListItem(this, entry);
504     },
506     /** @override */
507     deleteItemAtIndex: function(index) {
508       this.dataModel.splice(index, 1);
509     },
511     /** @override */
512     shouldFocusPlaceholderOnEditCommit: function() {
513       return false;
514     },
516     /**
517      * Called when a new list item should be validated; subclasses are
518      * responsible for implementing if validation is required.
519      * @param {number} index The index of the item that was inserted or changed.
520      * @param {number} remove The number items to remove.
521      * @param {string} value The value of the item to insert.
522      */
523     validateAndSave: function(index, remove, value) {
524       this.ignoreChangeEvents(function() {
525         this.dataModel.splice(index, remove, value);
526       }.bind(this));
527       this.selectIndexWithoutFocusing(index);
528     },
529   };
531   /**
532    * Create a new value list for phone number validation.
533    * @constructor
534    * @extends {options.autofillOptions.AutofillValuesList}
535    */
536   var AutofillNameValuesList = cr.ui.define('list');
538   AutofillNameValuesList.prototype = {
539     __proto__: AutofillValuesList.prototype,
541     /**
542      * @override
543      * @param {Array<string>} entry
544      */
545     createItem: function(entry) {
546       return new NameListItem(this, entry);
547     },
548   };
550   /**
551    * Create a new value list for phone number validation.
552    * @constructor
553    * @extends {options.autofillOptions.AutofillValuesList}
554    */
555   var AutofillPhoneValuesList = cr.ui.define('list');
557   AutofillPhoneValuesList.prototype = {
558     __proto__: AutofillValuesList.prototype,
560     /** @override */
561     validateAndSave: function(index, remove, value) {
562       var numbers = this.dataModel.slice(0, this.dataModel.length - 1);
563       numbers.splice(index, remove, value);
564       var info = new Array();
565       info[0] = index;
566       info[1] = numbers;
567       info[2] = document.querySelector(
568           '#autofill-edit-address-overlay [field=country]').value;
569       this.validationRequests_++;
570       chrome.send('validatePhoneNumbers', info);
571     },
573     /**
574      * The number of ongoing validation requests.
575      * @type {number}
576      * @private
577      */
578     validationRequests_: 0,
580     /**
581      * Pending Promise resolver functions.
582      * @type {Array<!Function>}
583      * @private
584      */
585     validationPromiseResolvers_: [],
587     /**
588      * This should be called when a reply of chrome.send('validatePhoneNumbers')
589      * is received.
590      */
591     didReceiveValidationResult: function() {
592       this.validationRequests_--;
593       assert(this.validationRequests_ >= 0);
594       if (this.validationRequests_ <= 0) {
595         while (this.validationPromiseResolvers_.length) {
596           this.validationPromiseResolvers_.pop()();
597         }
598       }
599     },
601     /**
602      * Returns a Promise which is fulfilled when all of validation requests are
603      * completed.
604      * @return {!Promise} A promise.
605      */
606     doneValidating: function() {
607       if (this.validationRequests_ <= 0)
608         return Promise.resolve();
609       return new Promise(function(resolve) {
610         this.validationPromiseResolvers_.push(resolve);
611       }.bind(this));
612     }
613   };
615   return {
616     AutofillProfileList: AutofillProfileList,
617     AddressListItem: AddressListItem,
618     CreditCardListItem: CreditCardListItem,
619     ValuesListItem: ValuesListItem,
620     NameListItem: NameListItem,
621     AutofillAddressList: AutofillAddressList,
622     AutofillCreditCardList: AutofillCreditCardList,
623     AutofillValuesList: AutofillValuesList,
624     AutofillNameValuesList: AutofillNameValuesList,
625     AutofillPhoneValuesList: AutofillPhoneValuesList,
626   };