A New Theme Format for Bluebird

Matt Patenaude (Updated 4/14/2009)

Bluebird hasn’t even been out for a week, and already we’ve had over 11,000 downloads. The response from the community has been tremendous, and though the development of Bluebird themes hasn’t been quite as rapid as the Bowtie theme explosion when we released it many months ago, the result has been excellent. We’re not done creating yet, though. Announcing the new Bluebird theme format, coming in the next build of Bluebird!

The Why

As great as the available themes are, we’ve noticed there are some things you just can’t do with them. For instance, animating new tweets and displaying “newness” just isn’t easy (or even possible) with the current format. Beyond that, adding new tweets into the list is slow and not modular: it requires an entire theme reload, and it makes it harder to remember scrollbar location. All-in-all, it has a lot of problems. We’ve studied a few other sources that use WebKit for theming, and we think we may have found a solution that will make everyone pretty happy.

The What

We’re modularizing the theming process into separate files. Like in the current system, there will be a main template file which loops over the tweets. However, instead of putting the display code for the tweets in that file, you will use an external file, and the main template file will load that every time a new tweet is added. Makes sense so far, right?

The beauty of this is that for new tweets after the initial load, Bluebird can only process that external “tweet” template file, then hand the result of that off to a JavaScript function, which you can then use to append it to your pre-existing list of tweets. This gives you a lot of flexibility: you can style it differently to represent newness, you can animate it into the list yourself — in general, you can do some pretty cool things.

Because the main template file will still be looping over tweets, the theme format is fully backwards-compatible: if you don’t update your theme to use the new format, no worries, it will continue to work exactly the way it used to. However, if you do update your theme, you’ll be able to take advantage of faster loading times, and cool things like animation. Trust me, you want to do this. (Note: as of Bluebird b1.1 [Maintenance Release], non-upgraded themes DO NOT WORK. Themes must be upgraded in order to be used with Bluebird b1.1. This may be changed by b2, we have not yet decided).

And we made it easy.

The How

In the current format, you specify either one or two files in your Info.plist file: BBMainFile points to the main template file for your theme. Unless BBDMFile is also set, this is the template file that is loaded any time tweets need to be displayed. If BBDMFile is set, the main template file is only used when Bluebird is in the “status updates” timeline mode; BBDMFile is loaded instead when viewing threaded Direct Messages.

This practice hasn’t changed in the new format, but we’ve added two more files to the package: tweet.html and (optionally) dm.html. The names of these files are not specified in Info.plist; they are coded into the app and cannot be changed. Only tweet.html needs to be present; if dm.html is also present, it will be substituted in when displaying a Direct Message (makes sense, right?).

Converting your theme is relatively simple. First, locate the block in your main template file that goes a little something like this:

{% for tweet in tweets %}
    <!-- Code for each tweet goes here -->
{% /for %}

Take everything in between the for brackets and stick that in tweet.html. This is now your tweet template. In its place in the main template file, use the new tweet property html. Calling that property will take everything you just put in the tweet.html file (or dm.html file) and substitute it in. The loop in your main template file should now look something like this:

{% for tweet in tweets %}
    {{tweet.html}}
{% /for %}

While (if the original code sample was real), your tweet.html file will contain only this:

<!-- Code for each tweet goes here -->

With me so far? Good! You’ve now taken the first step in modularizing your theme. (Note: the name of the tweet property html is symbolic of the fact that it inserts the rendered HTML of a given tweet. The fact that the command for this property, and the file that contains the HTML for your tweet, both happen to be named tweet.html is purely coincidence, and does not mean that calling {{florg.html}} in your template would load a file called florg.html). The only remaining thing to do is add all of the connective JavaScript.

First, you’ll want to insert a script block in the head section of your file.

<script type="text/javascript">
function addTweet(theTweet, shouldDisplay, nextTweet, isNew, tweetObj)
{

}
function removeTweet(theTweet)
{

}
function changeVisibility(theTweet, shouldDisplay)
{

}
</script>

You will be responsible for implementing three functions. In all sample code for these functions below, we will assume two things: that the function $() is defined as follows:

function $(el) { return document.getElementById(el); }

This is typical in most JavaScript libraries, including Prototype; and we will also assume that the object that contains your list of tweets — be it an unordered list (ul), a div, or whatever suits your fancy — has an ID of “container”. Now let’s talk about the functions one-by-one.

addTweet

The addTweet function is called whenever a tweet needs to be added to the list.

The first parameter, theTweet, is a DOM HTML element ready to be appended to the document tree. You can get the raw HTML of this object by reading the innerHTML property as with any other HTML element.

The second parameter, shouldDisplay, is a Boolean value indicating whether or not the tweet should be visible when added to the list of tweets. This is based on the filter bar settings (“All”, “Friends”, “Own”, etc.) and whatever is currently in the search bar. You must obey this value.

