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

ProductSingle

Purpose#

The ProductSingle component renders the product details page.
It:
renders a product gallery (main + thumbnails) and a sticky header summary on scroll
renders product title, tags, SKU/MPN (optionally), price, and add-to-cart actions
supports product actions (comparison, wishlist, shopping lists) when feature flags are enabled
supports variant selection and quantity rules (including a Matrix-style quantity table)
sends GA4 viewItem for the product

Inputs (model contract)#

Model shape (storefront example)#

{
  "showAttributes": false,
  "showAttachments": false,
  "showGalleryInline": true,
  "mainGalleryToLeft": true,
  "variantsDisplayType": "Simple",
  "productData": {
    "id": "Product Id",
    "title": "Sample product title",
    "tags": [
      "sample-tag",
      "..."
    ],
    "description": "<p>Sample description</p>",
    "link": "product/sample-product",
    "isMpnVisible": true,
    "isSkuVisible": true,
    "hasPriceRange": false,
    "maxPrice": 69.9,
    "minPrice": 69.9,
    "startPriceText": "Sample price text",
    "startPrice": 69.9,
    "vatIncluded": true,
    "inWishlist": false,
    "extraFields": [],
    "dimension1": {
      "attributeId": "Attribute Id",
      "name": "Sample text",
      "usedAsFilter": true,
      "displayOnProduct": true,
      "displayOnList": true,
      "displayOnCompare": true,
      "type": "Color",
      "items": [
        {
          "id": "Attribute Item Id",
          "value": "Sample text",
          "textColor": "#000000"
        },
        "..."
      ]
    },
    "dimension2": {
      "attributeId": "Attribute Id",
      "name": "Sample text",
      "usedAsFilter": true,
      "displayOnProduct": true,
      "displayOnList": true,
      "displayOnCompare": true,
      "type": "Size",
      "items": [
        {
          "id": "Attribute Item Id",
          "value": "S"
        },
        "..."
      ]
    },
    "attachments": [
      {
        "alt": "sample.pdf",
        "link": "https://example.com/media/sample.pdf",
        "title": "sample.pdf",
        "icon": {
          "alt": "sample.pdf",
          "link": "https://example.com/media/sample.pdf"
        }
      },
      "..."
    ],
    "attributes": [
      {
        "name": "Sample text",
        "attributeItemValue": "Sample text"
      },
      "..."
    ],
    "icoTags": [
      {
        "name": "Sample text",
        "icon": {}
      },
      "..."
    ],
    "labels": [
      {
        "name": "Sample text",
        "icon": {}
      },
      "..."
    ],
    "productVariants": [
      {
        "id": "Variant Id",
        "dimension1ItemId": "Attribute Item Id",
        "dimension2ItemId": "Attribute Item Id",
        "title": "",
        "canOrder": true,
        "startQuantity": 1,
        "price": 69.9,
        "initialPrice": 90,
        "finalPriceText": "Sample price text",
        "finalPrice": 69.9,
        "originalPriceText": "Sample price text",
        "originalPrice": 90,
        "mediaItem": {
          "id": "Media Item Id"
        },
        "stockAvailability": {
          "label": "OutOfStock"
        },
        "quantityConstraints": {
          "additive": {
            "minimum": 1,
            "maximum": 999999,
            "step": 1,
            "isValid": true
          },
          "absolute": {
            "minimum": 1,
            "maximum": 999999,
            "step": 1,
            "isValid": true
          }
        },
        "shoppingLists": []
      },
      "..."
    ],
    "mediaItems": [
      {
        "id": "Media Item Id",
        "link": "https://example.com/media/sample.jpg",
        "alt": "sample.jpg"
      },
      "..."
    ]
  },
  "groupProductsData": null,
  "name": "ProductSingle",
  "view": "Default",
  "section": "SectionA",
  "settings": {
    "additionalFields": [],
    "showAttributes": false,
    "showAttachments": false,
    "showGalleryInline": true,
    "mainGalleryToLeft": true,
    "variantsDisplayType": "Simple",
    "id": "Component Id",
    "section": "SectionA",
    "type": "NoirProductSingle",
    "name": "ProductSingle",
    "configuredInContentApi": true,
    "view": "Default",
    "displayName": "Sample text",
    "cssClass": "",
    "header": "",
    "alignment": "Left"
  },
  "translations": {
    "productImage": "Sample text",
    "viewImage": "Sample text",
    "oneSize": "Sample text",
    "...": "..."
  }
}

Required fields#

settings.id
productData
The component assumes productData is a full product view model.

Optional fields#

Flags that affect template rendering:
showGalleryInline
mainGalleryToLeft (used only when showGalleryInline == true)
showAttributes
showAttachments
variantsDisplayType (Simple or Matrix)
productData.mediaItems[]
When empty/missing, the template renders a fallback “no image” slide.
productData.tags[]
productData.isMpnVisible, productData.isSkuVisible
productData.dimension1, productData.dimension2
When present, the UI renders variant selectors / matrix table.
groupProductsData
When present (not null/empty), some sticky add-to-cart UI is suppressed.
translations.*
Many user-facing strings come from translations (only a small sample is shown above).

JavaScript#

Global object#

Components/ProductSingle/Default.js exposes:
Liquid calls it on page load:
productsingledefault.init({{ product | serialize | escape }});

init(product)#

What it does
Builds a minimal GA item object (id/title/price/initialPrice).
Sends GA4 viewItem for the product.
Key behaviors / edge cases
Price selection:
if product.hasPriceRange → uses product.startPrice
otherwise → uses product.productVariants[0].finalPrice
Advice
If you change where the “current variant price” lives, update this GA mapping.

