Displaying product variants

If your on-line store contains products with specified product variants, individual product details pages should contain some form of product variant selection.

This page demonstrates two approaches to displaying product variants, namely via:

Displaying product variants in a drop-down selector

The following section extends the scenario described in Displaying product details, and therefore presumes you already have working product details pages for standard products.

To display a selector with available product variants on a product’s details page:

  1. Open your MVC application in Visual Studio.

  2. To your product model, add the following properties and constructor. These properties extend the model with information about available product variants.

    
    
    
             public bool HasProductVariants { get; set; }
             public SelectList VariantSelectList { get; set; }
             public int SelectedVariantID { get; set; }
    
             /// <summary>
             /// Creates a new product model with variants.
             /// </summary>
             /// <param name="productPage">Product's page.</param>
             /// <param name="priceDetail">Price of the selected variant.</param>
             /// <param name="variants">Collection of selectable variants.</param>
             /// <param name="selectedVariantID">ID of the selected variant.</param>
             public ProductViewModel(SKUTreeNode productPage, ProductCatalogPrices priceDetail, List<ProductVariant> variants, int selectedVariantID)
                 : this(productPage, priceDetail)
             {
                 // Fills the selectable variants
                 HasProductVariants = variants.Any();
    
                 // Continues if the product has any variants
                 if (HasProductVariants)
                 {
                     // Selects a default variant
                     var selectedVariant = variants.FirstOrDefault(v => v.Variant.SKUID == selectedVariantID);
    
                     if (selectedVariant != null)
                     {
                         IsInStock = ((selectedVariant.Variant.SKUTrackInventory == TrackInventoryTypeEnum.Disabled) || (selectedVariant.Variant.SKUAvailableItems > 0));
                         SelectedVariantID = selectedVariantID;
                     }
    
                     // Creates a list of product variants
                     VariantSelectList = new SelectList(variants.Select(v => new SelectListItem
                     {
                         Text = string.Join(", ", v.ProductAttributes.Select(a => a.SKUName)),
                         Value = v.Variant.SKUID.ToString()
                     }), "Value", "Text");
                 }
             }
    
    
    
     
  3. In the controller managing product listing, modify the action displaying details pages to retrieve product variants. Also add a new endpoint that returns a variant from a provided variant ID.

    
    
    
             /// <summary>
             /// Displays product detail page of a product or product variant specified by the GUID of the product's or variant's page.
             /// </summary>
             /// <param name="guid">Node GUID of the product's (variant's) page.</param>
             /// <param name="productAlias">Node alias of the product's (variant's) page.</param>
             public ActionResult Detail(Guid guid, string productAlias)
             {
                 // Gets the product from the connected Kentico database
                 SKUTreeNode product = GetProduct(guid);
    
                 // If the product is not found or if it is not allowed for sale, redirects to error 404
                 if ((product == null) || !product.SKU.SKUEnabled)
                 {
                     return HttpNotFound();
                 }
    
                 // Redirects if the specified page alias does not match
                 if (!string.IsNullOrEmpty(productAlias) && !product.NodeAlias.Equals(productAlias, StringComparison.InvariantCultureIgnoreCase))
                 {
                     return RedirectToActionPermanent("Detail", new { guid = product.NodeGUID, productAlias = product.NodeAlias });
                 }
    
                 // Gets all product variants of the product
                 List<ProductVariant> variants = VariantHelper.GetVariants(product.NodeSKUID).OnSite(SiteContext.CurrentSiteID).ToList()
                     .Select(sku => new ProductVariant(sku.SKUID))
                     .OrderBy(v => v.Variant.SKUPrice).ToList();
    
                 // Selects the first product variant
                 ProductVariant selectedVariant = variants.FirstOrDefault();
    
                 // Calculates the price of the product or the variant
                 ShoppingCartInfo cart = shoppingService.GetCurrentShoppingCart();
    
                 SKUInfo selectedProduct = selectedVariant != null ? selectedVariant.Variant : product.SKU;
    
                 ProductCatalogPrices priceDetail = calculatorFactory
                    .GetCalculator(cart.ShoppingCartSiteID)
                    .GetPrices(selectedProduct, Enumerable.Empty<SKUInfo>(), cart);
    
                 // Initializes the view model of the product or product variant
                 ProductViewModel viewModel = new ProductViewModel(product, priceDetail, variants, selectedVariant?.Variant?.SKUID ?? 0);
    
                 // Displays the product detail page
                 return View(viewModel);
             }
    
             /// <summary>
             /// Loads information about the demanded variant to change the page content.
             /// </summary>
             /// <param name="variantID">ID of the selected variant.</param>
             [HttpPost]
             public JsonResult Variant(int variantID)
             {
                 // Gets SKU information based on the variant's ID
                 SKUInfo variant = SKUInfoProvider.GetSKUInfo(variantID);
    
                 // If the variant is null, returns null
                 if (variant == null)
                 {
                     return null;
                 }
    
                 var cart = shoppingService.GetCurrentShoppingCart();
    
                 // Calculates the price of the variant
                 ProductCatalogPrices variantPrice = calculatorFactory
                     .GetCalculator(cart.ShoppingCartSiteID)
                     .GetPrices(variant, Enumerable.Empty<SKUInfo>(), cart);
    
                 // Finds out whether the variant is in stock
                 bool isInStock = variant.SKUTrackInventory == TrackInventoryTypeEnum.Disabled || variant.SKUAvailableItems > 0;
    
                 // Creates a JSON response for the JavaScript that switches the variants
                 var response = new
                 {
                     totalPrice = String.Format(cart.Currency.CurrencyFormatString, variantPrice.Price),
                     inStock = isInStock,
                     stockMessage = isInStock ? "Yes" : "No"
                 };
    
                 // Returns the response
                 return Json(response);
             }
    
    
    
     
  4. Update the view of your product details page:

    1. Add a variant selector

      
      
      
       @if (Model.HasProductVariants)
       {
           @* Renders a drop-down selector allowing users to select a desired product variant. *@
           <div class="cart-item-selector" data-variant-action="@Url.Action("Variant")">
               <p>
                   Choose your desired variant:
                   @Html.DropDownListFor(m => m.SelectedVariantID, Model.VariantSelectList, new { @class = "js-variant-selector" })
               </p>
           </div>
       }
       @Scripts.Render("~/Scripts/jquery-2.1.4.min.js")
       @Scripts.Render("~/Scripts/variantSelector.js")
      
      
      
       
    2. Modify the add item to cart form to support product variants:

      
      
      
           @* Renders an add to cart button. *@
           using (Html.BeginForm("AddItem", "Checkout", FormMethod.Post))
           {
               <input type="hidden" name="itemSkuId" id="selectedVariantID" value="@(Model.SelectedVariantID > 0 ? Model.SelectedVariantID : Model.SKUID)" />
               <label>Qty</label>
               <input type="text" name="itemUnits" value="1" />
               <input type="submit" name="AddItem" value="Add to cart" />
           }
      
      
      
       
  5. Add a JavaScript file with a function that dynamically switches between variants (changes the view’s content) when the variant drop-down selector changes. The file is linked within the variant selector code, and the function uses the endpoint defined in the controller above.

    
    
    
      (function () {
         'use strict';
    
         // Initializes contextual variables
         var url = $('.cart-item-selector').data('variant-action'),
             stockMessage = $("#stockMessage"),
             totalPrice = $('#totalPrice'),
             selectedSKUID = $('#selectedVariantID');
    
         // Updates the displayed product details when a different variant
         // is selected from the variants drop-down selector
         $('.js-variant-selector').change(function () {
             var id = $(this).val();
             updateVariantSelection(id);
         });
    
         // Updates the product information from data retrieved for the selected variant
         function updateVariantSelection(variantId) {
             $.post(url, { variantID: variantId }, function (data) {
                 stockMessage.text(data.stockMessage);
                 totalPrice.text(data.totalPrice);
                 selectedSKUID.val(variantId);
             });
         }
    
     }());
    
    
     

