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

ProductComparison

Purpose#

The ProductComparison component renders a product comparison page.
It:
renders only when the feature flag GlobalData.Settings.ShowProductComparison is enabled
renders a Swiper carousel of compared products (one slide per product)
renders a second, linked Swiper for attribute values (so both carousels move together)
supports clearing the comparison list via a confirmation modal
warns the user when the list changes in another tab (cross-tab sync)

Inputs (model contract)#

Model shape (storefront example)#

{
  "products": [
    {
      "vatIncluded": true,
      "vatRate": 24,
      "isMpnVisible": true,
      "isSkuVisible": true,
      "title": "Sample product title",
      "subTitle": "Sample text",
      "description": "<p>Sample description</p>",
      "alias": "sample-product-alias",
      "status": "Active",
      "categoryId": "Category Id",
      "categoryName": "Sample category name",
      "categoryLink": "/category/sample",
      "tags": [
        "sample-tag",
        "..."
      ],
      "pathCategories": [
        "Path Category Id",
        "..."
      ],
      "attributeSetId": "Attribute Set Id",
      "id": "Product Id",
      "companyId": "Company Id",
      "link": "product/sample-product",
      "mediaItems": [
        {
          "id": "Media Item Id",
          "link": "https://example.com/media/sample.jpg",
          "position": 0,
          "alt": "sample.jpg",
          "mediaType": "Image"
        },
        "..."
      ],
      "productVariants": [
        {
          "quantityConstraints": {
            "additive": {
              "minimum": 1,
              "maximum": 999999,
              "step": 1,
              "isValid": true
            },
            "absolute": {
              "minimum": 1,
              "maximum": 999999,
              "step": 1,
              "isValid": true
            }
          },
          "id": "Variant Id",
          "mediaItem": {},
          "title": "Sample text",
          "mpn": "Sample text",
          "sku": "Sample text",
          "unitPrice": 70,
          "price": 70,
          "quantity": 0,
          "retail": {
            "price": 70,
            "unitId": "(UNDEFINED)",
            "unitPrice": 70
          },
          "dimension1ItemId": "Attribute Item Id",
          "dimension2ItemId": "Attribute Item Id",
          "sellOutOfStock": true,
          "requiresShipping": false,
          "translation": {},
          "canOrder": true,
          "bundleItems": [],
          "shoppingLists": [],
          "startQuantity": 1,
          "finalPriceText": "Sample price text",
          "finalPrice": 70
        },
        "..."
      ],
      "attributes": [
        {
          "attributeId": "Attribute Id",
          "attributeItemId": "Attribute Item Id",
          "attributeItemValue": "Sample text",
          "name": "Sample text",
          "slug": "sample-slug",
          "usedAsFilter": false,
          "displayOnProduct": true,
          "displayOnList": true,
          "displayOnCompare": true
        },
        "..."
      ],
      "attributeSet": {
        "id": "Attribute Set Id",
        "companyId": "Company Id",
        "title": "Sample text",
        "description": "Sample text",
        "groups": [
          {
            "title": "Sample text",
            "id": "Attribute Group Id",
            "position": 0,
            "items": [
              {
                "attributeId": "Attribute Id",
                "name": "Sample text",
                "value": "Sample text",
                "code": "sample-code",
                "usedAsFilter": false,
                "displayOnProduct": true,
                "displayOnList": true,
                "displayOnCompare": true,
                "slug": "sample-slug",
                "position": 0
              },
              "..."
            ]
          },
          "..."
        ],
        "updateDate": "2025-01-01T00:00:00.0000000+00:00",
        "insertDate": "2025-01-01T00:00:00.0000000+00:00"
      }
    },
    "..."
  ],
  "name": "ProductComparison",
  "view": "Default",
  "section": "SectionA",
  "settings": {
    "id": "Component Id",
    "section": "SectionA",
    "type": "NoirProductComparison",
    "name": "ProductComparison",
    "configuredInContentApi": true,
    "view": "Default",
    "displayName": "",
    "cssClass": ""
  },
  "translations": {
    "compare": "Sample text",
    "clearList": "Sample text",
    "modalTitleText": "Sample text",
    "...": "..."
  }
}

Required fields#

settings.id

Optional fields#

settings.cssClass
products[]
When empty/missing, the component renders an empty state.
translations.*
All user-facing strings.

JavaScript#

Global object#

