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

ProductGridItem

Purpose#

The ProductGridItem reusable renders a product card for grid/carousel listings (category/search results, related products, carousels). It supports:
product image + brand presentation
product labels (ico tags)
quick actions (comparison, wishlist, shopping lists)
variant dimension selection (color/other)
add-to-cart/quantity interaction via JS
GA select-item tracking

Where it's rendered#

This reusable is rendered from multiple listing components, including:
Components/FilterList/Default.liquid
Components/ProductsBlock/Default.liquid
Components/ProductsCarousel/Default.liquid
Components/RelatedProducts/Default.liquid
Components/Wishlist/Default.liquid
Components/LastVisitedProducts/Default.liquid
Components/ProductComparison/Default.liquid
Typical render call:
{% render 'Reusables\\ProductGridItem\\Default', product: product, modelId: id, colorExists: colorExists, listName: listName, position: forloop.index0 %}

Inputs (Liquid render parameters)#

Named parameters:
product
The product object shown in the card.
modelId
Used to build a unique key (modelId-product.id) for DOM scoping.
colorExists
A precomputed boolean used by parent components to indicate whether any product has a color dimension (used for layout decisions in some parents).
listName
Used for GA list tracking (select-item events).
position
Used for GA list tracking (select-item events).

Template behavior (Liquid + Alpine)#

High-level rendering:
Builds productImage from product.mediaItems[0] when available.
Renders a wrapper card that:
is keyboard focusable
redirects on click / Enter / Space to /product/{{ product.alias }}
sends GA select-item events
initializes Alpine via:
x-data='productgriditemreusabledefault.initComponent({{ product | serialize | escape }}, "{{ Reusables.ProductGridItem.Translations.AddToCartErrorMessage }}", "{{ Reusables.ProductGridItem.Translations.UpdateProductQuantityErrorMessage }}")'
Quick actions:
Comparison: Reusables/ProductComparisonButton
Wishlist: Reusables/WishlistButton
Shopping lists: Reusables/ShoppingListsButton
Each is gated by feature flags:
GlobalData.Settings.ShowProductComparison
GlobalData.Settings.EnableWishlist
GlobalData.Settings.EnableShoppingLists
Brand:
If product.brand.* exists, the card renders a brand badge (image when available, otherwise text).
Category + title:
Uses product.categoryName / product.categoryLink (when present).
Uses product.title and links to /product/{{ product.alias }}.
SKU/MPN and variants:
SKU/MPN blocks are shown when product.isMpnVisible and/or product.isSkuVisible.
Variant picker UI appears when product.productVariants.size > 1.
Dimension UI is driven by product.dimension1 / product.dimension2 (and their .type, .items).
Image fallback:
If there is no image link it falls back to GlobalData.Settings.noImage.link or /Assets/images/no-image.svg.

Data contract (JS runtime)#

Model shape (storefront example)#

Example (from a real product list response) keeping only the relevant fields used by this reusable:
{
  "id": "Product Id",
  "title": "Sample title",
  "alias": "sample",
  "categoryName": "Sample text",
  "categoryLink": "/category/sample",
  "isMpnVisible": true,
  "isSkuVisible": true,
  "mpn": "Sample text",
  "sku": "Sample text",
  "mediaItems": [
    {
      "id": "Media Id",
      "link": "https://example.com/sample",
      "position": 0,
      "alt": "Sample text",
      "mediaType": "Image"
    },
    ...
  ],
  "icoTags": [
    {
      "id": "Tag Id",
      "name": "Sample text",
      "backgroundColor": "Sample text",
      "textColor": "Sample text",
      "icon": { "link": "https://example.com/sample" }
    },
    ...
  ],
  "brand": {
    "name": "Sample text",
    "link": "/brand/sample",
    "image": { "link": "https://example.com/sample", "alt": "Sample text" }
  },
  "dimension1": {
    "type": "Color",
    "name": "Sample text",
    "items": [ { "id": "Dim Item Id", "value": "Sample text", "textColor": "Sample text" }, ... ]
  },
  "dimension2": {
    "type": "Size",
    "name": "Sample text",
    "items": [ { "id": "Dim Item Id", "value": "Sample text" }, ... ]
  },
  "productVariants": [
    {
      "id": "Variant Id",
      "dimension1ItemId": "Dim Item Id",
      "dimension2ItemId": "Dim Item Id",
      "finalPrice": 25,
      "finalPriceText": "25,00 €",
      "originalPrice": 35,
      "originalPriceText": "35,00 €",
      "canOrder": true,
      "sellOutOfStock": false,
      "quantity": 2,
      "minOrderQuantity": 1,
      "orderQuantityStep": 1
    },
    ...
  ],
  "startPrice": 25,
  "startPriceText": "25,00 €",
  "hasPriceRange": false,
  "inWishlist": false
}

