Home
Wiki
Home
Wiki
  1. 2. Components
  • 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. 2. Components

Cart

Purpose#

The Cart component renders the cart page UI, including:
Cart items list (with quantity editing, removal, undo removal)
Optional “Quick add product” search (if enabled by global settings)
Cart validation errors (e.g. product not found / quantity rules / stock issues)
Order summary section
Clear cart flow with confirmation modal
Optional wishlist integration (move to wishlist, toggle, etc.)
Optional matrix view for variants (when variantsDisplayType = "Matrix")

Inputs (model contract)#

Model shape (storefront example)#

{
  "errorCode": null,
  "name": "Cart",
  "view": "Default",
  "section": "SectionA",
  "settings": {
    "variantsDisplayType": "Simple",
    "id": "Component Id",
    "section": "SectionA",
    "type": "NoirCart",
    "name": "Cart",
    "configuredInContentApi": true,
    "view": "Default",
    "displayName": "Sample display name",
    "cssClass": ""
  },
  "translations": {
    "cart": "Sample translation",
    "noProductsInTheCart": "Sample translation",
    "goToCheckout": "Sample translation",
    "...": "..."
  }
}

Required fields#

settings.id
Used for wrapper id: comp-{{ id }}

Optional fields#

settings.cssClass
Applied to wrapper only when non-empty and not (UNDEFINED).
settings.variantsDisplayType
Controls variant display mode:
Simple (default)
Matrix (enables grouping behavior in JS)
errorCode
When present, shows a checkout-related error banner near the top of the cart.

JavaScript#

Global object#

The Cart component exposes a global object:
It’s initialized from Liquid via Alpine:
x-data="cartdefault.initComponent(variantMatrixView, updateProductCartErrorMessage, updateProductQuantityErrorMessage)"
initComponent(...) returns a large Alpine component object that:
reads live cart state from Alpine.store("cart")
maintains local UI state (loading flags, ghost items, grouped items)
performs async cart operations through servicesreusabledefault
drives sticky layout behavior and quick add behavior

initComponent(variantMatrixView, updateProductCartErrorMessage, updateProductQuantityErrorMessage)#

What it does
Factory that receives:
variantMatrixView (boolean): enables matrix/grouped mode
updateProductCartErrorMessage (string): toast message when cart update fails
updateProductQuantityErrorMessage (string): toast message when quantity validation/update fails
Returns Alpine state and methods (see below).
Advice
Keep the signature stable (Liquid contract).
Notice these 2 error messages are captured in closure and used inside multiple methods. If you refactor, keep them accessible to avoid breaking toasts.

init() (async)#

What it does
Runs automatically when Alpine mounts the cart.
Calls:
this.refreshItems(true) to build allItems and (on first load) run cart validation + GA event
Initializes sticky behavior:
updateHeaderHeight()
updateStickyElementTop()
Registers window listeners:
resize and scroll → recalculates header height and sticky top offset
Watches cart store changes:
$watch(() => Alpine.store("cart").items, (newItems) => { ... })
Removes ghosts that re-appeared as live items
Merges live + ghost items and refreshes UI
In matrix mode, rebuilds grouped view
Advice
There is no cleanup for event listeners. That’s OK if the cart page is a full navigation page, but if you later implement partial navigation, add guards to avoid duplicated listeners.
Watcher is on the cart store array reference; make sure the store updates the array in a way Alpine detects (the theme store generally does this correctly).

groupedCartItems(items)#

What it does
Ensures items are grouped by productId when matrix view is enabled.
Behavior:
1.
Detects if items are already grouped:
considers them grouped when every item has a variants property
if already grouped → returns items unchanged
2.
Otherwise groups flat cart items by productId and returns an array like:
{ productId, productTitle, imageLink, isDeleted, variants: [...] }
Advice
The “already grouped” detection is important because other methods may pass previously-grouped arrays back in.
If you add fields to cart items that the UI depends on, ensure the group object includes what the template expects (e.g., use consistent keys).

updateHeaderHeight()#

What it does
Reads the page <header> height:
document.querySelector("header")?.clientHeight
Stores it in this.headerHeight.
Advice
If layout changes and the header is not <header>, sticky aside offset will break.
Prefer a stable selector if the header markup changes (e.g. header.site-header).

