How to build a web widget (using jQuery)

Published on 15 June 2010, updated on 23 May 2012, Comments

Introduction

I created some web widgets for the London’s Design Museum and learned a few useful things in the process. Although all the necessary information can be found on the web, I couldn’t find a really comprehensive guide about doing this with jQuery so I decided to write this one. I’ll cover only techniques which are specific to web widgets so you should already be familiar with JavaScript, jQuery and web development if you want to follow easily.

The interesting points will be:

  • ensure the widget’s code doesn’t accidentally mess up with the rest of the page,
  • dynamically load external CSS and JavaScript files,
  • bypass browsers’ single-origin policy using JSONP.

Don’t worry if all this doesn’t make sense to you yet, we’ll see in details what these techniques are used for in the context of building web widgets.

What is a web widget?

A web widget is a component, a “chunk of web page” that you provide for other people to display data coming from your site on their own pages. A widget typically contains a slightly complex mixture of HTML, CSS and JavaScript. You will want to hide that complexity and make it as easy as possible for people to include your widget on their pages.

Widget usage

The widget developed in this tutorial can be embedded in a web page with just two HTML tags: one script tag to load the widget and one container tag (usually a div) to indicate where we want to put the widget on the page:

<script src="http://example.com/widget/script.js" type="text/javascript"></script>
<div id="example-widget-container"></div>

That’s really all that a web site owner would need to include our widget on her pages. The code referenced with the script tag will take care of downloading the different assets composing the widget and update the content of the container.

Can’t it be even simpler?

It is technically possible to create a widget that doesn’t require a destination container by using document.write() within the widget’s code. Although some widget providers do use that approach, and it can reduce the code necessary on the host page to just one script tag, we believe it’s not worth it because:

  • document.write() cannot be called once a page has loaded. This means our widget’s code would have to run immediately when the browser finds the script tag. Since we want this code to fetch data from our server, that could cause page loading to be interrupted for a while and it would make the page feel slow,
  • by using a separate tag which will contain the widget on the page, we are free to place our script tag anywhere. We can for instance put it at the bottom of the page, which is a recommended practice.

Code isolation

Because you can’t predict what JavaScript code will be running on the page which uses our widget, we need a way to ensure that it doesn’t clash with any other JavaScript code included on the host page. To do that, we just enclose all our code within an anonymous function and we call that function. The variables we create in our functions won’t interfere with the rest of the page.

Here is a simple example using this technique:

var foo = "Hello World!";
document.write("Before our anonymous function foo means '" + foo + '".');

(function() {
    // The following code will be enclosed within an anonymous function
    var foo = "Goodbye World!";
    document.write("Inside our anonymous function foo means '" + foo + '".');
})(); // We call our anonymous function immediately

document.write("After our anonymous function foo means '" + foo + '".');

You can view the result in your browser. As you can see the foo variable defined in an anonymous function doesn’t clash with the global foo variable.

Always declare variables with var

In order to avoid tampering with JavaScript variables defined outside of our widget, we must declare all our variables with the var keyword, otherwise we might be updating existing variables instead of creating our own. It’s anyway a good practice to use var every time you create a variable.

Loading JavaScript libraries

If your widget will use a fair amount of JavaScript, you’ll likely want to use a JavaScript framework such as jQuery. Since we can’t rely on having jQuery loaded on the host page, we’re going to load jQuery dynamically using JavaScript:

(function() {

// Localize jQuery variable
var jQuery;

/******** Load jQuery if not present *********/
if (window.jQuery === undefined || window.jQuery.fn.jquery !== '1.4.2') {
    var script_tag = document.createElement('script');
    script_tag.setAttribute("type","text/javascript");
    script_tag.setAttribute("src",
        "http://ajax.googleapis.com/ajax/libs/jquery/1.4.2/jquery.min.js");
    if (script_tag.readyState) {
      script_tag.onreadystatechange = function () { // For old versions of IE
          if (this.readyState == 'complete' || this.readyState == 'loaded') {
              scriptLoadHandler();
          }
      };
    } else { // Other browsers
      script_tag.onload = scriptLoadHandler;
    }
    // Try to find the head, otherwise default to the documentElement
    (document.getElementsByTagName("head")[0] || document.documentElement).appendChild(script_tag);
} else {
    // The jQuery version on the window is the one we want to use
    jQuery = window.jQuery;
    main();
}

/******** Called once jQuery has loaded ******/
function scriptLoadHandler() {
    // Restore $ and window.jQuery to their previous values and store the
    // new jQuery in our local jQuery variable
    jQuery = window.jQuery.noConflict(true);
    // Call our main function
    main(); 
}

/******** Our main function ********/
function main() { 
    jQuery(document).ready(function($) { 
        // We can use jQuery 1.4.2 here
    });
}

})(); // We call our anonymous function immediately

