Developer forum

Forum » Dynamicweb 10 » Handling Bundle/BOM products with variants

Handling Bundle/BOM products with variants

Martyna Gradzka
Reply

Hello,

I'm trying to figure out how to create and purchase a product bundle where two of the bundled products have variants (color and size). DW10, Swift2. 

From what I can see, there are two ways to add items to a bundle:

1. Adding a Group

If I add a group that contains a single product, I can see the variant dropdowns on the bundle detail page and select the desired options.

However, after clicking "Add to Cart" and navigating to the cart, the products included in the bundle do not show the selected variants. Instead, it appears that the cart displays the master products, which is not correct in this scenario.

 

2. Adding a Product

If I add the master product directly to the bundle, I don't see any variant picker on the bundle detail page or in the cart.

I also tried adding a custom variant picker to the template. This seemed to work correctly on the PDP, but once I added the bundle to the cart, the selected variant information was lost.

 

Did anyone have a similar situation or knows the correct approach for handling bundle products with variants?

I would appreciate some help with that. Thanks!


Replies

 
Nicolai Pedersen Dynamicweb Employee
Nicolai Pedersen
Reply

I have just written some updates docs on this topic - below the implementation part of it:
 

 

Bundles are products of the type BOM (bill of materials), also called parts lists - products which consist of other products. A bundle can be as simple as a fixed collection of products, like a bike sold as a frame, two wheels, and a handlebar, and as advanced as a fully configurable package where the customer picks products, variants, and quantities for each part of the bundle.

How bundles are created and priced in the administration is covered in the Manual article Bundles. This article covers the frontend implementation:

  • Rendering a bundle in a product details template using viewmodels
  • Adding a bundle to cart, from the simplest case to fully configured bundles
  • Rendering bundle order lines in the cart

The BOM viewmodels

Bundles are rendered like any other product with the Product Catalog for ViewModel app, where the details template inherits ProductViewModel. Two properties on the model tell you that you are dealing with a bundle and how it is priced:

  • Model.ProductType is ProductType.Bom for bundle products
  • Model.PriceType holds the price type - Calculated, FixedPrice, or CalculatedWithBase

The contents of the bundle are not part of the model itself - they are loaded with the GetBomConfiguration() extension method from ProductViewModelExtensions, which returns a BomConfigurationViewModel:

Viewmodel

Purpose

BomConfigurationViewModel

The complete bundle configuration - Items holds the fixed items and Groups the configurable groups

BomItemViewModel

A fixed item with Id, Name, Quantity, and a Product reference

BomGroupViewModel

A configurable group with Id, Name, NoneSelectedText, DefaultProduct, and the Products the customer can choose between

ProductInfoViewModel

A lightweight product reference holding ProductId and VariantId

The product references are deliberately lightweight. When you need names, images, prices, or variant information for the products inside the bundle, expand a reference to a full ProductViewModel with the GetProduct() extension method from ProductInfoViewModelExtensions - or expand a whole list at once with GetProducts():

@using Dynamicweb.Rendering

@using Dynamicweb.Ecommerce.ProductCatalog

@inherits ViewModelTemplate<ProductViewModel>

 

@{

    var bom = Model.GetBomConfiguration();

}

 

@foreach (var item in bom.Items)

{

    var itemProduct = item.Product.GetProduct();

    <p>@item.Quantity x @itemProduct?.Name (@itemProduct?.Price?.PriceFormatted)</p>

}

For a real-world reference implementation, see the Swift ProductBom template.

[!TIP] Product feeds do not include bundle contents automatically. To publish the elements of a bundle in a feed template, call GetBomConfiguration() on each bundle product and serialize the items and groups alongside the product data.

Adding a bundle to cart

A bundle is added to cart like any other product - by submitting the add cart command with a ProductID. The CartHandler receives the command and CartService builds one order line for the bundle itself plus one component order line for each bundle item.

What makes bundles special is the set of additional form fields which configure the bundle content. There are two groups of fields, and they are keyed differently:

Bundle item type

Field

Value

Behavior when omitted

Fixed item

{productId}Quantity

The quantity of the item per bundle

The quantity configured on the bundle item is used

Fixed item

{productId}VariantId

A variant combination id, e.g. VO51.VO87

The product is added without a variant

Group

{itemId}

The selected product id, or productId|variantId

The group's default product is used, or the first product

Group

{itemId}Quantity

The quantity of the selected product per bundle

Defaults to 1

Note the difference in how the fields are keyed:

  • Fields for fixed items are prefixed with the product id of the item, e.g. PROD42Quantity
  • Fields for groups are named after the bundle item id - the Id property on BomGroupViewModel, e.g. ITEM7 and ITEM7Quantity

