Salesforce Commerce Cloud: SFRA

The Dynamic Yield–Salesforce Commerce Cloud cartridge provides a quick way to implement Dynamic Yield, as it takes care of the basic implementation of Dynamic Yield on the site (script, page context, e-commerce events, and product feed). Part of the setup requires technical knowledge of Salesforce Commerce Cloud.

The cartridge is self-contained and can be integrated into any project. It can be configured in Business Manager and contains all elements necessary to integrate systems into any e-commerce environment.

❗️

If your environment is based on SFRA, see the Salesforce Commerce Cloud: SFRA article.

Prerequisite

This cartridge is designed for Salesforce Commerce Cloud API version 19.10, Compatibility Mode: 18.10. If you're using a different version, manual changes might be required.

Step 1: Create a Dynamic Yield section

Complete this step in Experience OS.

A section in Dynamic Yield Experience OS represents your store's site in our system. Each section has an associated product feed, which is your product catalog. You'll configure the sync of the catalog/feed in the following steps.

To create the section, go to Experience OS and follow the directions in Managing Sections in our Knowledge Base.

Step 2: Install the cartridge in the sandbox

Complete this step in Salesforce Commerce Cloud:

  1. Download the cartridge here.

  2. Open Salesforce Commerce Cloud Studio.

  3. Import the following downloaded cartridges into your IDE workspace:
    int_dynamicyield, int_dynamicyield_sfra, and bc_dynamicyield.

  4. Link the cartridge to the sandbox as follows:

    1. Select sandbox connection and select Properties.
    2. Select Project Reference.
    3. Check in int_dynamicyield_sfra, int_dynamicyield and bc_dynamicyield.
      For more details, see the For more details, see the Salesforce Commerce Cloud documentation.
  5. Verify that the cartridge has been installed by going to Business​ ​Manager​ › Merchant​ ​Tools​ ›​ ​Site​ Preferences​ ›​ ​Custom​ ​Site​ ​Preference​ and confirming that the Dynamic Yield folder is there.

Step 3: Set up the Business Manager

Complete this step in Salesforce Commerce Cloud:

  1. Go to Business Manager › Administration › Sites › Manage Sites.

  2. Select your site, and go to the Settings tab.

  3. In cartridges, add int_dynamicyield_sfra before :app_storefront_base, and add int_dynamicyield:bc_dynamicyield after :app_storefront_base as follows:

  4. Go back to Business Manager › Administration › Sites › Manage Sites.

  5. Select the site Business Manager, and go to the Settings tab.

  6. ​In​ ​cartridges​, add​ ​the​ string :​bc_dynamicyield.

  7. Go to Business Manager › Site Development › Site Import & Export.

  8. Upload the file metadata/dynamicyield.zip. This file contains all the necessary settings for the site.

  9. Import the file you just uploaded by selecting it and clicking Import. Click Yes to confirm.

  10. Verify that the implementation was successful by going to your ​test​ ​StoreFront​ and checking that the Dynamic Yield icon ​appears​ as​ ​shown in the following image:

Step 4: Configure the cartridge in Site Preferences

Complete this step in Salesforce Commerce Cloud

  1. Go​ ​to​ ​Business​ ​Manager:​ ​Sites​ ​›​ ​Your​ ​Site​ ›​ ​Site​ ​Preferences​ ›​ ​Custom​ ​Site​ ​Preferences​ ​› Dynamic​ ​Yield.
  2. Verify that Enable Dynamic Yield and Enable Catalog Export are set to Yes.
  3. Enter your Dynamic Yield Site ID.
  4. Enter the Product Feed’s S3 Access Key ID and Secret Access Key.
  5. If your product feed has custom columns beyond the mandatory columns, add them in the Product Attributes field. You can add up to 30. Note that custom columns can't be removed later.

📘

Custom columns can be used for targeting (say, add "color" and target users who purchased blue items), affinity score (include color affinity in the affinity recommendation algorithm), define merchandising rules in your recommendation widgets (never recommend products of a specific brand).

  1. Enter the Dynamic Yield Endpoint​ ​for​ ​Product​ ​Feed​ ​uploading (only host part):
    • If you're using our US data center:
      com.dynamicyield.feeds
    • If you are using our EU data center:
      dy-datafeeds-eu
      Also set Enable Dynamic Yield Europe Account to Yes

📘

How can you tell if you use the .com or .eu data center?

