XUL Trees and Objects: ClassTreeView

I love XUL trees. I even
smoke them
from time to time.
But what I don’t like is trying to build a hierarchy of objects in them – even
though that’s probably the best use for them.

Imagine that you want to show this tree of objects, with properties of each
object horizontally, and the objects themselves laid out vertically, indented
and illustrated to show which objects have which parent objects. DOM Inspector
does this with DOM nodes all the time. My chrome registry viewer code does
something similar for files (file systems are tree-like), and when you want to
see the properties of an object, JS object inspection is usually through a tree.
Even Venkman uses trees to show you functions in a file or webpage.

Still, for every different object tree I’ve come across, there’s a
different view that has to be built. Usually it’s custom-built for that tree.
So you’ve got two options: build your own view, from scratch, every time… or
build a XUL tree DOM and let Gecko’s own tree utilities show it to you.

Believe it or not, I’ve tried both approaches… and finally decided to
roll my own baseline solution. (If someone else has done this before, please let
me know. It’s best to have this in a common place.) More details in the extended section.

nsITreeView

Here’s the kind of image that comes to mind every time I’ve looked at the
nsITreeView interface:

nsITreeView’s heart

Seriously, what in the name of
(it’s Easter weekend, I don’t care to push my luck) what in
the world is this? At least half the methods on it deal with row-specific or
cell-specific details. Couldn’t the people who work with this just given me a
nsITreeRow interface and a getRow() method on the tree view?!?

I mean, it feels so dirty. It feels…
procedural.
Not object-oriented at
all. How do you map a row in this tree to an object? You don’t through this
interface – because there’s no row to grab.

It’s also an interface with a lot of methods on it to implement. Yeeeuck.
It’s not immediately obvious which ones you need, and when. I’ve never truly
understood this tree view stuff, even with XULPlanet’s tutorial lending a hand.

<xul:treeitem/> and friends

I do understand treeitems, treechildren, treerow, and the like, though.
It’s DOM! It’s something we already have! It’s easy to inspect, to debug! It’s
familiar!

This was my preferred method of building trees for five years. Never mind
the various people who kept telling me that tree views were so much better,
ignoring me when I said, “No, make this a DOM set of trees, that doesn’t need a
lot to understand.” It’s simple. I like simple.

Until two or three months ago, when I discovered
nsTreeContentView.
For those of you who don’t know about it, this is what takes your pretty
DOM-based XUL tree and converts it into a nsITreeView object.

So here’s what happens: My app spends a lot of time crafting this oh-so-nice,
fifteen-levels-deep, memory-crushing, CPU-melting DOM fragment. My program then
appends it, forcing nsTreeContentView to go to work, ripping that fragment apart
and creating… a nsITreeView that I did everything to avoid dealing with, and
which you just waited far too long for. Not to mention the bootstrapping I have
to put on top of that DOM tree to bind each row to some object.

There are a few things
that are more efficient
than this approach…

Starting over

So finally I said, “enough of this.” If I’m going to build a tree for
objects, I’m going to do it right. I’m going to create a generalized tree view
component, reading from both the tree and from objects, to show the object
hierarchy. I’m going to define a very simple, and very flexible, API that my
component’s users can build in and make it all work.

The first concept is that tree columns define what the cells show. So
let’s just go ahead and define a propertyname attribute to stick
on each <xul:treecol/>. element. We still need those,
anyway. For more complex properties, define a fallback by allowing the tree’s
author to set a function on the column element. I do this through the DOM 3
UserData API.

The second concept is that there’s a 1:1 mapping between rows and objects.
That is, for each row, there should be exactly one object, and vice versa. So,
at least internally, we need a TreeRow class to store a reference
to the original objects.

The third concept is that the tree view needs to know how to get child
objects of a given object. To do that, the tree view requires you to pass in
a function which takes an object and returns an array of objects, which you say
are children of that object.

The fourth concept is to provide a way to add top-level objects to this
tree. Every DOM tree has a root node, every file system has a few items at the
top. A generic tree view class can’t know about them beforehand, so you have to
tell it about them.

Put these four together, and you have enough to build a generic algorithm,
a generic “class tree viewer”.

ClassTreeView

Source Code

Sample chrome code (copied, altered from the XUL Tutorial on
developer.mozilla.org
)

testTreeView.xul



Element and MatterState represent (in this case) two similar classes which
live in the same tree – so really, you’d only need one of them. One column has
a propertyname attribute on it. The other one (further down in
the init() function) has a cellGetter function on it.
The getObjectChildren() function tells the tree view how to go from
one object to its children. Everything else is just raw data, initializing the
tree and the tree view, and adding top-level objects to the tree. It’s really
just that simple, and pretty light-weight.

Now, I will admit this ClassTreeView is not complete.
For instance, it’s read-only, and doesn’t yet support progress meters, check
boxes, etc. I don’t need those at this point, and to me it’s just more
complexity (see nsITreeView for details). As a starting point for showing a
true object hierarchy, though, it’s good enough. If you know tree views and
want to finish this, patches accepted!

API-wise, it’s a “best guess”. I designed this to work based on my needs
and understanding. Maybe there are better approaches – but for this particular
problem, I think it’s a good start.

The one true weakness to ClassTreeView is that content web
pages cannot use it. The reason for that is buried at the end of the
nsITreeView.idl file: the nsINativeTreeView interface. So perhaps
I will someday rewrite this in C++ code – after I figure out C++-to-JavaScript
interfaces, and with a lot of reviews to make sure I get it right – and I’ll
make it available to the Web. (Then again, you don’t see a whole lot of XUL
on the Web… maybe for reasons like this.)

4 thoughts on “XUL Trees and Objects: ClassTreeView”

  1. Yea, I’ve done this before. Unfortunately, it turned out to be about a million times more tricky than I realized going in, or I never would have bothered. Code that seems to work pefectly on simple examples utterly fails to capture the rich complexity that is actually required. Frankly, the nsITreeView interface was never meant to be implemented by mere mortals, or more than once.
    Jan Varga set me straight, however, and ripped out all of that code and replaced it with a template that pulls data from the sqlite query processor. That made it dead simple. I believe he maintains (though that may be too strong a word) a patch to backport the feature to the 1.8 branch, if that’s what you need.

  2. I wrote the original hierarchical nsITreeView example submitted to XULplanet and still available at http://neil.rashbrook.org/primary.xul although you’ll need to enable privileges to demonstrate it.
    What’s the deal with your propertyname attribute?
    (From Alex: It’s really just a convenience so that one can map a property of an object to a column in that row. The fallback is the cellGetter user data.)

Comments are closed.