Neil's Place

September 5, 2005

10:29 PM How Templates Work XLI - Sorting Tree Results

Using a tree builder, you can sort the results in a tree by a column. To do this, place a sort attribute on a <treecol> element referring to the variable to sort by for that column.

<treecol id="name" label="Name" sort="?name" flex="1"/>
<treecol id="date" label="Date" sort="?date" flex="1"/>

In this example, the first column will be sorted by the ?name variable and the second column by the ?date variable. When the sort is ascending, the tree rows will be sorted in alphabetical order. When the sort is descending, the tree rows will be sorted in the reverse order. For natural sorting, the rows will be sorted according to the natural order in the RDF datasource. Only one column applies a sort at a time. If the tree is sorted by name, and the user clicks on the date column header, the sort will change to the date column.

There are two additional attributes used for sorting, which you may set on a column to specify the initial sort. These attributes are modified when the user changes the sort. The sortDirection attribute may be used to specify the initial sort direction for a column. Only one column should have this attribute set, as a tree may only be sorted by one column at a time. The value should be either 'ascending', 'descending' or 'natural'. This last value is the default if the attribute is not specified. The sortActive attribute may be set to true or false and specifies which column the tree is sorted by. Only one column should have the sortActive attribute set to true at a time. The tree will change both attributes as necessary automatically when the column headers are clicked or the tree is sorted by other means.

If you don't want to allow sorting by a certain column, you can leave out the sort attribute. Only specify this attribute on columns that you wish to allow the user to sort by.

Here is a complete example of sorting a tree.

The sort attribute should be set to the variable that holds the values to sort by. Usually, this would be the same variable that is used to generate the label for the cells in that column, however this is not actually necessary. For instance, in the example the second column sorts by date, but if you were to use a different variable such as ?description, assuming a <binding> set it, the tree would sort by the value of the description variable for each row. In almost all situations however, you would normally sort using the same variable used for the label value. However, one situation where this is not desirable is if the displayed values would not generate the correct order as there is a lower representation that is more accurate. For example, the date 'May 15' would appear after 'August 24' when sorted purely alphabetically but before it when sorted chronologically.

Another way to sort by dates is to use the the parseType="Date" construct in the RDF datasource. This marks a literal as being a date value rather than a string. The builder will recognize this and sort chronologically instead. This also has the advantage that the dates will be displayed according to the user's current locale (meaning that the date is formatted so as to be suitable for the user's language). Here is a sample of how to specify this in the RDF/XML datasource:

<rdf:RDF xmlns="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
     xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
     xmlns:r="http://www.xulplanet.com/rdf/"
     xmlns:nc="http://home.netscape.com/NC-rdf#">
  <rdf:Description rdf:about="http://www.xulplanet.com/ndeakin/images/t/palace.jpg">
    <r:date nc:parseType="Date">1125966767295<r:date>
  </rdf:Description>
</rdf:RDF>

You can also specify parseType="Integer" for numbers which will allow sorting numerically. By specifing different types for different values, you can sort alphabetically, numerically or by date.

If you are using the simple rule syntax, there are no variables, so you need to specify the full predicate including the rdf: prefix in the sort attribute. For instance:

<treecol id="name" label="Name" sort="rdf:http://purl.org/dc/elements/1.1/title" flex="1"/>

Note that all of this discussion about sorting only applies to tree builders. For other elements or content trees, a different sorting mechanism must be used which will be discussed next.

Comments ( 5 )

August 28, 2005

5:46 PM How Templates Work XL - Sorting Results

The template content builder uses a separate component to insert generated nodes into the content tree. This is done when inserting the nodes when they are first created as well as when a new result is available. This additional component is called the sort service. It is responsible for determining where to insert nodes into the XUL document. Since the component is called the 'sort service' it is also used to sort the generated results. Since an RDF graph doesn't specify any order to results -- unless the items are in an RDF Seq -- the template builder will handle the results in any order. You may have noticed in the examples that results that are not in a Seq are not output in any particular order.

