Adding items to the Folder Pane

This tutorial examines how to add items to an existing Folder Pane view. The result is a javascript file that will add a "Numbers" container to the end of Thunderbird's "All Folders" mode. That container will have 3 child-items, the numbers 1, 2, and 3. Clicking on those items will display the number in the main viewing pane of Thunderbird.

First, some necessary background on how the Folder Pane constructs the data it displays. The Folder Pane tracks modifications to a user's folders and accounts. Each time the Folder Pane determines that it is necessary to invalidate all its data (because of too many changes, or more commonly because the Folder Pane's "mode" (aka view) has changed), a "rebuild" occurs. When this happens, the Folder Pane consults the map-generator for the current mode, and that generator returns the necessary data for the Folder Pane's display. This data is returned in the form of an array of folder-tree-items.

Listening for Folder Pane rebuilds

Every time the Folder Pane rebuilds, it fires a "mapRebuild" event, which is the ideal opportunity for extensions to step in and modify the display data. The following code snippet listens for that event:

let gNumbersExt = {
  load: function gne_load() {
    window.removeEventListener("load", gNumbersExt.load, false);
    let tree = document.getElementById("folderTree");
    tree.addEventListener("mapRebuild", gNumbersExt._insert, false);
  },

  _insert: function gne__insert() {
    // This function is called when a rebuild occurs
  }
};
window.addEventListener("load", gNumbersExt.load, true);

The structure of folder-tree-items

The Folder Pane stores its current display data in a property called _rowMap. This property is an array of folder-tree-items. Each item in this array satisfies the following interface:

id
(attribute)
a unique string for this object. Must persist over sessions
text
(attribute)
the text to display in the tree
level
(attribute)
the level in the tree to display the item at
open
(rw, attribute)
whether or not this container is open
children
(attribute)
an array of child items also conforming to this spec
getProperties
(function)
a call from getRowProperties or getCellProperties for this item will be passed into this function
command
(function)
this function will be called when the item is double-clicked

For our example extension, two different types of folder-tree-items will be defined. First, our "Numbers" container looks like this:

    let containerRow = {
      _NUMBERS: 3,
      id: "numbers-main-container",
      text: "Numbers",
      level: 0,
      open: false,

      _children: null,
      get children() {
        if (!this._children) {
          this._children = [];
          for (var i = 1; i <= this._NUMBERS; i++)
            this._children.push(new numberRow(i));
        }
        return this._children;
      },
      getProperties: function gne_getProps() {
        // Put your css attributes here
      },
      command: function gne_command() {
        // Just going to alert, to do something here
        Components.classes["@mozilla.org/embedcomp/prompt-service;1"]
                  .getService(Components.interfaces.nsIPromptService)
                  .alert(window, null, this.text);
      }
    };

Second, our child items (the numbers 1, 2, and 3) are copies of the following prototype:

    function numberRow(aNumber) {
      this._number = aNumber;
    }
    numberRow.prototype = {
      get id() { return "numbers-child-row-" + this._number; },
      get text() { return this._number; },
      level: 1,
      open: false,
      children: [],
      getProperties: function gne_kid_getProps() {}, // no-op
      command: function gne_kid_command() {
        // Just going to alert, to do something here
        Components.classes["@mozilla.org/embedcomp/prompt-service;1"]
                  .getService(Components.interfaces.nsIPromptService)
                  .alert(window, null, this.text);
      }
    };

Putting it all together

All that is left at this point is to actually add these newly defined folder-tree-items to the Folder Pane's _rowMap at the appropriate time. Thus, our _insert function becomes

  _insert: function gne__insert() {
    // Only insert our nodes into the "all" mode
    if (gFolderTreeView.mode != "all")
      return;

    gFolderTreeView._rowMap.push(containerRow);
  },

Right now, clicking on these additional items has no effect. However, the complete example file includes code to display the number selected in Thunderbird's main viewing pane, when such a number is selected in the Folder Pane.

A note about the initial rebuild

When Thunderbird first starts up, the Folder Pane does an initial rebuild to get the first data it should display. Unfortunately, this rebuild will occur before an extension's "load" event fires. Thus, extensions may need to do manual insertions when this load event fires, if they want to add items to the initial display. add_code.js also includes code to accomplish this result.