Purpose#
GroupChildrenTable lists the children of a group product in a table: title/SKU, price, a quantity editor, child attributes and optional actions (compare, shopping lists, wishlist).Shoppers edit quantities for several children and commit them with one Update cart action. New lines are added and existing cart lines are updated or removed.Where it's rendered#
In Reusables/ProductListItem/Default.liquid, and in Reusables/ProductGridItem/Default.liquid / Reusables/ProductGridItemClassic/Default.liquid for group products:{% render 'Reusables\\GroupChildrenTable\\Default', product: product, uniqueKey: groupChildrenKey %}
product: the group product. The template reads product.groupChildren and product.groupChildAttributes.
uniqueKey: key of the parent card.
Global settings read: ShowPrices, ShowAddToCart, ShowProductComparison, EnableShoppingLists, EnableWishlist. The actions column renders when at least one of the last three is on.Model shape (storefront example)#
{
"product": {
"id": "Product Id",
"groupChildAttributes": [
{ "title": "Sample name" }
],
"groupChildren": [
{
"id": "Product Id",
"alias": "sample-alias",
"title": "Sample title",
"sku": "Sample SKU",
"isSkuVisible": true,
"pricePending": true,
"productVariants": [
{
"id": "Variant Id",
"finalPrice": 10,
"originalPrice": 12,
"finalPriceText": "10,00 €",
"originalPriceText": "12,00 €"
}
]
}
]
},
"uniqueKey": "Component Id",
"translations": {
"title": "Sample translation",
"price": "Sample translation",
"quantity": "Sample translation",
"...": "..."
}
}
Template behavior (Liquid + Alpine)#
Renders nothing when product.groupChildren is empty.
Desktop: a horizontally scrollable table with a sticky title column and zebra rows. A separate mobile layout is rendered below lg.
Each child is addressed by its primary variant id (productVariants[0].id).
Quantities start from the cart (Alpine.store("cart")) and are edited locally until Update cart is pressed. The button is disabled while nothing changed, a row is invalid, or an update is running.
Invalid rows (below minimum, above maximum, or off-step) are flagged with InvalidQuantity / FixInvalidRows.
Data contract (JS runtime)#
children is the serialized product.groupChildren array. Each child must expose id and productVariants[0] (id, finalPrice, originalPrice, finalPriceText, originalPriceText); quantityConstraints is added by hydration.JavaScript#
Global object#
Global object: groupchildrentablereusabledefaultx-data='groupchildrentablereusabledefault.initComponent({{ children | serialize | escape }}, {{ Reusables.GroupChildrenTable.Translations.UpdateCartErrorMessage | serialize | escape }}, {{ showAddToCart | default: false }})'
initComponent#
initComponent(children, updateErrorMessage, showAddToCart = true) returns the Alpine state: children, edits (variant id → pending quantity), isUpdating, groupChildrenMaxRequest (200), showAddToCart and pricesPending.
init#
Seeds permissive default constraints on every child (servicesreusabledefault._applyDefaultConstraints) and starts hydrate().
hydrate#
Hydrates every child in parallel, then reassigns children so Alpine recomputes and clears pricesPending.
hydrateChild#
Hydrates constraints (only when showAddToCart) via the services reusable's hydrateProductConstraints(child), and prices (only when child.pricePending) via its hydrateProductPrices(child). Failures are logged and do not block other children.
chunked#
chunked(items, size) splits items into arrays of at most groupChildrenMaxRequest entries.
child#
child(variantId) returns the child whose primary variant id matches.
variantId#
variantId(child) returns the child's primary variant id.
variantOf#
variantOf(variantId) returns the child's primary variant.
finalPriceTextOf#
Returns the variant's finalPriceText, or "".
originalPriceTextOf#
Returns the variant's originalPriceText, or "".
showsOriginalPrice#
Returns true when originalPrice is greater than finalPrice.
constraints#
constraints(variantId) returns the absolute constraints when the variant is already in the cart, otherwise the additive ones; falls back to { minimum: 1, maximum: null, step: 1, isValid: true }.
cartItems#
Returns Alpine.store("cart").items, or [].
cartQuantityOf#
Returns the cart quantity of the variant, or 0.
syncFromCart#
Clears edits, so every row shows the cart quantity again.
inCart#
Returns true when the variant has a cart quantity above zero.
quantityOf#
Returns the pending edit, else the cart quantity, else 0.
setQuantity#
setQuantity(variantId, value) stores a normalized positive number as the pending edit, or 0 for invalid input.
normalize#
Rounds a quantity to 6 decimals to absorb floating-point error.
increase#
Starts at the minimum (or step) from zero, otherwise adds one step, capped at the maximum.
decrease#
Subtracts one step; a result below the minimum becomes 0 (removal).
isRowInvalid#
Returns true for a non-zero quantity that is not valid, below the minimum, above the maximum, or not on a step.
isOnStep#
isOnStep(quantity, minimum, step) returns true when the quantity is reachable from the minimum in whole steps (tolerance 1e-6 for decimal steps).
hasChanged#
Returns true when the row quantity differs from the cart quantity.
changedIds#
Getter that returns the edited variant ids whose quantity differs from the cart.
hasInvalidRow#
Getter that returns true when any edited row is invalid.
canUpdate#
Getter that returns true when not updating, something changed and no row is invalid.
update#
Commits the changes: adds new lines (addNewLines), then updates existing ones (applyEdits), then syncFromCart(). Stops at the first failure and always resets isUpdating.
addNewLines#
Adds variants that are not yet in the cart via servicesreusabledefault.addMultipleToCart, in chunks of groupChildrenMaxRequest. Returns false on the first failed chunk.
applyEdits#
Reads cartData from localStorage, applies the new quantities, drops lines set to 0, and sends the cart through servicesreusabledefault.updateProductCart.
succeeded#
Returns true for a 2xx response without success: false, lineFailures or cartErrors. On failure it shows the updateErrorMessage toast and calls syncFromCart().
Global Alpine stores#
Alpine.store("cart"): current cart lines.
Alpine.store("toast"): update error feedback.
Services / API calls#
Hydration: hydrateProductConstraints, hydrateProductPrices
servicesreusabledefault.addMultipleToCart
servicesreusabledefault.updateProductCart
Dependencies#
Reusables/Services (servicesreusabledefault)
localStorage key cartData (used by applyEdits)
Notes#
The page-level hydration batches split oversized requests themselves, so every child can be queued in the same tick.
The server revalidates quantities on every cart call.
Modified at 2026-10-06 10:11:16