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;
}
Updated 2 months ago