Home
Wiki
Home
Wiki
  1. 3. Reusables
  • Back to home
  • 1. Themes
  • Vs Code
    • Getting Started
  • Kitchenware
    • Layout
      • New Layout
      • Legacy Layout
    • Components
      • Announcement
      • Banner Carousel
      • Banner With Products Carousel
      • Blog Category List
      • Blog List
      • Brand List
      • Brands Carousel
      • Breadcrumb
      • Call To Action
      • Cart
      • Categories List
      • Change Password
      • Checkout
      • Cookie Manager
      • Filter list
      • Footer
      • Forgot Password
      • Form
      • Hero Carousel
      • Icon Block
      • Invitation
      • Last Visited Products
      • Layout
      • Login
      • Map
      • Nav Bar
      • Offer
      • Product Attachments
      • Product Attributes
      • Product Documentation
      • Product Expected
      • Product Modal
      • Products Block
      • Products Carousel
      • Product Single
      • Profile
      • Quote
      • Register
      • Related Products
      • Search
      • Stores
      • Subscribe Newsletter
      • Text with Image
      • Top Bar
      • Video
    • Reusables
      • Getting Started
    • Assets
      • Getting Started
    • SDK
      • Products
        • _findProductsByCategory
        • _findProductsByIds
        • _findProductsByTitle
        • _findProductsByFilter
        • _findProductsByCriteria
        • _findProductsAndCalculate
        • _findProductsThenCalculate
        • _getProductAttributeSet
        • _setLastVisited
      • Categories
        • _findCategoryTreeById
        • _findCategoriesByIds
        • _findCategoryByAlias
        • _findCategoryTreeByAlias
        • _getCategoryContent
      • Collections
        • _getCollectionContent
        • _findCollectionsByIds
        • _findCollectionsByIdsThenCalculate
      • Brands
        • _getBrandContent
        • _findBrandsByIds
      • Cart
        • _addToCartMulti
        • _addToCart
        • _setCart
        • _clearCart
        • _setCartListener
        • _removeFromCart
        • _calculateCart
      • Checkout
        • _startCheckout
        • _updateCheckout
        • _completeCheckout
      • Shopping Lists
        • _getShoppingLists
        • _updateShoppingList
        • _createShoppingList
        • _deleteShoppingList
        • _getShoppingListByAlias
      • Navigation
        • _getFooterMenu
        • _getHeaderMenu
      • Users
        • _getUserById
      • Utils
        • _calculateCurrency
        • _getCurrencySymbol
        • _getCulture
        • _subscribeToNewsletter
        • _findUnitsByIds
  • Noir
    • 0. Introduction
    • 1. Structure
      • Overview
      • LayoutA.liquid
      • ComponentsList.liquid
      • Metas.liquid
      • CssVariables.liquid
      • Json.liquid
      • GoogleTagManager.liquid
      • StagingButton.liquid
    • 2. Components
      • Overview
      • Announcement
      • BannerCarousel
      • BlogCategoryList
      • BlogList
      • BrandList
      • Breadcrumb
      • Cart
      • CategoriesList
      • ChangePassword
      • Checkout
      • CookieManager
      • FilterList
      • Footer
      • ForgotPassword
      • Form
      • IconBlock
      • Invitation
      • LastVisitedProducts
      • Login
      • Map
      • NavBar
      • ProductAttachments
      • ProductAttributes
      • ProductComparison
      • ProductDocumentation
      • ProductMixList
      • ProductsBlock
      • ProductsCarousel
      • ProductSingle
      • Profile
      • Register
      • RelatedProducts
      • SingleBlog
      • Stores
      • TextWithImage
      • ThankYouPage
      • TopBar
      • Wishlist
    • 3. Reusables
      • Overview
      • Addresses
      • BillingRetail
      • AddressForm
      • AnnouncementModal
      • BackToTop
      • Company
      • General
      • Login
      • LoginModal
      • Orders
      • Payment
      • ProductAttachments
      • ProductAttributes
      • ProductComparisonButton
      • ProductComparisonFloatingButton
      • ProductGridItem
      • ProductListItem
      • ShoppingListsButton
      • ProductModal
      • ProfileInfo
      • PromptModal
      • Register
      • Shipping
      • ShoppingLists
      • ShoppingListsNavbar
      • Toast
      • Users
      • VariantContent
      • WishlistButton
      • Services
    • 4. Assets
      • Fonts
      • Images
      • Templates
      • Javascript
        • Overview
        • theme.js
      • Css / Scss
        • Overview
        • ThemeClasses
    • 5. SDK
      • Overview
      • LiquidGlobals
      • ServicesSDK
  1. 3. Reusables

