• Skip to main content
  • Select language
  • Skip to search
MDN Web Docs
  • Technologies
    • HTML
    • CSS
    • JavaScript
    • Graphics
    • HTTP
    • APIs / DOM
    • WebExtensions
    • MathML
  • References & Guides
    • Learn web development
    • Tutorials
    • References
    • Developer Guides
    • Accessibility
    • Game development
    • ...more docs
Add-ons
  1. MDN
  2. Mozilla
  3. Add-ons
  4. Thunderbird extensions
  5. Creating a Custom Column

Creating a Custom Column

In This Article
  1. Getting Started
  2. Adding A Column
  3. The Column Handler
    1. The nsIMsgCustomColumnHandler Interface
    2. A Simple Implementation
  4. Putting the Pieces together
    1. Setting the Custom Column Handler
  5. Wrap Up
  6. Caveats

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:

overlay chrome://messenger/content/messenger.xul chrome://replyto_col/content/replyto_col_overlay.xul

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:

<?xml version="1.0"?>
<overlay id="sample"
         xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
  <tree id="threadTree">
    <treecols id="threadCols">
    <splitter class="tree-splitter" />
    <treecol id="colReplyTo" persist="hidden ordinal width" 
           currentView="unthreaded" flex="2"
           label="Reply-To" tooltiptext="Click to sort by the Reply-To header" />
    </treecols>
  </tree>
  <!-- include our javascript file -->
  <script type="text/javascript" src="chrome://replyto_col/content/replyto_col.js"/> 
</overlay>

That's it! Our new column exists and is ready to be used. Unfortunately the new column doesn't have any data in it and it doesn't react to sort clicks on it... yet.

The Column Handler

Now that our column physically exists it's time to give it a bit of personality.

The nsIMsgCustomColumnHandler Interface

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.

From nsITreeView:

  • getCellProperties(row, col, props): optionally modify the props array
  • getRowProperties(row, props): optionally modify the props array
  • getImageSrc(row, col): return a string (or null)
  • getCellText(row, col): return a string representing the actual text to display in the column

From nsIMsgCustomColumnHandler:

  • 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:

var columnHandler = {
   getCellText:         function(row, col) {
      //get the message's header so that we can extract the reply to field
      var hdr = gDBView.getMsgHdrAt(row);
      return hdr.getStringProperty("replyTo");
   },
   getSortStringForRow: function(hdr) {return hdr.getStringProperty("replyTo");},
   isString:            function() {return true;},
   getCellProperties:   function(row, col, props){},
   getRowProperties:    function(row, props){},
   getImageSrc:         function(row, col) {return null;},
   getSortLongForRow:   function(hdr) {return 0;}
}

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.

window.addEventListener("load", doOnceLoaded, false);
function doOnceLoaded() {
  var ObserverService = Components.classes["@mozilla.org/observer-service;1"].getService(Components.interfaces.nsIObserverService);
  ObserverService.addObserver(CreateDbObserver, "MsgCreateDBView", false);
}
var CreateDbObserver = {
  // Components.interfaces.nsIObserver
  observe: function(aMsgFolder, aTopic, aData)
  {  
     addCustomColumnHandler();
  }
}


In this example we have a function addCustomColumnHandler() that is called whenever the event is fired. This function is a simple one liner along the lines of:

function addCustomColumnHandler() {
   gDBView.addColumnHandler("colReplyTo", columnHandler);
}

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:

  • usually, the event sequence is: jsm load -> onload -> MsgCreateDBView
  • with Enigmail, the event sequence becomes jsm load -> MsgCreateDBView -> onload

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).

Document Tags and Contributors

Tags: 
  • Add-ons
  • Extensions
  • thunderbird
 Contributors to this page: wbamberg, Yonas_Yanfa, whitedavidp, Sevenspade, Aryx, mozjonathan, erik240, Vor0nwe, Jkchow, Amzoss, Rod Whiteley, DavidBienvenu, Sheppy, Nachmore
 Last updated by: wbamberg, Jan 15, 2016, 11:04:56 AM