Fields consumed directly by Liquid#

The Liquid template reads (non-exhaustive, but direct reads visible in Default.liquid):
product.id, product.title, product.alias
product.categoryName, product.categoryLink
product.mediaItems[0].link, product.mediaItems[0].alt
product.icoTags[] (label rendering)
product.brand.name, product.brand.link, product.brand.image.link, product.brand.image.alt
product.isMpnVisible, product.mpn
product.isSkuVisible, product.sku
product.productVariants (size, iteration, JS state)
product.dimension1, product.dimension2 and nested .type, .name, .items[]

JavaScript#

Global object#

Global object: productgriditemreusabledefault
Liquid wiring:
x-data="productgriditemreusabledefault.initComponent(product, addToCartErrorMessage, updateProductQuantityErrorMessage)"

initSwiper(refs)#

What it does
Creates a Swiper instance for dimension pickers (used by the color dimension UI).

initComponent(product, addToCartErrorMessage, updateProductQuantityErrorMessage, isProductSection = false)#

What it does
Initializes local Alpine state for:
selected dimensions and selected variant
responsive behavior
quantity state and debounced update behavior
Sends GA select-item events via sendSelectEvent(...) and sendSelectEventAndRedirect(...).
Key behaviors / edge cases
If there are no dimensions, it selects the first variant.
If a dimension has only one item, it auto-selects it.
Advice
This reusable serializes the full product object into the DOM ({{ product | serialize | escape }}). Keep the product payload reasonably sized.

init() (returned by initComponent)#

What it does
Initializes runtime state and event listeners.
Detects which dimension is Color (dimension1 or dimension2) and sets colorDimKey / otherDimKey.
Auto-selects dimension items when a dimension has exactly one option.
If the product has no dimensions, preselects the first variant and calls updateSelectedVariant().
Wires click handling for middle-click / Ctrl-click selection tracking via handleClickEvents().
When isProductSection is true, also initializes sticky/scroll helpers for product single sections.
Key behaviors / edge cases
Relies on window.innerWidth < 1440 to determine mobile behavior.

getTitleCatHeight()#

What it does
Measures .product-card__title-cat height and sets the CSS variable --heightTitleCat.

sendSelectEvent(product, listName, position)#

What it does
Sends GA selectItem for the clicked product.

sendSelectEventAndRedirect(product, listName, position)#

What it does
Sends GA selectItem and then navigates to /product/${product.alias}.

handleClickEvents()#

What it does
Adds a mousedown handler to the card.
If the user middle-clicks or Ctrl/Meta-clicks, it:
computes the GA position based on the closest list container ([data-product-list-double] or [data-product-list])
sends GA selectItem
opens the product in a new tab

setSelectedColor(id, label)#

What it does
Sets the selected Color dimension ids/labels (selectedDimColor* and the corresponding selectedDim1* / selectedDim2*).
Calls updateSelectedVariant().

setSelectedOther(id, label)#

What it does
Sets the selected non-color dimension ids/labels (selectedDimOther* and the corresponding selectedDim1* / selectedDim2*).
Calls updateSelectedVariant().

setDimension(key, id, label)#

What it does
Generic setter for selectedDim1* / selectedDim2*.
Calls updateSelectedVariant().

updateSelectedVariant()#

What it does
Finds the variant matching the currently selected dimension ids.
Sets selectedVariant and selectedVariantIndex.
Updates quantity state by calling updateQuantity(...).
When there are multiple variants and all required dimension selections are done, it also updates the active image via getActiveImage(...).
Key behaviors / edge cases
Uses quantity constraints (quantityConstraints.additive.minimum) as the default quantity when a variant is selected.

selectedVariant (getter)#

What it does
Computes the selected variant from dimension selections.
Returns null when not enough dimensions are selected.

isSingleVariant / isMultiVariant (getters)#

What they do
Convenience flags based on product.productVariants.length.

shouldShowAddToCartButton (getter)#

What it does
On desktop: always true.
On mobile: shows the add-to-cart button only for single-variant products.