If your site ID starts with 8 – use the .com information.
If your site ID starts with 9 – use the .eu information.

  1. Dynamic Yield uses an extremely fast dedicated CDN. However, if you want to use your own CDN, configure the following settings:
  • Enable CDN integration: Yes.
  • Custom CDN URL: A link to your CDN in the following format:
<script type="text/javascript" src="//[CUSTOM CDN URL]/api/[SECTION_ID]/api_dynamic.js"></script> <script type="text/javascript" src="//[CUSTOM CDN URL]/api/[SECTION_ID]/api_static.js"></script>
  1. Enable async integration: Usually set to No. If you configure the scripts to run asynchronously, unexpected behaviors will occur, affecting which variations are shown, loading times, flicker, and so on. Dynamic Yield provides a high server redundancy rate and fast loading times, so you don’t need to resort to asynchronous loading to speed things up. All other operations after loading the scripts are run asynchronously.

Step 5: Sync your product catalog with Dynamic Yield

Complete this step in Experience OS.

A synced product feed enables capabilities like recommendations, segmenting based on product engagement, social proof tactics, and more.

To enable the sync, you must enter the AWS S3 access and secret keys. To get these credentials:

  1. In Experience OS, go to Assets ›​ ​Data Feeds.

  2. Do one of the following:

    • If you already have a product feed, go to your feed and click Edit.
    • If you don't yet have a product feed, click Add New to create one.
  3. In the Feed Source, select Sync a file via Amazon S3.

  4. Do one of the following:

    • If you're editing an existing product feed, click Generate Credentials.

    • If you're creating a new feed, click Create a bucket.

      The Access Key and Secret Key are displayed.

Step 6: Configure the product feed sync schedule

Complete this step in Salesforce Commerce Cloud

  1. Go to Business Manager: Administration › Operations › Job Schedules › Dynamic Yield.
  2. ​Go​ ​to​ the ​Schedule​ ​and​ ​History​ ​tab​ ​​to​ ​define​ the synchronization schedule.
  1. Verify that Enabled is checked.
  2. Configure the Trigger for syncing the product catalog with Dynamic Yield:
    • Recurring Intervals (recommended): To automatically sync the feed periodically and keep the product feed updated with your product catalog.
    • Once: Sync only once. Note that if your product catalog changes later, it won't update the product feed in Dynamic Yield.
  3. Use the date picker in the From and To fields to define the first time to run the job, and when to no longer run it. If the To field is blank, the job will continue to run according to the run time schedule (configured in the next step). We recommend choosing today as the start date and keeping the end date empty.
  4. The Run Time configuration determines your sync interval (minimum: 60 minutes).
  5. Go​ ​to​ ​the Step​ ​Configurator​ ​tab and click Organization.
  6. Select the needed site for this job and click Assign.

Configure events

The cartridge supports the following events out of the box:

➡ Purchase       ➡ Add to Cart                  ➡ Signup
➡ Login              ➡ Remove from Cart      ➡ Sync Cart
➡ Sort Items      ➡ Filter Items                  ➡ Change Attribute
➡ Keyword Search                                     ➡ Promo Code Entered

To add additional events:

  1. Go to int_dynamicyield/cartridge/scripts/helper/dyHelper.js and locate the getDYresponse function. Use the examples of supported events to add custom events.
  2. Add the events to your JS code wherever you want to trigger them.
  3. Each event also requires a call from the client JS scripts to the DynamicYield-GetAPIProperties endpoint.

Learn more about Dynamic Yield events.

Step 8: Validate the implementation

Complete this step in Experience OS.

After implementing Dynamic Yield, it's important to validate that your script, page context, events, and data feed are properly set up. For details, see Validating your Web Implementation.

Cartridge code modifications

/cartridge/client/default/JS/cart/cart.js
  1. Add the following code to the .remove-product element.
// START - dynamicYield call for setRemovedItem
          var line = $(this).closest(".card");
          var quantity
          = line.find('select.quantity').val();
       dynamicYield.callEvent('setRemovedItem',
          {
         productId:
          productID,
          quantity:
          quantity
        });        
          // END ===================================        
  1. Add the following code to the .quantity-form element.
// START - Quantity - setAddedItem
        
          dynamicYield.callEvent('setAddedItem',
          {
      
          productId: productID,
       
           quantity: quantity
        });
        
          // END ================================     
// START - dynamicYield call for Add to Cart
        
          dynamicYield.callEvent('Add
          to Cart', {
       
          productId:
          productID,
       
          quantity:
          quantity       
});       
          //END ====================================   
  1. Add the following code to the .cart-delete_confirmation-btn element