First we create a local variable which will hold our version of jQuery. Then we check for the presence of the version of jQuery we need, so that we don’t download it again if by chance it’s already there on the host page. Here we’re being strict about the version number, but if you’ve tested your code with several versions, you can allow these versions as well, or even use a regular expression to match a major version number. To load the library, we then simply create a script element which points to the URL of the library file. In case we’re dealing with a poorly written page which doesn’t have a head, we simply append our script tag to the document element.

Of course, we want to use the library in our code, but before we do so, we must be sure that the library has loaded completely. We do that by defining a scriptLoadHandler function which will be called once the library has loaded. For most browsers (Firefox, Safari, Opera, Chrome, etc.) we just need to assign that function to the onload attribute of the script element. Internet Explorer before version 9, as often, has its own way. Here it requires us to register an onreadystatechange handler which will call our main function when the ready state is either complete or loaded. As people helpfully pointed out in the comments, IE9 and Opera support onload, but also still support onreadystatechange, so we make sure that we use only one of them by testing the presence of a readyState attribute on the script tag, in order to avoid calling our load handler twice.

Why test for two states in Internet Explorer’s old onreadystatechange handler?

Once the script is ready to use, Internet Explorer will set readyState to either complete or loaded, depending if it’s loaded from the cache or downloaded from the network. For instance, if you test only for loaded, the main function won’t be called if you navigate away from the page by clicking a link and then come back to it using the back button of your browser.

In our scriptLoadHandler function we make sure that jQuery can be used with other libraries and also with other versions of itself. The following line deserves particular attention:

jQuery = window.jQuery.noConflict(true);

Before this line gets executed, window.jQuery points to our own version of jQuery which would have overwritten any older version of the library. Calling window.jQuery.noConflict(true) restores window.jQuery to its old value and returns a reference to our freshly loaded version, which we store in our local variable. It also restores the $ variable to its old value, so that we can still use another library such as Prototype.

Finally, scriptLoadHandler calls our main function in which we can use our version of jQuery and build our widget.

Here are two examples of this technique. In both examples Prototype has been loaded. The only difference between the two is that here another version of jQuery has been loaded whereas here the same version of jQuery has been loaded.

Now that we’ve dealt with the underlying plumbing, we can finally get to the interesting part.

Loading data from our site

Since our widget is going to display data on the host page as HTML, there is probably no need to use an intermediate format such as XML or JSON to get data from our server. We can directly get HTML and update the content of the widget container using that HTML.

Let’s say our widget is going to display a list of headlines. The server can output a chunk of HTML such as:

<ul>
  <li><a href="http://example.com/a-first news">A first news</a></li>
  <li><a href="http://example.com/another-news">Another news</a></li>
  <li><a href="http://example.com/an-interesting-news">An interesting news</a></li>
</ul>

If the host page was located in the same domain we could just use regular AJAX (or to be pedantic in this case AHAH) to get this HTML and update the DOM. But in the context of a web widget, the host page and the server providing the widget’s data will usually be in different domains and browsers will simply not allow an AJAX request to be made to a different domain than the one which has served the page. This security restriction is know as the Same Origin Policy.

Luckily, there is a way around this: browsers allow to use script tags to include scripts from other domains. We can use this open door to get data from our server using a simple technique known as JSONP. The idea of JSONP is to create a script tag which will fetch JSON data wrapped into a function. If we create a script tag such as:

<script type="text/javascript" src="http://example.com/widget/data.js"></script>

The content of http://example.com/widget/data.js will look like this:

our_callback( {"foo": 42, "bar": 23 } )

We then define the function which will get the JSON data as a parameter:

function our_callback(json_data) {
    // do stuff with json_data
}

Now the good news is that jQuery supports JSONP natively and will take care for us of creating a script tag, creating a callback function and pass the name of that callback to the server as a parameter. In this case the only data we’re interested in is the piece of HTML we want to insert in the widget container. On the server side, we will use that parameter to build a piece of JSONP such as:

callback_name_generated_by_jquery( {"html": "(...)" } )

Here is an example of generating JSONP on the server using Ruby on Rails:

# Store HTML in a variable rather than returning in to the browser
html = render_to_string 
# Build a JSON object containing our HTML
json = {"html" => html}.to_json
# Get the name of the JSONP callback created by jQuery
callback = params[:callback]
# Wrap the JSON object with a call to the JSONP callback
jsonp = callback + "(" + json + ")"
# Send result to the browser
render :text => jsonp,  :content_type => "text/javascript"

In the client code, it will look like a normal AJAX call returning JSON.

var widget_url = "http://example.com/wiget_data.js?callback=?"
$.getJSON(widget_url, function(data) {
  $('#example-widget-container').html(data.html);
});

Don’t forget to include a string such as callback=? in your URL, otherwise the server won’t receive the name of the callback as a parameter.