updateStickyElementTop()#

What it does
Finds .cart-aside and sets its inline top style to match headerHeight:
stickyEl.style.top = \${this.headerHeight}px``
Advice
This assumes .cart-aside is position-sticky (or comparable) and uses top.
If you change the cart layout and rename .cart-aside, update this selector.

refreshItems(runOnce = false) (async)#

What it does
Builds the visual cart items list from:
live items from the cart store (Alpine.store("cart").items)
plus “ghost items” (recently removed items kept for undo UX)
Steps:
1.
Maps live items into a UI-friendly shape:
adds key, isDeleted: false, isLoading: false
2.
Removes ghost items that have reappeared as live items
3.
Inserts ghosts back into the list at their original positions (_ghostIndex)
4.
Sets this.allItems:
matrix mode → grouped via groupedCartItems(mergedItems)
simple mode → merged flat list
5.
First-load-only behavior (runOnce === true and items exist):
calls servicesreusabledefault.getCartValidation()
shows error panel by setting hideErrors = false when there are validation lines
sends GA event:
sendGAEvent("viewCart", { items: prepareItemsForCheckoutProcess(this.allItems) })
Advice
Ghost insertion uses _ghostIndex. If you later sort items differently, you may want to update how _ghostIndex is computed/stored.
This method calls cart validation only once on first load (when there are items). If you want validation to re-run after edits, call it explicitly after operations.

sendSelectEvent(product, listName, position)#

What it does
Sends a GA “select item” event for a product:
sendGAEvent("selectItem", { listName, items, position })
Uses helper:
prepareListProducts([product])
Advice
Keep analytics payload shape consistent; GA event naming is a shared convention across the theme.

clearCart(clearCartErrorMessage) (async)#

What it does
Clears existing toasts.
Sets global loading flag (this.isLoading = true).
Calls:
servicesreusabledefault.clearCart()
On failure: shows toast using the provided translation string.
Always clears loading flag.
Advice
clearCartErrorMessage is passed in at call time (from modal translations).
Keep it localized in translations; don’t hardcode strings in JS.

handleClearCartModal(modalTranslations)#

What it does
Opens a confirmation modal via the global modal store:
Alpine.store("modal").open({ title, message, buttons: [...] })
The “Clear” button:
closes modal
calls this.clearCart(modalTranslations.clearCartErrorMessage)
The “Cancel” button closes modal.
Advice
This is the preferred pattern in Noir: components don’t implement their own modal markup; they use the global store/modal reusable.

addProductInWishlist(productId, variantId, idx, removeFromWishlistErrorMessage, addToWishlistErrorMessage) (async)#

What it does
Toggles wishlist state via:
Alpine.store("wishlist").toggle(productId, ...)
Then removes item(s) from cart:
If variantId is an object (matrix/group item), removes all variants inside variantId.variants
Else removes a single variant via removeItem(...)
Manages loading flags (this.isLoading and per-item isLoading).
Advice
The method overloads variantId (can be string OR object that contains variants). That’s convenient, but fragile.
If you refactor, consider passing an explicit shape (e.g. { mode: "single"|"group", variantId|variants }) to avoid type ambiguity.
Wishlist store must exist; this method should only be shown/called when wishlist is enabled.

removeProduct(item, idx, removeItemErrorMessage) (async, matrix helper)#

What it does
Removes an entire grouped product (all variants) by calling removeItem(...) sequentially for each variant.
Advice
The sequential await is safer for state consistency (and less load on API).
If you ever parallelize, protect UI state carefully (ghostItems, isLoading flags).

removeItem(variantId, idx, removeItemErrorMessage, variantIdx = 0, showUndo = true) (async)#

What it does
Removes one cart item (or one variant inside a group) and optionally creates a “ghost item” for undo:
1.
Guard: if (idx === -1) return;
2.
Clears toasts, sets this.isLoading = true
3.
Sets per-item loading flag
4.
Copies the original item state (original)
5.
Calls API:
servicesreusabledefault.removeFromCart(variantId)
6.
On failure:
shows toast
resets loading flags
refreshes items
7.
On success and showUndo === true:
creates a ghost item:
isDeleted: true
key: <variantId>-ghost-<variantIdx>
_ghostIndex: idx
ensures only one ghost per variant
8.
Resets loading flags and refreshes items (merges ghosts + live items)
Advice
There is a likely typo in the loading flag assignment:
simple mode currently sets this.allItems[idx].isLoading = false at the start (should probably be true)
If the UI looks like it doesn’t show loading on remove, this is why.
Ghost items are stored in this.ghostItems and then merged in refreshItems(). Don’t try to manipulate DOM directly for undo; keep it state-driven.

