Fooling with XUL
|
Table of Contents |
|
The Finished Product |
With the interest and excitement surrounding Mozilla's approaching beta release, I decided it was time to put it through its paces. For a while now we have heard and seen presentations of XUL, the Extensible User Interface Language, which forms an integral part of Mozilla. Delegates at XTech 2000 were clearly excited by the project and its promise for delivering a richer user interface on the Web.
As far as buzzword-compliance goes, Mozilla boasts a host of W3C specifications, including XML, DOM, CSS, and RDF. I wanted to find out how all these bits fit together in reality, and how practical client-side development with Mozilla actually is. I attacked the problem in my favorite way, by just jumping in with an idea I wanted to implement.
One of the fun parts of the Mozilla browser is the sidebar. Working in a similar way to the so-called "Explorer" bars in Microsoft's browser, the sidebar allows for handy access to components such as bookmarks, search, and "What's Related?" I decided that a shortcut interface to the XMLhack site would be a reasonable-sized learning project to undertake.
Working from back to front, I'll show the finished product and
then demonstrate how I got there. The image on the right shows a
list of articles with icons, which when double-clicked, cause the
browser to navigate to the complete article on the web site.
The first step was to learn how to construct the user interface. Of course, I could have done this in straight HTML rather than using XUL, but I wanted to use the experience to learn about the Mozilla browser, and also for the user to feel that the component was more tightly integrated with the sidebar. Fortunately, XUL is one of the better-documented parts of the Mozilla browser, and I used Neil Deakin's XUL Tutorial as my starting point. This is an excellent document, although still a work in progress.
It is beyond the scope of this article to present a XUL
tutorial, but I will mention the key concepts that are
required. User interface descriptions in XUL usually span four
files: the layout (.xul) file, the CSS style sheet, scripts, and
localization information. For my experiments, I simply used the
default styles and omitted localization, so I only had to
consider two files: the .xul file itself, and a file for my
JavaScript.
Working through the XUL tutorial, I grasped the fundamentals of
creating an interface in XUL. Each .xul file
describes a window that contains widgets. The minimal XUL
file looks like this:
<?xml version="1.0"?>
<?xml-stylesheet href="chrome://global/skin/" type="text/css"?>
<window id="xmlhack-window" title="XMLhack Headlines"
xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
>
</window>
Mozilla makes extensive use of XML Namespaces, hence the
somewhat baroque URI denoting that window and its
contents have the XUL default namespace. The browser can
interpret elements from other vocabularies (such as HTML and
RDF) within XUL documents. These can be included in XUL descriptions by using
namespace-qualified names.
Another thing of note to point out is the
chrome:// URL scheme. This scheme allows Mozilla to
find localized resources for its user interface. If you look in
your Mozilla install directory, you will find the
chrome directory, beneath which are the XUL
descriptions for the user interface. Mozilla interprets the
chrome URL to find the actual file it wants,
adjusting for such things as the current locale.
So, to get going, I added some simple widgets to my skeleton
user interface, following the
"Find Files" example from the XUL tutorial. I placed the
XUL file on my web server, pointed Mozilla at
http://localhost/test.xul and got back the text
contents of the file in the browser, rather than a cool user
interface. Oh dear.
It turns out that a little bit of configuration to your web
server is required in order to get Mozilla to recognise XUL
files. In particular, XUL currently uses the Internet Media Type
text/xul. I simply added this line to my Apache's
mime.types file, which fixed the problem:
text/xul xul
That issue resolved, I continued with the tutorial and had soon created a pleasing user interface. Not everything worked quite as smoothly in the browser as it did in the tutorial though! Some of the widgets aren't properly implemented yet in Mozilla, and it can vary from platform to platform. For instance, I was able to get context pop-up menus ("right-click" menus) working on Windows, but not on Linux.
Arrangement of widgets in a XUL interface is done primarily by use of boxes. Readers au fait with the TeX typesetting system (or many other layout systems) will already be familiar with the box method. Boxes can have their contents stacked either horizontally or vertically. Layouts can be constructed by a combination of these boxes.
Each UI element has a flex attribute, which governs how
it will stretch to fill the space available. There is also a
spring element, which is an empty box you can use
to put space in between other UI elements. This example shows
how two pushbutton widgets and a spacer would be written:
<titledbutton class="push" value="OK"
style="min-width: 100px;" flex="1"/>
<spring flex="1"/>
<titledbutton class="push" value="Cancel"
style="min-width: 100px;" flex="1"/>
As the window containing these elements was expanded, each
button and the spring would all enlarge to consume the new space
in equal measure, owing to their having identical
flex values. Note the use of CSS styles to specify
minimum widget dimensions.
It's much more interesting to play with this for yourself than to have me recount all the ins and outs, so I recommend pursuing the exercises in the XUL Tutorial.
A collection of widgets is all very well, but not very useful
without something actually happening when they are
activated. This is achieved through the use of JavaScript. In a
similar way to HTML, a XUL document is exposed to the browser
via a DOM, which can be scripted. Event handlers such as
onClick can be specified as attributes of interface
elements, much as in HTML.
Although script can be included inline in XUL with the
<html:script> element, the documentation
advises against it. Instead, script should be stored in a
separate file, and referenced like this:
<html:script src="myScript.js"/>
It is relatively simple to get started with writing scripts. Here's a quick example. Imagine a window with this button inside:
<titledbutton class="push" value="Hello"
onclick="sayHello();"/>
... and this JavaScript:
function sayHello() {
alert("Hello, World!");
}
More complex operations can be achieved through the use of the
DOM. XUL provides two extension functions to the XML DOM:
getElementById and
getElementsByAttribute (see Introduction to XUL on Mozilla.org). Extensive use of the ID attribute
on user interface elements is made within interface
descriptions, in order to facilitate easy scripting and
modification of the interface. The interface's DOM can be
modified by scripts, enabling such things as the dynamic
alteration of style and content of widgets.
|
Table of Contents |
|
The Finished Product |
I had reached the point of defining what I wanted my interface to look like. I needed to use the tree widget to show my list of headlines and so roughed out an interface description. You can download the full XUL file here, but here's an extract demonstrating the tree widget:
|
<tree style="height: 100%; width: 100%" flex="2">
<treehead>
<treerow>
<treecell value="Headlines"/>
</treerow>
</treehead>
<treecol/>
<treechildren>
<treeitem>
<treerow>
<treecell
src="chrome://bookmarks/skin/bookmark-item.gif"
value="Story 1"/>
</treerow>
</treeitem>
...
</treechildren>
</tree>
Notice that I used the browser's bookmark icon at this point
instead of my own. This is one of the big advantages of Mozilla's
open approach: the actual interface components themselves
can be used for learning. In particular the
chrome/bookmarks/content/default/bm-panel.xul file
proved a great source of information in my experiment. This is
the interface description for the sidebar bookmarks panel.
However, I did not want to create the .xul file
each time with different contents; rather I wanted to be able to merge a
data file with the XUL layout, in much the same way I would
merge XML with XSLT to generate HTML. The way Mozilla handles
this is to use RDF datasources. Arbitrary data, described
in RDF, can be used to populate user interface elements.
Yes, I said "RDF." For one reason or another this specification has acquired a reputation as being unwieldy and difficult to understand. The good news is, you don't need to understand everything in order to be able to use it. I started from the point of an example RDF file, and with a little RDF knowledge, created what I needed for my purposes.
So here's the RDF primer: RDF is basically a way of describing things. The "things" are known as resources. A resource is associated with a URI. Here's an example description of me:
<RDF:RDF xmlns:RDF="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:ORN="http://www.oreillynet.com/rdf#">
<RDF:Description about="urn:oreillynet:editors:edd">
<ORN:Email>edd@xml.com</ORN:Email>
<ORN:Site>http://xml.com/</ORN:Site>
</RDF:Description>
</RDF:RDF>
RDF provides constructs such as Description,
Seq (sequence), and Alt (alternatives)
to make describing resources easier and more portable. In the
above example, I simply invented the
http://www.oreillynet.com/rdf namespace to contain my
Email and Site properties.
In practice there are some established vocabularies for
metadata, such as Dublin
Core, which you can use to improve the interoperability of
your descriptions (see also Using the Dublin Core with RDF).
For my XMLhack project, the next task was to create an RDF description of all the headlines on the front of the web site. I constructed a small vocabulary to describe each story. Here's an extract for one story:
<RDF:RDF xmlns:RDF="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:s="http://xmlhack.com/stories/rdf#">
<RDF:Description about="urn:xmlhack:story:207">
<s:title>Oracle adds new functionality to XSQL Pages</s:title>
<s:time>08:00, 10 Jan 2000</s:time>
<s:author>Edd Dumbill</s:author>
<s:url>http://xmlhack.com/read.php?item=207</s:url>
<s:icon>http://xmlhack.com/images/s_tools.gif</s:icon>
</RDF:Description>
...
</RDF:RDF>
You will note again that I could have profitably used elements from the Dublin Core to describe things such as "title," but as the sole intention of this particular RDF is to populate my user interface I preferred to take the simplest route.
Having described each story, I also wanted to describe a resource
that would be my "headlines" list. For this, I used an RDF
sequence I gave the ID headlines:
<RDF:Seq ID="headlines">
<RDF:li resource="urn:xmlhack:story:207"/>
<RDF:li resource="urn:xmlhack:story:206"/>
...
</RDF:Seq>
XMLhack is based on PHP and MySQL, so I had little difficulty writing the PHP script to generate this RDF. The remaining problem was inserting the data into the XUL.
From within a XUL file, you can associate an RDF data source with a user interface item:
<tree id="xmlhack-tree"
datasources="http://myserver.com/headlines.rdf"
ref="http://myserver.com/headlines.rdf#headlines"
style="height: 100%; width: 100%">
The datasources attribute signifies the source of the
data, and ref says where in the data source I want
to get data from. Here I indicate I want to read the
sequence of headlines.
The template and rule elements are then
used to merge the data with the interface:
<template>
<rule>
<treechildren>
<treeitem uri="rdf:*">
<treerow>
<treecell
src="rdf:http://xmlhack.com/stories/rdf#icon"
value="rdf:http://xmlhack.com/stories/rdf#title"
s:url="rdf:http://xmlhack.com/stories/rdf#url"/>
</treerow>
</treeitem>
</treechildren>
</rule>
</template>
This code replaces everything below the <treecol/>
from my interface mockup above. Its purpose is quite self
explanatory: the uri attribute matches each
resource URI in the headlines list, making that the starting
point. Thereafter, the rdf:-style attribute values select
properties of the resource: we described these properties in the
individual story descriptions earlier. Deeper understanding of what is
going on can be gleaned from reading the
RDF Syntax
Recommendation from the W3C.
One subtlety here that I didn't find explicitly stated in the XUL documentation is the security model. Much as with Java applets, you can only retrieve data sources served from the same place as the XUL. For instance, this means you can't have a filesystem data source in a XUL description served by a web server. I found this out the hard way.
From studying the template above,
you will note I added the s:url attribute to my tree
cell. As the full XUL
file shows, I earlier bound the s: prefix to
another namespace of my own. This is so I could communicate to
my JavaScript the URL to which the browser ought to navigate
when it is double-clicked. To tie in the script, I added this
attribute to the tree element:
onclick="return clicked(event, event.target);"
Again, I must acknowledge the value of the existing browser chrome
files for providing me with shortcuts to write this
script. Here's the definition of clicked():
function clicked(event, item) {
if (event.clickCount == 2 && event.button == 1) {
var tURL=item.getAttribute('s:url');
if (window.content == null) {
window.openDialog(
"chrome://navigator/content/navigator.xul",
"_blank", "chrome,all,dialog=no", tURL );
} else {
window.content.location = tURL;
}
return(true);
}
return(false);
}
On a double-click from the left mouse button, this script navigates to the destination URL, opening a new window if needed.
The final piece of the jigsaw is to allow the user to add the XMLhack panel to their sidebar. A short piece of Mozilla-specific JavaScript suffices:
<a href="javascript:sidebar.addPanel('<?xmlhack?>',
'http://xmlhack.com/mozPanel.xul','')">Add XMLhack
Panel To Sidebar</a>
Here's the link for you to try yourself: Add XMLhack Panel To Sidebar. I've tested this in M14 and later nightly builds of Mozilla. On Windows you may need to open and close the sidebar to get it to display properly.
Apart from having a lot of fun, what did I discover playing with Mozilla? (I will lay aside the obvious fact that there are numerous bugs, as the browser has not even reached beta release stage yet.)
Mozilla could mean widespread RDFI am of the opinion that what RDF needs is a "killer-app." Although lauded by many (especially Tim Berners-Lee), it has failed to break through in the same way XML has. Arbitrary data can be integrated into Mozilla interfaces via RDF with ease: the smallest part of my project was writing the RDF serialization of the data. Mozilla could well turn out to be a huge popularizer of RDF, leading to many sources of RDF information on the Web.
Mozilla provides a richer interface to online applicationsThe richer widget set provided by XUL compares well with those available for years in desktop applications. HTML has historically been woefully poor in its user interface elements, contributing to a Web in which "read" is much more common than "write." Mozilla could provide a truly cross-platform, network-aware framework for applications.
One proof of this is that the browser user interface itself is largely written in XUL and Javascript (and consequently the source code is a good place to explore how this stuff works!).
Having been involved in constructing online applications for several years, mainly in web content management, I'm excited by the prospect of more efficient web-enabled interfaces that are truly integrated with the browser. XUL delivers where Java never quite managed to.
Mozilla needs supportMicrosoft's Internet Explorer is still ahead of Mozilla in terms of functionality, and way ahead in terms of adoption. Several otherwise free-spirited developers tell me that they use IE5 simply because it is the better browser and their altruism has run out of steam. Mozilla certainly faces tough challenges. However, it's important that it get the support of the XML and open source communities, for these reasons among others:
Details on participating in the Mozilla project can be found at Mozilla.org.
XML.com Copyright © 1998-2006 O'Reilly Media, Inc.