The sort service may be used to order the results in some particular order, generally, ascending or descending based on the value of some predicate pointing out of the result node. The sort service also supports a third sort order, natural order, which is the default. It causes items to appear without any extra sorting in the order they are added. However, if the results are items in a Seq they will appear in the order listed in the Seq. For instance, the photos are listed in the same order in this example as they appear in the Seq in the datasource.

This method of sorting a Seq works best for simple rule conditions since it is obvious how the starting ref relates to the end member results (they are just the children), or for extended syntax rules that follow a similar pattern. For more complex rules, this natural sorting will not work, because the sort service assumes that the starting ref resource is the container and the end results are the children. In this case, the natural order of the results will just be the order that the template builder generates the results.

For ascending or descending sorts, this doesn't matter, since it will ignore whether results are containers and just sort by a value, alphabetically or numerically depending on the type of data.

The sort service only applies to content builders. The tree builder uses a different and much simpler means of sorting since there is no content to insert. It supports the same three types of sorting, natural, ascending or descending. In the latter two sort types, the tree builder sorts by the value in a column. For instance, if the photos were displayed in a two column tree showing the title and description, you could sort by either title or description. The user can change the sort column and direction by clicking the column headers, however, you can programmatically change the sort as well.

Next, we'll look at some examples of tree sorting. We'll examine trees first because they are much simpler to handle, and you will usually want to support sorting on trees.

Comments ( 0 )

August 26, 2005

4:20 PM How Templates Work XXXIX - More on RDF Modifications

Often, a new RDF triple is created in the datasource which would only affect a template rule's bindings. Since the bindings section of a rule specifies conditions that may optionally match, the addition or removal or this RDF data would never be able to add or remove a new result. At the very most, the change would cause a label to be filled in with a value, or cleared when removing an RDF triple.

As described earlier, the builder first scans the conditions part of a rule to see if it would cause a change. After this, the bindings are examined. This is done whether the conditions produced a new result, removed one, or the content was not affected, since a binding could have affected any existing results. It's possible, for instance, for every existing row to be affected by a single triple being added to the datasource. Consider the following binding:

<binding subject="?start"
            predicate="http://www.xulplanet.com/rdf/categoryName"
            object="?name"/>

This binding involves a triple pointing out from the starting variable that has been used in these examples. The value for this binding will be the same for every result, so if the category name changes, every result will need to change. However, the builder can use a much simpler process for recalculating the results. Instead of regenerating the content for a result, the builder just looks for attribute values that involve the ?name variable. Those attributes are just recomputed, substituting the new value for ?name instead. This process is repeated for each result that would be affected.

When a template involves multiple rules, the same process is used for each rule as with one rule. As when generating the results initially, only the highest matching rule needs to be applied. The only extra complication to deal with in the multiple rule case is when a particular result's member resource already matches a rule, yet the new RDF triple would cause an earlier rule to match. Since the earlier rule takes precedence, the builder handles this by removing the old content first and then adding the new content.

Next we will look at how the template builder determines where to insert content.

Comments ( 0 )

August 22, 2005

6:55 PM XULPLanet Status

By the way, in case you hadn't noticed, XULPlanet has returned after a brief hiatus. If you're following the template guide, you may have missed the most recent part.

In other news, the element reference has been updated with new information for Mozilla 1.8/Firefox 1.5.

Comments ( 0 )

August 18, 2005

8:03 PM How Templates Work XXXVIII - Adding RDF Triples

Let's say we've just added the triple below to the datasource.

subject: http://www.xulplanet.com/ndeakin/images/t/obelisk.jpg
predicate: http://purl.org/dc/elements/1.1/description
object: One of the thirty or so Egyptian obelisks

The template builder will be notified through the RDF observer mechanism of the change. The template builder will need to check all of the rules to see if this triple could cause a change in what would be displayed. If the triple wouldn't cause any change in the output, the builder won't make any changes. If the output would change, the builder will need to adjust the output, either by adding a new result, removing an old result, or by changing the value of some part of the result. The builder is smart enough to only change what needs to be changed and leave the remaining parts alone. Let's assume we have single rule with conditions as follows:

<conditions>
  <content uri="?start"/>
  <member container="?start" child="?photo"/>
  <triple subject="?photo"
             predicate="http://purl.org/dc/elements/1.1/title"
             object="?title"/>
  <triple subject="?photo"
             predicate="http://purl.org/dc/elements/1.1/description"
             object="?description"/>
</conditions>

These conditions will cause any photos with both a title and a description to be displayed. Assuming that the 'obelisk' photo doesn't have a description already, adding the triple listed above should cause a new result to be available for this photo. The builder scans through the conditions one by one.

The <content> tag can safely be skipped at this part of the process, so the builder moves onto the <member> condition. This type of condition can only cause a change when an item is being added or removed from a container. Since this is a new RDF triple that isn't an addition or removal from a container, this condition can be skipped. Effectively, if the result generation process was to evaluate this member condition, the same output would be supplied for the ?photo variable whether the new data is there or not. Thus, the member condition can be skipped.

The next condition is a <triple> involving the "http://purl.org/dc/elements/1.1/title" predicate. We aren't adding a arc involving this triple so we can ignore this conditon as well. The second triple, however, could cause a change, since the predicate attribute matches the predicate being added. The subject and object are variables so the builder accepts this as a possible change, and moves on to the next step. If the predicate was different, the builder would come to the end of the conditions and could just stop there. For instance, if the predicate of the triple being added was "http://purl.org/dc/elements/1.1/date", the builder could ignore it since the template doesn't even care about the date field. Similarly, if the triple didn't use a variable but a static value, this value would also need to match in order to continue processing.

Now that we know the conditions could cause a change in the template, the second step is to fill in the variables for this condition for what could potentially be a new result. In this situation, it fills in the ?photo and ?description variables using the values from the newly added triple.

(?photo = http://www.xulplanet.com/ndeakin/images/t/obelisk.jpg,
 ?description = 'One of the thirty or so Egyptian obelisks')

Next, the builder works its way backwards through the rules, in order to fill in the remaining variables. It does this in a similar manner as it does when it generates results, but traverses the rules in the opposite order. The previous triple will fill in a value for the ?title variable, since we now have a value for the ?photo variable referred to by the triple's subject attribute. Next, the <member> condition is examined, and, in this situation, the builder fills in the known ?photo variable, and looks for a parent container containing this value. There is a container "http://www.xulplanet.com/rdf/myphotos", so the ?start variable will be filled in with this value. Now, the potential result so far is:

(?photo = http://www.xulplanet.com/ndeakin/images/t/obelisk.jpg,
 ?description = 'One of the thirty or so Egyptian obelisks',
 ?start = http://www.xulplanet.com/rdf/myphotos,
 ?title = 'Obelisk')

As you can see, the result looks to have all the information necessary to create a new item in the output. If a condition hadn't generated a result, for instance if the photo did not have a title, or it wasn't contained in a parent container, there would be no match and the builder could stop processing the new triple. For instance, we might have added a description for a new photo, but haven't added the photo to the container resource. Once we do add it to the container with another RDF assertion, the process described above is applied again and this time it may match.

There are still two more things to do before a result is accepted as a new match. First, once the builder reaches the <content> condition, it checks what the container or reference variable is, in this case ?start, as specified by the uri attribute. The calculated value for the potential new match is "http://www.xulplanet.com/rdf/myphotos". The builder looks to see if this resource is being used as the stating point in the template. As it happens, this resource is being used, since it is the value of the ref attribute we've been using in these examples. This would also be the case for any starting points used in recursive generation. If the calculated ?start variable was something different, naturally we don't need to change the template output, as that resource isn't being used in a template.

Finally, the builder processes any conditions below the one we started at, in order to fill in any remaining variables. In this case, there are no other conditions, so the builder accepts this result as a new match. Since all the variables have been filled in, the action body for the rule can be processed and a new block of content generated and inserted into the output. We'll find out how the builder determines where to insert the new content is an upcoming section. However, this does show that the template builder can update the output upon changes without rebuilding the entire template.

When an unassertion occurs, or data is removed from the datasource, a different process is used. In this case, the builder looks at the results and determines which ones to remove. When it had first generated the results, the builder stored extra information to specify what parts of the graph were navigated over. It uses this information to help determine what results are no longer needed.