// START - dynamicYield call for setRemovedItem 
       
         dynamicYield.callEvent('Remove
          from Cart');
       // END ===================================      
  1. Add the following code to the .promo-code-form element.
// START dynamicYield - Promo Code Entered - preparation
       
          var cuponCode
          = $('.coupon-code-field').val();
       
       // END ======================      
// START dynamicYield - Promo Code Entered - execution
        
          dynamicYield.callEvent('Promo
          Code Entered', {
        
           code: cuponCode
         });
        // END ======================  
  1. Add the following code to the .update-cart-product-global element.
// START - updateProduct - setAddedItem
        dynamicYield.callEvent('setAddedItem',
          {
         productId: form.pid,
         quantity: form.quantity
        });// END ================================        
// START - updateProduct - Add to CartdynamicYield.callEvent('Add
          to Cart', {       
          productId: form.pid,      
          quantity: form.quantity
        });
        // END ====================================        
/cartridge/client/default/JS/product/base.js
  1. Add the following code to the button.add-to-cart element.
// START - dynamicYield call for setAddItemif(!form.pidsObj)
          { // if NOT setProduct
        
          dynamicYield.callEvent('setAddedItem', {
       
          productId: form.pid,
        
          quantity: form.quantity
        });
        }
        else
          {
        
          // setProduct type - setAddedItem
                  for(var i=0; i<setPids.length; i++) {
                  dynamicYield.callEvent('setAddedItem', {
                  productId: setPids[i].pid,
                  quantity: setPids[i].qty
        });
                  }
        }
        //
          END ====================================        
  1. Add the following code to the updateAddToCartFormData element.
// START - dynamicYield call for Add to Cart

if(!form.pidsObj)
          { // if NOT setProducts
        
         dynamicYield.callEvent('Add to Cart', {
         productId: form.pid,
         quantity: form.quantity
         });
}
 else
          {
 
          // setProduct type - Add to Cart
 
          for(var i=0; i<setPids.length; i++) {
 
          dynamicYield.callEvent('Add to Cart', {
         productId: setPids[i].pid,
       quantity: setPids[i].qty
         });
        
          }
        }
        //
          END ====================================
int_dynamicyield_sfra/cartridge/controllers/DynamicYield.js

Add the following code to the file.

