IntroductionIt’s quite amazing what can be done with HTML and AJAX these days. Today there are word processing, calendaring, spreadsheet and drawing applications that can run directly in your browser. The possibilities are still limited though, and the user experience of a typical HTML/AJAX powered application has yet to match that of one running on your local desktop. Let’s face it: HTML was just not made to define user interfaces; it was made to define document structures. The minute you decide to use it for anything else you’re bound to work around its limitations. XUL, on the other hand, is an XML dialect specifically designed for the platform-independent definition of user interfaces. Thanks to the Mozilla project and its Gecko engine, there is now a widely used runtime environment for XUL interfaces in the form of the Mozilla family of browsers, as well as the standalone XULRunner project, which can be used to distribute XUL applications that can be run without first firing up a browser and typing a URL.
XUL stands for XML User Interface Language and is pronounced “Zuul”, which should ring a bell or two for those of us who have religiously been watching the 1980s cult movie “Ghostbusters”, over and over again for the past few decades (as I have). There are lots more Ghostbuster-references to be discovered when working with XUL, but hey, programming is supposed to be fun, right? The basic idea is to have a highly portable runtime environment that can interpret and render interface definitions rendered in XUL; and that exposes a complete DOM implementation along with other standard functionality to a JavaScript interpreter. Application code written in XUL and JavaScript will run on all platforms to which this runtime environment is being ported to, and that too without change. Currently, this also includes the Mozilla browsers (including Firefox) as well as the afore-mentioned XULRunner project. There is yet another implementation written in Java, but unfortunately not 100% compatible with the Mozilla approach. Microsoft has jumped on the bandwagon as well, introducing its new XAML technology, which will be part of the upcoming Windows Vista release. Although XAML looks quite a lot like XUL, it is of course completely incompatible and also uses .NET-centric languages for handling the interface events, instead of JavaScript.
Diving into XULTo dive into XUL, let’s begin with the mandatory first example in every introduction to a new programming environment: The good old “Hello World” application! In XUL, it looks like this:
<?xml version=”1.0” encoding=”utf8” ?>
<?xml-stylesheet href=”chrome://global/skin/” type=”text/css” ?>
<window id=”MainWindow” xmlns=”http://www.mozilla.org/keymaster/gatekeeper/
there.is.only.xul”>
<description>Hello World</description>
</window>This can be saved into either a
.php file on your web server, or a .xul file on your local hard drive. When opening the local
.xul file, Mozilla will display the text “Hello World” using a smallish font within an otherwise terribly dull and empty browser window. When trying to access the file from a remote web server though, you’ll most likely see nothing, or simply an XML tree representation of your markup. In this case the web server might need to be made aware of the correct mime-type for
.xul. When using a .php script, you can send the correct header yourself:
header(‘Content-Type:application/vnd.mozilla.xul+xml’);In case short-open-tags are enabled in your PHP-installation, the parser will unfortunately choke on the xml-headers (<?xml...). One possible workaround is outputting them inside a PHPblock:
<?php
header(‘Content-Type: application/vnd.mozilla.xul+xml’);
echo ‘<?xml version=”1.0” encoding=”utf8” ?>’;
echo ‘<?xml-stylesheet href=”chrome://global/skin/” type=”text/css” ?>’;
?>Or you might consider using (almost) any template engine to output your XUL, which would not try to parse short-open-tags as well.
Styling the InterfaceThe observant reader will have noticed the stylesheet declaration at the top of the document. The Gecko engine uses standard CSS (plus a few engine-specific additions) to influence the look and feel of your XUL user interfaces. You can override colors, font sizes, border settings and the likes just as you can when using HTML. If you only use the standard stylesheet that Mozilla offers through its chrome protocol as shown in the above example, though, your application will follow the look and feel of the current browser skin. By default, this means it will look like any other application installed on your system. But it also means that if you decide to install fancy browser skins, your application will automatically adopt its look and feel – so it will always adapt to any kind of visual environment it’s running in.
XUL offers a broad range of standard user interface controls that are a lot richer than those provided by HTML. In addition to “normal” input fields, select-boxes, check-boxes and radio buttons, you can make use of context menus, pull-down menus, datagrid-like controls and more. In some cases you might still want to embed HTML by adding a namespace declaration to your document and prefixing HTML tags with the proper namespace. You can also define your own controls using XBL – another XML markup language that allows the combination of arbitrary XUL markup complete with JavaScript behaviors into a single control that can then be applied to a tag of your choice in your main XUL document. Although it would certainly be untrue to claim that the possibilities are limitless, it’s getting pretty close.
Instead of listing and describing all available controls in this article, there are some excellent resources out on the Web that already serve this purpose very well.
Both resources comprise an excellent XUL reference as well as tutorials. They are a mandatory read for everyone intending to learn about XUL and related technologies.
In this article we’ll focus more on the interaction of XUL and PHP – how to send data from your XUL applications to PHP and have proper responses sent back from the server. Most XUL tutorials advocate RDF data-representation as the format of choice to bind controls to datasources. But as we’re in the age of fancy buzzwords like AJAX, we’ll rather use that. Also, there is a nice PEAR-package to assist you on the PHP part.
Building a Search FormSearching data and displaying the results is a fairly common feature in most data-centric applications, so it will serve as a good example for interaction between PHP and XUL. We’ll use the HTML_AJAX from PEAR for sending the search term to the server and to retrieve the resultset. Listing 1 shows the XUL markup – save it in a file called
xul.php.
When opening this in Mozilla, you should see something similar to Figure 1. Notice how the columns are named “Book” and “Author” as a homage to the other famous beginner’s example: the dreadful bookstore example.
The main window tag has no additional attributes that define how to arrange the contained controls. So by default all controls will be arranged vertically. To override this for the first three controls, we use the
<hbox> element, which defines a container wherein all child elements are arranged horizontally. The attribute
align=“center” tells the browser to align the controls along the center line of the
<hbox> - this can be compared to valign=“middle” in plain old HTML table rows. The
<tree> control, not being in the
<hbox> but rather a direct child of the window tag, will be rendered beneath the
<hbox>.
Listing 1
<?php
header(‘Content-Type: application/vnd.mozilla.xul+xml’);
echo ‘<?xml version=”1.0” encoding=”utf8” ?>’;
echo ‘<?xml-stylesheet href=”chrome://global/skin/” type=”text/css” ?>’;
?>
<window id=”MainWindow” xmlns=”http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul”>
<hbox align=”center”>
<label value=”Search for:” control=”txtSearch” />
<textbox id=”txtSearch” flex=”1” />
<button id=”btSearch” label=”Search me!” />
</hbox>
<tree flex=”5” id=”trResult”>
<treecols>
<treecol id=”id” label=”ID” primary=”true” hidden=”true” persist=”width hidden” />
<splitter class=”tree-splitter” />
<treecol id=”name” label=”Book” fl ex=”4” persist=”width hidden” />
<splitter class=”tree-splitter” />
<treecol id=”type” label=”Author” fl ex=”1” persist=”width hidden” />
</treecols>
<treechildren />
</tree>
</window>
It might sound strange to display tabular data within a control called “tree”, but this implies that by properly nesting the elements within the
<treechildren> tag (which until now is empty) you could also display hierarchical data in the form of a tree structure. Listing 2 shows some example content for the
<treechildren> tag – we’ll generate a similar structure, on the fly, a litle later into this article. If you want more details about the possibilities of these and more tags, please go to the aforementioned XUL resources on the Web – you won’t find a better reference anywhere else.
The Server PartOn the server side, we’ll need some way to retrieve the search results for a given search term. First, create a database named “books” in the DBMS of your choice (MySQL, PostgreSQL, SQLite, ...). If you prefer to store data in XML files, you could use those as well – but I’ll stick to SQL for now. Inside the database, create a table, also called “books” here, with the columns “id”, “title” and “author”. Last, but not the least, populate the table with some fancy book titles. If that sounds like too much work to you, just use the SQL that I used to create my demo database in MySQL, as shown in Listing 3.
The next step is to write a class that contains a method, which takes some search phrase as an argument and returns the complete result set as a two-dimensional array. Listing 4 shows an example for such a class written in PHP5, using PDO for database connectivity – let’s call that file
search.php for now. The HTML_AJAX package that will come into play shortly also works with PHP4 though. So if that’s all what’s available to you, it shouldn’t be a problem to adapt the class a little bit to make it work in your environment. Note that I’ve used a locally installed database server with the username
ajax and the password
ajaxpw – you’ll most probably need to tweak the connection settings to your liking. Also note that you’ll never ever want to use similar code like this in production. This is only for the purposes of this article.
Listing 2
<treechildren>
<treeitem>
<treerow>
<treecell label=”1”/>
<treecell label=”Soul Music”/>
<treecell label=”Terry Pratchett”/>
</treerow>
</treeitem>
<treeitem>
<treerow>
<treecell label=”2”/>
<treecell label=”The Hitchhiker’s Guide To The Glaxy”/>
<treecell label=”Douglas Adams”/>
</treerow>
</treeitem>
</treechildren>
Listing 3
CREATE DATABASE `books`;
CREATE TABLE books (
id integer auto_increment,
title varchar(120),
author varchar(80),
PRIMARY KEY(id)
);
INSERT INTO books (title,author) VALUES (‘The Hitchhiker\’s Guide To The Galaxy’, ‘Douglas Adams’);
INSERT INTO books (title,author) VALUES (‘Soul Music’, ‘Terry Pratchett’);
INSERT INTO books (title,author) VALUES (‘The Complete Robot’, ‘Isaac Asimov’);
INSERT INTO books (title,author) VALUES (‘The Dark Elf Trilogy’, ‘R.A. Salvatore’);
INSERT INTO books (title,author) VALUES (‘The Lord Of The Rings’, ‘J.R.R. Tolkien’);
INSERT INTO books (title,author) VALUES (‘Imagica’, ‘Clive Barker’);
INSERT INTO books (title,author) VALUES (‘Neuromancer’, ‘William Gibson’);
But how can our XUL interface call the get_records method? XUL has no concept of sending form data via a POST request, so we’ll need to use AJAX to get all the data from the client to server and vice versa. To make this as painless as possible, we’ll use a PEAR:: HTML_AJAX. At the time of writing it is released in version 0.4, which is still marked as alpha quality. Because the “pear” command by default is set to install only stable components, we’ll first need to convince it that accepting alpha packages is a good idea. Thus, to install HTML_AJAX, you’ll need to type at the shell prompt:
pear install HTML_AJAXIf you don’t yet have the pear-installer, please visit
http://pear.php.net/ for detailed instructions. Don’t worry about the “alpha” status – the package already is quite stable and I’ve personally used it on multiple production sites without a problem.
Now that HTML_AJAX is properly installed, create another script called
ajax_server.php in the same directory in which you placed
xul.php. The server script will output the JavaScript classes needed to interact with the server from the client side. It will also create stub classes that can be used to work with the server side classes as if they were natively available on the client. Listing 5 shows what the server script must look like to expose our search class to the browser. Note that you’ll need to adapt the PEAR data-path to the location used on your server. If unsure use:
pear config-showThe property setting
$initMethods=true will tell the server class to call the method
initSearch() when the client requests the stub definitions for the “search” class. This method must then make an instance of the class to be served and register the instance with the server class. The
handleRequest() method will handle any
GET and POST requests and make the proper method calls to handle the server/client interaction. It will automatically convert all the passed data structures between PHP and JavaScript, so that whenever a server-side method returns a PHP array, the client will be able to access the result as a native JavaScript array. This works for all PHP data types, even for objects (Note: You won’t be able to call any methods of the returned PHP objects from JavaScript though. This only works with the classes you explicitly exposed via your AJAX server script – and that is a good thing).
include ‘HTML/AJAX/Server.php’;
class MyServer extends HTML_AJAX_Server {
public $initMethods = true;
public function initSearch() {
include_once ‘search.php’;
$this->registerClass(new search());
}
}
$server = new MyServer();
$server->clientJsLocation = ‘/path/to/PEAR/data/HTML_AJAX/js/’;
$server->handleRequest();Back to the ClientTo make use of the newly created AJAX server, import the generated JavaScript into your XUL definition. This is done via the <script> tag, similar to HTML. Insert the following two lines right after the opening window tag in the
xul.php file:
<script src=”ajax_server.php?client=all” />
<script src=”ajax_server.php?stub=all” />Calling the server script with the
client=all parameter will output all the convenience classes needed by HTML_AJAX for interacting with the server, while
stub=all outputs the generated class stubs for the classes that were exposed via the
init methods. If you have exposed multiple classes but only want to use the search class in this particular XUL window, you could also call the server script with
stub=search and you’d get only the stubs you need.
Listing 4
class search {
public function __construct() {
$this->pdo = new PDO(‘mysql:host=localhost;dbname=books’,
‘ajax’, ‘ajaxpw’);
}
public function get_records($search=’’) {
$sql = “SELECT * FROM books “;
if (trim($search) != ‘’) {
$sql .= “WHERE title LIKE “.$this->pdo->quote(‘%’.$search.’%’);
}
return $this->pdo->query($sql)->fetchAll(PDO_FETCH_ASSOC);
}
}
You’re now ready to send data to the server! To have a JavaScript function called when pressing the search button, add the attribute
oncommand=“doSearch()” to the button tag. Then add another script section just after the other script includes. For embedding JavaScript code directly into your XUL markup remember to use a CDATA-block. We’ll need to define the
doSearch() function that sends the current input in the textbox to the server and retrieves the results. Because we have already included all the JavaScript created by the server, this function is going to be very simple. See Listing 5.
The
doSearch() function reads the current input from the textbox, makes an instance of the search class that we defined on the serverside, exposed via the AJAX server script, and then calls its
get_records() method, passing the phrase to be searched for. This call is, again, a simplified example. Usually you wouldn’t want to make a synchronous call like this because it means your application will freeze until the complete resultset has been received from the server. It is better to pass a callback object to the constructor of the search class, which can then be used to asynchronously handle the returned data once it has arrived, while your application remains responsive. Please refer to the HTML_AJAX documentation (which you can find through the package homepage at
pear.php.net) to see how this is done.
When the result has arrived, it is still a standard JavaScript array, containing one JavaScript object for each row. Since JavaScript does not know associative arrays as PHP does, objects are used to emulate them. What’s still missing is a way to populate the tree control with our shiny new results. Unfortunately, XUL provides native data bindings only for RDF data – there’s no way to just throw a JavaScript array at a tree object and be done with it.
Listing 5
<script>
<![CDATA[
function doSearch() {
var phrase = document.getElementById(“txtSearch”).value;
var server = new search();
var result = server.get_records(phrase);
var tree = document.getElementById(“trResult”);
refreshTree(tree, result);
}
]]>
</script>
Thus, we’ll need to build the document structure from scratch. (There is another more efficient way to bind JavaScript data to tree controls, but that’s a bit more complicated. So again, let’s keep it simple.) Our
doSearch() function uses the
refreshTree() function to serve this purpose, which can be found in Listing 6. You might have noticed that all that
refreshTree() does is build exactly the kind of XUL tag structure that we’ve seen before in Listing 2.
Et voilá, we’re done! By entering some text in the textbox and clicking on the ‘search’ button, the application now calls the PHP class on the server, retrieves the result and displays it within the tree control. You could now begin to spice up the tree via CSS, using alternating row colors and/or different highlights for the selected rows. You could define a context menu for the tree, containing items such as “Add new item”, “Open item” or “Delete selected items” and define more event handlers which are called when the corresponding context menu item is clicked. These could get the list of selected tree rows from the tree control and act accordingly. Once a tree row is selected, you could also have more information about the selected book to be displayed in an iframe, which can be embedded into your XUL interface just as in HTML. By defining simple tag attributes, you can resize your tree columns, hide or show them individually at
runtime, change the column order and have Mozilla remember the user’s settings when it starts up the next time – you won’t even need to write any code for that.
Listing 6
function refreshTree(tree, result) {
// delete all previous results first!
emptyTree(tree);
// iterate through result rows
for (var i=0; i < result.length; i++) {
var treeItem = document.createElement(“treeitem”);
var treeRow = document.createElement(“treerow”);
// iterate through the current row’s fields
for (var name in result[i]) {
var cell = document.createElement(“treecell”);
cell.setAttribute(“label”,result[i][name]);
treeRow.appendChild(cell);
}
treeItem.appendChild(treeRow);
tree.childNodes[1].appendChild(treeItem);
}
}
function emptyTree(tree) {
while(tree.childNodes[1].hasChildNodes()) {
tree.childNodes[1].removeChild(tree.childNodes[1].childNodes[0]);
}
}
GotchasWhere there’s light, there are shadows. This universal rule applies to XUL as well. When browsing sites like XulPlanet and reading through all the tutorials (and really, you should) you’ll be amazed at what you can do with XUL – opening file requesters, defining your own dialog windows, there are even quite easy ways to define classic multi-page wizard dialogs. But then you’ll try to use these nifty features, only to find out that they mysteriously won’t work. Huh?
Unfortunately, not all XUL features work when running XUL applications from a remote server. Even more unfortunate is the fact that the official documentation fails to mention this. So before you try it there’s no way to know this unless you’ve already put some work into it. To save you some pain I can tell you that when calling XUL applications from remote servers, things like dialogs, wizards and file access will not work. While disallowing file access for remote applications is surely a good thing for security reasons, the rest is a mystery to me (and others). You will not run into these problems if you distribute your application for local installation – you can still communicate with the server via AJAX, but the user will have to install the application first.
Also, when you’re trying to use undetermined progress bars, you may wonder why they don’t do anything – usually you’d expect to see some kind of progress indication... at least that’s what they’re meant to do. This does not seem to be documented anywhere (at least not where it can easily be found), but you first need to apply a special stylesheet for undetermined progress bars to work:
progressmeter[mode=”undetermined”] {
-moz-binding:
url(“chrome://global/content/bindings/progressme
ter.xml#progressmeter-undetermined”);
}Thus, whenever you run into something that’s completely inexplicable to you, I recommend trying the forums at XulPlanet, or the IRC channel #xul at irc.mozilla.org.
FinallyAside from its few flaws, XUL is an amazing technology. It offers everything you need to write applications whose look and feel, overall responsiveness and comfort level are on par with desktop applications. If you don’t believe it, ask yourself what technology the Mozilla folks might have used to define the interfaces of popular desktop applications like Seamonkey, Firefox or Thunderbird – they’re written in XUL as well. All the popular Firefox extensions are using XUL, so learning to write XUL applications will also aid you in enhancing your browser’s capabilities, should you wish to do that.
You are not limited to using the pre-defined functionality of the Gecko engine through JavaScript and adding interactivity by making calls to a remote server. Mozilla also offers the XPCOM interface that can be used to expose the functionality of external libraries to the browser’s scripting engine. This, however, is one of those things that only work with local applications (for obvious reasons). Now, what are you waiting for? Go, learn XUL, and write something really cool!