matrixTable(product, updateProductQuantityErrorMessage)#

What it does
Returns an Alpine state object for the Matrix variant UI (quantity-per-dimension grid).
Manages quantities keyed by rendered matrix coordinates and prepares payloads for a “multiple add to cart” request.
Key behaviors / edge cases
Supports 1D and 2D matrices:
1D: only one dimension present (rows are null)
2D: both dimensions present
If Color is on dimension2, it swaps row/column axes so Color becomes rows.
Uses per-cell debounce + axios cancel tokens when checkQuantity is enabled.
Advice
The mapping between dimension items and UI axes is subtle (because of the Color swap). Be careful when changing dimension types.

getKey(rowId, columnId)#

What it does
Builds a stable string key for a matrix cell.
Key behaviors / edge cases
1D keys are just the column id.
2D keys are ${rowId}_${columnId}.

getVariantByMatrix(rowId, columnId)#

What it does
Finds the product variant object that corresponds to the provided matrix coordinates.
Key behaviors / edge cases
1D: matches either dimension1ItemId or dimension2ItemId.
2D: matches both dimension ids, respecting the Color swap rule.

init()#

What it does
Computes the matrix axes (rows, columns) based on product.dimension1 / product.dimension2.
Initializes quantities for each variant.
Initializes rowQuantities in 2D mode.

updateRowQuantities(rowId)#

What it does
Computes the total quantity across all columns for a single row.

updateVariantsToSend(rowId, columnId, quantity)#

What it does
Keeps variantsToSend in sync with the current quantities (only entries with quantity > 0).

getTotalQuantity()#

What it does
Sums all quantities values.

getTotalPrice()#

What it does
Calculates the total value based on each cell quantity * the matched variant finalPrice.
Key behaviors / edge cases
Returns a string with 2 decimals (toFixed(2)).

getVariant(rowId, columnId)#

What it does
Convenience wrapper for getVariantByMatrix(...).

debounceUpdateProduct(rowId, columnId, newQuantity, checkQuantity, delay = 800)#

What it does
Cancels any scheduled update for the same cell and schedules a new one.
Cancels the in-flight axios request for the same cell (if present).

updateQuantityMultiple(rowId, columnId, quantity, checkQuantity = false, useDebounce = true)#

What it does
Updates per-variant quantities.
When checkQuantity == true, calls servicesreusabledefault.updateProductQuantity(...) to validate/normalize requested quantities and update constraints.
Key behaviors / edge cases
Uses axios.CancelToken.source() per matrix cell to avoid races.
Clears toasts (Alpine.store("toast").removeAll()) before per-cell updates.
On API failure, shows a toast with updateProductQuantityErrorMessage.

isOutOfStock(rowId, columnId)#

What it does
Treats a variant as out of stock when its quantityConstraints.additive.isValid is false.

getQuantity(rowId, columnId)#

What it does
Reads the quantity for a given matrix cell.

handleMultiAddToCart(variants, addToCartMultipleErrorMessage)#

What it does
Sends a bulk add-to-cart request via servicesreusabledefault.addMultipleToCart(...).
On success, resets quantities based on the returned cart items’ minimum constraints.
Key behaviors / edge cases
Builds a variations payload that includes dimension text values (dim1Value, dim2Value) when dimensions exist.
Shows an error toast on failure.

increaseProductQuantityMultiple(rowId, columnId)#

What it does
Increments a cell quantity, respecting constraint:
minimum
maximum
step

decreaseProductQuantityMultiple(rowId, columnId)#

What it does
Decrements a cell quantity by step; when it drops below minimum, it becomes 0.

Global Alpine stores (used by ProductSingle)#

$store.toast (user feedback)#

The Matrix logic uses the toast store to show errors when API calls fail:
clears previous toasts before an update attempt (removeAll())
shows an error toast when updateProductQuantity(...) fails
shows an error toast when addMultipleToCart(...) fails

Services / API calls#

servicesreusabledefault.updateProductQuantity(...)
Used when Matrix mode validates a requested quantity.
servicesreusabledefault.addMultipleToCart(...)
Used to add multiple variants to cart in one request.

Dependencies#

Alpine.js
axios (for cancel tokens)
Theme utilities:
sendGAEvent(...)
Alpine stores:
toast

Notes#

The Liquid template uses productlistitemreusabledefault.initComponent(...) for the core product UI state (variant selection, quantity display, add-to-cart interactions, sticky state).
In the Liquid template you’ll see it referenced via Alpine expressions like variantOrDefault(...), canAddToCart(), handleAddToCart(), and updateStickyState().
These are not implemented in Components/ProductSingle/Default.js; they come from the Reusables\\ProductListItem JS.
productsingledefault only adds:
GA4 viewItem (init(product))
Matrix-table logic + API calls for quantity validation / multi-add (matrixTable(...))
The template reads feature flags from GlobalData.Settings (wishlist, comparison, shopping lists) to conditionally render action buttons.
The template renders product.description as raw HTML.

Extras#

Template behavior (Liquid + Alpine)#

Computes the image layout class based on showGalleryInline + mainGalleryToLeft.
Calls swiperInitSingleProductImages(id, showGalleryInline) to initialize the image gallery swipers.
Renders a sticky header summary with:
a fallback image if the product has no media
title, price, and add-to-cart button
sticky navigation buttons to sections that exist on the page (description, attributes, documentation)
Modified at 2026-04-14 13:18:56
Previous
ProductsCarousel
Next
Profile
Built with