Skip to content

Product Controller ​

The product controller manages the product detail page - variant selection, gallery interaction, pricing updates, and add-to-cart functionality.

Source: src/js/controllers/product-controller.js (~800 lines)

Targets ​

TargetElementPurpose
pricePrice displayUpdated when variant changes
addButtonAdd to cart buttonDisabled when out of stock
qtyQuantity inputQuantity selector
galleryImage gallery containerGallery image swapping
mainImageMain product imageUpdated on variant/swatch selection
thumbnailGallery thumbnailsActive state management
skuSKU displayUpdated on variant selection
stockStock indicatorIn/out of stock messaging
optionSelectOption dropdowns/swatchesConfigurable product options

Values ​

ValueTypeDescription
variantsString (JSON)Configurable product variants array
skuStringBase product SKU
typeStringProduct type (simple, configurable, grouped, bundle, downloadable, virtual, giftcard)
productIdNumberProduct entity ID
mediaGalleryString (JSON)Gallery image URLs

Actions ​

ActionTriggerBehavior
addToCartClick add buttonPOST to cart API, open cart drawer
selectOptionChange option select/click swatchFilter available variants, update price/image
updateQtyClick +/- or input changeUpdate quantity value
selectThumbnailClick thumbnailSwitch main gallery image
zoomImageClick main imageOpen full-size image overlay

Variant Selection Flow ​

Configurable Product Handling ​

For configurable products, the controller:

  1. Parses the variants value (JSON array of all variant combinations)
  2. On each option change, filters to find the matching variant
  3. Updates price (variant may have a different price than the parent)
  4. Swaps the gallery image if the variant has a unique image
  5. Checks stock quantity for the selected variant
javascript
// Variant data structure
{
  id: 123,
  sku: "PROD-RED-M",
  price: 49.95,
  finalPrice: 39.95,
  stockQty: 5,
  attributes: { color: "Red", size: "M" },
  imageUrl: "/media/catalog/product/red-variant.jpg"
}
  • Thumbnail click → swaps main image with smooth transition
  • Main image click → opens zoom overlay (pinch-zoom on mobile)
  • Variant selection → auto-scrolls to the variant's image in the gallery
  • Keyboard navigation → arrow keys cycle through gallery images

Add to Cart ​

The add-to-cart flow:

  1. Validates all required options are selected
  2. Ensures quantity > 0 and within stock limits
  3. Builds a type-specific request body via a per-type builder (see below)
  4. POSTs to /api/guest-carts/{maskedId}/items (guest) or the authenticated cart
  5. On success: dispatches cart:updated custom event, opens cart drawer
  6. On error: displays inline error message

Per-type body builders ​

add() dispatches to a table of private builders keyed by typeValue. Each builder reads its inputs from the DOM (data-* attributes wired by the ProductOptions components — see product-display components), validates required fields, and mutates the outgoing body in place.

TypeBuilderFields set on body
configurable_buildConfigurableBodysku (resolved child variant SKU)
grouped_buildGroupedBodysku, superGroup (childId → qty)
bundle_buildBundleBodysku, bundleOption, bundleOptionQty
downloadable_buildDownloadableBodysku, links
giftcard_buildGiftcardBodysku, giftcardAmount, giftcardSenderName/Email, giftcardRecipientName/Email, optional giftcardMessage + giftcardDeliveryDate
simple / virtual(default)sku

Two shared helpers run for every type after the type-specific builder:

  • _appendCustomOptions(body) — reads [data-custom-option-id] inputs, sets body.options = { optionId: valueId }.
  • _appendOptionFiles(body) — reads [data-custom-option-file-id] inputs, base64-encodes each file, sets body.options_files.

All keys are camelCase on the wire — the API-Platform DTO (Mage_Checkout_Api_CartProcessor::addItemToCart) converts them to snake_case for Maho's internal buy request. Sending bundle_option etc. from the client is silently dropped.

Source: src/js/controllers/product-controller.js