See also
  1. WebExtensions
  2. Getting started
    1. Prerequisites
    2. Anatomy of a WebExtension
    3. Packaging and Installation
    4. Walkthrough
    5. Examples
  3. Guides
    1. Content scripts
    2. Porting from Google Chrome
    3. Match patterns
    4. Debugging
    5. Chrome incompatibilities
  4. JavaScript APIs
    1. alarms
    2. bookmarks
    3. browserAction
    4. contextMenus
    5. cookies
    6. events
    7. extension
    8. extensionTypes
    9. i18n
    10. idle
    11. notifications
    12. pageAction
    13. runtime
    14. storage
    15. tabs
    16. webNavigation
    17. webRequest
    18. windows
  5. Manifest keys
    1. applications
    2. background
    3. browser_action
    4. content_scripts
    5. default_locale
    6. description
    7. icons
    8. manifest_version
    9. name
    10. page action
    11. permissions
    12. version
    13. web_accessible_resources
  6. Add-on SDK
  7. Getting started
    1. Installation
    2. Getting started
    3. Troubleshooting
  8. High-Level APIs
    1. addon-page
    2. base64
    3. clipboard
    4. context-menu
    5. hotkeys
    6. indexed-db
    7. l10n
    8. notifications
    9. page-mod
    10. page-worker
    11. panel
    12. passwords
    13. private-browsing
    14. querystring
    15. request
    16. selection
    17. self
    18. simple-prefs
    19. simple-storage
    20. system
    21. tabs
    22. timers
    23. ui
    24. url
    25. widget
    26. windows
  9. Low-Level APIs
    1. /loader
    2. chrome
    3. console/plain-text
    4. console/traceback
    5. content/content
    6. content/loader
    7. content/mod
    8. content/symbiont
    9. content/worker
    10. core/heritage
    11. core/namespace
    12. core/promise
    13. dev/panel
    14. event/core
    15. event/target
    16. frame/hidden-frame
    17. frame/utils
    18. fs/path
    19. io/byte-streams
    20. io/file
    21. io/text-streams
    22. lang/functional
    23. lang/type
    24. loader/cuddlefish
    25. loader/sandbox
    26. net/url
    27. net/xhr
    28. places/bookmarks
    29. places/favicon
    30. places/history
    31. platform/xpcom
    32. preferences/event-target
    33. preferences/service
    34. remote/child
    35. remote/parent
    36. stylesheet/style
    37. stylesheet/utils
    38. system/child_process
    39. system/environment
    40. system/events
    41. system/runtime
    42. system/unload
    43. system/xul-app
    44. tabs/utils
    45. test/assert
    46. test/harness
    47. test/httpd
    48. test/runner
    49. test/utils
    50. ui/button/action
    51. ui/button/toggle
    52. ui/frame
    53. ui/id
    54. ui/sidebar
    55. ui/toolbar
    56. util/array
    57. util/collection
    58. util/deprecate
    59. util/list
    60. util/match-pattern
    61. util/object
    62. util/uuid
    63. window/utils
  10. Firefox for Android
  11. Getting started
    1. Walkthrough
    2. Debugging
    3. Code snippets
  12. APIs
    1. Accounts.jsm
    2. BrowserApp
    3. HelperApps.jsm
    4. Home.jsm
    5. HomeProvider.jsm
    6. JavaAddonManager.jsm
    7. NativeWindow
    8. Notifications.jsm
    9. PageActions.jsm
    10. Prompt.jsm
    11. Snackbars.jsm
    12. Sound.jsm
    13. Tab
  13. Legacy
  14. Restartless extensions
    1. Overview
  15. Overlay extensions
    1. Overview
  16. Themes
  17. Lightweight themes
    1. Overview
  18. Complete themes
    1. Overview
  19. Publishing add-ons
  20. Guides
    1. Signing and distribution overview
    2. Submit an add-on
    3. Review policies
    4. Developer agreement
    5. Featured add-ons
    6. Contact addons.mozilla.org
  21. Community and support
  22. Channels
    1. Add-ons blog
    2. Add-on forums
    3. Stack Overflow
    4. Development newsgroup
    5. IRC Channel