Content rendering – Display the personalized content

Display the personalized content or product recommendations from Personalization on your channel.

📘

Important:

Rendering personalized content is the responsibility of the channel developer. The code snippets are provided here are for guidance and illustration purposes only.

Content flicker

You may notice flickering of content (for example, images) on your channel. Use the following steps to handle the content flickering on your channel. The flicker may occur when default content is momentarily displayed on the channel. Soon it is replaced by the personalized content that is returned by Personalization.

Example scenario 1:
When a visitor first loads the page, there is no personalized content to display. Since the website content loads from the client's server, the server content loads first. When the personalized content replaces the original content, it causes flickering.

Solution:
Use lightweight placeholder content instead of actual (default) content. Then, based on the PZN library response, you can replace the placeholder content. You can replace it with either the default or personalized content. The lightweight placeholder handles the graceful transition of contents.

Example scenario 2:
During subsequent page loads, there is no flicker, as there is no change in content. In this case, there is cached content in the browser. The personalized content is picked from the browser cache.

Example scenario 3:
During subsequent page loads, the personalized content returned from the library changes. And therefore, the content changes from the cached content. The content transition causes flicker. Solution: In this case, the cached content is displayed initially. When transitioning to the new content, you can blur the cached content. Then, based on the library response, replace the cached content. You can replace it with either the default or personalized content. Blurring the content takes care of the graceful transition of content.

In scenarios 1 and 2, CSS transitions assist in the smoother loading of the images.

📘

Tip:

For better image transitioning, it is recommended to use images with low file size.

Handle content flicker

There are multiple approaches to handle the content flicker. The following shows one approach.
Using the zone opacity
In your website CSS, add the following snippet.

<style>
      #zoneId {
            opacity: 0;
            }
</style>

Here, ZONEID is the id attribute of the DOM Node from the HTML page you want to personalize.
Setting the opacity to 0 (or a similar low value to slightly grey it out) keeps the zone content hidden. Hidden content prevents flicker. The web browser still renders the page and loads all the images, style sheets, and components.
Reset the opacity attribute back to 1 after PZN returns a response as part of the rendering logic.

Content rendering examples

Cache helps to render personalized content on the zones of your application quickly. Use the PZN rendering widget if your channel does not have the caching mechanism.

This example assumes that you are using Content as the content management system (CMS).

📘

Tip:

To render personalized images faster, use the image pre-loading feature of JavaScript.

The zones of your MPA must be registered in Personalization. For more information, see Register a zone.

  1. Add the following code snippet anywhere before the PZN library configuration code. It uses the browser caching feature to render personalized content on the zone faster.
