Pages

Thursday, August 29, 2013

How To: Loading ads in a Drupal AJAX call asynchronously

While working on LikeMag, one of our most recent releases, we were confronted with a challenge: loading ads during a Drupal AJAX (Asynchronous JavaScript and XML) call. This is how we solved it.

The issue

By definition synchronously loaded JavaScript can only add objects like files, content or ads to a page's structure until it is entirely rendered. An additional complexity that can occur while working with third-party advertising providers is that you can not control the performance of their infrastructure. Which means either you accept this risk or try finding a way to mitigate it. We went for the latter.
At this point it is important to stress that it is common practice for third party ads to use the document.write method which only works for the synchronous loading approach. To learn more about the synchronous and asynchronous loading of remote tags I suggest you read the article "synchronous vs. asynchronous tags - what’s the big deal?" on the krux blog. In our case however it became apparent that our solution had to include the asynchronous approach.
While researching possible solutions I found the following tools:
The issue with the the majority of them were, that active development or support wasn't visible, except for PostScribe. This solution offers the following features:
"Krux PostScribe enables the type of tag execution required by most ad formats in an easy-to-use-and-deploy format. It leverages innerHTML to ensure reliable, fast ad delivery."
"Unlike other innerHTML-based solutions (e.g., writeCapture, ControlJS, and OpenTag), Krux PostScribe seamlessly enables the “immediate writes” upon which most ad formats depend."
Which in plain English enables the quick rendering of the page to the user and allowing the ads to respond in their own time without reducing the user's experience.

How to use PostScribe

Download PostScribe directly from its github repository.
Include these files in your project:
/htmlParser/htmlParser.js /postscribe.js
Here is a small example:
<div id="myAd"></div> <script type="text/javascript"> $(function() { postscribe('#myAd', '<script src="remote.js"><\script>'); }); </script>

Our use case

As mentioned in the introduction this solution stems from LikeMag which, besides utilizing PostScribe, is a responsive website and uses the Isotope library to arrange items over the screen automatically depending on the viewport.
Since there are no free lunches on the web, LikeMag has to monetize too and one of their models is built around ads. So in order to deliver the perfect balance between content and ads for every viewport, we had to find a way to render the perfect amount after the identification. And that is moment the where PostScribe comes into play.
Instead of injecting the ads right into the Drupal view and slowing down the loading of the rest of the page, we just print the standard items. In a next step the system iterates over the items and adds the appropriate amount of ad containers to the content with jQuery. (These containers can be addressed with PostScribe, because PostScribe works best when the DOM is ready.)
LikeMag.com Ad
"But where is the AJAX you mentioned in the title?", I hear you cry. So let's talk about it right now. When we scroll down on LikeMag, which besides being responsive is a smart infinite scroll site too, new items will be loaded through an AJAX call. So here we can use PostScribe as we did before. The only thing that we have to check, is that we do not add ads to already "adified" content. In our case we can check if the view-item was already processed by Isotope.
Code from View, partially modified by isotope and postscribe
See the code as an example:
Drupal.behaviors.ViewsLoadMore = { attach: function(context, settings){ if ($(context).hasClass('view')) { $isotope = $('.view-id-articles .view-content'); $isotope.isotope('insert', $('.view-id-articles .view-content .views-row:not(.isotope-item)'), function(){ isotop_load_more_init(settings); Drupal.behaviors.advertisment.loadads() }); } else if ($(context).find("html").length == 1){ // If it contains html, it is the first behavior call. isotop_load_more_init(settings) } } } 
loadads: function(){ $('.view-articles .views-row.views-row-ad').once('postscribe',function(){ if(typeof(cachebuster) == "undefined"){var cachebuster = Math.floor(Math.random()*10000000000)} if(typeof(dcopt) == "undefined"){var dcopt = "dcopt=ist;"} else {var dcopt = ""} if(typeof(tile) == "undefined"){var tile = 1} else {tile++} var string = '<scr'+'ipt src="http://ad-emea.doubleclick.net/adj/likemag.ch/;' + dcopt + ';tile=' + tile + ';sz=300x250;ord=' + cachebuster + '?"></scr'+'ipt>'; postscribe($(this), string); }); } 
As you can see we process only the newly loaded items from the Drupal view and exclude the already processed items.
With this solution we are very flexible to deliver the page content as fast as we can while not depending on the speed of an ad-provider.

No comments:

Post a Comment

Thanks for your comment.