Pages

Friday, August 30, 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.

Alpine Select - Stock exchange compliance with Drupal

Alpine Select, one of our latest releases, was built for the eponymous and publicly listed investment company in co-operation with Christoph "Laser" Jaggi. He chose Amazee Labs as development partner and signed himself responsible for the requirement engineering and project management. 
The Drupal 7 combo might look "modest / conservative / business / discreet" at first but do not make the mistake to judge the book by its cover. The system is fully stock exchange compliant and takes the reporting duties to the edge with Twitter as an integral reporting channel. Read more about IT and SIX Swiss Exchange compliance in today's article on Inside IT. The new web presence consists of the following major requirements:
  • Display of the company profile
  • Automatic provision of the reporting infos, required by law, to the Swiss Stock Exchange Authorities. (e.g. Ad hoc release in compliance with SIX)
  • Generation of printable Monthly- and Quarterly Reports.
  • Provision of financial data to current and potential professionalinvestors. (e.g. Performance charts)
Besides these features a full content migration, from a custom developed solution, had to be done in order to maintain the gap-less documentation for investors.
 
 
Despite serving a new industry with different requirements, as any other site we previously built, the toolset required from a Drupal point of view remained rather similar to our other releases. RulesViewsWorkbench,Simplenews and some use case specific custom code stand at the heart of this Drupal solution. As it is our practice we will contribute some of the custom code, in a more general use case, to the Drupal community.
 
For an in-depth view on SIX Swiss Exchange compliance using a Drupal application, refer to Christoph Jaggis excellent paper (German).
 
We wish Alpine Select and its shareholders a successful future.

Drupal behaviors: A quick how to

If you’re adding JavaScript to your custom module it is very easy and tempting to simply add it like this:
jQuery(document).ready(function($){ alert(‘hot dog flavored water’); }); 
Now this code works perfectly fine but what if your JavaScript needs to be executed on page load and after an AJAX request? Imagine you have a view that uses “Views Infinite Scroll” and you want add a CSS class to every result like this:
jQuery(document).ready(function($){ $('.view-display-id-page .views-row').addClass('fancy-pants'); }); 
This will work for the results that are displayed initially but for all the results that are loaded by Infinite Scroll's AJAX call the class is not added. That’s where Drupal behaviors come in handy.  The behaviors will be executed on every request including AJAX requests, so let's do the equivalent of the code above but this time using this method:
Drupal.behaviors.infiniteScrollAddClass = { attach: function (context, settings) { $('.view-display-id-page .views-row').addClass('fancy-pants'); } }; 
I admit that was quick - so here are some explanations:
  • infiniteScrollAddClass: This is your namespace and should be unique. For example, this is typically the name of your module, but it isn't mandatory.
  • context: This is actually really really cool, on page load the context will contain the entire document and after an AJAX request will have all the newly loaded elements. This way you can treat content that is loaded in via AJAX differently than others.
  • settings: This contains information passed on to JavaScript via PHP, it is similar to accessing it via Drupal.settings. For further comprehension I recommend this source.
There obviously are cases where some functionality should not be executed on every request. In such a case its great to use jQuery's .once() method. So let's say we want to give all the initially loaded results in our view an additional class, for something like this we would proceed like so:
Drupal.behaviors.infiniteScrollAddClass = { attach: function (context, settings) { // these are the elements loaded in first $('.view-display-id-page').once(function(){ $(this).find('.views-row').addClass('i-was-here-first'); });  // everybody $('.view-display-id-page .views-row').addClass('fancy-pants'); } }; 
This will add the class “i-was-here-first” to all the view results present on page load, everybody else joining in via AJAX will just get the “fancy-pants” class.
So that’s a quick look at Drupal behaviors, if you haven’t used it do use it!
If you are looking for additional theoretical insight into this topic I can recommend these two sources for further reading:

Drupal 7 modules: The bare necessities

A while ago we wrote about the Drupal 6 modules you really need. Since that post a few things have changed, on one hand Drupal 7 has moved a bunch of those modules to its core, while on the other hand some were replaced by better alternatives.
Of course every Drupal site we build has different requirements, which makes our choice of contributed modules unique for every project. Nevertheless here is our current selection of trusted Drupal modules we don't want to miss. The bare necessities, if you will.
Internationalization & Entity translation
Working in a country, that has four officially recognized languages and English being the Internet's lingua franca, we install these modules without thinking anymore. Multilingual solutions have come on leaps and bounds since previous Drupal releases and despite not being perfect yet they make our lives much easier.
Pathauto
Regardless of how much attention you want to give to search engine optimization in a project - getting the basics right is essential. Pathauto enables you to create URL patterns for all entity types. The Pareto principle will thank you.
Views
This is by far the most powerful module in the Drupal ecosystem. Built on the Chaos tool suite (ctools), it gives the full control to display any kind of entity in the manner you want it. No wonder it will be part Drupal 8's core.
Devel
If you are a developer this one can help you out of the odd pickle. It allows you to gather information on what is happening in Drupal's background. Just remember to disable it on productive environments.
Wysiwyg & CKEditor
Until the day arrives where everybody is a HTML wizard, WYSIWYG editors will remain a feature, which just belong to every Drupal installation.
Google Analytics
The quickest and easiest way to make your websites visits quantifiable. You can only manage what you can measure.
Panels
Need a complex layout on the quick? Panels will be your trusted friend. You can define layout templates and define in which contexts they should appear.
Display Suite / Panelizer
Depending on the level of complexity of our brief we will opt for one of these two masters of data display control. Although both have their pros and cons we tend to use Panelizer more often, since it's benefits can be leveraged on large and complex websites.
Rules
An amazing module, built upon the Entity API, which allows you to define workflows and actions while you barely have to write any code. It integrates into many modules witch makes it a powerful and useful resource.
Webforms
You can argue that allowing visitors to create nodes with appropriate field permissions can act a contact forms - we have seen this approach in the wild... In our opinion using the Webforms module is the better option for the majority of use cases where a form is needed. You don't want your content overview screen cluttered with spam that got through Mollom's net, right?
What do you think? What are your bare necessities?