function renderingWidget(zoneArray) { let zoneData, outletDiv, now; let contentFromLS = JSON.parse(window.localStorage.getItem('personalisedContent')); zoneArray.forEach(function (outletId) { outletDiv = document.getElementById(outletId); if(outletDiv){ outletDiv.style.background = "url(" + <<placeHolderImageUrl>> + ") 0px 0px / cover no-repeat"; if (contentFromLS && contentFromLS[outletId]) { zoneData = contentFromLS[outletId]; /* Add display logic code to display the personalized/default content contained . . . window.localStorage.setItem('personalisedContent', JSON.stringify(contentFromLS)); } } } }); } /* Call the rendering widget function for an array of outletIds for registered zones with similar contentType */ renderingWidget(<< Array of outlet Ids >>);
  1. Use the caching feature used in rendering logic. The latest cached content for a zone configured for personalization is fetched. Make the following changes in the zone configuration code.
contentObjFromLS = (window.localStorage.getItem('personalisedContent')) ? JSON.parse(window.localStorage.getItem('personalisedContent')) : {}; oldZoneInformation = contentObjFromLS[outletId];
  1. Add the following code to zone configuration code to handle the following scenarios:
    • When there is no data in local storage.
    • When the new content received from the library is not the same as the one stored in LocalStorage. The code displays the latest content received and updates the localStorage.
if (!oldZoneInformation || (contentId !== oldZoneInformation[0].contentID)) { const outletArea = personalizationObject.getZoneArea(outletId); displayContent(contentId, outletArea); ……. . . contentObjFromLS[outletId] = newZoneInformation; window.localStorage.setItem('personalisedContent', JSON.stringify(contentObjFromLS));

Sample code
Example code for rendering widget, specific for rendering the URL content.

function renderingWidget(zoneArray) { let zoneData, outletDiv, now; let contentFromLS = JSON.parse(window.localStorage.getItem('personalisedContent')); zoneArray.forEach(function (outletId) { outletDiv = document.getElementById(outletId); if(outletDiv){ outletDiv.style.background = "url(" + <<placeHolderImageUrl>> + ") 0px 0px / cover no-repeat"; if (contentFromLS && contentFromLS[outletId]) { zoneData = contentFromLS[outletId]; if (zoneData[0].contentID == 'DCIDNF404') { outletDiv.style.background = "url(" + <<defaultContent_url>> + ") 0px 0px / cover no-repeat"; outletDiv.style.opacity = 1; } else { let image = new Image(); outletDiv.style.background = "url(" + zoneData[0].contentID + ") 0px 0px / cover no-repeat"; outletDiv.style.filter = 'blur(5px)'; image.onload = function () { outletDiv.style.background = "url(" + zoneData[0].contentID + ") 0px 0px / cover no-repeat"; outletDiv.style.animation = '1s ease-in-out 0s 1 slideInFromLeft'; outletDiv.style.filter = 'blur(0px)'; outletDiv.style.opacity = 1; }; image.src = zoneData[0].contentID; } now = new Date().getTime(); ..... ..... } } }); renderingWidget(['Outlet1', 'Outlet2', 'Outlet3']);

Next, add code like the following example in the function that processes the content IDs. For example, processContent()) function inside the init() method of your zone configuration snippet.

📘

Note:

The function names shown here are for guidance purposes only. Use the same functions/variables that you used for zone configuration.

{ let contentId = personalizationObject.getContentId(zoneId); /* fetch local storage data for zone */ let contentObjFromLS = (window.localStorage.getItem('personalisedContent')) ? JSON.parse(window.localStorage.getItem('personalisedContent')) : {}; let oldZoneInformation = contentObjFromLS[outletID]; if (!oldZoneInformation || (contentId !== oldZoneInformation[0].contentID)) { const outletArea = personalizationObject.getZoneArea(outletId); displayContent(outletArea, contentId); /* if data doesnt exist,add a new entry for the zone */ let newZoneInformation = []; newZoneInformation.push({ contentID: contentId, expiry_time: new Date().getTime() + (1 * 60 * 60 * 1000) /* expiring the localStorage data every 1 hour */ }); contentObjFromLS[outletID] = newZoneInformation; window.localStorage.setItem('personalisedContent', JSON.stringify(contentObjFromLS)); } }

In the code snippets:

  • Replace ZONEARRAY with an array of outlet IDs corresponding to the registered zones.
  • Replace ZONEID with the registered zone ID.
  • Replace OUTLETID with the outlet ID (usually the div id) of the page that has the registered zone.
  • Replace CONTENTID with the content ID for the personalized content from your CMS.
  • Replace PLACEHOLDERIMAGEURL with an image URL to display as a placeholder image. The actual content of the zone will replace it once it has loaded.
  • Replace DEFAULTCONTENT_URL with the default content to display if rules do not match the user behavior.
  • 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 a case, Personalization does not return any personalized content. The channel must handle this scenario by specifying a default content to be displayed in DEFAULTCONTENT at the specified place in the code snippet.
  • Provide the personalizationObject to refer to a personalization object.

The zones of your Angular SPA must be registered in Personalization. For more information, see Register a zone.

  1. Add the following two rendering widget functions in the .ts file of your component:
  • renderingWidget() function – uses the browser cache feature to render personalized content faster.
renderingWidget(zone) { let zoneData , zoneContent, now; let contentFromLS = window.localStorage.getItem('personalisedContent') ? JSON.parse(window.localStorage.getItem('personalisedContent')) : {}; if (contentFromLS && contentFromLS[zone]){ zoneData = contentFromLS[zone]; zoneContent = zoneData[0].contentID; now = new Date().getTime(); if (now > zoneData[0].expiry_time) { /* Remove the localStorage entry for the zone if expiry time has been reached */ zoneData.splice(0); localStorage.set('personalisedContent', JSON.stringify(contentFromLS)); } } return zoneContent ? zoneContent : ''; }
  • updateZoneInfo() function – updates zone and content data in the webstorage object. The content is updated every time content is received from the PZN library. This update helps renderingWidget() to achieve faster rendering.
updateZoneInfo(zone, contentId){ let contentFromLS = window.localStorage.getItem('personalisedContent') ? JSON.parse(window.localStorage.getItem('personalisedContent')) : {}; let newZoneInformation = []; newZoneInformation.push({ contentID: contentId, expiry_time: new Date().getTime() + (1 * 60 * 60 * 1000) /* expiring the localStorage data every 1 hour */ }); contentFromLS[zone] = newZoneInformation; window.localStorage.setItem('personalisedContent', JSON.stringify(contentFromLS)); }
  1. Next, use the stored content from local storage to set the value of the ContentVariable. The ContentVariable handles rendering this content on the registered zone. Call the renderingWidget() function in the ngOnInit() function on the zone you want to implement rendering logic. Here is an example code that illustrates this step:
<<ContentVariable>> = this.renderingWidget(<<zoneName>>)
  1. Call the updateZoneInfo to update the content information in the storage object. This call ensures the content data is available in the local storage for rendering logic on the zone. You can call the function after receiving content from the library.
this.updateZoneInfo((<<zoneName>>, <<contentId>>);

Sample code
Example code shows the placement of the function calls inside the ngOnInit() function.

function renderingWidget(zoneArray) { let zoneData, outletDiv, now; let contentFromLS = JSON.parse(window.localStorage.getItem('personalisedContent')); zoneArray.forEach(function (outletId) { outletDiv = document.getElementById(outletId); if(outletDiv){ outletDiv.style.background = "url(" + <<placeHolderImageUrl>> + ") 0px 0px / cover no-repeat"; if (contentFromLS && contentFromLS[outletId]) { zoneData = contentFromLS[outletId]; if (zoneData[0].contentID == 'DCIDNF404') { outletDiv.style.background = "url(" + <<defaultContent_url>> + ") 0px 0px / cover no-repeat"; outletDiv.style.opacity = 1; } else { let image = new Image(); outletDiv.style.background = "url(" + zoneData[0].contentID + ") 0px 0px / cover no-repeat"; outletDiv.style.filter = 'blur(5px)'; image.onload = function () { outletDiv.style.background = "url(" + zoneData[0].contentID + ") 0px 0px / cover no-repeat"; outletDiv.style.animation = '1s ease-in-out 0s 1 slideInFromLeft'; outletDiv.style.filter = 'blur(0px)'; outletDiv.style.opacity = 1; }; image.src = zoneData[0].contentID; } now = new Date().getTime(); ..... ..... } } }); renderingWidget(['Outlet1', 'Outlet2', 'Outlet3']);

In the code snippets:

  • Replace ZONEARRAY with an array of outlet IDs corresponding to the registered zones.
  • Replace ZONEID with the registered zone ID.
  • Replace OUTLETID with the outlet ID (usually the div id) of the page that has the registered zone.
  • Replace CONTENTID with the content ID for the personalized content from your CMS.
  • In the IMG_CONST, provide a map of content IDs and the actual content for personalization.
  • 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 a case, Personalization does not return any personalized content. The channel must handle this scenario by specifying a default content to be displayed in DEFAULTCONTENT at the specified place in the code snippet.
  • Provide the personalizationObject to refer to a personalization object.

The zones of your React SPA must be registered in Personalization. For more information, see Register a zone.

  1. Import local storage in the React component where you would like to use the rendering widget:
import ls from 'local-storage';
  1. Add the following rendering widget functions, after componentDidMount() function. The functions must be added before the render() functions of your component:
    • renderingWidget() function – uses the browser cache feature to render personalized content faster.
renderingWidget(zone) { let zoneData , zoneContent, now; let contentFromLS = ls.get('personalisedContent') ? JSON.parse(ls.get('personalisedContent')) : {}; if (contentFromLS &amp;&amp; contentFromLS[zone]){ zoneData = contentFromLS[zone]; zoneContent = zoneData[0].contentID; now = new Date().getTime(); if (now > zoneData[0].expiry_time) { /* remove the localStorage entry for the zone if expiry time has been reached */ zoneData.splice(0); ls.set('personalisedContent', JSON.stringify(contentFromLS)); } } return zoneContent ? zoneContent : ''; }
  • updateZoneInfo() function – updates zone and content data in the webstorage object. The content is updated every time content is received from the PZN library. This update helps renderingWidget() to achieve faster rendering.
updateZoneInfo(zone, contentId){ let contentFromLS = ls.get('personalisedContent') ? JSON.parse(ls.get('personalisedContent')) : {}; let newZoneInformation = []; newZoneInformation.push({ contentID: contentId, expiry_time: new Date().getTime() + (1 * 60 * 60 * 1000) /* expiring the localStorage data every 1 hour */ }); contentFromLS[zone] = newZoneInformation; ls.set('personalisedContent', JSON.stringify(contentFromLS)); }
  1. Next, use the stored content from local storage to set the ContentVariable's value. The ContentVariable handles rendering this content on the registered zone. Call the renderingWidget() function in the componentDidMount() function on the zone. Set the state for zoneContent as received from the personalization library.
    The Acoustic engine continues to look for new content in the background. Here is a sample code that illustrates how to this step:
this.setState({ welcomeBannerContent: this.renderingWidget(<<zoneName>>),})

Sample code
Example code shows the placement of the function calls inside the ComponentDidMount() function.

componentDidMount () { this.WRTP = this.context; this._isMounted = true; this._isMounted &amp;&amp; this.setState({ /* Calling renderingWidget() to fetch zone data from LS and set the state of WelcomeBannner zone content with the stored content */ welcomeBannerContent: this.renderingWidget('WelcomeBannerZone'), }); this._isMounted &&; this.WRTP.onReady(() => { let contentId = this.WRTP.getContentId('WelcomeBannerZone'); this._isMounted && this.setState({ welcomeBannerContent: contentId, }); /* Calling updateZoneInfo() to update newly received content data from personalization library for WelcomeBanner zone in LS */ this.updateZoneInfo('WelcomeBannerZone', contentId); }); }

In the code snippets:

  • Replace ZONE with the registered zone ID.
  • Replace OUTLETID with the outlet ID (usually the div id) of the page that has the registered zone.
  • Replace CONTENTID with the content ID for the personalized content from your CMS.
  • In the IMG_CONST, provide a map of content IDs and the actual content for personalization.
  • 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 a case, Personalization does not return any personalized content. The channel must handle this scenario by specifying a default content to be displayed in DEFAULTCONTENT at the specified place in the code snippet.
  • Provide the personalizationObject to refer to a personalization object.