XUL Tutorial - 6.6 - Templates
Previous Contents Reference Next

XUL Tutorial - Templates

In this section, we'll find out to populate elements with data.

Populating Elements

XUL provides a method in which we create elements from data supplied by RDF, either from an RDF file or from an internal datasource. Numerous datasources are provided with Mozilla such as bookmarks, history and mail messages. More details on these will be provided in the next section.

Usually, elements such as treeitems and menuitems will be populated with data. However, you can use other elements if you want although they are more useful for specialized cases. Nevertheless, we'll start with these other elements because trees and menus require more code.

To allow the creation of elements based on RDF data, you need to provide a simple template which will be duplicated for each element that is created. Essentially, it is like providing only the first element and the remaining elements are constructed based on the first one.

The template is created using the template element. Inside it, you can place the elements that you want to use for each constructed element. The template element should be placed inside the container that will contain the constructed elements. For example, if you are using a tree, you should place the template element inside a tree element.

This is better explained with an example. Let's take a simple example where we want to create a button for each top-level bookmark. Mozilla provides a bookmarks datasource so it can be used to get the data. This case will only get the top-level bookmarks (or bookmark folders) as we're going to create buttons. For child bookmarks, we need to use an element that displays a hierarchy such as a tree or menu.

This example and any others that reference internal RDF datasources will only work if you load them from a chrome URL. For security reasons, Mozilla doesn't allow access to them from other sources.
Example 6.6.1
<box datasources="rdf:bookmarks" ref="NC:BookmarksRoot"
    orient="vertical" flex="1">
  <template>
    <button uri="rdf:*" value="rdf:http://home.netscape.com/NC-rdf#Name"/>
  </template>
</box>

Here, a vertical box has been created that will contain a column of buttons, one for each top-level bookmark. You can see that the template contains a single button. This single button is used as a basis for all the buttons that need to be created. You can see in the image below that the set of buttons has been created, one for each bookmark.

[Image of generated bookmark buttons]

The template itself is placed insiide a vertical box. The box has two special attributes that allow it to be used for templates. These two attributes are used to specify where the data comes from.

The first attribute on the box is the datasources attribute. This is used to declare what RDF datasource will be providing the data to create the elements. In this case, rdf:bookmarks is used. You can probably guess that this means to use the bookmarks datasource. In this case, this datasource is provided by Mozilla. To use your own datasource, specify the URL of an RDF file for the datasources attribute, as indicated in the example below:

<box datasources="chrome://zoo/content/animals.rdf"
  ref="urn:animals:data">

You can even specify multiple datasources at a time, by separating them by a space in the attribute value. This can be used to display data from multiple sources.

The ref attribute indicates where in the datasource you would like to retrieve data from. In the case of the bookmarks, the value NC:BookmarksRoot is used to indicate the root of the bookmarks hierarchy. Other values that you can use will depend on the datasource you are using. If you are using your own RDF file as a datasource, the value will correspond to the value of an about attribute on an RDF Bag, Seq or Alt element.

By adding these two attributes to the box above, it allows the generation of elements using the template. However, the elements inside the template need to be declared differently. You may notice in the example above that the button has an attribute uri and an unusual value for the value attribute.

The unusual value attribute is simply a reference name that specifies the name of a bookmark. It is constructed by taking the namespace URL used by the datasource and appending the field name. If you don't understand this, try re-reading the last part of the previous section. It explains how resources in RDF can be refered to. Here, we only use the name of the bookmark but numerous other fields are available.

The value of the buttons is set to this special URI because we want the labels on the buttons to be set to the names of the bookmarks. We could have put a URI in any of the attributes of the button, or any other element. The values of these attributes are replaced with data supplied by the datasource which, in this case, is the bookmarks. So we end up with the labels on the buttons set to the names of the bookmarks.

The example below shows how we might set other attributes of a button using a datasource. Of course, this assumes that the datasource supplies the appropriate resources. If a particular one is not found, the value of the attribute will be set to an empty string.