addProduct(item, idx, addToCartErrorMessage, variantIdx = 0) (async)#

What it does
Used mainly for “undo removal” behavior:
Resolves the correct variant (matrix vs simple).
Calls:
servicesreusabledefault.addToCart(productId, productVariantId, quantity, finalPrice, originalPrice, false)
On failure: shows toast and returns.
On success:
removes the corresponding ghost item
finds the added cart item in response
calls updateQuantity(minimumQty, addedProduct, addedProduct.productVariantId) to normalize quantity rules
refreshes UI
Advice
This assumes servicesreusabledefault.addToCart(...) returns updated cart data including cartItems.
If backend changes response shape, the “find addedProduct” logic must be updated.

debounceUpdate(item, delay = 800)#

What it does
Debounces calls to updateProductCart(item, false) using setTimeout.
Uses debounceMap[item.id] to store timeout id.
Cancels any pending axios request for the same item via cancelTokenMap[item.id].
Advice
item.id must be stable across renders; it’s used as a debounce key.
Debouncing is important because notes/quantity edits can generate many updates quickly.

debounceUpdateProduct(newQuantity, product, productVariantId, checkQuantity, delay = 800)#

What it does
Same strategy as debounceUpdate, but for quantity updates:
schedules updateQuantity(..., checkQuantity, false) after delay
Uses debounceMap[product.id] and cancelTokenMap[product.id].
Advice
Different methods use item.id vs product.id as keys. Make sure the passed objects actually have the expected id field, or debounce/cancel won’t work properly.

updateProductCart(item, useDebounce = true) (async)#

What it does
Persists cart item changes (notes/quantity) by:
1.
If useDebounce is true → delegates to debounceUpdate(item) and returns.
2.
Clears toasts.
3.
Cancels any previous in-flight request for the same item (cancelTokenMap[item.id]).
4.
Creates a new axios cancel token.
5.
Reads current cartData from localStorage.
6.
Finds the matching cart item in cartData.cartItems by id and updates its notes and quantity.
7.
Calls:
servicesreusabledefault.updateProductCart(cartData, cancelToken)
8.
On failure: shows toast using updateProductCartErrorMessage.
9.
Clears cancel token entry and loading flag.
Advice
Critical dependency: localStorage cartData must exist and match the updated store.
If cartData is missing, this method currently returns early without resetting isLoading (it sets this.isLoading = true before reading). If you see a stuck loader, this is a reason to add a safe finally/cleanup.
If you ever add more editable fields, update the localStorage mutation logic accordingly.

increaseQuantity(product)#

What it does
Computes step/min/max from:
product.quantityConstraints.absolute.step (default 1)
product.quantityConstraints.absolute.maximum (default Infinity)
uses minimum as fallback base
Increments quantity by step, clamps to max.
Sets product.quantity = newQty and calls:
this.updateProductCart(product, true) (debounced)
Advice
This does not call server-side quantity validation directly; it relies on cart update + optional validation flow elsewhere.
If you need strict stock validation before applying changes, use updateQuantity(..., checkQuantity=true).

decreaseQuantity(product)#

What it does
Decrements quantity by step, clamps to min.
Updates product.quantity and calls updateProductCart(product, true).
Advice
Same as increase: it’s fast UI-side, then persisted via debounced update.

updateQuantity(newQuantity, product, productVariantId, checkQuantity = false, useDebounce = true) (async)#

