The Tree Widget
- Feature Owner
- David Hyatt
The tree widget provides the ability to display multiple items in a tabular format. Each item is represented as a row in the table. Each row may consist of multiple cells. A tree widget has all the same components as an HTML table, and any users of this widget should be thoroughly familiar with the capabilites of HTML tables. Trees can also respond to all of the same CSS properties as HTML tables, as documented in the CSS2 spec on tables.
The following tags are roughly analogous to their HTML table counterparts.
table
| tree
|
---|---|
table
| tree
|
caption
| treecaption
|
colgroup
| treecolgroup
|
col
| treecol
|
thead
| treehead
|
tbody
| treechildren, treeitem
|
tfoot
| treefoot
|
tr
| treerow
|
td
| treecell
|
To insert a tree widget into a document, use the
tree
tag. This tag is analogous
to the table
tag in HTML.
The tree widget can contain a caption, which can be
specified using the treecaption
tag.
The tree's column information can be specified using the
treecolgroup
and
treecol
tags; these tags
respond to all the same properties that the HTML
col
tags do. These column tags
can be used to specify column widths and to apply styles to
individual columns in the tree view. Tree column tags can
respond to an additional CSS property,
column-layout
, which indicates
whether or not this column's size is alterable using the
tree's column resizing capabilities. It has values of fixed
and flexible, with the default being flexible.
This is not yet
implemented.
A tree can contain headers, bodies, and footers, just as a table does. Not yet implemented, but coming online soon, so pay attention. A tree widget's body, unlike a table's body will constrain itself when the height of the table is constrained.
The treeitem
tag is used to
declare a single item (along with that item's row and
children) in the tree. The tree item contains one or more
rows, which are specified using the
treerow
tag. Each row consists
of one or more cells, which are specified using the
treecell
tag.
<tree> <treehead> <treerow> <treecell>Name</treecell> <treecell>URL</treecell> </treerow> </treehead> <treechildren> <treeitem> <treerow> <treecell>Netscape Home Page</treecell> <treecell>http://www.netscape.com</treecell> </treerow> </treeitem> </treechildren> </tree>
Tree items are either open or closed, and their state is
specified using the open
attribute. This attribute, when set to true, indicates that
the tree item's children should be displayed in the tree. If
it is set to false, or if it isn't set at all, then the tree
item's children are not displayed.
A tree item's children are specified using the
treechildren
tag, which is
placed within the parent
treeitem
tag. The
treechildren
tag contains tree
items that represent the children of the parent node.
<tree> <treehead> <treerow> <treecell>Name</treecell> <treecell>URL</treecell> </treeitem> </treerow> <treechildren> <treeitem> <treerow> <treecell>Netscape Bookmarks Folder</treecell> <treecell/> </treerow> <treechildren> <treeitem> <treerow> <treecell>Netscape Home Page</treecell> <treecell>http://www.netscape.com</treecell> </treerow> </treeitem> </treechildren> </treeitem> </treechildren> </tree>
The tree automatically handles selection, and it is
capable of supporting single and multiple selection (through
the use of CTRL/command and SHIFT modifier keys). The tree's
selection is reflected into the content model. Tree items
that are selected have an attribute called
selected
set to true. Tree rows
that are selected have an attribute called
selectedrow
set. Tree cells
that are selected have an attribute called
selectedcell
that is set to
true. CSS can be applied using attribute selector rules,
thus enabling the tree designer to control the look of the
tree's selection.
A select
handler can be
attached to the tree, and this handler will be invoked
whenever the tree's selection changes. You can either add it
as an attribute (e.g.,
onselect
) on the
tree
tag or add it using the
DOM addEventListener
method.
The tree widget also supports hover feedback on items and
cells. This hover feedback is also reflected into the
content model. Tree items that are currently moused over
have an attribute called hover
set to true. Tree cells that are being moused over have an
attribute called hover
set to
true. Tree rows also use hover
.
CSS can be applied using attribute selector rules, which
enable control of the hover feedback effects for the tree.
This will probably change to use
the :hover mechanism at some point, assuming some sort of
bubbling mechanism for hover is determined.
Because the tree handles selection, events that would
normally go to the content inside the cell are intercepted
by the cell. This behavior can be turned off on a cell by
cell basis by setting the
treeallowevents
attribute on
the cell. This attribute, when set to true, indicates that
the cell is unselectable, and that the content inside should
receive events (like mouse clicks and keystrokes).
To selectively allow events to go to cell content (e.g.,
a button inside a cell), the
treeallowevents
attribute can
be placed on a node that is a descendant of the cell.
The tree widget supports a selection API that can be used to modify its current selection. This API is outlined below.
interface XULTreeElement : XULElement { readonly attribute NodeList selectedItems; readonly attribute NodeList selectedCells; void selectItem(in XULElement treeItem); void selectCell(in XULElement treeCell); void clearItemSelection(); void clearCellSelection(); void addItemToSelection(in XULElement treeItem); void removeItemFromSelection(in XULElement treeItem); void addCellToSelection(in XULElement treeCell); void removeCellFromSelection(in XULElement treeCell); void toggleItemSelection(in XULElement treeItem); void toggleCellSelection(in XULElement treeCell); void selectItemRange(in XULElement startItem, in XULElement endItem); void selectCellRange(in XULElement startItem, in XULElement endItem); void selectAll(); void invertSelection(); };
Content in a tree cell can be automatically indented
based on the node's level in the tree by using the
treeindentation
tag. The
indentation tag can have a
width
property applied to it
using CSS, and this property is multiplied by the node's
level of indentation to determine how much padding to
insert. The default if no property is specified is 16 pixels
per level. Style support for
treeindentation is not yet implemented.
Answers to Common Questions
- How do I give column headers a visually distinct look?
-
You can make a CSS rule like the following to apply a look only to cells inside the head of the table.
treehead > treerow > treecell { ... }
- Can I set the widths of my columns using percentages?
- Yes. Click here for details.
- I don't want the text in my tree cells to wrap. What do I do?
- Set the
whitespace
property on your tree cells in CSS to a value of nowrap. Then constrain the width of your tree (e.g., give your tree a width of 100%). Once you have done this, the contents of the cells will be clipped. - I would like the text in my tree cells to be cropped in addition to being clipped. What do I do?
-
Not yet implemented. You will be able to specify this through CSS. If the
nowrap
property is set and an additionalcropstyle
property is set (whose value can be left, middle, or right), then the text in the cell can be made to crop.A temporary workaround to get this feature working would be to use titled buttons to hold the text. (See Titled Buttons for details.) Titled buttons already know how to crop right by default when their widths are constrained.
- I want to use the same event handler for all items in my tree, but I don't want to define these handlers over and over on each node. What do I do?
-
Take advantage of the DOM's event handling capabilities. You can allow events to bubble up to your
tree
node, and handle the events there. The target of an event that bubbles up can be retrieved usingevt.target
.When using bubbling on a tree view, it is important to remember that the events will bubble from child node to parent node. Don't think of it like a table, where the events will always go from cell to row to body. If a node is 11 levels deep, then the event will bubble out through the node's parents before getting to the tree body. Be aware of this distinction when writing event-handling code.
- How do I make the tree's body scroll independently of the header/footer?
- Constrain the height of the tree and this overflow will happen automatically when there are too many items to fit in the displayable area. Boxes can be used to make the tree springy (and to make the tree be sized to the width and height of a window).
- How do I make clickable graphical cell contents like in Messenger (flags, checks, etc.)?
- Use Titled Buttons. In the click handler for the button, set an attribute on the button to cycle through the various states. Use attribute selector rules for each possible attribute value to set up different images for each state of the button. If desired, the button's border can even be hidden using CSS.
- How do I hide columns?
- You can read about this capability here.
- I want to do "insert my feature here" with the tree, but I don't know where to start!
- It is important to remember that a tree is an extended table, and therefore many of its capabilities are inherited from tables. Make sure you read the CSS2 and HTML specs on tables (links are at the top of this page) to see if your question is answered there.
Tree Drag & Drop
The main thing to remember during a tree drag is that the
event target
will be a tree
cell, but all of the information about the drag is done at
the tree item level. This requires going up two levels in
the content tree from the target.
Tracking During The Drag
The tree item makes use of a DOM event capturer to annotate the item's content node with information about where the drag will go in order to draw the correct drop feedback.
The tree needs a little help from the
ondragover
event handler to
trigger the drop feedback magic. The capturer always
annotates the tree item with various attributes, but it
might not be valid to drop on the tree based on the contents
of the drag. When the event handler determines that a drag
is valid, it needs to touch the
dd-triggerrepaint
attribute on
the tree item which triggers a repaint with the correct drop
feedback. By not touching this attribute, the item will not
redraw and this prevents the feedback from appearing. If you
don't want drop feedback at all, this is how you'd
accomplish it.
function DragOverTree ( event ) { var validFlavor = false; var dragSession = null; var dragService = Components.classes["component://netscape/widget/dragservice"].getService(Components.interfaces.nsIDragService); if ( dragService ) { dragSession = dragService.getCurrentSession(); if ( dragSession ) { if ( dragSession.isDataFlavorSupported("moz/toolbaritem") ) validFlavor = true; else if ( dragSession.isDataFlavorSupported("text/plain") ) validFlavor = true; //XXX other flavors here... // touch the attribute on the rowgroup to trigger the repaint with the drop feedback. if ( validFlavor ) { var treeItem = event.target.parentNode.parentNode; treeItem.setAttribute ( "dd-triggerrepaint", 0 ); dragSession.canDrop = true; event.preventBubble(); } } } } // DragOverTree
Determining Where To Drop
Instead of placing an
ondragdrop
event handler on
every single tree item, it's easiest to place one on the
topmost tree
tag and use the
target
of the event to find the
actual drop location. Remember that the tree cell is the
actual target and you need to go up two levels to get the to
correct tree item.
The event capturer sets two attributes on the target tree
item during the drag tracking which are useful when the user
finally releases the mouse over the tree. The first is
dd-droplocation
which a boolean
which when true indicates that the drop should go
before this tree item. If it is false, the drop
should go after. The second attribute,
dd-dropon
, tells if the drop
should go on (or into) a container on the tree. When
this is true, dd-droplocation
is undefined.
The tree item determines if something is a container by
checking the container
attribute of the content node, and determines if it is open
by checking the open
attribute.
Very Important
When the target of the drop is a container and the
dd-droplocation
is false
(meaning after), you must check if the container is open and
do different things in each situation. If the container is
open, the dropped item should be placed as the first child
of the container. If the container is closed, the item
should be placed after the container, as its sibling.
There really isn't a good way to hide this situation within the implementation of the tree because we're not allowed to change event.target after the fact. As a result, all JS implementors need to take this into account. A good wrapper layer could hide this detail from average users (hint hint).
Here's a simple example which demonstrates how to get the before/after/on information for a handler registered on the tree as a whole.
<tree id="bookmarksTree" ref="NC:BookmarksRoot" context="contextual" datasources="rdf:bookmarks rdf:files rdf:ftp rdf:localsearch rdf:internetsearch" ondragdrop="return DropOnTree(event);">
function DropOnTree ( event ) { // id (url) is on the <treeitem> which is two levels above the <treecell> which is // the target of the event. var treeItem = event.target.parentNode.parentNode; var id = treeItem.getAttribute("id"); var dropBefore = treeItem.getAttribute("dd-droplocation"); var dropOn = treeItem.getAttribute("dd-dropon"); dump("**** before is " + dropBefore + " on is " + dropOn + "\n"); var beforeString = "before"; if ( dropBefore == "false" ) beforeString = "after"; dump ( "*** tree drop " + beforeString + " target [" + id + "]\n" ); event.preventBubble(); }
Customizing Drop Feedback
The drop feedback marker bar is normally black, but can
be customized on a particular toolbar via CSS. To do this,
use the :-moz-drop-marker
pseudoelement. You can also customize the color a
container's cell is colored indicating it will be dropped on
with :-moz-drop-container-bg
.
:-moz-drop-marker { color: blue; } :-moz-drop-container-bg { color: blue; }