Note: If your e-commerce platform is not Salesforce Commerce Cloud, use the generic implementation guidelines.
The Dynamic Yield – Salesforce Commerce Cloud cartridge provides a quick and seamless way to implement Dynamic Yield. The cartridge 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.
There are two versions of this article, depending on your Salesforce Commerce Cloud environment. This article is for environments using the SFRA architecture. If your environment uses Controllers, see Implementing Dynamic Yield on Salesforce Commerce Cloud - Controllers.
The cartridge is self-contained and can integrate into any project. It can be configured in Business Manager and contains all elements necessary to integrate systems into any eCommerce environment.
Prerequisite
This cartridge is designed for Salesforce Commerce Cloud API version 19.10, Compatibility Mode: 18.10. If you are using a different version manual changes might be required.
Step 1: Create a Dynamic Yield site
- Click the dropdown arrow on your site name at the top left of the screen, and then click Create.
- Enter a name for your site
- Select Website as the platform type.
- Enter the URL of your site.
- Select Salesforce Commerce Cloud as the e-commerce platform.
- In the Localization section, select the currency to use in all Dynamic Yield revenue-based reports.
- Click Create. You will receive an email with your site ID, access key, and sƒecret access key. These are needed later in the implementation process.
Step 2: Install the cartridge in the sandbox
- Download the cartridge here.
- Open Salesforce Commerce Cloud Studio.
- Import the downloaded cartridges into your IDE workspace:
int_dynamicyield, int_dynamicyield_sfra, and bc_dynamicyield
- Link the cartridge to the sandbox as follows:
- Select sandbox connection and then select Properties.
- Select Project Reference.
- Check in int_dynamicyield_sfra, int_dynamicyield and bc_dynamicyield.
For more details, see the Salesforce Commerce Cloud documentation.
- Verify that the cartridge has been installed by going to Business Manager › Merchant Tools › Site Preferences › Custom Site Preference and seeing that the Dynamic Yield folder is there.
Step 3: Set up the Business Manager
- Go to Business Manager › Administration › Sites › Manage Sites.
- Select your site, and go to the Settings tab.
- In cartridges, add int_dynamicyield_sfra before :app_storefront_base and int_dynamicyield:bc_dynamicyield after :app_storefront_base as follows:
- Go to Business Manager › Administration › Sites › Manage Sites.
- Select the site Business Manager, and select the Settings tab.
- In cartridges, add the string :bc_dynamicyield as shown in the image.
- Go to Business Manager › Site Development › Site Import & Export.
- Upload the file metadata/dynamicyield.zip. This file contains all necessary settings for the site.
- Import the file you just uploaded by selecting it and clicking Import. Click Yes to confirm.
- Verify that the implementation is successful by going to your test StoreFront and checking that the Dynamic Yield icon appears as shown in the image:
Step 4: Configure the cartridge in Site Preferences
- Go to Business Manager: Sites › Your Site › Site Preferences › Custom Site Preferences › Dynamic Yield.
- Verify that Enable Dynamic Yield and Enable Catalog Export are set to Yes.
- Enter your Dynamic Yield Site ID.
- Enter the Product Feed’s S3 Access Key ID and Secret Access Key.
- If your product feed has custom columns, in addition to the required columns, add them in the Product Attributes field. You can add up to 30. Once custom columns are added, they can no longer be removed.
These can later be used for targeting (for example, if you add "color", you can use it to target users who purchased blue items), affinity score (for example, include color affinity in the affinity recommendation algorithm), define merchandising rules in your recommendation widgets (for example, never recommend products of a specific brand). - Enter the Dynamic Yield Endpoint for Product Feed uploading (only the host part):
-
If you are using the Dynamic Yield US data center:
Dynamic Yield Endpoint: com.dynamicyield.feeds - If you are using the Dynamic Yield EU data center, update the following fields:
- Dynamic Yield Endpoint: dy-datafeeds-eu
- Enable Dynamic Yield Europe Account: 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.
-
- Dynamic Yield uses a dedicated, blazing-fast 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>
- Enable async integration: This should be set to No in most circumstances. If you configure the scripts to run asynchronously, unexpected behaviors can occur affecting which variations are shown, loading times, flicker, and so on. Dynamic Yield provides a high server redundancy rate and lightning-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: Configure the product feed sync schedule
- Go to Business Manager: Administration › Operations › Job Schedules › Dynamic Yield.
- Go to the Schedule and History tab to define the synchronization schedule.
- Verify that Enabled is checked.
- 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. Notice that if your product catalog changes later, it will not update the Product Feed in Dynamic Yield.
- Use the date picker in the From and To fields to define the first time the job will run, and the time in which the job will no longer run. If the To field is blank, the job will continue to run according to the Run Time schedule. We recommend choosing today as the start date and keeping the end date empty.
- The Run Time determines the your sync rate (minimum: 60 minutes).
- Go to the Step Configurator tab.
- Click Organization.
- Select needed site for this job and click Assign.
Step 6: Configure events
The cartridge automatically supports the following events:
- Purchase
- Add to cart
- Signup
- Login
- Remove from cart
- Sync cart
- Sort items
- Filter items
- Change attribute
- Promo code entered
- Search
To add additional events:
- Go to int_dynamicyield/cartridge/scripts/helper/dyHelper.js and locate the getDYresponse function. Use the examples of supported events to add custom events.
- Add the events to your JS code wherever you want to trigger them.
- Each event also requires a call from the client JS scripts to the DynamicYield-GetAPIProperties endpoint.
Step 7: Validate implementation
Once you have implemented Dynamic Yield, it is 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
This section describes the changes implemented by the cartridge. These details may be needed if you have made custom modifications to any of these items.
- The following code is added 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 ===================================
- The following code is added 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 ====================================
- The following code is added to the .cart-delete_confirmation-btn element:
// START - dynamicYield call for setRemovedItem dynamicYield.callEvent('Remove from Cart'); // END ===================================
- The following code is added 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 ======================
- The following code is added 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 ====================================
- The following code is added 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 ====================================
- The following code is added 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 ====================================
The following code is added 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();
The following code is added 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
The following code is added 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
The following code is added 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:
The following code is added:
<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>
The following code is added:
<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>
The following code is added:
<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>
The following code is added:
<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>
The following code is added:
<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>
The following code is added:
<isscript>
var assets = require('*/cartridge/scripts/assets');
assets.addJs('/js/dynamicYield/dynamicYieldSfra.js');
</isscript>
The following code is added:
<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>
The following code is added:
<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>
The following code is added:
<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>
The following code is added:
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>
The following code is added:
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>
The following code is added:
<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>
The following code is added:
<li class="col-sm-4 col-md-12 ${!refinementValue.selectable
? 'disabled' : ''}"
data-type="${refinementValue.type}" data-value="${refinementValue.displayValue}">
The following code is added:
<li class="color-attribute ${!refinementValue.selectable ?
'disabled' : ''}"
data-type="${refinementValue.type}" data-value="${refinementValue.displayValue}">
The following code is added:
<li class="col-sm-2 col-md-12 ${!refinementValue.selectable
? 'disabled' : ''}"
data-type="${refinementValue.type}" data-value="${refinementValue.displayValue}">
The following code is added:
<li data-type="category" data-value="${category.displayValue}">
The following code is added:
<li class="col-sm-4 col-md-12"
data-type="price" data-value="${refinementValue.displayValue}">
The following code is added:
<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>
The following code is added:
<isscript>
var assets = require('*/cartridge/scripts/assets');
assets.addJs('/js/dynamicYield/dyFilterTrack.js');
</isscript>