Create a widget to support saved carts

To enable and manage saved carts for a registered individual or account-based shopper, you write a custom widget to include on your storefront’s Cart pages.

For detailed information about creating widgets, see Create a Widget.

This topic includes the following sections:

Create the widget structure for the saved-carts sample widget

Widgets that include user interface elements must include display templates. The following shows an example of the files and directories in a saved-carts widget. Notice that it includes two display templates; these are described in detail in Create template files for the saved-carts sample widget.

MultiCartDemoWidget/
    ext.json
    widget/
        multiCart_v1/
            widget.json
            js/
                multi-cart.js
            less/
                widget.less
            locales/
            en/
                ns.multicart.json
            templates/
                display.template
                pagination.template

Because this widget includes user interface elements that allow the shopper to work with saved carts, you must not create it as a global widget. Set the global property in the widget.json file to false:

"global": false

The JavaScript code you write extends the multiCartViewModel class. For more information about the widget structure and the contents of the ext.json and widget.json files, see Create the widget structure .

Create the JavaScript file for the saved-carts sample widget

The widget’s JavaScript file includes functions that let shoppers save, retrieve, merge, and delete carts:

  • listIncompleteOrders gets all the incomplete orders (saved carts) associated with the logged-in shopper profile.
  • createOrderWithTemporaryItems creates an incomplete order for a shopper who has not logged in.
  • createNewIncompleteCart crates a new saved cart for the logged-in shopper
  • loadParticularIncompleteOrder displays a saved cart.
  • mergeWithParticularIncompleteOrder merges a saved cart with the current cart.
  • deleteParticularIncompleteOrders deletes a saved cart.

The following example shows sample JavaScript that implements the saved-cart functionality:

