1/* global PostcodeEuSettings, PostcodeNlAddressFieldMapping, PostcodeNlStateToValueMapping */ 2(function () { 3 'use strict'; 4 5 if (typeof PostcodeEuSettings === 'undefined') 6 { 7 return; 8 } 9 10 const $ = jQuery, 11 __ = wp.i18n.__, 12 settings = PostcodeEuSettings, 13 initializedElements = new Set(), 14 addressDetailsCache = new Map(), 15 storedAddresses = {}, 16 checkoutElements = {}, 17 loadingClassName = 'postcode-eu-address-validation-loading'; 18 19 const initialize = function () 20 { 21 if (typeof PostcodeNlAddressFieldMapping !== 'undefined') 22 { 23 // Provide backward compatibility with the `PostcodeNlAddressFieldMapping.mapping` global. 24 $.extend(settings.addressFieldMapping, PostcodeNlAddressFieldMapping.mapping); 25 } 26 27 $('.postcode-eu-autofill-intl').closest('div').each(function () { 28 if (initializedElements.has(this)) 29 { 30 return; // Already initialized. 31 } 32 33 const container = $(this), 34 addressType = this.querySelector('#billing_city') ? 'billing' : 'shipping'; 35 36 storedAddresses[addressType] ??= new StoredAddress(addressType); 37 38 if (false === container.is(':visible')) 39 { 40 return; // Skip if the container is not visible. Will try again on update_checkout. 41 } 42 43 initializedElements.add(this); 44 45 const addressFields = getAddressFields(addressType); 46 checkoutElements[addressType] = { container, addressFields }; 47 48 if (settings.displayMode === 'default') 49 { 50 addFormattedAddressOutput(container); 51 } 52 53 addAddressAutocompleteNl(container, addressType); 54 addAddressAutocompleteIntl(container, addressType); 55 56 if (settings.displayMode === 'showAll') 57 { 58 return; 59 } 60 61 if (settings.allowAutofillIntlBypass === 'y') 62 { 63 addAutofillIntlBypass(container, addressType); 64 } 65 66 container.find('.country_to_state').on('change.postcode-eu.address-fields', function () { 67 const selectedCountry = this.value; 68 69 window.setTimeout( // Execute after Woocommerce field logic: 70 () => toggleAddressFields(addressFields, !isSupportedCountry(selectedCountry), true) 71 ); 72 }).trigger('change.postcode-eu.address-fields'); 73 }); 74 }; 75 76 $(initialize); // On DOMReady 77 $(document.body).on('updated_checkout', initialize); 78 79 const isSupportedCountry = function (countryIso2) 80 { 81 for (let i in settings.enabledCountries) 82 { 83 if (settings.enabledCountries[i].iso2 === countryIso2) 84 { 85 return true; 86 } 87 } 88 89 return false; 90 }; 91 92 const getAddressFields = function (addressType) 93 { 94 const fields = {}; 95 96 for (const [key, addressPart] of Object.entries(settings.addressFieldMapping)) 97 { 98 const field = document.forms.checkout[`${addressType}_${key}`]; 99 if (field && addressPart !== null && field.type !== 'hidden') 100 { 101 fields[key] = $(field); 102 } 103 } 104 105 return fields; 106 }; 107 108 const getAddressFieldValues = function (addressType) 109 { 110 const fields = checkoutElements[addressType]?.addressFields ?? {}; 111 return Object.fromEntries(Object.entries(fields).map(([k, el]) => [k, el.val()])); 112 }; 113 114 const resetAddressFields = function (addressFields) 115 { 116 for (let i in addressFields) 117 { 118 addressFields[i] 119 .val('') 120 .trigger('input'); // Clears field validation. 121 } 122 }; 123 124 const toggleAddressFields = function (addressFields, state, force) 125 { 126 if (!force) 127 { 128 if (settings.displayMode === 'default') 129 { 130 // Fields remain hidden in default mode. A formatted address is shown instead. 131 state = false; 132 } 133 else if (settings.displayMode === 'showAll') 134 { 135 // Fields always visible in showAll mode. 136 state = true; 137 } 138 } 139 140 for (let i in addressFields) 141 { 142 let field = addressFields[i]; 143 144 if (field.length > 0 && field.prop('type') !== 'hidden') 145 { 146 field.closest('.form-row').toggle(state); 147 } 148 } 149 }; 150 151 const setAddressWithStorage = function (addressType, apiAddress, mailLines = null) 152 { 153 const mappedValues = {}, 154 values = { 155 street: apiAddress.street, 156 streetAndHouseNumber: apiAddress.streetLine, 157 houseNumber: apiAddress.buildingNumber ?? apiAddress.houseNumber, 158 houseNumberAddition: apiAddress.buildingNumberAddition ?? apiAddress.houseNumberAddition, 159 houseNumberAndAddition: apiAddress.building, 160 postcode: apiAddress.postcode, 161 city: apiAddress.locality ?? apiAddress.city, 162 province: apiAddress.province, 163 }; 164 165 for (const [key, field] of Object.entries(checkoutElements[addressType].addressFields)) 166 { 167 mappedValues[key] = String(values[settings.addressFieldMapping[key]] ?? ''); 168 field[0].value = mappedValues[key]; 169 field[0].dispatchEvent(new Event('change', {bubbles: true, cancelable: true})); 170 } 171 172 storedAddresses[addressType].set(mappedValues, mailLines); 173 174 if (addressType === 'billing' && hasUnusedShippingForm()) 175 { 176 // Also set shipping to avoid redundant validation at next pageview. 177 storedAddresses.shipping.set(mappedValues, mailLines); 178 } 179 180 // Force WooCommerce to recalculate shipping costs after address change. 181 $(document.body).trigger('update_checkout'); 182 }; 183 184 const setFieldValidity = function (field, errorMessage) 185 { 186 errorMessage = errorMessage || ''; 187 console.assert($.type(errorMessage) === 'string', 'Error message must be a string.'); 188 clearFieldValidity(field); 189 190 if (errorMessage === '') 191 { 192 field.closest('.form-row').addClass('woocommerce-validated'); 193 } 194 else 195 { 196 field.closest('.form-row').addClass('woocommerce-invalid'); 197 field.after($('<span>', {class: 'postcode-eu-address-validation-warning', text: errorMessage})); 198 } 199 }; 200 201 const clearFieldValidity = function (field) 202 { 203 field.closest('.form-row').removeClass('woocommerce-validated woocommerce-invalid'); 204 field.siblings('.postcode-eu-address-validation-warning').remove(); 205 }; 206 207 const getMappedAddressValues = (addressType) => { 208 // Get values according to field mapping. Mapped key must exist in address fields. 209 const values = {}; 210 for (const [key, prop] of Object.entries(settings.addressFieldMapping)) 211 { 212 if (prop && key in checkoutElements[addressType].addressFields) 213 { 214 values[prop] = checkoutElements[addressType].addressFields[key].val(); 215 } 216 } 217 218 // Check for separate street and house number fields, fill combined fields. 219 if ('street' in values) 220 { 221 if ('houseNumber' in values) 222 {
223 values.houseNumberAndAddition = [values.houseNumber, values.houseNumberAddition ?? ''].join(' ').trim(); 224 } 225 226 if ('houseNumberAndAddition' in values) 227 { 228 values.streetAndHouseNumber = [values.street, values.houseNumberAndAddition].join(' '); 229 } 230 } 231 232 // Return values if required fields are filled. Otherwise return null. 233 if (values.postcode && values.city && values.streetAndHouseNumber) 234 { 235 return values; 236 } 237 238 return null; 239 }; 240 241 const validateAddress = (countryIso3, streetAndBuilding = '', postcode = '', locality = '') => { 242 const url = settings.actions.validate 243 .replace('${country}', encodeURIComponent(countryIso3)) 244 .replace('${streetAndBuilding}', encodeURIComponent(streetAndBuilding)) 245 .replace('${postcode}', encodeURIComponent(postcode)) 246 .replace('${locality}', encodeURIComponent(locality)); 247 248 return fetch(url, {headers: {'X-WC-Checkout-Type': 'classic'}}).then((response) => { 249 if (response.ok) 250 { 251 return response.json(); 252 } 253 254 throw new Error(response.statusText, {cause: response}); 255 }); 256 }; 257 258 const getValidatedAddress = (...args) => { 259 return validateAddress(...args) 260 .then((response) => { 261 const top = response.matches[0]; 262 if ( 263 top?.status 264 && !top.status.isAmbiguous 265 && top.status.grade < 'C' 266 && ['Building', 'BuildingPartial'].includes(top.status.validationLevel) 267 ) 268 { 269 return top; 270 } 271 272 return null; 273 }) 274 .catch((error) => { 275 console.error(error); 276 throw error; 277 }); 278 }; 279 280 const validatePoBox = function (addressType) 281 { 282 return settings.allowPoBoxShipping === 'y' || !(addressType === 'shipping' || isBillingAsShipping()); 283 }; 284 285 const destruct = function () 286 { 287 for (const addressType in checkoutElements) 288 { 289 checkoutElements[addressType].container.find('.country_to_state').off('change.postcode-eu'); 290 checkoutElements[addressType].container.find('.form-row.postcode-eu-autofill').remove(); 291 toggleAddressFields(checkoutElements[addressType].addressFields, true, true); 292 } 293 }; 294 295 const addAddressAutocompleteNl = function (container, addressType) 296 { 297 if (typeof settings.enabledCountries.NL === 'undefined' || settings.netherlandsMode !== 'postcodeOnly') 298 { 299 return; 300 } 301 302 const countryToState = container.find('.country_to_state'), 303 nlFields = container.find('.postcode-eu-autofill-nl'), 304 lookupDelay = 750, 305 postcodeRegex = /[1-9][0-9]{3}\s*[a-z]{2}/i, 306 houseNumberRegex = /[1-9]\d{0,4}(?:\D.*)?$/i, 307 postcodeField = container.find('.postcode-eu-autofill-nl-postcode input'), 308 houseNumberField = container.find('.postcode-eu-autofill-nl-house-number input'), 309 houseNumberSelect = container.find('.postcode-eu-autofill-nl-house-number-select select'), 310 addressFields = checkoutElements[addressType].addressFields = getAddressFields(addressType), 311 storedAddress = storedAddresses[addressType]; 312 313 let lookupTimeout, 314 currentAddress = null, 315 currentStatus = null; 316 317 const toggleNlFields = function (state) 318 { 319 nlFields.slice(0, 2).toggle(state); 320 nlFields.slice(-1).toggle(state && houseNumberSelect.children().length > 1); 321 }; 322 323 const isNl = function () 324 { 325 return countryToState.val() === 'NL'; 326 }; 327 328 const isPostcodeValid = () => postcodeRegex.test(postcodeField.val()); 329 330 const isHouseNumberValid = () => houseNumberRegex.test(houseNumberField.val()); 331 332 const setHouseNumberAddition = function (value) 333 { 334 currentAddress.houseNumberAddition = value; 335 currentAddress.streetLine = `${currentAddress.street} ${currentAddress.houseNumber} ${currentAddress.houseNumberAddition ?? ''}`.trim(); 336 }; 337 338 postcodeField.on('input', () => { 339 window.clearTimeout(lookupTimeout); 340 clearFieldValidity(postcodeField); 341 resetHouseNumberSelect(); 342 currentStatus = null; 343 344 if (isPostcodeValid() && isHouseNumberValid()) 345 { 346 lookupTimeout = window.setTimeout(getAddress, lookupDelay); 347 } 348 }); 349 350 houseNumberField.on('input', () => { 351 window.clearTimeout(lookupTimeout); 352 clearFieldValidity(houseNumberField); 353 resetHouseNumberSelect(); 354 currentStatus = null; 355 356 if (isHouseNumberValid() && isPostcodeValid()) 357 { 358 lookupTimeout = window.setTimeout(getAddress, lookupDelay); 359 } 360 }); 361 362 // Replace WooCommerce checkout validation. 363 postcodeField.on('change focusout validate', (e) => { 364 e.stopPropagation(); 365 366 setFieldValidity( 367 postcodeField, 368 isPostcodeValid() ? '' : __('Please enter a valid postcode', 'postcode-eu-address-validation') 369 ); 370 }); 371 372 houseNumberField.on('change focusout validate', (e) => { 373 e.stopPropagation(); 374 375 if (currentStatus === null || currentStatus === 'valid') 376 { 377 setFieldValidity( 378 houseNumberField, 379 isHouseNumberValid() ? '' : __('Please enter a valid house number', 'postcode-eu-address-validation') 380 ); 381 } 382 }); 383 384 countryToState.on('change.postcode-eu', () => { 385 toggleNlFields(isNl()); 386 387 if (!isNl()) 388 { 389 return; 390 } 391 392 resetAddressFields(addressFields); 393 storedAddress.clear(); 394 395 if (isPostcodeValid() && isHouseNumberValid()) 396 { 397 getAddress(); 398 } 399 }); 400 401 postcodeField.on('address-result', function (_, data) { 402 if (data.status === 'valid') 403 { 404 fillAddressFieldsNl(data.address); 405 } 406 }); 407 408 houseNumberSelect.on('change validate', function () { 409 if (this.value === '0') // '0' is the select-house-number label. 410 { 411 if (this.children.length > 1) 412 { 413 setHouseNumberAddition(null); 414 postcodeField.trigger('address-result', {address: currentAddress, status: 'houseNumberAdditionIncorrect'}); 415 toggleAddressFields(addressFields, false); 416 resetAddressFields(addressFields); 417 setFieldValidity( 418 houseNumberSelect, 419 __('Please select a house number.', 'postcode-eu-address-validation') 420 ); 421 } 422 423 return; 424 } 425 426 toggleAddressFields(addressFields, true); 427 setHouseNumberAddition(this.value); 428 postcodeField.trigger('address-result', {address: currentAddress, status: 'valid'}); 429 clearFieldValidity(houseNumberSelect); 430 }); 431 432 container.on('reset-address', () => { 433 if (isNl()) 434 { 435 postcodeField.val(''); 436 houseNumberField.val(''); 437 resetHouseNumberSelect(); 438 resetAddressFields(addressFields); 439 storedAddress.clear(); 440 postcodeField.focus(); 441 } 442 }); 443 444 const getAddress = function () 445 {
446 const postcode = postcodeRegex.exec(postcodeField.val())[0].replace(/\s/g, ''), 447 houseNumberAndAddition = houseNumberRegex.exec(houseNumberField.val())[0].trim(), 448 url = settings.actions.dutchAddressLookup.replace('${postcode}', encodeURIComponent(postcode)).replace('${houseNumberAndAddition}', encodeURIComponent(houseNumberAndAddition)); 449 450 resetHouseNumberSelect(); 451 resetAddressFields(addressFields); 452 currentAddress = currentStatus = null; 453 postcodeField.addClass(loadingClassName); 454 455 $.get({ 456 url: url, 457 cache: true, 458 dataType: 'json', 459 headers: { 'X-WC-Checkout-Type': 'classic' }, 460 success: function (response) { 461 if (response.status === 'notFound') 462 { 463 setFieldValidity( 464 houseNumberField, 465 __('Address not found.', 'postcode-eu-address-validation') 466 ); 467 return; 468 } 469 470 if (response.address.addressType === 'PO box' && !validatePoBox(addressType)) 471 { 472 setFieldValidity( 473 houseNumberField, 474 __('Sorry, we cannot ship to a PO Box address.', 'postcode-eu-address-validation') 475 ); 476 response.status = 'poBoxNotAllowed'; 477 return; 478 } 479 480 if (response.status === 'houseNumberAdditionIncorrect') 481 { 482 setHouseNumberOptions(response.address); 483 houseNumberSelect.closest('.postcode-eu-autofill').show(); 484 } 485 else 486 { 487 toggleAddressFields(addressFields, true); 488 setFieldValidity(postcodeField); 489 setFieldValidity(houseNumberField); 490 } 491 492 currentAddress = response.address; 493 } 494 }).fail(function (jqXhr) { 495 if (jqXhr.status === 503) 496 { 497 destruct(); 498 return; 499 } 500 501 setFieldValidity( 502 houseNumberField, 503 __('An error has occurred. Please try again later or contact us.', 'postcode-eu-address-validation') 504 ); 505 }).always(function (response, textStatus) { 506 currentStatus = response.status ?? textStatus; 507 508 postcodeField.removeClass(loadingClassName); 509 510 postcodeField.trigger( 511 'address-result', 512 textStatus === 'success' ? response : {status: textStatus, address: null} 513 ); 514 }); 515 }; 516 517 const setHouseNumberOptions = function (address) 518 { 519 houseNumberSelect.children().eq(0).siblings().remove(); 520 521 let options = document.createDocumentFragment(); 522 523 address.houseNumberAdditions.forEach((addition) => options.appendChild(createOption(addition))); 524 options.appendChild(createOption( 525 address.unknownHouseNumberAddition, 526 '(' + __('unknown addition', 'postcode-eu-address-validation') + ')' 527 )); 528 houseNumberSelect.append(options); 529 530 function createOption(addition, labelSuffix) 531 { 532 const option = document.createElement('option'); 533 option.value = addition; 534 option.textContent = (address.houseNumber + ' ' + addition + ' ' + (labelSuffix ?? '')).trim(); 535 return option; 536 } 537 }; 538 539 const fillAddressFieldsNl = function (apiAddress) 540 { 541 setAddressWithStorage(addressType, apiAddress, getMailLinesNl(apiAddress)); 542 }; 543 544 const resetHouseNumberSelect = function () 545 { 546 houseNumberSelect.closest('.postcode-eu-autofill').hide(); 547 houseNumberSelect.children().eq(0).siblings().remove(); 548 }; 549 550 const extractHouseNumber = (streetAndHouseNumber) => { 551 const matches = [...streetAndHouseNumber.matchAll(/[1-9]\d{0,4}\D*/g)]; 552 553 if (matches[0]?.index === 0) 554 { 555 matches.shift(); // Discard leading number as a valid house number. 556 } 557 558 if (matches.length === 1) // Single match is most likely the house number. 559 { 560 return matches[0][0].trim(); 561 } 562 563 return null; // No match or ambiguous (i.e. multiple numbers found). 564 }; 565 566 // Initialize 567 (() => { 568 toggleNlFields(isNl()); 569 570 if (!isNl()) 571 { 572 return; 573 } 574 575 const prefilledAddressValues = getMappedAddressValues(addressType); 576 if (prefilledAddressValues === null) 577 { 578 return; 579 } 580 581 // Check for stored address to use. 582 if (!storedAddress.isExpired() && storedAddress.isEqual(getAddressFieldValues(addressType))) 583 { 584 const {values, mailLines} = storedAddress.get(); 585 586 postcodeField.val(values.postcode); 587 houseNumberField.val(values.houseNumberAndAddition); 588 589 setFieldValidity(postcodeField); 590 setFieldValidity(houseNumberField); 591 592 container.find('.postcode-eu-autofill-intl').trigger('address-result', {mailLines}); 593 594 toggleAddressFields(addressFields, true); 595 596 return; 597 } 598 599 // Stored address expired or not found, continue with prefilled values. 600
601 const houseNumberAndAddition = prefilledAddressValues.houseNumberAndAddition ?? 602 extractHouseNumber(prefilledAddressValues.streetAndHouseNumber); 603 604 postcodeField.val(prefilledAddressValues.postcode); 605 606 if (houseNumberAndAddition) 607 { 608 houseNumberField.val(houseNumberAndAddition); 609 } 610 611 // Use NL API if the postcode and house number are both valid. 612 if (isPostcodeValid() && isHouseNumberValid()) 613 { 614 getAddress(); 615 return; 616 } 617 618 // Fall back to Validate API for ambiguous house number cases. Because when a street line contains 619 // multiple numbers, the house number can't easily be determined via pattern matching. 620 if (houseNumberRegex.test(prefilledAddressValues.streetAndHouseNumber)) 621 { 622 postcodeField.addClass(loadingClassName); 623 624 const {postcode, city, streetAndHouseNumber} = prefilledAddressValues; 625 getValidatedAddress('NLD', streetAndHouseNumber, postcode, city) 626 .then((result) => { 627 if (result === null) 628 { 629 resetAddressFields(addressFields); 630 setFieldValidity( 631 houseNumberField, 632 __('Please enter a valid address.', 'postcode-eu-address-validation') 633 ); 634 currentStatus = 'notFound'; 635 } 636 else if (result.isPoBox && !validatePoBox(addressType)) 637 { 638 resetAddressFields(addressFields); 639 setFieldValidity( 640 houseNumberField, 641 __('Sorry, we cannot ship to a PO Box address.', 'postcode-eu-address-validation') 642 ); 643 currentStatus = 'poBoxNotAllowed'; 644 } 645 else 646 { 647 fillAddressFieldsNl(result.address); 648 649 setFieldValidity(postcodeField); 650 setFieldValidity(houseNumberField); 651 652 container.find('.postcode-eu-autofill-intl').trigger('address-result', result); 653 654 toggleAddressFields(addressFields, true); 655 } 656 }) 657 .catch((error) => { 658 if (error.cause?.status === 503) 659 { 660 destruct(); 661 } 662 }) 663 .finally(() => postcodeField.removeClass(loadingClassName)); 664 } 665 })(); 666 }; 667 668 const addAddressAutocompleteIntl = function (container, addressType) 669 { 670 const countryToState = container.find('.country_to_state'), 671 intlFormRow = container.find('.postcode-eu-autofill-intl'), 672 intlField = intlFormRow.find('input'), 673 storedAddress = storedAddresses[addressType]; 674 675 let autocompleteInstance = null, 676 addressFields = checkoutElements[addressType].addressFields = getAddressFields(addressType), 677 matches = []; 678 679 const isSupportedCountryIntl = function (countryIso2) 680 { 681 if (settings.netherlandsMode === 'postcodeOnly' && countryIso2 === 'NL') 682 { 683 return false; 684 } 685 686 return isSupportedCountry(countryIso2); 687 }; 688 689 const fillAddressFieldsIntl = function (result) 690 { 691 setAddressWithStorage(addressType, result.address, result.mailLines); 692 }; 693 694 const reset = function () 695 { 696 intlField.val(''); 697 resetAddressFields(addressFields); 698 storedAddress.clear(); 699 autocompleteInstance?.reset(); 700 }; 701 702 countryToState.on('change.postcode-eu', () => window.setTimeout(() => { 703 const isSupported = isSupportedCountryIntl(countryToState.val()); 704 if (isSupported) 705 { 706 reset(); 707 autocompleteInstance?.setCountry(settings.enabledCountries[countryToState.val()].iso3); 708 } 709 710 intlFormRow.toggle(isSupported); 711 })); 712 713 container.on('reset-address', () => { 714 if (isSupportedCountryIntl(countryToState.val())) 715 { 716 reset(); 717 intlField.focus(); 718 } 719 }); 720 721 const intlFieldObserver = new IntersectionObserver(function (entries) { 722 entries.forEach((entry) => { 723 if (!entry.isIntersecting || autocompleteInstance !== null) 724 { 725 return; 726 } 727 728 let deferred = $.Deferred(); 729 730 const selectAutocompleteAddress = function (item) 731 { 732 const callback = (result) => { 733 if (result.isPoBox && !validatePoBox(addressType)) 734 { 735 resetAddressFields(addressFields); 736 setFieldValidity( 737 intlField, 738 __('Sorry, we cannot ship to a PO Box address.', 'postcode-eu-address-validation') 739 ); 740 } 741 else 742 { 743 fillAddressFieldsIntl(result); 744 toggleAddressFields(addressFields, true); 745 intlField.trigger('address-result', result); 746 setFieldValidity(intlField); 747 } 748 749 deferred.resolve(); 750 }; 751
752 if (addressDetailsCache.has(item.context)) 753 { 754 callback(addressDetailsCache.get(item.context)); 755 return; 756 } 757 758 intlField.addClass(loadingClassName); 759 760 autocompleteInstance.getDetails(item.context, (result) => { 761 callback(result); 762 addressDetailsCache.set(item.context, result); 763 intlField.removeClass(loadingClassName); 764 }); 765 }; 766 767 const isSingleAddressMatch = () => matches.length === 1 && matches[0].precision === 'Address'; 768 769 autocompleteInstance = new PostcodeNl.AutocompleteAddress(intlField[0], { 770 autocompleteUrl: settings.actions.autocomplete, 771 addressDetailsUrl: settings.actions.getDetails, 772 context: (settings.enabledCountries[countryToState.val()]?.iso3 ?? 'nld').toLowerCase(), 773 }); 774 775 autocompleteInstance.getSuggestions = function (context, term, response) 776 { 777 const encodedTerm = new TextEncoder().encode(term), 778 binaryTerm = Array.from(encodedTerm, (byte) => String.fromCodePoint(byte)).join(''), 779 url = this.options.autocompleteUrl.replace('${context}', encodeURIComponent(context)).replace('${term}', encodeURIComponent(btoa(binaryTerm))); 780 781 return this.xhrGet(url, response); 782 }; 783 784 autocompleteInstance.getDetails = function (addressId, response) 785 { 786 const url = this.options.addressDetailsUrl.replace('${context}', encodeURIComponent(addressId)); 787 return this.xhrGet(url, response); 788 }; 789 790 intlField[0].addEventListener('autocomplete-xhr-send', ({detail: xhr}) => { 791 xhr.setRequestHeader('X-WC-Checkout-Type', 'classic'); 792 }); 793 794 intlField[0].addEventListener('autocomplete-select', function (e) { 795 if (e.detail.precision === 'Address') 796 { 797 selectAutocompleteAddress(e.detail); 798 } 799 }); 800 801 intlField[0].addEventListener('autocomplete-error', function (e) { 802 console.error('Autocomplete XHR error', e); 803 intlField.removeClass(loadingClassName); 804 805 if (e.detail.request.status === 503) 806 { 807 // API seems to be down, restore standard address fields. 808 destruct(); 809 return; 810 } 811 812 toggleAddressFields(addressFields, true); 813 setFieldValidity( 814 intlField, 815 __('An error has occurred while retrieving address data. Please contact us if the problem persists.', 'postcode-eu-address-validation') 816 ); 817 }); 818 819 // Clear the previous values when searching for a new address. 820 intlField[0].addEventListener('autocomplete-search', function () { 821 resetAddressFields(addressFields); 822 }); 823 824 intlField[0].addEventListener('autocomplete-response', function (e) { 825 matches = e.detail.matches; 826 827 deferred = $.Deferred(); 828 829 deferred.fail(() => setFieldValidity(intlField, __('Please enter an address and select it', 'postcode-eu-address-validation'))); 830 }); 831 832 intlField.on('blur', () => { 833 if ( 834 false === isSingleAddressMatch() 835 && false === autocompleteInstance.elements.menu.classList.contains('postcodenl-autocomplete-menu-open') 836 ) 837 { 838 deferred.reject(); 839 } 840 841 matches = []; 842 }); 843 844 // Prevent default validation via delegated event handler. 845 intlField.on('change focusout validate', (e) => e.stopPropagation()); 846 }); 847 }); 848 849 intlFieldObserver.observe(intlFormRow[0]); 850 851 intlFormRow.toggle(isSupportedCountryIntl(countryToState.val())); 852 853 // Initialize 854 (() => { 855 if (false === isSupportedCountryIntl(countryToState.val())) 856 { 857 return; // Only use values from supported countries. 858 } 859 860 const prefilledAddressValues = getMappedAddressValues(addressType); 861 if (prefilledAddressValues === null) 862 { 863 return; 864 } 865 866 const {postcode, city, streetAndHouseNumber} = prefilledAddressValues; 867 intlField.val(`${postcode} ${city} ${streetAndHouseNumber}`.trim()); 868 869 // Check for stored address to use. 870 if (!storedAddress.isExpired() && storedAddress.isEqual(getAddressFieldValues(addressType))) 871 { 872 setFieldValidity(intlField); 873 874 const {mailLines} = storedAddress.get(); 875 intlFormRow.trigger('address-result', {mailLines}); 876 877 return; 878 } 879 880 intlField.addClass(loadingClassName); 881 882 // Autocomplete prefilled address values using the Validate API. 883 const countryIso3 = settings.enabledCountries[countryToState.val()].iso3; 884 getValidatedAddress(countryIso3, streetAndHouseNumber, postcode, city) 885 .then((result) => { 886 if (result === null) 887 { 888 resetAddressFields(addressFields); 889 setFieldValidity( 890 intlField, 891 __('Please select a valid address', 'postcode-eu-address-validation') 892 ); 893 } 894 else if (result.isPoBox && !validatePoBox(addressType)) 895 { 896 resetAddressFields(addressFields); 897 setFieldValidity( 898 intlField, 899 __('Sorry, we cannot ship to a PO Box address.', 'postcode-eu-address-validation') 900 ); 901 } 902 else 903 { 904 fillAddressFieldsIntl(result); 905 setFieldValidity(intlField); 906 intlField.trigger('address-result', result); 907 toggleAddressFields(addressFields, true); 908 } 909 }) 910 .catch((error) => { 911 if (error.cause?.status === 503) 912 { 913 destruct(); 914 } 915 }) 916 .finally(() => intlField.removeClass(loadingClassName)); 917 })(); 918 }; 919 920 const getMailLinesNl = function (address) 921 { 922 return [address.streetLine, `${address.postcode} ${address.city}`]; 923 }; 924 925 const addFormattedAddressOutput = function (container) 926 { 927 const formRow = $('<div>', {class: 'form-row form-row-wide postcode-eu-autofill form-row-postcode-eu-autofill-address'}), 928 resetButton = $( 929 '<span>', 930 { 931 text: 'â', 932 title:__('Remove address', 'postcode-eu-address-validation'), 933 class: 'postcode-eu-autofill-address-reset', 934 click: () => { 935 container.trigger('reset-address'); 936 formRow.hide(); 937 }, 938 } 939 ), 940 addressElement = $('<address>', {class: 'postcode-eu-autofill-address'}); 941 942 formRow.append(resetButton, addressElement); 943 container.find('.postcode-eu-autofill').last().after(formRow); 944 945 container.find('.country_to_state').on('change', function () { 946 formRow.hide(); 947 }); 948 949 container.find('.postcode-eu-autofill-intl').on('address-result', function (_, result) { 950 addressElement.html(result.mailLines.join('<br>')); 951 formRow.show(); 952 }); 953 954 container.find('.postcode-eu-autofill-intl input').on('autocomplete-search', function () { 955 formRow.hide(); 956 }); 957 958 container.find('.postcode-eu-autofill-nl').on('address-result', function (_, result) { 959 if (result.status !== 'valid') 960 { 961 formRow.hide(); 962 return; 963 } 964 965 addressElement.html(getMailLinesNl(result.address).join('<br>')); 966 formRow.show(); 967 }); 968 }; 969 970 const addAutofillIntlBypass = function (container, addressType) 971 { 972 const formRow = container.find('.form-row.postcode-eu-autofill-intl'), 973 wrapper = $('<span>', {'class': 'postcode-eu-autofill-intl-bypass'}), 974 link = $('<a>', {text: settings.autofillIntlBypassLinkText}); 975 976 link.on('click', function () { 977 toggleAddressFields(checkoutElements[addressType].addressFields, true, true); 978 formRow.hide(); 979 return false;
980 }); 981 982 wrapper.append(link); 983 formRow.append(wrapper); 984 }; 985 986 const hasUnusedShippingForm = function () 987 { 988 if (typeof window.checkout.ship_to_different_address === 'undefined') 989 { 990 return false; // No separate shipping form exists. 991 } 992 993 return !window.checkout.ship_to_different_address.checked; 994 }; 995 996 const isBillingAsShipping = function () 997 { 998 if (typeof window.checkout.ship_to_different_address === 'undefined') 999 { 1000 return true; // Forced shipping to billing address. No separate shipping form. 1001 } 1002 1003 return !window.checkout.ship_to_different_address.checked; 1004 }; 1005 1006 class StoredAddress { 1007 storageKey; 1008 1009 constructor(addressType) 1010 { 1011 this.storageKey = 'postcode-eu-validated-address-' + addressType; 1012 } 1013 1014 get() 1015 { 1016 return JSON.parse(window.localStorage.getItem(this.storageKey)) ?? null; 1017 } 1018 1019 set(values, mailLines) 1020 { 1021 window.localStorage.setItem( 1022 this.storageKey, 1023 JSON.stringify({ 1024 timestamp: Date.now(), 1025 token: settings.localStorageToken, 1026 values: Object.fromEntries(Object.entries(values).filter(([, v]) => v !== '')), 1027 mailLines, 1028 }) 1029 ); 1030 } 1031 1032 isEqual(values) 1033 { 1034 const storedValues = this.get()?.values; 1035 return (storedValues ?? false) && Object.entries(storedValues).every(([k, v]) => values[k] === v); 1036 } 1037 1038 isExpired() 1039 { 1040 const data = JSON.parse(window.localStorage.getItem(this.storageKey)); 1041 return data?.timestamp + 90 * 24 * 60 * 60 * 1000 < Date.now() || data?.token !== settings.localStorageToken; 1042 } 1043 1044 clear() 1045 { 1046 window.localStorage.removeItem(this.storageKey); 1047 } 1048 } 1049})();
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.