/*
        * Dynamic Yield main controller
        */
        
        'use strict';
        
        var server = require('server');
        /* API includes */
        
        var Site = require('dw/system/Site');
        var BasketMgr = require('dw/order/BasketMgr');
        var ProductMgr = require('dw/catalog/ProductMgr');
        
          var DYlib = require('bc_dynamicyield/cartridge/scripts/lib/DYlib');
        
        
          var DynamicYieldHelper = require('*/cartridge/scripts/helper/dyHelper.js');
        
        server.get(
        'Render',
        function (req, res, next) {
         // Do nothing if Dynamic Yield is disabled
          globally - or section ID is not set
         var DY_SectionID = DYlib.getSectionID();
         if (!DYlib.isDynamicYieldEnabled() ||
          empty(DY_SectionID)) {DYlib.log('info', 'Cartridge for Dynamic
          Yield is disabled');
         return;
         }
        
         // Prepare variables
         var sfraParameterMap = req.querystring;
         // helper section
         var recommendationContext = DynamicYieldHelper.getRecommendationContext(
          sfraParameterMap, true );
         // Render template with Dynamic Yield
          script
         recommendationContext.data = recommendationContext.data.toString();
         res.render('dynamicyield/render', {
         'DY_SectionID' : DY_SectionID,
         'recommendationContext' : recommendationContext,
         'DY_LocaleID': request.locale,
         'DY_cdn' : DYlib.getCDN(),
         'async' : DYlib.isAsyncEnable() ? "async"
          : ''
         });
        return next();
        }
        );
        server.get(
        'GetAPIProperties',
        function (req, res, next) {
         // Do nothing if Dynamic Yield is disabled
          globally - or section ID is not set
         var DY_SectionID = DYlib.getSectionID();
         if (!DYlib.isDynamicYieldEnabled() ||
          empty(DY_SectionID)) {
         DYlib.log('info', 'Cartridge for Dynamic
          Yield is disabled');
         return;
         }
         var sfraParameterMap = req.querystring;
         //code
         var params = sfraParameterMap.params!=''
          ? JSON.parse(sfraParameterMap.params) : {};
         var eventName = sfraParameterMap.eventName;
         // helper section
         var DYresponse = DynamicYieldHelper.getDYresponse(params,
          eventName);
         //render
         res.json( DYresponse );
       return next();
       }
        );
        module.exports = server.exports();
../static/defaul/js/dynamicYield/dyChangeAttributeTrack.js

Add the following code to the file.

$(document).ready(function() {
        // Options - (input swatcher like : size, width)
        
          $('div.attribute:not(.quantity)').find('select').on('change',
          function(el) {
        var $el = $(this).find(':selected');
        dynamicYield.callEvent('Change Attribute', {
        type: $(this).closest('.row').attr("data-attr").trim(),
        value: $el.text().trim()
        });
        });
        
        // Variations - (buttons swatcher like : color)
        
          $('div.attribute').find('button.color-attribute').on('click',
          function(el) {
        
        dynamicYield.callEvent('Change Attribute', {
        type: $(this).closest('.row').attr('data-attr').trim(),
        
          value: $(this).find('.swatch-value').attr("data-attr-value").trim()
        
        });
        });
        
        }); // ready
../static/default/js/dynamicYield/dyFilterTrack.js

Add the following code to the file.

// Refinement tracking
        $(document).ready(function() {      
// normal first call after page load
        
        attachRefinementEventHandlers();        
         // AGAIN reatach refinment handlers,
          after clicking, becouse entire section is re-rendered and old
          listeners lost        
        
         $('.refinements').on('DOMSubtreeModified',
          function(el) {
        
        attachRefinementEventHandlers();
        });
        // custom function, that attach eventListeners
          to all clickable refinement items
        function attachRefinementEventHandlers()
          {
        // refine items
        $('div.refinement').find('li').on('click',
          function(el) {
        dynamicYield.callEvent('Filter
          Items', {
        type:
          $(this).attr("data-type"),
        value:
          $(this).attr("data-value")
        });
        });
        //sorting
        $('select[name=sort-order]').off("change");
        $('select[name=sort-order]').on('change',
          function(el) {
         var sBy = $("select[name=sort-order]
          option:selected").attr("data-id");
       var sOrder = "ASC";
         if( sBy === "price-high-to-low" || sBy
          === "product-name-descending") {
         sOrder = "DESC";
         }
         dynamicYield.callEvent('Sort Items',
          {
         sortBy: sBy,
         sortOrder: sOrder
         });
        });
        }
        }); // ready      
../static/defaul/js/dynamicYield/dynamicYieldSfra.js

Add the following code to the file.

var dynamicYield = {
        
   callEvent: function(eventName, params){
   var DYhref = '',
   DYform = '';
    if(typeof params == "object"){
    DYhref = params.DYhref || '';
    DYform = params.DYform || '';
    }

    var params = JSON.stringify(params) || '';
        
     // these properties are loaded via action call defined in isml
     as <span ... data-url> parameter
   
   var DYGetAPIProperties = $('#sfraSpan').data('url');
        
   $.ajax({
   url: DYGetAPIProperties,
   data: {
   eventName: eventName,
   params: params
   },
   success: function(response) {
   if(response.doCall){
   if(DY.API) {
   DY.API('event', {
   name: response.eventName,
   properties: response.properties
   });
        
     // refreshing DY Static Data - SKUs, without need to page-refresh

     if(DY.recommendationContext.type == "CART" && response.eventName
     == "Remove from Cart") {
        
   refreshSKUs( response );
   }
   }
   // in case of async loading
   else {
        
     document.getElementById("DY_api_static").onload = function(){
   DY.API('event', {
   name: response.eventName,
   properties: response.properties
   });
        
     // refreshing DY Static Data - SKUs, without need to page-refresh
     if(DY.recommendationContext.type == "CART" && response.eventName
     == "Remove from Cart") {
   
   refreshSKUs( response );
   }
   }
   }
   }
   if(DYhref!=''){
   window.location.href = DYhref;
   }
   if(DYform!=''){
   DYform.trigger('submit');
   }
   },
   error : function(e){
   console.log(e);
   }
   });
   }
   }
   function refreshSKUs( response ) {
   var newCartIds = [];
   for(var i=0; i<response.properties.cart.length; i++) {
   newCartIds.push( response.properties.cart[i].productId );
   }
   DY.API('spa', {
   context: {
   type: 'CART',
   data: newCartIds
   }
   });
   return;
   }

Cartridge template modifications

Each of these templates is modified by the cartridge.

templates/default/account/accountDashboard.isml

Adds the following code:

<iscomment>DynamicYield section ADDED - START</iscomment>
          <iscomment>Call controller with prepared parameters</iscomment>
          <isinclude url="${URLUtils.url('DynamicYield-Render', ['page_type',
          'OTHER'])}"/>
        
        <script defer>
        document.addEventListener('DOMContentLoaded', function () {
        dynamicYield.callEvent('Account');
        });
        </script>
        
          <iscomment>DynamicYield section ADDED - END</iscomment>
templates/default/account/login.isml

Adds the following code:

<iscomment>DynamicYield section ADDED - START</iscomment>
          <iscomment>Call controller with prepared parameters</iscomment>
          <isinclude url="${URLUtils.url('DynamicYield-Render', ['page_type',
          'OTHER'])}"/>
        
        <script defer>
        document.addEventListener('DOMContentLoaded', function () {
        dynamicYield.callEvent('Account');
        });
        </script>
        
          <iscomment>DynamicYield section ADDED - END</iscomment>
templates/default/checkout/confirmation/confirmationDetails.isml

Adds the following code:

<iscomment>DynamicYield section ADDED - START</iscomment>        
          <iscomment>Call controller with prepared parameters</iscomment>
          <isinclude url="${URLUtils.url('DynamicYield-Render', ['page_type',
          'OTHER'])}"/>       
          <span id="sfraSpan" data-url="${URLUtils.url('DynamicYield-GetAPIProperties').toString()}"></span>       
       <script defer>
        document.addEventListener('DOMContentLoaded', function () {
        dynamicYield.callEvent('Purchase', {
        orderId: '${pdict.order.orderNumber}'
        });
        });
        </script>
        
          <iscomment>DynamicYield section ADDED - END</iscomment>
templates/default/checkout/checkout.isml

Adds the following code:

<iscomment>DynamicYield section ADDED - START</iscomment>       
     <iscomment>Call controller with prepared parameters</iscomment>      
     <isinclude url="${URLUtils.url('DynamicYield-Render', ['page_type',
     'OTHER'])}"/>       
     <span id="sfraSpan" data-url="${URLUtils.url('DynamicYield-GetAPIProperties').toString()}"></span>
 <script defer>
  document.addEventListener('DOMContentLoaded', function () {
  dynamicYield.callEvent('Account');
   });
   </script>
     <iscomment>DynamicYield section ADDED - END</iscomment>
templates/default/checkout/checkoutLogin.isml

Adds the following code:

          <iscomment>DynamicYield section ADDED - START</iscomment>        
          <iscomment>Call controller with prepared parameters</iscomment>        
          <isinclude url="${URLUtils.url('DynamicYield-Render', ['page_type',
          'OTHER'])}"/>       
          <span id="sfraSpan" data-url="${URLUtils.url('DynamicYield-GetAPIProperties').toString()}"></span>
         <script defer>
        document.addEventListener('DOMContentLoaded', function () {
        dynamicYield.callEvent('Account');
        });
        </script>
        
          <iscomment>DynamicYield section ADDED - END</iscomment> 
templates/default/common/scripts.isml

Adds the following code:

 <isscript>
        var assets = require('*/cartridge/scripts/assets');
        assets.addJs('/js/dynamicYield/dynamicYieldSfra.js');
        </isscript>
templates/default/components/header/pageHeader.isml

Adds the following code:

         <iscomment>DynamicYield section ADDED - START</iscomment>        
          <iscomment>call for dynamicyieldSfra.js is placed in: 
          scripts.isml, becouse its for all pages</iscomment>
          <iscomment>initial definition of DynamicYield-GetAPIProperties
          necessary for dynamicyieldSfra.js</iscomment>        
          <span id="sfraSpan" data-url="${URLUtils.url('DynamicYield-GetAPIProperties').toString()}"></span>
        
          <isif condition="${!empty(session.custom.DYnewSession) &&
          session.custom.DYnewSession=='true'}">
        <script defer>
        document.addEventListener('DOMContentLoaded', function () {
        dynamicYield.callEvent('Sync cart');
        });
        </script>
        </isif>
        
          <iscomment>DynamicYield section ADDED - END</iscomment>
templates/default/home/homePage.isml

Adds the following code:

          <iscomment>DynamicYield section ADDED - START</iscomment>       
          <iscomment>Call controller with prepared parameters</iscomment>    
          <isinclude url="${URLUtils.url('DynamicYield-Render', ['page_type',
          'HOMEPAGE'])}"/>       
          <iscomment>DynamicYield section ADDED - END</iscomment>       
