HomeDev GuideAPI Reference
Dev GuideAPI ReferenceUser GuideLegal TermsGitHubNuGetDev CommunityOptimizely AcademySubmit a ticketLog In
Dev Guide

Dynamic packages

Describes how to enable and use dynamic packages in Optimizely Commerce Connect.

👍

Beta

This feature is currently in beta as of Commerce Connect 14.29.0. Follow the steps below to enable it.

Enable dynamic packages

Add the following option to appSettings.json:

"EPiServer":  {
    "Commerce":  {
        "ApplicationOptions": {
            "Features": {
    	"DynamicPackageFeature": {
      	    "Feature": "DynamicPackageFeature",
      	    "State": 0,
      	    "Type": "Mediachase.Commerce.Core.Features.DynamicPackageFeature, Mediachase.Commerce"
                }
            }
        }
    }
}

Save a dynamic package

On the cshtml page

Add a label to display the product name and a drop0down list to display the product entries.

<div class="col-xs-12">
    @Html.PropertyFor(x => x.Package.Name)
    <p>
        @{
            var dic = Model.Package.GetEntries();
        }
        @foreach (var group in Model.Entries)
        {
            var quantity = dic.Keys.FirstOrDefault(k => k.Child == group.Key.ContentLink).Quantity;
            @for (var i = 0; i < quantity; i++)
            {
                <label>@group.Key.DisplayName</label>
                var items = new List<SelectListItem>();
                @foreach (var entry in group.Value)
                {
                    var inStock = Model.EntriesAvailability[entry.Code];
                    var item = new SelectListItem(
                        $"{entry.DisplayName} Size: {entry.Size} Color: {entry.Color} Stock: {inStock}",
                        entry.Code,
                        false,
                        inStock <= 0);
                    items.Add(item);
                }
                <div>
                    @Html.DropDownList("DynamicPackageListItems", items, new {@class = "form-control address-region-input"})
                </div>
            }
        }
    </p>
    <p>
        <strong>SKU:</strong> @Html.PropertyFor(x => x.Package.Code)
    </p>
</div>

For the form on the page, the AddToCart action will be like the following:

<div class="col-md-7">
    @if (Model.IsAvailable)
    {
        using (Html.BeginForm("AddToCart", "Cart", FormMethod.Post, new { @class = "form-inline", data_container = "MiniCart" }))
        {
            @Html.Hidden("code", Model.Package.Code)
            <button type="submit" role="button" class="btn btn-primary jsAddToCart" data-container="MiniCart"><span class="glyphicon glyphicon-shopping-cart"></span> @Html.Translate("/Product/Button/AddToCart")</button>
        }
        if (User.Identity.IsAuthenticated && !(bool)ViewData["IsReadOnly"])
        {
            using (Html.BeginForm("AddToCart", "WishList", FormMethod.Post, new { @class = "form-inline jsAddToWishList", data_container = "WishListMiniCart" }))
            {
                @Html.Hidden("code", Model.Package.Code)
                <button type="submit" role="button" class="btn btn-default btn--blue jsAddToCart" data-container="WishListMiniCart"><span class="glyphicon glyphicon-heart"></span> @Html.Translate("/Product/Button/AddToWishList")</button>
            }
        }
    }
</div>

On JavaScript

Add the selectedPackageItems parameter for the data when posting to the server.

addCartItem: function (e) {

    e.preventDefault();
    var form = $(this).closest("form");
    var formContainer = $("#" + form.data("container"));
    var skuCode = $("#code", form).val();

    $("#CartWarningMessage").hide()
    $(".warning-message", $("#CartWarningMessage")).html("");

    var selectedItems = new Array();
    $('select[name="DynamicPackageListItems"]').each(function () {
        selectedItems.push($(this).val());
    });

    $.ajax({
        type: "POST",
        url: form[0].action,
        data: { code: skuCode, selectedPackageItems: selectedItems },
        traditional: true,
        success: function (result) {

            formContainer.html($(result));
            $('.cartItemCountLabel', formContainer.parent()).text($('#CartItemCount', formContainer).val());
            $('.cartTotalAmountLabel', formContainer.parent()).text($('#CartTotalAmount', formContainer).val());

            formContainer.change();
        },
        error: function (xhr, status, error) {
            $(".warning-message", $("#CartWarningMessage")).html(xhr.statusText);
            $("#CartWarningMessage").show();
        }
    });
},

On the controller

In the AddToCart action of CartController, add the selectedPackageItems parameter.

[HttpPost]
public async Task<ActionResult> AddToCart(string code, IList<string> selectedPackageItems)
{
    ModelState.Clear();

    if (Cart == null)
    {
        _cart = _cartService.LoadOrCreateCart(_cartService.DefaultCartName);
    }

    var result = _cartService.AddToCart(Cart, code, selectedPackageItems, 1);
    if (result.EntriesAddedToCart)
    {
        _cartService.ApplyDiscounts(Cart);
        _orderRepository.Save(Cart);
        var change = new CartChangeData(CartChangeType.ItemAdded, code);
        await _recommendationService.TrackCartAsync(HttpContext, new List<CartChangeData> { change });
        return MiniCartDetails();
    }

    return StatusCode(StatusCodes.Status500InternalServerError, result.GetComposedValidationMessage());
}

