We’ve migrated our documentation to a new site, which means some URLs have changed.
Audience

Example 6 - Combined Cxense Content (recs) and Ads widget

By combining recommendations and ads into the same widget, a publisher can avoid the user completely overlooking blocks of ads, and in most cases get a higher CTR for the ads because they are actually in the view of the user, as he browses down the list of article recommendations.

This code example is based on the approach decribed in the Cxense Content Integration Guide.

image2013-3-6 9_59_50.png

Produces an widget with output like this:

Tag (in a test page):

recs_and_ads_mockup.html


<!DOCTYPE HTML>
<html>
<head>
    <title>Recs &amp: Ads widget test page</title>
    <meta charset='utf-8'>
</head>
<body>
    <!-- Recs&ads widget start -->
        <!-- Recs will be inserted just before this div -->
        <div id="recsAndAdsTarget" style="display:none"></div>
        <script type="text/javascript">
            var cX = cX || {}; cX.callQueue = cX.callQueue || [];
            cX.callQueue.push(['insertWidget',{
                width: 305, height: 840, // The initial size
                resizeToContentSize: true, // Allow size override from template, in pixels
                insertBeforeElementId: 'recsAndAdsTarget',
                renderTemplateUrl: 'recs_and_ads_template.html'
            }]);
        </script>
        <!-- Async load cx.js -->
        <script type="text/javascript">
            (function() { try { var scriptEl = document.createElement('script'); scriptEl.type = 'text/javascript'; scriptEl.async = 'async';
            scriptEl.src = ('https:' == document.location.protocol) ? 'https://scdn.cxense.com/cx.js' : 'http://cdn.cxense.com/cx.js';
            var targetEl = document.getElementsByTagName('script')[0]; targetEl.parentNode.insertBefore(scriptEl, targetEl); } catch (e) {};} ());
        </script>
    <!-- Recs&ads widget end -->
</body>
</html>
 

 

The content-widget configuration needs at least this resultFields to make the following example work:

"resultFields": [
  "url",
  "title",             <== or "og-title"
  "description",
  "dominantthumbnail", <== or "thumbnail"
  "dominantthumbnaildimensions",
   
],

 Render template ('recs_and_ads_template.html'):

(replace all "TODOs" with your own identifiers)

The main flow of this code:

  1. Setup and send the insertAdSpace() request to Cxense Advertising 

  2. Setup and send the insertWidget() request to Cxense Content (in parallel)

  3. Wait for both requests to have completed

  4. Render the content by iterating over the two result sets, selecting an ad at the positions 4, 7 and 10.

 recs_and_ads_template.html