What it does
Two-mode quantity update:
A) checkQuantity === true (server-validated quantity)
If useDebounce is true:
debounces via debounceUpdateProduct(...) and returns
Cancels previous request for product.id
Calls:
servicesreusabledefault.updateProductQuantity({ productId, productVariantId, requestedQuantity }, cancelToken)
On failure:
shows toast using updateProductQuantityErrorMessage
returns
On success:
sets this.quantity = response.data?.absoluteQuantity.value
sets product.quantity accordingly
calls this.updateProductCart(product, true) (debounced persistence)
B) checkQuantity === false (no server validation)
Sets this.quantity = newQuantity
Updates product.quantity
Calls updateProductCart(product, true)
Advice
If you have strict quantity rules (min/max/step, stock), prefer checkQuantity = true.
this.quantity is used as a shared field; in concurrent updates it can be overwritten. If that starts causing UI bugs, move quantity state to per-item keyed storage.

handleCheckout()#

What it does
If userAuthenticated is true:
redirects to /checkout
Otherwise:
sets Alpine.store("loginModal").goToCheckoutPage = true
opens login modal: Alpine.store("loginModal").open(true)
Advice
userAuthenticated comes from a global injected variable (layout).
Ensure it’s available on the cart page; otherwise this will throw.

itemInvalid(itemId)#

What it does
Checks this.cartValidation.lines (if available) and returns whether the given cart line id is part of a failed validation line.
Logic:
finds a validation line where line.lineIds contains itemId
returns !line.isSuccess when found, otherwise false
Advice
This assumes cartValidation uses lineIds that match cart item id. If backend changes ids, invalid highlighting will break.

clearItems(lineIds, errorType, clearItemErrorMessage) (async)#

What it does
Removes multiple items that belong to a validation error group:
Sets this.errorRemoving[errorType] = true (per-error-type loading flag)
Filters cart store items whose id is in lineIds
For each item:
finds its index in this.allItems
calls removeItem(productVariantId, idx, clearItemErrorMessage, 0, false) (no undo)
refreshes items
Re-fetches validation:
this.cartValidation = await servicesreusabledefault.getCartValidation()
Clears loading flag.
Advice
This removes items sequentially, which is safer and yields consistent state.
Pass showUndo = false intentionally here: validation cleanup should be definitive.

getTotalQuantity(variants)#

What it does
In matrix mode, sums quantity across non-deleted variants:
ignores variants where isDeleted === true
Advice
Quantity defaults to 0 when missing.

getTotalPrice(variants)#

What it does
Sums subTotalPrice across non-deleted variants.
Returns a string with 2 decimals via .toFixed(2).
Advice
This returns a string. If you later need numeric arithmetic on the result, parse it.

quickAddProductComponent()#

What it does
Returns an Alpine sub-component used by the “Quick add to cart” feature.
It provides local state:
showSearchResult, searchLoading, searchText
resultProducts
timerId (for debounce)
maxSearchResult
isAdding
and methods:
search(searchSize = 5)
triggerVoiceSearch()
isOrderable(product)
handleAddItem(product, addToCartErrorMessage, showPopUp = true)
Advice
This is a nested Alpine object pattern. It relies on being evaluated inside the parent component scope so it can call this.updateQuantity(...) and access stores/services.

search(searchSize = 5) (inside quickAddProductComponent)#

What it does
Debounces product search by 400ms.
If searchText is empty:
hides results and clears list
Otherwise:
shows results area, sets loading
after delay, builds criteria:
{ page: 1, pageSize: searchSize + 1, sort: "-SortDate", search: this.searchText }
calls:
servicesreusabledefault.findProductsByCriteriaExtended(criteria)
stores results into resultProducts
clears loading in finally
Updates maxSearchResult to at least 5.
Advice
pageSize: searchSize + 1 is typically used to detect “has more results” UI. If you don’t use it, you can simplify.
Consider trimming searchText to avoid queries for whitespace.

triggerVoiceSearch() (inside quickAddProductComponent)#

What it does
Uses Web Speech API (SpeechRecognition / webkitSpeechRecognition) to capture voice input.
If unsupported:
alerts the user
Otherwise:
starts recognition (lang = "en-US")
on result:
sets searchText to transcript
opens results area
Advice
Language is hardcoded to "en-US". If your storefront locale is Greek, you probably want "el-GR" or dynamic locale-based selection.
alert(...) is a blunt UX. If you want consistency, replace with $store.toast error message.

isOrderable(product) (inside quickAddProductComponent)#