A few rules apply across both field groups:

  • All per-item quantities are multiplied with the Quantity submitted for the bundle itself - ordering 2 bundles where an item has quantity 3 results in a component line with quantity 6
  • Submitting a quantity of 0 (or less) for an item excludes it from the purchase entirely - this is how a customer removes an optional part from the bundle
  • {productId}VariantId is only applied when the bundle item points at the product in general - if the editor locked the item to a specific variant, the field is ignored
  • A group field can hold several comma-separated values - submitting multiple products from the same group (e.g. via checkboxes) adds a component line for each

[!NOTE] Units cannot be selected per bundle item - the UnitID parameter on the add command applies to the bundle's own order line only. Also note that submitting an empty value for a group does not deselect it; the default product is added instead. To let the customer opt out of a group, submit 0 in the group's quantity field as shown in the scenarios below.

Scenarios

The scenarios below progress from the simplest bundle to a fully configurable one. They all assume a details template as shown above, with bom holding the result of Model.GetBomConfiguration().

A simple bundle

A bike sold as a fixed collection - a frame, two wheels, and a handlebar. Nothing is configurable, so the template just presents the contents and submits the bundle product itself:

<h1>@Model.Name</h1>

<p>@Model.Price?.PriceFormatted</p>

 

<ul>

    @foreach (var item in bom.Items)

    {

        <li>@item.Quantity x @item.Name</li>

    }

</ul>

 

<form method="post">

    <input type="hidden" name="ProductID" value="@Model.Id" />

    <input type="hidden" name="Quantity" value="1" />

    <button type="submit" name="CartCmd" value="add">Add to cart</button>

</form>

The cart now contains a bundle line and four component lines - the wheel line with quantity 2, as configured on the bundle item.

Adjustable item quantities

To let the customer decide how many of each fixed item they want, add a quantity field per item inside the form. The field is named after the item's product id, and 0 removes the item from the purchase:

@foreach (var item in bom.Items)

{

    <label>

        @item.Name

        <input type="number" name="@(item.Product.ProductId)Quantity" value="@item.Quantity" min="0" />

    </label>

}

Items with variants

When a fixed item points at a product with variants - say the frame and the wheels both come in two sizes - the customer must be able to pick the variant. Expand the item product and render a variant selector named {productId}VariantId:

@foreach (var item in bom.Items)

{

    var itemProduct = item.Product.GetProduct();

 

    <label>

        @item.Name

        @if (string.IsNullOrEmpty(item.Product.VariantId) && itemProduct?.VariantInfo?.VariantInfo is not null)

        {

            <select name="@(item.Product.ProductId)VariantId">

                @foreach (var option in itemProduct.VariantInfo.VariantInfo)

                {

                    <option value="@option.OptionID">@option.OptionName</option>

                }

            </select>

        }

        <input type="number" name="@(item.Product.ProductId)Quantity" value="@item.Quantity" min="0" />

    </label>

}

The string.IsNullOrEmpty(item.Product.VariantId) check matters: it distinguishes items where the editor locked a specific variant (nothing to select) from items pointing at a variant master (customer selects). For products with two variant dimensions, join the option ids with a period, e.g. VO51.VO87 - see the Swift template for a two-dimensional example.

Selecting a product from a group

A configurable slot - the customer chooses between several handlebars. Render the group as radio buttons (or a dropdown) named after the group id, with the default product preselected:

@foreach (var group in bom.Groups)

{

    <fieldset>

        <legend>@group.Name</legend>

        @foreach (var option in group.Products)

        {

            var optionProduct = option.GetProduct();

            var isDefault = group.DefaultProduct?.ProductId == option.ProductId;

 

            <label>

                <input type="radio" name="@group.Id" value="@option.ProductId" checked="@isDefault" />

                @optionProduct?.Name (@optionProduct?.Price?.PriceFormatted)

            </label>

        }

    </fieldset>

}

Optional groups

To make a group optional - an add-on the customer can skip - use the group's quantity field, since submitting 0 excludes the slot from the purchase. The NoneSelectedText configured on the bundle item is meant as the label for the opt-out choice:

@foreach (var group in bom.Groups)

{

    <label>

        @group.Name

        <select name="@(group.Id)Quantity">

            <option value="0">@group.NoneSelectedText</option>

            <option value="1" selected>1</option>

            <option value="2">2</option>

        </select>

    </label>

    @* ...followed by the product selector for the group as shown above *@

}

Group products with variants

