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;
10 * AutofillEditAddressOverlay class
11 * Encapsulated handling of the 'Add Page' overlay page.
14 function AutofillEditAddressOverlay() {
15 OptionsPage.call(this, 'autofillEditAddress',
16 loadTimeData.getString('autofillEditAddressTitle'),
17 'autofill-edit-address-overlay');
20 cr.addSingletonGetter(AutofillEditAddressOverlay);
22 AutofillEditAddressOverlay.prototype = {
23 __proto__: OptionsPage.prototype,
26 * The GUID of the loaded address.
32 * The BCP 47 language code for the layout of input fields.
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.
45 savedFieldValues_: {},
48 * Initializes the page.
50 initializePage: function() {
51 OptionsPage.prototype.initializePage.call(this);
53 this.createMultiValueLists_();
56 $('autofill-edit-address-cancel-button').onclick = function(event) {
57 self.dismissOverlay_();
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(
71 self.dismissOverlay_();
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
80 $('autofill-edit-address-apply-button').onmousedown = function(event) {
81 event.preventDefault();
83 $('autofill-edit-address-cancel-button').onmousedown = function(event) {
84 event.preventDefault();
88 this.populateCountryList_();
89 this.rebuildInputFields_(
90 loadTimeData.getValue('autofillDefaultCountryComponents'));
92 loadTimeData.getString('autofillDefaultCountryLanguageCode');
93 this.connectInputEvents_();
94 this.setInputFields_({});
95 this.getCountrySwitcher_().onchange = function(event) {
96 self.countryChanged_();
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.
106 handleCancel: function() {
107 this.dismissOverlay_();
111 * Creates, decorates and initializes the multi-value lists for phone and
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;
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.
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));
147 * Clears any uncommitted input, resets the stored GUID and dismisses the
151 dismissOverlay_: function() {
152 this.setInputFields_({});
153 this.inputFieldChanged_();
155 this.languageCode_ = '';
156 this.savedInputFields_ = {};
157 OptionsPage.closeOverlay();
161 * @return {Element} The element used to switch countries.
164 getCountrySwitcher_: function() {
165 return this.pageDiv.querySelector('[field=country]');
169 * Returns all list elements.
170 * @return {!NodeList} The list elements.
173 getLists_: function() {
174 return this.pageDiv.querySelectorAll('list[field]');
178 * Returns all text input elements.
179 * @return {!NodeList} The text input elements.
182 getTextFields_: function() {
183 return this.pageDiv.querySelectorAll('textarea[field], input[field]');
187 * Creates a map from type => value for all text fields.
188 * @return {Object} The mapping from field names to values.
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);
200 var fields = this.getTextFields_();
201 for (var i = 0; i < fields.length; i++) {
202 address[fields[i].getAttribute('field')] = fields[i].value;
209 * Sets the value of each input field according to |address|.
210 * @param {object} address The object with values to use.
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')] || []);
222 var fields = this.getTextFields_();
223 for (var i = 0; i < fields.length; i++) {
224 fields[i].value = address[fields[i].getAttribute('field')] || '';
229 * Aggregates the values in the input fields into an array and sends the
230 * array to the Autofill handler.
233 saveAddress_: function() {
234 var inputFields = this.getInputFields_();
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 || [],
250 chrome.send('setAddress', address);
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
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);
267 * Disables the 'Ok' button if all of the fields are empty.
270 inputFieldChanged_: function() {
271 var disabled = !this.getCountrySwitcher_().value;
273 // Length of lists are tested for > 1 due to the "add" placeholder item
275 var lists = this.getLists_();
276 for (var i = 0; i < lists.length; i++) {
277 if (lists[i].items.length > 1) {
285 var fields = this.getTextFields_();
286 for (var i = 0; i < fields.length; i++) {
287 if (fields[i].value) {
294 $('autofill-edit-address-apply-button').disabled = disabled;
298 * Updates the address fields appropriately for the selected country.
301 countryChanged_: function() {
302 var countryCode = this.getCountrySwitcher_().value;
304 chrome.send('loadAddressEditorComponents', [countryCode]);
306 this.inputFieldChanged_();
310 * Populates the country <select> list.
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);
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.
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;
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.
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];
357 this.rebuildInputFields_(input.components);
358 this.setInputFields_(this.savedFieldValues_);
359 this.inputFieldChanged_();
360 this.connectInputEvents_();
361 this.languageCode_ = input.languageCode;
365 * Clears address inputs and rebuilds the input fields according to
367 * @param {Array.<Array.<Object>>} components A list of information about
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')
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;
411 AutofillEditAddressOverlay.loadAddress = function(address) {
412 AutofillEditAddressOverlay.getInstance().loadAddress_(address);
415 AutofillEditAddressOverlay.loadAddressComponents = function(input) {
416 AutofillEditAddressOverlay.getInstance().loadAddressComponents_(input);
419 AutofillEditAddressOverlay.setTitle = function(title) {
420 $('autofill-address-title').textContent = title;
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();
432 AutofillEditAddressOverlay: AutofillEditAddressOverlay