Preserve the values of the hidden input fields when switching countries.
[chromium-blink-merge.git] / chrome / browser / resources / options / autofill_edit_address_overlay.js
blobd3e211324e0c88ddad491eece37fc27da00a1a9a
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 cr.define('options', function() {
6   /** @const */ var OptionsPage = options.OptionsPage;
7   /** @const */ var ArrayDataModel = cr.ui.ArrayDataModel;
9   /**
10    * AutofillEditAddressOverlay class
11    * Encapsulated handling of the 'Add Page' overlay page.
12    * @class
13    */
14   function AutofillEditAddressOverlay() {
15     OptionsPage.call(this, 'autofillEditAddress',
16                      loadTimeData.getString('autofillEditAddressTitle'),
17                      'autofill-edit-address-overlay');
18   }
20   cr.addSingletonGetter(AutofillEditAddressOverlay);
22   AutofillEditAddressOverlay.prototype = {
23     __proto__: OptionsPage.prototype,
25     /**
26      * The GUID of the loaded address.
27      * @type {string}
28      */
29     guid_: '',
31     /**
32      * The BCP 47 language code for the layout of input fields.
33      * @type {string}
34      */
35     languageCode_: '',
37     /**
38      * The saved field values for the address. For example, if the user changes
39      * from United States to Switzerland, then the State field will be hidden
40      * and its value will be stored here. If the user changes back to United
41      * States, then the State field will be restored to its previous value, as
42      * stored in this object.
43      * @type {Object}
44      */
45     savedFieldValues_: {},
47     /**
48      * Initializes the page.
49      */
50     initializePage: function() {
51       OptionsPage.prototype.initializePage.call(this);
53       this.createMultiValueLists_();
55       var self = this;
56       $('autofill-edit-address-cancel-button').onclick = function(event) {
57         self.dismissOverlay_();
58       };
60       // TODO(jhawkins): Investigate other possible solutions.
61       $('autofill-edit-address-apply-button').onclick = function(event) {
62         // Blur active element to ensure that pending changes are committed.
63         if (document.activeElement)
64           document.activeElement.blur();
65         // Blurring is delayed for list elements.  Queue save and close to
66         // ensure that pending changes have been applied.
67         setTimeout(function() {
68           self.pageDiv.querySelector('[field=phone]').doneValidating().then(
69               function() {
70                 self.saveAddress_();
71                 self.dismissOverlay_();
72               });
73         }, 0);
74       };
76       // Prevent 'blur' events on the OK and cancel buttons, which can trigger
77       // insertion of new placeholder elements.  The addition of placeholders
78       // affects layout, which interferes with being able to click on the
79       // buttons.
80       $('autofill-edit-address-apply-button').onmousedown = function(event) {
81         event.preventDefault();
82       };
83       $('autofill-edit-address-cancel-button').onmousedown = function(event) {
84         event.preventDefault();
85       };
87       this.guid_ = '';
88       this.populateCountryList_();
89       this.rebuildInputFields_(
90           loadTimeData.getValue('autofillDefaultCountryComponents'));
91       this.languageCode_ =
92           loadTimeData.getString('autofillDefaultCountryLanguageCode');
93       this.connectInputEvents_();
94       this.setInputFields_({});
95       this.getCountrySwitcher_().onchange = function(event) {
96         self.countryChanged_();
97       };
98     },
100     /**
101     * Specifically catch the situations in which the overlay is cancelled
102     * externally (e.g. by pressing <Esc>), so that the input fields and
103     * GUID can be properly cleared.
104     * @override
105     */
106     handleCancel: function() {
107       this.dismissOverlay_();
108     },
110     /**
111      * Creates, decorates and initializes the multi-value lists for phone and
112      * email.
113      * @private
114      */
115     createMultiValueLists_: function() {
116       var list = this.pageDiv.querySelector('[field=phone]');
117       options.autofillOptions.AutofillPhoneValuesList.decorate(list);
118       list.autoExpands = true;
120       list = this.pageDiv.querySelector('[field=email]');
121       options.autofillOptions.AutofillValuesList.decorate(list);
122       list.autoExpands = true;
123     },
125     /**
126      * Updates the data model for the |list| with the values from |entries|.
127      * @param {cr.ui.List} list The list to update.
128      * @param {Array} entries The list of items to be added to the list.
129      * @private
130      */
131     setMultiValueList_: function(list, entries) {
132       // Add special entry for adding new values.
133       var augmentedList = entries.slice();
134       augmentedList.push(null);
135       list.dataModel = new ArrayDataModel(augmentedList);
137       // Update the status of the 'OK' button.
138       this.inputFieldChanged_();
140       list.dataModel.addEventListener('splice',
141                                       this.inputFieldChanged_.bind(this));
142       list.dataModel.addEventListener('change',
143                                       this.inputFieldChanged_.bind(this));
144     },
146     /**
147      * Clears any uncommitted input, resets the stored GUID and dismisses the
148      * overlay.
149      * @private
150      */
151     dismissOverlay_: function() {
152       this.setInputFields_({});
153       this.inputFieldChanged_();
154       this.guid_ = '';
155       this.languageCode_ = '';
156       this.savedInputFields_ = {};
157       OptionsPage.closeOverlay();
158     },
160     /**
161      * @return {Element} The element used to switch countries.
162      * @private
163      */
164     getCountrySwitcher_: function() {
165       return this.pageDiv.querySelector('[field=country]');
166     },
168     /**
169      * Returns all list elements.
170      * @return {!NodeList} The list elements.
171      * @private
172      */
173     getLists_: function() {
174       return this.pageDiv.querySelectorAll('list[field]');
175     },
177     /**
178      * Returns all text input elements.
179      * @return {!NodeList} The text input elements.
180      * @private
181      */
182     getTextFields_: function() {
183       return this.pageDiv.querySelectorAll('textarea[field], input[field]');
184     },
186     /**
187      * Creates a map from type => value for all text fields.
188      * @return {Object} The mapping from field names to values.
189      * @private
190      */
191     getInputFields_: function() {
192       var address = {country: this.getCountrySwitcher_().value};
194       var lists = this.getLists_();
195       for (var i = 0; i < lists.length; i++) {
196         address[lists[i].getAttribute('field')] =
197             lists[i].dataModel.slice(0, lists[i].dataModel.length - 1);
198       }
200       var fields = this.getTextFields_();
201       for (var i = 0; i < fields.length; i++) {
202         address[fields[i].getAttribute('field')] = fields[i].value;
203       }
205       return address;
206     },
208     /**
209      * Sets the value of each input field according to |address|.
210      * @param {object} address The object with values to use.
211      * @private
212      */
213     setInputFields_: function(address) {
214       this.getCountrySwitcher_().value = address.country || '';
216       var lists = this.getLists_();
217       for (var i = 0; i < lists.length; i++) {
218         this.setMultiValueList_(
219             lists[i], address[lists[i].getAttribute('field')] || []);
220       }
222       var fields = this.getTextFields_();
223       for (var i = 0; i < fields.length; i++) {
224         fields[i].value = address[fields[i].getAttribute('field')] || '';
225       }
226     },
228     /**
229      * Aggregates the values in the input fields into an array and sends the
230      * array to the Autofill handler.
231      * @private
232      */
233     saveAddress_: function() {
234       var inputFields = this.getInputFields_();
235       var address = [
236         this.guid_,
237         inputFields.fullName || [],
238         inputFields.companyName || '',
239         inputFields.addrLines || '',
240         inputFields.dependentLocality || '',
241         inputFields.city || '',
242         inputFields.state || '',
243         inputFields.postalCode || '',
244         inputFields.sortingCode || '',
245         inputFields.country || '',
246         inputFields.phone || [],
247         inputFields.email || [],
248         this.languageCode_,
249       ];
250       chrome.send('setAddress', address);
251     },
253     /**
254      * Connects each input field to the inputFieldChanged_() method that enables
255      * or disables the 'Ok' button based on whether all the fields are empty or
256      * not.
257      * @private
258      */
259     connectInputEvents_: function() {
260       var fields = this.getTextFields_();
261       for (var i = 0; i < fields.length; i++) {
262         fields[i].oninput = this.inputFieldChanged_.bind(this);
263       }
264     },
266     /**
267      * Disables the 'Ok' button if all of the fields are empty.
268      * @private
269      */
270     inputFieldChanged_: function() {
271       var disabled = !this.getCountrySwitcher_().value;
272       if (disabled) {
273         // Length of lists are tested for > 1 due to the "add" placeholder item
274         // in the list.
275         var lists = this.getLists_();
276         for (var i = 0; i < lists.length; i++) {
277           if (lists[i].items.length > 1) {
278             disabled = false;
279             break;
280           }
281         }
282       }
284       if (disabled) {
285         var fields = this.getTextFields_();
286         for (var i = 0; i < fields.length; i++) {
287           if (fields[i].value) {
288             disabled = false;
289             break;
290           }
291         }
292       }
294       $('autofill-edit-address-apply-button').disabled = disabled;
295     },
297     /**
298      * Updates the address fields appropriately for the selected country.
299      * @private
300      */
301     countryChanged_: function() {
302       var countryCode = this.getCountrySwitcher_().value;
303       if (countryCode)
304         chrome.send('loadAddressEditorComponents', [countryCode]);
305       else
306         this.inputFieldChanged_();
307     },
309     /**
310      * Populates the country <select> list.
311      * @private
312      */
313     populateCountryList_: function() {
314       var countryList = loadTimeData.getValue('autofillCountrySelectList');
316       // Add the countries to the country <select> list.
317       var countrySelect = this.getCountrySwitcher_();
318       // Add an empty option.
319       countrySelect.appendChild(new Option('', ''));
320       for (var i = 0; i < countryList.length; i++) {
321         var option = new Option(countryList[i].name,
322                                 countryList[i].value);
323         option.disabled = countryList[i].value == 'separator';
324         countrySelect.appendChild(option);
325       }
326     },
328     /**
329      * Loads the address data from |address|, sets the input fields based on
330      * this data, and stores the GUID and language code of the address.
331      * @param {!Object} address Lots of info about an address from the browser.
332      * @private
333      */
334     loadAddress_: function(address) {
335       this.rebuildInputFields_(address.components);
336       this.setInputFields_(address);
337       this.inputFieldChanged_();
338       this.connectInputEvents_();
339       this.guid_ = address.guid;
340       this.languageCode_ = address.languageCode;
341     },
343     /**
344      * Takes a snapshot of the input values, clears the input values, loads the
345      * address input layout from |input.components|, restores the input values
346      * from snapshot, and stores the |input.languageCode| for the address.
347      * @param {{languageCode: string, components: Array.<Array.<Object>>}} input
348      *     Info about how to layout inputs fields in this dialog.
349      * @private
350      */
351     loadAddressComponents_: function(input) {
352       var inputFields = this.getInputFields_();
353       for (var fieldName in inputFields) {
354         if (inputFields.hasOwnProperty(fieldName))
355           this.savedFieldValues_[fieldName] = inputFields[fieldName];
356       }
357       this.rebuildInputFields_(input.components);
358       this.setInputFields_(this.savedFieldValues_);
359       this.inputFieldChanged_();
360       this.connectInputEvents_();
361       this.languageCode_ = input.languageCode;
362     },
364     /**
365      * Clears address inputs and rebuilds the input fields according to
366      * |components|.
367      * @param {Array.<Array.<Object>>} components A list of information about
368      *     each input field.
369      * @private
370      */
371     rebuildInputFields_: function(components) {
372       var content = $('autofill-edit-address-fields');
373       content.innerHTML = '';
375       var customContainerElements = {fullName: 'div'};
376       var customInputElements = {fullName: 'list', addrLines: 'textarea'};
378       for (var i in components) {
379         var row = document.createElement('div');
380         row.classList.add('input-group', 'settings-row');
381         content.appendChild(row);
383         for (var j in components[i]) {
384           if (components[i][j].field == 'country')
385             continue;
387           var fieldContainer = document.createElement(
388               customContainerElements[components[i][j].field] || 'label');
389           row.appendChild(fieldContainer);
391           var fieldName = document.createElement('div');
392           fieldName.textContent = components[i][j].name;
393           fieldContainer.appendChild(fieldName);
395           var input = document.createElement(
396               customInputElements[components[i][j].field] || 'input');
397           input.setAttribute('field', components[i][j].field);
398           input.classList.add(components[i][j].length);
399           input.setAttribute('placeholder', components[i][j].placeholder || '');
400           fieldContainer.appendChild(input);
402           if (input.tagName == 'LIST') {
403             options.autofillOptions.AutofillValuesList.decorate(input);
404             input.autoExpands = true;
405           }
406         }
407       }
408     },
409   };
411   AutofillEditAddressOverlay.loadAddress = function(address) {
412     AutofillEditAddressOverlay.getInstance().loadAddress_(address);
413   };
415   AutofillEditAddressOverlay.loadAddressComponents = function(input) {
416     AutofillEditAddressOverlay.getInstance().loadAddressComponents_(input);
417   };
419   AutofillEditAddressOverlay.setTitle = function(title) {
420     $('autofill-address-title').textContent = title;
421   };
423   AutofillEditAddressOverlay.setValidatedPhoneNumbers = function(numbers) {
424     var instance = AutofillEditAddressOverlay.getInstance();
425     var phoneList = instance.pageDiv.querySelector('[field=phone]');
426     instance.setMultiValueList_(phoneList, numbers);
427     phoneList.didReceiveValidationResult();
428   };
430   // Export
431   return {
432     AutofillEditAddressOverlay: AutofillEditAddressOverlay
433   };