Creating a Custom Column

One of the new and exciting features available to Thunderbird Extension developers in Thunderbird 2.0 is the ability to easily create and handle custom columns in Thunderbird's main view.

The following step through will get you well on the road to creating your own columns and populating them with custom data.

Getting Started

In this example we will be developing a small extension that will be adding a column that will display the "Reply-To:" field of an email (if it exists, it if often not set). If you are unfamiliar with the setup and creation of an extension please read Building a Thunderbird Extension.

Adding A Column

By far the easiest part is the physical addition of the column. For this we overlay messenger.xul, by placing the following line in our chrome.manifest file:

Now that our overlay is set up we need to connect a column to the current columns that exist. Looking in messenger.xul reveals that the columns reside inside a tree with the id "threadTree" whose columns (treecols) reside in "threadCols". To attach our column we add another treecol tag ("colReplyTo") with the settings that we would like to use. Don't forget to add in a splitter before the column to ensure that the user can easily resize and drag our column around.

Our replyto_col_overlay.xul should now contain something similar to this:

The Column Handler

To handle a column we need to implement the nsIMsgCustomColumnHandler interface which defines the basic set of functionality that must be implemented to handle a column. Note that an object that implements this interface can also take control of an existing, built-in column, but more about that later.

nsIMsgCustomColumnHandler extends (inherits from) nsITreeView and as such needs to implement functions from both. The following is a list of functions that must be implemented to ensure that your extension works well with Thunderbird. Rest assured that these functions will be called - so implement them, even with empty function.

getSortStringForRow(hdr): return the string value that the column will be sorted by

getSortLongForRow(hdr): return the long value that the column will be sorted by

isString(): return true / false

Warning! Do not get confused between GetCellText() and GetSortString/LongForRow()! Though they sound similar you may not want to return the same value from both. GetCellText() is the text that is displayed to the user while GetSort*ForRow() is what is used internally when sorting by your column

A Simple Implementation

Objects in Javascript are just "advanced" variables, so an implementation of the nsIMsgCustomColumnHandler interface looks like:

Basically, all we are doing here is making sure that both the text that is displayed to the user (getCellText()) and the string we sort according to (when the user decided to sort the view by our custom column) are identical.

Putting the Pieces together

We now have our column and our handler object - it's time to put everything together. For this we use a new function, addColumnHandler that takes a column ID and a handler object. Our implementation will need to call something similar to:

gDBView.addColumnHandler("colReplyTo", columnHandler);

Setting the Custom Column Handler

We've added a special event that gets fired whenever a view is created. When that event fires, you should set up the custom column handler on the new view.

Wrap Up

As you have seen it is quite easy to add your own custom column handler. Most of the dirty tasks (displaying and sorting) are done for you in the background - so that all that's left to you, the Extension developer is to populate the code with your feature.

Don't forget that columns aren't restricted to text - they can also load images (via getImageSrc()) so that your extension can display a new visual cue to the user.

As you develop your custom extension, please revisit this article and add any helpful hints that you find along the way!

Caveats

Some weird behaviors have been witnessed, namely failing to be notified of the MsgCreateDBView event when Enigmail is installed [protz]. What happens is:

To workaround this, you can either i) manually run addCustomHandler() at the end of your onload handler or ii) register CreateDbObserver when your .jsm is loaded. Some extensions (e.g. rkent's junquilla) use i).