Tree Views and Data Adapters
This document describes how Thunderbird displays large amounts of data in a tree or list structure.
Thunderbird is transitioning away from using the XUL <tree> element and the associated
nsITreeView interface for providing data to it. This is tracked on Bugzilla in the
tb-deforestation bug.
Note
Confusingly, both the UI element displaying data and the classes providing the data are called “view”. Naming is hard. We are moving towards calling the latter a “data adapter” instead.
And there may or may not be any tree (hierarchy) involved, the data could be a flat list.
TreeView
TreeView is an HTML custom element (<tree-view>) replacement for <tree>. It is currently used
as the message list in the mail tab and the contacts list in the address book tab. For the mail tab
it supports nsITreeView as the mail views are currently implemented in the C++ back end (see
nsMsgDBView and friends). There are also a few extra pieces of code which really should be in
about3Pane.js but … aren’t.
The custom element contains a table for displaying the data in rows and columns (“table layout”), although that’s not strictly necessary. Both the mail tab and address book tab default to a single-column “cards layout” where each item’s data is displayed in a multi-line card.
Only the rows in view, and some rows just off-screen, actually exist as rows in the table. To set the table’s height for scrolling, and to preserve the scroll position, spacer rows are positioned above and below the visible rows. The height of the spacer rows changes as the table is scrolled, or if the total number of rows changes. Initially the visible rows are rendered, then a buffer of off-screen rows is added above and below the visible rows for display while scrolling. The number of buffer rows depends on the number of visible rows.
TreeViewTableRow
TreeViewTableRow is another custom element which is the rows in the table. It is a base class
providing various selection and utility functions which are the same for every tree view. In all
uses a subclass should extend it, with the relevant code for display.
To tell a TreeView which row element to use, set the rows attribute to the name of the element.
The ROW_HEIGHT property sets the height of table rows. Do not allow rows to be taller than this
height, or you’re going to have a bad time.
The index property is the index of the row within the tree.
The fillRow method is used to create the display for a row. Subclasses should override this and
call back to it. fillRow is usually called one frame after index is set, and index can be
set many times before fillRow is called – for example if the view is being filled very fast.
AutoTreeView
AutoTreeView is a subclass of TreeView designed to be a better drop-in replacement for XUL
trees and such it does several things automatically (hence the name). Most importantly a row class
does not need to be provided for each use, it has its own. Set the defaultColumns and view
properties and AutoTreeView will do everything for you.
Column visibility, width, order, and sorting are handled and these are persisted in the profile for the next time the page appears.
Hierarchical data structures can be displayed, with automatic indentation and twisty buttons.
Cells can contain icons (styled by CSS based on row properties) and check boxes.
AutoTreeView does not support nsITreeView views.
AutoTreeViewTableRow
AutoTreeViewTableRow is a row class for table layout which handles rows and cells as configured
by AutoTreeView.
TreeDataAdapter
To get data from where it is to the UI, use a TreeDataAdapter or, more likely, a subclass for
tidiness. This class is based heavily on the nsITreeView interface it replaces, and until we
remove the need for TreeView to support nsITreeView (hopefully with Panorama),
it needs to support some old ways of doing things.
In a simple example, fill the _rowMap property with any number of TreeDataRow objects, and tell
the tree view about them with the invalidate and rowCountChanged methods.
TreeDataRow
This class contains just the data to be displayed, and the meta-data to do so. Each row object has the following properties:
texts– The text to be displayed for this row. This is a JS object, where the keys are column IDs, and values are the text to display.values– Same astexts, but instead the values are string or numeric values for sorting rows.properties– A collection of properties (strings) for this row.level– How deep in the tree this row is. Top-level rows are at level 0.open– Whether or not this row is open (i.e. its children are visible).parent– The parent of this row, or null if this is a top-level row.children– An array of child rows of this row.
Testing
Tests of the tree view and data adapters have the tag dataadapter. There are both unit tests and
UI tests. Run mach xpcshell-test --tag dataadapter and mach mochitest --tag dataadapter to run
them all. Please tag any new tests.
Tests of the tree view elements are in mail/base/test/browser/widgets. Tests of the base data adapter are in mail/base/test/unit, and the subclasses each have tests in their relevant components.
Future Plans
(In no particular order.)
Replace
nsMsgDBViewand friends withLiveViewDataAdapterin the Panorama project. Once this is complete,TreeViewwill no longer need to supportnsITreeViewand we can make changes that would otherwise break things.Remove or replace all instances of
PROTO_TREE_VIEW, an distant ancestor ofTreeDataAdapter.Convert all remaining XUL trees to AutoTreeView (or get rid of them). The remaining XUL trees are in little-used parts of the UI and as such have poor test coverage, so we need to be careful. And add test coverage.
Refactor the about:3pane-specific code out of
TreeView, and into about3Pane.js or somewhere related.Merge
AutoTreeViewback intoTreeView, or at least rearrange some of the code to more logical places.AutoTreeViewwas created to provide more behaviour without the risk of breakingTreeView, which is some of the most important UI code in Thunderbird.Extract the selection code from the display code, so that we can reuse what we’ve already created in use-cases where selection is not required. For example, the chat message display.
Investigate returning to an HTML structure that doesn’t use a
<table>, but instead has rows as<div>elements positioned bytransformstyles. This may or may not improve layout speed.