The third parameter, nextTweet, is a DOM HTML element representing the tweet that is displayed in the list immediately after this one. If it’s NULL, that means the tweet needs to go at the end of the list (this is almost always usually the case). If this value is set, however, that means you must insert this new element into the tree before the element specified by nextTweet (for instance, if the current tweet is old, it will need to go higher up in the list). This makes it very easy to use the DOM method insertBefore to add new tweets into your list (insertBefore with a NULL second parameter behaves identically to appendChild).

The fourth parameter, isNew, tells you whether this tweet just arrived, or is an old tweet that just needs to be added into the list. You can safely ignore this property, but use it if you want to apply special styling to new tweets (you’ll have to use a timer or something to turn this off yourself after some time).

The fifth parameter, tweetObj, is a JavaScript object with the same syntax as the MGTemplateEngine tweet object. For instance, if for some reason in your JavaScript function you want to know the full name of the author of the tweet, you can simply call tweetObj.author.fullName. Often, you won’t use this, but it’s here as a convenience. (Note: this object DOES NOT WORK in b1.1. This WILL be fixed by b2).

A simple, barebones implementation of this method (provided as sample code) would work something like this:

function addTweet(theTweet, shouldDisplay, nextTweet, isNew, tweetObj)
{
    if (!shouldDisplay)
        theTweet.style.display = 'none';

    $('container').insertBefore(theTweet, nextTweet);
}

Not too painful, is it?

removeTweet

The removeTweet function does just what its name replies: removes tweets.

The first and only parameter, theTweet, is a DOM HTML element that refers to the tweet that is to be removed.

A simple, barebones implementation of this method (provided as sample code) would work something like this:

function removeTweet(theTweet)
{
    $('container').removeChild(theTweet);
}

changeVisibility

The changeVisibility function tells you when a tweet needs to be shown or hidden. This is in response to changes in the filter bar (“All”, “Friends”, “Own”, etc.) or the contents of the search field. Showing and hiding DOM elements is faster and more efficient then adding them to or removing them from the DOM tree.

The first parameter, theTweet, is a DOM HTML element that refers to the tweet whose visibility needs to be changed.

The second parameter, shouldDisplay, is a Boolean value indicating whether or not the tweet should be displayed.

A simple, barebones implementation of this method (provided as sample code) would work something like this:

function changeVisibility(theTweet, shouldDisplay)
{
    theTweet.style.display = (shouldDisplay) ? '' : 'none';
}

Or, in more expanded form for those not comfortable with the conditional operator:

function changeVisibility(theTweet, shouldDisplay)
{
    if (shouldDisplay)
        theTweet.style.display = '';
    else
        theTweet.style.display = 'none';
}

All together, a completed script section would look like this (assuming your tweets are contained in an element with the ID “container”):

<script type="text/javascript">
function $(el) { return document.getElementById(el); }
function addTweet(theTweet, shouldDisplay, nextTweet, isNew, tweetObj)
{
    if (!shouldDisplay)
        theTweet.style.display = 'none';

    $('container').insertBefore(theTweet, nextTweet);
}
function removeTweet(theTweet)
{
    $('container').removeChild(theTweet);
}
function changeVisibility(theTweet, shouldDisplay)
{
    theTweet.style.display = (shouldDisplay) ? '' : 'none';
}
</script>

Add that script into your theme, and congratulations! Your theme is now completely utilizing the new format. You can now go back into some of the functions and play around to add things like animation and other effects.

The only remaining thing you’ll have to do is tell Bluebird that it’s time to load tweets into your theme. This is done by calling the Bluebird.loadTweets() method. Do this as soon as your page has loaded, typically using your body’s onload attribute, like so:

<body onload="Bluebird.loadTweets();">
<!-- Blah blah blah -->
</body>

And we’re done!

Anything Else?

Well yes, now that you mention it, there is something else. We’re also in the process of developing a tool called Nest, which should make developing themes just a little bit more pleasant. It contains a built-in mini editor for Info.plist files (for those of you who don’t have Apple’s Property List Editor, included with Xcode), and allows you to preview theme files instantly with a fake set of tweets, then export that to a preview HTML file to use in Bluebird. It isn’t all that fancy, but it should make things a whole lot easier!

The When

A build supporting the new theme format (along with fixing a few bugs, like the notorious “disappearing tweets” issue, and minorly reducing memory usage) is now available, and is called Bluebird b1.1. This is not beta 2, but rather a maintenance release, a) to make sure everyone can enjoy Bluebird fully while we work on new features, and b) to get themers adjusted to using the new theme format well before final release. Nest will probably take a bit longer than that, but shouldn’t be far behind.

Until next time, Bluebirdians, happy theming!