How to build a web widget (using jQuery)

Introduction

I recently created some web widgets for the London’s Design Museum and learnt 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 creating web widgets with jQuery so I decided to write this one. I’ll cover only techniques which are specific to web widget development so you should already be familiar with JavaScript, jQuery and web development if you want to follow easily.

The interesting points will be:

  • how to ensure the widget’s code doesn’t accidentely mess up with the rest of the page,
  • how to dynamically load external JavaScript files (the jQuery library in this case) and CSS,
  • how to 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 builind 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 include on their own webpages. It can be used for people to display data coming from your website on their own website. A widget typically contains a slightly complex mixture of HTML, CSS, 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 developped 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 in her page. 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 wigdet 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 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 encounters 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 give a feeling of slowlyness,
  • 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 your widget, we need a way to ensure that your widget’s code doesn’t clash with the host page’s code. To do that we just enclose all our code into 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 that shows how it works:

var foo = "Hello World!";
document.write("<p>Before our anomymous function foo means '" + foo + '".</p>');

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

document.write("<p>After our anomymous function foo means '" + foo + '".</p>');

You can view the result in your browser. As you can see the foo variable defined in the 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 and we want to minimize the code required on the host page to include the widget, we’re going to load jQuery dynamically using JavaScript:

/******** Load jQuery if not present *********/
if (typeof jQuery === "undefined") {
    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");
    script_tag.onload = main; // Run main() once jQuery has loaded
    script_tag.onreadystatechange = function () { // Same thing but for IE
        if (this.readyState == 'complete' || this.readyState == 'loaded') {
            main();
        }
    }
    document.getElementsByTagName("head")[0].appendChild(script_tag);
} else {
    main();
}

function main() { // Called once jQuery is loaded
    jQuery.noConflict();
    jQuery(document).ready(function($) { 
      // .... use jQuery here
    });
    // We can still use Prototype here
}

We first test for the presence of the jQuery object so we don’t load the library again if by chance it’s already loaded on the host page. To load the library, we simply create a script element which points to the URL of the library file. The rest of our code is going to make use of the library so we need to wait for it to be loaded completely before we execute our code. We do that by defining a main function that will contain our widget’s code. 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, 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.

Why test for two states in Internet Explorer’s 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 main function we also make sure that jQuery can be used with other libraries which may have been loaded by the host page’s author.

Here is a demo page for this technique.

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

Loading data from your site

Since your widget is going to display data on the host page as HTML, there is usually no need to use an intermediate format such as XML or JSON to get data from your server, you can directly get HTML and update the content of the widget container defined by the host page with that HTML.

Let’s say our widget is going to display a list of headlines. The server can output a chunck of HTML which look like this:

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

If the host page was located on the same server 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 different and browsers will simply not allow an AJAX request to be made to a different server 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 use the script tag to get JSON data wrapped into a function call. On the host page, we define the corresponding function which get the JSON data as a parameter, and do whatever it needs to do with it. Here the only data we’re interested is the piece of HTML we want to insert in the widget container so the JSON object sent by the server will look like:

{"html": "<ul><li>(...)</li></ul>" }

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. On the server side, we will need to wrap our HTML within a JSONP script. Here is how I’ve implemented it 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 and send the
# result to the browser
render :text => callback + "(" + json + ")",  :content_type => "text/javascript"

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

$.getJSON(widget_url, function(data) {
  $('#example-widget-container').html(data.html);
});

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 want to add a bit of styling.

Loading CSS

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

$("head").append("<link>");
var css = $("head").children(":last");
css.attr({
  rel:  "stylesheet",
  type: "text/css",
  href: "http://example.com/css/style.css"
});

Putting everything together