Jump to content

Year

Day

24 ways to impress your friends

On July 14, 1789, citizens of Paris stormed the Bastille, igniting a revolution that toppled the French monarchy. On July 14 of this year, there was a less dramatic (though more tweeted) takedown: The Deck network, which delivers advertising to some of the most popular web design and culture destinations, was down for about thirty minutes. During this period, most partner sites running ads from The Deck could not be viewed as result.

A few partners were unaffected (aside from not having an ad to display). Fortunately, Dribbble, was one of them. In this article, I’ll discuss outages like this and how to defend against them. But first, a few qualifiers: The Deck has been rock solid – this is the only downtime we’ve witnessed since joining in June. More importantly, the issues in play are applicable to any web widget you might add to your site to display third-party content.

Down and out

Your defense is only as good as its weakest link. Web pages are filled with links, some of which threaten the ability of your page to load quickly and correctly. If you want your site to work when external resources fail, you need to identify the weak links on your site. In this article, we’ll talk about web widgets as a point of failure and defensive JavaScript techniques for handling them.

Widgets 101

Imagine a widget that prints out a Pun of the Day on your site. A simple technique for both widget provider and consumer is for the provider to expose a URL:

http://widgetjonesdiary.com/punoftheday.js

which returns a JavaScript file like this:

document.write("<h2>The Pun of the Day</h2><p>Where do frogs go for beers after work? Hoppy hour!</p>");

The call to document.write() injects the string passed into the document where it is called. So to display the widget on your page, simply add an external script tag where you want it to appear:

<div class="punoftheday">
  <script src="http://widgetjonesdiary.com/punoftheday.js"></script>
  <!-- Content appears here as output of script above -->
</div>

This approach is incredibly easy for both provider and consumer. But there are implications…

document.write()… or wrong?

As in the example above, scripts may perform a document.write() to inject HTML. Page rendering halts while a script is processed so any output can be inlined into the document. Therefore, page rendering speed depends on how fast the script returns the data. If an external JavaScript widget hangs, so does the page content that follows. It was this scenario that briefly stalled partner sites of The Deck last summer.

The elegant solution

To make our web widget more robust, calls to document.write() should be avoided. This can be achieved with a technique called JSONP (AKA JSON with padding). In our example, instead of writing inline with document.write(), a JSONP script passes content to a callback function:

publishPun("<h2>Pun of the Day</h2><p>Where do frogs go for beers after work? Hoppy hour!</p>");

Then, it’s up to the widget consumer to implement a callback function responsible for displaying the content. Here’s a simple example where our callback uses jQuery to write the content into a target <div>:


View Example 1

Even if the widget content appears at the top of the page, our script can be included at the bottom so it’s non-blocking: a slow response leaves page rendering unaffected. It simply invokes the callback which, in turn, writes the widget content to its display destination.

The hack

But what to do if your provider doesn’t support JSONP? This was our case with The Deck. Returning to our example, I’m reminded of computer scientist David Wheeler’s statement, “All problems in computer science can be solved by another level of indirection… Except for the problem of too many layers of indirection.”

In our case, the indirection is to move the widget content into position after writing it to the page. This allows us to place the widget <script> tag at the bottom of the page so rendering won’t be blocked, but still display the widget in the target. The strategy:

  1. Load widget content into a hidden <div> at the bottom of the page.
  2. Move the loaded content from the hidden <div> to its display location.

and the code:


View Example 2

After the external punoftheday.js script has processed, the rendered HTML will look as follows:

<div class="loading-dock hidden">
  <script src="http://widgetjonesdiary.com/punoftheday.js"></script>
  <h2>Pun of the Day</h2>
  <p>Where do frogs go for beers after work? Hoppy hour!</p>
</div>

The ‘loading-dock’ <div> now includes the widget content, albeit hidden from view (if we’ve styled the ‘hidden’ class with display: none). There’s just one more step: move the content to its display destination. This line of jQuery (from above) does the trick:

$('.punoftheday').append($('.loading-dock').children(':gt(0)'));

This selects all child elements in the ‘loading-doc’ <div> except the first – the widget <script> tag which generated it – and moves it to the display destination. Worth noting is the :gt(0) jQuery selector extension, which allows us to exclude the first (in a 0-based array) child element – the widget <script> tag – from selection.

Since all of this happens at the bottom of the page, just before the </body> tag, no rendering has to wait on the external widget script. The only thing that fails if our widget hangs is… the widget itself. Our weakest link has been strengthened and so has our site. DE-FENSE!

Like what you read?

Comments

Comments are ordered by helpfulness, as indicated by you. Help us pick out the gems and discourage asshattery by voting on notable comments.

