Improve Storefront Performance for Large Carts

When a shopper creates an order by adding a large number of items to their shopping cart, performance can suffer, sometimes to the point where the shopper cannot complete or submit the order.

This document describes how to configure Commerce to improve performance for large-cart orders. This section includes the following topics:

Understand large cart support

From a performance perspective, a large cart is a shopper’s cart that contains 200 items or more. Carts this large may experience performance issues. Shoppers typically create orders this large using the Commerce Quick Order widget.

You can enable large cart support by configuring the CCStoreConfiguration.largeCart property. This reduces the number of situations where large cart information is refreshed (price calls), improving overall performance. All cart information always refreshes during the checkout phase.

New endpoints also enable large cart batching during GET product/SKU calls.

Enable support for large carts

To enable deferred pricing, set this property in application.js:

CCStoreConfiguration.largeCart

By default, this property is not set.

To override deferred pricing, use this method:

CCStoreConfiguration.isLargeCartCCStoreConfiguration.isLargeCart

You do not need to do anything to enable the use of large-cart endpoints, but you can change the number of items in a cart that triggers large-cart endpoints by using these properties:

  • CCStoreConfiguration.batchSizeForProdAndSkuData is a property that specifies the number of items that must be in a cart before Commerce automatically uses listProductsForLargeCart and listSkusForLarge Cart instead of listSkus and listProducts.
  • CCStoreConfiguration.threshholdSizeForStockStatusData is a property that specifies the number of items that must be in a cart before Commerce Cloud automatically uses listStockStatusesForLargeCart instead of getStockStatuses.

Understand view model support for large carts

This section describes the Cart view model properties and methods that support deferred pricing calls and batching the Store API Products and SKUs endpoints.

CartViewModel.hardPricing is a property that triggers cart pricing when the CCStoreConfiguration.largeCart flag is set to true. See Sample widgets and elements for a code sample that uses this property to trigger pricing. The default value of this property is false.

CartViewModel.userActionPending is a property that you should set to true whenever you update the cart and the largeCart flag is set to true. Use this flag to display messages reminding the shopper to save the cart. You can also use this flag to display messages that remind customers that prices are approximate and they will have to click the save cart button to trigger pricing and see the correct prices.

CartViewModel.updateItemPriceForLargeCart is a method to calculate the item total with an approximate price when you defer pricing calls. This method calculates prices by using list prices and sale prices. You can override this method to accommodate manual pricing calculations.

Use the method CartViewModel.updateItemShippingGroupRelationShipForLargeCart to update shipping group relationships from the client side when you have deferred pricing calls.

Use CartViewModel to implement these functions to update cart data when you defer pricing calls:

  • CartViewModel.updateCartItemDataForLargeCart is a method to update the cart total and subtotal list price and the sale price of an item added to the cart when you defer pricing calls. It also updates numberOfItems for the Cart Summary widget. You can override this method to accommodate manual pricing calculations.
  • CartViewModel.updateCartAfterLoginForLargeCart is a method to update cart data after a registered shopper logs into their account.

CartViewModel.priceCartBeforeRefreshInLargeCart is a method to reprice the entire cart.

CartViewModel.getBatchSizeForProdAndSkuData is a method that returns the number of products or SKUs whose data you need to retrieve in each listProduct or listSku request. If this is set to 1, it indicates that all products or SKUs will be sent to listProduct or listSku at once. Any other positive value indicates the number of products/SKUs sent in one call to the listProduct or listSku endpoint. In this case, multiple calls to these endpoints retrieve data for all IDs.

CartViewModel.getThreshholdSizeForStockStatusData returns the threshold on the number of SKUs beyond which a POST equivalent of the stock call would trigger.

Sample widgets and elements

This section describes sample widget code that you can use to implement support for large carts.

Create an element to manually trigger pricing

The following sample button uses the hardPricing flag to let the shopper manually price the cart.

saveCartButtonHandler :function(){
 this.cart().hardPricing = true;
 this.cart().markDirty();
},

When pricing calls are deferred and you enable the large cart feature, all the item update/delete/add operations normally triggered from the Product Details widget, Shopping Cart widget, Header widget, Quick Order widget, the copy order operation from Order Details widget, and the Purchase list widget are handled from client side.

Shopping Cart Widget and Header widget implementation