What it does
Returns true if:
single-variant product has quantityConstraints.absolute.isValid, OR
product is multi-variant
Otherwise returns false.
Advice
It references this.isSingleVariant and this.isMultiVariant, which must be provided by the template scope (or computed elsewhere).
If they’re missing, this will always fall back to falsey behavior. Make sure those flags exist in the quick-add template.

handleAddItem(product, addToCartErrorMessage, showPopUp = true) (inside quickAddProductComponent)#

What it does
Two paths:
A) Single-variant products
Clears toasts, sets isAdding = true
Picks the first variant:
product.productVariants[0]
Calls:
servicesreusabledefault.addToCart(product.id, variant.id, variant.minimumQty, variant.finalPrice, variant.originalPrice, showPopUp)
On failure:
shows toast and stops
On success:
finds added cart item from response
calls this.updateQuantity(minimum, addedProduct, addedProduct.productVariantId)
Clears isAdding.
B) Multi-variant products
Opens product modal:
Alpine.store("productModal").open(product, "cart")
Advice
Quick add assumes variant[0] exists for single-variant items. Add a guard if backend can return empty variants.
For multi-variant items, the modal flow depends on the productModal store existing globally.

Global Alpine stores (used by Cart)#

The Cart component is store-driven and depends heavily on shared Alpine stores that live in Assets/js/theme.js.

$store.cart (cart state)#

This is the single source of truth for cart totals and items across the theme.
Defined in Assets/js/theme.js as Alpine.store("cart", ...).
Key fields:
items (array)
Current cart items.
netValueText, vatValueText, finalPriceText
Totals displayed in the UI.
count (getter)
Total quantity across all items.
Key behaviors:
loadFromStorage()
Loads cart data from localStorage.cartData.
update(cartData, addedProductsArray = [], showPopUp = false)
Updates store state and persists it back into local storage.
Can optionally show a popup with added products.
Also listens for storage events on cartData so multiple tabs stay in sync.

$store.toast (user feedback)#

Used for showing error/success feedback.
Common pattern:
Alpine.store("toast").removeAll();
Alpine.store("toast").add(message, "ic-warning", "error");

$store.modal (confirmation dialogs)#

Used to show confirmation dialogs (e.g. clear cart confirmation).
Cart opens a modal with buttons/actions, then invokes clearCart(...) if confirmed.

$store.wishlist (optional)#

Cart interacts with wishlist when wishlist is enabled by global settings (e.g. “move to wishlist” / toggle flows).

$store.loginModal (checkout gating)#

When the user is not authenticated, checkout is gated through the login modal:
sets Alpine.store("loginModal").goToCheckoutPage = true
opens the login modal so the user can continue to checkout after login

Dependencies#

Alpine.js
Alpine stores: cart, toast, modal, and optionally wishlist, loginModal
Services layer (servicesreusabledefault) for cart API calls:
clear/remove/update/quantity validation, etc.

Notes#

Cart UI is driven by $store.cart.items (loaded from local storage) and then updated by API calls and store updates.
In matrix mode, items are grouped by productId and rendered/managed as groups with variants inside.
“Ghost items” are used to support an undo-like UX after removal (items stay visible temporarily as deleted).
Quick add search and wishlist integration are feature-flagged by global settings; they may not appear in all stores.

Extras#

Template behavior (Liquid + Alpine)#

The component initializes via:
x-data="cartdefault.initComponent(variantMatrixView, updateProductCartErrorMessage, updateProductQuantityErrorMessage)"

Cart visibility#

The main cart layout is shown when:
Alpine.store('cart').items.length > 0 OR
ghostItems.length > 0 (items recently removed but still visible for undo)

Quick add product (optional)#

Quick add is shown only when a global setting is enabled:
GlobalData.Settings.EnableQuickAddToCart
This enables:
search input
results dropdown
add-to-cart buttons for results

Cart validation errors#

On first load (when there are items), the component calls cart validation:
servicesreusabledefault.getCartValidation()
If validation returns lines with errors, the UI shows grouped error blocks and enables “remove items” actions per error type.

Clear cart modal#

The template builds a JSON-like translation payload for the confirmation modal, then the JS uses the global modal store to open it.
Modified at 2026-04-14 13:18:56
Previous
Breadcrumb
Next
CategoriesList
Built with