10.4. Template Dynamics

Once you learn how to create templates and modify datasources, the ultimate in template mastery is to apply datasources to a template dynamically.

This process is done through the database property of a XUL element that contains a template. The object returned by this property has only two methods, AddDataSource and RemoveDataSource. A separate builder.rebuild function is also available for refreshing the template's display, but you probably won't need it once the template automatically updates itself. The addition and removal of a datasource to a <tree> template is demonstrated here:

tree = document.getElementById('tree-template');
tree.database.AddDataSource(someDatasource);
// tree will now update its display to show contents
tree.database.RemoveDataSource(someDatasource);
// tree will now be empty
// Optional, use only when tree is not updating for some reason
tree.builder.rebuild( );

You can add and remove any datasource as long as the template actually matches the data inside it. Also, multiple datasources can be applied to the same template with no problems, which allows you to aggregate data from different places, such as contact data, work information, and computer hardware information (e.g., "Eric uses a Compaq with the serial number 1223456-1091 to write his book and he sits on the fourth floor of the Acme Building, which is the Bay Area branch of Acme Enterprises.)

10.4.1. Template Dynamics in XBL

Putting templates inside XBL can be a useful organizational scheme. Here is a basic implementation of a widget that creates a list of people based on names listed in an attribute:

<people names="Brian King,Eric Murphy,Ian Oeschger,Pete Collins,David Boswell"/>

Obviously, the comma is used as the delimiter for this list. The constructor element in Example 10-11 uses JavaScript to break up this string.

Example 10-11. Binding with in-memory datasource and <listbox> template

<?xml version="1.0"?>
<bindings xmlns ="http://www.mozilla.org/xbl"      
xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
  <binding id="people">
    <implementation>
      <constructor>
      <![CDATA[
        // Read the Names into an Array
        names = document.getAnonymousNodes(this)[0].getAttribute('names');
        names = new String(names);
        namesArray= names.split(',');
        // Initialize the RDF Service
        rdf = Components
             .classes['@mozilla.org/rdf/rdf-service;1']
             .getService(Components.interfaces.nsIRDFService);
        // Initialize a Datasource in Memory
             inMemory = '@mozilla.org/rdf/datasource;1?name=in-memory-datasource';
        datasource = Components.classes[inMemory].
           createInstance(Components.interfaces.nsIRDFDataSource);
        // Create the Root Node and an Anonymous Resource to Start With
        root   = rdf.GetResource('urn:root');
        people = rdf.GetAnonymousResource( );
        // Insert the People resource into the RDF graph
        datasource.Assert
          (root,
           rdf.GetResource('http://www.mozdev.org/rdf#people'),
           people,true);
        // Initialize Methods needed for Containers
        rdfc = Components
              .classes['@mozilla.org/rdf/container-utils;1']
              .getService(Components.interfaces.nsIRDFContainerUtils);
        // For the People resource, make a Sequence of people
        peopleSequence = rdfc.MakeSeq(datasource, people);
        for(i=0;i<namesArray.length;i++)
        {
          // Create a Person, with a Unique Number, for example
          person = rdf.GetResource(i);
          // Insert the Person's name into the RDF graph underneath number
          datasource.Assert
            (person,
             rdf.GetResource('http://www.mozdev.org/rdf#name'),
             rdf.GetLiteral(namesArray[i]),true);
          peopleSequence.AppendElement(person);
        }
        list = document.getAnonymousNodes(this)[1];
        list.database.AddDataSource(datasource);
      ]]>
      </constructor>
    </implementation>
    <content>
      <xul:box id="names" inherits="names" flex="0"/>
      <xul:listbox datasources="rdf:null" ref="urn:root" flex="1">
        <xul:template>
          <xul:rule>
            <xul:conditions>
              <xul:content uri="?uri"/>
              <xul:triple subject="?uri"
                       predicate="http://www.mozdev.org/rdf#people"     
                         object="?people"/>
              <xul:member container="?people" child="?person"/>
              <xul:triple subject="?person"
                       predicate="http://www.mozdev.org/rdf#name"
                         object="?name"/>
            </xul:conditions>
            <xul:action>
              <xul:listitem uri="?person">
                <xul:listcell>
                  <xul:description value="?person "/>
                  <xul:description value="?name"/>
                </xul:listcell>
              </xul:listitem>
            </xul:action>
          </xul:rule>
        </xul:template>
      </xul>
    </content>
  </binding>
</bindings>

In Example 10-11, everything you need to display a datasource dynamically is present. The only difference between this dynamically generated version and a static RDF-based template is the datasources="rdf:null", which specifies that the template does not refer to an actual datasource. Data that is edited, rearranged, or changed in a different way is often displayed dynamically in the UI with templates in this manner.