Components/ProductComparison/Default.js defines a global singleton + Alpine factory:
Liquid binds it using:
<section
  x-data='productcomparisondefault.initComponent("{{ id }}", {{ productsCount }}, {{ products | serialize | escape }}, "{{ model.Translations.Compare }}")'
>

initComponent(id, productsCount, products, headerTitle)#

What it does
Creates and returns the Alpine state object.
Carries both rendering state (removed ids, sticky state, swipers) and sync state (list + reload warning).
Key behaviors / edge cases
headerHeight is read from the <header> element; if layout changes, sticky offsets may be wrong.
slidesPerView depends on viewport width (>= 1440 → 4 else 2).
Advice
Keep selectors stable:
#carousel-sticky
#product-comparison-list-<id>
#product-comparison-attribute-list-<id>
.attribute-value + data-attribute

init()#

What it does
1.
Syncs the comparison list from cookie.
2.
Sends GA4 viewItemList.
3.
Registers listeners:
storage (cross-tab sync)
comparisonListUpdated (same-tab updates)
resize (uniform heights)
Key behaviors / edge cases
Uses lastHandledTimestamp to avoid processing the same update multiple times.
Sets showReloadWarning = true when it detects changes from another tab.
Advice
The Liquid template sets window.productcomparisondefaultInstance = $data in x-init. The resize listener relies on this.

syncListFromGlobal(newList)#

What it does
Keeps the singleton (productcomparisondefault.list) and Alpine state (this.list) aligned.

initStickyObserver()#

What it does
Measures the initial sticky carousel top position and sets inline top based on headerHeight.

checkSticky()#

What it does
Updates isSticky based on scroll position and container bounds.
Updates inline top to match current header height.

reloadPage()#

What it does
Reloads the page (location.reload()).

setUniformHeightsPerAttribute()#

What it does
Groups .attribute-value elements by data-attribute.
Applies the max height per group so rows align across product columns.

swiperInit()#

What it does
Initializes 2 Swipers and links them via controller.
Key behaviors / edge cases
Requires Swiper to be loaded globally.

clearList()#

What it does
Clears the persisted list via productcomparisondefault.setList([]).

handleClearModal(modalTranslations)#

What it does
Opens a confirmation modal via Alpine.store("modal").open(...).

getList()#

What it does
Reads cookie comparisonData and parses JSON.

setList(list)#

What it does
1.
Persists list in cookie (comparisonData).
2.
Stores comparisonSync in localStorage with a timestamp (cross-tab).
3.
Dispatches comparisonListUpdated (same-tab).

Dependencies#

Alpine.js
Alpine stores:
modal
Swiper
Reusables\\ProductGridItem\\Default
Theme utilities:
prepareListProducts(...)
sendGAEvent(...)
getCookie(...), setCookie(...)

Notes#

Feature-flagged by GlobalData.Settings.ShowProductComparison.
When products is empty, an empty state is rendered.

Extras#

Template behavior (Liquid + Alpine)#

Builds a unique attribute name list by scanning product.attributes.
Renders two Swipers (products + attributes).
Uses removedIds to hide removed product slides.

Required fields#

settings.id
products[] (the page can render an empty-state when missing/empty)

Optional fields#

settings.cssClass
translations.* (all user-facing strings)

JavaScript#

Global object#

Components/ProductComparison/Default.js exposes a factory:
The Liquid template binds it via Alpine:
<section
  x-data='productcomparisondefault.initComponent("{{ id }}", {{ productsCount }}, {{ products | serialize | escape }}, "{{ model.Translations.Compare }}")'
>
Key behaviors:
Sends GA4 viewItemList for the comparison list.
Initializes two linked Swipers (products + attributes).
Cross-tab sync using storage events and comparisonListUpdated.
"Sticky" behavior for the carousel on scroll.
Clears list via Alpine.store("modal").open(...) confirmation.

Dependencies#

Alpine.js
Swiper
Reusables\\ProductGridItem\\Default
Theme-wide analytics helpers:
prepareListProducts(...)
sendGAEvent(...)

Notes#

The component is feature-flagged by GlobalData.Settings.ShowProductComparison.
If products is empty, an empty state is rendered (with navigation back to /).

Extras#

Template behavior (Liquid + Alpine)#

Builds a unique list of attribute names by scanning product.attributes across all products.
Renders:
a Swiper with one slide per product
a second Swiper with attribute values per product
Uses a removedIds array to hide removed product slides.
Modified at 2026-04-14 13:18:56
Previous
ProductAttributes
Next
ProductDocumentation
Built with