Disclaimer: This website requires JavaScript to function properly. Some features may not work as expected. Please enable JavaScript in your browser settings for the best experience.

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;
}