You can remove line items from the Header mini cart and Shopping Cart widget. The following code sample is the basic implementation of client side handling for the removal of line items. Line items here correspond to shipping group relationship and the removeShippingGroupRelationShip method as the handler for the remove items event.

removeShippingGroupRelationShip :function(cartItem,shippingGroupRelationship){
  if(this.storeConfiguration.isLargeCart() === true){

    var quantityChange=(-1)*shippingGroupRelationship.updatableQuantity();
    var price=cartItem.productData().childSKUs[0].salePrice ? cartItem.productData().childSKUs[0].salePrice : cartItem.productData().childSKUs[0].listPrice;
    //remove the SGR
    cartItem.shippingGroupRelationships.remove(shippingGroupRelationship);
    if(cartItem.shippingGroupRelationships().length === 0){
      this.cart().items.remove(cartItem);
    }else{
      cartItem.quantity(data.quantity()+ quantityChange);
      cartItem.itemTotal(data.itemTotal()+price*quantityChange);
    }
    //update the number of items
    this.cart().numberOfItems(this.cart().numberOfItems()+quantityChange);
    this.cart().updateAllItemsArray();
    this.cart().subTotal(this.cart().subTotal()+price*quantityChange);
    this.cart().total(this.cart().total()+(price)*quantityChange);
    this.cart().userActionPending(true);
    this.cart().saveCartCookie();
    $.Topic(pubsub.topicNames.CART_REMOVE_SUCCESS).publishWith([{message:"success"}]);
    return true;
  }
}

Widget implementation for update

Line item quantity can be updated from the Shopping Cart widget. The following code is the basic implementation of client side handling for the update of line items. These line items correspond to the shipping group relationship and updateQuantity methods that you can use as a handler for the update item event.

updateQuantity: function(data, event, id, shippingGroup) {

  if('click' === event.type || ('keypress' === event.type && event.keyCode === 13)) {
    // update the 'updatableQuantity' to cart-item.
    var cartItemTotal = 0;
    for(var index=0; index < data.shippingGroupRelationships().length; index++) {
      cartItemTotal = parseInt(cartItemTotal) + parseInt(data.shippingGroupRelationships()[index].updatableQuantity());
    }
    data.updatableQuantity(parseInt(cartItemTotal));

    if(data.updatableQuantity && data.updatableQuantity.isValid()) {
      if(this.storeConfiguration.isLargeCart() === true){
        var quantityChange=data.updatableQuantity()-data.quantity();
        var price=data.productData().childSKUs[0].salePrice ? data.productData().childSKUs[0].salePrice : data.productData().childSKUs[0].listPrice;
        this.cart().numberOfItems(this.cart().numberOfItems()+quantityChange);
        data.quantity(data.updatableQuantity());
        data.itemTotal(data.itemTotal()+price*quantityChange);
        shippingGroup.price(shippingGroup.price()+price*quantityChange);
        shippingGroup.quantity(shippingGroup.quantity()+quantityChange);
        this.cart().updateAllItemsArray();
        this.cart().subTotal(this.cart().subTotal()+price*quantityChange);
        this.cart().shippingSurcharge(this.cart().shippingSurcharge()+data.shippingSurcharge*quantityChange);

this.cart().total(this.cart().total()+(price+data.productData().shippingSurcharge)*quantityChange);
        this.cart().userActionPending(true);
        this.cart().saveCartCookie();
        return true;
      }
      $.Topic(pubsub.topicNames.CART_UPDATE_QUANTITY).publishWith(
      data.productData(),[{"message":"success", "commerceItemId": data.commerceItemId, "shippingGroup": shippingGroup}]);

      var button = $('#' + id);
      button.focus();
      button.fadeOut();
    }
  } else {
    this.quantityFocus(data, event);
  }

  return true;
},

Product Details widget

Adding the product from Product Details widget will not trigger pricing. The added item updates in the cart view model first and after that, pricing triggers. The new methods are only called during nested view model logic when the large cart flag is turned on, just before system shunts the pricing. This ensures that the shopper has a view of the approximate prices of the items added to cart.

When calculating prices, sale and list prices are used. In the absence of pricing data, you can override application.js to update the specific item price using other special parameters such as bundled pricing, volume pricing, or tiered pricing from existing product data.

updateItemPriceForLargeCart