The most advanced case - a Mountain bike starter package where the customer picks a bike, a t-shirt, and a saddle, and each of those products comes in variants like color and size. The group field accepts the combined format productId|variantId, so the simplest robust pattern is a single dropdown per group listing every product and variant combination:

@foreach (var group in bom.Groups)

{

    <label>

        @group.Name

        <select name="@group.Id">

            @foreach (var option in group.Products)

            {

                var optionProduct = option.GetProduct();

 

                @if (optionProduct?.VariantInfo?.VariantInfo is not null)

                {

                    foreach (var variant in optionProduct.VariantInfo.VariantInfo)

                    {

                        <option value="@(option.ProductId)|@(variant.OptionID)">@optionProduct.Name - @variant.OptionName</option>

                    }

                }

                else

                {

                    <option value="@option.ProductId">@optionProduct?.Name</option>

                }

            }

        </select>

    </label>

}

For a richer UI - product cards with a radio button per product and a separate variant dropdown which updates the radio value - see the Swift ProductBom template, which uses a small piece of JavaScript to write the productId|variantId value into the selected radio button.

Bundles in the cart

When a bundle is added to cart, the components are not separate lines in the main order line collection - they are attached to the bundle's order line. In the tag-based Shopping Cart templates this means the OrderLines loop contains one line per bundle, and the components are available in a nested loop called BOMItems where all the usual order line tags apply:

@foreach (LoopItem line in GetLoop("OrderLines"))

{

    <div>

        @line.GetValue("Ecom:Order:OrderLine.Quantity") x @line.GetValue("Ecom:Order:OrderLine.ProductName")

        <strong>@line.GetValue("Ecom:Order:OrderLine.Price")</strong>

 

        @foreach (LoopItem component in line.GetLoop("BOMItems"))

        {

            <div class="ps-4">

                @component.GetValue("Ecom:Order:OrderLine.Quantity") x @component.GetValue("Ecom:Order:OrderLine.ProductName")

                @component.GetValue("Ecom:Order:OrderLine.ProductVariantText")

            </div>

        }

    </div>

}

A few things to be aware of when rendering bundle lines:

  • Component quantities are totals - for 2 bundles each containing 2 wheels, the wheel component line has quantity 4
  • The price on the bundle line follows the bundle's price type - for Fixed price bundles the component prices do not contribute to the line price
  • Deleting the bundle order line - e.g. with the delorderline command - also removes its component lines

In contexts where order content is rendered from viewmodels, OrderLineViewModel exposes the same structure through the BomOrderLines property, and the GetProduct() extension method loads the full product for any of the lines.

 

 
Nicolai Pedersen Dynamicweb Employee
Nicolai Pedersen
Reply

An the setup documentation

Sometimes you don't sell a single product - you sell a combination. A camera with a bag and a memory card. A bike where the customer picks the saddle. A starter package where the customer chooses a bike, a t-shirt, and a saddle in their own size and color. In DynamicWeb these are bundles - products with the product type Bundle, also known as BOM products (bill of materials) or parts lists.

A bundle is a regular product in most respects - it has a name, a number, descriptions, and images, it lives in a product group, and it appears in product lists and channels like any other product. What sets it apart is that it consists of other products. The bundle itself is usually virtual - the physical things you put in the box are the products inside it.

This article explains the concept and shows you how to set up bundles, starting with a simple fixed kit and progressing to configurable bundles with variants.

Bundle items

The contents of a bundle are defined by its bundle items. There are two kinds:

  • A product item points at a specific product and a quantity - it is always part of the bundle
  • A group item points at a product group - the customer chooses one of the products in that group when buying the bundle

The two kinds can be combined freely, and the mix determines the character of the bundle:

  • Only product items - a fixed bundle where the contents are predefined, like a camera kit
  • One or more group items - a configurable bundle where the customer makes choices, like a bike with a choice of saddle

Creating a bundle

A bundle starts its life as an ordinary product:

  1. Create a new product - or open an existing one - and click Edit
  2. In the Purchasability section, set Product type to Bundle
  3. Save

Changing the product type to Bundle changes two things:

  • A Price type field appears in the Pricing section - this controls how the bundle price is calculated, see bundle pricing below
  • A Bundles tab appears in the product details view - this is where you manage the bundle contents

ProductDetailsBundles

Adding bundle items

From the Bundles tab, click Manage to open the bundle screen. It lists the bundle items with their type - Product or Group - name, and quantity. From the action menu you can:

  • Add products - select one or more products to add as product items
  • Add groups - select one or more product groups to add as group items
  • Sort - control the order in which the items are presented to the customer
  • Delete - remove the selected bundle items

Click an item in the list to open it and adjust its settings. The available fields depend on the item type.