We'll look a bit more at how RDF changes affect the template next.

Comments ( 1 )

August 17, 2005

8:32 PM How Templates Work XXXVII - RDF Modifications

The third type of observer involved in a template builder is an nsIRDFObserver. The template builder implements this interface to listen for RDF modifications. When the datasource is modified, the datasource will notify any observers of the change. The template builder uses these notifcations to update the template as necessary based on the new or removed information. You don't need to implement this observer yourself, although you may add an observer to the datasource if you want to be notified when the data changes.

There are two main situations when the notifications are made. The first is when the modification functions on the datasource are called. There are four such functions: 'Assert', to add a new triple (or arrow) to the RDF graph, 'Unassert' to remove a triple, 'Change' to adjust the target of a triple, and 'Move' to adjust the source of a 'triple'. For Mozilla's datasources, the latter two just Unassert the old triple and add a new one, creating the effect of changing the value. However, only one notification is made.

For instance, an Assert call looks like the following:

var source = RDF.GetResource("http://www.xulplanet.com/ndeakin/images/t/obelisk.jpg");
var predicate = RDF.GetResource("http://purl.org/dc/elements/1.1/description");
var target = RDF.GetLiteral("One of the thirty or so Egyptian obelisks");
datasource.Assert(source, predicate, target, true);

The Assert call adds a new triple to the RDF datasource. When this happens, any templates observing the datasource will be notified via the RDF observer's onAssert method.

The second situation when notifications are made is when a datasource is being loaded or reloaded. Actually, internally, this isn't any different than the other notifications, but it is worth discussing separately. When the RDF parser loads RDF/XML, it starts with a new empty datasource, and as the parser parses the input data, it calls the datasource's Assert function to add each found triple. In effect, this isn't any different than adding the same set of triples yourself using the Assert method.

When reloading a datasource, you might think that the RDF parser removes all the existing data, loads the new data, and adds it to the datasource. Or, you might think that it creates a fresh datasource with the new data. Actually, the parser does something smarter. When reloading a datasource, it keeps the existing RDF triples intact, and only modifies the datasource based on what has changed. When parsing, any triples that already exist are not added again. If a triple does not exist yet, it will be added. Any triples that don't exist in the new data but were there before are removed. This means that the observer will be called only for the triples that differ between the new and old version of the data. If the reloaded datasource hasn't changed, the builder won't receive any notifications. This saves a lot of extra work.

The RDF observer also has two methods onBeginUpdateBatch and onEndUpdateBatch. These are called when performing a lot of operations on a datasource. When changing the datasource, the changes are surrounded by begin and end batch calls. Then, rather than notify on every change, the datasource will send one notification when the changes are finished. The template builder then rebuilds the template completely when done. This is useful when making a large amount of changes to avoid having to keep recalculating parts of the template that might change again quickly.

We'll look at some specifics of how the template builder handles changes next.

Comments ( 0 )

August 15, 2005

7:46 PM How Templates Work XXXVI - Tree Builder Listeners

The second type of listener is used to handle particular actions related to trees. The tree builder implements the nsITreeView interface, so handles the gathering of data and passing it on to the tree. The tree widget informs the view when certain operations are performed that might affect the data. The tree view handles all of these operations, but allows an observer to be attached which is invoked during these operations. For instance, the observer may have an onToggleOpenState method which will be called when the user opens or closes a row. The tree builder will handle the adding or removing of rows, but will call the observer so that it can perform some task.

The tree builder observer implements the nsIXULTreeBuilderObserver interface and may be attached to a tree builder using the builder's addObserver method. You can add more than observer if needed, and can remove them again with the builder's removeObserver method.

The observer is always invoked before the appropriate operation is performed. For instance, the onToggleOpenState method of any observers will be called before the tree item is opened. After the observers have finished, the tree builder opens the row and adds any child rows inside. Note that you cannot cancel the operation from within the observer.