define(

  //-------------------------------------------------------------------
  // DEPENDENCIES
  //-------------------------------------------------------------------
  ['knockout', 'pubsub', 'notifier', 'CCi18n', 'ccConstants',
 'navigation', 'ccRestClient','viewModels/multiCartViewModel'],

  //-------------------------------------------------------------------
  // MODULE DEFINITION
  //-------------------------------------------------------------------
  function(ko, pubsub, notifier, CCi18n, CCConstants, navigation,
 ccRestClient, MultiCartViewModel) {

    "use strict";

    return {

      WIDGET_ID:        "multiCart",
      display: ko.observable(false),
      currentCartName: ko.observable(""),
      fetchSize: ko.observable(10),
      cartNameSearch: ko.observable(""),

      onLoad: function(widget) {
    	var self = this;
        widget.listingViewModel = ko.observable();
        widget.listingViewModel(new MultiCartViewModel());
        widget.listingViewModel().itemsPerPage = widget.fetchSize();
        widget.listingViewModel().blockSize = widget.fetchSize();

    	$.Topic(pubsub.topicNames.USER_AUTO_LOGIN_SUCCESSFUL)
.subscribe(function(){
    		widget.listIncompleteOrders();
    	});
    	$.Topic(pubsub.topicNames.USER_LOGIN_SUCCESSFUL).subscribe(function(){
    	widget.listIncompleteOrders();
        if(widget.cart().items().length>0){
        	widget.cart().isCurrentCallInProgress = true;
        	widget.createOrderWithTemporaryItems();
        }
        });

    	$.Topic(pubsub.topicNames.CART_PRICE_SUCCESS).subscribe(function(){
    	if(widget.user().loggedIn()){
    		widget.listIncompleteOrders();
            widget.currentCartName("");
    	}
        });
    	$.Topic(pubsub.topicNames.CART_DELETE_SUCCESS).subscribe(function(){
        	if(widget.user().loggedIn()){
        		widget.listIncompleteOrders();
        	}
            });


        widget.listOfIncompleteOrders = ko.computed(function() {
            var numElements, start, end, width;
            var rows = [];
            var orders;
              var startPosition, endPosition;
              // Get the orders in the current page
              startPosition = (widget.listingViewModel().currentPage()
 - 1) * widget.listingViewModel().itemsPerPage;
              endPosition = startPosition +
 parseInt(widget.listingViewModel().itemsPerPage,10);
              orders = widget.listingViewModel().data.slice(startPosition,
 endPosition);

            if (!orders) {
              return;
            }
            numElements = orders.length;
            width = parseInt(widget.listingViewModel().itemsPerRow(), 10);
            start = 0;
            end = start + width;
            while (end <= numElements) {
              rows.push(orders.slice(start, end));
              start = end;
              end += width;
            }
            if (end > numElements && start < numElements) {
              rows.push(orders.slice(start, numElements));
            }
            return rows;
          }, widget);
      },

      beforeAppear: function (page) {
          var widget = this;
          if (widget.user().loggedIn() == false) {
          	widget.display(false);
          } else {
          	widget.listIncompleteOrders();
          	widget.display(true);
          }
        },

      /**
       * @function
       * @name multi-cart#listIncompleteOrders
       *
       * call to list incomplete orders for logged in profile.
       */
      listIncompleteOrders : function() {
    	 var self = this;
    	 var inputDate ={};
    	 //inputDate[CCConstants.SORTS] = "lastModifiedDate:desc";
    	 self.listingViewModel().sortProperty = "lastModifiedDate:desc";
    	 //set self.listingViewModel().cartNameSearch
    	 //string to search based on cartname
    	 if (self.user() && !self.user().loggedinAtCheckout()) {
    	 self.listingViewModel().refinedFetch();
    	 }
      },

      /**
       * @function
       * @name multi-cart#createOrderWithTemporaryItems
       *
       * method to create new incomplete cart with anonymous cart items
       */
      createOrderWithTemporaryItems : function() {
    	 var self = this;
    	 self.cart().createNewCart(true);
    	 self.cart().validateServerCart();
    	 self.cart().getProductData();
    	 self.cart().createCurrentProfileOrder();
      },

      /**
       * @function
       * @name multi-cart#createNewIncompleteCart
       */
      createNewIncompleteCart : function() {
         var self = this;
         self.cart().createNewCart(true);
 ccRestClient.setStoredValue(CCConstants.LOCAL_STORAGE_CREATE_NEW_CART,true);
         self.cart().emptyCart();
         self.user().orderId('');
         self.user().persistedOrder(null);
         self.user().setLocalData('orderId');
         self.currentCartName("");
       },


       deleteParticularIncompleteOrders: function(pOrderId) {
         var self = this;
         self.cart().deleteParticularIncompleteOrders(pOrderId);
       },

       /**
        * @function
        * @name UserViewModel#loadParticularIncompleteOrder
        */
        loadParticularIncompleteOrder : function(pOrderId) {
        var self = this;
          self.cart().loadCartWithParticularIncompleteOrder(pOrderId);
        },
       /**
        * @function
        * @name UserViewModel#mergeWithParticularIncompleteOrder
        */
        mergeWithParticularIncompleteOrder : function(pOrderId) {
        var self = this;
          self.cart().mergeCartWithParticularIncompleteOrder(pOrderId);
        },

        saveIncompleteCart : function(pOrderId) {
        var self = this;
          self.cart().cartName(self.currentCartName());
          self.cart().priceItemsAndPersist();
        }

    };
  }
);

Create template files for the saved-carts sample widget

The widget’s display.template file contains code that renders a page where shoppers can see a list of saved carts, display a saved cart, merge a saved cart with the current cart, or delete a saved cart.

The widget’s display.template file contains the following code for rendering the page:

<!-- ko if: display-->
 <!-- ko with: cart -->
 <!-- ko if:($parent.user().loggedInUserName() && ($parent.user().loggedIn()
 || $parent.user().isUserSessionExpired()))-->
     <div id="CC-multiCart">
          <div class="row col-md-12">
           <h3 class="modal-title text-center">Your Saved Carts</h3>
          </div>
             <div id="CC-multicartorder-table-md-lg-sm" class="row hidden-xs">
             <section id="orders-info" class="col-md-12" >
              <table class="table" >
              <thead>
          <tr>
       <th class="col-md-2 " scope="col" data-bind="widgetLocaleText :
 'orderNumber'"></th>
            <th class="col-md-2 " scope="col" data-bind="widgetLocaleText:
 'cartName'"></th>
            <th class="col-md-2 " scope="col" data-bind="widgetLocaleText:
 'orderTotal'"></th>
            <th class="col-md-3" scope="col"><div class="sr-only"></div></th>
            <th class="col-md-3" scope="col"><div class="sr-only"></div></th>
            <th class="col-md-3 " scope="col" data-bind="widgetLocaleText:
 'delete'"></th>
                 </tr>
                </thead>
                <!-- ko if: $parent.listOfIncompleteOrders().length > 0 -->
                <tbody data-bind="foreach:$parent.listOfIncompleteOrders">
                <tr>
            <td class="col-md-2" data-bind="text : $data[0].orderId"
 scope="row"></td>
            <td class="col-md-2" data-bind="text : $data[0].cartName"
 scope="row"></td>
            <td class="col-md-2" data-bind="currency: {price: $data[0].total,
 currencyObj: $data[0].priceListGroup.currency}"  scope="row"></td>
            <td class="col-md-3">
              <button class="cc-button-primary pull-right" href="#"
 data-dismiss="modal"
 data-bind="click:$parents[1].loadParticularIncompleteOrder.bind($parents[1],
$data[0].orderId)" >
                <span data-bind="widgetLocaleText: 'LoadThis'
 ,attr: {title: 'Clicking this will clear the cart and load this order'}">
</span>
              </button>
            </td>
            <td class="col-md-3">
              <button class="cc-button-primary pull-right" href="#"
 data-dismiss="modal" data-bind="click:$parents[1].mergeWithParticularIncompleteOrder.bind($parents[1],
$data[0].orderId)" >
                <span data-bind="widgetLocaleText: 'MergeInto',attr:
 {title: 'Clicking this will merge the cart items into this order'}"></span>
             </button>
            </td>
            <td class="col-md-3">
            <button class="cc-button-primary pull-right" data-bind="click:$parents[1].deleteParticularIncompleteOrders.bind($parents[1],
$data[0].orderId)" >
                <span data-bind="widgetLocaleText: 'delete' ,attr:
 {title: 'Clicking this will delete this cart'}"></span>
              </button>
            </td>
               </tr>
               </tbody>
               <!-- /ko -->
               <!-- ko if: $parent.listOfIncompleteOrders().length == 0 -->
               <tbody>
               <tr>
               <td colspan="5">
                  <span data-bind="widgetLocaleText:'noOrders'">
                  </span></td>
               </tr>
               </tbody>
               <!-- /ko -->
               </table>
              </section>
              </div>
                <!-- ko with: $parent.listingViewModel -->
  <div id="cc-paginated-controls-bottom"
 class="row col-md-12 visible-xs visible-sm visible-md visible-lg">
    <div data-bind="visible : (totalNumberOfPages() > 1)">
      <div>
        <div data-bind="template: { name:
$parents[1].templateAbsoluteUrl('/templates/paginationControls.template')
 , templateUrl: ''}"
          class="row pull-right"></div>
      </div>
    </div>
  </div>
  <!-- /ko -->
      <div class="row col-md-12">
        <!-- ko if: $data.items().length == 0 -->
         <button type="button" class="btn btn-default"
 data-bind="click:$parent.createNewIncompleteCart.bind($parent)">