Addresses

Purpose

The Addresses reusable renders the address book UI inside the Profile component.

It supports:

  • listing existing addresses
  • setting an address as primary
  • editing an existing address
  • creating a new address
  • optional invoice details per address
  • optional delivery-zones validation flow (postal-code + countryCode-driven)

Where it's rendered

It’s rendered from Components/Profile/Default.liquid on the addresses tab:

{% render 'Reusables\\Addresses\\Default',
    addresses: addresses,
    countriesList: countriesList
%}

Inputs (Liquid render parameters)

addresses

Expected shape: array of items where each item is an object with:

  • address: an address object
  • invoice: (optional) invoice object
  • isValid: boolean (only used when delivery zones are enabled)

The JS normalizes input:

  • if addresses is an array → used as-is
  • if addresses is a single object → wrapped in a single-item array
  • else → []

countriesList

Expected shape: array of countries such as:

  • code
  • name

Used only when GlobalData.Settings.deliveryZonesEnabled is true.

Template behavior (Liquid + Alpine)

List vs edit

  • List view is shown when selectedAddressIndex == null.
  • Edit/create form is shown when selectedAddressIndex != null.

In list view it renders:

  • an “Add” card that calls startEditing() (note: the button is currently rendered as disabled in Liquid)
  • an address grid iterating addresses.slice(0, -1) (the last item is expected to be an empty draft)

Delivery zones mode

The reusable reads GlobalData.Settings.deliveryZonesEnabled.

When enabled:

  • country input is a <select> bound to addresses[selectedAddressIndex].countryCode
  • postal code is a <select> with options loaded for the selected country
  • per-address validity is taken from each input item’s isValid
  • invalid addresses can trigger a toast flow (only on /profile/addresses route)

When disabled:

  • country and postal code are plain text inputs
  • address validity is treated as true for all stored items

Data contract (JS runtime)

Alpine entry

The root element binds:

<div
  x-data='addressesreusabledefault.initComponent(addresses, countriesList, deliveryZonesEnabled, ...translations)'>

Factory signature (see Reusables/Addresses/Default.js):

  • addresses
  • countriesList
  • deliveryZonesEnabled
  • inValidAddressTitle
  • inValidAddressMessage
  • inValidAddressMessageButton
  • removeAddressErrorMessage
  • postalCodesErrorMessage

Normalization

Internally the reusable keeps:

  • startAddresses: original input array
  • addresses: normalizedAddresses.map((a) => a.address)
  • invoices: normalizedAddresses.map((a) => a.invoice || {})
  • storedAddressesValidity:
    • if delivery zones enabled → normalizedAddresses.map((a) => a.isValid)
    • else → true for all

It also ensures there is an empty address draft at the end (used as the “new address” form row).

JavaScript

Global object

The reusable exposes a global object:

const addressesreusabledefault = {
  initComponent(addresses, countriesList, deliveryZonesEnabled, inValidAddressTitle, inValidAddressMessage, inValidAddressMessageButton, removeAddressErrorMessage, postalCodesErrorMessage) { ... }
}

It’s instantiated from Liquid via:

x-data='addressesreusabledefault.initComponent(...)'

initComponent(addresses, countriesList, deliveryZonesEnabled, inValidAddressTitle, inValidAddressMessage, inValidAddressMessageButton, removeAddressErrorMessage, postalCodesErrorMessage)

What it does

  • Factory that normalizes the input addresses and returns the Alpine component state + methods.
  • Captures translation strings in closure so methods can show consistent toast messages.

Key behaviors

  • Normalizes addresses into normalizedAddresses:
    • array -> as-is
    • single object -> wrapped into [addresses]
    • falsy -> []
  • Projects runtime arrays:
    • this.addresses = normalizedAddresses.map(a => a.address)
    • this.invoices = normalizedAddresses.map(a => a.invoice || {})
  • Initializes per-address invoice toggle:
    • invoiceOption[i] = strings/non-empty check on invoice.companyName
  • Initializes validity:
    • if delivery zones enabled -> storedAddressesValidity = normalizedAddresses.map(a => a.isValid)
    • else -> all true

init() (async)

What it does

  • Runs when Alpine mounts.
  • Ensures a draft address exists and syncs state to shared stores.

Key behaviors

  • Calls startNewAddress() so the last item is a blank “draft” address.
  • Calls clearInvalidAddressFields() to wipe postalCode/country/countryCode on invalid addresses (delivery zones mode).
  • Publishes to Alpine.store('sharedAddresses'):
    • sharedAddresses.addresses
    • sharedAddresses.validAddresses
    • sharedAddresses.invoices
  • In delivery-zones mode, when the route ends with /profile/addresses, it calls controlToastVisibility().

Watchers

  • Watches Alpine.store('sharedAddresses').runToast:
    • Calls resetToasts()
    • If true, calls controlToastVisibility()
  • Watches Alpine.store('profile').tabChanged:
    • If editing, calls cancelEditing(selectedAddressIndex, true) and exits edit mode.

startEditing(index)

What it does

  • Enters edit mode for an existing address, or prepares the draft last-row for creating a new one.

Key behaviors

  • Clears toasts.
  • If index == null, targets the last draft row; if it’s no longer empty, it calls startNewAddress() then uses the new last index.
  • Stores rollback snapshots:
    • originalData[index] = { address: {...}, invoice: {...} }
    • originalInvoiceOption[index] = invoiceOption[index]
  • Resets per-index UI state (touched, openPopup, validity booleans).
  • If editing an existing address:
    • delivery-zones mode -> calls onCountryChange(index) (fetches postal codes + validates selects)
    • else -> validates all address fields on next tick.

cancelEditing(index, skipToastReset = false)

What it does

  • Exits edit mode and restores the address/invoice from snapshots.

Key behaviors

  • Restores addresses[index], invoices[index], and invoiceOption[index] from originalData/originalInvoiceOption.
  • If skipToastReset is false, sets Alpine.store('sharedAddresses').runToast = true (so delivery-zones warnings can be recalculated).
  • Scrolls to top via scrollToTop().

saveAddress(index, errorMessage) (async)

What it does

  • Validates the form and creates or updates the user address via the SDK.

Key behaviors

  • Collects:
    • address fields: [id^="profile-address-edit-"]
    • invoice fields: [id^="company-profile-address-edit-"]
  • Validates every field via validateField(...) and updates errors.
  • Builds request payload:
    • address: this.addressData
    • invoice: invoiceOption[index] ? this.invoiceData : null
    • isValid: true
  • Create vs update decision:
    • missing/empty addresses[index].id -> createUserAddress(...) (and removes address.id from payload)
    • else -> updateUserAddress(payload, id)
  • On failure (non-2xx or success === false) shows a toast and triggers sharedAddresses.runToast = true.
  • On success:
    • updates addresses[index] and invoices[index]
    • sets validity flags to true
    • calls moveEmptyAddressToEnd() to keep the blank draft last
    • scrolls to top and triggers sharedAddresses.runToast = true

confirmAddressDeletion(index, modalTranslations)

What it does

  • Opens a confirmation modal via Alpine.store('modal') and delegates deletion to removeAddress(index).

removeAddress(index) (async)

What it does

  • Deletes an address via servicesreusabledefault.deleteUserAddress(id) and removes it from all parallel arrays.

Key behaviors

  • Sets loading flags (isRemoving[index], isAddressStateChanging) and disables interactions.
  • On failure shows removeAddressErrorMessage toast and restores loading flags.
  • On success calls removeAddressData(index).

setAddressAsPrimary(index, setAddressAsPrimaryErrorMessage) (async)

What it does

  • Sets an address as primary in the backend and then reorders all arrays so the selected item becomes index 0.

Key behaviors

  • Calls servicesreusabledefault.setUserAddressAsPrimary(id).
  • On success unshifts the selected item to index 0 across:
    • addresses, invoices, storedAddressesValidity, invoiceOption, etc.

validateField(field)

What it does

  • Implements field validation and writes error state to errors[field.name].

