Extract code handling PrinterProviderAPI from PrintPreviewHandler
[chromium-blink-merge.git] / chrome / browser / resources / options / autofill_edit_address_overlay.js
blobb5fe9be00dc6ee5d5a520f875a79fd031c0cea31
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 Page = cr.ui.pageManager.Page;
7   /** @const */ var PageManager = cr.ui.pageManager.PageManager;
8   /** @const */ var ArrayDataModel = cr.ui.ArrayDataModel;
10   /**
11    * AutofillEditAddressOverlay class
12    * Encapsulated handling of the 'Add Page' overlay page.
13    * @constructor
14    * @extends {cr.ui.pageManager.Page}
15    */
16   function AutofillEditAddressOverlay() {
17     Page.call(this, 'autofillEditAddress',
18               loadTimeData.getString('autofillEditAddressTitle'),
19               'autofill-edit-address-overlay');
20   }
22   cr.addSingletonGetter(AutofillEditAddressOverlay);
24   AutofillEditAddressOverlay.prototype = {
25     __proto__: Page.prototype,
27     /**
28      * The GUID of the loaded address.
29      * @type {string}
30      */
31     guid_: '',
33     /**
34      * The BCP 47 language code for the layout of input fields.
35      * @type {string}
36      */
37     languageCode_: '',
39     /**
40      * The saved field values for the address. For example, if the user changes
41      * from United States to Switzerland, then the State field will be hidden
42      * and its value will be stored here. If the user changes back to United
43      * States, then the State field will be restored to its previous value, as
44      * stored in this object.
45      * @type {Object}
46      */
47     savedFieldValues_: {},
49     /** @override */
50     initializePage: function() {
51       Page.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       PageManager.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'] || loadTimeData.getString('defaultCountryCode'),
246         inputFields['phone'] || [],
247         inputFields['email'] || [],
248         this.languageCode_,
249       ];
250       chrome.send('setAddress', address);
252       // If the GUID is empty, this form is being used to add a new address,
253       // rather than edit an existing one.
254       if (!this.guid_.length) {
255         chrome.send('coreOptionsUserMetricsAction',
256                     ['Options_AutofillAddressAdded']);
257       }
258     },
260     /**
261      * Connects each input field to the inputFieldChanged_() method that enables
262      * or disables the 'Ok' button based on whether all the fields are empty or
263      * not.
264      * @private
265      */
266     connectInputEvents_: function() {
267       var fields = this.getTextFields_();
268       for (var i = 0; i < fields.length; i++) {
269         fields[i].oninput = this.inputFieldChanged_.bind(this);
270       }
271     },
273     /**
274      * Disables the 'Ok' button if all of the fields are empty.
275      * @private
276      */
277     inputFieldChanged_: function() {
278       var disabled = !this.getCountrySwitcher_().value;
279       if (disabled) {
280         // Length of lists are tested for > 1 due to the "add" placeholder item
281         // in the list.
282         var lists = this.getLists_();
283         for (var i = 0; i < lists.length; i++) {
284           if (lists[i].items.length > 1) {
285             disabled = false;
286             break;
287           }
288         }
289       }
291       if (disabled) {
292         var fields = this.getTextFields_();
293         for (var i = 0; i < fields.length; i++) {
294           if (fields[i].value) {
295             disabled = false;
296             break;
297           }
298         }
299       }
301       $('autofill-edit-address-apply-button').disabled = disabled;
302     },
304     /**
305      * Updates the address fields appropriately for the selected country.
306      * @private
307      */
308     countryChanged_: function() {
309       var countryCode = this.getCountrySwitcher_().value;
310       if (countryCode)
311         chrome.send('loadAddressEditorComponents', [countryCode]);
312       else
313         this.inputFieldChanged_();
314     },
316     /**
317      * Populates the country <select> list.
318      * @private
319      */
320     populateCountryList_: function() {
321       var countryList = loadTimeData.getValue('autofillCountrySelectList');
323       // Add the countries to the country <select> list.
324       var countrySelect = this.getCountrySwitcher_();
325       // Add an empty option.
326       countrySelect.appendChild(new Option('', ''));
327       for (var i = 0; i < countryList.length; i++) {
328         var option = new Option(countryList[i].name,
329                                 countryList[i].value);
330         option.disabled = countryList[i].value == 'separator';
331         countrySelect.appendChild(option);
332       }
333     },
335     /**
336      * Called to prepare the overlay when a new address is being added.
337      * @private
338      */
339     prepForNewAddress_: function() {
340       // Focus the first element.
341       this.pageDiv.querySelector('input').focus();
342     },
344     /**
345      * Loads the address data from |address|, sets the input fields based on
346      * this data, and stores the GUID and language code of the address.
347      * @param {!Object} address Lots of info about an address from the browser.
348      * @private
349      */
350     loadAddress_: function(address) {
351       this.rebuildInputFields_(address.components);
352       this.setInputFields_(address);
353       this.inputFieldChanged_();
354       this.connectInputEvents_();
355       this.guid_ = address.guid;
356       this.languageCode_ = address.languageCode;
357     },
359     /**
360      * Takes a snapshot of the input values, clears the input values, loads the
361      * address input layout from |input.components|, restores the input values
362      * from snapshot, and stores the |input.languageCode| for the address.
363      * @param {{languageCode: string, components: Array.<Array.<Object>>}} input
364      *     Info about how to layout inputs fields in this dialog.
365      * @private
366      */
367     loadAddressComponents_: function(input) {
368       var inputFields = this.getInputFields_();
369       for (var fieldName in inputFields) {
370         if (inputFields.hasOwnProperty(fieldName))
371           this.savedFieldValues_[fieldName] = inputFields[fieldName];
372       }
373       this.rebuildInputFields_(input.components);
374       this.setInputFields_(this.savedFieldValues_);
375       this.inputFieldChanged_();
376       this.connectInputEvents_();
377       this.languageCode_ = input.languageCode;
378     },
380     /**
381      * Clears address inputs and rebuilds the input fields according to
382      * |components|.
383      * @param {Array.<Array.<Object>>} components A list of information about
384      *     each input field.
385      * @private
386      */
387     rebuildInputFields_: function(components) {
388       var content = $('autofill-edit-address-fields');
389       content.innerHTML = '';
391       var customContainerElements = {fullName: 'div'};
392       var customInputElements = {fullName: 'list', addrLines: 'textarea'};
394       for (var i in components) {
395         var row = document.createElement('div');
396         row.classList.add('input-group', 'settings-row');
397         content.appendChild(row);
399         for (var j in components[i]) {
400           if (components[i][j].field == 'country')
401             continue;
403           var fieldContainer = document.createElement(
404               customContainerElements[components[i][j].field] || 'label');
405           row.appendChild(fieldContainer);
407           var fieldName = document.createElement('div');
408           fieldName.textContent = components[i][j].name;
409           fieldContainer.appendChild(fieldName);
411           var input = document.createElement(
412               customInputElements[components[i][j].field] || 'input');
413           input.setAttribute('field', components[i][j].field);
414           input.classList.add(components[i][j].length);
415           input.setAttribute('placeholder', components[i][j].placeholder || '');
416           fieldContainer.appendChild(input);
418           if (input.tagName == 'LIST') {
419             options.autofillOptions.AutofillValuesList.decorate(input);
420             input.autoExpands = true;
421           }
422         }
423       }
424     },
425   };
427   AutofillEditAddressOverlay.prepForNewAddress = function() {
428     AutofillEditAddressOverlay.getInstance().prepForNewAddress_();
429   };
431   AutofillEditAddressOverlay.loadAddress = function(address) {
432     AutofillEditAddressOverlay.getInstance().loadAddress_(address);
433   };
435   AutofillEditAddressOverlay.loadAddressComponents = function(input) {
436     AutofillEditAddressOverlay.getInstance().loadAddressComponents_(input);
437   };
439   AutofillEditAddressOverlay.setTitle = function(title) {
440     $('autofill-address-title').textContent = title;
441   };
443   AutofillEditAddressOverlay.setValidatedPhoneNumbers = function(numbers) {
444     var instance = AutofillEditAddressOverlay.getInstance();
445     var phoneList = instance.pageDiv.querySelector('[field=phone]');
446     instance.setMultiValueList_(assertInstanceof(phoneList, cr.ui.List),
447                                 numbers);
448     phoneList.didReceiveValidationResult();
449   };
451   // Export
452   return {
453     AutofillEditAddressOverlay: AutofillEditAddressOverlay
454   };