Some useful functions of the observer are the drag and drop related callbacks to handle when an item is dragged onto the tree. This makes handling dragging onto a tree fairly simple. All you need to do is implement two methods, canDrop and onDrop. Note that in Firefox 1.0 (Mozilla 1.7) and earlier, the drag functions are slightly different. There, three functions are used, canDropOn, canDropBeforeAfter and onDrop. The two 'can' functions were combined into one with an extra argument. If you want to support both earlier and newer releases, you can implement all of the functions in the observer, sharing code as necessary.

The tree observer receives drag related events in three places: over a container row, before a row, and after a row. This allows you to handle dragging with more flexibility. For example, in some situations you may want to require dragging onto a folder type of row. In other situations, you may wish to allow items to be dragged between (before or after) rows. This would be the situation if you were dragging items from that tree around, for instance dragging a bookmark from one location to another. The tree widget will draw a small line between the rows while dragging. All you need to do is add a tree builder observer which returns true for the canDrop method. Note that the 'drag on' case only allows dragging onto containers, not ordinary rows.

var treeBuilderObserver = {
  canDropBeforeAfter : function(idx, orient) { return false; },
  canDropOn : function(idx, orient) { return true; },
  canDrop : function(idx, orient) { return !orient; },
  onDrop : function(idx, orient) {
    // do something here
  },
};
tree.builder.addObserver(treeBuilderObserver);

This observer implements both the older and newer methods and only allows dragging on rows. The canDropBeforeAfter method returns false since we do not want to allow before and after drops. The canDropOn method returns true however. The Mozilla 1.8 method canDrop checks the orientation and returns the opposite. This works as the 'on' value is 0 and the 'between' values are -1 and 1. Obviously, this code is much simpler than what we would really want to use -- we should be checking what is being dragged to make sure that it is compatible with the tree. Or, we might want to allow dropping on specific rows only; the drop methods are supplied with an index argument so we can check for this.

Comments ( 1 )

August 14, 2005

12:07 AM How Templates Work XXXV - Template Listeners

There are several listeners (or observers) used during the template build process, each used for different purposes. These each implement a different XPCOM interface, as listed below:

The first of these is the simplest and involves two methods, willRebuild and didRebuild. You would implement this object with these two methods if you wish to be notified when the template is rebuilt using the builder's rebuild call. The template builder might also force a rebuild when the underlying data change notifications require it. The primary use of this listener is to store some state before the template is rebuilt and restore it afterwards. Recall that when a template is rebuilt, all of the existing content will be removed and generated fresh. The willRebuild method of any listeners will be called before the content is removed, and didRebuild method will be called when the content has been regenerated. This listener will also work for tree builders, and will call the appropriate methods before and after the tree has been generated.

To assign a builder listener to a builder, use the addListener method.

var someListener = {
  item: null,
  willRebuild : function(builder) {
    this.item = builder.getResourceAtIndex(builder.root.currentIndex);
  },
  didRebuild : function(builder) {
    if (this.item) {
      var idx = builder.getIndexOfResource(this.item)
      if (idx != -1) builder.root.view.selection.select(idx);
    }
  }
};
tree.builder.addListener(someListener);

This example is very simple and just saves and restores the selected index after a rebuild. Since the content goes away during a rebuild, the selection is lost, so it is restored here during the didRebuild method. The Firefox bookmarks window uses this technique. If you try an example using the code above, you will notice that the first tree will maintain the selection when the Rebuild button is pressed, whereas in the second tree does not. This is because the listener is only attached to the first tree.

The example above makes use of the getResourceAtIndex and getIndexOfResource methods. These two methods are available for tree builders and will convert between an index in the tree and the associated member resource for the item at the index. Naturally, we can't store the index as the item may have moved its position. Or, the resource may no longer be part of the results, which is why we need to check the return value of the getIndexOfResource method. (As this example uses the RDF resources directly, it requires elevated privileges so you will need a chrome URL to test it.)

You might also guess that the builder's root property, which is used above, refers to the tree. In a content builder, it will return the element with the datasources attribute, which in the template builder is referred to as the root element.

Finally, you can remove a listener using the builder's removeListener method.

Comments ( 0 )