diff options
author | rouslan@chromium.org <rouslan@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2014-05-04 05:13:57 +0000 |
---|---|---|
committer | rouslan@chromium.org <rouslan@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2014-05-04 05:13:57 +0000 |
commit | f80cec86d2eac6ac9730615c093d369b120a678d (patch) | |
tree | 998a47d220296d2c1be9285892a0aa70bd347e45 | |
parent | 699232a42be203a95c97a7153f30f6da176ca1f5 (diff) | |
download | chromium_src-f80cec86d2eac6ac9730615c093d369b120a678d.zip chromium_src-f80cec86d2eac6ac9730615c093d369b120a678d.tar.gz chromium_src-f80cec86d2eac6ac9730615c093d369b120a678d.tar.bz2 |
i18n address editing in chrome://settings/autofillEditAddress.
BUG= 333387
Review URL: https://codereview.chromium.org/243013004
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@268095 0039d316-1c4b-4281-b951-d872f2087c98
9 files changed, 545 insertions, 338 deletions
diff --git a/chrome/app/generated_resources.grd b/chrome/app/generated_resources.grd index 31b906b..aa0c82a 100644 --- a/chrome/app/generated_resources.grd +++ b/chrome/app/generated_resources.grd @@ -10039,41 +10039,14 @@ Would you like to start <ph name="CONTROL_PANEL_APPLET_NAME">$1<ex>Add/Remove Pr Add a credit card </message> - <message name="IDS_AUTOFILL_FIELD_LABEL_FIRST_NAME" desc="The label of the first name entry."> - First name - </message> - <message name="IDS_AUTOFILL_FIELD_LABEL_MIDDLE_NAME" desc="The label of the middle name entry."> - Middle name(s) - </message> - <message name="IDS_AUTOFILL_FIELD_LABEL_LAST_NAME" desc="The label of the last name entry."> - Last name - </message> <message name="IDS_AUTOFILL_FIELD_LABEL_EMAIL" desc="The label of the Email entry."> Email </message> - <message name="IDS_AUTOFILL_FIELD_LABEL_COMPANY_NAME" desc="The label of the Company name entry."> - Company name - </message> - <message name="IDS_AUTOFILL_FIELD_LABEL_ADDRESS_LINE_1" desc="The label of the Address Line 1 entry."> - Address line 1 - </message> - <message name="IDS_AUTOFILL_FIELD_LABEL_ADDRESS_LINE_2" desc="The label of the Address Line 2 entry."> - Address line 2 - </message> - <message name="IDS_AUTOFILL_FIELD_LABEL_CITY" desc="The label of the City entry."> - City/Town - </message> <message name="IDS_AUTOFILL_FIELD_LABEL_PHONE" desc="The label of the Phone entry."> Phone </message> - <message name="IDS_AUTOFILL_FIELD_LABEL_ADD_FIRST_NAME" desc="The placeholder text of the first name field."> - Add first name - </message> - <message name="IDS_AUTOFILL_FIELD_LABEL_ADD_MIDDLE_NAME" desc="The placeholder text of the middle name field."> - Add middle name(s) - </message> - <message name="IDS_AUTOFILL_FIELD_LABEL_ADD_LAST_NAME" desc="The placeholder text of the last name field."> - Add last name + <message name="IDS_AUTOFILL_FIELD_LABEL_ADD_NAME" desc="The placeholder text of the recipient or contact name field."> + Add name </message> <message name="IDS_AUTOFILL_FIELD_LABEL_ADD_PHONE" desc="The placeholder text of the phone field."> Add phone number diff --git a/chrome/browser/resources/options/autofill_edit_address_overlay.html b/chrome/browser/resources/options/autofill_edit_address_overlay.html index a54bc85..3b7ef00 100644 --- a/chrome/browser/resources/options/autofill_edit_address_overlay.html +++ b/chrome/browser/resources/options/autofill_edit_address_overlay.html @@ -2,66 +2,26 @@ <div class="close-button"></div> <h1 id="autofill-address-title"></h1> <div class="content-area"> - <div> - <div id="autofill-name-labels"> - <span i18n-content="autofillFirstNameLabel"></span> - <span i18n-content="autofillMiddleNameLabel"></span> - <span i18n-content="autofillLastNameLabel"></span> - </div> - </div> - <div> - <list id="full-name-list"></list> - </div> - - <label class="settings-row"> - <div i18n-content="autofillCompanyNameLabel"></div> - <input id="company-name" type="text"> - </label> - - <label class="settings-row"> - <div i18n-content="autofillAddrLine1Label"></div> - <input id="addr-line-1" type="text"> - </label> - - <label class="settings-row"> - <div i18n-content="autofillAddrLine2Label"></div> - <input id="addr-line-2" type="text"> - </label> - - <div class="input-group settings-row"> - <label> - <div i18n-content="autofillCityLabel"></div> - <input id="city" type="text"> - </label> - - <label> - <div id="state-label"></div> - <input id="state" type="text"> - </label> - - <label> - <div id="postal-code-label"></div> - <input id="postal-code" type="text"> - </label> + <div id="autofill-edit-address-fields"> </div> <div class="settings-row"> <label> <div i18n-content="autofillCountryLabel"></div> - <select id="country"></select> + <select class="country" field="country"></select> </label> </div> <div class="input-group settings-row"> <div> <div i18n-content="autofillPhoneLabel"></div> - <list id="phone-list" + <list class="short" field="phone" i18n-values="placeholder:autofillAddPhonePlaceholder"></list> </div> <div> <div i18n-content="autofillEmailLabel"></div> - <list id="email-list" + <list class="short" field="email" i18n-values="placeholder:autofillAddEmailPlaceholder"></list> </div> </div> diff --git a/chrome/browser/resources/options/autofill_edit_address_overlay.js b/chrome/browser/resources/options/autofill_edit_address_overlay.js index 1f1bf5b..c5e9f25 100644 --- a/chrome/browser/resources/options/autofill_edit_address_overlay.js +++ b/chrome/browser/resources/options/autofill_edit_address_overlay.js @@ -9,6 +9,9 @@ cr.define('options', function() { // The GUID of the loaded address. var guid; + // The BCP 47 language code for the layout of input fields. + var languageCode; + /** * AutofillEditAddressOverlay class * Encapsulated handling of the 'Add Page' overlay page. @@ -46,10 +49,11 @@ cr.define('options', function() { // Blurring is delayed for list elements. Queue save and close to // ensure that pending changes have been applied. setTimeout(function() { - $('phone-list').doneValidating().then(function() { - self.saveAddress_(); - self.dismissOverlay_(); - }); + self.pageDiv.querySelector('[field=phone]').doneValidating().then( + function() { + self.saveAddress_(); + self.dismissOverlay_(); + }); }, 0); }; @@ -64,10 +68,17 @@ cr.define('options', function() { event.preventDefault(); }; - self.guid = ''; - self.populateCountryList_(); - self.clearInputFields_(); - self.connectInputEvents_(); + this.guid = ''; + this.populateCountryList_(); + this.rebuildInputFields_( + loadTimeData.getValue('autofillDefaultCountryComponents')); + this.languageCode = + loadTimeData.getString('autofillDefaultCountryLanguageCode'); + this.connectInputEvents_(); + this.setInputFields_({}); + this.getCountrySelector_().onchange = function(event) { + self.countryChanged_(); + }; }, /** @@ -81,34 +92,27 @@ cr.define('options', function() { }, /** - * Creates, decorates and initializes the multi-value lists for full name, - * phone, and email. + * Creates, decorates and initializes the multi-value lists for phone and + * email. * @private */ createMultiValueLists_: function() { - var list = $('full-name-list'); - options.autofillOptions.AutofillNameValuesList.decorate(list); - list.autoExpands = true; - - list = $('phone-list'); + var list = this.pageDiv.querySelector('[field=phone]'); options.autofillOptions.AutofillPhoneValuesList.decorate(list); list.autoExpands = true; - list = $('email-list'); + list = this.pageDiv.querySelector('[field=email]'); options.autofillOptions.AutofillValuesList.decorate(list); list.autoExpands = true; }, /** - * Updates the data model for the list named |listName| with the values from - * |entries|. - * @param {string} listName The id of the list. + * Updates the data model for the |list| with the values from |entries|. + * @param {element} list The list to update. * @param {Array} entries The list of items to be added to the list. + * @private */ - setMultiValueList_: function(listName, entries) { - // Add data entries. - var list = $(listName); - + setMultiValueList_: function(list, entries) { // Add special entry for adding new values. var augmentedList = entries.slice(); augmentedList.push(null); @@ -129,32 +133,106 @@ cr.define('options', function() { * @private */ dismissOverlay_: function() { - this.clearInputFields_(); + this.setInputFields_({}); + this.inputFieldChanged_(); this.guid = ''; + this.languageCode = ''; OptionsPage.closeOverlay(); }, /** + * Returns the country selector element. + * @return {element} The country selector. + * @private + */ + getCountrySelector_: function() { + return this.pageDiv.querySelector('[field=country]'); + }, + + /** + * Returns all list elements. + * @return {NodeList} The list elements. + * @private + */ + getLists_: function() { + return this.pageDiv.querySelectorAll('list[field]'); + }, + + /** + * Returns all text input elements. + * @return {NodeList} The text input elements. + * @private + */ + getTextFields_: function() { + return this.pageDiv.querySelectorAll( + ':-webkit-any(textarea, input)[field]'); + }, + + /** + * Aggregates the values in the input fields into an object. + * @return {object} The mapping from field names to values. + * @private + */ + getInputFields_: function() { + var address = {}; + address['country'] = this.getCountrySelector_().value; + + var lists = this.getLists_(); + for (var i = 0; i < lists.length; i++) { + address[lists[i].getAttribute('field')] = + lists[i].dataModel.slice(0, lists[i].dataModel.length - 1); + } + + var fields = this.getTextFields_(); + for (var i = 0; i < fields.length; i++) { + address[fields[i].getAttribute('field')] = fields[i].value; + } + + return address; + }, + + /** + * Sets the value of each input field according to |address|. + * @param {object} address The object with values to use. + * @private + */ + setInputFields_: function(address) { + this.getCountrySelector_().value = address['country'] || ''; + + var lists = this.getLists_(); + for (var i = 0; i < lists.length; i++) { + this.setMultiValueList_( + lists[i], address[lists[i].getAttribute('field')] || []); + } + + var fields = this.getTextFields_(); + for (var i = 0; i < fields.length; i++) { + fields[i].value = address[fields[i].getAttribute('field')] || ''; + } + }, + + /** * Aggregates the values in the input fields into an array and sends the * array to the Autofill handler. * @private */ saveAddress_: function() { + var inputFields = this.getInputFields_(); var address = new Array(); - address[0] = this.guid; - var list = $('full-name-list'); - address[1] = list.dataModel.slice(0, list.dataModel.length - 1); - address[2] = $('company-name').value; - address[3] = $('addr-line-1').value; - address[4] = $('addr-line-2').value; - address[5] = $('city').value; - address[6] = $('state').value; - address[7] = $('postal-code').value; - address[8] = $('country').value; - list = $('phone-list'); - address[9] = list.dataModel.slice(0, list.dataModel.length - 1); - list = $('email-list'); - address[10] = list.dataModel.slice(0, list.dataModel.length - 1); + var argCounter = 0; + address[argCounter++] = this.guid; + address[argCounter++] = inputFields['fullName'] || []; + address[argCounter++] = inputFields['companyName'] || ''; + address[argCounter++] = inputFields['addrLines'] || ''; + address[argCounter++] = inputFields['dependentLocality'] || ''; + address[argCounter++] = inputFields['city'] || ''; + address[argCounter++] = inputFields['state'] || ''; + address[argCounter++] = inputFields['postalCode'] || ''; + address[argCounter++] = inputFields['sortingCode'] || ''; + address[argCounter++] = inputFields['country'] || ''; + address[argCounter++] = inputFields['phone'] || []; + address[argCounter++] = inputFields['email'] || []; + address[argCounter++] = this.languageCode; chrome.send('setAddress', address); }, @@ -167,51 +245,56 @@ cr.define('options', function() { */ connectInputEvents_: function() { var self = this; - $('company-name').oninput = $('addr-line-1').oninput = - $('addr-line-2').oninput = $('city').oninput = $('state').oninput = - $('postal-code').oninput = function(event) { - self.inputFieldChanged_(); - }; - - $('country').onchange = function(event) { - self.countryChanged_(); - }; + var fields = this.getTextFields_(); + for (var i = 0; i < fields.length; i++) { + fields[i].oninput = function(event) { self.inputFieldChanged_(); }; + } }, /** - * Checks the values of each of the input fields and disables the 'Ok' - * button if all of the fields are empty. + * Disables the 'Ok' button if all of the fields are empty. * @private */ inputFieldChanged_: function() { - // Length of lists are tested for <= 1 due to the "add" placeholder item - // in the list. - var disabled = - $('full-name-list').items.length <= 1 && - !$('company-name').value && - !$('addr-line-1').value && !$('addr-line-2').value && - !$('city').value && !$('state').value && !$('postal-code').value && - !$('country').value && $('phone-list').items.length <= 1 && - $('email-list').items.length <= 1; + var disabled = true; + if (this.getCountrySelector_().value) + disabled = false; + + if (disabled) { + // Length of lists are tested for > 1 due to the "add" placeholder item + // in the list. + var lists = this.getLists_(); + for (var i = 0; i < lists.length; i++) { + if (lists[i].items.length > 1) { + disabled = false; + break; + } + } + } + + if (disabled) { + var fields = this.getTextFields_(); + for (var i = 0; i < fields.length; i++) { + if (fields[i].value) { + disabled = false; + break; + } + } + } + $('autofill-edit-address-apply-button').disabled = disabled; }, /** - * Updates the postal code and state field labels appropriately for the - * selected country. + * Updates the address fields appropriately for the selected country. * @private */ countryChanged_: function() { - var countryCode = $('country').value || - loadTimeData.getString('defaultCountryCode'); - - var details = loadTimeData.getValue('autofillCountryData')[countryCode]; - var postal = $('postal-code-label'); - postal.textContent = details.postalCodeLabel; - $('state-label').textContent = details.stateLabel; - - // Also update the 'Ok' button as needed. - this.inputFieldChanged_(); + var countryCode = this.getCountrySelector_().value; + if (countryCode) + chrome.send('loadAddressEditorComponents', [countryCode]); + else + this.inputFieldChanged_(); }, /** @@ -222,7 +305,7 @@ cr.define('options', function() { var countryList = loadTimeData.getValue('autofillCountrySelectList'); // Add the countries to the country <select> list. - var countrySelect = $('country'); + var countrySelect = this.getCountrySelector_(); // Add an empty option. countrySelect.appendChild(new Option('', '')); for (var i = 0; i < countryList.length; i++) { @@ -234,52 +317,78 @@ cr.define('options', function() { }, /** - * Clears the value of each input field. + * Loads the address data from |address|, sets the input fields based on + * this data, and stores the GUID and language code of the address. * @private */ - clearInputFields_: function() { - this.setMultiValueList_('full-name-list', []); - $('company-name').value = ''; - $('addr-line-1').value = ''; - $('addr-line-2').value = ''; - $('city').value = ''; - $('state').value = ''; - $('postal-code').value = ''; - $('country').value = ''; - this.setMultiValueList_('phone-list', []); - this.setMultiValueList_('email-list', []); - - this.countryChanged_(); + loadAddress_: function(address) { + this.rebuildInputFields_(address.components); + this.setInputFields_(address); + this.inputFieldChanged_(); + this.connectInputEvents_(); + this.guid = address.guid; + this.languageCode = address.languageCode; }, /** - * Loads the address data from |address|, sets the input fields based on - * this data and stores the GUID of the address. + * Takes a snapshot of the input values, clears the input values, loads the + * address input layout from |input.components|, restores the input values + * from snapshot, and stores the |input.languageCode| for the address. * @private */ - loadAddress_: function(address) { + loadAddressComponents_: function(input) { + var address = this.getInputFields_(); + this.rebuildInputFields_(input.components); this.setInputFields_(address); this.inputFieldChanged_(); - this.guid = address.guid; + this.connectInputEvents_(); + this.languageCode = input.languageCode; }, /** - * Sets the value of each input field according to |address| + * Clears address inputs and rebuilds the input fields according to + * |components|. * @private */ - setInputFields_: function(address) { - this.setMultiValueList_('full-name-list', address.fullName); - $('company-name').value = address.companyName; - $('addr-line-1').value = address.addrLine1; - $('addr-line-2').value = address.addrLine2; - $('city').value = address.city; - $('state').value = address.state; - $('postal-code').value = address.postalCode; - $('country').value = address.country; - this.setMultiValueList_('phone-list', address.phone); - this.setMultiValueList_('email-list', address.email); - - this.countryChanged_(); + rebuildInputFields_: function(components) { + var content = $('autofill-edit-address-fields'); + while (content.firstChild) { + content.removeChild(content.firstChild); + } + + var customContainerElements = {'fullName': 'div'}; + var customInputElements = {'fullName': 'list', 'addrLines': 'textarea'}; + + for (var i in components) { + var row = document.createElement('div'); + row.classList.add('input-group', 'settings-row'); + content.appendChild(row); + + for (var j in components[i]) { + if (components[i][j].field == 'country') + continue; + + var fieldContainer = document.createElement( + customContainerElements[components[i][j].field] || 'label'); + row.appendChild(fieldContainer); + + var fieldName = document.createElement('div'); + fieldName.textContent = components[i][j].name; + fieldContainer.appendChild(fieldName); + + var input = document.createElement( + customInputElements[components[i][j].field] || 'input'); + input.setAttribute('field', components[i][j].field); + input.classList.add(components[i][j].length); + input.setAttribute('placeholder', components[i][j].placeholder || ''); + fieldContainer.appendChild(input); + + if (input.tagName == 'LIST') { + options.autofillOptions.AutofillValuesList.decorate(input); + input.autoExpands = true; + } + } + } }, }; @@ -287,14 +396,19 @@ cr.define('options', function() { AutofillEditAddressOverlay.getInstance().loadAddress_(address); }; + AutofillEditAddressOverlay.loadAddressComponents = function(input) { + AutofillEditAddressOverlay.getInstance().loadAddressComponents_(input); + }; + AutofillEditAddressOverlay.setTitle = function(title) { $('autofill-address-title').textContent = title; }; AutofillEditAddressOverlay.setValidatedPhoneNumbers = function(numbers) { - AutofillEditAddressOverlay.getInstance().setMultiValueList_('phone-list', - numbers); - $('phone-list').didReceiveValidationResult(); + var instance = AutofillEditAddressOverlay.getInstance(); + var phoneList = instance.pageDiv.querySelector('[field=phone]'); + instance.setMultiValueList_(phoneList, numbers); + phoneList.didReceiveValidationResult(); }; // Export diff --git a/chrome/browser/resources/options/autofill_edit_overlay.css b/chrome/browser/resources/options/autofill_edit_overlay.css index 166f0cd..5a302fc 100644 --- a/chrome/browser/resources/options/autofill_edit_overlay.css +++ b/chrome/browser/resources/options/autofill_edit_overlay.css @@ -10,14 +10,21 @@ min-width: 500px; } -#full-name-list input, -#company-name, -#addr-line-1, -#addr-line-2 { +#autofill-edit-address-overlay .long div[role='listitem'] > div > div, +#autofill-edit-address-overlay .long input, +#autofill-edit-address-overlay textarea.long, +#autofill-edit-address-overlay input.long { width: 16em; } -#country { +#autofill-edit-address-overlay .short div[role='listitem'] > div > div, +#autofill-edit-address-overlay .short input, +#autofill-edit-address-overlay textarea.short, +#autofill-edit-address-overlay input.short { + width: 14em; +} + +#autofill-edit-address-overlay .country { max-width: 450px; } @@ -40,38 +47,10 @@ } :-webkit-any(#autofill-edit-credit-card-overlay, #autofill-edit-address-overlay) - .settings-row div + :-webkit-any(input, select) { + .settings-row div + :-webkit-any(input, select, textarea) { margin-top: 4px; } -#autofill-name-labels { - display: -webkit-inline-box; -} - -#autofill-name-labels span { - -webkit-box-flex: 1; - display: block; -} - -#full-name-list { - display: inline-block; -} - -#full-name-list div[role='listitem'] > div { - display: -webkit-box; -} - -#full-name-list div[role='listitem'] > div > div, -#autofill-name-labels span { - -webkit-margin-end: 5px; - width: 16em; -} - -:-webkit-any(#phone-list, #email-list) div[role='listitem'] > div > div, -:-webkit-any(#phone-list, #email-list) input { - width: 14em; -} - .input-group > * { -webkit-box-orient: vertical; -webkit-margin-end: 2px; diff --git a/chrome/browser/resources/options/autofill_options_list.js b/chrome/browser/resources/options/autofill_options_list.js index 540bf56..8fd7fdb 100644 --- a/chrome/browser/resources/options/autofill_options_list.js +++ b/chrome/browser/resources/options/autofill_options_list.js @@ -489,7 +489,8 @@ cr.define('options.autofillOptions', function() { var info = new Array(); info[0] = index; info[1] = numbers; - info[2] = $('country').value; + info[2] = document.querySelector( + '#autofill-edit-address-overlay [field=country]').value; this.validationRequests_++; chrome.send('validatePhoneNumbers', info); }, diff --git a/chrome/browser/ui/webui/DEPS b/chrome/browser/ui/webui/DEPS index 7d9e166..f3b4c54 100644 --- a/chrome/browser/ui/webui/DEPS +++ b/chrome/browser/ui/webui/DEPS @@ -11,6 +11,7 @@ include_rules = [ "+device/nfc", "+third_party/angle", # For ANGLE version. "+third_party/zlib/zlib.h", # For compression level constants. + "+third_party/libaddressinput/chromium/cpp/include", # For i18n address input. # DOM Distiller. "+components/dom_distiller/core", diff --git a/chrome/browser/ui/webui/options/autofill_options_browsertest.js b/chrome/browser/ui/webui/options/autofill_options_browsertest.js index 690f292..4cbd76d 100644 --- a/chrome/browser/ui/webui/options/autofill_options_browsertest.js +++ b/chrome/browser/ui/webui/options/autofill_options_browsertest.js @@ -3,6 +3,26 @@ // found in the LICENSE file. /** + * Returns the HTML element for the |field|. + * @param {string} field The field name for the element. + * @return {HTMLElement} The HTML element. + */ +function getField(field) { + return document.querySelector( + '#autofill-edit-address-overlay [field=' + field + ']'); +} + +/** + * Returns the size of the |list|. + * @param {HTMLElement} list The list to check. + * @return {int} The size of the list. + */ +function getListSize(list) { + // Remove 1 for placeholder input field. + return list.items.length - 1; +} + +/** * TestFixture for autofill options WebUI testing. * @extends {testing.Test} * @constructor @@ -47,7 +67,7 @@ TEST_F('AutofillEditAddressWebUITest', function() { assertEquals(this.browsePreload, document.location.href); - var phoneList = $('phone-list'); + var phoneList = getField('phone'); expectEquals(0, phoneList.validationRequests_); phoneList.doneValidating().then(function() { phoneList.focus(); @@ -61,3 +81,99 @@ TEST_F('AutofillEditAddressWebUITest', }); }); }); + +TEST_F('AutofillEditAddressWebUITest', + 'testInitialFormLayout', + function() { + assertEquals(this.browsePreload, document.location.href); + + assertEquals(getField('country').value, ''); + assertEquals(0, getListSize(getField('phone'))); + assertEquals(0, getListSize(getField('email'))); + assertEquals(0, getListSize(getField('fullName'))); + assertEquals('', getField('city').value); + + testDone(); +}); + +TEST_F('AutofillEditAddressWebUITest', + 'testLoadAddress', + function() { + assertEquals(this.browsePreload, document.location.href); + + var testAddress = { + guid: 'GUID Value', + fullName: ['Full Name 1', 'Full Name 2'], + companyName: 'Company Name Value', + addrLines: 'First Line Value\nSecond Line Value', + dependentLocality: 'Dependent Locality Value', + city: 'City Value', + state: 'State Value', + postalCode: 'Postal Code Value', + sortingCode: 'Sorting Code Value', + country: 'CH', + phone: ['123', '456'], + email: ['a@b.c', 'x@y.z'], + languageCode: 'de', + components: [[ + {field: 'postalCode', length: 'short'}, + {field: 'sortingCode', length: 'short'}, + {field: 'dependentLocality', length: 'short'}, + {field: 'city', length: 'short'}, + {field: 'state', length: 'short'}, + {field: 'addrLines', length: 'long'}, + {field: 'companyName', length: 'long'}, + {field: 'country', length: 'long'}, + {field: 'fullName', length: 'long', placeholder: 'Add name'} + ]] + }; + AutofillEditAddressOverlay.loadAddress(testAddress); + + assertEquals(testAddress.guid, AutofillEditAddressOverlay.getInstance().guid); + assertEquals(testAddress.languageCode, + AutofillEditAddressOverlay.getInstance().languageCode); + + var lists = ['fullName', 'email', 'phone']; + for (var i in lists) { + var field = getField(lists[i]); + assertEquals(testAddress[lists[i]].length, getListSize(field)); + assertTrue(field.getAttribute('placeholder').length > 0); + assertTrue(field instanceof cr.ui.List); + } + + var inputs = ['companyName', 'dependentLocality', 'city', 'state', + 'postalCode', 'sortingCode']; + for (var i in inputs) { + var field = getField(inputs[i]); + assertEquals(testAddress[inputs[i]], field.value); + assertTrue(field instanceof HTMLInputElement); + } + + var addrLines = getField('addrLines'); + assertEquals(testAddress.addrLines, addrLines.value); + assertTrue(addrLines instanceof HTMLTextAreaElement); + + var country = getField('country'); + assertEquals(testAddress.country, country.value); + assertTrue(country instanceof HTMLSelectElement); + + testDone(); +}); + +TEST_F('AutofillEditAddressWebUITest', + 'testLoadAddressComponents', + function() { + assertEquals(this.browsePreload, document.location.href); + + var testInput = { + languageCode: 'fr', + components: [[{field: 'city'}], + [{field: 'state'}]] + }; + AutofillEditAddressOverlay.loadAddressComponents(testInput); + + assertEquals('fr', AutofillEditAddressOverlay.getInstance().languageCode); + expectEquals(2, $('autofill-edit-address-fields').children.length); + + testDone(); +}); diff --git a/chrome/browser/ui/webui/options/autofill_options_handler.cc b/chrome/browser/ui/webui/options/autofill_options_handler.cc index 60b5aeb..93b2351 100644 --- a/chrome/browser/ui/webui/options/autofill_options_handler.cc +++ b/chrome/browser/ui/webui/options/autofill_options_handler.cc @@ -28,6 +28,9 @@ #include "content/public/browser/web_ui.h" #include "grit/component_strings.h" #include "grit/generated_resources.h" +#include "grit/libaddressinput_strings.h" +#include "third_party/libaddressinput/chromium/cpp/include/libaddressinput/address_ui.h" +#include "third_party/libaddressinput/chromium/cpp/include/libaddressinput/address_ui_component.h" #include "ui/base/l10n/l10n_util.h" #include "ui/base/webui/web_ui_util.h" @@ -36,11 +39,107 @@ using autofill::ServerFieldType; using autofill::AutofillProfile; using autofill::CreditCard; using autofill::PersonalDataManager; +using i18n::addressinput::AddressUiComponent; namespace { const char kSettingsOrigin[] = "Chrome settings"; +static const char kFullNameField[] = "fullName"; +static const char kCompanyNameField[] = "companyName"; +static const char kAddressLineField[] = "addrLines"; +static const char kDependentLocalityField[] = "dependentLocality"; +static const char kCityField[] = "city"; +static const char kStateField[] = "state"; +static const char kPostalCodeField[] = "postalCode"; +static const char kSortingCodeField[] = "sortingCode"; +static const char kCountryField[] = "country"; + +static const char kComponents[] = "components"; +static const char kLanguageCode[] = "languageCode"; + +// Fills |components| with the address UI components that should be used to +// input an address for |country_code| when UI BCP 47 language code is +// |ui_language_code|. If |components_language_code| is not NULL, then sets it +// to the BCP 47 language code that should be used to format the address for +// display. +void GetAddressComponents(const std::string& country_code, + const std::string& ui_language_code, + base::ListValue* address_components, + std::string* components_language_code) { + DCHECK(address_components); + + std::vector<AddressUiComponent> components = + i18n::addressinput::BuildComponents( + country_code, ui_language_code, components_language_code); + if (components.empty()) { + static const char kDefaultCountryCode[] = "US"; + components = i18n::addressinput::BuildComponents( + kDefaultCountryCode, ui_language_code, components_language_code); + } + DCHECK(!components.empty()); + + base::ListValue* line = NULL; + static const char kField[] = "field"; + static const char kLength[] = "length"; + for (size_t i = 0; i < components.size(); ++i) { + if (i == 0 || + components[i - 1].length_hint == AddressUiComponent::HINT_LONG || + components[i].length_hint == AddressUiComponent::HINT_LONG) { + line = new base::ListValue; + address_components->Append(line); + } + + scoped_ptr<base::DictionaryValue> component(new base::DictionaryValue); + component->SetString( + "name", l10n_util::GetStringUTF16(components[i].name_id)); + + switch (components[i].field) { + case i18n::addressinput::COUNTRY: + component->SetString(kField, kCountryField); + break; + case i18n::addressinput::ADMIN_AREA: + component->SetString(kField, kStateField); + break; + case i18n::addressinput::LOCALITY: + component->SetString(kField, kCityField); + break; + case i18n::addressinput::DEPENDENT_LOCALITY: + component->SetString(kField, kDependentLocalityField); + break; + case i18n::addressinput::SORTING_CODE: + component->SetString(kField, kSortingCodeField); + break; + case i18n::addressinput::POSTAL_CODE: + component->SetString(kField, kPostalCodeField); + break; + case i18n::addressinput::STREET_ADDRESS: + component->SetString(kField, kAddressLineField); + break; + case i18n::addressinput::ORGANIZATION: + component->SetString(kField, kCompanyNameField); + break; + case i18n::addressinput::RECIPIENT: + component->SetString(kField, kFullNameField); + component->SetString( + "placeholder", + l10n_util::GetStringUTF16(IDS_AUTOFILL_FIELD_LABEL_ADD_NAME)); + break; + } + + switch (components[i].length_hint) { + case AddressUiComponent::HINT_LONG: + component->SetString(kLength, "long"); + break; + case AddressUiComponent::HINT_SHORT: + component->SetString(kLength, "short"); + break; + } + + line->Append(component.release()); + } +} + // Sets data related to the country <select>. void SetCountryData(const PersonalDataManager& manager, base::DictionaryValue* localized_strings) { @@ -52,8 +151,6 @@ void SetCountryData(const PersonalDataManager& manager, // An ordered list of options to show in the <select>. scoped_ptr<base::ListValue> country_list(new base::ListValue()); - // A dictionary of postal code and state info, keyed on country code. - scoped_ptr<base::DictionaryValue> country_data(new base::DictionaryValue()); for (size_t i = 0; i < countries.size(); ++i) { scoped_ptr<base::DictionaryValue> option_details( new base::DictionaryValue()); @@ -62,18 +159,19 @@ void SetCountryData(const PersonalDataManager& manager, "value", countries[i] ? countries[i]->country_code() : "separator"); country_list->Append(option_details.release()); - - if (!countries[i]) - continue; - - scoped_ptr<base::DictionaryValue> details(new base::DictionaryValue()); - details->SetString("postalCodeLabel", countries[i]->postal_code_label()); - details->SetString("stateLabel", countries[i]->state_label()); - country_data->Set(countries[i]->country_code(), details.release()); - } localized_strings->Set("autofillCountrySelectList", country_list.release()); - localized_strings->Set("autofillCountryData", country_data.release()); + + scoped_ptr<base::ListValue> defaultCountryComponents(new base::ListValue); + std::string defaultCountryLanguageCode; + GetAddressComponents(countries.front()->country_code(), + g_browser_process->GetApplicationLocale(), + defaultCountryComponents.get(), + &defaultCountryLanguageCode); + localized_strings->Set("autofillDefaultCountryComponents", + defaultCountryComponents.release()); + localized_strings->SetString("autofillDefaultCountryLanguageCode", + defaultCountryLanguageCode); } // Get the multi-valued element for |type| and return it in |ListValue| form. @@ -107,68 +205,6 @@ void SetValueList(const base::ListValue* list, profile->SetRawMultiInfo(type, values); } -// Get the multi-valued element for |type| and return it in |ListValue| form. -void GetNameList(const AutofillProfile& profile, - scoped_ptr<base::ListValue>* names) { - names->reset(new base::ListValue); - - std::vector<base::string16> first_names; - std::vector<base::string16> middle_names; - std::vector<base::string16> last_names; - profile.GetRawMultiInfo(autofill::NAME_FIRST, &first_names); - profile.GetRawMultiInfo(autofill::NAME_MIDDLE, &middle_names); - profile.GetRawMultiInfo(autofill::NAME_LAST, &last_names); - DCHECK_EQ(first_names.size(), middle_names.size()); - DCHECK_EQ(first_names.size(), last_names.size()); - - // |GetRawMultiInfo()| always returns at least one, potentially empty, item. - if (first_names.size() == 1 && first_names.front().empty() && - middle_names.front().empty() && last_names.front().empty()) { - return; - } - - for (size_t i = 0; i < first_names.size(); ++i) { - base::ListValue* name = new base::ListValue; // owned by |list| - name->Set(0, new base::StringValue(first_names[i])); - name->Set(1, new base::StringValue(middle_names[i])); - name->Set(2, new base::StringValue(last_names[i])); - (*names)->Set(i, name); - } -} - -// Set the multi-valued element for |type| from input |list| values. -void SetNameList(const base::ListValue* names, AutofillProfile* profile) { - const size_t size = names->GetSize(); - std::vector<base::string16> first_names(size); - std::vector<base::string16> middle_names(size); - std::vector<base::string16> last_names(size); - - for (size_t i = 0; i < size; ++i) { - const base::ListValue* name; - bool success = names->GetList(i, &name); - DCHECK(success); - - base::string16 first_name; - success = name->GetString(0, &first_name); - DCHECK(success); - first_names[i] = first_name; - - base::string16 middle_name; - success = name->GetString(1, &middle_name); - DCHECK(success); - middle_names[i] = middle_name; - - base::string16 last_name; - success = name->GetString(2, &last_name); - DCHECK(success); - last_names[i] = last_name; - } - - profile->SetRawMultiInfo(autofill::NAME_FIRST, first_names); - profile->SetRawMultiInfo(autofill::NAME_MIDDLE, middle_names); - profile->SetRawMultiInfo(autofill::NAME_LAST, last_names); -} - // Pulls the phone number |index|, |phone_number_list|, and |country_code| from // the |args| input. void ExtractPhoneNumberInformation(const base::ListValue* args, @@ -306,6 +342,10 @@ void AutofillOptionsHandler::RegisterMessages() { base::Bind(&AutofillOptionsHandler::LoadAddressEditor, base::Unretained(this))); web_ui()->RegisterMessageCallback( + "loadAddressEditorComponents", + base::Bind(&AutofillOptionsHandler::LoadAddressEditorComponents, + base::Unretained(this))); + web_ui()->RegisterMessageCallback( "loadCreditCardEditor", base::Bind(&AutofillOptionsHandler::LoadCreditCardEditor, base::Unretained(this))); @@ -332,32 +372,12 @@ void AutofillOptionsHandler::SetAddressOverlayStrings( base::DictionaryValue* localized_strings) { localized_strings->SetString("autofillEditAddressTitle", l10n_util::GetStringUTF16(IDS_AUTOFILL_EDIT_ADDRESS_CAPTION)); - localized_strings->SetString("autofillFirstNameLabel", - l10n_util::GetStringUTF16(IDS_AUTOFILL_FIELD_LABEL_FIRST_NAME)); - localized_strings->SetString("autofillMiddleNameLabel", - l10n_util::GetStringUTF16(IDS_AUTOFILL_FIELD_LABEL_MIDDLE_NAME)); - localized_strings->SetString("autofillLastNameLabel", - l10n_util::GetStringUTF16(IDS_AUTOFILL_FIELD_LABEL_LAST_NAME)); - localized_strings->SetString("autofillCompanyNameLabel", - l10n_util::GetStringUTF16(IDS_AUTOFILL_FIELD_LABEL_COMPANY_NAME)); - localized_strings->SetString("autofillAddrLine1Label", - l10n_util::GetStringUTF16(IDS_AUTOFILL_FIELD_LABEL_ADDRESS_LINE_1)); - localized_strings->SetString("autofillAddrLine2Label", - l10n_util::GetStringUTF16(IDS_AUTOFILL_FIELD_LABEL_ADDRESS_LINE_2)); - localized_strings->SetString("autofillCityLabel", - l10n_util::GetStringUTF16(IDS_AUTOFILL_FIELD_LABEL_CITY)); localized_strings->SetString("autofillCountryLabel", - l10n_util::GetStringUTF16(IDS_AUTOFILL_FIELD_LABEL_COUNTRY)); + l10n_util::GetStringUTF16(IDS_LIBADDRESSINPUT_I18N_COUNTRY_LABEL)); localized_strings->SetString("autofillPhoneLabel", l10n_util::GetStringUTF16(IDS_AUTOFILL_FIELD_LABEL_PHONE)); localized_strings->SetString("autofillEmailLabel", l10n_util::GetStringUTF16(IDS_AUTOFILL_FIELD_LABEL_EMAIL)); - localized_strings->SetString("autofillAddFirstNamePlaceholder", - l10n_util::GetStringUTF16(IDS_AUTOFILL_FIELD_LABEL_ADD_FIRST_NAME)); - localized_strings->SetString("autofillAddMiddleNamePlaceholder", - l10n_util::GetStringUTF16(IDS_AUTOFILL_FIELD_LABEL_ADD_MIDDLE_NAME)); - localized_strings->SetString("autofillAddLastNamePlaceholder", - l10n_util::GetStringUTF16(IDS_AUTOFILL_FIELD_LABEL_ADD_LAST_NAME)); localized_strings->SetString("autofillAddPhonePlaceholder", l10n_util::GetStringUTF16(IDS_AUTOFILL_FIELD_LABEL_ADD_PHONE)); localized_strings->SetString("autofillAddEmailPlaceholder", @@ -451,27 +471,60 @@ void AutofillOptionsHandler::LoadAddressEditor(const base::ListValue* args) { base::DictionaryValue address; address.SetString("guid", profile->guid()); scoped_ptr<base::ListValue> list; - GetNameList(*profile, &list); - address.Set("fullName", list.release()); - address.SetString("companyName", profile->GetRawInfo(autofill::COMPANY_NAME)); - address.SetString("addrLine1", - profile->GetRawInfo(autofill::ADDRESS_HOME_LINE1)); - address.SetString("addrLine2", - profile->GetRawInfo(autofill::ADDRESS_HOME_LINE2)); - address.SetString("city", profile->GetRawInfo(autofill::ADDRESS_HOME_CITY)); - address.SetString("state", profile->GetRawInfo(autofill::ADDRESS_HOME_STATE)); - address.SetString("postalCode", + GetValueList(*profile, autofill::NAME_FULL, &list); + address.Set(kFullNameField, list.release()); + address.SetString( + kCompanyNameField, profile->GetRawInfo(autofill::COMPANY_NAME)); + address.SetString(kAddressLineField, + profile->GetRawInfo(autofill::ADDRESS_HOME_STREET_ADDRESS)); + address.SetString( + kCityField, profile->GetRawInfo(autofill::ADDRESS_HOME_CITY)); + address.SetString( + kStateField, profile->GetRawInfo(autofill::ADDRESS_HOME_STATE)); + address.SetString( + kDependentLocalityField, + profile->GetRawInfo(autofill::ADDRESS_HOME_DEPENDENT_LOCALITY)); + address.SetString(kSortingCodeField, + profile->GetRawInfo(autofill::ADDRESS_HOME_SORTING_CODE)); + address.SetString(kPostalCodeField, profile->GetRawInfo(autofill::ADDRESS_HOME_ZIP)); - address.SetString("country", + address.SetString(kCountryField, profile->GetRawInfo(autofill::ADDRESS_HOME_COUNTRY)); GetValueList(*profile, autofill::PHONE_HOME_WHOLE_NUMBER, &list); address.Set("phone", list.release()); GetValueList(*profile, autofill::EMAIL_ADDRESS, &list); address.Set("email", list.release()); + address.SetString(kLanguageCode, profile->language_code()); + + scoped_ptr<base::ListValue> components(new base::ListValue); + GetAddressComponents( + base::UTF16ToUTF8(profile->GetRawInfo(autofill::ADDRESS_HOME_COUNTRY)), + profile->language_code(), components.get(), NULL); + address.Set(kComponents, components.release()); web_ui()->CallJavascriptFunction("AutofillOptions.editAddress", address); } +void AutofillOptionsHandler::LoadAddressEditorComponents( + const base::ListValue* args) { + std::string country_code; + if (!args->GetString(0, &country_code)) { + NOTREACHED(); + return; + } + + base::DictionaryValue input; + scoped_ptr<base::ListValue> components(new base::ListValue); + std::string language_code; + GetAddressComponents(country_code, g_browser_process->GetApplicationLocale(), + components.get(), &language_code); + input.Set(kComponents, components.release()); + input.SetString(kLanguageCode, language_code); + + web_ui()->CallJavascriptFunction( + "AutofillEditAddressOverlay.loadAddressComponents", input); +} + void AutofillOptionsHandler::LoadCreditCardEditor(const base::ListValue* args) { DCHECK(IsPersonalDataLoaded()); @@ -514,48 +567,53 @@ void AutofillOptionsHandler::SetAddress(const base::ListValue* args) { if (!IsPersonalDataLoaded()) return; + int arg_counter = 0; std::string guid; - if (!args->GetString(0, &guid)) { + if (!args->GetString(arg_counter++, &guid)) { NOTREACHED(); return; } AutofillProfile profile(guid, kSettingsOrigin); - std::string country_code; base::string16 value; const base::ListValue* list_value; - if (args->GetList(1, &list_value)) - SetNameList(list_value, &profile); + if (args->GetList(arg_counter++, &list_value)) + SetValueList(list_value, autofill::NAME_FULL, &profile); - if (args->GetString(2, &value)) + if (args->GetString(arg_counter++, &value)) profile.SetRawInfo(autofill::COMPANY_NAME, value); - if (args->GetString(3, &value)) - profile.SetRawInfo(autofill::ADDRESS_HOME_LINE1, value); + if (args->GetString(arg_counter++, &value)) + profile.SetRawInfo(autofill::ADDRESS_HOME_STREET_ADDRESS, value); - if (args->GetString(4, &value)) - profile.SetRawInfo(autofill::ADDRESS_HOME_LINE2, value); + if (args->GetString(arg_counter++, &value)) + profile.SetRawInfo(autofill::ADDRESS_HOME_DEPENDENT_LOCALITY, value); - if (args->GetString(5, &value)) + if (args->GetString(arg_counter++, &value)) profile.SetRawInfo(autofill::ADDRESS_HOME_CITY, value); - if (args->GetString(6, &value)) + if (args->GetString(arg_counter++, &value)) profile.SetRawInfo(autofill::ADDRESS_HOME_STATE, value); - if (args->GetString(7, &value)) + if (args->GetString(arg_counter++, &value)) profile.SetRawInfo(autofill::ADDRESS_HOME_ZIP, value); - if (args->GetString(8, &country_code)) - profile.SetRawInfo(autofill::ADDRESS_HOME_COUNTRY, - base::ASCIIToUTF16(country_code)); + if (args->GetString(arg_counter++, &value)) + profile.SetRawInfo(autofill::ADDRESS_HOME_SORTING_CODE, value); + + if (args->GetString(arg_counter++, &value)) + profile.SetRawInfo(autofill::ADDRESS_HOME_COUNTRY, value); - if (args->GetList(9, &list_value)) + if (args->GetList(arg_counter++, &list_value)) SetValueList(list_value, autofill::PHONE_HOME_WHOLE_NUMBER, &profile); - if (args->GetList(10, &list_value)) + if (args->GetList(arg_counter++, &list_value)) SetValueList(list_value, autofill::EMAIL_ADDRESS, &profile); + if (args->GetString(arg_counter++, &value)) + profile.set_language_code(base::UTF16ToUTF8(value)); + if (!base::IsValidGUID(profile.guid())) { profile.set_guid(base::GenerateGUID()); personal_data_->AddProfile(profile); diff --git a/chrome/browser/ui/webui/options/autofill_options_handler.h b/chrome/browser/ui/webui/options/autofill_options_handler.h index 5d58d21..a98a986 100644 --- a/chrome/browser/ui/webui/options/autofill_options_handler.h +++ b/chrome/browser/ui/webui/options/autofill_options_handler.h @@ -55,6 +55,11 @@ class AutofillOptionsHandler : public OptionsPageUIHandler, // |args| - A string, the GUID of the address to load. void LoadAddressEditor(const base::ListValue* args); + // Requests input form layout information for a specific country code. Calls + // into WebUI with the layout information. + // |args| - A string, the country code to load. + void LoadAddressEditorComponents(const base::ListValue* args); + // Requests profile data for a specific credit card. Calls into WebUI with the // loaded profile data to open the credit card editor. // |args| - A string, the GUID of the credit card to load. |