Key behaviors

  • Required fields:
    • input: invalid when empty
    • select: invalid when no value / first option
  • Email validation:
    • basic regex check -> invalidEmail
  • Adds/removes UI classes:
    • invalid / valid on inputs and .select-wrap
    • toggles .icon-state classes (ic-warning / ic-check-circle-fill)

handleInput(event, index) / handleInvoiceInput(event, index)

What it does

  • Marks the row as touched, validates the field, and recomputes addressValidity[index] or invoiceValidity[index].

isAddressModified(index)

What it does

  • Computes whether the Save button should be enabled.

Key behaviors

  • Compares current form buffers (addressData, invoiceData) with persisted arrays (addresses[index], invoices[index]).
  • Also detects invoice toggle changes (invoiceOption[index] vs originalInvoiceOption[index]).

onCountryChange(index) (async)

What it does

  • Delivery-zones mode helper: syncs countryCode <-> country and fetches postal codes.

Key behaviors

  • Resets postal state when no country is selected.
  • Sets errors[postalCode] = 'wait' while fetching.
  • Calls servicesreusabledefault.fetchPostalCodes(countryCode).
  • Maps errors to:
    • errorFetchingPostalCodes (unexpected response shape)
    • notFound (failed request / no data)
  • On success:
    • stores this.postalCodes
    • tries to keep a saved postal code from originalData[index] if still available
    • revalidates all address fields on next tick.

controlToastVisibility() / resetToasts()

What it does

  • Delivery-zones mode: shows/removes per-address invalid toasts, and wires toast “scroll to card” button.

Key behaviors

  • Uses invalidToastId[index] to avoid duplicates.
  • Builds toast extra HTML with a button that scrolls to .address-wrap-box at the same index.

moveEmptyAddressToEnd()

What it does

  • Ensures the completely empty address object is always the last item.

Key behaviors

  • Finds the first address where all values are "" and moves it to the end across all parallel arrays.

Global Alpine stores

The reusable syncs shared state to Alpine.store('sharedAddresses'):

  • .addresses
  • .validAddresses
  • .invoices

It also uses:

  • .runToast flag to (re)trigger delivery-zones invalid-address toast logic.

It uses Alpine.store('toast') for errors and delivery-zone warnings.

Services / API calls

This reusable calls SDK wrappers from servicesreusabledefault:

  • createUserAddress(data) (when address.id is missing/empty)
  • updateUserAddress(data, id)

On non-2xx or success === false it shows an error toast.

Dependencies

  • Alpine.js (component state + watchers)
  • Alpine.store('toast')
  • Alpine.store('sharedAddresses')
  • servicesreusabledefault.createUserAddress(...)
  • servicesreusabledefault.updateUserAddress(...)

Notes

  • The “Add new address” card button is rendered with the disabled attribute in Liquid.
  • In list view the template uses addresses.slice(0, -1); the last address is expected to be the “draft” row.
  • Delivery zones mode uses countryCode + dynamically loaded postal codes; if input data has countryCode empty, the selects may be disabled until the user chooses a country.

Examples

Storefront model example (from Profile)

This reusable doesn’t receive the full Profile model — only addresses and countriesList.

Sanitized example based on your Profile model:

{
  "countriesList": [
    { "code": "COUNTRY_CODE", "name": "Country name" },
    { "code": "...", "name": "..." }
  ],
  "addresses": [
    {
      "address": {
        "id": "ADDRESS_ID",
        "firstName": "FIRST_NAME",
        "lastName": "LAST_NAME",
        "phoneNumber": "PHONE",
        "email": "EMAIL",
        "address1": "ADDRESS_LINE_1",
        "additionalInfo": "",
        "label": "",
        "city": "CITY",
        "postalCode": "POSTAL_CODE",
        "state": "STATE",
        "country": "COUNTRY_NAME",
        "countryCode": "COUNTRY_CODE"
      },
      "invoice": {
        "companyName": "COMPANY_NAME",
        "profession": "PROFESSION",
        "companyPhoneNumber": "COMPANY_PHONE",
        "taxOffice": "TAX_OFFICE",
        "tin": "TIN"
      },
      "isValid": true
    },
    {
      "address": {
        "id": "...",
        "firstName": "...",
        "lastName": "..."
      },
      "invoice": null,
      "isValid": true
    }
  ]
}
Modified at 2026-04-14 13:18:56
Previous
Overview
Next
BillingRetail
Built with