The AddToCart method in the CartService is like the following:

public AddToCartResult AddToCart(ICart cart, string code, IList<string> selectedPackageItems, decimal quantity)
{
    var result = new AddToCartResult();
    var contentLink = _referenceConverter.GetContentLink(code);
    var entryContent = _contentLoader.Get<EntryContentBase>(contentLink);

    if (entryContent is BundleContent)
    {
        foreach (var relation in _relationRepository.GetChildren<BundleEntry>(contentLink))
        {
            var entry = _contentLoader.Get<EntryContentBase>(relation.Child);
            var recursiveResult = AddToCart(cart, entry.Code, relation.Quantity ?? 1);
            if (recursiveResult.EntriesAddedToCart)
            {
                result.EntriesAddedToCart = true;
            }

            foreach (var message in recursiveResult.ValidationMessages)
            {
                result.ValidationMessages.Add(message);
            }
        }

        return result;
    }

    var lineItem = cart.GetAllLineItems().FirstOrDefault(x => x.Code == code && !x.IsGift);

    if (lineItem == null)
    {
        if (entryContent is DynamicPackageContent)
        {
            lineItem = AddNewLineItem(cart, code, quantity, entryContent.DisplayName, selectedPackageItems);
        }
        else
        {
            lineItem = AddNewLineItem(cart, code, quantity, entryContent.DisplayName);
        }
    }
    else
    {
        var shipment = cart.GetFirstShipment();
        cart.UpdateLineItemQuantity(shipment, lineItem, lineItem.Quantity + quantity);
    }

    var validationIssues = ValidateCart(cart);

    AddValidationMessagesToResult(result, lineItem, validationIssues);

    return result;
}

The AddNewLineItem method is like the following:

private ILineItem AddNewLineItem(
    ICart cart, string code, decimal quantity, string displayName,
    IEnumerable<string> selectedOptions)
{
    var newLineItem = cart.CreateLineItem(code, _orderGroupFactory);
    newLineItem.Quantity = quantity;
    newLineItem.DisplayName = displayName;

    newLineItem.SaveDynamicPackageInfo(selectedOptions);

    cart.AddLineItem(newLineItem, _orderGroupFactory);

    var price = _pricingService.GetPrice(code);
    if (price != null)
    {
        newLineItem.PlacedPrice = price.UnitPrice.Amount;
    }

    return newLineItem;
}

The SaveDynamicPackageInfo method of IlineItemExtensions is like the following:

public static void SaveDynamicPackageInfo(this ILineItem lineItem, IEnumerable<string> selectedOptions)
{
    var properties = lineItem.Properties;
    if (properties != null)
    {
        properties[DynamicPackageField.FieldName] = string.Join(";", selectedOptions);
    }
}

Save the selected dynamic package items (selected entries) to the property of the line item.

Load dynamic packages

The CreateCartItemViewModel method of CartItemViewModelFactory class was updated to the following:

public virtual CartItemViewModel CreateCartItemViewModel(ICart cart, ILineItem lineItem, EntryContentBase entry)
{
    var viewModel = new CartItemViewModel
    {
        Code = lineItem.Code,
        DisplayName = entry.DisplayName,
        ImageUrl = entry.GetAssets<IContentImage>(_contentLoader, _urlResolver).FirstOrDefault() ?? "",
        DiscountedPrice = GetDiscountedPrice(cart, lineItem),
        PlacedPrice = _pricingService.GetMoney(lineItem.PlacedPrice),
        Quantity = lineItem.Quantity,
        Url = entry.GetUrl(_relationRepository, _urlResolver),
        Entry = entry,
        IsAvailable = _pricingService.GetPrice(entry.Code) != null,
        DiscountedUnitPrice = GetDiscountedUnitPrice(cart, lineItem),
        IsGift = lineItem.IsGift
    };

    var selectedVariants = lineItem.GetDynamicPackageInfo();
    if (selectedVariants != null)
    {
        var variants = _catalogContentService.GetItems<FashionVariant>(selectedVariants);
        var selectedOptions = variants.Select(v => $"{v.DisplayName} Size: {v.Size} Color: {v.Color} Code: {v.Code}");
        viewModel.SelectedOptions = selectedOptions;
    }

    var productLink = entry is VariationContent ?
        entry.GetParentProducts(_relationRepository).FirstOrDefault() :
        entry.ContentLink;

    FashionProduct product;
    if (_contentLoader.TryGet(productLink, out product))
    {
        viewModel.Brand = GetBrand(product);
    }

    var variant = entry as FashionVariant;
    if (variant != null)
    {
        viewModel.AvailableSizes = GetAvailableSizes(product, variant);
    }

    return viewModel;
}

The GetDynamicPackageInfo method of  IlineItemExtensions will be like this:
public static IEnumerable<string> GetDynamicPackageInfo(this ILineItem lineItem)
{
    var properties = lineItem.Properties;
    if (properties != null && properties.ContainsKey(DynamicPackageField.FieldName))
    {
        var options = properties[DynamicPackageField.FieldName] as string;
        return options.Split(";");
    }

    return null;
}