Customers can now add product variants to their shopping cart by selecting a variant from the drop-down selector. 

Instead of listing all available product variants in a drop-down selector, you can alternatively display a product option selector. This way, you allow customers manual selection from individual options defined for each product option category of the Attribute type (e.g. in the form of radio buttons). Based on this selection, the ‘Add to cart’ functionality can then add the appropriate product variant to the shopping cart. See the Adding a product option selector to product details pages section for more information.

Changing the product image for different variants

You may find it useful to display a different image for every product variant. For example, if you sell T-shirts, you may want to display a red T-shirt when a red variant is selected and a blue T-shirt when a blue variant is selected.

To display a different image for each variant:

  1. Enable the image path for each variant
  2. Display the variant’s image in your MVC application

Enabling a specific image path for each variant

Change the default setting of the variant detail layout to include a selector for its image.

  1. Open the Modules application in Kentico.
  2. Edit the E-commerce module.
  3. Switch to the Classes tab.
  4. Edit the SKU class.
  5. Switch to the Alternative forms tab.
  6. Edit the Variant properties form.
  7. Switch to the Fields tab.
  8. Select SKUImagePath in the field panel.
  9. Select the Display field in the editing form check box.
    Selecting the Display field in the editing form check box
  10. Click Save.

When you now edit a product variant, you can see a selector for an image. By default, Kentico tries to display an image of the product variant. If the variant does not contain any image, the system uses an image of the variant’s parent product.