<!DOCTYPE HTML>
<html>
<head>
    <meta charset="utf-8">
    <title>Recs &amp; Ads widget</title>
    <style type="text/css" media="all">
        html, body, div { margin: 0; padding: 0; border: 0; outline: 0; }
        .recsHeading { margin-bottom: 6px; height: 20px; padding-top: 5px; padding-bottom: 3px; padding-left:5px; color: #000000;
                       text-transform: uppercase;
                       background-image: url("http://cdn.cxense.com/images/bg.jpg"); background-repeat: repeat-x;
                       font-family:'PT Sans Narrow','Tahoma','Arial',sans-serif;font-size:18px;font-weight:700;
        }
        .recsItem { min-height: 91px; border-bottom: 1px solid #f0f0f0; border-bottom: 1px solid #cacaca; padding-top:10px;}
        .recsText { margin-left: 90px;  font-size: 12px; line-height: 14px; font-weight: normal; font-family: 'Arial',sans-serif;margin-bottom:10px;}
        .recsImageContainer { float: left; margin: 0px 5px 5px 0px; background-color: #f0f0f0; /* border: 1px solid #ccc; */ }
        .recsTitle { color: #333333; font-size: 16px; line-height: 16px; font-weight: bold; font-family: 'PT Sans','Arial',sans-serif; text-decoration: none; }
        .recsTeaser { font-size: 12px; line-height: 15px; font-weight: normal; font-family: 'Arial',sans-serif; color: #000; }
        .poweredBy { text-align: right; }
        .poweredBy a { font-size: 9px; font-family: Arial,Verdana,Tahoma,sans-serif; color: #777777; }
        a, a:visited, a:active { text-decoration: none; }
        a:hover { text-decoration:underline; color:#5F6F7F; }
        .ad { background-color: #f0f0f0; }
        .recs { background-color: White; }
        .advertisement { font-size: 8px; text-transform: uppercase; color: #b0b0b0; text-align: right; margin: 2px; font-family: 'Arial',sans-serif; }
        .recsText br{line-height:20px}
        .template { display: none; }
     </style>
</head>
<body>
<div id="widgetElement">
    <!-- Include our cx.js script -->
    <script type="text/javascript" src="http://cdn.cxense.com/cx.js"></script>
    <div class="recsHeading">Also of interest:</div>
    <div id="targetElement">
        <!-- Template (invisible) -->
        <div id="resultsTemplate" class="template">
            <!--%
            var adIdx = 0;
            var recsIdx = 0;
            for (var i = 0; i < 12; i++) {
                var itemClass = 'recsItem recs';
                var imageSrc = '';
                var imageDimensions = '0x0';
                var title = '';
                var teaser = '';
                var isAd = false;
                if (i === 4 || i === 7 || i=== 10) {
                     
                    //
                    // Ads at these positions
                    //
                    
                    isAd = true;
                    itemClass = 'recsItem ad';
 
       
                    if (adData.searchResult.spaces && adIdx >= adData.searchResult.spaces[0].ads.length) {
                        continue;
                    }
               
                    var ads = adData.searchResult.spaces[0].ads;
                    var ad = ads[adIdx];
                    if (ad && ad.creative && ad.creative.content && ad.creative.content.length && ad.creative.images && ad.creative.images.length) {
                        var displayUrlText = '';
                        var trackingImageSrc = null;
                        var content = ad.creative.content;
                        for (var j = 0; j < content.length; j++) {
                            var contentItem = content[j];
                            if (contentItem.key.toLowerCase() === 'title')
                                title = cX.library.getAllText(contentItem.value);
                            if (contentItem.key.toLowerCase() === 'content')
                                teaser = cX.library.getAllText(contentItem.value);
                            if (contentItem.key.toLowerCase() === 'display url')
                                displayUrlText = cX.library.getAllText(contentItem.value);
                        }
                        // get the image
                        var images = ad.creative.images;
                        var imageSrc = '';
                        for (var k = 0; k < images.length; k++) {
                           var image = images[k];
                            if (image.key.toLowerCase() === 'rectangle image') {
                                imageSrc = image.source;
                                imageDimensions = '' + image.width + 'x' + image.height;
                            }   
                        }
                        var clickUrl = ad.clickUrl;
                    }
                    adIdx ++;
                } else {
                    //
                    // Recs at all other positions               
                    //
 
                    if (recsIdx >= recsData.response.items.length) {
                        continue;
                    }
                    var item = recsData.response.items[recsIdx];
                    //
                    // populate the fields for rendering
                    //
                    var teaser = item.description || '';
                    if (teaser.length > 100) {
                        teaser = teaser.substr(0,98) + '...';
                    }
                    var title = item['og-title'] || item.title || "No title";
                     
                    // try first the dominantthumbnail and fallback to thumbnail
                    if (item.dominantthumbnaildimensions && item.dominantthumbnail) {
                        imageSrc = item.dominantthumbnail;
                        imageDimensions = item.dominantthumbnaildimensions;
                    } else {
                        imageSrc = item.thumbnail;
                        imageDimensions = '200x150';
                    }
 
                    var displayUrlText = item.url;
                    var clickUrl = item.click_url;
                    // check next one
                    recsIdx ++;
                }
                if (title && title.length > 0) {
                %-->
                <div tmp:class="{{itemClass}}">
                    <!--% if (imageSrc) { %-->
                    <a target="_top" tmp:id="{{cX.clickTracker(clickUrl)}}" tmp:href="{{displayUrlText}}"> 
                    <!--%
                    // Cxense image scaling support will generate the img-tag within a div container
                    cX.renderContainedImage({
                        container: { width: 80, height: 60, cssClass: 'recsImageContainer' },
                        image: { src: imageSrc, dimensions: imageDimensions }
                    });
                    %-->
                    </a>
                    <!--% } %-->
                    <div class="recsText">
                        <a target="_top" class="recsTitle" tmp:href="{{cX.clickTracker(clickUrl)}}">{{title}}</a><br />
                        <a target="_top" class="recsTeaser" tmp:id="{{cX.clickTracker(clickUrl)}}" tmp:href="{{displayUrlText}}">{{teaser}}</a><br />
 
                         <!--% if (isAd) { %-->
                        <div class="advertisement">Advertisement</div>
                        <!--% } %-->
                    </div>
                </div>
                <!--%
                }
            } %-->
        </div>
    </div>
    <div class="poweredBy"><a href="http://www.cxense.com/" target="_blank">Powered by cXense</a></div>
    <!-- Callback to parent window to resize this IFRAME now that ad content metrics are known: -->
    <script type="text/javascript">
        function sendParentResizeMessage(context) {
            if (context && context.parentElementId) {
                if (parent && parent.postMessage) {
                    // Measure content width and height
                    var contentWidth = 305;
                    var contentHeight = 840;
                    try {
                        var adsDivEl = document.getElementById('widgetElement');
                        contentWidth = adsDivEl.clientWidth ? adsDivEl.clientWidth : adsDivEl.offsetWidth;
                        contentHeight = adsDivEl.clientHeight ? adsDivEl.clientHeight : adsDivEl.offsetHeight;
                    } catch (e) {
                        contentWidth = 305;
                        contentHeight = 840;
                    }
                    // Add the search result count
                    var parentElementId = context.parentElementId;
                    var message = 'method=updateAdSpace'
                        + '&contentWidth=' + contentWidth
                        + '&contentHeight=' + contentHeight
                        + '&elementId=' + encodeURIComponent(parentElementId);
                    // It's safe to use '*' as targetOrigin here, because the message content is not secret.
                    window.parent.postMessage(message, '*');
                }
            }
        }
 
        var myHashArguments = cX.library.parseHashArgs();
        var adData;
        var recsData;
        function renderWidget() {
            cX.library.renderTemplate('resultsTemplate', 'targetElement');
            var context = myHashArguments;
            sendParentResizeMessage(myHashArguments );
        }
 
        //
        // Ads renderFunction
        //
        function onGotAdData(data, context) {
            adData = data;
            if (typeof recsData !== 'undefined') {
                renderWidget();
            }
        }
 
        //
        // Recs renderFunction
        //
        function onGotRecsData(data) {
            recsData = data;
            if (typeof adData !== 'undefined') {
                renderWidget();
            }
        }
 
        // Ads setup
        cX.library.insertAdSpace({
            adSpaceId: '1234', // TODO: <-- Insert your Cxense Advertising adspace id here!
            backend:'production',
            forwardHashArgs: true,
            renderFunction: onGotAdData
        });
 
        //
        // Recs setup based on new API style, skipped all the legacy request setup
        // this is a direct call
        //
        cX.library.insertWidget({
            widgetId: '4567',  // TODO: <-- Insert your Cxense Content widget id here!
            renderFunction: onGotRecsData
        });
    </script>
</div>
</body>
</html>


Last updated: