Sneaky Abstractions

Subscribe to my Feed, follow me on , recommend me on Working With Rails or see my code on GitHub

How to create a Twitter widget/client with unobtrusive, degradable JavaScript and FakeJAX

Posted on June 27, 2008 18:26 Tagged with twitter, widget, javascript, active_element.

In this post I’ll go through adding a Twitter widget to a web page unobtrusively with JavaScript. The Twitter aspect of it is not really what I’m trying to show; it just makes it a bit more interesting I guess, and is something I thought of after I wrote a Twitter client to learn XUL. There are three main reasons I wanted to write this:

  • I wanted to show that using JavaScript in an unobtrusive style is far from being a chore and actually makes things a lot easier, organised and scalable,
  • to show how incredibly helpful it is to know and love JavaScript (I used to bang on it until it sort of worked, and that took much longer and was so frustrating) when developing dynamic web pages,
  • and to showcase something I’ve been working on ;)

The rest of the article and a working widget example after the jump..

There are three different parts to this widget: the “Everyone” timeline, the “Friends” timeline and the update form at the bottom. Note that when you click the “Friends” tab, you will probably be asked for your username and password. This is Twitter’s HTTP Basic authentication and is needed to get your personal timeline.

(Ok, it turns out I lied a little about the update form, which will only work when loaded from the disk because Twitter doesn’t like the HTTP_REFERER header, but the point of the article is still valid :) You can get a version to load locally from my GitHub account)

Let’s have a look at the HTML:

<div id="tabs">
  <div class="tab">
    <h2 class="field title">Everyone</h2>

    <div class="twits refresh:5 type:public">
      <p class="fallback">
        <a href="http://twitter.com/public_timeline">Public timeline</a>
      </p>
    </div>

  </div>
  <div class="tab">
    <h2 class="field title">Friends</h2>

    <div class="twits refresh:5 type:private">
      <p class="fallback">
        <a href="http://twitter.com/home">Friends timeline</a>
      </p>
    </div>

  </div>
</div>

<form action="http://twitter.com/statuses/update.xml" method="post" id="new_twitter_update" class="new_twitter_update">
  <p>
    <label for="twitter_update_status">Message</label>
    <input type="text" name="status" id="twitter_update_status" value="What are you doing?" />
    <input class="button" type="submit" value="Update" />
  </p>
</form>

Not that interesting really, but that’s the point. It’s just normal HTML that doesn’t have any idea what we’re going to do to it. Now, this widget, or widgets in general, is not a good example of unobtrusive JavaScript because they rely on outside data. Unless you fetch this data through your own server and actually put it in the HTML before it’s sent to the browser, you end up with something very sparse like what we have here – a skeleton. Ideally, the target for JavaScript enhancement already has all the data in it, and this is the case most of the time. But when that’s not true you can still have your widget degrade somewhat gracefully when JavaScript isn’t available. In this widget, that consists of a link to the Twitter site where the data can be found.

The parts we care about for this article are the elements with a class name of “twits”, and the form at the bottom. The surrounding elements make up the tabs, but let’s just ignore them for simplicity’s sake; they’re not integral to the functionality of the widget. To convert this HTML into a widget, we’re going to do 4 things:

  • First, we have to locate it in the DOM. For this we’ve added identifiers to the elements we’re interested in (id, class names)
  • Then we wrap them in JavaScript objects and keep a reference to these objects so we can add state and behaviour to them.
  • Some of this behaviour will be the action of fetching the updates from Twitter and building new elements that get inserted into the wrapped elements.
  • Lastly, we add some style to the elements (both pre-existing and dynamically inserted) to make it look more widgety

Adding behaviour

Above the HTML are two script tags:

<script src="/assets/0000/0005/tabs.js"></script>
<script src="/assets/0000/0006/twits.js"></script>

These scripts do all the heavy lifting. Obviously, the tabs.js file is for the tabs, so we’ll ignore that. The twits.js file, on the other hand, is what makes the widget interesting. If you open the file, you’ll see what are basically JavaScript classes, named Twits, PublicTwits, PrivateTwits, Twit and UpdateForm. Everything is added to the global object Blahger, which is where I put everything used on this site. There’s also a big closure around the whole thing and a helper function named E, but they can safely be disregarded.

The classes all have properties and methods, and they all inherit indirectly from the class ActiveElement which is from another script file. What ActiveElement does isn’t that important, but basically it has a lot of useful methods that we can use in our own classes. The idea here is that we create objects that wrap around DOM (HTML) elements in order to do stuff to them and enhance them. We can then have methods and state on the wrapper objects that in turn affect the element itself.

Locating the targets

If you look at some of the classes, they have an extend property with two methods in it:

//...
extend: {
  findInDocument: function(){
    return new this($('new_twitter_update'));
  },
  attach: function(form){
    Blahger.updateForm = form;
  }
},
//...

These are “special” methods in ActiveElement that get run on page load. The findInDocument method searches the DOM for an element that can be wrapped by the class, wraps it and returns it. If there’s an attach method, the wrapped object is passed to it so it can be placed somewhere where we can reach it later. If you have Firebug, you can inspect these: Blahger.updateForm and Blahger.tabs (try Blahger.tabs.first().get('title')). You’ll notice that there’s no extend property in the Twits class, that’s because the tabs take care of wrapping these and adding them to itself; try Blahger.tabs.first().twits. We could’ve done the same with Twits as well, but this just seems natural because the “twits” elements are inside the “tab” elements. The classes with a plural name are “collections” that each contain instances of “singular” classes, so a Tabs object contains several (well, two in this case) Tab objects.

We like it DRY

The Twits class is not used directly. Instead there are two classes, PublicTwits and PrivateTwits that inherit from it. This is because while they’re mostly the same, they behave a little differently from each other. These two classes wrap around the .twits elements. Let’s have a look at the methods defined in the Twits “abstract” class and see how the content is replaced with data fetched from the Twitter server..

activate: function(){
  if (!this.get('active')) {
    this.loadStatuses();
    this.setRefreshInterval();
    this.set('active', true);
  }
},

The first method is named activate. This is the method that initiates the replacement. The difference between the two subclasses is that for the public timeline, this is run when the element is first wrapped, but for private twits it waits until you select the tab because it will pop up an anthentication box. The method checks to see if the element has the “active” class, in which case it’s been activated before and nothing happens. If that’s not the case, it adds the class after calling a couple of methods to do the dirty work.

The nitty-gritty

loadStatuses: function(callbackName){
  //Create a temporary function that can be reached as the
  //callback for the Twitter response
  var callbackName = (new Date()).valueOf();
  var that = this;
  window[callbackName] = function(json){
    that.setStatuses(json);
    delete window[callbackName];
    that.set('loading', false);
  };

  this.set('loading', true);
  this.loadJSON(this.timelineURL, 'window["'+callbackName+'"]');
}

First, it calls the loadStatuses method. This is the method that fetches the updates from Twitter and puts them into the DOM. The way this is done is by adding a script tag to the document, making use of the Twitter API’s ability to call a method when the data is returned. The data gets passed to another method, setStatuses and then refreshStatuses, which builds the elements that make up the updates, wrapped as Twit objects. The collection is reloaded with this.findItems(). While waiting for the server to respond, the element has the “loading” class, which allows for visual feedback. In the widget above, this is done by setting the top border colour to yellow.

Now that the update messages have been loaded initially, it sets an interval which does this regularly (setRefreshInterval()):

setRefreshInterval: function(){
  this.clearRefreshInterval();
  //get('refresh') calls getRefreshValue which is defined below
  var interval = this.get('refresh'),
      that = this;
  if (interval) {
    this._refreshInterval = setInterval(function(){
      that.loadStatuses();
    }, interval*60000);
  }
}

The number of minutes to wait between each refresh is found by inspecting the class property on the wrapped element. Both elements have a class name “refresh:5”. The “5” part is extracted and is used as the value. This isn’t really an ideal way to deal with these things, but there aren’t that many alternatives. It’s possible to use custom HTML attributes, but that doesn’t validate, so it’s going to be less than ideal no matter what. But both methods work quite well. Note that this is only necessary for metadata; that is, data that’s not part of the content, but serve as “attributes” to be used by the script.

(A third option could be to add the refresh value as a JavaScript property in the Twits class. As it’s only going to be used within the realm of JavaScript anyway, that’s not really such a bad idea)

Making it interactive

There’s one piece missing until we can replace the word “widget” with “client”: We need to be able to send updates as well as receive. This isn’t straightforward because there are strict limitations to cross-domain requests, which is necessary to make this work. Now, this widget isn’t meant to be used for anything other than an example, and this part isn’t very important to the article, so I’ll make it quick.

The UpdateForm is a class that wraps the form used to send updates to Twitter. The class inherits a special Form class that is specialised for use with form elements. It has a method, hijack which attaches an event to the form element. When the form is submitted, an iframe is created and the form is redirected into this to make it seem “Ajaxy”. It’s in fact a giant hack and I’m not surprised if it doesn’t work in your browser.

sendUpdate: function(){
  var name = 'humbaba'+(new Date()).valueOf();//unique name for the iframe
  var iframe = E('iframe', {style:'display:none', name:name});
  this.element.insert({bottom:iframe});

  var previousTarget = this.element.readAttribute('target');
  this.element.writeAttribute('target', name);//Redirect the form to the iframe
 
  //Watch the iframe until it's loaded (request completed)
  var interval = setInterval(function(){
    var loaded = false;
    try { iframe.contentWindow.src } catch (e) { loaded = true; } //X-domain raises exception
    if (loaded) {
      clearTimeout(interval);
      iframe.remove();
      this.element.writeAttribute('target', previousTarget);
      ActiveElement.messages.fire('status update sent');
    }
  }.bind(this), 100);

  this.element.submit();
}

When the request has completed (as determined by another hack), the iframe is removed and a message is sent. It’s like an event, and the PrivateTwits object subscribes to this message. When it receives the message, it reloads itself.

Looking good

If you look at the script we’ve been going through, you’ll see that there’s nothing in there that changes the way an element looks. Doing this would be obtrusive and we’re trying to be unobtrusive. Instead, CSS classes are added and removed to the elements so they can be targeted in various states. Of course, this means you don’t get visual effects (they’re obtrusive), but I find that they’re mostly just annoying anyway. There’s nothing stopping you from applying effects to elements if you want that though.

You can see the styles applied to the widget in the stylesheet

And we’re done

Wasn’t that easy? Well, there are lots of details that I didn’t cover, but the point was more to show the procedure for unobtrusively adding behaviour to a page with JavaScript. If I were to make this procedure into a recipe-like and abstract list, it would look something like this:

  • Write your HTML as you would if you only planned a static page, but add identifiers (ids, class names) to the elements so you can locate them in the DOM.
  • Load JavaScripts and stylesheets from external files
  • In the scripts, locate the elements in the dom
  • Wrap the located elements in JavaScript objects
  • Add methods and properties to the wrapper objects that can perform actions on the elements
  • Save a reference to the wrapper objects somewhere so you can access them when you need to

This abstracts the nature of the DOM away from the interface with which you’re going to interact, so instead of telling a DOM element to “change your style so that the background color is red instead of blue because that’s how it should look then the element is inactive” you just say to the wrapped object “become inactive” and control its appearance with CSS.

⬆ ⬆ ⬇ ⬇ ⬅ ➡ ⬅ ➡ B A