Displaying product variant images

This process assumes your MVC application can display product variants as described in Displaying product variants in a drop-down selector.

  1. Open your MVC project in Visual Studio.

  2. Add a recognizable ID tag to the image in product details view.

    
    
    
     <img id="js-product-image" ... />
    
    
    
     
  3. Add the image ID to the JavaScript handling changes of the selected variant.

    
    
    
     var productImage = $("#js-product-image");
    
     function updateVariantSelection(variantId) {
    
         // ...
    
         productImage.attr("src", data.imagePath);
     }
    
    
     
  4. Add the image path for the specific variant to the JSON response created in the controller.

    
    
    
                 var response = new
                 {
                     // ...
    
                     imagePath = Url.Content(variant.Variant.SKUImagePath)
                 };
    
    
    
     

Your product details pages now display a different image for every variant. If the variant does not have any image specified, the system uses the parent product’s image.

Adding a product option selector to product details pages

A product option selector allows customers manual selection from individual options defined for each product option category of the Attribute type (e.g., in the form of radio buttons, or drop-down lists). Based on this selection, the system can retrieve the appropriate product variant from the database and add it to the shopping cart.

For a clearer idea of how a product option selector may look like, here’s an implementation from our Dancing Goat MVC sample site:

Product option selector

To retrieve and dynamically display variants from product option selectors:

  1. Create a view model for product option categories.

    • The view model needs to contain information required to select, display, and process a product variant’s options.
  2. To the action that displays the product details page (for example the Detail action from the example above), add logic that loads a default product variant and its product option categories.

    
    
    
                 // Gets the cheapest variant of the product
                 List<ProductVariant> variants = VariantHelper.GetVariants(product.NodeSKUID).OnSite(SiteContext.CurrentSiteID).ToList()
                     .Select(s => new ProductVariant(s.SKUID))
                     .OrderBy(v => v.Variant.SKUPrice).ToList();
    
                 ProductVariant cheapestVariant = variants.FirstOrDefault();
    
                 // Gets the product's option categories
                 IEnumerable<OptionCategoryInfo> categories = VariantHelper.GetProductVariantsCategories(sku.SKUID).ToList();
    
                 // Gets the cheapest variant's selected attributes
                 IEnumerable<ProductOptionCategoryViewModel> variantCategories = cheapestVariant?.ProductAttributes.Select(
                     option =>
                         new ProductOptionCategoryViewModel(sku.SKUID, option.SKUID,
                                 categories.FirstOrDefault(c => c.CategoryID == option.SKUOptionCategoryID)));
    
    
    
     
  3. Use your extended model class to pass the data of the available product options to your view.

  4. Modify the endpoint that retrieves variant data to retrieve a product variant based on list IDs of selected product options.

    
    
    
                 // Retrieves a product variant from a given product ID and a collection of option IDs
                 var variantSKU = VariantHelper.GetProductVariant(productId, new ProductAttributeSet(optionIds));
    
                 if (variantSKU == null)
                 {
                     return null;
                 }
    
                 return variantSKU;
    
    
    
     
  5. If you followed the example above, you might need to modify the JavaScript function that switches between variants.

  6. Add selectors for the available product options to the view that displays product details.

    To display the default text for drop-down lists specified by the Default text field, access the DefaultText property of the ProductOptionCategory type.

Customers can now configure product variants with attribute selectors.