We are now able to retrieve HTML from our server and insert it in the widget container on the host page. We’re pretty much done, we might just want to add a bit of styling.

Loading CSS

To load our stylesheets, we just create a link tag using JavaScript:

var css_link = $("", { 
    rel: "stylesheet", 
    type: "text/css", 
    href: "style.css" 
});
css_link.appendTo('head');

Putting things together

Now that we’ve seen the different bits we need to build a web widget, we can put everything together and build a simple example. Here is the JavaScript code:

(function() {

// Localize jQuery variable
var jQuery;

/******** Load jQuery if not present *********/
if (window.jQuery === undefined || window.jQuery.fn.jquery !== '1.4.2') {
    var script_tag = document.createElement('script');
    script_tag.setAttribute("type","text/javascript");
    script_tag.setAttribute("src",
        "http://ajax.googleapis.com/ajax/libs/jquery/1.4.2/jquery.min.js");
    if (script_tag.readyState) {
      script_tag.onreadystatechange = function () { // For old versions of IE
          if (this.readyState == 'complete' || this.readyState == 'loaded') {
              scriptLoadHandler();
          }
      };
    } else {
      script_tag.onload = scriptLoadHandler;
    }
    // Try to find the head, otherwise default to the documentElement
    (document.getElementsByTagName("head")[0] || document.documentElement).appendChild(script_tag);
} else {
    // The jQuery version on the window is the one we want to use
    jQuery = window.jQuery;
    main();
}

/******** Called once jQuery has loaded ******/
function scriptLoadHandler() {
    // Restore $ and window.jQuery to their previous values and store the
    // new jQuery in our local jQuery variable
    jQuery = window.jQuery.noConflict(true);
    // Call our main function
    main(); 
}

/******** Our main function ********/
function main() { 
    jQuery(document).ready(function($) { 
        /******* Load CSS *******/
        var css_link = $("", { 
            rel: "stylesheet", 
            type: "text/css", 
            href: "style.css" 
        });
        css_link.appendTo('head');          

        /******* Load HTML *******/
        var jsonp_url = "http://al.smeuh.org/cgi-bin/webwidget_tutorial.py?callback=?";
        $.getJSON(jsonp_url, function(data) {
          $('#example-widget-container').html("This data comes from another server: " + data.html);
        });
    });
}

})(); // We call our anonymous function immediately

And here is an example of implementation on the server, using a simple CGI script written in Python:

#!/usr/bin/python

import cgi

params = cgi.FieldStorage()

print "Content-Type: text/javascript\n"

if not 'callback' in params:
    print "ERROR: you must pass a callback parameter"
else:
    jsonp = "%s ( {'html': 'Hello World!' } )"
    print jsonp % params['callback'].value

Of course, real world widgets will be much more complex, both on the client and on the server, but hopefully you now have a better understanding of the basic steps involved.

Dealing with errors on host pages

Although the code above will work in a lot of cases, Shyam Subramanyan from Listly has reported an issue with using jQuery(document).ready in the main function. The problem can happen when the host page already has loaded jQuery and has its own jQuery(document).ready hooks. If there are any errors in any of the host page jQuery hooks, the one inside the widget main function will never get executed.

The solution Shyam suggests is to write custom code to test if the page is ready to host the widget. Instead of waiting for the whole document to be ready, only check for the presence of DOM elements or other JavaScript objects that your widget depends on. The code he’s been successfully using for Listly looks like this:

Listly.listlyReady = function () {
  // Check for presence of required DOM elements or other JS your widget depends on
  if (Listly.jQuery.listlybox && ListlyAuth) {
    window.clearInterval(Listly._readyInterval);
    // Make stuff here
  }
};

// This our new main function
function main () {
  Listly._readyInterval = window.setInterval(Listly.listlyReady, 500);
}

According to Shyam, this type of code has been working fine for Listly and more importantly, the widget typically loads much quicker especially in cases where the host page is heavy or has errors.

Conclusion

A lot of people have asked how to load plugins, but I’m afraid I don’t have a perfect answer to this question. Personally I’ve dealt with this simply by including the minified source code of the plugin with my script, inside the anonymous function. Be careful when choosing plugins though, some of them might not have been designed with cross-domain widgets in mind and could interfere with host pages.

Another question that comes often is how can you make your widget configurable. As suggested in the comments, if you want to minimize markup you could use the query string of your widget URL and add an id to the script tag. By doing this your script would be able to locate its own script tag, parse the query string of its own URL and configure itself. A more straightforward approach could be to simply add some invisible markup to the embed code of your widget. That’s how social sharing buttons typically do it.

I hope this tutorial has been of interest. Thank you everyone for reading and special thanks to Corey Hart for his valuable help with dynamic loading of jQuery. Make sure you browse the comments as some of them contain useful advice about how to make your widget customizable, how to reduce even further the size of the embed code, etc.

blog comments powered by Disqus