How to customize the Wish List add-on further, including code examples.
Architecture
Data access layer
The Repository class contains all the methods to persist and manipulate data in the database. It does not contain any business logic except selecting lists, sorting list items and adding existing list items in the databas
Business logic layer
Business logic is implemented with the injection of the WishListFactoryService class and implementation of IUserContext. For example, it ensures that the WishList products of a logged in user has a user id but no cookie id. The WishList products of an anonymous user has a cookie id but not a user id. It also handles cookie generation and ensures that the lists and their items are cached in the session.
UserContext handles anonymous user’s and logged in user’s own WishList products. It returns user-id, channel, market, website and shopping-cart information. It also handles cookie generation, looks out for anonymous users without cookies, and sees to that lists and their items are cached in the session.
AdminUserContext is used by administrators to handle customers’ WishList products. It is possible to pass user-id, channel and shopping-cart information to the AdminUserContext. It processes the data directly through the repository. There is no caching in the session for AdminUserContext. It does not handle cookies or shopping carts.
Warning! Modifying a wish list while the user has an on-going session may result in unexpected data loss for the user.
Data carriers
List and ListItem classes are data carriers. They have no logic implemented. They are used to carry data between the data, business-logic and representation layers.
Code examples
All code examples are based on that Litium Accelerator has been implemented.
1. Creating WishListService
For the administrator:
var wishListService = _wishListServiceFactory.CreateAdminContext(_adminUserId, _Channel.SystemId);
For anonymous or logged-in customers:
var wishListService = _wishListServiceFactory.CreateUserContext(_channel.SystemId);
2. Adding an item to the wish list.
Assuming that your input model contains VariantSystemId and Quantity, and that the variant is not null. If the wish list does not exist it will be created.
var variant = _variantService.Get(model.VariantSystemId);
if (variant != null)
{
if (model.Quantity > 0)
{
wishListService.AddListItem("test", "test", variant, model.Quantity, true);
}
}
3. Implementing wish list features in Litium Accelerator
The code below shows:
- How to add an item to a wish list.
- How to display and update all items in the wish list, and add them to the shopping cart.
3.1 Before you start
Please read the article about Setting up a front end development environment.
3.2 Prepare web APIs for the wish list
Create an API controller with 3 actions: Add, Update and AddToCart:
[RoutePrefix("api/wishlist")]
public class WishListController : ApiControllerBase
The add action:
[HttpPost]
[Route("add")]
[ApiValidateAntiForgeryToken]
public IHttpActionResult Add(AddToWishListViewModel model)
{
var variant = _variantService.Get(model.ArticleNumber);
IList<WishListItem> wishListItems = new List<WishListItem>();
if (variant!=null && model?.Quantity >= 0)
{
wishListItems = _wishListService.AddListItem(WishListViewModel.DefaultListName, WishListViewModel.DefaultListType, variant, model.Quantity, true);
}
return Ok(wishListItems);
}
The update action:
[HttpPost]
[Route("update")]
[ApiValidateAntiForgeryToken]
public IHttpActionResult Update(WishListViewModel model)
{
var wishListItems = _wishListService
.GetListItems(WishListViewModel.DefaultListName, WishListViewModel.DefaultListType, true);
foreach (var item in model.Items)
{
var existing = wishListItems.FirstOrDefault(w => w.ArticleId == item.VariantId);
var quantity = existing != null ? item.Quantity - existing.Quantity : item.Quantity;
_wishListService.AddListItem(WishListViewModel.DefaultListName, WishListViewModel.DefaultListType,
_variantService.Get(item.VariantId), quantity, true);
}
return Ok(true);
}
The AddToCart action:
[HttpPost]
[Route("addtocart")]
[ApiValidateAntiForgeryToken]
public IHttpActionResult AddToCart()
{
_wishListService.AddToShoppingCart(WishListViewModel.DefaultListName, WishListViewModel.DefaultListType);
return Ok(true);
}
3.3 How to add an item to the wish list - server side
Create a wish-list button and add an event:
public static MvcHtmlString WishListButton<TModel>(this HtmlHelper<TModel> htmlHelper, Expression<Func<TModel, ProductItemViewModel>> expression, string cssClass = null)
{
var modelMetadata = ModelMetadata.FromLambdaExpression(expression, htmlHelper.ViewData);
return WishListButton(htmlHelper, (ProductItemViewModel)modelMetadata.Model, cssClass);
}
public static MvcHtmlString WishListButton(this HtmlHelper<ProductItemViewModel> htmlHelper, string cssClass = null)
{
return WishListButton(htmlHelper, htmlHelper.ViewData.Model, cssClass);
}
private static MvcHtmlString WishListButton(this HtmlHelper htmlHelper, ProductItemViewModel model, string cssClass)
{
var tag = new TagBuilder("a")
{
InnerHtml = "product.wishlist".AsWebSiteString()
};
var quantityFieldId = model.ShowQuantityField ? $"'{model.QuantityFieldId}'" : "null";
tag.Attributes.Add("onclick", $"window.__litium.events.onAddToWishListButtonClick(this,'{model.Id}', {quantityFieldId})");
cssClass = $"button wishlist-button {cssClass ?? string.Empty}".Trim();
if (!string.IsNullOrWhiteSpace(cssClass))
{
tag.Attributes.Add("class", cssClass);
}
return MvcHtmlString.Create(tag.ToString(TagRenderMode.Normal));
}
Add a wish-list button to the product item page:
@Html.WishListButton(x => x.ProductItem, cssClass: "product-detail__buy-button")
3.4 How to add an item to the wish list - client side
Create Wishlist.action.js and add the add action:
export const add = ({ articleNumber, quantity = 1, nodeIdToShowNotification = null, notificationMessage = null, hash = '' }) => {
return (dispatch) => {
if (!quantity || isNaN(quantity) || parseInt(quantity) <= 0) {
return;
}
return post('/api/wishlist/add', { articleNumber, quantity: parseInt(quantity) })
.then(response => response.json())
.then((items) => {
nodeIdToShowNotification && dispatch(showNotification(nodeIdToShowNotification, notificationMessage, hash));
})
.catch(ex => dispatch(catchError(ex, error => ({
type: WISHLIST_LOAD_ERROR,
payload: {
error,
}
}))));
}
}
Declare this action in index.js:
import { add as addToWishList } from './Actions/WishList.action';
Bind the action to window.__litium:
reduxActions: bindActionCreators({ addToCart, addToWishList, reorder, setProductImageSlideShows, showProductImageSlideShow, setProductImageSlideShowIndex} , store.dispatch),
Now, add the onAddToWishListButtonClick event to window.__litium:
onAddToWishListButtonClick:(sourceDomNode, articleNumber, quantityFieldId = null) => {
const nodeIdToShowNotification = sourceDomNode.id = Date.now();
window.__litium.reduxActions.addToWishList({
articleNumber,
quantity: quantityFieldId ? document.getElementById(quantityFieldId).value : 1,
nodeIdToShowNotification,
notificationMessage: translate('tooltip.addedtocart'),
hash: Date.now(),
});
},
Now we can add an item to the wish list by clicking the button on the product page.