templates/default/product/bundleDetails.isml

Adds the following code::

          <iscomment>DynamicYield section ADDED - START</iscomment>       
          <iscomment>Call controller with prepared parameters</iscomment>       
          <isinclude url="${URLUtils.url('DynamicYield-Render', ['page_type',
          'PRODUCT', 'pid', request.httpParameterMap.pid.stringValue])}"/>       
          <iscomment>change attribute - tracking is in included asset
          JS on top of this code</iscomment>      
          <iscomment>DynamicYield section ADDED - END</iscomment>       
templates/default/product/productDetails.isml

Adds the following code::

assets.addJs('/js/dynamicYield/dyChangeAttributeTrack.js');
          <iscomment>DynamicYield section ADDED - START</iscomment>       
          <iscomment>Call controller with prepared parameters</iscomment>        
          <isinclude url="${URLUtils.url('DynamicYield-Render', ['page_type',
          'PRODUCT', 'pid', request.httpParameterMap.pid.stringValue])}"/>        
       
          <iscomment>change attribute - tracking is in included asset
          JS on top of this code</iscomment>      
          <iscomment>DynamicYield section ADDED - END</iscomment>        
templates/default/product/setDetails.isml

Adds the following code:

assets.addJs('/js/dynamicYield/dyChangeAttributeTrack.js');
          <iscomment>DynamicYield section ADDED - START</iscomment>        
          <iscomment>Call controller with prepared parameters</iscomment>        
          <isinclude url="${URLUtils.url('DynamicYield-Render', ['page_type',
          'PRODUCT', 'pid', request.httpParameterMap.pid.stringValue])}"/>        
          <iscomment>change attribute - tracking is in included asset
          JS on top of this code</iscomment>       
templates/default/rendering/category/catLanding.isml

Adds the following code:

          <iscomment>DynamicYield section ADDED - START</iscomment>        
          <iscomment>Call controller with prepared parameters</iscomment>        
          <isinclude url="${URLUtils.url('DynamicYield-Render', ['page_type',
          'CATEGORY', 'cgid', request.httpParameterMap.cgid.stringValue])}"/>       
          <iscomment>DynamicYield section ADDED - END</iscomment>      
templates/default/search/refinements/attributes/boolean.isml

Adds the following code:

<li class="col-sm-4 col-md-12 ${!refinementValue.selectable ? 'disabled' : ''}"
  data-type="${refinementValue.type}" data-value="${refinementValue.displayValue}">
templates/default/search/refinements/attributes/color.isml

Adds the following code:

<li class="color-attribute ${!refinementValue.selectable ? 'disabled' : ''}"
  data-type="${refinementValue.type}" data-value="${refinementValue.displayValue}">
templates/default/search/refinements/attributes/size.isml

Adds the following code:

<li class="col-sm-2 col-md-12 ${!refinementValue.selectable ? 'disabled' : ''}"
  data-type="${refinementValue.type}" data-value="${refinementValue.displayValue}">
templates/default/search/refinements/categories.isml

Adds the following code:

<li data-type="category" data-value="${category.displayValue}">
templates/default/search/refinements/prices.isml

Adds the following code:

<li class="col-sm
templates/default/search/searchResults.isml

Adds the following code:

    <iscomment>DynamicYield section ADDED - START</iscomment>       
     <isif condition="${pdict.productSearch.isCategorySearch}">
     <isinclude url="${URLUtils.url('DynamicYield-Render', ['page_type',
     'CATEGORY', 'cgid', request.httpParameterMap.cgid.stringValue])}"/>
   <iselse>
   
     <isinclude url="${URLUtils.url('DynamicYield-Render', ['page_type',
     'OTHER'])}"/>
   
   </isif>
        
   <script defer>
   document.addEventListener('DOMContentLoaded', function () {
   // keyword search tracking on the begining only
        
     <isif condition="${!empty(pdict.productSearch.searchKeywords)}">
        
        
             dynamicYield.callEvent('Keyword Search',
     { keywords: '<isprint value="${pdict.productSearch.searchKeywords}"
     encoding="htmlsinglequote" />' });
  </isif>
   });
    </script>
        
     <iscomment>DynamicYield section ADDED - END</iscomment>        
 
templates/default/search/searchResultsNoDecorator.isml

Adds the following code:

<isscript>
 var assets = require('*/cartridge/scripts/assets');
 assets.addJs('/js/dynamicYield/dyFilterTrack.js');
 </isscript>