In this section, we'll find out how 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 later 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, you provide only the first element and the remaining elements are constructed based on the first one.
The template is created using the
element. Inside it, you can place the elements that you want to use for each constructed element. The template
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
element.tree
Simple Template Example
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 example will only get the top-level bookmarks (or bookmark folders) as we're going to create buttons. For child bookmarks, we would 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.
To view this example, you will need to create a chrome package and load the file from there (you can do this easily, see Manifest Files). You can then enter the chrome URL into the browser URL field.
var el = env.locale; Example 1 : Source
<vbox datasources="rdf:bookmarks" ref="NC:BookmarksRoot" flex="1"> <template> <button uri="rdf:*" label="rdf:http://home.netscape.com/NC-rdf#Name"/> </template> </vbox>
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
. This single button is used as a basis for all the buttons that need to be created. You can see in the image that the set of buttons has been created, one for each bookmark.button
Try adding a bookmark in the browser while you have the example window open. You'll notice that the buttons in the example get updated instantly. (You may have to focus the window for it to change.)
Container and Datasources
The template itself is placed inside a vertical box. The box has two special attributes that allow it to be used for templates, which 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. 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="http://www.some-fictitious-zoo.com/all-animals">
You can even specify multiple datasources at a time by separating them with 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 usually correspond to the value of an about
attribute on an RDF Bag
, Seq
or Alt
element.
Inside the Template
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 a uri
attribute and an unusual value for the label
attribute.
An attribute value inside the template that begins with 'rdf:' indicates that the value should be taken from the datasource. In the example earlier, this is the case for the label
attribute. The remainder of the value refers to the name property in the datasource. It is constructed by taking the namespace URL used by the datasource and appending the property 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 referred to. Here, we only use the name of the bookmark but numerous other fields are available.
The label
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:*" label="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 where content generation will begin. Content earlier will only be generated once whereas content inside will be generated for each resource. We'll see more about this when we get to creating templates for trees.
More Examples
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.
var el = env.locale; Example 2 : Source
<vbox datasources="rdf:bookmarks" ref="NC:BookmarksRoot" flex="1"> <template> <vbox uri="rdf:*"> <button label="rdf:http://home.netscape.com/NC-rdf#Name"/> <label value="rdf:http://home.netscape.com/NC-rdf#URL"/> </vbox> </template> </vbox>
This creates a vertical box with a button and a label for each bookmark. The button will have the name of the bookmark and the label will have the URL.
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.
You can also specify mutliple resource values in the same attribute by separating them with a space, as in the example below. More about resource syntax (XULPlanet).
var el = env.locale; Example 3 : Source
<vbox datasources="rdf:bookmarks" ref="NC:BookmarksRoot" flex="1"> <template> <label uri="rdf:*" value="rdf:http://home.netscape.com/NC-rdf#Name rdf:http://home.netscape.com/NC-rdf#URL"/> </template> </vbox>
How Templates are Built
When an element has a datasources
attribute, it indicates that the element is expected to be built from a template. Note that it isn't the template
tag that determines whether content is built, it is the datasources
attribute. When this attribute is present, an object called a Builder is added to the element which is responsible for building the content from the template. In JavaScript you can access the builder object with the builder
property, although usually you would only need to do this to have the builder regenerate the content in situations where it is not done automatically.
There are two different types of builders. The first is a content builder and is used in most situations, and the other is a tree builder which is used only for trees.
Content Builder
The content builder takes the content inside the template
element and duplicates it for each row. For instance, if the user had ten bookmarks in the example above, ten
elements would be created and added as children of the label
element. If you were to use DOM functions to traverse the tree, you will find these elements there and can query their properties. These elements get displayed, but the vbox
template
itself is not displayed, although it still exists in the document tree. In addition, the id
of each of the labels will be set to the RDF resource for that row.
The content builder always starts at the place where uri="rdf:*" is specfied. If the uri
attribute is placed on an element lower in the element tree, the elements outside are only created once. In the example below, one
will be created and it will be filled with a label for each item.hbox
<template> <hbox> <label uri="rdf:*" value="rdf:http://home.netscape.com/NC-rdf#Name"/> </hbox> </template>
If there is other content inside the element with the datasources
attribute but outside the template, that content will also appear. This way, you can mix static content and dynamic content from a template.
Tree Builder
The tree builder, on the other hand, doesn't generate the DOM elements for the rows. Instead, it gets the data directly from the RDF datasource whenever it needs it. Since trees are often expected to display thousands of rows of data, this is much more efficient. Creating an element for every cell would be too costly. However, the tradeoff is that trees may only display text, and, since no elements are created, you can't use CSS properties to style tree cells in the same way.
The tree builder is only used for trees. Other elements use only the content builder. This isn't a problem though, since other elements such as menus wouldn't be expected to display too many items. It's also possible to use the content builder for trees as well, and a
element and related elements will be created for each row.treeitem
Rules
In the image of the earlier example, you may have noticed that the third button is simply a button with hyphens on it. This is a separator 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 do is add a small amount of spacing instead of a button for separator resources. 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
element. We define a rule for each variation of elements that we want to have created. In our case, we would need a rule for bookmarks and a rule for separators. Attributes placed on the rule
rule
element determine which rules apply to which RDF resource.
When scanning for which rule applies to the data, each rule
element is checked in sequence for a match. That means that the order in which you define rules is important. Earlier rules will override later rules.
Rule Example
The following example demonstrates the earlier example with two rules:
var el = env.locale; Example 4 : Source
<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"> <vbox datasources="rdf:bookmarks" ref="NC:BookmarksRoot" flex="1"> <template> <rule rdf:type="http://home.netscape.com/NC-rdf#BookmarkSeparator"> <spacer uri="rdf:*" height="16"/> </rule> <rule> <button uri="rdf:*" label="rdf:http://home.netscape.com/NC-rdf#Name"/> </rule> </template> </vbox> </window>
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, the bookmarks datasource supplies a rdf:type
property 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 Description
element.
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 a
element, which will display a 16 pixel gap. 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.spacer
You should also have noticed that because we wanted to get an attribute from the RDF namespace (rdf:type
), we needed to add the namespace declaration to the
tag. If we didn't do this, the attribute would be looked for in the XUL namespace. Because 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.window
You should be able to guess what would happen if the second rule was removed. The result would be a single spacer 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
. Because you can just use your own attributes with your own namespace, 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.
Next, we'll look at using templates with trees.