Purpose#
The ShoppingListsButton reusable renders a toggle button that lets authenticated users add/remove the currently selected product variant to one or more shopping lists.It opens a modal UI to select lists (and optionally create a new one), and then persists list changes via the shopping lists SDK endpoints.Where it's rendered#
This reusable is rendered from product cards and product pages, for example:Reusables/ProductGridItem/Default.liquid
Reusables/ProductListItem/Default.liquid
{% render 'Reusables\\ShoppingListsButton\\Default', product: product %}
The product object for which the current variant will be added/removed.
The variant selection is derived at runtime from $store.shoppingLists.dimension1Id / dimension2Id.
Template behavior (Liquid + Alpine)#
Rendering is gated by the feature flag:
GlobalData.Settings.EnableShoppingLists
The button is disabled while the store is loading:
:disabled="$store.shoppingLists.loading"
Active state is derived from the store:
:class='{"active": $store.shoppingLists.isInShoppingLists(product)}'
On init, it wires DOM listeners for variant selection changes:
x-init="shoppinglistsbuttonreusabledefault.init($el)"
If the user is not authenticated, it opens the login modal:
$store.loginModal.open(false, true)
Otherwise, it opens a modal flow:
shoppinglistsbuttonreusabledefault.handleShoppingListsModal($el, modalTranslations, product)
modalTranslations is constructed in Liquid as a JSON object string built from Reusables.ShoppingListsButton.Translations.* keys.
Data contract (JS runtime)#
Model shape (storefront example)#
The reusable needs enough product data to resolve a selected variant id:{
"id": "Product Id",
"title": "Sample title",
"productVariants": [
{
"id": "Variant Id",
"dimension1ItemId": "Dim Item Id",
"dimension2ItemId": "Dim Item Id",
"quantityConstraints": {
"additive": { "minimum": 1 }
}
},
...
],
"dimension1": { "items": [ { "id": "Dim Item Id" }, ... ] },
"dimension2": { "items": [ { "id": "Dim Item Id" }, ... ] }
}
Runtime state#
$store.shoppingLists.listArray of lists loaded from the API, plus a synthetic new list option.
$store.shoppingLists.dimension1Id / $store.shoppingLists.dimension2IdUpdated by shoppinglistsbuttonreusabledefault.init($el) and isVariantSelected(...) by listening to variant pickers in the closest .product-card / .product-single.
JavaScript#
Global object#
Global object: shoppinglistsbuttonreusabledefaultinit(element)#
Waits for $store.shoppingLists to be ready.
Finds the closest .product-card or .product-single container.
Attaches click / change listeners to dimension pickers (.product-card__variants__single) in order to keep $store.shoppingLists.dimension1Id and dimension2Id in sync.
Key behaviors / edge casesIf no product container is found, it logs a warning and returns.
Polls until $store.shoppingLists exists and has finished loading.
Optionally calls store.fetch() once if the list is empty.
Ensures a valid variant selection via isVariantSelected(element).
Computes the currently selected variant id via $store.shoppingLists.fetchSelectedVariantId(product).
checkbox options for existing lists
a conditional text input to create a new list
a confirm button that calls setShoppingLists(...)
Key behaviors / edge casesIf variants are not selected, it shows a toast with modalTranslations.selectVariantsFirst.
Parses selected list ids from the modal output.
adds the variant item if selected and not present
removes the variant item if unselected and present
Persists changes by calling servicesreusabledefault.updateShoppingList(list).
Refreshes state via $store.shoppingLists.fetch().
isVariantSelected(element)#
Finds the closest .product-card / .product-single.
Detects variant selection from either:or <select> selected option data-variantid
Writes ids into $store.shoppingLists.dimension1Id / dimension2Id.
Returns true only when the required dimensions are selected.
Global Alpine stores#
A client-side representation of shopping lists (loaded from the API) plus helper methods:fetchSelectedVariantId(product)
isInShoppingLists(product)
How this reusable uses itDetermines active state for the button.
Resolves selected variant ids.
Provides list options for the modal.
store.modal/store.toast / $store.loginModal#
$store.modal is used to render the selection UI.
$store.toast is used for errors and validation feedback.
$store.loginModal is used when the user is not authenticated.
Services / API calls#
servicesreusabledefault.getShoppingLists() (indirectly via $store.shoppingLists.init() and .fetch())
servicesreusabledefault.createShoppingList({ title })
servicesreusabledefault.updateShoppingList(list)
Dependencies#
Liquid: Reusables/ShoppingListsButton/Default.liquid
JS: Reusables/ShoppingListsButton/Default.js
Translations: Reusables/ShoppingListsButton/Default.json
Feature flag: GlobalData.Settings.EnableShoppingLists
$store.shoppingLists (defined in Assets/js/theme.js)
SDK wrapper: servicesreusabledefault.* shopping list methods
Notes#
Variant selection is mandatory for multi-variant products; otherwise the modal will refuse to proceed.
The button inspects DOM selectors (.product-card__variants__single) to infer selection state. If those selectors change, the shopping lists integration must be updated.
Modified at 2026-04-14 13:18:56