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