The following code is the method to update items during the “single item-add” operation of updateItemPriceForLargeCart.

CartViewModel.prototype.updateItemPriceForLargeCart = function(data,cartItem){
  var self =this;
  var price = data.childSKUs[0].salePrice ? data.childSKUs[0].salePrice :data.childSKUs[0].listPrice;
  cartItem.itemTotal(cartItem.itemTotal()+price*data.orderQuantity);
};

updateCartItemDataForLargeCart

When calculating prices, sale and list prices are used. You can implement other scenarios based on your own requirements.

The following code shows the method to update the cart properties during the “single item-add” operation:

CartViewModel.prototype.updateCartItemDataForLargeCart = function(data){
  var self =this;
  var price = data.childSKUs[0].salePrice ? data.childSKUs[0].salePrice :data.childSKUs[0].listPrice;
  self.numberOfItems(self.numberOfItems()+data.orderQuantity);
  self.events=[];
  self.shippingSurcharge(self.shippingSurcharge()+data.shippingSurcharge);
  self.subTotal(price*data.orderQuantity+self.subTotal());
  self.total(self.total()+price+data.shippingSurcharge);
  self.saveCartCookie();
};

You can extend this process by adding any other business related view model properties.

Adding multiple items at once (Quick order widget, Copy order, Purchase list)

Adding multiple items at once will not trigger pricing calls when large cart flag is true. Without modification, added items update in cart view model first and then, pricing triggers. The new methods are called during the nested view model logic when large cart flag is turned on, just before system shunts the pricing. This ensures that the shopper has a view of the approximate prices of the item added to cart.

When calculating prices, sale and list prices are used. In the absence of pricing data, you can override these from application.js to update the item price taking into account such special parameters such as bundled pricing, volume pricing, or tiered pricing from existing product data.

updateCartItemsDataForLargeCart

The following code is a method to update the multiple item during “multiple item-add” operation:

    CartViewModel.prototype.updateCartItemsDataForLargeCart = function(data){
  var self =this;

  //NOT REUSING updateCartItemDataForLargeCart to minimise mulple notifications due to multiple update
  var i,newQuantitiesAddedToCart=0,newAddedshippingSurcharge=0,newAddedsubTotal=0;
  for(i=0;i<data.length;i++){
    var price = data[i].childSKUs[0].salePrice ? data[i].childSKUs[0].salePrice :data[i].childSKUs[0].listPrice;
    newQuantitiesAddedToCart+=data[i].orderQuantity;
    newAddedshippingSurcharge+=data[i].shippingSurcharge;
    newAddedsubTotal+=price*data[i].orderQuantity;
  }
 self.numberOfItems(newQuantitiesAddedToCart+self.numberOfItems());
 self.events=[];
 self.shippingSurcharge(self.shippingSurcharge()+newAddedshippingSurcharge);
 self.subTotal(newAddedsubTotal+self.subTotal());
 self.total(self.total()+newAddedsubTotal+newAddedshippingSurcharge);
 self.saveCartCookie();
 if (self.callbacks && self.callbacks.hasOwnProperty(ccConstants.ADD_ITEMS_SUCCESS_CB)
     && typeof self.callbacks[ccConstants.ADD_ITEMS_SUCCESS_CB] === 'function') {
   self.callbacks[ccConstants.ADD_ITEMS_SUCCESS_CB](data);
 }
}

When calculating prices, sale and list prices are used. You can implement other scenarios based on your own requirements.

updateCartItemDataForLargeCart

The following code is a method to update the cart properties during a “multiple item-add” operation:

CartViewModel.prototype.updateCartItemDataForLargeCart = function(data){
      var self =this;
      var price = data.childSKUs[0].salePrice ? data.childSKUs[0].salePrice :data.childSKUs[0].listPrice;
      self.numberOfItems(self.numberOfItems()+data.orderQuantity);
      self.events=[];
      self.shippingSurcharge(self.shippingSurcharge()+data.shippingSurcharge);
      self.subTotal(price*data.orderQuantity+self.subTotal());
      self.total(self.total()+price+data.shippingSurcharge);
      self.saveCartCookie();
    };

You can implement other scenarios based on your own requirements. In the Quick Order widget, instead of using the existing default widget batching logic, you can invoke view model methods to add multiple items. You will need to modify the Quick Order widget /Purchase List widget to accommodate large carts.