Tree Box Objects

This section will describe the tree box object used to handle how a tree is displayed.

About the Tree Box Object

Box objects were described in an earlier section. The tree box object is a specialized box object used specifically for trees. The tree box implements the TreeBoxObject interface.

We already saw the rowCountChanged function of the tree box object in the previous section. It is used to indicate that one or more rows have been added to the tree or removed from the tree. The tree will redraw the affected area. You don't need to call the rowCountChanged function when a row has simply changed in some way, for example if a cell's label changes. In this case, there are other drawing functions that can be used. The simplest is to call invalidateRow which will redraw a specific row in the tree. The tree will call the view to get the updated data and update the contents of the tree on screen.

Other redrawing functions are invalidateCell to redraw only a single cell, invalidateColumn to redraw a column, invalidateRange to redraw a range of rows, or the invalidate function to redraw the entire tree. Note that redrawing does not occur until the calling script ends since Mozilla does not redraw in the background.

You can also scroll the tree using four different methods, similar to those available for listboxes. The scrollToRow function may be used to scroll to a particular row. Here is a simple example.

Example 8.6.1: Source View
<script>
function doScroll()
{
  var value = document.getElementById("tbox").value;
  var tree = document.getElementById("thetree");

  var boxobject = tree.boxObject;
  boxobject.QueryInterface(Components.interfaces.nsITreeBoxObject);
  boxobject.scrollToRow(value);
}
</script>

<tree id="thetree" rows="4">
  <treecols>
    <treecol id="row" label="Row" primary="true" flex="1"/>
  </treecols>
  <treechildren>
    <treeitem label="Row 0"/>
    <treeitem label="Row 1"/>
    <treeitem label="Row 2"/>
    <treeitem label="Row 3"/>
    <treeitem label="Row 4"/>
    <treeitem label="Row 5"/>
    <treeitem label="Row 6"/>
    <treeitem label="Row 7"/>
    <treeitem label="Row 8"/>
    <treeitem label="Row 9"/>
  </treechildren>
</tree>

<hbox align="center">
  <label value="Scroll to row:"/>
  <textbox id="tbox"/>
  <button label="Scroll" oncommand="doScroll();"/>
</hbox>

Note that we use the rows attribute on the tree to specify that only four rows are displayed at a time. This makes it easier to see how the example works. Also, notice that the first row is 0.

The doScroll function gets the box object and calls the scrollToRow function with an argument set to the value of the textbox. You might notice that the tree box object can be retieved in the same way as other box objects using the boxObject property, however we need to call QueryInterface to cast the box object to the more specific tree box object. The functions of the more general box object are also available to trees.

Additional scroll methods include the scrollByLines, scrollByPages and ensureRowIsVisible functions. The scrollByLines scrolls up or down by a certain number of rows. Use a positive number to go down and a negative number to go up. The scrollByPages function scrolls by a number of pages and is called automatically when the user presses the page up or page down keys while the tree is focused. A page is equal to the number of visible rows. For example if the tree shows 10 rows at a time, a page will be equivalent to 10 rows. This is a convenient method since when the user resizes a flexible tree, the page size will grow and shrink, so you don't need to calculate the page size manually. This isn't too hard to calculate manually anyway since the tree box object also provides a getPageLength function which returns the number of rows in a page. In the scrolling example above, getPageLength would return four.

Note that in Firefox 1.0 and Mozilla 1.7 and earlier, the getPageLength function is called getPageCount instead. The name was changed to getPageLength since it was confusing before since it doesn't return the number of pages, but the size of each page. You could determine the number of pages by dividing the total number of rows by the page length.

The ensureRowIsVisible function will scroll to a row just as scrollToRow does, but does not scroll if the row is already visible.

Cell Coordinates

Some of the most interesting functions of the tree box object are several functions which may be used to get the parts of the tree at specific coordinates and vice versa. The getCellAt function may be used to get the cell at specific pixel location while the getRowAt function may be used to get a row at a specific location. The getRowAt function takes two arguments, the x and y coordinates to use.

tree.boxObject.getRowAt( 50, 100 );