<button class="rdf:http://www.example.com/rdf#class"
  uri="rdf:*"
  value="rdf:http://www.example.com/rdf#name"/>
  crop="rdf:http://www.example.com/rdf#crop"/>

As you can see, you can dynamically generate lists of elements with the attributes provided by a separate datasource.

The uri attribute is used to specify the element which will be used as the starting point of the template. We'll see more about this when we get to creating templates for trees.

By adding these features to the container the template is in, which in this case is a box, and to the elements inside the template, we can generate various interesting lists of content from external data. We can of course put more than one element inside a template and add the special RDF references to the attributes on any of the elements. The example below demonstrates this.

Example 6.6.2
<box datasources="rdf:bookmarks" ref="NC:BookmarksRoot"
    orient="vertical" flex="1">
  <template>
    <button uri="rdf:*" value="rdf:http://home.netscape.com/NC-rdf#Name"/>
    <button uri="rdf:*" value="rdf:http://home.netscape.com/NC-rdf#URL"/>
  </template>
</box>

The new elements that are created are functionally no different from ones that you put directly in the XUL file. The id attribute is added to every element created through a template which is set to a value which identifies the resource. You can use this to identify the resource.

Rules

In the image of the earlier example, you may have noticed that the fourth and third to last button are simply buttons with hyphens on them. These are the separators in the bookmark list. In the way that we have been using it, the RDF bookmarks datasource supplies the separators as if they were just regular bookmarks. What we would really like to have happen is that in the case of separators, a simple horizontal line is drawn. We don't want a button for these. That means that we want to have two different types of content be created, one type for regular bookmarks and a second type for separators.

We can do this by using the rule element. We define a rule for each variantion of elements that we want to have created. In our case, we would need a rule for bookmarks and a rule for separators. Each rule becomes a different case. Attributes placed on the rule element determine which rules apply to which RDF resource.

When scanning for which rule applies to the data, each rule element is checked for a match in sequence. That means that the order in which you define rules is important. Earlier rules will override later rules.

The following example demonstrates the earlier example with two rules:

Example 6.6.3
<window
  id="example-window"
  title="Bookmarks List"
  xmlns:html="http://www.w3.org/1999/xhtml"
  xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"  
  xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">

 <box datasources="rdf:bookmarks" ref="NC:BookmarksRoot"
    orient="vertical" flex="1">
  <template>

    <rule rdf:type="http://home.netscape.com/NC-rdf#BookmarkSeparator">
     <html:hr uri="rdf:*" width="100%" size="1"/>
    </rule>

    <rule>
      <button uri="rdf:*" value="rdf:http://home.netscape.com/NC-rdf#Name"/>
    </rule>
  
  </template>
 </box>

</window>

[Image of generated bookmark buttons with two rules]

By using two rules, we have allowed the contents of the template to be selectively generated. In the first rule, bookmark separators are selected, as can be seen by the rdf:type attribute. The second rule does not have any attributes so all data matches it.

All of the attributes placed on the rule tag are used as match criteria. In this case, we used the rdf:type attribute to distinguish separators. This attribute is set to a special value for separators in the RDF bookmarks datasource. This is how we can distinguish them from non-separators. You can use a similar technique for any attribute that might be on an RDF content Description element.

The bookmarks datasource adds an attribute of rdf:type onto each resource. The special URL value given in the example above for the first rule is used for separators. That means that separators will follow rule one and generate an HTML hr element, which will display a horizontal bar. Elements that are not separators will not match rule one and will fall through to rule two. Rule two does not have any attributes on it. This means that it will match all data. This is, of course, what we want to have happen to the rest of the data. You might have noticed that HTML elements are allowed inside rules.

You should also have noticed that since we wanted to get an attribute from the RDF namespace (rdf:type), we needed to add the namespace declaration to the window tag. If we didn't do this, the attribute would be looked for in the XUL namespace. Since it does not exist there, the rule will not match. If you use attributes in your own custom namespace, you need to add the namespace declaration in order to match them.