showMobileModalButton (getter)#

What it does
On mobile + multi-variant products: shows a modal trigger button.

handleAddToCart()#

What it does
Guards against duplicate attempts (isAdding) and checks canAddToCart().
Calls servicesreusabledefault.addToCart(productId, variantId, quantity, finalPrice, originalPrice).
On failure: shows $store.toast with addToCartErrorMessage.
On success:
finds the added cart item in the response
updates variant.quantityConstraints
refreshes quantity by calling updateQuantity(...)

hasMultipleVariants (getter)#

What it does
Returns true when (product.productVariants || []).length > 1.

allDimensionsSelected (getter)#

What it does
Returns whether the required dimension selections are complete.

displayStartPrice / displayFinalPrice / displayOriginalPrice (getters)#

What they do
displayStartPrice: used when the product has a price range and dimensions are not fully selected.
displayFinalPrice: returns the selected variant price text, or a fallback price text.
displayOriginalPrice: returns the selected variant original price text, or a fallback.

isOtherOptionOrderable(otherId)#

What it does
For 2-dimension products, checks whether a given "other" dimension value is orderable with the currently selected color.

isSingleOptionOrderable(dimId, isDim1)#

What it does
Determines whether a dimension option is orderable by checking whether any matching variant has quantityConstraints.additive.isValid.

canAddToCart()#

What it does
With no dimensions: returns true if any variant is orderable.
With dimensions: returns true only when selectedVariant.quantityConstraints.additive.isValid.

variantOrDefault(path, displayNullLine = false)#

What it does
Reads a nested value from the selected variant first, then falls back to the product.
Updates customColorActive when stockAvailability.label === 'Custom' and a color exists.
Key behaviors / edge cases
Returns '-' when displayNullLine is true and the value is missing.

getDynamicLabelClass(el)#

What it does
Computes a CSS class for availability labels based on variantOrDefault('stockAvailability.label').
Removes an initial Liquid-injected class once if the label changes.

swiperInitSingleProductImages(id, isSwiperInline)#

What it does
Initializes Swiper carousels for product single image galleries.
Integrates LightGallery for zoom/fullscreen.
Computes the initial slide based on the variant-linked media item.

getActiveImage(mediaItemId)#

What it does
Locates the image matching a media item id and slides the main carousel to it.

updateHeaderHeight() / updateStickyElementTop() / updateStickyState()#

What they do
Helpers to keep a product-single sticky element aligned under the header.
Used only when isProductSection === true.

scrollToTop(selector)#

What it does
Scrolls smoothly to a section and updates activeButton.

updateActiveButtonOnScroll()#

What it does
Tracks which product-single section is currently active and updates activeButton.

increaseQuantity(groupVariant = {}) / decreaseQuantity(groupVariant = {})#

What they do
Adjust the internal quantity using quantityConstraints.additive.step and min/max.

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

What it does
Debounces calls to updateQuantity(...) per product id.
Cancels in-flight quantity-check requests via Axios cancel tokens.

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

What it does
When checkQuantity is true, calls servicesreusabledefault.updateProductQuantity(...) to validate/normalize quantity server-side.
When checkQuantity is false, it updates this.quantity directly.
Key behaviors / edge cases
Uses $store.toast for error feedback when the API call fails.

Global Alpine stores#

This reusable uses (indirectly via nested reusables and functions):
$store.toast (used by related actions like add-to-cart errors)

Services / API calls#

This reusable’s JS uses servicesreusabledefault for cart/quantity-related operations (see Reusables/ProductGridItem/Default.js).

Dependencies#

Liquid: Reusables/ProductGridItem/Default.liquid
JS: Reusables/ProductGridItem/Default.js
Translations: Reusables/ProductGridItem/Default.json
Nested reusables:
Reusables/ProductComparisonButton/Default
Reusables/WishlistButton/Default
Reusables/ShoppingListsButton/Default
Vendor JS: Swiper (dimension carousels)

Notes#

The card navigates to /product/{{ product.alias }} on click and on Enter/Space when focused.
The template assumes product.mediaItems[0] is an image; in real data, the first item should be an Image mediaType for best results.
Some product fields are used only by JS (variant selection, cart interaction). Keep Liquid and JS contracts in sync.
Modified at 2026-04-14 13:18:56
Previous
ProductComparisonFloatingButton
Next
ProductListItem
Built with