This example will return the index of the row with a horizontal position of 50 and a vertical position of 100. Naturally, it doesn't really matter what the value of the x coordinate is since rows always take up the entire horizontal space of the tree. One important thing to note is that the coordinates are measured from the upper left corner of the containing document, not the edge of the tree itself. This makes it easy to pass event coordinates directly to these functions, as in the following example of the getCellAt function.

Example 8.6.2: Source View
<script>
function updateFields(event)
{
  var row = {}, column = {}, part = {};
  var tree = document.getElementById("thetree");

  var boxobject = tree.boxObject;
  boxobject.QueryInterface(Components.interfaces.nsITreeBoxObject);
  boxobject.getCellAt(event.clientX, event.clientY, row, column, part);

  if (typeof column.value != "string") column.value = column.value.id;

  document.getElementById("row").value = row.value;
  document.getElementById("column").value = column.value;
  document.getElementById("part").value = part.value;
}
</script>

<tree id="thetree" flex="1" onmousemove="updateFields(event);">
  <treecols>
    <treecol id="utensil" label="Utensil" primary="true" flex="1"/>
    <treecol id="count" label="Count" flex="1"/>
  </treecols>
  <treechildren>
    <treeitem>
      <treerow>
        <treecell label="Fork"/>
        <treecell label="5"/>
      </treerow>
    </treeitem>
    <treeitem>
      <treerow>
        <treecell label="Knife"/>
        <treecell label="2"/>
      </treerow>
    </treeitem>
    <treeitem>
      <treerow>
        <treecell label="Spoon"/>
        <treecell label="8"/>
      </treerow>
    </treeitem>
  </treechildren>
</tree>

<label value="Row:"/>
<label id="row"/>
<label value="Column:"/>
<label id="column"/>
<label value="Child Type:"/>
<label id="part"/>

The getCellAt function takes five arguments, the coordinates to look up and three out parameters. An out parameter is used since the function needs to return more that one value. You will see a number of interfaces that use out parameters in the object reference. These are indicated by the word 'out' before the argument. For these, you will need to supply an empty object and the function will fill in the 'value' property with the necessary value.

The three out parameters will be filled in with the row, the column and a child type. The row is the index of the row the mouse is over, since we call it with the event coordinates of a mousemove event. If the coordinate is not over a row, the row value will be set to -1. The column is a column object in Mozilla 1.8 and later. In earlier versions, columns are identified with a string, the id of the column. In later versions, a separate column object exists, which can be queried for column data.

The following line is used so that the example above will work in all versions.

if (typeof column.value != "string") column.value = column.id;

If the column is a string, we are running on Mozilla 1.7 or earlier, but for later versions we get the column id from the column object. If you are writing code for multiple versions, you should check for this as above.

The last argument to getCellAt is the child type which is filled in with a string depending on what part of the cell the coordinate is at. If you move the mouse around in the previous example, you might notice the label change between 'text' and 'cell'. The value 'text' indicates the area where the text would be drawn and the value 'cell' indicates the area around the text, for example, the margin on the left side where the open and close twisties are normally drawn. If there was a twisty, however, the value would be 'twisty' instead. This is convenient since you could determine whether the user clicked on a twisty instead of another part of the row. In fact, this is what the underlying tree code does when the user double clicks the twisty. The final value that may be returned is 'image' if there is an image in the tree cell and the coordinate corresponds to a location where the image is. Of course, in many cases you may not care what part of the cell the coordinate is on and just want the row and column.

To go in reverse and get the cell at a specific coordinate, use the getCoordsForCellItem function. It takes seven arguments, as described below.

var x = {}, y = {}, width = {}, height = {};
tree.boxObject.getCoordsForCellItem( row, column, part, x, y, width, height );

The row, column, and part arguments are similar to those returned from the getCellAt function. Again, the column should be either a string or a column object depending on which version you are using. The cell part type may be used to get the coordinates of either the text, the entire cell, the twisty or the image in the cell. The same values as the getCellAt function are used. The getCoordsForCellItem function returns the x and y coordinates in addition to the width and height, all as out parameters.


(Next) Next, we'll look at RDF which can be used to automatically populate trees and other elements.

Examples: 8.6.1 8.6.2


Copyright (C) 1999 - 2004 XulPlanet.com