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
    • 6. FAQ
    • 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
      • Quote
      • Register
      • RelatedProducts
      • SingleBlog
      • Stores
      • TextWithImage
      • ThankYouPage
      • TopBar
      • Wishlist
    • 3. Reusables
      • _Overview
      • Addresses
      • AddressForm
      • AnnouncementModal
      • BackToTop
      • BillingRetail
      • Company
      • General
      • Login
      • LoginModal
      • MonthlyTransactions
      • Orders
      • Payment
      • ProductAttachments
      • ProductAttributes
      • ProductComparisonButton
      • ProductComparisonFloatingButton
      • ProductGridItem
      • ProductListItem
      • ProductModal
      • ProfileInfo
      • PromptModal
      • Quotes
      • Register
      • Services
      • Shipping
      • ShoppingLists
      • ShoppingListsButton
      • ShoppingListsNavbar
      • Toast
      • Transactions
      • Users
      • VariantContent
      • WishlistButton
    • 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).
The component settings also include:
model.Settings.CssClass
model.Settings.VariantsDisplayType

JavaScript#

Global object#

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

init#

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
Notes:
If you change where the “current variant price” lives, update this GA mapping.

matrixTable#

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.
Notes:
The mapping between dimension items and UI axes is subtle (because of the Color swap). Be careful when changing dimension types.

getKey#

Builds a stable string key for a matrix cell.
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#

Finds the product variant object that corresponds to the provided matrix coordinates.
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#

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

updateRowQuantities#

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

updateVariantsToSend#

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

getTotalQuantity#

Sums all quantities values.
Sums all quantities values.

getTotalPrice#

Calculates the total value based on each cell quantity * the matched variant finalPrice.
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#

Convenience wrapper for getVariantByMatrix(...).
Convenience wrapper for getVariantByMatrix(...).

debounceUpdateProduct#

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).
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#

Updates per-variant quantities.
When checkQuantity == true, calls servicesreusabledefault.updateProductQuantity(...) to validate/normalize requested quantities and update constraints.
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#

Returns whether the cell variant is out of stock (based on variant availability / stock flags).
Treats a variant as out of stock when its quantityConstraints.additive.isValid is false.

getQuantity#

Returns the quantity for a given matrix cell.
Reads the quantity for a given matrix cell.

handleMultiAddToCart#

Sends a “multiple add to cart” request for the selected variants.
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#

Increases quantity for the given cell while enforcing step/min/max rules.
Increments a cell quantity, respecting constraint:
minimum
maximum
step

decreaseProductQuantityMultiple#

Decreases quantity for the given cell while enforcing min rules.
Decrements a cell quantity by step; when it drops below minimum, it becomes 0.

Global Alpine stores (used by ProductSingle)#

Alpine.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-05-12 07:41:40
Previous
ProductsCarousel
Next
Profile
Built with