Create New</button>
         <!-- /ko -->
         <!-- ko if: $data.items().length > 0 -->
         <section id="cart-details-heading" >
         <h3 class="modal-title text-center"
 data-bind="widgetLocaleText:'currentCart'"></h3>
         </section>
         <section id="cart-info" class="col-md-12" >
         Cart Name: <span data-bind="text: cartName"></span>
         <table class="table" >
               <thead>
                 <tr>
            <th class="col-md-3 " scope="col" data-bind="widgetLocaleText:
 'referenceId'"></th>
            <th class="col-md-3 " scope="col" data-bind="widgetLocaleText:
 'quantity'"></th>
            <th class="col-md-3 " scope="col" data-bind="widgetLocaleText:
 'total'"></th>
                 </tr>
                </thead>
                <tbody data-bind="foreach:$data.items" >
                <tr>
                   <td class="col-md-3 text-left" data-bind="text :catRefId"
 scope="row"></td>
                   <td class="col-md-3 text-left" data-bind="text :quantity()"
 scope="row"></td>
                   <td class="col-md-3 text-left" data-bind="text :itemTotal()"
 scope="row"></td>
                </tr>
                </tbody>
                  </table>

                  <input type="text" class="col-md-4 form-control"
 name="currentCartName" id="currentCartName" data-bind="value:
 $parent.currentCartName, widgetLocaleText : {value:'cartNameText',
 attr:'placeholder'}">
                  <button type="button" class="btn btn-default"
 data-bind="click:$parent.saveIncompleteCart.bind($parent)">Save Cart</button>
                  </section>
                  <section id="footer-buttons">
         <button type="button" class="btn btn-default" data-bind="click:$parent.createNewIncompleteCart.bind($parent)">Create New</button>
         </section>
         <!-- /ko -->
        </div>
   </div>
     <!-- /ko -->
   <!-- /ko -->
 <!-- /ko -->

The widget’s display.template calls another template file, paginationControls.template. This template file contains .the following code for rendering multiple pages when the list of carts is long:

<div class="btn-group">

    <a href="#" class="btn btn-default" data-bind="click:
 getFirstPage, widgetLocaleText :
 {value:'goToFirstPageText', attr:'aria-label'},
 makeAccess: {readerText: 'Go to first page &nbsp;', cssContent: 'on'},
 css: { disabled: $data.currentPage() == 1 }, widgetLocaleText:
 'goToFirstPagePaginationSymbol'" >&lt;&lt;</a>
    <a href="#" class="btn btn-default" data-bind="click: decrementPage,
 widgetLocaleText : {value:'goToPreviousPageText', attr:'aria-label'},
 makeAccess: {readerText: 'Go to previous page &nbsp;', cssContent: 'on'},
 css: { disabled: $data.currentPage() == 1 }, widgetLocaleText:
 'goToPreviousPagePaginationSymbol'" rel="prev">&lt;</a>

    <!-- ko foreach: pages -->
      <a href="#" class="btn btn-default" data-bind="click:
 $parent.changePage.bind($parent, $data), css: {active:
 $data.pageNumber===$parent.clickedPage() }">
        <!-- ko if: $data.selected === true -->
          <span data-bind="widgetLocaleText : {value:'activePageText',
 attr:'aria-label'}, makeAccess: {readerText: 'Active page is &nbsp;',
 cssContent: 'on'}"></span>
        <!-- /ko -->
        <!-- ko if: $data.selected === false -->
          <span data-bind="widgetLocaleText : {value:'clickToViewText',
 attr:'aria-label'}, makeAccess: {readerText: 'Click to view page &nbsp;',
 cssContent: 'on'}"></span>
        <!-- /ko -->
        <span data-bind="ccNumber: $data.pageNumber"></span>
      </a>
    <!-- /ko -->

    <a href="#" class="btn btn-default" data-bind="click: incrementPage,
 widgetLocaleText : {value:'goToNextPageText', attr:'aria-label'}, makeAccess:
 {readerText: 'Go to next page &nbsp; ', cssContent: 'on'}, css: { disabled:
 currentPage() == $data.totalNumberOfPages() }, widgetLocaleText:
 'goToNextPagePaginationSymbol'" rel="next">&gt;</a>
    <a href="#" class="btn btn-default" data-bind="click: $data.getLastPage,
 widgetLocaleText : {value:'goToLastPageText', attr:'aria-label'}, makeAccess:
 {readerText: 'Go to last page &nbsp;', cssContent: 'on'}, css: { disabled:
 currentPage() == $data.totalNumberOfPages() }, widgetLocaleText:
 'goToLastPagePaginationSymbol'">&gt;&gt;</a>

</div>