viewItem for the product{
"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",
"...": "..."
}
}settings.idproductDataproductData is a full product view model.showGalleryInlinemainGalleryToLeft (used only when showGalleryInline == true)showAttributesshowAttachmentsvariantsDisplayType (Simple or Matrix)productData.mediaItems[]productData.tags[]productData.isMpnVisible, productData.isSkuVisibleproductData.dimension1, productData.dimension2groupProductsDatatranslations.*Components/ProductSingle/Default.js exposes:productsingledefault.init({{ product | serialize | escape }});init(product)viewItem for the product.product.hasPriceRange → uses product.startPriceproduct.productVariants[0].finalPricematrixTable(product, updateProductQuantityErrorMessage)null)checkQuantity is enabled.getKey(rowId, columnId)${rowId}_${columnId}.getVariantByMatrix(rowId, columnId)dimension1ItemId or dimension2ItemId.init()rows, columns) based on product.dimension1 / product.dimension2.quantities for each variant.rowQuantities in 2D mode.updateRowQuantities(rowId)updateVariantsToSend(rowId, columnId, quantity)variantsToSend in sync with the current quantities (only entries with quantity > 0).getTotalQuantity()quantities values.getTotalPrice()finalPrice.toFixed(2)).getVariant(rowId, columnId)getVariantByMatrix(...).debounceUpdateProduct(rowId, columnId, newQuantity, checkQuantity, delay = 800)updateQuantityMultiple(rowId, columnId, quantity, checkQuantity = false, useDebounce = true)checkQuantity == true, calls servicesreusabledefault.updateProductQuantity(...) to validate/normalize requested quantities and update constraints.axios.CancelToken.source() per matrix cell to avoid races.Alpine.store("toast").removeAll()) before per-cell updates.updateProductQuantityErrorMessage.isOutOfStock(rowId, columnId)quantityConstraints.additive.isValid is false.getQuantity(rowId, columnId)handleMultiAddToCart(variants, addToCartMultipleErrorMessage)servicesreusabledefault.addMultipleToCart(...).variations payload that includes dimension text values (dim1Value, dim2Value) when dimensions exist.increaseProductQuantityMultiple(rowId, columnId)minimummaximumstepdecreaseProductQuantityMultiple(rowId, columnId)$store.toast (user feedback)removeAll())updateProductQuantity(...) failsaddMultipleToCart(...) failsservicesreusabledefault.updateProductQuantity(...)servicesreusabledefault.addMultipleToCart(...)sendGAEvent(...)toastproductlistitemreusabledefault.initComponent(...) for the core product UI state (variant selection, quantity display, add-to-cart interactions, sticky state).variantOrDefault(...), canAddToCart(), handleAddToCart(), and updateStickyState().Components/ProductSingle/Default.js; they come from the Reusables\\ProductListItem JS.productsingledefault only adds:viewItem (init(product))matrixTable(...))GlobalData.Settings (wishlist, comparison, shopping lists) to conditionally render action buttons.product.description as raw HTML.showGalleryInline + mainGalleryToLeft.swiperInitSingleProductImages(id, showGalleryInline) to initialize the image gallery swipers.