Got something to add? You can leave a comment below.

  • barryvan http://www.barryvan.com.au

    Another option might be to override document.write. Assuming you are in control of the ad providers, and don’t make use of document.write yourself (which you shouldn’t!), you could then put the provider’s <script> tag at the bottom of your document, and use your own document.write method to deal with the content as appropriate.

    The only caveat with this approach is that when setting innerHTML (as you will ultimately need to), <script> tags aren’t processed, so these would need to be manually found and added. This is the approach that Steve Souders takes in his ControlJS library, and, to my mind at least, it seems to be a pretty good idea.

    There are, of course, other approaches that you could take. Iframes purely for the provider’s script, which can then be positioned or scraped, could work as well. Of course, the best possible solution would be to use a provider that supports JSONP. :)

    Vote Helpful or Unhelpful

  • TVD http://techoctave.com/c7

    Great article Rich! I’m a huge fan of your work on Dribble.

    Quite often document.write() gets a bad rap. But, to be fair, the issue isn’t using document.write() to write content to the DOM.

    The overarching issue is should we use document.write() to load cross-domain scripts (or any scripts for that matter)? I believe we can all agree the answer is no.

    Using a Callback approach is novel indeed, but what I really like about it is its unobtrusive foundation and the control it places in the hands of the developer.

    Today, widgets like The Deck answer the “what”, “where”, “when” and “how” of injected content. Using a predefined callback puts the “where” and “when” concerns back in the hands of the developer – where they belong!

    With a 50/50 split, and the right concerns in the right hands, surely this is a win/win for both parties.

    How would a Callback approach compare to loading The Deck’s script asynchronously?

    Asynchronous Loading Example:

    (function(id) {

    var s = document.createElement(‘SCRIPT’), s1 = document.getElementsByTagName(‘SCRIPT’) [0];

    s.type = ‘text/javascript’;

    s.async = true;

    s.src = ‘http://widgetjonesdiary.com/punoftheday.js?id=’ + id;

    s1.parentNode.insertBefore(s, s1);
    })(“24ways”);

    Love the approach! Take Care!

    Vote Helpful or Unhelpful

  • Damien Petitjean http://www.comparemandataire.fr

    This is something really useful, thanks for the post. I would like to add some information :

    This can be combined with asynchronous loading of javascript, where you can earn some seconds during loading. That’s what I use to display share buttons such as Facebook or Google+.

    Let’s take a look at the code

    // Google+ Tag
    googleTag.type = ‘text/javascript’;
    googleTag.async = true;
    googleTag.src = ‘http://apis.google.com/js/plusone.js’;

    var s = document.getElementsByTagName(‘script’)0;
    s.parentNode.insertBefore(googleTag, s);

    Have some fun !

    Vote Helpful or Unhelpful

  • Alan White http://azcazandco.com

    I spent a lot of time recently looking into all this stuff and I did try the approach in this article, where I found it fell over was when scripts are writing iframes which are in in turn writing more scripts… rinse & repeat, pretty messy!

    In this scenario, I found it extremely difficult to know when ads had actually finished doing whatever they were doing so that you could copy them in their entirety, it gets even worse when ads refresh :(

    Anyway, I just wanted to add some of my experiences to the conversation. Do you have an answer to this problem?

    Vote Helpful or Unhelpful

  • Thomas http://wellisolutions.de

    Maybe we can solve one of such issues, but what if I want to integrate more 3rd party JavaScripts using document.write()?
    If it is the first one hanging, all other will not be executed as well.

    Vote Helpful or Unhelpful

  • Andy Davies http://andysnotebook.com

    I’d encourage everyone to go read yesterday’s post on the Performance Advent Calendar – http://calendar.perfplanet.com/2011/the-art-and-craft-of-the-async-snippet/

    Stoyan covers how to write async loading javascript for third party widgets and so avoid the single point of failure they can introduce.

    @andydavies

    Vote Helpful or Unhelpful

  • Nicolas Chevallier http://www.nicolas-chevallier.fr

    More info about that on Steve’s blog, for example :
    http://www.stevesouders.com/blog/2010/12/15/controljs-part-1/

    It’s even more important now to load javascript asynchronously because of performance and because Google will take care of website speed in the future.

    Vote Helpful or Unhelpful

  • Mark Wales http://smallhadroncollider.com

    Firstly, you should never use document.write() – it has the potential to delete the entire DOM if used in the wrong place. That’s not something you want to do! There are always better alternatives, like using innerHTML (supported in all browsers, and much safer).

    Maybe I’m missing something, but this code seems unnecessarily complicated – and relies on jQuery, which you shouldn’t force developers to put on their site (don’t get me wrong, I’m a big fan of jQuery, but if you’re only using it for a few lines of DOM manipulation then 31KB is a big price to pay).

    Could you not do something as simple as:

    HTML
    <div id=“pun-of-the-day”></div>

    <!— bottom of page —>
    <script src=“http://adnetworksite.ads/puns.js”></script>

    AD JAVASCRIPT

    // Self-executing function (to keep variables out of global scope

    (function () {

    var div = document.getElementById(‘pun-of-the-day’); div.innerHTML = ‘A good pun is its own reword’;

    }());

    ======

    Obviously the ad javascript would be more clever than that and have multiple puns in and such. But it works. And it involves less code for both ends.

    As long as the script is called at the bottom of the page (which it should be anyway) this will work. No jQuery needed, no complicated DOM manipulations.

    Vote Helpful or Unhelpful

  • Russell Heimlich http://www.russellheimlich.com/blog/

    I appreciated the domain of the widget provider being widgetjonesdiary.com. It made me chuckle.

    Vote Helpful or Unhelpful

Impress us

Be friendly / use Textile

About the author

Rich Thornett

Rich Thornett wanted to play pro basketball when he grew up, but found himself trapped in the body of a software developer. After working on his game for over a decade at software shops large and small, he created Dribbble, a show and tell site for designers. What was once a side project is now a small company where he serves as lead developer and product designer.

He lives in Salem, MA with his witty, wonderful wife and two kids, who are lost in bonkers. If you believe the pun is mightier than the sword, you can follow him on Twitter.

More information

Brought to you by:

Perch - a really little cms

The easiest way to publish fast, flexible HTML5 websites your clients will love.