Upgrading a template with a customized sessions and history widget

This upgrade guide documents the steps required to upgrade a template for an existing built search that used a customized session search and click history or customised session cart tools widgets.

This guide provides the steps required to update a template that includes a customized sessions and history widget. If you are using a template that includes a default sessions and history widget please see: upgrading a template with a default sessions and history widget.

Upgrade path

The upgrade process involves two steps: replacing JavaScript libraries and updating Freemarker templates.

The specifics of each step depend on the upgrade path, however. Follow the procedure for the upgrade path being taken.

Upgrade from Funnelback 15 to Funnelback 16

Replace JavaScript libraries

Prior to Funnelback 15.24, the widget was delivered using an angularJS based session library. As of Funnelback 15.24, this was split into two independent widgets; one to handle search and click history and another to handle cart functionality.

Because of this the funnelback-session-1.0.0.js library uses AngularJS. It has been superseded by newer libraries using Handlebars. funnelback-session-1.0.0.js should be removed.

Regarding upgrading from Funnelback 15
  1. The example below assumes the Funnelback Session JavaScript library includes the version number in the file-name. In some older iterations, however, this library did not append the version number: the file was named funnelback-session.js.

    The example below assumes the library filename includes the version number.

  2. The AngularJS library was previously used only for the Funnelback session tool, and, as noted above, it should also be removed.

    It is possible, however, for custom functionality relying on this library to be added to a customized template. If that is the case, the AngularJS library should not be removed.

    The example below assumes the AngularJS library is removed.

Replace:

<script src="${GlobalResourcesPrefix}thirdparty/angular-1.0.7/angular.js"></script>
<script src="${GlobalResourcesPrefix}thirdparty/angular-1.0.7/angular-resource.js"></script>
<script src="${GlobalResourcesPrefix}js/funnelback-session-1.0.0.js"></script>

with:

<script nomodule type="text/javascript" src="${GlobalResourcesPrefix}thirdparty/es6-promise-4.2.5/es6-promise.auto.min.js"></script>
<script type="text/javascript" src="${GlobalResourcesPrefix}thirdparty/handlebars-4.7/handlebars.min.js"></script>
<script type="text/javascript" src="${GlobalResourcesPrefix}js/funnelback.session-cart-0.1.min.js"></script>
<script type="text/javascript" src="${GlobalResourcesPrefix}js/funnelback.session-history-0.1.min.js"></script>

<script type="text/javascript">
  var flbSessionCart = new Funnelback.SessionCart({collection: '${question.collection.id}'});
  var flbSessionHistory = new Funnelback.SessionHistory({collection: '${question.collection.id}'});
</script>

Update Freemarker templates

Remove angular styles from <head> section

From the <style> tag located in the <head> section, remove the following AngularJS specific classes:

  • [ng\:cloak]

  • [ng-cloak]

  • [data-ng-cloak]

  • [x-ng-cloak]

  • .ng-cloak

  • .x-ng-cloak { display: none !important; }

Remove angular attribute data-ng-app

The new session history and cart Javascript libraries don’t require any apps defined. Remove the following AngularJS apps:

  • data-ng-app="Funnelback"

Remove angular attribute data-ng-controller

The new session history and cart Javascript libraries don’t require any controllers defined. Remove the following AngularJS controllers:

  • data-ng-controller="DefaultCtrl"

  • data-ng-controller="SearchHistoryCtrl"

  • data-ng-controller="ClickHistoryCtrl"

  • data-ng-controller="CartCtrl"

Removing the above may lead to empty HTML elements which can be removed.

Remove angular attribute data-ng-class

The new session history and cart Javascript libraries control the HTML elements which used to be managed by the AngularJS attribute data-ng-class. Remove the data-ng-class attributes from HTML elements.

Remove angular attribute ng-cloak and data-ng-cloak

The new session history and cart Javascript libraries don’t require ng-cloak class or the data-ng-cloak and should be removed.

Removing the above may lead to HTML elements with no classes or attributes which can be removed if they are not required by the css styling or to manage other HTML elements.

Replace angular attribute data-ng-show

