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.
- 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 >>);
- 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];
- 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 ofoutlet 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 inDEFAULTCONTENT
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.
- 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 helpsrenderingWidget()
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));
}
- Next, use the stored content from local storage to set the value of the
ContentVariable
. TheContentVariable
handles rendering this content on the registered zone. Call therenderingWidget()
function in thengOnInit()
function on the zone you want to implement rendering logic. Here is an example code that illustrates this step:
<<ContentVariable>> = this.renderingWidget(<<zoneName>>)
- 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 ofoutlet 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 inDEFAULTCONTENT
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.
- Import local storage in the React component where you would like to use the rendering widget:
import ls from 'local-storage';
- Add the following rendering widget functions, after
componentDidMount()
function. The functions must be added before therender()
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 && 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 thewebstorage
object. The content is updated every time content is received from the PZN library. This update helpsrenderingWidget()
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));
}
- Next, use the stored content from local storage to set the
ContentVariable
's value. TheContentVariable
handles rendering this content on the registered zone. Call therenderingWidget()
function in thecomponentDidMount()
function on the zone. Set the state forzoneContent
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 &&
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 inDEFAULTCONTENT
at the specified place in the code snippet. - Provide the
personalizationObject
to refer to a personalization object.
Updated almost 2 years ago