Modular interfaces
This is the second in a series of articles on aspects of designing XUL applications.
Displaying repetitive data in XUL interfaces, such as a list of people, a set of messages, or any other collection of things whose number may vary at runtime, is usually done by painstakingly creating those document parts with createElement() and placing them with appendChild() and insertBefore().
For example, to add an person item to a richlistbox representing an addressbook, one might have:
function addPerson(firstname, lastname, telephone) {
var xulPerson = document.createElement('richlistitem');
item.setAttribute('role', 'person');
item.setAttribute('orient', 'vertical');
var xulName = document.createElement('hbox');
var xulFirstName = document.createElement('label');
xulFirstName.setAttribute('role', 'firstname');
xulFirstName.setAttribute('value', firstname);
xulName.appendChild(xulFirstName);
var xulLastName = document.createElement('label');
xulLastName.setAttribute('role', 'lastname');
xulLastName.setAttribute('value', lastname);
xulName.appendChild(xulLastName);
xulPerson.appendChild(xulName);
var xulTelephone = document.createElement('label');
xulTelephone.setAttribute('role', 'telephone');
xulTelephone.setAttribute('value', telephone);
xulPerson.appendChild(xulTelephone);
document.getElementById('people').appendChild(xulPerson);
}
After calling:
addPerson('ford', 'prefect', '0042-42-42');
The richlistbox will look like:
<richlistbox id="people">
<richlistitem role="person" orient="vertical">
<hbox>
<label role="firstname" value="ford"/>
<label role="lastname" value="prefect"/>
</hbox>
<label role="telephone" value="0042-42-42"/>
</richlistitem>
</richlistbox>
That a function almost twenty lines long generates an XML fragment not longer than ten lines probably needs no comment.
From a wider perspective, taking structural information out of the XML document and putting it in the Javascript code means no longer having a single place where to look for it.
There is a very simple technique to address this.
- Let the document provide examples or “blueprints” of instances of repetitive data;
- tell the interface renderer not to show them;
- clone them as needed and insert the cloned elements in the right places.
The same example, reworked using this technique, is shown below.
Document:
<richlistbox id="people"/>
<box id="blueprints" hidden="true">
<richlistitem role="person" orient="vertical">
<hbox>
<label role="firstname" value=""/>
<label role="lastname" value=""/>
</hbox>
<label role="telephone" value=""/>
</richlistitem>
<!-- Other blueprints -->
</box>
Code:
function addPerson(firstname, lastname, telephone) {
var xulPerson = document.getElementById('blueprints')
.getElementsByAttribute('role', 'person')[0].cloneNode(true);
xulPerson.getElementsByAttribute('role', 'firstname')[0]
.setAttribute('value', firstname);
xulPerson.getElementsByAttribute('role', 'lastname')[0]
.setAttribute('value', lastname);
xulPerson.getElementsByAttribute('role', 'telephone')[0]
.setAttribute('value', telephone);
document.getElementById('people').appendChild(xulPerson);
}
(For a description of the technique used to access descendants of an element by their role see Better interface maintenance and addressing.)
Function body has become half the previous one and the description of the structure has gone back to the XML document.
Summary
Building more than trivial interface parts with DOM functions is time-consuming and disperses information about structure. Keeping “blueprints” in a specific document area and cloning them through cloneNode is quicker and keeps information about structure inside the document.
Related readings
- Better interface maintenance and addressing: First article in the XUL Patterns series.
- XUL Template Guide: Another approach for building interface parts for repetitive data.
Interesting solution, decupling render from data is always a wise solution. But cloning isn’t always sufficient, consider when different nodes must have set different attributes. For example node A but have attribute label and node B but have a class attribute. The business logic (ie the code) requires again to be customized.
In this application, cloning is not meant to isolate the code from changes in the interface, it is meant to provide a single point of reference for information about interface parts. When one modifies the interface, so does with the related code: the difference is that such changes are expressed in XUL and then propagated to the code, rather than expressed partly in XUL, partly in code, then propagated to the code.
The symmetry in the last example probably suggests unrelated benefits, I will try and make it clearer. Thanks for pointing this out!
In the example, you call getElementById with two parameters. That seems wrong; I assume this should be a different function call?
Good catch! An appendChild() was supposed to be there. Fixed.
Nice solution !
I like this technique.
A small note: there appear to be some typos on the XML, easily fixed.
Spotted. Thanks!
Post new comment