You should be able to guess what would happen if the second rule was removed. The result would be two separators displayed but no bookmarks because they don't match any of the rules.

Put simply, a rule matches if all of the attributes placed on the rule element match the corresponding attributes on the RDF resource. In the case of an RDF file, the resources would be the Description elements.

There are some small exceptions however. You cannot match based on the attributes id, rdf:property or rdf:instanceOf. Since you can just use your own attributes with your own namespace anyway, it probably doesn't really matter anyway.

Note that a template with no rules in it, as in the first example, is really equivalent functionally to a template with a single rule with no attributes.

Templates with Trees

We've seen how to create a list of buttons for the bookmarks. However, the bookmarks list would be more suitable when placed inside a tree, so that we can see the child bookmarks as well. Nothing special has to be done to use trees with templates. We just have to add the various tree elements in the right place. The example below shows how.

Example 6.6.4
<window
  id="example-window"
  title="Bookmarks List"
  xmlns:html="http://www.w3.org/1999/xhtml"
  xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"  
  xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">

  <tree flex="1" datasources="rdf:bookmarks" ref="NC:BookmarksRoot">
   <template>

    <rule rdf:type="http://home.netscape.com/NC-rdf#BookmarkSeparator">
      <treechildren flex="1" >
       <treeitem uri="rdf:*">
         <treerow>
           <treecell>
             <html:hr width="100%" size="1"/>
           </treecell>
         </treerow>
       </treeitem>
      </treechildren>
    </rule>

    <rule>
      <treechildren flex="1">
       <treeitem uri="rdf:*">
         <treerow>
           <treecell class="treecell-indent" value="rdf:http://home.netscape.com/NC-rdf#Name"/>
         </treerow>
       </treeitem>
      </treechildren>
    </rule>

   </template>
  </tree>

</window>

[Image of generated bookmarks in a tree]

We have used the same two rules again but this time inside a tree instead of a box. As before, each resource in the RDF datasource will be matched against the rules and elements created based on the matches. In this case, treeitem elements will be created for each element.

Here is where that uri attribute becomes useful. Notice how it has been placed on the treeitems in the example, even though they are not the direct descendants of the rules. We need to put this attribute on only those elements that we want repeated for each resource. Since we don't want multiple treechildren elements, we don't put it there. Instead we put the uri attributes on the treeitem elements. Effectively, the elements outside (or above) the element with the uri attribute are not duplicated whereas the element with the uri attribute and the elements inside it are duplicated for each resource.

Note in the image that additional child elements below the top-level elements have been added automatically. XUL knows how to add child elements when the templates or rules contain tree elements or menu elements. It will generate tree elements as nested as the RDF data contains.

An interesting part of RDF datasources is that the resource values are only determined when the data is needed. This means that values that are deeper in the resource hierarchy are not determined until the user navigates to that node in the tree. This becomes useful for certain datasources where the data is determined dynamically.

We could just as easily create trees with multiple columns by adding more treecell elements with-in each rule. We would need to add the same number of cells in each rule. For separators, we would put a horizontal line in each cell.

Interestingly, we are actually getting close to how the bookmarks window is actually created in Mozilla. All we need to do is add some additional fields, a menu bar and make some additional cosmetic changes.

Additional Rule Attributes

There are two additional attributes that can be added to the rule element that allow it to match in certain special circumstances. Both are boolean attributes.

The two attributes above are not really the reverse of each other. A resource might be a container and be an empty one as well. However, this is different from a resource that is not a container. For example, a bookmark folder is a container but it might or might not have children. However a single bookmark or separator is not a container.

You can combine these two elements with other attribute matches for more specific rules.


(Next) Next, we'll look at some of the provided datasources provided by Mozilla.

Examples: 6.6.1 6.6.2 6.6.3 6.6.4

XUL Tutorial - 6.6 - Templates
Previous Contents Reference Next