3.5 How to display and update all items in the wish list, and add them to the shopping cart - server side
Create a view model for the wish list and wish-list items:
public class WishListViewModel : IViewModel
{
public const string DefaultListName = "Default";
public const string DefaultListType = "Default";
public Guid Id { get; set; }
public string Name { get; set; }
public string Type { get; set; }
public IList<WishListItemViewModel> Items { get; set; }
}
public class WishListItemViewModel : IAutoMapperConfiguration
{
public WishListItemViewModel()
{
Id = Guid.NewGuid();
}
public Guid Id { get; }
public Guid ListId { get; set; }
public decimal Quantity { get; set; }
public string ArticleNumber { get; set; }
public string Image { get; set; }
public string Name { get; set; }
public Guid VariantId { get; set; }
public long SortIndex { get; set; }
[UsedImplicitly]
public void Configure(IMapperConfigurationExpression cfg)
{
cfg.CreateMap<WishListItem, WishListItemViewModel>()
.ConvertUsing<WishListItemConverter>();
}
}
Create a converter to convert WishListItem to WishListItemViewModel:
[UsedImplicitly]
private class WishListItemConverter : ITypeConverter<WishListItem, WishListItemViewModel>
{
private readonly VariantService _variantService;
private readonly BaseProductService _baseProductService;
public WishListItemConverter(VariantService variantService, BaseProductService baseProductService)
{
_variantService = variantService;
_baseProductService = baseProductService;
}
public WishListItemViewModel Convert(WishListItem source, WishListItemViewModel destination, ResolutionContext context)
{
var variant = _variantService.Get(source.ArticleId);
if (variant == null)
{
return new WishListItemViewModel();
}
var baseProduct = _baseProductService.Get(variant?.BaseProductSystemId ?? Guid.Empty);
var image = (variant?.Fields.GetValue<IList<Guid>>(SystemFieldDefinitionConstants.Images)?.FirstOrDefault()
?? baseProduct?.Fields.GetValue<IList<Guid>>(SystemFieldDefinitionConstants.Images)?.FirstOrDefault())
.MapTo<ImageModel>();
var viewModel = new WishListItemViewModel
{
ListId = source.ListId,
ArticleNumber = variant.Id,
VariantId = variant.SystemId,
Image = image.GetUrlToImage(Size.Empty, new Size(200, 120)).Url,
Name = variant?.Localizations.CurrentCulture.Name.NullIfWhiteSpace() ?? baseProduct?.Localizations.CurrentCulture.Name.NullIfWhiteSpace() ?? variant.Id,
Quantity = source.Quantity,
SortIndex = source.SortIndex
};
return viewModel;
}
}
Create a builder to build the view model:
public class WishListViewModelBuilder : IViewModelBuilder<WishListViewModel>
{
private readonly WishListService _wishListService;
public WishListViewModelBuilder(WishListServiceFactory wishListServiceFactory, RouteRequestLookupInfoAccessor routeRequestLookupInfoAccessor)
{
_wishListService =
wishListServiceFactory.CreateUserContext(routeRequestLookupInfoAccessor.RouteRequestLookupInfo.Channel);
}
public virtual WishListViewModel Build(PageModel pageModel)
{
var wishListItems = _wishListService
.GetListItems(WishListViewModel.DefaultListName, WishListViewModel.DefaultListType, true)
.MapTo<IEnumerable<WishListItemViewModel>>();
return new WishListViewModel()
{
Name = WishListViewModel.DefaultListName,
Type = WishListViewModel.DefaultListType,
Items = wishListItems.ToList()
};
}
}
Create a controller:
public class WishListController : ControllerBase
{
private readonly WishListViewModelBuilder _builder;
public WishListController(WishListViewModelBuilder builder)
{
_builder = builder;
}
[HttpGet]
public ActionResult Index(PageModel currentPageModel)
{
var model = _builder.Build(currentPageModel);
return View(model);
}
}
Create a view: we will pass the view model to React:
<div id="wishlist"></div>
<script type="text/javascript">
window.__litium = window.__litium || {};
window.__litium.preloadState = window.__litium.preloadState || {};
window.__litium.preloadState.wishlist = @Html.Json(Model);
</script>
3.6 How to display and update all items in the wish list, and add them to the shopping cart - client side
Create a wish-list component:
import React, { Component, Fragment } from 'react';
class WishList extends Component {
constructor(props) {
super(props);
}
handleQuantityChange(articleNumber, quantity) {
this.props.onQuantityChange(articleNumber, quantity)
}
render() {
const productContentClass = "columns small-12 medium-4 large-6" + " checkout-cart__image-container";
const quantityInputClass = "columns small-2 medium-2 large-1";
return (
<div>
<div className="row checkout__container">
<div className="small-12 simple-table">
<div className="row small-unstack no-margin">
<div className="columns small-12 medium-4 large-6"></div>
<div className="columns small-4 medium-3 large-2">Quantity</div>
</div>
{this.props.wishList && this.props.wishList.items.map((item, index, array) => (
<div key={index} className="row small-unstack no-margin">
<div className={productContentClass}>
<div className="checkout-cart__image-wrapper">
<img className="checkout-cart__image" src={item.image} alt={item.name} />
</div>
<div className="checkout-cart__image-info">
{item.name}
</div>
</div>
<div className={quantityInputClass}>
<input className="checkout-cart__input" type="number" min="1" maxLength={3}
value={item.quantity}
onChange={event => this.handleQuantityChange(item.articleNumber, event.target.value)} />
</div>
</div>
))}
</div>
</div>
</div>
)
}
}
export default WishList;
Add the actions changeQuantity, updateWishList and addToCart to WishList.action.js:
export const changeQuantity = (articleNumber, quantity) => {
return {
type: WISHLIST_CHANGE_QUANTITY,
articleNumber,
quantity
}
}
export const updateWishList = () => (dispatch, getState) => {
return post('/api/wishlist/update', getState().wishlist)
.then(response => response.json())
.then(result => {
dispatch({
type: WISHLIST_UPDATE,
payload: {
success: true,
message: 'Wishlist was updated successfully'
}
});
})
.catch(ex => {
dispatch({
type: WISHLIST_UPDATE,
payload: {
success: false,
message: 'There was an error'
}
});
});
}
export const addToCart = () => (dispatch) => {
return post('/api/wishlist/addtocart')
.then(response => response.json())
.then(result => {
dispatch({
type: WISHLIST_ADD_TO_CART,
payload: {
success: true,
message: 'Wishlist items were added to cart successfully'
}
})
})
.catch(ex => {
dispatch({
type: WISHLIST_ADD_TO_CART,
payload: {
success: false,
message: 'There was an error'
}
});
});
}
Create WishList.reducer.js:
import { WISHLIST_CHANGE_QUANTITY, WISHLIST_UPDATE, WISHLIST_ADD_TO_CART } from '../Actions/WishList.action';
export const wishlist = (state = {}, action) => {
switch (action.type) {
case WISHLIST_CHANGE_QUANTITY:
return {
...state,
items: state.items.map(
(wish) =>
(wish.articleNumber === action.articleNumber) ? { ...wish, quantity: action.quantity } : wish),
};
case WISHLIST_UPDATE :
case WISHLIST_ADD_TO_CART:
return {
...state,
payload: action.payload
};
default:
return state;
}
}
Create Wishlist.container.js and combine them:
import React, { Component, Fragment } from 'react';
import { connect } from 'react-redux';
import WishList from '../Components/WishList'
import { changeQuantity, updateWishList, addToCart } from '../Actions/WishList.action'
class WishListContainer extends Component {
constructor(props) {
super(props);
}
render() {
const { wishlist } = this.props;
if (!wishlist || !wishlist.items || wishlist.items.length < 1) {
return (
<div className="row">
<div className="small-12">
<h2 className="checkout__title">Your wish list is empty</h2>
</div>
</div>
);
}
const { payload } = wishlist;
return (
<Fragment>
{
payload && (<div>{payload.message}</div>)
}
<div class="row"><div class="small-12"><h2 class="checkout__title">Wish list</h2></div></div>
<WishList wishList={wishlist} onQuantityChange={(articleNumber, quantity) => this.props.changeQuantity(articleNumber, quantity)} />
<div className="row">
<div className="small-12 medium-6 columns">
<button type="submit" className="checkout__submit-button" onClick={() => this.props.update()} >Update</button>
</div>
<div className="small-12 medium-6 columns">
<button type="submit" className="checkout__submit-button" onClick={() => this.props.addToCart()} >Add to Cart</button>
</div>
</div>
</Fragment>
)
}
}
const mapStateToProps = state => {
return {
wishlist: state.wishlist,
}
}
const mapDispatchToProps = dispatch => {
return {
changeQuantity: (articleNumber, quantity) => dispatch(changeQuantity(articleNumber, quantity)),
update: () => dispatch(updateWishList()),
addToCart: () => dispatch(addToCart())
}
}
export default connect(mapStateToProps, mapDispatchToProps)(WishListContainer)
Go to Litium back office and create an new field template. Set WishlistController as Controller and Index as Action to the display template. Finally, create a new page in website against the new template.