GTM Integration

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

Prerequisites

  • Before you begin with this section, ensure that you have completed the prerequisites.
  • Ensure that you are using Personalization Library version >= 1.2.0. For more information about configuring the zones using Personalization Library, see Zone Configuration.

Adding a specific tag in GTM for Personalization

  1. Create and configure the personalization zones in your MPA channel. For more information, see Zone configuration
  2. In the Google Tag Manager (GTM), create a custom HTML tag, with name, for example, UbxWrtpIntegrationTag
  3. 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: Log in to Acoustic Exchange. From the Tools menu, go to UBX Capture. Click Enablement Key icon. 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 by the highlighted line of code 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, RTP) {
        RTP.onReady(function() {
            ubxOleUtils.loadContent(zoneIdToRenderingMap, RTP);
        });
    },

    loadRecommendationContent: function(recommendationZoneMap, RTP) {

        ubxOleUtils.loadContent(recommendationZoneMap, RTP)
    },

    loadContent: function(zoneMap, RTP) {

        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 = RTP.getZoneArea(zoneMap[i].zoneId);
                    if (zoneArea !== undefined) {
                        var numberOfRecommendations = zoneMap[i].numberOfRecommendations;
                        ubxOleUtils.callZoneRecommendation(zoneMap[i].zoneId, numberOfRecommendations, callbackFunction, RTP, zoneArea);
                        zoneMap.splice(i, 1);
                        i--;
                        continue;
                    }
                }

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

            retries--;
        }, 50)
    },

    callZoneRecommendation: function(zoneId, numberOfRecommendations, callbackFunction, RTP, zoneArea) {
        var zonearea = zoneArea;
        RTP.getRecommendedProducts(zoneId, numberOfRecommendations).then(function(response) {
            try {
                callbackFunction(zonearea, response, RTP);
            } 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 RTP = acoustic.personalization.JsWRTP.create();

                    if (typeof(personalizationZoneMap) != 'undefined' && personalizationZoneMap != null)
                        ubxOleUtils.loadAndPersonalizeContent(personalizationZoneMap, RTP);
                    if (typeof(recommendationZoneMap) != 'undefined' && recommendationZoneMap != null)
                        ubxOleUtils.loadRecommendationContent(recommendationZoneMap, RTP);
                    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,RTP,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
  • RTP: 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,RTP,outletId,zoneId) {
         if (contentId == 'DCIDNF404') {
            domOutlet.style.opacity = 1;
        } else {
            domOutlet.style.background = "url(" + contentId + ") 0px 0px / cover no-repeat";
            domOutlet.style.opacity = 1;
        }
  
              RTP.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
  • RTP: Personalization Library object
The following code snippet shows an example implementation.

<script>
var renderRecommendation = function(zoneArea, RESPONSE , RTP){
              if (RESPONSE !== 'PRNF404' && RESPONSE !== undefined) {
                            // Rendering logic
                                    /**
                                    * Create an HTMLElement dynamically for each product 
                                    * Add RTP.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 Knowledge 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.
    • Select the Don't fire Page View tag if UbxWrtpIntegrationTag fails or is paused check box.
  2. 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.
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

References

For an example code snippet to render product recommendations using Google Tag Manager, see Content rendering using GTM