Product items

A product item adds a specific product to the bundle and has two fields:

  • Product - the product to include; the selector also lets you pick a specific variant of a product with variants
  • Quantity - how many units of the product are included per bundle, e.g. 2 for a kit which includes two batteries

Group items

A group item lets the customer choose between the products in a product group and has three fields:

  • Group - the product group the customer chooses from
  • Default product - the product from the group which is preselected; select None to force the customer to make a choice
  • No product text - a text like No thanks shown as an opt-out option; when set, the customer can skip this item entirely, which makes it optional

The default products across all group items make up the default configuration of the bundle - this is the configuration used when the bundle price is shown in product lists.

[!TIP]
Products in a group item are often regular products from your catalog, but you can also create a dedicated product group containing products made specifically for the bundle - for instance upgrade options priced relative to the standard choice.

Bundle pricing

Bundles can be priced in different ways - the Price type field in the Pricing section of the product edit screen controls which one is used:

Price type The bundle price is
Calculated price The sum of the item prices - the bundle's own price is ignored
Fixed price The price set on the bundle itself - the items don't affect the price
Fixed price plus changes The bundle's own price plus the sum of the item prices

With Calculated price, each item is priced the way the product would normally be priced - including price matrix prices - multiplied by the item quantity. When the customer swaps a product in a group item, the total simply changes to the sum of the new selection.

With Fixed price, the bundle sells at its own price no matter which products it contains and which choices the customer makes. This is the classic bundle discount setup - sell the kit for less than the parts. The bundle must have a price of its own, otherwise the price is 0.

With Fixed price plus changes, the bundle's own price acts as a base price which is added on top of the calculated item prices. The price shown in product lists is the base price plus the default configuration, and when the customer swaps a default product for a different one, the total changes by the price difference between the two products. This price type is only meaningfully different from the others when the bundle contains group items - and if the bundle has no price of its own, it behaves like Calculated price.

Common scenarios

The scenarios below progress from simple to advanced and show how the item types and price types combine in practice.

A fixed kit

You want to sell a Camera starter kit consisting of a camera, a bag, and two memory cards:

  1. Create a product called Camera starter kit and set Product type to Bundle
  2. On the Bundles tab, use Add products to add the camera, the bag, and the memory card
  3. Open the memory card item and set Quantity to 2
  4. Set Price type to Calculated price

The kit now sells for the sum of the parts - if the camera costs 800, the bag 150, and a memory card 50, the bundle price is 1,050. If prices on the parts change, the bundle price follows automatically.

To sweeten the deal, give the kit a bundle discount: set Price type to Fixed price and give the bundle a price of 999. The kit now sells for 999 regardless of what the parts cost individually.

A configurable bundle

You sell a City bike where the customer can choose between three saddles - standard, gel, and leather - and the price should reflect the choice:

  1. Create the City bike bundle product and give it a price of 4,999
  2. Add a product item for the bike itself, or model the bike as the bundle and only add items for the configurable parts
  3. Use Add groups to add a Saddles group item
  4. Open the group item and set Default product to the standard saddle
  5. Set Price type to Fixed price plus changes

If the standard saddle costs 0, the gel saddle 250, and the leather saddle 400, the bike is shown at 4,999 in the product list - the base price plus the default configuration. A customer who picks the leather saddle pays 5,399.

Optional add-ons

You want to offer an optional bike insurance with the city bike from the previous scenario:

  1. Add a group item pointing at a group containing the insurance products
  2. Set Default product to None
  3. Set No product text to something like No insurance

The customer now sees the insurance options alongside a No insurance choice - and since nothing is preselected, the add-on doesn't affect the default price of the bundle. If they pick an insurance product, its price is added to the total.

Bundle items with variants

The products inside a bundle can themselves have variants. Say you sell a Mountain bike starter package where the bike comes in different colors and sizes, the t-shirt in colors and sizes, and the saddle in a men's and a women's model. You have two options for each item:

  • Lock the variant - when adding products to the bundle, the product selector lets you pick a specific variant, e.g. Mountain bike, Red, 54 cm. The bundle then always contains that exact variant
  • Let the customer choose - add the main product instead of a specific variant. The customer then selects the variant they want when the bundle is added to the cart

The second option is what makes a scenario like the starter package work: the customer picks their bike size and color, their t-shirt size, and their saddle model, all within a single bundle.

[!NOTE]
This article focuses on setting up bundles in the administration. How bundles are rendered in the webshop and added to the cart - including selecting products, variants, and quantities - is covered in the Developer documentation article Bundles (BOM).

 

You must be logged in to post in the forum