Purpose#
AddressForm is the reusable that drives the address selection + address form UX in Checkout.It renders saved addresses (when present), allows editing, supports creating a new address, optionally collects invoice data (billing), and synchronizes billing/shipping via a shared Alpine store.On progression it also updates the Checkout state by calling servicesreusabledefault.updateCheckout(...).Where it's rendered#
It’s not rendered directly from a component; it’s rendered by BillingRetail (Checkout billing section).From Reusables/BillingRetail/Default.liquid:{% render 'Reusables\\AddressForm\\Default',
addressType: 'billing',
addresses: addresses,
countriesList: countriesList,
headQuarterAddress: headQuarterAddress
%}
{% render 'Reusables\\AddressForm\\Default',
addressType: 'shipping',
addresses: addresses,
countriesList: countriesList,
headQuarterAddress: headQuarterAddress
%}
Source: Reusables/AddressForm/Default.liquid.The reusable is initialized via Alpine using:x-data='addressformreusabledefault.initComponent(
{{ addresses | serialize | escape }},
{{ countriesList }},
{{ deliveryZonesEnabled }},
"{{ addressType }}",
"{{ inValidAddressMessage }}",
{{ modalTranslationsInvoice }},
"{{ addressErrorMessage }}",
"{{ addressUpdateErrorMessage }}",
"{{ postalCodesErrorMessage }}",
{{ isB2bCustomer }}
)'
Model shape (storefront example)#
Below is a sanitized excerpt from a real Checkout model dump (the fields used by this reusable via addresses and countriesList).{
"countriesList": [
{
"code": "GR",
"name": "Greece"
},
...
],
"addresses": [
{
"name": "",
"isSelectable": true,
"address": {
"id": "Address Id",
"firstName": "Sample first name",
"lastName": "Sample last name",
"phoneNumber": "Sample phone",
"address1": "Sample address",
"city": "Sample city",
"state": "Sample state",
"country": "Sample country",
"countryCode": "GR",
"postalCode": "10552",
"email": "sample@example.com"
},
"requiresInvoice": false
}
]
}
Required fields#
addresses#
Expected input is an array; each entry is expected to look like:Used as a “branch label” in some display cases.
Used when deliveryZonesEnabled is true.
The actual address fields that the forms bind to (examples seen in Liquid/JS):label (optional; shown in some recaps)
additionalInfo (used for new addresses)
countriesList#
addressType#
String, passed as 'billing' or 'shipping'.
Optional fields#
headQuarterAddress (object)Used only in the B2B flow.
In Default.liquid it’s displayed when isB2bCustomer == true and addressType == 'billing'.
deliveryZonesEnabled (boolean)Derived from GlobalData.Settings.deliveryZonesEnabled.
When enabled, the country/postal code behavior changes and extra validity checks apply.
Translation/error stringsinValidAddressMessage, addressErrorMessage, addressUpdateErrorMessage, postalCodesErrorMessage.
Template behavior (Liquid + Alpine)#
Source: Reusables/AddressForm/Default.liquid.Saved address list rendered with x-for over displayAddresses (computed method in JS).
Retail: editable forms + saved address selection.
For billing, it shows headQuarterAddress recap (read-only).
For shipping, it still renders radios and uses the shared store for selection.
The form fields’ ids follow patterns that Default.js depends on:Edit form fields: ${addressType}-edit-${index}-<field>
New form fields: ${addressType}-new-${index}-<field>
Invoice fields: company-${addressType}-edit-${index}-<field>
Data contract (JS runtime)#
addressformreusabledefault.initComponent(...)#
From Reusables/AddressForm/Default.js:initComponent(
addresses,
countriesList,
deliveryZonesEnabled,
addressType,
inValidAddressMessage,
modalTranslationsInvoice,
addressErrorMessage,
addressUpdateErrorMessage,
postalCodesErrorMessage,
isB2bCustomer
)
addresses can be an array, an object, or null; it gets normalized:
addresses: normalizedAddresses.map(a => a.address)
branchesNames: normalizedAddresses.map(a => a.name || '')
deliveryZonesEnabled ? normalizedAddresses.map(a => a.isSelectable) : normalizedAddresses.map(() => true)
JavaScript#
Source: Reusables/AddressForm/Default.js.init()#
Sets up the initial address arrays and the billing/shipping synchronization.Retail flow (!isB2bCustomer):Creates blank “new address” rows via startNewAddress().
In billing mode, it also seeds $store.sharedAddresses.addresses and $store.sharedAddresses.invoices.
In shipping mode, it reads back from $store.sharedAddresses.
Watches $store.sharedAddresses.updateShippingAddresses to keep the two forms in sync.
B2B flow (isB2bCustomer):Reads field values from the DOM and pushes them into $store.sharedAddresses.
Then keeps only the first address and calls saveTemporarilyAddress(0).
Uses $store.sharedAddresses.addresses.
displayAddresses() / displayInvoices()#
These methods decide what list items to show.They hide “blank” placeholder entries depending on:addressType (billing vs shipping)
whether there were saved addresses initially (normalizedAddresses.length > 0)
startEditing(index) / cancelEditing(index)#
Enables edit mode for a selected address.
Takes snapshots in originalData[index] and originalInvoiceData[index].
Clears errors and, when deliveryZonesEnabled, normalizes country values and triggers onCountryChange(...).
Resets touched/validity state.
saveTemporarilyAddress(index, type = 'edit')#
Validates the address form fields and stores the normalized data.Determines whether it’s an edit vs new form based on:addresses[index]?.editFlag
addresses[index]?.tempType === 'new'
Updates $store.sharedAddresses.validAddresses[index].
Shows a blocking toast (inValidAddressMessage) and resets the current checkout step if the address is not selectable.
Writes updates into $store.sharedAddresses (retail flow).
If the address is new and addressType === 'billing', it continues into saveTemporarilyInvoice(...).
saveTemporarilyInvoice(index, type = 'edit')#
Validates invoice fields (billing only), updates $store.sharedAddresses.invoices, and may persist the address/invoice in the profile via updateAddressDataProfile(...).Also flips $store.sharedAddresses.updateShippingAddresses = true to sync shipping.nextCheckoutStep()#
Builds an updated checkout payload from $store.checkout.data + $store.sharedAddresses and calls:servicesreusabledefault.updateCheckout(updatedCheckoutData, stepName)
Behavior differs per addressType:Sets documentType to Invoice vs Receipt based on invoiceOption.
If shipping is already valid, it updates the shipping step too.
Always updates checkout with billingShippingAddress as the step.
updateAddressDataProfile(addressId, type = 'edit')#
Persists an address to the user profile (create or update) using:servicesreusabledefault.createUserAddress(dataToSend)
servicesreusabledefault.updateUserAddress(dataToSend, addressId)
validateField(field)#
Client validation rules for required fields, email format, and select validation.For SELECT required fields, behavior depends on delivery zones:When deliveryZonesEnabled is true, it also consults $store.sharedAddresses.validAddresses[this.selectedAddressIndex].
onCountryChange(index, showToast = false)#
Delivery-zone-specific behavior:Syncs countryCode with country (using countriesList).
Updates postalCodes array and validates/sets postalCode field accordingly.
Invoice helpers#
validateInvoice() toggles invoice mode and may open a modal confirmation via $store.modal.
startInvoiceEditing(index) and isInvoiceModified(index) manage invoice edit state.
Global Alpine stores#
updateAddress(index, data, ...)
updateInvoice(index, data)
updateValidAddress(index, boolean)
validateStep(stepName, ...)
add(...), remove(...), removeAll()
Services / API calls#
From Reusables/AddressForm/Default.js:servicesreusabledefault.updateCheckout(updatedCheckoutData, stepName)
servicesreusabledefault.createUserAddress(dataToSend)
servicesreusabledefault.updateUserAddress(dataToSend, addressId)
servicesreusabledefault.fetchPostalCodes(countryCode)
Dependencies#
Reusables/AddressForm/Default.liquid
Reusables/AddressForm/Default.js
Reusables/AddressForm/Default.json (translations)
Reusables/BillingRetail/Default.liquid (render site)
$store.checkout, $store.sharedAddresses, $store.toast, $store.modal in Assets/js/theme.js
servicesreusabledefault (SDK)
Notes#
The logic depends heavily on DOM id naming conventions (${addressType}-edit-${index}-... etc). If you change ids in Liquid, you must update the JS selectors.
There are two different “persistence” paths:Checkout update (updateCheckout) – used to progress checkout steps.
Profile persistence (createUserAddress / updateUserAddress) – used when storing/updating addresses.
When deliveryZonesEnabled is enabled, postal code list is fetched per country; the postal code field becomes a select-driven validation path.
Examples#
{% render 'Reusables\\AddressForm\\Default',
addressType: 'billing',
addresses: model.addresses,
countriesList: model.countriesList,
headQuarterAddress: GlobalData.Settings.IsB2bCustomer ? model.checkout.billingAddress : nil
%}
{% render 'Reusables\\AddressForm\\Default',
addressType: 'shipping',
addresses: model.addresses,
countriesList: model.countriesList,
headQuarterAddress: GlobalData.Settings.IsB2bCustomer ? model.checkout.billingAddress : nil
%}
Modified at 2026-04-14 13:18:56