Configure GTM with Exchange using Library v1.5.0

You can use Google Tag Manager to add Acoustic Personalization tags to your Single-Page Application (SPA) or Multi-Page Application (MPA).

Prerequisites and assumptions

Before proceeding with Google Tag Manager (GTM) integration with Acoustic Personalization, ensure that the following prerequisites and assumptions are fulfilled.

  • This topic covers Google Tag Manager implementation for Single-Page Applications (SPA) or Multi-Page Applications (MPA).
  • Ensure that you are using Personalization Library version 1.5.0.
  • GTM tags are used with Google Analytics – Universal Analytics.
  • You must have a GTM account configured with a Google Analytics container.
  • The Google Analytics settings variable should be created as a User-defined variable. In this topic, the Google Analytics setting variable is named as GA-Id. Also, enable the Ecommerce option for this variable by selecting the Enable Enhanced Ecommerce features and Use data layer check boxes.
  • The event actions in the tags must use the standard Google nomenclature.

Create a Page View tag

It is required to have a Page View tag for each page in your website. If you do not have this tag, create it with the details as below:

  • Tag Type: Google Analytics – Universal Analytics
  • Track Type: Page View
  • Google Analytics Settings > Select Settings Variable: Select the Google Analytics Settings variable (in this case, GA-Id that you created as a prerequisite.
  • Tag Firing Options: Once per page
  • Triggering > Firing options: All Pages

Add GTM tracking code to your SPA or MPA

  • In your Single-Page Application, add the following tracking code at the beginning of HEAD tag of the index.html page.
  • In your Multi-Page Application, add the following tracking code at the beginning of HEAD tag of each HTML page.
    This code enables Google Tag Manager on your website. In the code snippet, replace GTM_ID with your own Google Tag Manager tracking ID. If you have GTM already configured on your website, this code will already be present in the HEAD tag.
<script>
 (function (w, d, s, l, i) 
  {
    w[l] = w[l] || []; w[l].push({'gtm.start': new Date().getTime(), event: 'gtm.js'}); 
    var f = d.getElementsByTagName(s)[0],
    j = d.createElement(s), dl = l != 'dataLayer' ? '&l=' + l : ''; j.async = true; j.src =
'https://www.googletagmanager.com/gtm.js?id=' + i + dl; f.parentNode.insertBefore(j, f);
   })(window, document, 'script', 'dataLayer', 'GTM_ID');
</script>

Create custom variables in Google Tag Manager

Create the following custom variables in Google Tag Manager.
Create a user-defined variable gtmDimensionVariable in Google Tag Manager as shown below.

  • Variable Type: Data Layer Variable
  • Data Layer Variable Name: dimension1
  • Data Layer Version: Version 2
    Next, create another custom variable named SendHitTaskOverrideVariable of type Custom JavaScript.
    Add the following code snippet to this custom variable.

📘

Note

The code snippet shown here uses gaClientID as an example. You must decide the identifier and use the same.
Update the identifiers mapper array according to the Exchange unique identifier types.

function() 
{
  return function(model)
 {
  var globalSendTaskName = '_' + model.get('trackingId') + '_sendHitTask';
  var clientId = model.get('clientId');
  var identifiersMapperArray = [];
  identifiersMapperArray.push({ "name": "gaClientId", "value": clientId });
  var originalSendHitTask = window[globalSendTaskName] = window[globalSendTaskName] || model.get('sendHitTask');
  model.set('sendHitTask', function(sendModel) 
   {
    originalSendHitTask(sendModel);
    var payLoad = sendModel.get('hitPayload');
    var hitType = model.get("hitType");
    //var searchKeyword ; this must be declared globally on the website, and holds value of the search term 
    if (hitType=="pageview" && typeof searchKeyword !== 'undefined' &&  searchKeyword !== null)     
                {
                        var result = {};
                        payLoad.split("&").forEach(function (part) 
                        {
                             var item = part.split("=");
                             result[item[0]] = decodeURIComponent(item[1]);
                         });
                         var eveObj = google_ubx.mapToUBXEvent(result, google_ubx.googleToUBXPageViewEvent);
                         eveObj.attributes.push({"name":"searchTerms", "value": searchKeyword, "type": "string"});
                         eveObj.identifiers = identifiersMapperArray;
                         eveObj.eventCode = "ibmsearchedSite";
                         ibm_ubx.sendEvent(eveObj, google_ubx.host, google_ubx.authKey,"GOOGLEANALYTICS"); 
                         }
                         else{
                         google_ubx.sendEventFromPayload(payLoad, identifiersMapperArray);
                         }
   });
 };
}

In your Google Analytics settings variable (GA-Id), set the references to custom variables as shown below.

  • Fields to Set > Field name: customTask, Value: {{SendHitTaskOverrideVariable}}
  • Custom Dimensions > Index: 1, Dimension Value: {{gtmDimensionVariable}}
    Note that these values are only for illustrative purpose. You must use the Index and Dimension Value as per your setup.
    Next, enable the following in-built GTM variables:
  • Container ID
  • HTML ID

GTM configuration

Add a specific tag in GTM for Personalization

Create and configure the personalization zones in your MPA channel. For more information, see Zone configuration.

  1. In the Google Tag Manager (GTM), create a custom HTML tag, with name, for example, UbxWrtpIntegrationTag
  2. Add the following code to the UbxWrtpIntegrationTag custom HTML tag. Make the relevant changes to the code as instructed below before adding it to the tag.

Step 1: Initialize the variables

These variables are used to configure channel details for personalized website.

<script>
   var channelID = "<<Provide the channel tenant ID>>",           
   channelDimension = "<<Provide Index of Google Analytics custom dimension used for channelId>>";
  
  /*Do Not Modify Below Variables*/
    var ubxEvents = []; 
            var containerId = {{Container ID}} ; 
            var htmlId = {{HTML ID}} ;
            var personalizationZoneMap = [], recommendationZoneMap = [];
</script>

Step 2: Get Acoustic Exchange Capture Enablement Key

To get the Enablement Key:

  1. Log in to Acoustic Exchange.
  2. From the Tools menu, go to Exchange Capture.
  3. Click Enablement Key icon.
  4. Copy the two lines with the enablement key and paste them in the UbxWrtpIntegrationTag. Make the necessary change as indicated in the code snippet.
<script type="text/javascript" src="<<//path/to/ubxCapture.js>>"></script>
<script>
ubxCapture.setTenantID("WRTP",channelID);
</script>
<script type="text/javascript">
ubxCapture.setID("<<Enablement Key from UBX Capture>>"); 
//The function setTenantID must be called before setID. 
</script>

Optional: If you have more than one Google Analytics endpoints registered in the Acoustic Exchange Capture, then you need to select the endpoint to be used by specifying its Endpoint authentication key, as shown in the following snippet.

<script type="text/javascript" src="<<//path/to/ubxCapture.js>>"></script>
<script>
ubxCapture.setTenantID("WRTP",channelID);
ubxCapture.setTenantID("GA","<<Endpoint authentication key for Google Analytics endpoint in UBX>>");
</script>
<script type="text/javascript">
ubxCapture.setID("<<Enablement Key from UBX Capture>>"); 
//The function setTenantID must be called before setID. 
</script>

Step 3: Add the following snippet in the GTM tag

Copy and paste the following code snippet as-is, into your UbxWrtpIntegrationTag GTM tag. No changes need to be made to this code snippet.

<script type="text/javascript">

var ubxOleUtils = ({

    loadAndPersonalizeContent: function(zoneIdToRenderingMap, personalizationObject) {
        personalizationObject.onReady(function() {
            ubxOleUtils.loadContent(zoneIdToRenderingMap, personalizationObject);
        });
    },

    loadRecommendationContent: function(recommendationZoneMap, personalizationObject) {
       personalizationObject.onRecommendationsReady(function (){
             ubxOleUtils.loadContent(recommendationZoneMap, personalizationObject)
        }); 
    },

    loadContent: function(zoneMap, personalizationObject) {

        var retries = 200;
        var timerHandle = setInterval(function() {

            if (retries <= 0 || zoneMap.length == 0 || document.readyState == 'complete') {
                clearInterval(timerHandle);
            }
            for (var i = 0; i < zoneMap.length; i++) {

                var callbackFunction = zoneMap[i].renderingFunction;

                if (zoneMap[i].numberOfRecommendations) {
                    var zoneArea = personalizationObject.getZoneArea(zoneMap[i].zoneId);
                    if (zoneArea !== undefined) {
                        var numberOfRecommendations = zoneMap[i].numberOfRecommendations;
                        ubxOleUtils.callZoneRecommendation(zoneMap[i].zoneId, numberOfRecommendations, callbackFunction, personalizationObject, zoneArea);
                        zoneMap.splice(i, 1);
                        i--;
                        continue;
                    }
                }

                if (zoneMap[i].outletId) {
                    var outletId = zoneMap[i].outletId;
                    var outletArea = personalizationObject.getZoneArea(outletId);
                    if (outletArea !== undefined) {
                        var contentId = personalizationObject.getContentId(zoneMap[i].zoneId);
                        zoneMap[i].renderingFunction(contentId, outletArea, personalizationObject, outletId, zoneMap[i].zoneId);
                        zoneMap.splice(i, 1);
                        i--;
                        continue;
                    }
                }
            }

            retries--;
        }, 50)
    },

    callZoneRecommendation: function(zoneId, numberOfRecommendations, callbackFunction, personalizationObject, zoneArea) {
        var zonearea = zoneArea;
        personalizationObject.getRecommendedProducts(zoneId, numberOfRecommendations).then(function(response) {
            try {
                callbackFunction(zonearea, response, personalizationObject);
            } catch (error) {
                console.log('RTP: Please check HTML element or rendering for recommendations');
            }
        });

    },

    setGtmEvents: function() {
        try {
            var gtmEvents = ['googleToUBXAddToCartEvent', 'googleToUBXCartPurchaseEvent', 'googleToUBXCartPurchaseItemEvent', 'googleToUBXFormErrorEvent',
                'googleToUBXPageViewEvent', 'googleToUBXProductRatingEvent', 'googleToUBXProductReviewEvent', 'googleToUBXProductViewEvent', 'googleToUBXRemoveFromCartEvent',
                'googleToUBXVideoCompletedEvent', 'googleToUBXVideoEvent', 'googleToUBXVideoLaunchedEvent', 'googleToUBXVideoPausedEvent', 'googleToUBXVideoPlayedEvent'
            ]
            for (var i = 0; i < gtmEvents.length; i++) {
                google_ubx[gtmEvents[i]]
                    .attributesMapper
                    .push({
                        "googleName": "cd" + channelDimension,
                        "ubxName": "channeltenantId",
                        "type": "string"
                    })
            }
        } catch (error) {
            console.error(error);
        }
    },

    notifySuccess: function(htmlID, containerID) {
        try {
            var gtm = window.google_tag_manager[containerID];
            gtm.onHtmlSuccess(htmlID);
        } catch (e) {
            gtm.onHtmlFailure(htmlID);
        }
    },

    registerUbxEventCallbacks: function() {
        ibm_ubx.registerCallback("WRTP", function(eventPayload) {
            if (eventPayload) ubxEvents.push(eventPayload);
        });
    },

    setGtmDimension: function(containerID, channelDimension, channelID) {
        google_tag_manager[containerID]
            .dataLayer
            .set('dimension' + channelDimension, channelID);
    },


    initializeRTPLib: function(channelData, personalizationZoneMap, recommendationZoneMap) {

        var OLEInitComplete = false;
        var mapperInitComplete = false;
        var documentReady = false;
        var retries = 3000;
        var timerHandle = setInterval(function() {

            if (retries <= 0 || (OLEInitComplete && mapperInitComplete)) {

                if (typeof channelData.customFunction !== 'undefined') {
                    try {
                        channelData.customFunction();
                    } catch (err) {
                        console.log(err);
                    }
                }
                clearInterval(timerHandle); 
                ubxOleUtils.notifySuccess(channelData.htmlID, channelData.containerID);
            }

            if (google_ubx.googleToUBXAddToCartEvent && typeof(ibm_ubx.registerCallback) == "function" && !mapperInitComplete) {
                try {
                    ubxOleUtils.setGtmEvents(channelData.channelDimension);
                    ubxOleUtils.setGtmDimension(channelData.containerID, channelData.channelDimension, channelData.channelID);
                    ubxOleUtils.registerUbxEventCallbacks();
                    mapperInitComplete = true;
                } catch (error) {
                    console.log(error);
                }
            }

            if (ubxCapture.wrtpLoaded && typeof acoustic != 'undefined' && typeof(ibm_ubx.sendEvent) == "function" && !OLEInitComplete) {
                try {
                    var personalizationObject = acoustic.personalization.JsWRTP.create();

                    if (typeof(personalizationZoneMap) != 'undefined' && personalizationZoneMap != null)
                        ubxOleUtils.loadAndPersonalizeContent(personalizationZoneMap, personalizationObject);
                    if (typeof(recommendationZoneMap) != 'undefined' && recommendationZoneMap != null)
                        ubxOleUtils.loadRecommendationContent(recommendationZoneMap, personalizationObject);
                    OLEInitComplete = true;

                } catch (error) {
                    console.log(error);
                }
            }

            retries--;
        }, 10);
    },
});

</script>

Step 4: Rendering logic

The channel developers should write their own rendering logic. The following code snippets provide guidance to render the personalized content or the recommended products on the channel.

For Content Personalization
Create a function with the following signature:

function(contentId, domOutlet,personalizationObject,outletId,zoneId)

Where:

  • contentId: Content ID that provides the actual personalized content as configured in Acoustic Personalization UI.
  • domOutlet: HTML DOM element to be personalized
  • personalizationObject: Personalization Library object
  • outletId: ID selector for domOutlet
  • zoneId: Registered zone to be personalized
    The function should render the personalized content received as a part of contentId on the personalized zone specified as domOutlet. The following code snippet shows an example implementation.
<script> 
var renderContentPersonalization =  function(contentId, domOutlet,personalizationObject,outletId,zoneId) {
  //use this IF-ELSE block for content as image URL     
  if (contentId == 'DCIDNF404') {
            domOutlet.style.opacity = 1;
        } else {
            domOutlet.style.background = "url(" + contentId + ") 0px 0px / cover no-repeat";
            domOutlet.style.opacity = 1;
        }
  
  //use this IF-ELSE block for content as HTML
  if (contentId == 'DCIDNF404') {
            domOutlet.style.opacity = 1;
        } else {
            domOutlet.innerHTML = contentId;
            domOutlet.style.opacity = 1;
        }
  
  //This is optional, required only capturing the click events
             personalizationObject.trackEvent("<<zone ID>>","<<element ID>>","<<event>>");
    }
   personalizationZoneMap.push({'zoneId':'<<Zone ID>>','outletId':'<<OutletID>>',
'renderingFunction' : renderContentPersonalization});
</script>

The above code snippet illustrates content personalization for a single zone. If multiple zones use the same rendering logic, you can reuse the same rendering function. If different zones use different rendering logic, create multiple functions as applicable.
After these rendering functions are created, populate the personalizationZoneMap array that holds objects in the format:

{'zoneId':'<<Zone ID>>','outletId':'<<OutletID>>','renderingFunction' : renderContentPersonalization}

Where:

  • zoneId: Registered zone to be personalized
  • outletId: ID selector for domOutlet
  • renderContentPersonalization: The rendering function defined above and applicable to the Zone ID mentioned in the object.

For Product Recommendations
Create a function with the following signature:

function(zoneArea, RESPONSE, RTP)

Where:

  • zoneArea: Zone on which product recommendations are to be displayed
  • RESPONSE: List of recommended products
  • personalizationObject: Personalization Library object
    The following code snippet shows an example implementation.
<script>
var renderRecommendation = function(zoneArea, RESPONSE,personalizationObject){
              if (RESPONSE !== 'PRNF404' && RESPONSE !== undefined) {
                            // Rendering logic
                                    /**
                                    * Create an HTMLElement dynamically for each product 
                                    * Add personalizationObject.trackProduct(RESPONSE[i].PRODUCT_ID, event); on click of each HTMLElement.
                                    **/
                                    zoneArea.append(/*created element*/);
                                    zoneArea.style.opacity = 1;
                                    // Add code to display the content
                    
             } else {
                  console.log('WRTP:Error in getting the recommendation.');
             }
            }
 recommendationZoneMap.push({'zoneId':'<<Zone ID>>',
'numberOfRecommendations':<<Integer value for number of recommendations>>, 'renderingFunction' : renderRecommendation});
</script>

The above code snippet illustrates product recommendations for a single zone. If multiple zones use the same rendering logic, you can reuse the same rendering function. If different zones use different rendering logic, create multiple functions as applicable.
After these rendering functions are created, populate the recommendationZoneMap map. This map is an array that holds objects in the format:

{'zoneId':'<<Zone ID>>', 'numberOfRecommendations':<<Integer value for number of recommendations>>, 'renderingFunction' : renderRecommendation}

Where:

  • zoneID: ID of the zone where product recommendations should be displayed
  • numberOfRecommendations: Integer value for number of recommendations, for example: 10
  • renderRecommendation: The rendering function defined above and applicable to the Zone ID mentioned in the object.

Step 5: Add code to trigger the script execution

Add the following code snippet as it is.

<script>            
var channelData = {
                    channelID : channelID,
                    channelDimension: channelDimension,
                    htmlID : htmlId,
                    containerID : containerId,
                    customFunction : function (){ 
                                                  }
                                                  };
ubxOleUtils.initializeRTPLib(channelData,personalizationZoneMap,recommendationZoneMap);

</script>
  • Optional: If you want to add any custom function, you can add it in the customFunction : function () {ADD YOUR CUSTOM FUNCTION HERE} For more information, see this Help Center page.

In the above code snippets:

  • Replace ZONE ID with the registered zone ID.
  • Replace OUTLET ID with the outlet ID (usually the div id) of the page that has the registered zone.
  • The trackEvent function is optional and it is used for tracking the click events on your personalized zones. In this function:
    *Replace ZONE ID with the ID of the zone to which you want to attribute the click events. Ensure that this zone ID is the same as that configured in the Acoustic Personalization.
    • Replace ELEMENTID with the ID of the HTML element for which you want to track the specified event.
    • Replace EVENT with the event type that you want to track for the given zone. In this case, replace "event" with click to track the click events on the specified zone.
  • Ensure that you specify the parameters in the same order as specified in the code snippet.
    For more information about the capturing the click events, see Capturing the click events on zones
  • You must have one custom dimension available for Acoustic Personalization to send the channelTenantId information. To get this information, log in to Google Analytics and create a custom dimension field with User's scope. Name the field as channelTenantId.
  • For the UbxWrtpIntegrationTag tag, choose the Tag Firing option as Once Per Page.
  • The error code DCIDNF404 is returned when no published rules are available or when none of the published rules match the visitor behavior on the channel. In such case, Acoustic Personalization does not return any personalized content. The channel must handle this scenario by specifying a default content to be displayed. For more information about the error codes, see Personalization Library response codes

Next, perform the following steps.

  1. Go to the Page View tag, and make the following updates in the Tag Sequencing section:
  • Select the Fire a tag before Page View fires check box.
  • Select UbxWrtpIntegrationTag from the drop-down list.
641
  1. After you have set up your tags, click the Submit button and then Publish button at the top of the page, to publish your Google Tag Manager container.
  2. You can verify the flow of events by triggering events on your website and verifying them using the Test Drive. For more information, see Testing the events flow in Acoustic Exchange