The new session history and cart Javascript libraries use classes and Javascript to show, toggle and hide HTML elements which used to be managed by the AngularJS attribute data-ng-show. How these attributes are replaced depends on what functionality the attribute provides. These are outlined below:

  • If the attribute displays the 'zero for an empty cart' then it and the associated HTML can be removed as this is now done by the session cart Javascript. As seen in the example below. Remove: <span data-ng-show>0</span>.

  • If the attribute indicates where to show 'search results' then remove the attribute and add the class search-results-content, as seen in the example below:

    <section class="search-results pt-3" data-ng-show="isDisplayed('results')" aria-label="Search Results">

    becomes

    <section class="search-results pt-3 search-results-content" aria-label="Search Results">
  • If the attribute indicates where to show the history breadcrumbs then remove the attribute and add the class session-history-breadcrumb, as seen in the example below:

    <div class="breadcrumb" data-ng-controller="SearchHistoryCtrl" data-ng-show="!searchHistoryEmpty">

    becomes

    <div class="breadcrumb session-history-breadcrumb">
  • If the attribute indicates where to show the list of clicks from history then remove the attribute and add the class session-history-click-results. As this is now done by the session history Javascript the Freemarker list element must be an ancestor of the element with class session-history-click-results. Use the Freemarker item element around the repeated HTML, as seen in the example below:

    <div data-ng-show="!clickHistoryEmpty && <@fb.HasClickHistory />">
      ...
      <ul>
        <#list session.clickHistory as h>
          <li> ... </li>
        </#list>
      </ul>
    </div>

    becomes

    <#list session.clickHistory>
      <div class="session-history-click-results">
        ...
        <ul>
          <#items as h>
    	<li> ... </li>
          </#items>
        </ul>
      </div>
    </#list>
  • If the attribute indicates where to show information when there is no click history then remove the attribute and add the class session-history-click-empty, as seen in the example below:

    <div class="card" data-ng-show="clickHistoryEmpty || !<@fb.HasClickHistory />">
      ...
    </div>

    becomes

    <div class="card session-history-click-empty">
      ...
    </div>
  • If the attribute indicates where to show the list of searches from history then remove the attribute and add the class session-history-search-results. As this is now done by the session history Javascript. Also rearrange the Freemarker list element must be an ancestor of the element with class session-history-search-results. Use the Freemarker item element around the repeated HTML, as seen in the example below:

    <div data-ng-show="!searchHistoryEmpty && <@fb.HasSearchHistory />">
      ...
      <ul>
        <#list session.searchHistory as h>
          <li> ... </li>
        </#list>
      </ul>
    </div>

    becomes

    <#list session.searchHistory>
      <div class="session-history-search-results">
        ...
        <ul>
          <#items as h>
    	<li> ... </li>
          </#items>
        </ul>
      </div>
    </#list>
  • If the attribute indicates where to show information when there is no search history then remove the attribute add the class session-history-search-empty, as seen in the example below:

    <div class="card" data-ng-show="searchHistoryEmpty || !<@fb.HasSearchHistory />">
      ...
    </div>

    becomes

    <div class="card session-history-search-empty">
      ...
    </div>
  • If the attribute determines that it should only show information when the data model element is not empty then remove the attribute and wrap the element in a Handlebars if statement, as seen in the example below:

    <img data-ng-show="item.metaData.I" class="img-fluid float-right ng-cloak" alt="Thumbnail for {{result.title}}" data-ng-src="{{item.listMetadata.I[0]}}">

    becomes

    {{#if metaData.I}}
      <img class="img-fluid float-right" alt="Thumbnail for {{result.title}}" src="{{#byIndex 0 "|"}}{{metaData.I}}{{/byIndex}}"/>
    {{/if}}

Replace angular attributes data-ng-switch and data-ng-switch-when.

The new session history and cart Javascript libraries use Handlebars in the template section and therefore the attributes data-ng-switch and data-ng-switch-when are removed and replaced with Handlebars notation. To achieve the same result as the AngularJS a Handlebars helper is added using the Javascript below. The Javascript goes in the script element after the configuration of Funnelback.SessionCart, as seen in the example below:

<script type="text/Javascript">
  var flbSessionCart = new Funnelback.SessionCart({
    ...
  });

  flbSessionCart.Handlebars.registerHelper({
    // usage: {{#if (eq collection "test")}}
    eq: function (v1, v2) {
      return v1 === v2;
    }
  });
</script>

As well as adding the above Javascript the details data-ng-switch and data-ng-switch-when attributes are removed and used to create the handle bars notation element using data-ng-switch=<value1> and data-ng-switch-when=<value2> and adding {{ #if (eq <value1> <value2>) }}, as seen in the example below:

<div data-ng-switch="item.collection">
  <#-- Output templates for all results depending on their source collection, per configured in collection.cfg -->
  <#list question.collection.configuration.valueKeys() as key>
    <#if key?starts_with("stencils.template.shortlist.")>
      <#assign itemCollection = key?substring("stencils.template.shortlist."?length)>
      <#assign itemNamespace = question.collection.configuration.value(key)>
      <#if .main[itemNamespace]??>
        <div data-ng-switch-when="${itemCollection}">
          <@.main[itemNamespace].ShortListTemplate />
        </div>
      </#if>
    </#if>
  </#list>
  ...
</div>

becomes

{{#if collection}}
      <#list question.collection.configuration.valueKeys() as key>
	    <#if key?starts_with("stencils.template.shortlist.")>
          <#assign itemCollection = key?substring("stencils.template.shortlist."?length)>
          <#assign itemNamespace = question.collection.configuration.value(key)>
          <#if .main[itemNamespace]??>
            {{#if (eq collection "${itemCollection}")}}
              <@.main[itemNamespace].ShortListTemplate />
            {{/if}}
          </#if>
        </#if>
      </#list>
{{/if}}

Replace angular attribute data-ng-repeat:

The cart Javascript will add the ul element as well as a li element for each item to display so any classes for these elements will need to be added using the Javascript below. The Javascript goes in the script element after the configuration of Funnelback.SessionCart:

<script type="text/Javascript">
	  var flbSessionCart = new Funnelback.SessionCart({
    ...
  });

	  function styleItems() {
	    var boxItems = document.getElementsByClassName('flb-cart-box-item');
	    for (var i = 0; i < boxItems.length; i++) {
  	      boxItems.item(i).classList.add('mb-3');
	    }
	  }

	  styleItems();
</script>

As well as adding the above Javascript the details of the HTML element which has the data-ng-repeat attribute and it’s children are used to configure the 'item' section of the Funnelback.SessionCart which creates the individual cart result item HTML elements. The child HTML elements of the li will form the template input for Funnelback.SessionCart item object. Since the template has to be a single line the Freemarker <@compress single_line=true> can be used if HTML formatting is retained. If display is defined in a macro object then it can be separated to a new macro which the template JSON object can point to the new macro. Alternatively the HTML can be added to template JSON object directly. The example below uses a new macro and assigns this to the template.

<#macro Cart>
  <#if question.collection.configuration.valueAsBoolean("ui.modern.session")>
    <section id="search-cart" class="search-cart pt-3" data-ng-cloak data-ng-show="isDisplayed('cart')" data-ng-controller="CartCtrl" aria-label="Search shortlist">
      <div class="container">
    <div class="row">
      <div class="col-md-12">
        ...
          <div class="row search-results mt-3">
            <div class="col-md-12">
              <ul class="list-unstyled">
                <li data-ng-repeat="item in cart" class="mb-3">
                  <div data-ng-switch="item.collection">
                    <#-- Output templates for all results depending on their source collection, per configured in collection.cfg -->
                    <#list question.collection.configuration.valueKeys() as key>
                      <#if key?starts_with("stencils.template.shortlist.")>
                        <#assign itemCollection = key?substring("stencils.template.shortlist."?length)>
                        <#assign itemNamespace = question.collection.configuration.value(key)>
                        <#if .main[itemNamespace]??>
                         <div data-ng-switch-when="${itemCollection}">
                           <@.main[itemNamespace].ShortListTemplate />
                         </div>
                        </#if>
                      </#if>
                    </#list>
                    ...
                    </div>
                  </div>
                </li>
              </ul>
            </div>
          </div>
      </div>
    </div>
      </div>
    </section>
  </#if>
</#macro>

becomes

<#macro Cart>
  <#if question.collection.configuration.valueAsBoolean("ui.modern.session")>
    <section class="search-cart" aria-label="Search shortlist">
      <div class="container">
        <div class="row search-results mt-3">
          <div id="search-cart" class="col-md-12 pt-3"></div>
        </div>
      </div>
    </section>
  </#if>
</#macro>

<#macro CartItem>
  <@compress single_line=true>
    {{#if collection}}
      <#list question.collection.configuration.valueKeys() as key>
        <#if key?starts_with("stencils.template.shortlist.")>
          <#assign itemCollection = key?substring("stencils.template.shortlist."?length)>
          <#assign itemNamespace = question.collection.configuration.value(key)>
          <#if .main[itemNamespace]??>
            {{#if (eq collection "${itemCollection}")}}
              <@.main[itemNamespace].ShortListTemplate />
            {{/if}}
          </#if>
        </#if>
      </#list>
    {{/if}}
  </@compress>
</#macro>

with

var flbSessionCart = new Funnelback.SessionCart({
  ...
  item: {
    selector: '#search-results',
    template: '<@history_cart.CartItem />',
  }
});

Replace angular attribute data-ng-click:

The new session history and cart Javascript libraries use classes and Javascript to show, toggle and hide HTML elements which used to be managed by the AngularJS attribute data-ng-click. How these attributes are replaced depends on what functionality the attribute provides. These are outlined below

  • If the attribute toggles the session history then remove the attribute and add the class session-history-toggle, as seen in the example below:

    <a data-ng-class="{active: isDisplayed('history')}" data-ng-click="toggleHistory()" href="#" aria-label="Search History">
      <span class="fal fa-history"></span> History
    </a>

    becomes

    <a href="#" class="session-history-toggle" aria-label="Search History">
      <span class="fal fa-history"></span> History
    </a>
  • If the attribute displays the session history then remove the attribute and add the class session-history-show. If you want the link hidden if the history is empty you can add the class session-history-link, as seen in the example below

    <a title="Click history" href="#" class="${class}" data-ng-click="toggleHistory()">
      Last visited ${prettyTime(session.getClickHistory(result.indexUrl).clickDate)}
    </a>

    becomes

    <a title="Click history" href="#" class="${class} session-history-show session-history-link">
      Last visited ${prettyTime(session.getClickHistory(result.indexUrl).clickDate)}
    </a>
  • If the attribute hides the session history then remove the attribute and add the class session-history-hide, as seen in the example below:

    <a href="#" data-ng-click="hideHistory()"><span class="fal fa-arrow-left"></span> Back to results</a>

    becomes

    <a href="#" class="session-history-hide"><span class="fal fa-arrow-left"></span> Back to results</a>
  • If the attribute clears the session click history then remove the attribute and add the class session-history-clear-click, as seen in the example below:

    <button class="btn btn-danger btn-sm float-right" title="Clear click history" data-ng-click="clear('Your history will be cleared')">
      <span class="fal fa-times"></span> Clear
    </button>

    becomes

    <button class="btn btn-danger btn-sm float-right session-history-clear-click" title="Clear click history">
      <span class="fal fa-times"></span> Clear
    </button>
  • If the attribute clears the session search history then remove the attribute and add the class session-history-clear-search, as seen in the example below:

    <button class="btn btn-danger btn-sm float-right" title="Clear click history" data-ng-click="clear('Your history will be cleared')">
      <span class="fal fa-times"></span> Clear
    </button>

    becomes

    <button class="btn btn-danger btn-sm float-right session-history-clear-click" title="Clear click history">
      <span class="fal fa-times"></span> Clear
    </button>
  • If the attribute displays the cart count then any classes will need to be added using the Javascript in the example below. The Javascript goes in the script element after the configuration of Funnelback.SessionCart.

    <script type="text/Javascript">
      var flbSessionCart = new Funnelback.SessionCart({
        ...
      });
    
      function styleCartCountItems() {
        var cartCountItems = document.getElementsByClassName('.flb-cart-count');
        for (var i = 0; i < cartCountItems.length; i++) {
          cartCountItems.item(i).classList.add(<comma seperated classes>);
        }
      }
    
      styleCartCountItems();
    </script>

    As well as adding the above Javascript the details of the HTML element which has the data-ng-click attribute and it’s children are used to configure the cartCount section of the Funnelback.SessionCart which creates the cart count HTML elements. To ensure the Javascript works add the class flb-cart-count to the parent of the HTML element which has the data-ng-click attribute. Icons and labels are moved to the cartCount section as seen below.

    <small>
      <a data-ng-class="{active: isDisplayed('cart'), disabled: cart.length < 1}" data-ng-click="toggleCart()" title="{{cart.length}} item(s) in your shortlist" href="#" >
        <span class="fal fa-star"></span> Shortlist (<span class="ng-cloak">{{cart.length}}</span>)
      </a>
    </small>

    becomes

    <small class="flb-cart-count" ></small>

    with

    var flbSessionCart = new Funnelback.SessionCart({
      ...
      cartCount: {
        selector: '.flb-cart-count',
        icon: 'fal fa-star',
        isLabel: true,
        label: 'Shortlist',
        template: '{{>icon-block}} {{>label-block}} (<span>{{count}}</span>)'
      }
    });
    Additional HTML elements may need to be added to the template depending on the original HTML.
  • If the attribute is used to add and remove search result items from the cart then any classes will need to be added using the Javascript below. The Javascript also includes adding an event listener to the click event of the element which displays the cart results screen. The Javascript goes in the script element after the configuration of Funnelback.SessionCart.

    <script type="text/Javascript">
    	  var flbSessionCart = new Funnelback.SessionCart({
        ...
      });
    
      function styleCartClickItems() {
        var triggerItems = document.getElementsByClassName('.flb-cart-item-trigger');
        for (var i = 0; i < triggerItems.length; i++) {
          triggerItems.item(i).classList.add(<comma seperated classes>);
        }
      }
    
      styleCartClickItems();
    
      var countTriggers = document.getElementsByClassName('flb-cart-count-trigger');
      for (var i = 0; i < countTriggers.length; i++) {
        countTriggers.item(i).addEventListener("click", styleCartCountItems);
      }
    </script>

    As well as adding the above Javascript the details of the HTML element which has the data-ng-click attribute and it’s children are used to configure the itemTrigger section of the Funnelback.SessionCart which manages items using the cart Javascript. To ensure the Javascript works add a class from the parent of the html element which has the data-ng-click attribute to the itemTrigger selector. Icons and labels are moved to the itemTrigger section as seen below.

    <div class="card-header">
      <#if question.collection.configuration.valueAsBoolean("ui.modern.session")>
        <a href="Javascript:;" class="btn btn-light border border-secondary float-right ng-cloak shortlist-button" data-ng-click="toggle()" data-cart-link data-css="far fa-star|fas fa-star" data-labels="Add to shortlist|Remove" title="{{label}}">
          <span class="{{css}}"></span>
          <span class="ng-cloak">{{label}}</span>
        </a>
      </#if>
      ...
    </div>

    becomes

    <div class="card-header">
      ...
    </div>

    with

      var flbSessionCart = new Funnelback.SessionCart({
      ...
      itemTrigger: {
        // Set location of item trigger to add to / delete from cart
        selector: '.card-header',
        position: 'afterbegin',
    
        // Set display of item trigger to add to / delete from cart
        iconAdd: 'far fa-star',
        iconDelete: 'fas fa-star',
        isLabel: true,
        labelAdd: 'Add to shortlist',
        labelDelete: 'Remove',
        template: '{{>icon-block}} {{>label-block}}'
      }
    });

    The position is set to beforebegin, afterbegin, beforeend, afterend relative position to selector element as follows:

    • beforebegin: HTML is added before the selector element

    • afterbegin: HTML is added before the first child inside the selector element

    • beforeend: HTML is added after the last child inside the selector element

    • afterend: HTML is added after the selector element itself

    This process may result in empty HTML elements which can be removed

  • If the attribute changes the display from the cart results to the search results then any classes on this element and child elements will need to be added using the Javascript below. The Javascript goes in the script element after the configuration of Funnelback.SessionCart.

    <script type="text/Javascript">
      var flbSessionCart = new Funnelback.SessionCart({
          ...
      });
    
      var boxHeaders = document.getElementsByClassName('flb-cart-box-header');
    
      for (var i = 0; i < boxHeaders.length; i++) {
        boxHeaders.item(i).classList.add('text-center');
      }
    </script>

    As well as adding the above Javascript the details of the HTML element which has the data-ng-click attribute and it’s children are used to configure the cart section of the Funnelback.SessionCart which displays the header for the cart results page. To ensure the Javascript works add the id search-cart to the parent of the html element which has the data-ng-click attribute. Icons and labels are moved to the cart section as seen below.

    <div class="col-md-12">
      <a href="#" data-ng-click="hideCart()"><span class="fal fa-arrow-left"></span> Back to results</a>
      <h2 class="text-center">
        <span class="fal fa-star"></span> Shortlist
        <button class="btn btn-danger btn-sm" title="Clear selection" data-ng-click="clear('Your selection will be cleared')"><span class="fal fa-times"></span> Clear</button>
      </h2>
      ...
    </div>

    becomes

    <div id="search-cart" class="col-md-12"></div>

    with

      var flbSessionCart = new Funnelback.SessionCart({
      ...
      cart: {
        selector: '#search-cart',
        pageSelector: ['#search-results-content', '#search-history'],
        icon: 'fal fa-star',
        label: 'Shortlist',
        backIcon: 'fal fa-arrow-left',
        backLabel: ' Back to results',
        clearClasses: 'btn btn-danger btn-sm',
        clearIcon: 'fal fa-times',
        clearLabel: 'Clear'
      }
    });
    The HTML used to display the individual items needs to be moved as per instructions in the data-ng-repeat section for showing cart results
  • If the attribute is used on the cart results page to remove items from the cart then any classes will need to be added using the Javascript below. The Javascript goes in the script element after the configuration of Funnelback.SessionCart.

    <script type="text/Javascript">
        var flbSessionCart = new Funnelback.SessionCart({
        ...
      });
    
      function styleItems() {
        var triggerItems = document.getElementsByClassName('flb-cart-item-trigger');
        for (var i = 0; i < triggerItems.length; i++) {
          triggerItems.item(i).classList.add(<comma seperated copied classes);
        }
      }
    
      styleItems();
    </script>

    As well as adding the above Javascript the details of the HTML element which has the data-ng-click attribute and it’s children are used to configure the cartItemTrigger section of the Funnelback.SessionCart which manages items using the cart Javascript. To ensure the Javascript works add a class from the parent of the html element which has the data-ng-click attribute to the cartItemTrigger selector. Icons and labels are moved to the cartItemTrigger section as seen below.

    <div class="card-header">
      ...
      <a href="Javascript:;" class="btn btn-light border border-secondary float-right ng-cloak shortlist-button" data-ng-click="remove(item.indexUrl)">
        <i class="fal fa-times"></i>
        <span class="ng-cloak">Remove</span>
      </a>
    </div>

    becomes

    <div class="card-header">
      ...
    </div>

    with

    var flbSessionCart = new Funnelback.SessionCart({
      ...
      cartItemTrigger: {
        selector: '.card-header,
        position: 'beforeend',
    
        // Set display of item trigger to add to / delete from cart
        iconAdd: '', // Never seen
        iconDelete: 'fal fa-times',
        isLabel: true,
        labelAdd: '', // Never seen
        labelDelete: 'Remove',
        template: '{{>icon-block}} {{>label-block}}'
      }
    });

    The position is set to beforebegin, afterbegin, beforeend, afterend relative position to selector element as follows

    • beforebegin: HTML is added before the selector element

    • afterbegin: HTML is added before the first child inside the selector element

    • beforeend: HTML is added after the last child inside the selector element

    • afterend: HTML is added after the selector element itself

    This process may result in empty HTML elements which can be removed.

Replace angular attribute data-ng-disabled

We can no longer use the angular notation data-ng-disabled. These attributes should be removed.

Replace angular attribute data-ng-href

We can no longer use the angular notation data-ng-href. These attributes should be replaced with standard HTML href attributes, as seen in the example below:

<a data-ng-href="{{item.indexUrl}}">{{item.title}}</a>

becomes

<a href="{{indexUrl}}">{{title}}</a>

Replace angular attribute data-ng-src

We can no longer use the angular notation data-ng-src. These attributes should be replaced with standard HTML src attributes, as seen in the example below.

<img class="img-fluid float-left ng-cloak" alt="Thumbnail for {{item.title}}" data-ng-src="{{item.listMetadata.image[0]}}">

becomes

<img class="img-fluid float-left" alt="Thumbnail for {{title}}" src="{{#byIndex 0 "|"}}{{metaData.image}}{{/byIndex}}">

Replace icon prefix

The cart Javascript allows a prefix to be set for icons to simply development. If your icons don’t have a common prefix this can be set to nothing by using empty single quotes (''). As seen in the example below.

var flbSessionCart = new Funnelback.SessionCart({
  collection: <value>,
  iconPrefix: ''
}

Remove the item. Prefix

The cart Javascript will break when the item. prefix is used on data model items. For userId, collection, indexURL, title, summary and addedDate the item prefix can be removed. When displaying metadata items you may need an item from a specific index. This can be achieved by adding the Javascript shown below. The Javascript goes in the script element after the configuration of Funnelback.SessionCart.

<script type="text/Javascript">
  var flbSessionCart = new Funnelback.SessionCart({
    ...
  });

  flbSessionCart.Handlebars.registerHelper({
    // usage: {{#byIndex "0" "|"}}{{image}}{{/byIndex}}
    byIndex: function (index, delimiter, options) {
      const str = options.fn(this);
      const arr = str.split(delimiter);
      if (index < arr.length) return arr[index];
      return "";
    }
  });
</script>

As well as adding the above Javascript the HTML element which has the item. prefix is modified to use the byIndex helper. i.e. {{#byIndex "0" "|"}}{{metaData.<name>}}{{/byIndex}}, as shown in the example below:

<a class="text-muted" href="mailto:{{item.listMetadata.peopleEmail[0]}}">{{item.listMetadata.peopleEmail[0]}}</a>

becomes

<a class="text-muted" href="mailto:{{#byIndex 0 "|"}}{{metaData.peopleEmail}}{{/byIndex}}">{{#byIndex 0 "|"}}{{metaData.peopleEmail}}{{/byIndex}}</a>

Replace the AngularJS | notation

The cart Javascript templating uses Handlebars notation and will break if the used to manipulate data source items and need to be replaced as per the following:

  • When used to truncate an object remove |truncate:<value> and wrap with {{#truncate <value>}}{{<data item>}}{{/truncate}}, as shown in the example below:

    {{item.summary|truncate:255}}

    becomes

    {{#truncate 255}}{{summary}}{{/truncate}}
  • When used to truncate an object remove |cut:<value> and wrap with {{#cut <value>}}{{<data item>}}{{/cut}}, as shown in the example below:

    {{item.indexUrl|cut:'https://'}}

    becomes

    {{#cut "https://"}}{{indexUrl}}{{/cut}}
  • When used to replace characters in the data model item if would depend on the regex. For example, |replace:'\\|.*$' is the same as getting everything before the first pipe and therefore can be replaced by the Handlebars helper 'byIndex' as shown in the example below:

    <h5>{{item.listMetadata.peoplePosition[0]|replace:'\\|.*$'}}</h5>

    becomes

    <h5>{{#byIndex 0 "|"}}{{metaData.peoplePosition}}{{/byIndex}}</h5>

Replace the AngularJS || notation

The cart Javascript templating uses Handlebars notation and will break if used to display a data model or default value or a default value. It needs to be replaced, as shown in the example below:

<span>{{item.listMetadata.stencilsCourseCredit[0] || '-'}}</span>

becomes

<span>
  {{#if metaData.stencilsCourseCredit}}
    {{#byIndex 0 "|"}}{{metaData.stencilsCourseCredit}}{{/byIndex}}
  {{else}}
    -
  {{/if}}
</span>

Upgrade from Funnelback 15 to Funnelback DXP

Replace JavaScript libraries

Prior to Funnelback 15.24, the widget was delivered using an angularJS based session library. As of Funnelback 15.24, this was split into two independent widgets; one to handle search and click history and another to handle cart functionality.

Because of this the funnelback-session-1.0.0.js library uses AngularJS. It has been superseded by newer libraries using Handlebars. funnelback-session-1.0.0.js should be removed.

Regarding upgrading from Funnelback 15
  1. The example below assumes the Funnelback Session JavaScript library includes the version number in the file-name. In some older iterations, however, this library did not append the version number: the file was named funnelback-session.js.

    The example below assumes the library filename includes the version number.

  2. The AngularJS library was previously used only for the Funnelback session tool, and, as noted above, it should also be removed.

    It is possible, however, for custom functionality relying on this library to be added to a customized template. If that is the case, the AngularJS library should not be removed.

    The example below assumes the AngularJS library is removed.

Replace JavaScript Libraries

Replace:

<script src="${GlobalResourcesPrefix}thirdparty/angular-1.0.7/angular.js"></script>
<script src="${GlobalResourcesPrefix}thirdparty/angular-1.0.7/angular-resource.js"></script>
<script src="${GlobalResourcesPrefix}js/funnelback-session-1.0.0.js"></script>

with:

<script nomodule type="text/javascript" src="${GlobalResourcesPrefix}thirdparty/es6-promise-4.2.5/es6-promise.auto.min.js"></script>
<script type="text/javascript" src="${GlobalResourcesPrefix}thirdparty/handlebars-4.7/handlebars.min.js"></script>
<script type="text/javascript" src="${GlobalResourcesPrefix}js/funnelback.session-cart-1.0.min.js"></script>
<script type="text/javascript" src="${GlobalResourcesPrefix}js/funnelback.session-history-1.0.min.js"></script>

<script type="text/javascript">
  var flbSessionCart = new Funnelback.SessionCart({collection: '${question.collection.id}'});
  var flbSessionHistory = new Funnelback.SessionHistory({collection: '${question.collection.id}'});
</script>

Update Freemarker templates

Make the following changes to simple.ftl.

  1. Replace the recent Session history block with a <div id="search-history-recent"> div.

    That is replace

              <#-- Build list of previous queries -->
    
              <#assign qsSignature = computeQueryStringSignature(QueryString) />
              <#if session.searchHistory?? && (session.searchHistory?size gt 1 || session.searchHistory[0].searchParamsSignature != qsSignature)>
                <div class="breadcrumb session-history-breadcrumb">
                  <button class="btn btn-link pull-right session-history-show"><small class="text-muted"><span class="glyphicon glyphicon-plus"></span> More&hellip;</small></button>
                  <ol class="list-inline" >
                    <li class="text-muted">Recent:</li>
                    <#list session.searchHistory as h>
                      <#if h.searchParamsSignature != qsSignature>
                        <#outputformat "plainText">
                        <#assign facetDescription><#compress>
                        <#list h.searchParams?matches("f\\.([^=]+)=([^&]+)") as f>
                            ${urlDecode(f?groups[1])?split("|")[0]} = ${urlDecode(f?groups[2])}<#if f_has_next><br></#if>
                        </#list>
                        </#compress></#assign>
                        </#outputformat>
                        <li>
                          <a <#if facetDescription != ""> data-toggle="tooltip" data-placement="bottom" title="${facetDescription}"</#if> title="${prettyTime(h.searchDate)}" href="${question.currentProfileConfig.get("ui.modern.search_link")}?${h.searchParams}">${h.originalQuery!} <small>(${h.totalMatching})</small></a>
                          <#if facetDescription != ""><i class="glyphicon glyphicon-filter"></i></a></#if>
                        </li>
                      </#if>
                    </#list>
                  </ol>
                </div>
              </#if>

    with

    <div id="search-history-recent"></div>
  2. Replace the Session history block with a <div id="search-history"> div.

    That is replace

          <div id="search-history">
            <div class="row">
              <div class="col-md-12">
                <a href="#" class="session-history-hide"><span class="glyphicon glyphicon-arrow-left"></span> Back to results</a>
                <h2><span class="glyphicon glyphicon-time"></span> History</h2>
    
                <div class="row">
                  <div class="col-md-6">
      <h3>
        <span class="glyphicon glyphicon-heart"></span> Recently clicked results
        <button class="btn btn-danger btn-xs session-history-clear-click" title="Clear click history"><span class="glyphicon glyphicon-remove"></span> Clear</button>
      </h3>
      <#list session.clickHistory>
        <ul class="session-history-click-results">
        <#items as h>
          <li><a href="${h.indexUrl}">${h.title}</a> &middot; <span class="text-warning">${prettyTime(h.clickDate)}</span><#if h.query??><span class="text-muted"> for &quot;${h.query!}&quot;</#if></span></li>
        </#items>
        </ul>
      </#list>
      <p class="session-history-click-empty text-muted">Your click history is empty.</p>
    </div>
                  <div class="col-md-6">
      <h3>
        <span class="glyphicon glyphicon-search"></span> Recent searches
        <button class="btn btn-danger btn-xs session-history-clear-search" title="Clear search history"><span class="glyphicon glyphicon-remove"></span> Clear</button>
      </h3>
      <#list session.searchHistory>
        <ul class="session-history-search-results list-unstyled">
        <#items as h>
          <li><a href="?${h.searchParams}">${h.originalQuery!} <small>(${h.totalMatching})</small></a> &middot; <span class="text-warning">${prettyTime(h.searchDate)}</span></li>
        </#items>
        </ul>
      </#list>
      <p class="session-history-search-empty text-muted">Your search history is empty.</p>
    </div>
                </div>
              </div>
            </div>
          </div>

    with

    <div id="search-history"></div>
  3. Finally, in the Last visited time display block, remove this line:

    <#if question.currentProfileConfig.get("ui.modern.session")?boolean && session?? && session.getClickHistory(s.result.indexUrl)??><small class="text-warning session-history-link"><span class="glyphicon glyphicon-time"></span> <a title="Click history" href="#" class="text-warning session-history-show">Last visited ${prettyTime(session.getClickHistory(s.result.indexUrl).clickDate)}</a></small></#if>

Upgrade from Funnelback 16 to Funnelback DXP

Replace JavaScript Libraries

Replace:

<script nomodule type="text/javascript" src="${GlobalResourcesPrefix}thirdparty/es6-promise-4.2.5/es6-promise.auto.min.js"></script>
<script type="text/javascript" src="${GlobalResourcesPrefix}thirdparty/handlebars-4.7/handlebars.min.js"></script>
<script type="text/javascript" src="${GlobalResourcesPrefix}js/funnelback.session-cart-0.1.min.js"></script>
<script type="text/javascript" src="${GlobalResourcesPrefix}js/funnelback.session-history-0.1.min.js"></script>

<script type="text/javascript">
  var flbSessionCart = new Funnelback.SessionCart({collection: '${question.collection.id}'});
  var flbSessionHistory = new Funnelback.SessionHistory({collection: '${question.collection.id}'});
</script>

with:

<script nomodule type="text/javascript" src="${GlobalResourcesPrefix}thirdparty/es6-promise-4.2.5/es6-promise.auto.min.js"></script>
<script type="text/javascript" src="${GlobalResourcesPrefix}thirdparty/handlebars-4.7/handlebars.min.js"></script>
<script type="text/javascript" src="${GlobalResourcesPrefix}js/funnelback.session-cart-1.0.min.js"></script>
<script type="text/javascript" src="${GlobalResourcesPrefix}js/funnelback.session-history-1.0.min.js"></script>

<script type="text/javascript">
  var flbSessionCart = new Funnelback.SessionCart({collection: '${question.collection.id}'});
  var flbSessionHistory = new Funnelback.SessionHistory({collection: '${question.collection.id}'});
</script>

Update Freemarker templates

Make the following changes to simple.ftl.

  1. Replace the recent Session history block with a <div id="search-history-recent"> div.

    That is replace

              <#-- Build list of previous queries -->
    
              <#assign qsSignature = computeQueryStringSignature(QueryString) />
              <#if session.searchHistory?? && (session.searchHistory?size gt 1 || session.searchHistory[0].searchParamsSignature != qsSignature)>
                <div class="breadcrumb session-history-breadcrumb">
                  <button class="btn btn-link pull-right session-history-show"><small class="text-muted"><span class="glyphicon glyphicon-plus"></span> More&hellip;</small></button>
                  <ol class="list-inline" >
                    <li class="text-muted">Recent:</li>
                    <#list session.searchHistory as h>
                      <#if h.searchParamsSignature != qsSignature>
                        <#outputformat "plainText">
                        <#assign facetDescription><#compress>
                        <#list h.searchParams?matches("f\\.([^=]+)=([^&]+)") as f>
                            ${urlDecode(f?groups[1])?split("|")[0]} = ${urlDecode(f?groups[2])}<#if f_has_next><br></#if>
                        </#list>
                        </#compress></#assign>
                        </#outputformat>
                        <li>
                          <a <#if facetDescription != ""> data-toggle="tooltip" data-placement="bottom" title="${facetDescription}"</#if> title="${prettyTime(h.searchDate)}" href="${question.currentProfileConfig.get("ui.modern.search_link")}?${h.searchParams}">${h.originalQuery!} <small>(${h.totalMatching})</small></a>
                          <#if facetDescription != ""><i class="glyphicon glyphicon-filter"></i></a></#if>
                        </li>
                      </#if>
                    </#list>
                  </ol>
                </div>
              </#if>

    with

    <div id="search-history-recent"></div>
  2. Replace the Session history block with a <div id="search-history"> div.

    That is replace

          <div id="search-history">
            <div class="row">
              <div class="col-md-12">
                <a href="#" class="session-history-hide"><span class="glyphicon glyphicon-arrow-left"></span> Back to results</a>
                <h2><span class="glyphicon glyphicon-time"></span> History</h2>
    
                <div class="row">
                  <div class="col-md-6">
      <h3>
        <span class="glyphicon glyphicon-heart"></span> Recently clicked results
        <button class="btn btn-danger btn-xs session-history-clear-click" title="Clear click history"><span class="glyphicon glyphicon-remove"></span> Clear</button>
      </h3>
      <#list session.clickHistory>
        <ul class="session-history-click-results">
        <#items as h>
          <li><a href="${h.indexUrl}">${h.title}</a> &middot; <span class="text-warning">${prettyTime(h.clickDate)}</span><#if h.query??><span class="text-muted"> for &quot;${h.query!}&quot;</#if></span></li>
        </#items>
        </ul>
      </#list>
      <p class="session-history-click-empty text-muted">Your click history is empty.</p>
    </div>
                  <div class="col-md-6">
      <h3>
        <span class="glyphicon glyphicon-search"></span> Recent searches
        <button class="btn btn-danger btn-xs session-history-clear-search" title="Clear search history"><span class="glyphicon glyphicon-remove"></span> Clear</button>
      </h3>
      <#list session.searchHistory>
        <ul class="session-history-search-results list-unstyled">
        <#items as h>
          <li><a href="?${h.searchParams}">${h.originalQuery!} <small>(${h.totalMatching})</small></a> &middot; <span class="text-warning">${prettyTime(h.searchDate)}</span></li>
        </#items>
        </ul>
      </#list>
      <p class="session-history-search-empty text-muted">Your search history is empty.</p>
    </div>
                </div>
              </div>
            </div>
          </div>

    with

    <div id="search-history"></div>
  3. Finally, in the Last visited time display block, remove this line:

    <#if question.currentProfileConfig.get("ui.modern.session")?boolean && session?? && session.getClickHistory(s.result.indexUrl)??><small class="text-warning session-history-link"><span class="glyphicon glyphicon-time"></span> <a title="Click history" href="#" class="text-warning session-history-show">Last visited ${prettyTime(session.getClickHistory(s.result.indexUrl).clickDate)}</a></small></#if>