From b7060b6cdc8ec3378964078fc63be863ba1227bb Mon Sep 17 00:00:00 2001 From: Jerry Smidt Date: Mon, 13 Jun 2022 16:05:59 +0200 Subject: [PATCH 1/5] Issue #38: Make house number addition required depending on the address --- Helper/ApiClientHelper.php | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/Helper/ApiClientHelper.php b/Helper/ApiClientHelper.php index 6f9189d..34acd8a 100644 --- a/Helper/ApiClientHelper.php +++ b/Helper/ApiClientHelper.php @@ -210,7 +210,12 @@ public function getNlAddress(string $zipCode, string $houseNumber): array $address = $this->_prepareResponse($address, $client); $status = 'valid'; - if (!is_null($houseNumberAddition) && (is_null($address['houseNumberAddition']) || strcasecmp($houseNumberAddition, $address['houseNumberAddition']) != 0) + if ( + (isset($address['parsedHouseNumberAddition']) && strcasecmp($address['parsedHouseNumberAddition'], $address['houseNumberAddition'] ?? '') != 0) + || + (!isset($address['parsedHouseNumberAddition']) && strcasecmp($address['houseNumberAddition'] ?? '', $houseNumberAddition ?? '') != 0) + || + (!empty($address['houseNumberAdditions']) && is_null($address['houseNumberAddition'])) ) { $status = 'houseNumberAdditionIncorrect'; } From c6f02e96c6794851367780241fa4ccec9af2f851 Mon Sep 17 00:00:00 2001 From: Jerry Smidt Date: Mon, 13 Jun 2022 16:29:02 +0200 Subject: [PATCH 2/5] Issue #39: Validate autocomplete fields * Hide formatted output when searching for a new address. * Tweak formatted output style. * Move autocomplete field KO binding to separate file. * Add translations. --- Api/Data/Autocomplete.php | 10 +- Block/Onepage/LayoutProcessor.php | 161 ++++++----- etc/frontend/di.xml | 2 +- i18n/en_US.csv | 3 + i18n/nl_NL.csv | 3 + view/frontend/layout/checkout_index_index.xml | 87 +++--- view/frontend/requirejs-config.js | 7 + view/frontend/web/css/source/_module.less | 4 +- .../address-autofill-formatted-output.js | 9 +- .../js/form/components/address-autofill-nl.js | 273 ++++++++++++++++++ .../js/form/element/address-autofill-field.js | 14 + .../js/form/element/address-autofill-intl.js | 61 ++-- .../js/form/element/house-number-select.js | 26 ++ .../web/js/form/element/house-number.js | 15 + view/frontend/web/js/form/element/postcode.js | 15 + .../js/ko/bindings/init-intl-autocomplete.js | 48 +++ view/frontend/web/js/model/address-nl.js | 9 + .../web/js/validation/validator-mixin.js | 42 +++ 18 files changed, 624 insertions(+), 165 deletions(-) create mode 100644 view/frontend/web/js/form/components/address-autofill-nl.js create mode 100644 view/frontend/web/js/form/element/address-autofill-field.js create mode 100644 view/frontend/web/js/form/element/house-number-select.js create mode 100644 view/frontend/web/js/form/element/house-number.js create mode 100644 view/frontend/web/js/form/element/postcode.js create mode 100644 view/frontend/web/js/ko/bindings/init-intl-autocomplete.js create mode 100644 view/frontend/web/js/model/address-nl.js create mode 100644 view/frontend/web/js/validation/validator-mixin.js diff --git a/Api/Data/Autocomplete.php b/Api/Data/Autocomplete.php index 51ec39e..a272de9 100644 --- a/Api/Data/Autocomplete.php +++ b/Api/Data/Autocomplete.php @@ -7,7 +7,7 @@ class Autocomplete implements AutocompleteInterface /** * @var Autocomplete\MatchInterface[] */ - public $matches = []; + public $matches = []; /** * __construct function. @@ -16,13 +16,13 @@ class Autocomplete implements AutocompleteInterface * @param array $response * @return void */ - public function __construct(array $response) - { - foreach ($response['matches'] as $match) + public function __construct(array $response) + { + foreach ($response['matches'] ?? [] as $match) { $this->matches[] = new Autocomplete\AutocompleteMatch($match); } - } + } /** * @inheritdoc diff --git a/Block/Onepage/LayoutProcessor.php b/Block/Onepage/LayoutProcessor.php index 7d49add..e27a029 100644 --- a/Block/Onepage/LayoutProcessor.php +++ b/Block/Onepage/LayoutProcessor.php @@ -10,7 +10,6 @@ class LayoutProcessor extends AbstractBlock implements LayoutProcessorInterface { protected $scopeConfig; - /** * __construct function. * @@ -26,7 +25,6 @@ public function __construct(Context $context, array $data = []) parent::__construct($context, $data); } - /** * process function. * @@ -38,103 +36,126 @@ public function process($jsLayout) { $moduleEnabled = $this->scopeConfig->getValue('postcodenl_api/general/enabled', \Magento\Store\Model\ScopeInterface::SCOPE_STORE); - if ($moduleEnabled && isset($jsLayout['components']['checkout']['children']['steps']['children']['shipping-step']['children']['shippingAddress']['children']['shipping-address-fieldset'])) { - $formFields = $this->_getFormFields($jsLayout['components']['checkout']['children']['steps']['children']); + if (!$moduleEnabled) { + return $jsLayout; + } - foreach ($formFields as &$fields) { - $fields = array_merge($fields, $this->_getAutofillFields($jsLayout)); - $fields = $this->changeAddressFieldPosition($fields); + // Shipping fields + $shippingFields = &$jsLayout['components'] + ['checkout']['children'] + ['steps']['children'] + ['shipping-step']['children'] + ['shippingAddress']['children'] + ['shipping-address-fieldset']['children']; + + $shippingFields = $this->_changeAddressFieldsPosition($shippingFields); + + // Autofill fields copy + $autofillFields = array_intersect_key($shippingFields, ['address_autofill_nl' => 1, 'address_autofill_intl' => 1, 'address_autofill_formatted_output' => 1]); + + // Billing step + $billingConfiguration = &$jsLayout['components'] + ['checkout']['children'] + ['steps']['children'] + ['billing-step']['children'] + ['payment']['children'] + ['payments-list']['children']; + + if (isset($billingConfiguration)) { + foreach($billingConfiguration as $key => &$billingForm) { + if (!strpos($key, '-form')) { + continue; + } + + // Billing fields + $billingForm['children']['form-fields']['children'] += $this->_updateCustomScope($autofillFields, $billingForm['dataScopePrefix']); + $billingForm['children']['form-fields']['children'] = $this->_changeAddressFieldsPosition($billingForm['children']['form-fields']['children']); } + } + // Billing address on payment page + $billingFields = &$jsLayout['components'] + ['checkout']['children'] + ['steps']['children'] + ['billing-step']['children'] + ['payment']['children'] + ['afterMethods']['children'] + ['billing-address-form']['children'] + ['form-fields']['children']; + + if (isset($billingFields)) { + $billingFields += $this->_updateCustomScope($autofillFields, 'billingAddressshared'); + $billingFields = $this->_changeAddressFieldsPosition($billingFields); + } + + // Compatibility + $magePlazaBillingFields = &$jsLayout['components'] + ['checkout']['children'] + ['steps']['children'] + ['shipping-step']['children'] + ['billingAddress']['children'] + ['billing-address-fieldset']['children']; + + if (isset($magePlazaBillingFields)) { + $magePlazaBillingFields += $this->_updateCustomScope($autofillFields, 'billingAddress'); + $magePlazaBillingFields = $this->_changeAddressFieldsPosition($magePlazaBillingFields); } return $jsLayout; } /** - * Get references to $jsLayout form fields. + * Find and update customScope * * @access private - * @param mixed $jsLayout - * @param array $result - Accumulates form fields. - * @return array - Array of form fields by reference. + * @param array $fields + * @param string $dataScope + * @return array - Fields with modified customScope. */ - private function _getFormFields(&$jsLayout, &$result = []) + private function _updateCustomScope($fields, $dataScope) { - foreach ($jsLayout as $name => &$value) { - if (in_array($name, ['form-fields', 'shipping-address-fieldset', 'billing-address-fieldset'], true)) { - $result[] = &$value['children']; + foreach ($fields as $name => $items) { + if (isset($items['config'], $items['config']['customScope'])) { + $fields[$name]['config']['customScope'] = $dataScope; } - else if (is_array($value)) { - $this->_getFormFields($value, $result); + + if (isset($items['children'])) { + $fields[$name]['children'] = $this->_updateCustomScope($items['children'], $dataScope); } } - return $result; + return $fields; } /** - * Get autofill fields from shipping fieldset. + * Change sort order of address fields. * * @access private - * @param mixed $jsLayout - * @return array - */ - private function _getAutofillFields($jsLayout) - { - $shippingFields = $jsLayout['components']['checkout']['children']['steps']['children'] - ['shipping-step']['children']['shippingAddress']['children']['shipping-address-fieldset']['children']; - - return array_intersect_key($shippingFields, ['address_autofill_nl' => 1, 'address_autofill_intl' => 1, 'address_autofill_formatted_output' => 1]); - } - - /** - * changeAddressFieldPosition function. - * - * @access public - * @param mixed $addressFields + * @param array $addressFields * @return array */ - public function changeAddressFieldPosition($addressFields) + private function _changeAddressFieldsPosition($addressFields) { if ($this->scopeConfig->getValue('postcodenl_api/general/change_fields_position') != '1') { return $addressFields; } - if (isset($addressFields['country_id'])) { - $addressFields['country_id']['sortOrder'] = '900'; - } - - if (isset($addressFields['address_autofill_intl'])) { - $addressFields['address_autofill_intl']['sortOrder'] = '910'; - } - - if (isset($addressFields['address_autofill_nl'])) { - $addressFields['address_autofill_nl']['sortOrder'] = '920'; - } - - if (isset($addressFields['address_autofill_formatted_output'])) { - $addressFields['address_autofill_formatted_output']['sortOrder'] = '930'; - } - - if (isset($addressFields['street'])) { - $addressFields['street']['sortOrder'] = '940'; - } - - if (isset($addressFields['postcode'])) { - $addressFields['postcode']['sortOrder'] = '950'; - } - - if (isset($addressFields['city'])) { - $addressFields['city']['sortOrder'] = '960'; - } - - if (isset($addressFields['region'])) { - $addressFields['region']['sortOrder'] = '970'; - } - - if (isset($addressFields['region_id'])) { - $addressFields['region_id']['sortOrder'] = '975'; + $fieldToSortOrder = [ + 'country_id' => '900', + 'address_autofill_intl' => '910', + 'address_autofill_nl' => '920', + 'address_autofill_formatted_output' => '930', + 'street' => '940', + 'postcode' => '950', + 'city' => '960', + 'region' => '970', + 'region_id' => '975', + ]; + + foreach ($fieldToSortOrder as $name => $sortOrder) { + if (isset($addressFields[$name])) { + $addressFields[$name]['sortOrder'] = $sortOrder; + } } return $addressFields; diff --git a/etc/frontend/di.xml b/etc/frontend/di.xml index 50fd465..6f02571 100644 --- a/etc/frontend/di.xml +++ b/etc/frontend/di.xml @@ -10,7 +10,7 @@ - Flekto\Postcode\Block\Onepage\LayoutProcessor + Flekto\Postcode\Block\Onepage\LayoutProcessor diff --git a/i18n/en_US.csv b/i18n/en_US.csv index abbf83d..64777a8 100644 --- a/i18n/en_US.csv +++ b/i18n/en_US.csv @@ -42,3 +42,6 @@ "Address not found.","Address not found." "Please enter a valid zip/postal code.","Please enter a valid zip/postal code." "Please enter a valid house number.","Please enter a valid house number." +"Please enter an address and select it.","Please enter an address and select it." +"An error has occurred while retrieving address data. Please contact us if the problem persists.","An error has occurred while retrieving address data. Please contact us if the problem persists." +"Please select a house number.","Please select a house number." diff --git a/i18n/nl_NL.csv b/i18n/nl_NL.csv index 89fef22..85ed160 100644 --- a/i18n/nl_NL.csv +++ b/i18n/nl_NL.csv @@ -42,3 +42,6 @@ "Address not found.","Adres niet gevonden." "Please enter a valid zip/postal code.","Vul alstublieft een geldige postcode in." "Please enter a valid house number.","Vul alstublieft een geldig huisnummer in." +"Please enter an address and select it.","Vul alstublieft een adres in en selecteer het." +"An error has occurred while retrieving address data. Please contact us if the problem persists.","Er is een fout opgetreden bij het ophalen van adres gegevens. Neem contact met ons op als het probleem blijft optreden." +"Please select a house number.","Selecteer alstublieft een huisnummer." diff --git a/view/frontend/layout/checkout_index_index.xml b/view/frontend/layout/checkout_index_index.xml index a3426f9..fb718b7 100644 --- a/view/frontend/layout/checkout_index_index.xml +++ b/view/frontend/layout/checkout_index_index.xml @@ -20,67 +20,66 @@ - Flekto_Postcode/js/form/element/address-autofill-nl - - ui/collection + Flekto_Postcode/js/form/components/address-autofill-nl + + ui/collection - - checkoutProvider - 65 - - - Magento_Ui/js/form/element/abstract + + 65 + + + Flekto_Postcode/js/form/element/postcode Zip/Postal Code - - ui/form/field - ui/form/element/input - true - 1234 AB - + + ui/form/field + ui/form/element/input + true + 1234 AB + shippingAddress + address-autofill-nl-postcode - - true + checkoutProvider + + + Flekto_Postcode/js/form/element/house-number + House number and addition + + ui/form/field + ui/form/element/input + true + shippingAddress - - - Magento_Ui/js/form/element/abstract - House number and addition - - ui/form/field - ui/form/element/input - true - address-autofill-nl-house-number - - true + checkoutProvider + + + Flekto_Postcode/js/form/element/house-number-select + Which house number do you mean? + + ui/form/field + ui/form/element/select + true + - Select house number - + shippingAddress - - - Magento_Ui/js/form/element/select - Which house number do you mean? - - ui/form/field - ui/form/element/select - true - - Select house number - - address-autofill-nl-house-number-select - - - + checkoutProvider + + + Flekto_Postcode/js/form/element/address-autofill-intl ui/form/field Flekto_Postcode/form/element/address-autofill-intl + shippingAddress + checkoutProvider Start typing your address or zip/postal code 66 address-autofill-intl-input - - true - + Flekto_Postcode/js/form/components/address-autofill-formatted-output diff --git a/view/frontend/requirejs-config.js b/view/frontend/requirejs-config.js index 38bbe3e..63a58a3 100644 --- a/view/frontend/requirejs-config.js +++ b/view/frontend/requirejs-config.js @@ -2,4 +2,11 @@ var config = { paths: { 'ui/template/group/group': 'Flekto_Postcode/template/group/group' }, + config: { + mixins: { + 'Magento_Ui/js/lib/validation/validator': { + 'Flekto_Postcode/js/validation/validator-mixin': true, + }, + }, + }, }; diff --git a/view/frontend/web/css/source/_module.less b/view/frontend/web/css/source/_module.less index d35e7d2..8130cb0 100644 --- a/view/frontend/web/css/source/_module.less +++ b/view/frontend/web/css/source/_module.less @@ -18,6 +18,6 @@ .address-autofill-formatted-output address { padding: 1em; - font-size: 1.2em; - background-color: #eee; + font-size: 1.8rem; + background-color: @color-white-smoke; } diff --git a/view/frontend/web/js/form/components/address-autofill-formatted-output.js b/view/frontend/web/js/form/components/address-autofill-formatted-output.js index 47535a2..b5e0304 100644 --- a/view/frontend/web/js/form/components/address-autofill-formatted-output.js +++ b/view/frontend/web/js/form/components/address-autofill-formatted-output.js @@ -55,8 +55,13 @@ define([ }, renderIntlAddress: function (address) { - this.content(address.mailLines[0] +'
' + address.mailLines[1]); - this.visible(true); + if (address === null) { + this.visible(false); + } + else { + this.content(address.mailLines[0] +'
' + address.mailLines[1]); + this.visible(true); + } }, }); }); diff --git a/view/frontend/web/js/form/components/address-autofill-nl.js b/view/frontend/web/js/form/components/address-autofill-nl.js new file mode 100644 index 0000000..8037f31 --- /dev/null +++ b/view/frontend/web/js/form/components/address-autofill-nl.js @@ -0,0 +1,273 @@ +define([ + 'uiCollection', + 'uiRegistry', + 'ko', + 'jquery', + 'mage/translate', + 'Flekto_Postcode/js/model/address-nl', +], function (Collection, Registry, ko, $, $t, addressModel) { + 'use strict'; + + return Collection.extend({ + defaults: { + imports: { + countryCode: '${$.parentName}.country_id:value', + postcodeValue: '${$.name}.postcode:value', + houseNumberValue: '${$.name}.house_number:value', + onChangeCountry: '${$.parentName}.country_id:value', + onInputPostcode: '${$.name}.postcode:value', + onInputHouseNumber: '${$.name}.house_number:value', + onChangeHouseNumberAddition: '${$.name}.house_number_select:value', + }, + modules: { + street: '${$.parentName}.street', + city: '${$.parentName}.city', + postcode: '${$.parentName}.postcode', + regionIdInput: '${$.parentName}.region_id_input', + childPostcode: '${$.name}.postcode', + childHouseNumber: '${$.name}.house_number', + childHouseNumberSelect: '${$.name}.house_number_select', + }, + settings: window.checkoutConfig.flekto_postcode.settings, + address: null, + lookupTimeout: null, + loading: false, + status: null, + addressFields: null, + }, + + initialize: function () { + this._super(); + + this.addressFields = Registry.async([ + this.parentName + '.street', + this.parentName + '.city', + this.parentName + '.postcode', + this.parentName + '.region_id_input', + ]), + + // The "loading" class will be added to the house number element based on loading's observable value. + // I.e. when looking up an address. + this.childHouseNumber(function (component) { + component.additionalClasses['loading'] = this.loading; + }.bind(this)); + + this.address.subscribe(this.setInputAddress.bind(this)); + + if (this.settings.fixedCountry !== null) { + this.countryCode = this.settings.fixedCountry; + this.onChangeCountry(); + } + + return this; + }, + + initElement: function (childInstance) { + childInstance.visible(this.isNl() && childInstance.index !== 'house_number_select'); + }, + + initObservable: function () { + this._super(); + this.observe('address loading status'); + return this; + }, + + onChangeCountry: function () { + this.addressFields(function () { // Wait for address fields to be available. + const isNl = this.isNl(); + + this.childPostcode().visible(isNl); + this.childHouseNumber().visible(isNl); + this.childHouseNumberSelect().visible(isNl && this.childHouseNumberSelect().options().length > 0); + this.toggleFields(!isNl, true); + + if (isNl) { + this.resetInputAddress(); + } + }.bind(this)); + }, + + isNl: function () { + return this.countryCode === 'NL'; + }, + + onInputPostcode: function (value) { + clearTimeout(this.lookupTimeout); + + if (value === '') { + return this.childPostcode().error(false) + } + + this.lookupTimeout = setTimeout(function () { + if (addressModel.postcodeRegex.test(value)) { + if (addressModel.houseNumberRegex.test(this.childHouseNumber().value())) { + this.getAddress(); + } + + return; + } + + this.resetHouseNumberSelect(); + }.bind(this), addressModel.lookupDelay); + }, + + onInputHouseNumber: function (value) { + clearTimeout(this.lookupTimeout); + + if (value === '') { + this.resetHouseNumberSelect(); + return this.childHouseNumber().error(false); + } + + this.lookupTimeout = setTimeout(function () { + if (addressModel.houseNumberRegex.test(value)) { + if (addressModel.postcodeRegex.test(this.childPostcode().value())) { + this.getAddress(); + } + + return; + } + + this.resetHouseNumberSelect(); + }.bind(this), addressModel.lookupDelay); + }, + + getAddress: function () { + const postcode = addressModel.postcodeRegex.exec(this.childPostcode().value())[0].replace(/\s/g, ''), + houseNumber = addressModel.houseNumberRegex.exec(this.childHouseNumber().value())[0].trim(); + + this.resetHouseNumberSelect(); + this.resetInputAddress(); + this.loading(true); + + const url = this.settings.base_url + 'postcode-eu/V1/nl/address/' + postcode + '/' + houseNumber; + + $.get({ + url: url, + cache: true, + dataType: 'json', + success: function (response) { + if (response[0].error) { + return this.childHouseNumber().error(response[0].message_details); + } + + this.status(response[0].status); + + if (this.status() === 'notFound') { + return this.childHouseNumber().error($t('Address not found.')); + } + + this.address(response[0].address); + + if (this.status() === 'houseNumberAdditionIncorrect') { + this.childHouseNumberSelect() + .setOptions(response[0].address.houseNumberAdditions) + .show(); + } + else { + this.toggleFields(true); + } + }.bind(this) + }).always(this.loading.bind(null, false)); + }, + + setInputAddress: function (address) { + const streetInputs = this.street().elems(), + addition = address.houseNumberAddition ? ' ' + address.houseNumberAddition : ''; + + if (streetInputs.length > 2) { + streetInputs[0].value(address.street); + streetInputs[1].value(String(address.houseNumber)); + streetInputs[2].value(addition.trim()); + } + else if (streetInputs.length > 1) { + streetInputs[0].value(address.street); + streetInputs[1].value(address.houseNumber + addition); + } + else { + streetInputs[0].value(address.street + ' ' + address.houseNumber + addition); + } + + this.city().value(address.city); + this.postcode().value(address.postcode); + this.regionIdInput().value(address.province); + }, + + onChangeHouseNumberAddition: function (value) { + if (typeof value === 'undefined') { + this.toggleFields(false); + this.resetInputAddress(); + return; + } + + const option = this.childHouseNumberSelect().getOption(value); + + if (typeof option.houseNumberAddition !== 'undefined') { + this.address().houseNumberAddition = option.houseNumberAddition; + this.status('valid'); + this.address.valueHasMutated(); + this.toggleFields(true); + } + }, + + resetInputAddress: function () { + this.street().elems.each(function (streetInput) { streetInput.reset(); }); + this.city().reset(); + this.postcode().reset(); + this.regionIdInput().reset(); + this.status(null); + }, + + resetHouseNumberSelect: function () { + this.childHouseNumberSelect().setOptions([]).hide(); + }, + + toggleFields: function (state, force) { + if (!this.isNl()) { + // Always re-enable region. This is not needed for .visible() because the region field has its own logic for that. + this.regionIdInput(function (component) { component.enable() }); + return; + } + + switch (this.settings.show_hide_address_fields) { + case 'disable': + { + const fields = ['city', 'postcode', 'regionIdInput']; + + for (let i = 0, field; field = fields[i++];) { + this[field](function (component) { component.disabled(!state) }); + } + + let j = 4; + + while (j--) { + Registry.async(this.street().name + '.' + j)('disabled', !state); + } + } + break; + case 'format': + if (!force) + { + if (!this.street().visible()) { + return; + } + + state = false; + } + /* falls through */ + case 'hide': + { + const fields = ['street', 'city', 'postcode']; + + for (let i = 0, field; field = fields[i++];) { + this[field](function (component) { component.visible(state) }); + } + + this.regionIdInput(function (component) { component.visible(state) }); + } + break; + } + }, + + }); +}); diff --git a/view/frontend/web/js/form/element/address-autofill-field.js b/view/frontend/web/js/form/element/address-autofill-field.js new file mode 100644 index 0000000..9440cc8 --- /dev/null +++ b/view/frontend/web/js/form/element/address-autofill-field.js @@ -0,0 +1,14 @@ +define([ + 'Magento_Ui/js/form/element/abstract', +], function (Abstract) { + 'use strict'; + + return Abstract.extend({ + + defaults: { + validation: { + 'required-entry': window.checkoutConfig.flekto_postcode.settings.show_hide_address_fields !== 'show', + }, + }, + }); +}); diff --git a/view/frontend/web/js/form/element/address-autofill-intl.js b/view/frontend/web/js/form/element/address-autofill-intl.js index 3c37434..e6bbcc2 100644 --- a/view/frontend/web/js/form/element/address-autofill-intl.js +++ b/view/frontend/web/js/form/element/address-autofill-intl.js @@ -1,9 +1,9 @@ define([ 'Magento_Ui/js/form/element/abstract', 'uiRegistry', - 'ko', - 'Flekto_Postcode/js/lib/postcode-eu-autocomplete-address', -], function (Abstract, Registry, ko, AutocompleteAddress) { + 'mage/translate', + 'Flekto_Postcode/js/ko/bindings/init-intl-autocomplete', +], function (Abstract, Registry, $t) { 'use strict'; return Abstract.extend({ @@ -44,10 +44,17 @@ define([ Registry.async(fields)(this.onChangeCountry.bind(this, this.countryCode)); } - this.bindKoHandler(); this.additionalClasses['loading'] = this.loading; this.address.subscribe(this.setInputAddress.bind(this)); + if (this.settings.show_hide_address_fields !== 'show') { + this.validation['validate-callback'] = { + message: $t('Please enter an address and select it.'), + isValid: this.isValid.bind(this), + }; + this.additionalClasses['required'] = true; + } + return this; }, @@ -84,44 +91,11 @@ define([ return this.intlAutocompleteCountries.indexOf(countryCode) > -1; }, - bindKoHandler: function () { - ko.bindingHandlers.initIntlAutocomplete = { - update: function (element, valueAccessor, allBindings, viewModel, bindingContext) { - if (viewModel.intlAutocompleteInstance !== null || !ko.unwrap(valueAccessor())) { - return; // Autocomplete instance already created or element not visible. - } - - viewModel.intlAutocompleteInstance = new AutocompleteAddress(element, { - autocompleteUrl: viewModel.settings.base_url + 'postcode-eu/V1/international/autocomplete', - addressDetailsUrl: viewModel.settings.base_url + 'postcode-eu/V1/international/address', - context: viewModel.countryCode || 'NL', - }); - - element.addEventListener('autocomplete-select', function (e) { - if (e.detail.precision === 'Address') { - viewModel.loading(true); - - viewModel.intlAutocompleteInstance.getDetails(e.detail.context, function (result) { - viewModel.address(result[0]); - viewModel.toggleFields(true); - viewModel.loading(false); - }); - } - }); - - document.addEventListener('autocomplete-xhrerror', function (e) { - console.error('Autocomplete XHR error', e); - viewModel.toggleFields(true); - viewModel.loading(false); - }); - - // Clear the previous values when searching for a new address. - element.addEventListener('autocomplete-search', viewModel.resetInputAddress.bind(viewModel)); - } - }; - }, - setInputAddress: function (result) { + if (result === null) { + return; + } + const address = result.address, streetInputs = this.street().elems(), number = String(address.buildingNumber || ''), @@ -148,6 +122,7 @@ define([ this.street().elems.each(function (streetInput) { streetInput.reset(); }); this.city().reset(); this.postcode().reset(); + this.address(null); }, toggleFields: function (state, force) { @@ -184,5 +159,9 @@ define([ } }, + isValid: function () { + return this.visible() === false || this.address() !== null; + }, + }); }); diff --git a/view/frontend/web/js/form/element/house-number-select.js b/view/frontend/web/js/form/element/house-number-select.js new file mode 100644 index 0000000..bbe28c8 --- /dev/null +++ b/view/frontend/web/js/form/element/house-number-select.js @@ -0,0 +1,26 @@ +define([ + 'Magento_Ui/js/form/element/select', + 'mage/translate', +], function (Select, $t) { + 'use strict'; + + return Select.extend({ + + initialize: function () { + this._super(); + + if (window.checkoutConfig.flekto_postcode.settings.show_hide_address_fields !== 'show') { + this.validation['validate-callback'] = { + message: $t('Please select a house number.'), + isValid: this.isValid.bind(this), + }; + this.additionalClasses['required'] = true; + } + }, + + isValid: function () { + return this.visible() === false || typeof this.value() !== 'undefined'; + }, + + }); +}); diff --git a/view/frontend/web/js/form/element/house-number.js b/view/frontend/web/js/form/element/house-number.js new file mode 100644 index 0000000..e8917d4 --- /dev/null +++ b/view/frontend/web/js/form/element/house-number.js @@ -0,0 +1,15 @@ +define([ + 'Flekto_Postcode/js/form/element/address-autofill-field', +], function (autofillField) { + 'use strict'; + + return autofillField.extend({ + + defaults: { + validation: { + 'validate-house-number': true, + }, + }, + + }); +}); diff --git a/view/frontend/web/js/form/element/postcode.js b/view/frontend/web/js/form/element/postcode.js new file mode 100644 index 0000000..8cc5854 --- /dev/null +++ b/view/frontend/web/js/form/element/postcode.js @@ -0,0 +1,15 @@ +define([ + 'Flekto_Postcode/js/form/element/address-autofill-field', +], function (autofillField) { + 'use strict'; + + return autofillField.extend({ + + defaults: { + validation: { + 'validate-postcode': true, + }, + }, + + }); +}); diff --git a/view/frontend/web/js/ko/bindings/init-intl-autocomplete.js b/view/frontend/web/js/ko/bindings/init-intl-autocomplete.js new file mode 100644 index 0000000..89d975f --- /dev/null +++ b/view/frontend/web/js/ko/bindings/init-intl-autocomplete.js @@ -0,0 +1,48 @@ +define([ + 'ko', + 'Magento_Ui/js/lib/knockout/template/renderer', + 'Flekto_Postcode/js/lib/postcode-eu-autocomplete-address', +], function (ko, renderer, AutocompleteAddress) { + 'use strict'; + + ko.bindingHandlers.initIntlAutocomplete = { + update: function (element, valueAccessor, allBindings, viewModel, bindingContext) { + if (viewModel.intlAutocompleteInstance !== null || !ko.unwrap(valueAccessor())) { + return; // Autocomplete instance already created or element not visible. + } + + viewModel.intlAutocompleteInstance = new AutocompleteAddress(element, { + autocompleteUrl: viewModel.settings.base_url + 'postcode-eu/V1/international/autocomplete', + addressDetailsUrl: viewModel.settings.base_url + 'postcode-eu/V1/international/address', + context: viewModel.countryCode || 'NL', + autoFocus: true, + }); + + element.addEventListener('autocomplete-select', function (e) { + if (e.detail.precision === 'Address') { + viewModel.loading(true); + + viewModel.intlAutocompleteInstance.getDetails(e.detail.context, function (result) { + viewModel.address(result[0]); + viewModel.toggleFields(true); + viewModel.loading(false); + viewModel.error(false); + }); + } + }); + + element.addEventListener('autocomplete-error', function (e) { + console.error('Autocomplete XHR error', e); + viewModel.toggleFields(true); + viewModel.loading(false); + viewModel.error($t('An error has occurred while retrieving address data. Please contact us if the problem persists.')); + }); + + // Clear the previous values when searching for a new address. + element.addEventListener('autocomplete-search', viewModel.resetInputAddress.bind(viewModel)); + } + }; + + renderer.addAttribute('initIntlAutocomplete'); + +}); diff --git a/view/frontend/web/js/model/address-nl.js b/view/frontend/web/js/model/address-nl.js new file mode 100644 index 0000000..fbb1a30 --- /dev/null +++ b/view/frontend/web/js/model/address-nl.js @@ -0,0 +1,9 @@ +define([], function () { + 'use strict'; + + return { + lookupDelay: 750, + postcodeRegex: /[1-9][0-9]{3}\s*[a-z]{2}/i, + houseNumberRegex: /[1-9]\d{0,4}(?:\D.*)?$/i, + }; +}); diff --git a/view/frontend/web/js/validation/validator-mixin.js b/view/frontend/web/js/validation/validator-mixin.js new file mode 100644 index 0000000..d0d64f0 --- /dev/null +++ b/view/frontend/web/js/validation/validator-mixin.js @@ -0,0 +1,42 @@ +/*! + * Validator mixin for Magento_Ui/js/lib/validation/validator + */ + +define([ + 'mage/translate', + 'Flekto_Postcode/js/model/address-nl', +], function ($t, addressNlModel) { + 'use strict'; + + return function (validator) { + + /** + * Add validator rule that simply calls isValid() on the params object. + * This allows validation logic to be implemented in UI components. + */ + validator.addRule( + 'validate-callback', + /** + * @param value - Current element value (not used here). + * @param {Object} params - Object with isValid() method. + * @return {boolean} - Valid if true. + */ + function (value, params) { return params.isValid(); }, + $t('Please enter a valid value.') // Customize via params.message property. + ); + + validator.addRule( + 'validate-postcode', + function (value) { return value === '' || addressNlModel.postcodeRegex.test(value); }, + $t('Please enter a valid zip/postal code.') + ); + + validator.addRule( + 'validate-house-number', + function (value) { return value === '' || addressNlModel.houseNumberRegex.test(value); }, + $t('Please enter a valid house number.') + ); + + return validator; + }; +}); From 06b1481f8193a7b3a192773dc4743c0941c5cd31 Mon Sep 17 00:00:00 2001 From: Jerry Date: Wed, 22 Jun 2022 13:30:10 +0200 Subject: [PATCH 3/5] Issue #38: Cleanup old debug code --- Helper/ApiClientHelper.php | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/Helper/ApiClientHelper.php b/Helper/ApiClientHelper.php index 34acd8a..a03d861 100644 --- a/Helper/ApiClientHelper.php +++ b/Helper/ApiClientHelper.php @@ -211,9 +211,7 @@ public function getNlAddress(string $zipCode, string $houseNumber): array $status = 'valid'; if ( - (isset($address['parsedHouseNumberAddition']) && strcasecmp($address['parsedHouseNumberAddition'], $address['houseNumberAddition'] ?? '') != 0) - || - (!isset($address['parsedHouseNumberAddition']) && strcasecmp($address['houseNumberAddition'] ?? '', $houseNumberAddition ?? '') != 0) + (strcasecmp($address['houseNumberAddition'] ?? '', $houseNumberAddition ?? '') != 0) || (!empty($address['houseNumberAdditions']) && is_null($address['houseNumberAddition'])) ) { From 426b6da5a68d5897d82800b518c71e44fe890467 Mon Sep 17 00:00:00 2001 From: Jerry Date: Wed, 22 Jun 2022 15:15:38 +0200 Subject: [PATCH 4/5] Issue #39: Validate intl field on change instead of keyup event --- .../web/template/form/element/address-autofill-intl.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/view/frontend/web/template/form/element/address-autofill-intl.html b/view/frontend/web/template/form/element/address-autofill-intl.html index c9b4306..0e9e516 100644 --- a/view/frontend/web/template/form/element/address-autofill-intl.html +++ b/view/frontend/web/template/form/element/address-autofill-intl.html @@ -1,7 +1,7 @@