XUL Tutorial
XUL Tutorial
1. Introduction
Introduction
This tutorial guides you to learning XUL (XML User-interface Language) which is a cross-
platform language for describing user interfaces of applications.
This tutorial will demonstrate creating a simple find file user interface, much like that provided by
the Macintosh's Sherlock or the find file dialog in Windows. Note that only the user interface will
be created and some limited functionality. The actual finding of files will be not be implemented.
A blue line will appear to the left of a paragraph where the find file dialog is being modified. You
can follow along at these sections.
XUL (pronounced zool and it rhymes with cool) was created to make development of the Mozilla
browser easier and faster. It is an XML language so all features available to XML are also
available to XUL.
Most applications need to be developed using features of a specific platform making building
cross-platform software time-consuming and costly. A number of cross-platform solutions have
been developed in the past. Java, for example, has portability as a main selling point. XUL is
one such language designed specifically for building portable user interfaces. It takes a long
time to build an application even for only one platform. The time required to compile and debug
can be lengthy. With XUL, an interface can be implemented and modified quickly and easily.
XUL has all the advantages of other XML languages. For example XHTML or other XML
languages such as MathML or SVG can be inserted within it. Also, text displayed with XUL is
easily localizable, which means that it can be translated into other languages with little effort.
XUL provides the ability to create most elements found in modern graphical interfaces. Some
elements that can be created are:
The displayed content can be created from the contents of a XUL file or with data from a
datasource. In Mozilla, such datasources include a user's mailbox, their bookmarks and search
results. The contents of menus, trees and other elements can be populated with this data, or
with your own data supplied in an RDF file.
• Firefox extension - an extension adds functionality to the browser itself, often in the
form of extra toolbars, context menus, or UI to customize the browser UI. This is done
using a feature of XUL called an overlay, which allows the UI provided from one source,
in this case, the Firefox browser, to be merged together with the UI from the extension.
Extensions may also be applied to other Mozilla based products such as Thunderbird.
• Standalone XULRunner application - XULRunner is a packaged version of the Mozilla
platform which allows you to create standalone XUL applications. A browser isn't required
to run these applications, as they have their own executable file.
• XUL package - in between the other two are applications which are created in the same
way as an extension, but they act like a separate application in a separate window. This
is used when you don't want to have the larger size of a complete XULRunner
application, but don't mind requiring a Mozilla browser to be installed to be able to run the
application.
• Remote XUL application - you can also just place XUL code on a web server and open
it in a browser, as you would any other web page. This method is limited however, as
there will be security concerns that will limit the kinds of things you will be able to do,
such as opening other windows.
The first three types all require an installation to be performed on the user's machine. However,
these types of applications do not have security restrictions placed on them, so they may
access local files and read and write preferences, for example. For extensions, the XUL files
and associated scripts and images used by an application would be packaged into a single file
and downloaded and installed by the user. Mozilla applications such as Firefox provide an
extension manager which allows packages to be installed without having to write a lot of
complex code.
It is also possible to open XUL files directly from the file system or from a remote web site,
however they will be restricted in the kinds of operations they can do, and some aspects of XUL
will not work. However, if you do want to load XUL content from a remote site, the Web server
must be set up to send XUL files with the content type 'application/vnd.mozilla.xul+xml'. XUL is
usually stored in files with a .xul extension. You can open a XUL file with Mozilla as you would
any other file, using the Open File command from the File menu or typing the URL into the
address bar.
You should have an understanding of HTML and at least a basic understanding of XML and
CSS. Here are some guidelines to keep in mind:
• XUL elements and attributes should all be entered in lowercase as XML is case-sensitive
(unlike HTML).
• Attribute values in XUL must be placed inside quotes, even if they are numbers.
• XUL files are usually split into four files, one each for the layout and elements, for style
declarations, for entity declarations (used for localization) and for scripts. In addition, you
may have extra files for images or for platform specific data.
XUL is supported in Mozilla and browsers that are also based upon on the Gecko engine, such
as Netscape 6 or later and Mozilla Firefox. Due to various changes in XUL syntax over time,
you will want to get the latest version for the examples to work properly. Most examples should
work in Mozilla 1.0 or later. XUL is fairly similar in Firefox and in other Mozilla-based browsers,
although there are some specific differences such as support for customizable toolbars.
This tutorial attempts to cover much of XUL's functionality, however, not all features are
discussed. Once you are familiar with XUL, you can use the XUL Element Reference to find out
about other features supported by specific elements.
XUL Structure
We'll begin by looking at how the XUL is handled in Mozilla.
In Mozilla, XUL is handled much in the same way that HTML or other types of content are
handled. When you type the URL of an HTML page into the browser's address field, the
browser locates the web site and downloads the content. The Mozilla rendering engine takes
the content in the form of HTML source and transforms it into a document tree. The tree is then
converted into a set of objects that can be displayed on the screen. CSS, images and other
technologies are used to control the presentation. XUL functions in much the same way.
In fact, in Mozilla, all document types, whether they are HTML or XUL, or even SVG are all
handled by the same underlying code. This means that the same CSS properties may be used
to style both HTML and XUL, and many of the features can be shared between both. However,
there are some features that are specific to HTML such as forms, and others which are specific
to XUL such as overlays. Since XUL and HTML are handled in the same way, you can load
both from either your local file system, from a web page, or from an extension or standalone
XULRunner application.
Content from remote sources, regardless or whether they are HTML or XUL or another
document type, are limited in the type of operations they can perform, for security reasons. For
this, reason, Mozilla provides a method of installing content locally and registering the installed
files as part of its chrome system. This allows a special URL form to be used called a chrome
URL. By accessing a file using a chrome URL, the files receive elevated privileges to access
local files, access preferences and bookmarks and perform other privileged operations.
Obviously, web pages do not get these privileges, unless they are signed with a digital
certificate and the user has granted permission to perform these operations.
This chrome package registration is the way in which Firefox Extensions are able to add
features to the browser. The extensions are small packages of XUL files, Javascript, style
sheets and images packed together into a single file. This file can be created by using a ZIP
utility. When the user downloads it, it will be installed onto the user's machine. It will hook into
the browser using a XUL specific feature called an overlay which allows the XUL from the
extension and the XUL in the browser to combine together. To the user, it may seem like the
extension has modified the browser, but in reality, the code is all separate, and the extension
may be uninstalled easily. Registered packages are not required to use overlays, of course. If
they don't, you won't be able to access them via the main browser interface, but you can still
access them via the chrome URL, if you know what it is.
Standalone XUL applications may include XUL code in a similar way, but, of course, the XUL for
application will be included will be included as part of the installation, instead of having to be
installed separately as an extension. However, this XUL code will be registered in the chrome
system such that the application can display the UI.
It is worth noting that the Mozilla browser itself is actually just a set of packages containing XUL
files, JavaScript and style sheets. These files are accessed via a chrome URL and have
enhanced privileges and work just like any other package. Of course, the browser is much
larger and more sophisticated than most extensions. Firefox and Thunderbird as well as number
of other components are all written in XUL and are all accessible via chrome URLs. You can
examine these packages by looking in the chrome directory where Firefox or another XUL
application is installed.
The chrome URL always begins with 'chrome://'. Much like how the 'http://' URL always refers to
remote web sites accessed using HTTP and the 'file://' URL always refers to local files, the
'chrome://' URL always refers to installed packages and extensions. We'll look more at the
syntax of a chrome URL in the next section. It is important to note that when accessing content
through a chrome URL, it gains the enhanced privileges described above that other kinds of
URLs do not. For instance, an HTTP URL does not have any special privileges, and an error will
occur if a web page tries, for example, to read a local file. However, a file loaded via a chrome
URL will be able to read files without restriction.
This distinction is important. This means that there are certain things that content of web pages
cannot do, such as read the user's bookmarks. This distinction is not based on the kind of
content being displayed; only on the type of URL used. Both HTML and XUL placed on a web
site have no extra permissions; however both HTML and XUL loaded through a chrome URL
have enhanced permissions.
If you are going to use XUL on a web site, you can just put the XUL file on the web site as you
would an HTML file, and then load its URL in a browser. Ensure that your web server is
configured to send XUL files with the content type of 'application/vnd.mozilla.xul+xml'. This
content type is how Mozilla knows the difference between HTML and XUL. Mozilla does not use
the file extension, unless reading files from the file system, but you should use the .xul
extension for all XUL files. You can load XUL files from your own machine by opening them in
the browser, or by double-clicking the file in your file manager. Remember that remote XUL will
have significant restrictions on what it can do.
Mozilla uses a distinctly different kind of document object for HTML and XUL, although they
share most of their functionality. There are three main types of document in Mozilla: HTML,
XML and XUL. Naturally, the HTML document is used for HTML documents, the XUL document
is used for XUL documents, and the XML document is used for other types of XML documents.
Since XUL is also XML, the XUL document is a subtype of the more generic XML document.
There are subtle differences in functionality. For example while the form controls on an HTML
page are accessible via the 'document.forms' property, this property isn't available for XUL
documents since XUL doesn't have forms in the HTML sense. On the other hand, XUL specific
features such as overlays and templates are only available in XUL documents.
This distinction between documents is important. It is possible to use many XUL features in
HTML or XML documents since they aren't document type specifc, however other features
require the right kind of document. For instance, you can use the XUL layout types in other
documents since they don't rely on the XUL document type to function.
• Mozilla renders both HTML and XUL using the same underlying engine and uses CSS to
specify their presentation.
• XUL may be loaded from a remote site, the local file system, or installed as a package
and accessed using a chrome URL. This is what browser extensions do.
• Chrome URLs may be used to access installed packages and open them with enhanced
privileges.
• HTML, XML and XUL each have a different document type. Some features may be used
in any document type whereas some features are specific to one kind of document.
The next few sections describe the basic structure of a chrome package which can be installed
into Mozilla. However, if you just want to get started building a simple application, you may skip
ahead to section 2 and save this section for later.
Package Organization
Mozilla is organized in such a way that you can have as many components as you want pre-
installed. Each extension is also a component with a separate chrome URL. It will also have one
component for each installed theme and locale. Each of these components, or packages, is
made up of a set of files that describe the user interface for it. For example, the messenger
component will have descriptions of the mail messages list window, the composition window
and the address book dialogs.
The packages that are provided with Mozilla are located within the chrome directory, which you
will find in the directory where you installed Mozilla. The chrome directory is where you will find
all the files that describe the user interface used by the Mozilla browser, mail client and other
applications. You will typically put all of the XUL files for an application in this directory, except
for extensions which will be installed in the extensions directory for a particular user. Just
copying a XUL file into the 'chrome' directory doesn't give the file any extra permissions nor can
it be accessed via a chrome URL. To gain the extra privileges, you will need to create a
manifest file and put that in the chrome directory. This file is easy to create, as it is typically only
a couple of lines long. It is used to map a chrome URL to a file or directory path on the disk
where the XUL files are located. Details of how to create this file will be discussed in the next
section.
The only way to create content that can be accessed through a chrome URL is by creating a
package as described in the next few sections. This directory is called 'chrome' likely because it
seemed like a convenient name to use for the directory where the chrome packages that are
included with Mozilla are kept.
To further the confusion, there are two other places where the word chrome might appear. The
first is the '-chrome' command line argument, and the chrome modifier to the 'window.open'
function. Neither of these features grant extra privileges; instead they are used to open a new
top-level window without the browser UI such as the menu and toolbar. You will commonly use
this feature in more complex XUL applications since you wouldn't want the browser UI to exist
around your dialog boxes.
The files for a package are usually combined into a single JAR file. A JAR file may created and
examined using a ZIP utility. For instance, you can open the JAR files in Mozilla's chrome
directory to see the basic structure of a package. Although it's normal to combine the files into a
JAR file, packages may also be accessed in expanded form into a directory. Although you don't
normally distribute a package this way, it is handy during development since you can edit the
file directly and then reload the XUL file without having to repackage or reinstall the files.
There are usually three different parts to a chrome package, although they are all optional. Each
part is stored in a different directory. These three sets are the content, the skin and the locale,
described below. A particular package might provide one or more skins and locales, but a user
can replace them with their own. In addition, the package might include several different
applications each accessible via different chrome URLs. The packaging system is flexible
enough so that you can include whatever parts you need, and allow other parts, such as the text
for different languages, to be downloaded separately.
Content Packages
The name of the JAR file might describe what it contains, but you can't be sure unless you view
its contents. Let's use the browser package included with Firefox as an example. If you extract
the files in browser.jar, you will find that it contains a directory structure much like the following:
content
browser
browser.xul
browser.js
-- other mail XUL and JS files goes here --
bookmarks
-- bookmarks files go here --
preferences
-- preferences files go here --
.
.
.
This is easily recognizable as a content package, as the top-level directory is called 'content'.
For skins, this directory will usually be called 'skin' and for locales, it will usually be called
'locale'. This naming scheme isn't necessary but this is a common convention to make the parts
of a package clearer. Some packages may include a content section, a skin and a locale. In this
case, you will find a subdirectory for each type. For example, Chatzilla is distributed in this way.
The content/browser directory contains a number of files with xul and js extensions. The XUL
files are the ones with the xul extension. The files with js extensions are JavaScript files which
contain scripts that handle the functionality of a window. Many XUL files have a script file
associated with them, and some may have more than one.
In the listing above, two files have been shown. There are others of course, but for simplicity
they aren't shown. The file browser.xul is the XUL file that describes the main browser window.
The main window for a content package should have the same name as the package with a xul
extension. In this case, the package name is 'browser', so we expect to find 'browser.xul'. Some
of the other XUL files describe separate windows. For example, the file pageInfo.xul describes
the page info dialog.
Many packages will include a contents.rdf, which describes the package, its author and the
overlays it uses. However, this file is obsolete and has been replaced with a simpler
mechanism. This newer method is the manifest file mentioned earlier, and you will find these as
files with the .manifest extension in the chrome directory. For instance, browser.manifest
describes the browser package.
Several subdirectories, such as bookmarks and preferences, describe additional sections of the
browser component. They are placed in different directories only to keep the files more
organized.
Skins or Themes
The underlying code of Mozilla calls them skins, although the user interface calls them themes,
but they both refer to the same thing. The classic.jar file describes the default theme provided
with Firefox. The structure is similar to the content packages. For example, examining
classic.jar:
skin
classic
browser
-- browser skin files go here --
global
contents.rdf
-- global skin files go here --
.
.
.
Again, this directory structure isn't necessary and is used for convenience. You can actually put
all the files in one directory at the top level and not use subdirectories. However, for larger
applications, subdirectories are used to separate the different components. In the example
above, a directory exists for theme related files for the browser and another for global theme
related files. The global directory contains skin files that are general to all packages. These files
will apply to all components and will be included with your own standalone applications. The
global part defines the appearance of all of the common XUL widgets, whereas the other
directories have files that are specific to those applications. Firefox includes both the global and
browser theme files in one archive, but they can be included separately.
A skin is made up of CSS files and a number of images used to define the look of an interface.
The file browser.css is used by browser.xul and contains styles which define the appearance of
various parts of the browser interface. Again, note how the file browser.css has the same name
as the package. By changing the CSS files, you can adjust the appearance of a window without
changing its function. This is how you can create a new theme. The XUL part remains the same
but the skin part changes independently.
Locales
The file en-US.jar describes the language information for each component, in this case for US
English. Like the skins, each language will contain files that specify text used by the package
but for a specific language. The locale structure is similar to the others, so it won't be listed it
here.
The localized text is stored in two types of files, DTD files, and properties files. The DTD files
have a dtd extension and contain entity declarations, one for each text string that is used in a
window. For example, the file browser.dtd contains entity declarations for each menu command.
In addition, keyboard shortcuts for each command are also defined, because they may be
different for each language. DTD files are used by XUL files so, in general, you will have one
per XUL file. The locale part also contains properties files, which are similar, but are used by
script files. The file browser.properties contains a few such localized strings.
This structure allows you to translate Mozilla or a component into a different language by just
adding a new locale for that language. You don't have to change the XUL part. In addition,
another person could supply a separate package that applies a skin or locale to your content
part, thus providing support for a new theme or language without having to change the original
package.
Other Packages
There is a one special package called toolkit (or global). We saw the global directory earlier for
skins. The file toolkit.jar contains the corresponding content part for it. It contains some global
dialogs and definitions. It also defines the default appearance and functionality of the various
common XUL widgets such as textboxes and buttons. The files located in the global part of a
skin package contain the default look for all of the XUL interface elements. The toolkit package
is used by all XUL applications.
Adding a Package
Mozilla places the packages that are included with the installation in the chrome directory.
However, they do not need to be placed there. If you have another package installed, it can be
placed anywhere on the disk, as long as a manifest file points to it. It is common to place
packages into the chrome directory simply because it is convenient, however they will work just
as well from another directory or somewhere on your local network. You cannot store them on a
remote site, unless the remote site is mounted through the local file system.
There are two chrome directories used for XUL applications, one is installed in the same place
where the application is installed, while the other is part of user's profile. The former allows
packages that are shared by all users while the latter allows packages to be created only for a
specific user or users. Extensions, while installed in a separate extensions directory, are also
usually user specific. Any manifest files located in either chrome directory will be examined to
see which packages are installed.
XUL files can be referenced with a regular HTTP URL (or any type of URL) just like HTML files.
However, packages that are installed into Mozilla's chrome system can be referenced with
special chrome URLs. The packages included with Mozilla will already be installed but you can
register your own.
Installed packages have the advantage that they don't have security restrictions placed on
them, which is necessary for many applications. Another advantage over other URL types is
that they automatically handle mulitple themes and locales. For example, a chrome URL lets
you refer to a file in the theme such as an image without needing to know which theme the user
is using. As long as the filenames are the same in each theme, you can refer to the file using a
chrome URL. Mozilla will take care of determining where the file is located and return the right
data. This also means that it doesn't matter where the package is installed to be able to access
it. The chrome URLs are independent of where the files might physically be located. This makes
it much easier to write applications that have lots of files since you don't have to worry about the
details of locating files.
chrome://<package name>/<part>/<file.xul>
The text <package name> is the package name, such as messenger or editor. The <part> is
either 'content', 'skin' or 'locale' depending on which part you want. 'file.xul' is simply the
filename.
Example: chrome://messenger/content/messenger.xul
The example here refers to the messenger window. You could point to a file that is part of a skin
by replacing 'content' with 'skin' and changing the filename. Similarly, you can point to a file that
is part of a locale by using 'locale' instead of 'content'.
When you open a chrome URL, Mozilla looks through its list of installed packages and tries to
locate the JAR file or directory that matches the package name and part. The mapping between
chrome URLs and JAR files are specified in the manifest files stored in the chrome directory. If
you were to move the file messenger.jar somewhere else and update the manifest file
accordingly, Thunderbird will still work since it doesn't rely on its specific installed location. By
using chrome URLs we can leave details like this to Mozilla. Similarly, if the user changes their
theme, the 'skin' part of the chrome URL translates to a different set of files, yet the XUL and
scripts don't need to change.
Here are some more examples. Note how none of the URLs specify which theme or locale is
used and none specify a specific directory.
chrome://messenger/content/messenger.xul
chrome://messenger/content/attach.js
chrome://messenger/skin/icons/folder-inbox.png
chrome://messenger/locale/messenger.dtd
To refer to subdirectories, you can just add them to the end of the chrome URL. The following
URLs will refer to the bookmarks window, listed both for the Mozilla suite and Firefox, since the
package name is different in both:
chrome://communicator/content/bookmarks/bookmarksManager.xul (Mozilla)
chrome://browser/content/bookmarks/bookmarksManager.xul (Firefox)
You can enter chrome URLs anywhere normal URLs can be used. You can even enter them
directly into the URL bar in a Mozilla browser window. If you enter one of the URLs mentioned
above into the browser's address bar, you should see that window appear like a web page
might do and for the most part will function as if it was a separate window. Some dialog boxes
may not work right, however, as they may be expecting arguments to be supplied from the
window that opened them.
You will also see chrome URLs without specified filenames, such as:
chrome://browser/content/
In this case, only the package name and part is specified. This type of reference will
automatically select an appropriate file from that right directory. For content, a file with the same
name of the package and a xul extension is selected. In the above example, the file browser.xul
is selected. For messenger, messenger.xul would be selected. When creating your own
applications, you will want to create a file for your main window with the same name as the
package, so it can be referred to using this shorter form. This is convenient since all a user
needs to know is the package name to be able to open the application. Of course, for
extensions that modify the browser interface, the user will not need to know the URL, as the
extension will present itself in the UI.
For a skin, the file <package name>.css is selected; for a locale, the file <package name>.dtd is
selected.
Remember, the chrome URL is not related to where it is located on disk. The first two pieces
are the package name and the part (either content, skin or locale). While it is common to put the
content files in a directory called 'content', this is purely out of convention, and these files may
be placed in an entirely different structure.
Contents.rdf Files
In this section, we'll see how to put chrome and XUL files into a package and create the
manifest files for them.
Packages
A package is a set of XUL files and scripts that define the functionality of a user interface.
Packages may be installed into Mozilla and referenced with chrome URLs. A package can
contain any kinds of files and may be split into subdirectories for different parts of the package.
A package can be stored either as a directory or as a JAR archive.
Manifest Files
A manifest file describes a package and maps its location on disk to a chrome URL. The
manifest files in the chrome directory will be examined when a Mozilla application starts up to
see what packages are installed. That means that all you need to do to install a new package is
add a new manifest file either into the application chrome directory or the user specific chrome
directory. This latter chrome directory is normally the one used since the application directory
might not have sufficient permissions to write into it.
If you just want to try testing a privileged XUL code in the Firefox browser, you can do this easily
by just using a manifest with only one line in it:
1. Create a new directory somewhere. For example, on a Windows machine, you might use
C:\testfiles
2. Create a new file called test.manifest in the chrome directory. It doesn't actually matter
what the file is called as long as it has the .manifest extension.
3. Add the following line to it:
The file path in that line should point to the directory created above. If you aren't sure
what the file path is, open that directory in a browser and copy the URL from the address
field.
That's it! Now, all you need to do is add some XUL files into that new directory, and you will be
able to load them by typing in a chrome URL of the form chrome://tests/content/<filename>. Of
course, you will need to restart the browser for the changes to take effect. If the file doesn't load,
make sure that the file path is correct.
The basic syntax of the lines in the manifest file for content packages is:
The first field 'content' indiciates a content package. For themes, 'skin' is used while 'locale' is
used for locales. The packagename is the example above is 'tests', which means that the first
field in the chrome URL is 'tests' as in chrome://tests/content/sample.xul. If the package name
was 'browser', the chrome URL would be chrome://browser/content/. The final field is the path
where the files are located. This can be either a local file path using a file URL or a JAR archive
using a jar URL, which will be described in a moment. You can specify multiple packages by
including another line in the manifest file.
Two packages are listed here, 'branding' and 'browser'. Three overlays are also specified, which
allow content from different packages to combine together. Extensions will make the most use
of overlays, since they merge their UI with the browser UI.
The file paths for the branding and browser packages use jar URLs as the content is packaged
up into an archive. A JAR archive can be created with a ZIP utility. For a JAR file located in the
chrome directory, the syntax is fairly simple:
jar:<filename.jar>!/<path_in_archive>
For the browser package, the archive is browser.jar, located alongside the manifest file in the
chrome directory. The path 'content/browser' specifies the path inside the archive where the
XUL files are located. You won't need to specify a path if you don't have any directories in the
archive. In this case, there is, since the files for the branding package are stored in a different
path in the same archive.
For the 'tests' package created above, the files are not packaged into an archive, so a direct file
path is used instead. This is good for development since you don't have to package up all the
files every time you change them. However, when distributing an application or extension, you
will want to package them into an archive to avoid having to install lots of smaller files.
The xpcnativewrappers=yes part at the end of the manifest line is a flag that may optionally be
used. In JavaScript, it is possible for a web page to override built-in functions with their own
code. If the xpcnativewrappers flag is specified, it indicates that scripts running in a privileged
context don't call these overriden versions, but the original built-in versions instead. Otherwise,
if an extension attempted to call the modified versions, it would likely not work properly, or
worse, create a security hole. This flag was added to prevent this problem and should always
be used for newer extensions, but is left out for older extensions that might not be compatible
with the change.
The themes and locales, the syntax is similar as for content packages, but you also need to
specify the content package you are providing a theme or locale for. For example:
For these, the extra field has been added to indicate that the skin and locale applies to the
browser. The skin name is 'classic/1.0'. In this case, a version number is being used as part of
the theme name, but that is optional if you are making your own theme. Mozilla doesn't handle
the version number in a special way; the version number is just part of the theme name. The
locale is 'en-US'. The chrome URLs that these would map to would be chrome://browser/skin
and chrome://browser/locale. If you were creating your own theme or locale for the browser, all
you need to do is create a manifest file with one of these two lines in it, modified to suit your
theme or locale.
You can also combine all of the three types into a single file if you wish. This may be done when
creating an extension such that all of the parts are in one file. We will do this for the find files
dialog. Create a file findfiles.manifest in the chrome directory. Add the following to the file:
Naturally, you will want to use directory paths suitable for your system. In this case, we are just
creating test directories. If we were distributing the package, we would want to package them up
into a JAR file, and modify the paths. Note how the second field of the skin and locale lines
specifies 'findfiles'. This means that the skin and locale modify the findfiles package, which was
specified on the first line.
The three paths above specify subdirectories for each part. You will want to create these
subdirectories to keep each part's files separate.
Installing a Package
For an application to be installed, you will need to create an installer for it, or include it as part of
another application. The method used depends on what kind of application you are creating. For
extensions, you will need to create an install file install.rdf which describes what will be installed,
the author of the extension and which versions of the browser or other applications it is
compatible with. A specific directory structure is needed as well since extensions are limited in
where the files may be installed to. An extension is packaged up into an XPI file. XPI is short for
XPInstall and is used by Mozilla to install components. Like a JAR file, an XPI file is just a ZIP
file with a different extension, so you can create and view XPI files with a ZIP utility.
Firefox's extension manager handles installing extensions packaged into XPI files automatically.
It is recommended to upload extensions to the Mozilla Add-ons site, where users can locate
them for installation. While they may be installed from any site, other sites are not configured to
allow installations by default.
It is also possible to use a install script written in JavaScript to install files. This allows you to
copy files to any location and perform other file management tasks. However, applications
installed with a script will not be listed in the extension manager and there is no automated
method to uninstall them. For this reason, the install scripts are not used often.
For standalone applications, they can be packaged up using XULRunner. This allows a
separate executable file, and the application may be distributed independently of a browser.
Older Applications
If you are creating applications for older versions of Mozilla software, that is, before Firefox 1.5
or Mozilla 1.8, the process is a bit more involved. The following describes how to set up a
package for earlier versions. This section may be skipped if you are writing new extensions or
XUL applications.
1. Create a directory somewhere on your disk. Many people put this as a subdirectory
inside Mozilla's chrome directory, but this isn't necessary. The directory could be
anywhere and on any disk. Put your XUL files in this directory.
2. Create a file called contents.rdf and place it in this directory. Copy the text in the box
below into the new contents.rdf file. This file is used to identify the application id, its
name, author, version and so on.
<?xml version="1.0"?>
<RDF:RDF xmlns:RDF="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:chrome="http://www.mozilla.org/rdf/chrome#">
<RDF:Seq about="urn:mozilla:package:root">
<RDF:li resource="urn:mozilla:package:myapplication"/>
</RDF:Seq>
<RDF:Description about="urn:mozilla:package:myapplication"
chrome:displayName="Application Title"
chrome:author="Author Name"
chrome:name="myapplication"
chrome:extension="true"/>
</RDF:RDF>
3. Change the highlighted parts of the file above to your own information. The red text
'myapplication' should be the ID of your application. You make this up, but typically, the
ID is similar to your application's name. Replace the blue highlighted text above with your
application's title and author.
4. If the 'chrome:extension' field is true, the application is a Mozilla Firefox Extension and it
will show up in the Extensions window of the browser. If false, it will not appear.
5. Save the contents.rdf and make sure it is in the directory you created in step 1.
6. Open the file <mozilla-directory>/chrome/installed-chrome.txt, where <mozilla-directory>
is the directory where Mozilla is installed. Exit Mozilla before you do this.
7. Next, you are going to register the new application with Mozilla so it will know where to
find it. Add a line at the end of installed-chrome.txt pointing to the new directory you
created in step 1.
content,install,url,file:///main/app/
Change the highlighted text to the file URL of the directory. Make sure that it URL ends
with a slash and that you press enter at the end of the line. If you aren't sure what the
URL is, open the directory created in step 1 into a Mozilla browser and copy the URL
from the location field. Note that the reference should always be a directory, not a file.
8. Delete the file <mozilla-directory>/chrome/chrome.rdf.
9. Start Mozilla. You should be able to view any XUL files you put into the directory using a
URL of the form: chrome://applicationid/content/file.xul where file.xul is the filename.
Your main XUL file should be applicationid.xul which you can load using the shortcut
URL chrome://applicationid/content/.
If you are creating skin and/or locale portions, repeat the steps above, except that the format of
the contents.rdf file is slightly different. Look at the contents.rdf files in other applications for
details.
Creating a chrome package can often be tricky and it is difficult to diagnose problems. Here are
a few tips in case you get stuck.
• Open the file <mozilla-directory>/chrome/chrome.rdf. You should find references to your
application ID in there. If not, something is wrong with registration. If it is there, you are
probably using the wrong chrome URL when you load the file.
• Try deleting the <mozilla-directory>/chrome/chrome.rdf file. It will get regenerated. Also
delete the entire <mozilla-directory>/chrome/overlayinfo/ directory if you are using
overlays.
• Make sure that the URL in the line you added to installed-chrome.txt ends with a slash
and the file itself ends with a blank line.
• On Windows, file URLs are of the form file:///C|/files/app/, where C is the drive letter.
• Make sure the contents.rdf file is in the right directory and is well-formed. Open the
contents.rdf file in Mozilla to see if it parses as well-formed XML. If not, you will see an
error on a yellow background.
• If you are using a debug build of Mozilla, some info will be printed to the terminal when
starting up indicating what chrome applications are being checked. Check if your
application is listed.
2. Simple Elements
Creating a Window
We're going to be creating a simple find files utility throughout this tutorial. First, however, we
should look at the basic syntax of a XUL file.
A XUL file can be given any name but it really should have a .xul extension. The simplest XUL
file has the following structure:
<?xml version="1.0"?>
<?xml-stylesheet href="chrome://global/skin/" type="text/css"?>
<window
id="findfile-window"
title="Find Files"
orient="horizontal"
xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
...
</window>
This window will not do anything since it doesn't contain any UI elements. Those will be added
in the next section. Here is a line by line breakdown of the code above:
1. <?xml version="1.0"?>
This line simply declares that this is an XML file. You would normally add this line as is at
the top of each xul file, much like one would put the HTML tag at the top of an HTML file.
2. <?xml-stylesheet href="chrome://global/skin/" type="text/css"?>
This line is used to specify the style sheets to use for the file. This is the syntax that XML
files use to import style sheets. In this case, we import the styles found in the global/skin
chrome directory. We didn't specify a specific file so Mozilla will determine which files in
the directory to use. In the case, the all-important global.css file is selected. This file
contains all the default declarations for all of the XUL elements. Because XML does not
have any knowledge of how elements should be displayed, the file indicates how.
Generally, you will put this line at the top of every XUL file. You can also import other
style sheets using a similar syntax. Note that you would normally import the global style
sheet from within your own style sheet file.
3. <window
This line declares that you are describing a window. Each user interface window is
described in a separate file. This tag is much like the BODY tag in HTML which
surrounds the entire content. Several attributes can be placed in the window tag -- in this
case there are four. In the example, each attribute is placed on a separate line but they
do not have to be.
4. id="findfile-window"
The id attribute is used as an identifier so that the window can be referred to by scripts.
You will usually put an id attribute on all elements. The name can be anything you want
although it should be something relevant.
5. title="Find Files"
The title attribute describes the text that would appear on the title bar of the window when
it is displayed. In this case the text 'Find Files' will appear.
6. orient="horizontal"
The orient attribute specifies the arrangement of the items in the window. The value
horizontal indicates that items should be placed horizontally across the window. You
may also use the value vertical, which means that the items are placed in a column. This
is the default value, so you may leave the attribute off entirely if you wish to have vertical
orientation.
7. xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
This line declares the namespace for XUL, which you should put on the window element
to indicate that all of its children are XUL. Note that this URL is never actually
downloaded. Mozilla will recognize this URL internally.
8. ...
This is where the elements (the buttons, menus and other user interface components)
would be declared. We'll add some of these in the next set of sections.
9. </window>
And finally, we need to close the window tag at the end of the file.
Opening a Window
In order to open a XUL window, there are several methods that can be used. If you are only in
the development stage, you can just type the URL (whether a chrome:, file: or other URL type)
into the location bar in a Mozilla browser window. You should also be able to double-click the
file in your file manager, assuming that XUL files are associated with Mozilla. The XUL window
will appear in the browser window as opposed to a new window, but this is often sufficient
during the early stages of development.
The correct way, of course, is to open the window using JavaScript. No new syntax is
necessary as you can use the window.open() function as one can do for HTML documents.
However, one additional flag, called 'chrome' is necessary to indicate to the browser that this is
a chrome document to open. This will open the window without the toolbars and menus and so
forth that a normal browser window has. The syntax is described below:
window.open(url,windowname,flags);
Example:
window.open("chrome://navigator/content/navigator.xul", "bmarks",
"chrome,width=600,height=300");
Let's begin by creating the basic file for the find file dialog. Create a file called findfile.xul and put
it in a new directory somewhere. It doesn't matter where you put it, but the
chrome/findfile/content directory is a suitable place. Add the XUL template shown at the top of
this page to the file and save it.
You can use the command-line parameter '-chrome' to specify the XUL file to open when
Mozilla starts. If this is not specified, the default window open will open. (Usually the browser
window.) For example, if you have created a manifest file for the findfiles application in the
chrome directory, we could open the find files dialog with the following:
If you run this command from a command-line (assuming you have one on your platform), the
find files dialog will open by default instead of the Mozilla browser window. Of course, because
we haven't put any UI elements in the window, you won't see a window appear. We'll add some
elements in the next section.
To see the effect though, the following will open the bookmarks window:
The '-chrome' argument doesn't give the file any additional privileges. Instead, it causes the
specified file to open as a top-level window without any browser chrome, such as the address
field or menu. Only chrome URLs have additional privileges.
Adding Buttons
In this section, we will look at how to add some simple buttons to a window.
The window we've created so far has had nothing in it, so it isn't very interesting yet. In this
section, we will add two buttons, a Find button and a Cancel button. We will also learn a simple
way to position them on the window.
Like HTML, XUL has a number of tags that can be used to create user interface elements. The
most basic of these is the button tag. This element is used to create simple buttons.
The button element has two main properties associated with it, a label and an image. You need
one or the other or both. Thus, a button can have a label only, an image only or both a label and
an image. Buttons are commonly used for the OK and Cancel buttons in a dialog, for example.
<button
id="identifier"
class="dialog"
label="OK"
image="images/image.jpg"
disabled="true"
accesskey="t"/>
• id
A unique identifier so that you can identify the button with. You'll see this attribute on all
elements. You'll want to use this if you want to refer to the button in a style sheet or
script. However, you should add this attribute to almost all elements. It isn't always
placed on elements in this tutorial for simplicity.
• class
The style class of the button. This works the same as in HTML. It is used to indicate the
style that the button appears in. In this case the value dialog is used. In most cases, you
will not use a class for a button.
• label
The label that will appear on the button. For example, OK or Cancel. If this is left out, no
text appears.
• image
The URL of the image to appear on the button. If this is attribute is left out, no image
appears. You can also specify the image in a stylesheet using the list-style-image
property.
• disabled
If this attribute is set to true, the button is disabled. This is usually drawn with the text in
grey. If the button is disabled, the function of the button cannot be performed. If this
attribute is left out entirely, the button is enabled. You can switch the disabled state of the
button using JavaScript.
• accesskey
This should be set to a letter that is used as a shortcut key. This letter should appear in
the label text and will typically be drawn underlined. When the user presses ALT (or a
similar key that varies on each platform) and the access key, the button will be focused
from anywhere in the window.
Note that a button supports more attributes than those listed above. Others will be discussed
later. Some examples of buttons:
<button label="Normal"/>
<button label="Disabled" disabled="true"/>
The examples above will generate the buttons in the image. The first button is a
normal button. The second button is disabled so it appears greyed out.
We'll start by creating a simple Find button for the find files utility. The example
code below shows how to do this.
Note that Firefox doesn't allow you to open chrome windows from web pages, so the View links
in the tutorial will open in normal browser windows. Due to this, the buttons will appear to
stretch across the window. You can add align="start" to the window tag to prevent the
stretching.
Let's add this code to the file findfile.xul that we created in the previous section. The code needs
to be inserted in-between the window tags. The code to add is shown in red below:
<?xml version="1.0"?>
<?xml-stylesheet href="chrome://global/skin/" type="text/css"?>
<window
id="findfile-window"
title="Find Files"
orient="horizontal"
xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
<button id="find-button" label="Find"/>
<button id="cancel-button" label="Cancel"/>
</window>
You'll notice that the Cancel button was added also. The window has been given a horizontal
orientation so that the two buttons appear beside each other. If you open the file in Mozilla, you
should get something like the image shown here.
Note that we shouldn't put text labels directly in the XUL file. We should
use entities instead so that text can be easily translated.
Text Elements
You cannot embed text directly into a XUL file without tags around it and expect it to be
displayed. The most basic way to include text in a window is to use the label element. An
example is shown below:
The value attribute can be used to specify the text that you wish to have displayed. The text will
not wrap, so the text will appear on only one line. This is suitable for short sections of text.
For longer text, you can place content inside opening and closing description tags. Unlike text
specified with the label element and the value attribute, the child text will wrap onto multiple
lines if necessary. Resize the window to see the wrapping. Like HTML, line breaks and extra
whitespace are collapsed into a single space. Later we'll find out how to constrain the width of
elements so that we can see the wrapping more easily.
<description>
This longer section of text is displayed.
</description>
Internally, both the label element and the description element are the same, which means that
labels can have wrapped text if you place the text inside the tag, and descriptions can have a
value attribute. The label element is intended for labels of controls, such as text fields. The
description element is intended for other descriptive text such as informative text at the top of a
dialog box. By convention, you should follow this guideline.
You can use the control attribute to set which control the label is associated with. When the user
clicks the label, that control will be focused. Set the value of the control attribute to the id of the
element to be focused.
In the example above, clicking the label will cause the button to be focused.
Images
Like HTML, XUL has an element to display images within a window. This element is
appropriately named image. Note that the tag name is different than HTML (image instead of
img). You can use the src attribute to specify the URL of the image file. The example below
shows this:
<image src="images/banner.jpg"/>
Although you can use this syntax, it would be better to use a style sheet to set the image URL.
A later section will describe how to use style sheets, but an example will be shown here for
completeness. You can use the list-style-image CSS property to set the URL for the image.
Here are some examples:
XUL:
<image id="image1"/>
<image id="search"/>
Style Sheet:
#image1 {
list-style-image: url("chrome://findfile/skin/banner.jpg");
}
#search {
list-style-image: url("chrome://findfile/skin/images/search.jpg");
}
These images come from within the chrome directory, in the skin for the findfile package.
Because images vary by skin, you would usually place images in the skin directory.
Input Controls
XUL has elements that are similar to the HTML form controls.
HTML has an input element which can be used for text entry controls. XUL has a similar
element, textbox, used for text entry fields. Without any attributes, the textbox element creates a
box in which the user can enter text. Textboxes accept many of the same attributes as HTML
input controls. The following are some of them:
• id
A unique identifier so that you can identify the textbox.
• class
The style class of the textbox.
• value
If you want the textbox to have default text, supply it with the value attribute.
• disabled
Set to true to have text entry disabled.
• type
You can set this attribute to the special value password to create a textbox that hides
what it types. This is usually used for password entry fields.
• maxlength
The maximum number of characters that the textbox allows.
Note that while in HTML, several different kinds of fields can be created with the input element,
in XUL there are separate elements for each type. The following example shows some
textboxes:
The textbox examples above will create text inputs that can only be used for entering one line of
text. HTML also has a textarea element for creating a larger text entry area. In XUL, you can
use the textbox element for this purpose as well -- two separate elements are not necessary. If
you set the multiline attribute to true, the text entry field will display multiple rows.
For example:
<textbox multiline="true"
value="This is some text that could wrap onto multiple lines."/>
Like the HTML textarea, you can use the rows and cols attributes to set the size. This should be
set to the number of rows and columns of characters to display.
Let's add a search entry field to the find file dialog. We'll use the textbox element.
Add these lines before the buttons we created in the last section. If you open this window, you
will see something much like that shown in the image below.
Two additional elements are used for creating check boxes and radio buttons. They are
variations of buttons. The checkbox is used for options that can be enabled or disabled. Radio
buttons can be used for a similar purpose when there are a set of them where only one can be
selected at once.
You can use most of the same attributes on checkboxes and radio buttons as with buttons. The
example below shows some simple checkboxes and radio buttons.
The first line creates a simple checkbox. When the user clicks the checkbox, it switches
between checked and unchecked. The checked attribute can be used to indicate the default
state. You should set this to either true or false. The label attribute can be used to assign a label
that will appear beside the check box. For radio buttons, you should use the selected attribute
instead of the checked attribute. Set it to true to have a radio button selected by default, or
leave it out for other radio buttons.
In order to group radio buttons together, you need to use the radiogroup element. Only one of
the radio buttons in a radio group can be selected at once. Clicking one will turn off all of the
others in the same group. The following example demonstrates this.
<radiogroup>
<radio id="orange" label="Orange"/>
<radio id="violet" selected="true" label="Violet"/>
<radio id="yellow" label="Yellow"/>
</radiogroup>
Like buttons, check boxes and radio buttons are made up of a label and an image, where the
image switches between checked and unchecked when it is pressed. Check boxes have many
of the same attributes as buttons:
• label
The label on the check box or radio button.
• disabled
Set this to either true or false to disable or enable the check box or radio button.
• accesskey
The shortcut key that can be used to select the element. The letter specified is usually
drawn underlined in the label.
List Controls
XUL has a number of types of elements for creating list boxes.
List Boxes
A list box is used to display a number of items in a list. The user may select an item from the
list.
XUL provides two types of elements to create lists, a listbox element to create multi-row list
boxes, and a menulist element to create drop-down list boxes. They work similar to the HTML
select element, which performs both functions, but the XUL elements have additional features.
The simplest list box uses the listbox element for the box itself, and the listitem element for each
item. For example, this list box will have four rows, one for each item.
<listbox>
<listitem label="Butter Pecan"/>
<listitem label="Chocolate Chip"/>
<listitem label="Raspberry Ripple"/>
<listitem label="Squash Swirl"/>
</listbox>
Like with the HTML option element, you can assign a value for each item
using the value attribute. You can then use the value in a script. The list
box will default to a suitable size, but you can control the size with the
rows attribute. Set it to the number of rows to display in the list box. A
scroll bar will appear that the user can use to display the additional rows.
<listbox rows="3">
<listitem label="Butter Pecan" value="bpecan"/>
<listitem label="Chocolate Chip" value="chocchip"/>
<listitem label="Raspberry Ripple" value="raspripple"/>
<listitem label="Squash Swirl" value="squash"/>
</listbox>
The example has been changed to display only 3 rows at a time. Values have also been added
to each item in the list. List boxes have some additional features, which will be described later.
The listbox also supports multiple columns. Each cell may have arbitrary content within it,
although usually only text is used. When the user selects an item in the list, the entire row is
selected. You cannot have a single cell selected.
Two tags are used to specify the columns in the listbox. The listcols element is used to hold the
column information, each of which is specified using a listcol element. You will need one listcol
element for each column in the listbox.
The listcell element may be used for each cell in a row. If you want to have three columns, you
will need to add three listcell elements inside each listitem. To specify the text content of a cell,
place a label attribute on a listcell. For the simple case where there is only one column, you may
also place the label attributes directly on the listitem elements and leave the listcell elements out
entirely, as was seen in the earlier listbox examples.
The following example is of a listbox with two columns and three rows:
<listbox>
<listcols>
<listcol/>
<listcol/>
</listcols>
<listitem>
<listcell label="George"/>
<listcell label="House Painter"/>
</listitem>
<listitem>
<listcell label="Mary Ellen"/>
<listcell label="Candle Maker"/>
</listitem>
<listitem>
<listcell label="Roger"/>
<listcell label="Swashbuckler"/>
</listitem>
</listbox>
Header Rows
List boxes also allow a special header row to be used. This is much like a regular row except
that it is displayed differently. You would use this to create column headers. Two new elements
are used.
The listhead element is used for the header rows, just as the listitem element is used for regular
rows. The header row is not a normal row however, so using a script to get the first row in the
list box will skip the header row.
The listheader element is used for each cell in the header row. Use the label attribute to set the
label for the header cell.
<listbox>
<listhead>
<listheader label="Name"/>
<listheader label="Occupation"/>
</listhead>
<listcols>
<listcol/>
<listcol flex="1"/>
</listcols>
<listitem>
<listcell label="George"/>
<listcell label="House Painter"/>
</listitem>
<listitem>
<listcell label="Mary Ellen"/>
<listcell label="Candle Maker"/>
</listitem>
<listitem>
<listcell label="Roger"/>
<listcell label="Swashbuckler"/>
</listitem>
</listbox>
Drop-down Lists
Drop-down lists can be created in HTML using the select element. The user can see a single
choice in a textbox and may click the arrow or some similar such button next to the textbox to
make a different selection. The other choices will appear in a pop-up window. XUL has a
menulist element which can be used for this purpose. It is made from a textbox with a button
beside it. Its name was chosen because it pops up a menu with the choices in it.
Three elements are needed to describe a drop-down box. The first is the menulist element,
which creates the textbox with the button beside it. The second, menupopup, creates the popup
window which appears when the button is clicked. The third, menuitem, creates the individual
choices.
<menulist label="Bus">
<menupopup>
<menuitem label="Car"/>
<menuitem label="Taxi"/>
<menuitem label="Bus" selected="true"/>
<menuitem label="Train"/>
</menupopup>
</menulist>
This menulist will contain four choices, one for each menuitem element. To show
the choices, click the arrow button on the menulist. When one is selected, it
appears as the choice in the menulist. The selected attribute is used to indicate the
value that is selected by default.
By default, you can only select choices from the list. You cannot enter your own
text by typing it in. A variant of the menulist allows editing the text in the field. For
example, the URL field in the browser has a drop-down for selecting previously typed URLs, but
you can also type them in yourself.
<menulist editable="true">
<menupopup>
<menuitem label="www.mozilla.org"/>
<menuitem label="www.xulplanet.com"/>
<menuitem label="www.dmoz.org"/>
</menupopup>
</menulist>
The URL field created here has three pre-populated choices that the user can select or they can
enter one of their own by typing it into the field. The text the user enters is not added as a new
choice. Because the label attribute was not used in this example, the default value will be blank.
Progress Meters
In this section, we'll look at creating progress meters.
A progress meter is a bar that indicates how much of a task has been completed. You typically
see it when downloading files or when performing a lengthy operation. XUL has a progress
meter element which can be used to create these. There are two types of progress meters:
determinate and undeterminate.
Determinate progress meters are used when you know the length of time that an operation will
take. The progress meter will fill up and, once full, the operation should be finished. This can be
used for the download file dialog as the size of the file is known.
Undeterminate progress meters are used when you do not know the length of time of an
operation. The progress meter will have an animation such as a spinning barber pole or a
sliding box, depending on the platform and theme used.
<progressmeter
id="identifier"
mode="determined"
value="0%"/>
• id
The unique identifer of the progress meter
• mode
The type of the progress meter. If this is set to determined, the progress meter is a
determinate progress meter where it fills up as the task completes. If this is set to
undetermined, the progress meter is undeterminate where you do not know the length
of time. The value determined is the default if you do not specify this attribute.
• value
The current value of the progress meter. You should only use this for a progress meter
that is determinate. The value should be set to a percentage from 0% to 100%. The value
would be changed by a script as the task completes.
Let's add a progress meter to our find file dialog. We would normally put an undeterministic
progress meter as we don't know how many files we'll be searching or how long the search will
take. However, we'll add a normal one for now as an animating one can be distracting during
development. The progress meter would normally only appear while the search is taking place.
We'll add a script later to turn it on and off.
<hbox>
<spacer flex="1"/>
The value has been set to 50% so that we can see the meter on the window. A margin has
been set to 4 pixels so that it is separated from the edge of the window. As was stated earlier,
we only want the progress bar to be displayed while the search was occuring. A script will show
and hide it as necessary.
In addition to all of the XUL elements that are available, you can also add HTML elements
directly within a XUL file. You can actually use any HTML element in a XUL file, meaning that
Java applets and tables can be placed in a window. You should avoid using HTML elements in
XUL files if you can. However, this section will describe how to use them anyways. Remember
that XML is case-sensitive though, so you'll have to enter the tags and attributes in lowercase.
In order to use HTML elements in a XUL file, you must declare that you are doing so using the
XHTML namespace. This way, Mozilla can distinguish the HTML tags from the XUL ones. The
attribute below should to be added to the window tag of the XUL file, or to the outermost HTML
element.
xmlns:html="http://www.w3.org/1999/xhtml"
This is a declaration of HTML much like the one we used to declare XUL. This must be entered
exactly as shown or it won't work correctly. Note that Mozilla does not actually download this
URL, but it does recognize it as being HTML.
<?xml version="1.0"?>
<?xml-stylesheet href="chrome://global/skin/" type="text/css"?>
<window
id="findfile-window"
title="Find Files"
orient="horizontal"
xmlns:html="http://www.w3.org/1999/xhtml"
xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
Then, you can use HTML tags as you would normally, keeping in mind the following:
• You must add a html: prefix to the beginning of each tag, assuming you declared the
HTML namespace as above.
• The tags must be entered in lowercase.
• Quotes must be placed around all attribute values.
• XML requires a trailing slash at the end of tags that have no content. This may be clearer
from the examples below.
You can use any HTML tag although some such as head and body are not really useful. Some
examples of using HTML elements are shown below.
<html:img src="banner.jpg"/>
<html:table>
<html:tr>
<html:td>
A simple table
</html:td>
</html:tr>
</html:table>
These examples will create an image from the file banner.jpg, a checkbox and a single-cell
table. You should always use XUL features if they are available and you probably should not
use tables for layout in XUL. (There are XUL elements for doing layout). Notice that the prefix
html: was added to the front of each tag. This is so that Mozilla knows that this is an HTML tag
and not a XUL one. If you left out the html: part, the browser would think that the elements were
XUL elements and they would not display because img, input, table, and so on are not valid
XUL tags.
In XUL, you can add labels with the description or label element. You should use these
elements when you can. You can also add labels to controls either by using the HTML label
element, or you can simply put the text inside another HTML block element (such as p or div) as
in the example below.
<html:p>
Search for:
<html:input id="find-text"/>
<button id="okbutton" label="OK"/>
</html:p>
This code will cause the text 'Search for:' to be displayed, followed by an input element and an
OK button. Notice that the XUL button can appear inside the HTML elements, as it does here.
Plain text will only be displayed when placed inside an HTML element that would normally allow
you to display text (such as a p tag). Text outside of one will not be displayed, unless the XUL
element the text is inside allows this (the description element, for example). The examples
below may help.
What follows are some examples of adding HTML elements to windows. In each case, the
window and other common information has been left out for simplicitiy.
<html:p>
Click the box below to remember this decision.
<html:p>
<html:input id="rtd" type="checkbox"/>
<html:label for="rtd">Remember This Decision</html:label>
</html:p>
</html:p>
In this case, one p tag was used to place the text in and another was used to break apart the
text into multiple lines.
2. Text outside of HTML blocks
<html:div>
Would you like to save the following documents?
<html:hr/>
</html:div>
Expense Report 1
What I Did Last Summer
<button id="yes" label="Yes"/>
<button id="no" label="No"/>
As can be seen in the image, the text inside the div tag was displayed but the other text
(Expense Report 1 and What I Did Last Summer) was not. This is because there is no HTML or
XUL element capable of displaying text enclosing it. To have this text appear, you would need
to put it inside the div tag, or enclose the text in a description tag.
<html:po>Case 1</html:po>
<div>Case 2</div>
<html:description value="Case 3"/>
All three of the cases above will not display, each for a different reason.
Case 1: po is not a valid HTML tag and Mozilla has no idea what to do with it.
Case 2: div is valid but only in HTML. To get it to work, you will need to add the html: qualifier.
Case 3: A description element is only valid in XUL and not in HTML. It should not have the html:
qualifier.
Using Spacers
In this section, we'll find out how to add some spacing in between the elements we have
created.
Adding Spacers
One of the problems with developing user interfaces is that each user has a different display.
Some users may have larger displays with higher resolutions and others may have lower
resolutions. In addition, different platforms may have special requirements on the user interface.
If adding support for multiple languages, the text for one language may require more room than
another.
Applications that need to support multiple platforms and languages usually have their windows
laid out with lots of space to allow for this. Some platforms and user interface toolkits provide
components that are smart enough to resize and re-position themselves to fit the needs of the
user. (Java uses layout managers for example.)
XUL provides the capability for elements to position and resize automatically. As we've seen,
the find files window has appeared in a size that will fit the elements inside it. Each time we add
something, the window gets bigger.
XUL uses a layout system called the 'Box Model'. We'll talk more about this is the next section
but it essentially allows you to divide a window into a series of boxes that hold elements. The
boxes will be positioned and resize based on specifications that you can define. For now, just
know that the window element is a type of box.
Before we get into detail about boxes, we'll introduce another XUL element that is useful for
layout, the spacer. A spacer is very simple and only requires one attribute, which will be
explained in a moment. The simplest spacer looks like the following:
<spacer flex="1"/>
A spacer is used to place blank space into a window. Its most useful ability is that is can grow or
shrink as the user resizes the window. This would be how one would place buttons on the right
or bottom of a window and have them stick to the right or bottom edge no matter what size the
window is. As we'll see, you can use a series of spacers to create a number of layout effects.
In this syntax above, the spacer has one attribute, called flex. This is used to define the
flexibility of the spacer. In the case above, the spacer has a flex of 1. This makes the spacer
element stretchy. If you place a spacer directly inside a window, the spacer will grow in size
when the size of the window is changed.
We'll add a spacer to our find file dialog soon. First, let's take a look at what happens when the
current dialog is resized.
If you change the size of the find files window, you can see that the elements have remained
where they started. None of them have been moved or resized even though the window has
more room in it. Let's see what happens when a spacer is added between the text box and the
Find button.
By adding a spacer and resizing the window, you can see that the spacer has expanded to fill
the space. The buttons have been pushed over. The code to add a spacer is shown below.
Insert it just before the Find button.
<spacer flex="1"/>
XUL lays out elements on a window by calculating suitable widths and heights for the elements
and then adding space where they are flexible. Unless you specify information about the width
and height of an element, the default size of an element is determined by its contents. You'll
notice that the Cancel button in the dialogs has always set its width so that it fits the text inside
it. If you create a button with a very long label, the button's default size will be large enough to
hold the entire label. Other elements, such as the text box have chosen a suitable default size.
The flex attribute is used to specify if an element can change size to fit the box (in this case, the
window) it is in. We've already seen the flex attribute applied to spacers, but it can be applied to
any element. For example, you might want to have the Find button resize instead.
As you can see in the image, by placing a flex attribute on the Find button, it resizes when the
window does. A spacer is really nothing special. It could really be considered a hidden button. It
works much the same way as a button except it does not draw on screen.
You may have noticed something about the image above. Not only did the Find button grow in
size but some space has appeared between the main label and the button. Of course, this is the
spacer that we put in earlier. It has resized itself also. If you look closely enough, you should
notice that the change in size has been divided up equally between the spacer and the button.
The spacer has received half of the free space and the button has received the other half.
The reason we're seeing this effect is that both the spacer and the Find button have a flex
attribute. Because both are flexible, both the button and the spacer resize equally.
What if you want to set one element to grow twice as large an another? You can use a higher
number as the value of the flex attribute. The values of the flex element are a ratio. If one
element has a flex of 1 and the next one has a flex of 2, the second one will grow at twice the
rate of the first one. In effect, a flex of 2 says that this element has a flex that is two times the
elements that have a flex of one.
The flex attribute isn't used to specify an actual size. Instead, it specifies how empty space it
divided among the children of a container box. We'll look at boxes in the next section. Once the
default sizes of the children of a box are determined, the flexibility values are used to divide up
the remaining empty space in the box. For example, if a box is 200 pixels wide and contains two
flexible buttons, the first 50 pixels and the other 90 pixels, there will be 60 pixels of space left
over. If both buttons have a flex value of 1, the space will be divided evenly with 30 extra pixels
of width going to each button. If the second button's flexibility was increased to 2, the first button
would receive 20 pixels of the extra space and the second button would receive 40 pixels of
extra space instead.
The flex attribute can be placed on any element, however it only has any meaning when placed
on an element directly inside a XUL box. This means that even though you can place a flex on
an HTML element, it will have no effect if that element is inside a non-box element.
Example 1:
<button label="Find" flex="1"/>
<button label="Cancel" flex="1"/>
Example 2:
<button label="Find" flex="1"/>
<button label="Cancel" flex="10"/>
Example 3:
<button label="Find" flex="2"/>
<button label="Replace"/>
<button label="Cancel" flex="4"/>
Example 4:
<button label="Find" flex="2"/>
<button label="Replace" flex="2"/>
<button label="Cancel" flex="3"/>
Example 5:
<html:div>
<button label="Find" flex="2"/>
<button label="Replace" flex="2"/>
</html:div>
Example 6:
<button label="Find" flex="145"/>
<button label="Replace" flex="145"/>
Example 1: in this case the flexibility is divided up evenly between both buttons. Both buttons
will change size evenly.
Example 2: here, both buttons will grow, but the Find button will grow ten times as much as the
Cancel button, because it has a flex value that is 10 times the flex value of the Find button.
Available space will be divided into one part for the Find button and 10 parts for the Cancel
button.
Example 3: only two of the buttons are marked as flexible here. The Replace button will never
change size but the other two will. The Cancel button will always resize twice as large as the
Find button because its flex value is twice as large.
Example 4: in this case, all three buttons are flexible. Both the Find and Replace buttons will be
the same size but the Cancel button will be somewhat larger (50% larger to be exact).
Example 5: here, the two buttons are placed inside a div element. Flexibility is meaningless
here as the buttons are not directly in a box. The effect would be the same if the flex attributes
were left out.
Example 6: because the flex values are the same on both buttons, their will flex equally. This
would work just as well with flex values of one instead of 145. There's no difference in this case.
It is recommended that you use lower numbers for readability.
Note that other factors such as the button labels and button minimum sizes will affect the actual
sizes of the buttons. For instance, a button won't shrink less than the space needed to fit its
label.
Specifying a flex value of 0 has the same effect as leaving the flex attribute out entirely. It
means that the element is not flexible at all. You may also sometimes see a flex value specified
as a percentage. This has no special meaning and is treated as if the percent sign was not
there.
You may have noticed that when you resize the find file dialog vertically, the buttons resize
themselves to fit the height of the window. This is because all of the buttons have an implied
vertical flex given to them by the window. In the next section we'll learn how to change this.
Adding an Image
You can add an image to a button by specifying a URL in the image attribute. The image is
loaded from the URL, which can be a relative or absolute URL, and then the image is displayed
on the button.
The button below will have both a label and the image 'happy.png'. The image will appear to the
left of the label. You can change this position by using two other attributes. This will be
explained in a moment.
Another way to specify the image by using the CSS list-style-image style property on the button.
This is designed to allow the 'skin' (in this case, the appearance of the image) to be changed
without changing the XUL file. An example is shown below.
<button id="find-button"
label="Find" style="list-style-image: url('happy.png')"/>
In this case, the image 'happy.png' is displayed on the button. The style attribute functions
similar to its HTML counterpart. In general, it can be used on all XUL elements. Note that you
really should put the style declarations in a separate style sheet.
By default, the image on a button will appear to the left of the text label. There are two attributes
that can be used to control this position.
The dir attribute controls the direction of the image and text. By setting this attribute to the value
reverse, the image will be placed on the right side of the text. By using the value normal, or
leaving the attribute out entirely, the image will be placed on the left side of the text.
The orient attribute can be used to place the image above or below the text. The default value is
horizontal which is used to place the image on the left or right. You can also use the value
vertical to place the image above or below. In this case, the dir attribute controls the placement
above or below. The same values are used, where normal means place the image above the
text, and reverse means place the image below the text.
Buttons may have arbitrary markup contained inside them, and it will be rendered inside the
button. You probably wouldn't use this very often, but you might use it when creating custom
elements.
For example, the following will create a button where two of the words are red:
<button>
<description value="This is a"/>
<description value="rather strange" style="color: red;"/>
<description value="button"/>
</button>
Any XUL element may be placed inside the button. HTML elements will be ignored, so you need
to wrap them inside a description element. If you specify the label attribute on the button, it will
override any content placed inside the button.
You can place a menupopup inside the button to cause a menu to drop down when the button is
pressed, much like the menulist. However, in this case you must set the type attribute to the
value menu.
In this example, the user may click the button to pop up a menu containing three items. Note
that selecting one of these menu items doesn't change the label on the button, unlike a
menulist. This type of button is intended to be used like a menu, with scripts attached to each
item to perform a task. We'll see more on menus later.
You can also set the type attribute to the value menu-button. This also creates a button with a
menu, but the appearance will be different. The image to the right shows the difference. The left
one is a 'menu' and the second one is a 'menu-button'. It has an arrow indicating the presence
of a menu. For the 'menu', the user may click anywhere on the
button to show the menu. For the 'menu-button', the user must click
the arrow to show the menu.
Introduction to Boxes
The main form of layout in XUL is called the 'Box Model'. This model allows you to divide a
window into a series of boxes. Elements inside of a box will orient themselves horizontally or
vertically. By combining a series of boxes, spacers and elements with flex attributes, you can
control the layout of a window.
Although a box is the fundamental part of XUL element layout, it follows a few very simple rules.
A box can lay out its children in one of two orientations, either horizontally or vertically. A
horizontal box lines up its elements horizontally and a vertical box orients its elements vertically.
You can think of a box as one row or one column from an HTML table. Various attributes placed
on the child elements in addition to some CSS style properties control the exact position and
size of the children.
<hbox>
...
</hbox>
<vbox>
...
</vbox>
The hbox element is used to create a horizontally oriented box. Each element placed in the
hbox will be placed horizontally in a row. The vbox element is used to create a vertically
oriented box. Added elements will be placed underneath each other in a column.
There is also a generic box element which defaults to horizontal orientation, meaning that it is
equivalent to the hbox. However, you can use the orient attribute to control the orientation of the
box. You can set this attribute to the value horizontal to create a horizontal box and vertical to
create a vertical box.
<vbox>
<box orient="vertical">
The three buttons here are oriented vertically as was indicated by the box. To
change the buttons so that they are oriented horizontally, all you need to do is
change the vbox element to a hbox element.
You can add as many elements as you want inside a box, including other boxes.
In the case of a horizontal box, each additional element will be placed to the right
of the previous one. The elements will not wrap at all so the more elements you
add, the wider the window will be. Similarly, each element added to a vertical box will be placed
underneath the previous one. The example below shows a simple login prompt:
<vbox>
<hbox>
<label control="login" value="Login:"/>
<textbox id="login"/>
</hbox>
<hbox>
<label control="pass" value="Password:"/>
<textbox id="pass"/>
</hbox>
<button id="ok" label="OK"/>
<button id="cancel" label="Cancel"/>
</vbox>
If you look closely at the image of the login dialog, you can see that the two textboxes are not
aligned with each other horizontally. It would probably be better if they were. In order to do this
we need to add some additional boxes.
<vbox>
<hbox>
<vbox>
<label control="login" value="Login:"/>
<label control="pass" value="Password:"/>
</vbox>
<vbox>
<textbox id="login"/>
<textbox id="pass"/>
</vbox>
</hbox>
<button id="ok" label="OK"/>
<button id="cancel" label="Cancel"/>
</vbox>
Notice how the text boxes are now aligned with each other. To do this, we needed to add boxes
inside of the main box. The two labels and textboxes are all placed inside a horizontal box.
Then, the labels are placed inside another box, this time a
vertical one, as are the textboxes. This inner box is what makes
the elements orient vertically. The horizontal box is needed as
we want the labels vbox and the inputs vbox to be placed
horizontally with each other. If this box was removed, both
textboxes would appear below both of the labels.
Let's add some boxes to the find files dialog. A vertical box will be added around all of the
elements, and a horizontal box with be added around the textbox and the buttons. The result
will be that the buttons will appear below the textbox.
<vbox flex="1">
<description>
Enter your search criteria below and select the Find button to begin
the search.
</description>
<hbox>
<label value="Search for:" control="find-text"/>
<textbox id="find-text"/>
</hbox>
<hbox>
<spacer flex="1"/>
The vertical box causes the main text, the box with the textbox and the box with the buttons to
orient vertically. The inner boxes orient their elements horizontally. As you see in the image
below, the label and text input are placed side by side. The spacer and two buttons are also
placed horizontally in their box. Notice how the spacer causes the buttons to appear on the right
side, because it is flexible.
Element Positioning
Here we'll look at controlling the position and size of an element.
So far, we know how to position elements either horizontally or vertically inside a box. We will
often need more control over the position and size of elements within the box. For this, we first
need to look at how a box works.
The position of an element is determined by the layout style of its container. For example, the
position of a button in a horizontal box is to the right of the previous button, if any. The size of
an element is determined by two factors, the size that the element wants to be and the size you
specify. The size that an element wants to be is determined by what is in the element. For
example, a button's width is determined by the amount of text inside the button.
An element will generally be as large as it needs to be to hold its contents, and no larger. Some
elements, such as textboxes have a default size, which will be used. A box will be large enough
to hold the elements inside the box. A horizontal box with three buttons in it will be as wide as
the three buttons, plus a small amount of padding.
In the image below, the first two buttons have been given a suitable size to hold their text. The
third button is larger because it contains more content. The width of the box containing the
buttons is the total width of the buttons plus the padding between them. The height of the
buttons is a suitable size to hold the text.
You may need to have more control over the size of an element in a window. There are a
number of features that allow you to control the size of an element. The quick way is to simply
add the width and height attributes on an element, much like you might do on an HTML img tag.
An example is shown below:
However, it is not recommended that you do this. It is not very portable and may not fit in with
some themes. A better way is to use style properties, which work similarly to style sheets in
HTML. The following CSS properties can be used.
• width
This specifies the width of the element.
• height
This specifies the height of the element.
By setting either of the two properties, the element will be created with that width and height. If
you specify only one size property, the other is calculated as needed. The size of these style
properties should be specified as a number followed by a unit.
The sizes are fairly easy to calculate for non-flexible elements. They simply obey their specified
widths and heights, and if the size wasn't specified, the element's default size is just large
enough to fit the contents. For flexible elements, the calculation is slightly trickier.
Flexible elements are those that have a flex attribute set to a value greater than 0. Recall that
flexible elements grow and shrink to fit the available space. Their default size is still calculated
the same as for inflexible elements. The following example demonstrates this:
<window orient="horizontal"
xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
<hbox>
<button label="Yes" flex="1"/>
<button label="No"/>
<button label="I really don't know one way or the other"/>
</hbox>
</window>
The window will initially appear like in the image earlier. The first two buttons will be sized at a
suitable default width and the third button will be larger because it has a longer label. The first
button is made flexible and all three elements have been placed inside a box. The width of the
box will be set to the initial total width of all three buttons (around 430 pixels in the image).
If you increase the width of the window, elements are checked to see whether they are flexible
to fill the blank space that would appear. The button is the only flexible element, but it will not
grow wider. This is because the box that the button is inside is not flexible. An inflexible element
never changes size even when space is available, so the button can't grow either. Thus, the
button won't get wider.
The solution is to make the box flexible also. Then, when you make the window wider, extra
space will be available, so the box will grow to fill the extra space. Because the box is larger,
more extra space will be created inside it, and the flexible button inside it will grow to fit the
available space. This process repeats for as many nested boxes as necessary.
You may want to allow a element to be flexible but constrain the size so that it cannot be larger
than a certain size. Or, you may want to set a minimum size. You can set this by using four
attributes:
• minwidth
This specifies the minimum width that the element can be.
• minheight
This specifies the minimum height that the element can be.
• maxwidth
This specifies the maximum width that the element can be.
• maxheight
This specifies the maximum height that the element can be.
The values are always measured in pixels. You can also use the corresponding CSS properties,
min-width, min-height, max-width and max-height.
These properties are only useful for flexible elements. By setting a maximum height, for
example, a stretchy button will only grow to a certain maximum height. You will still be able to
resize the window beyond that point but the button will stop growing in size. The box the button
is inside will also continue to grow, unless you set a maximum height on the box also.
If two buttons are equally flexible, normally both will share the amount of extra space. If one
button has a maximum width, the second will still continue to grow and take all of the remaining
space.
If a box has a maximum width or height, the children cannot grow larger than that maximum
size. If a box has a minimum width or height, the children cannot shrink smaller than that
minimum size. Here are some examples of setting widths and heights:
<button label="1" style="width: 100px;"/>
<button label="2" style="width: 100em; height: 10px;"/>
<button label="3" flex="1" style="min-width: 50px;"/>
<button label="4" flex="1" style="min-height: 2ex; max-width: 100px"/>
<textbox flex="1" style="max-width: 10em;"/>
<description style="max-width: 50px">This is some boring but simple
wrapping text.</description>
Example 1: the first button will be displayed with a width of 100 pixels (px means pixels). You
need to add the unit or the width will be ignored.
Example 2: the second button will be displayed with a height of ten pixels and a width of 100
ems (an em is the size of a character in the current font).
Example 3: the third button is flexible so it will grow based on the size of the box the button is
in. However, the button will never shrink to be less than 50 pixels. Other flexible components
such as spacers will absorb the remaining space, breaking the flex ratio.
Example 4: the fourth button is flexible and will never have a height that is smaller than 2 ex (an
ex is usually the height of the letter x in the current font) or wider than 100 pixels.
Example 5: the text input is flexible but will never grow to be larger than 10 ems. You will often
want to use ems when specifying sizes with text in them. This unit is useful for textboxes so that
the font can change and the textboxes would always be a suitable size, even if the font is very
large.
Example 6: the description element is constrained to have a maximum width of 50 pixels. The
text inside will wrap to the next line, after fifty pixels.
Let's add some of these styles to the find files dialog. We'll make it so that the textbox will resize
to fit the entire window.
Here, the text input has been made flexible. This way, it will grow if the user changes the size of
the dialog. This is useful if the user wants to enter a long string of text. Also, a minimum width of
15 ems has been set so that the text box will always show at least 15 characters. If the user
resizes the dialog to be very small, the text input will not shrink past 15 ems. It will be drawn as
if it extends past the edge of the window. Notice in the image below that the text input has
grown to extend to the full size of the window.
Box Packing
Let's say you have a box with two child elements, both of which are not flexible, but the box is
flexible. For example:
<box flex="1">
<button label="Happy"/>
<button label="Sad"/>
</box>
If you resize the window, the box will stretch to fit the window size. The buttons are not flexible,
so they will not change their widths. The result is extra space that will appear on the right side of
the window, inside the box. You may wish, however, for the extra space to appear on the left
side instead, so that the buttons stay right aligned in the window.
You could accomplish this by placing a spacer inside the box, but that gets messy when you
have to do it numerous times. A better way is to use an additional attribute pack on the box.
This attribute indicates how to pack the child elements inside the box. For horizontally oriented
boxes, it controls the horizonal positioning of the children. For vertically oriented boxes, it
controls the vertical positioning of the children. You can use the following values:
• start
This positions elements at the left edge for horizontal boxes and at the top edge for
vertical boxes. This is the default value.
• center
This centers the child elements in the box.
• end
This positions elements at the right edge for horizontal boxes and at the bottom edge for
vertical boxes.
The pack attribute applies to the box containing the elements to be packed, not to the elements
themselves.
Now, when the window is resized, the buttons center themselves horizontally. Compare this
behavior to that of the previous example.
Box Alignment
If you resize the window in the Happy-Sad example above horizontally, the box will grow in
width. If you resize the window vertically however, you will note that the buttons grow in height.
This is because the flexibility is assumed by default in the other direction.
You can control this behavior with the align attribute. For horizontal boxes, it controls the
position of the children vertically. For vertical boxes, it controls the position of the children
horizontally. The possible values are similar to the pack attribute.
• start
This aligns elements along the top edge for horizontal boxes and along the left edge for
vertical boxes.
• center
This centers the child elements in the box.
• end
This aligns elements along the bottom edge for horizontal boxes and along the right edge
for vertical boxes.
• baseline
This aligns the elements so that the text lines up. This is only useful for horizontal boxes.
• stretch
This value, the default, causes the elements to grow to fit the size of the box, much like a
flexible element, but in the opposite direction.
As with the pack attribute, the align attribute applies to the box containing the elements to be
aligned, not to the elements themselves.
For example, the first box below will have its children stretch, because that is the default. The
second box has an align attribute, so its children will be placed centered.
<?xml version="1.0"?>
<?xml-stylesheet href="chrome://global/skin/" type="text/css"?>
<hbox>
<button label="Yes"/>
<button label="No"/>
</hbox>
<hbox align="center">
<button label="Maybe"/>
<button label="Perhaps"/>
</hbox>
</window>
You can also use the style properties -moz-box-pack and -moz-box-align instead of specifying
attributes.
You may find the Box Alignment Example useful for trying out the various box properties.
You could potentially create a button element that contains a label that is larger than the
maximum width of the button. Of course, a solution would be to increase the size of the button.
However, buttons (and other elements with a label) have a special attribute called crop that
allows you to specify how the text may be cropped if it is too big.
If the text is cropped, an ellipsis (...) will appear on the button where the text was taken out.
Four possible values are valid:
• left
The text is cropped on its left side
• right
The text is cropped on its right side
• center
The text is cropped in the middle.
• none
The text is not cropped. This is the default value.
This attribute is really only useful when a dialog has been designed to be useful at any size. The
crop attribute can also be used with other elements that use the label attribute for labels. The
following shows this attribute in use:
Notice how the text on the button has had the right side of it cropped after the
window is made smaller.
The style properties such as min-width and max-height can be applied to any element. We've
added them to buttons and textboxes, but we can also add them to spacers or boxes. In
addition, the flex attribute can be applied to any element.
<hbox flex="1">
<button label="Left" style="min-width: 100px;" flex="1"/>
<spacer flex="1"/>
<button label="Right" style="min-width: 100px;" flex="1"/>
</hbox>
In the example above, all three elements will resize themselves as they are all flexible. The two
buttons indicate a minimum width of 100 pixels. The buttons will never be smaller than this size
but they may grow larger. Here the window should appear just over 200 pixels wide. That's
enough for the two buttons. Because all three elements are flexible, but they don't need any
more room, the flexibility adds no extra space.
As shown in the image above, there are two buttons which expand vertically to fit their
container, which in this case is the hbox. The align attribute controls this behavior on a
horizontal box. You can also prevent this stretching by placing a maximum height on the
elements or, better, on the box itself. If a box has a maximum height, the elements inside it are
constrained by this. However, the problem with this is that you need to know how big the
element will be beforehand. The following shows the example with a align attribute set.
• Horizontal boxes
1. Lay out their elements next to each other horizontally.
• Flexible elements are flexed horizontally.
• Packing controls their horizontal placement of child elements.
• Alignment controls how the row of elements are aligned vertically.
• Vertical boxes
1. Lay out their elements vertically in a column.
• Flexible elements are flexed vertically.
• Packing controls the vertical placement of child elements.
• Alignment controls how the column of child elements are aligned horizontally.
You can put boxes anywhere in a XUL file, including inside an HTML element such as a table.
However, the layout will be partly controlled by the HTML element. That means that the flex
might not work exactly as you want it. Remember that flexibility only has meaning for elements
that are directly inside a box or an element that is a type of box.
Examples
1. Using Spacers
<hbox>
<button label="One"/>
<spacer style="width: 5px"/>
<button label="Two"/>
</hbox>
Here, a spacer is used as a separator between the two buttons, by setting an explicit width of 5
pixels. You could also set margins (using the CSS margin property).
2. Centering Buttons
This example contains a horizontal box with two buttons in it, contained inside a flexible box.
The box has the pack attribute which is used to center the buttons horizontally. The align
attribute aligns the buttons vertically. The result is that the buttons will be centered in the box in
both directions. This example will work with a vertical box as well, although the second button
will be underneath the first one, instead of beside it.
<vbox flex="3">
<label control="t1" value="Search Text:"/>
<textbox id="t1" style="min-width: 100px;" flex="1"/>
</vbox>
</window>
Here, two vertical boxes are created, one for the textbox and the other for the check box and
button. The left box has a flexiblity that is 3 times greater than the right one so it will always
receive 3 times as much of the extra space when the window size is increased. The right box
enforces a minimum width of 150 pixels.
The textbox is flexible so it will resize as the window resizes. The textbox also enforces a
minimum width of 100 pixels. The check box appears in the right box along with its label. Just
below the check box is a spacer. The spacer will grow and shrink but not exceed 30 pixels. The
result is that the check box and the Find button will be spaced apart from each other by some
space of no more than 30 pixels.
The second box has an alignment of start. This causes the child elements to be aligned on the
left edge. If this was not specified, the default would be stretch, which would make the child
elements stretch horizontally. Because we don't want the Find button to change size, we need
to set an alignment.
Groupboxes
This section describes a way to include elements into groups.
Groupboxes
HTML provides an element, fieldset which can be used to group elements together. A border is
typically drawn around the elements to show that they are related. An example would be a
group of check boxes. XUL provides an equivalent element, groupbox which can be used for a
similar purpose.
As its name implies, the groupbox is a type of box. That means that the elements inside it align
themselves according to the rules of boxes. There are two differences between groupboxes and
regular boxes:
1. A beveled border is drawn around the groupbox by default. You can change this behavior
by changing the CSS style.
2. The groupbox supports a caption which is placed along the top part of the border.
Because groupboxes are types of boxes, you can use the same attributes such as orient and
flex. You can put whatever elements you want inside the box, although typically they will be
related in some way.
The label across the top of the groupbox can be created by using the caption element. It works
much like the HTML legend element. A single caption element placed as the first child is
sufficient.
<groupbox>
<caption label="Answer"/>
<description value="Banana"/>
<description value="Tangerine"/>
<description value="Phone Booth"/>
<description value="Kiwi"/>
</groupbox>
This will cause four pieces of text to be displayed surrounded by a box with the
label Answer. Note that the groupbox has a vertical orientation by default which
is necesssary to have the text elements stack in a single column.
You can also add child elements inside the caption element to create a more
complex caption. For example, Mozilla's Font preferences panel uses a drop-
down menu as a caption. Although any content can be used, usually you would
use a checkbox or dropdown menu.
<groupbox flex="1">
<caption>
<checkbox label="Enable Backups"/>
</caption>
<hbox>
<label control="dir" value="Directory:"/>
<textbox id="dir" flex="1"/>
</hbox>
<checkbox label="Compress archived files"/>
</groupbox>
You can use the radiogroup element to group radio elements together. The radiogroup is a type
of box. You can put any element you want inside it, and apart from its special handling of radio
buttons, it works like any other box.
Any radio buttons placed inside the radio group will be grouped together, even if they are inside
nested boxes. This could be used to add extra elements within the structure, such as in the
following example:
<radiogroup>
<radio id="no" value="no" label="No Number"/>
<radio id="random" value="random" label="Random Number"/>
<hbox>
<radio id="specify" value="specify" label="Specify Number:"/>
<textbox id="specificnumber"/>
</hbox>
</radiogroup>
Note that the radiogroup element does not draw a border around it. You should place a
groupbox element around it if a border and caption are desired.
We'll add some more elements to the find files dialog now. First, we'll add the capability to
search for other information such as the file size and date.
<hbox>
<menulist id="searchtype">
<menupopup>
<menuitem label="Name"/>
<menuitem label="Size"/>
<menuitem label="Date Modified"/>
</menupopup>
</menulist>
<spacer style="width: 10px;"/>
<menulist id="searchmode">
<menupopup>
<menuitem label="Is"/>
<menuitem label="Is Not"/>
</menupopup>
</menulist>
<spacer style="width: 10px;"/>
<textbox id="find-text" flex="1" style="min-width: 15em;"/>
</hbox>
Two drop down boxes have been added to the dialog. A spacer has been added in-between
each element to separate them. These spacers have been given an explicit width of 10 pixels
each. You'll notice that if the window is resized, the textbox grows but the other components do
not. You'll also notice that the label was removed.
If you resize the window vertically, the elements do not change size. This is because they are
inside horizontal boxes. In might be more appropriate if the Find and Cancel buttons always
stayed along the bottom of the window. This is easy to do by adding a spacer in-between the
two horizontal boxes.
<hbox>
Now when the dialog is resized, the two buttons will move so that they are always along the
bottom of the dialog. The first spacer adds extra spacing in-between the title label and the
search criteria elements.
It might look nicer if there was a border around the search criteria. There are two ways to do
this. We could use the CSS border property or we could use the groupbox element. This first
method would require that we set the style on the box itself. We'll use the latter method,
however. A groupbox has the advantage that it draws a box with a nice beveled look, suitable
for the current theme.
<groupbox orient="horizontal">
<caption label="Search Criteria"/>
<menulist id="searchtype">
.
.
.
<spacer style="width: 10px;"/>
<textbox id="find-text" flex="1" style="min-width: 15em;"/>
</groupbox>
There are other cosmetic problems as well. We could also have the groupbox grow so that it
extends vertically to the bottom of the box. Also, we could modify some of the margins so that
the elements are positioned better.
We'll see more examples of the box model and some of its features as we continue to add
elements throughout the tutorial.
Containers
Each XUL box is a container that can contain any other element. There are a number of
elements that are specialized types of boxes, such as toolbars and tabbed panels. The box tag
creates the simplest box with no special properties. However, the specialized types of boxes
work just like regular boxes in the way they orient the elements inside them, but they have
additional features.
In fact, many components can contain other elements. We've already seen that buttons may
contain other things besides the default. A scroll bar is just a special type of box that creates its
own elements if you don't provide them. It also handles the moving of the scroll bar thumb.
In the next few sections, we'll introduce some elements that are designed for holding other
elements. They are all special types of boxes and allow all of the attributes of boxes on them.
Stacks
The stack element is a simple box. It works like any other box but has the special property that
its children are laid out all on top of each other. The first child of the stack is drawn underneath,
the second child is drawn next, followed by the third and so on. Any number of elements may be
stacked up in a stack.
The orient property has little meaning on a stack as children are laid out above each other
rather than from side to side. The size of the stack is determined by its largest child, but you can
use the CSS properties width, height, min-width and other related properties on both the stack
and its children.
The stack element might be used for cases where a status indicator needs to be added over an
existing element. For example, a progress bar might be created using a bar and a label overlaid
on top of it.
One convenient use of the stack element however is that you could emulate a number of CSS
properties with it. For example, you could create an effect similar to the text-shadow property
with the following:
<stack>
<description value="Shadowed" style="padding-left: 1px; padding-top: 1px; font-
size: 15pt"/>
<description value="Shadowed" style="color: red; font-size: 15pt;"/>
</stack>
Both description elements create text with a size of 15 points. The first, however is offset one
pixel to the right and down by adding a padding to its left and top sides. This has the result of
drawing the same text 'Shadowed' again but slightly offset from the other. The second
description element is drawn in red so the effect is more visible.
This method has advantages over using text-shadow because you could completely style the
shadow apart from the main text. It could have its own font, underline or size. (You could even
make the shadow blink). It also useful as Mozilla doesn't currently support CSS text shadowing.
A disadvantage is that the area taken up by the shadow makes the size of the stack larger.
Shadowing is very useful for creating the disabled appearance of buttons:
This arrangement of text and shadow colors creates the disabled look under some platforms.
Note that events such as mouse clicks and keypresses are passed to
the element on the top of the stack, that is, the last element in the
stack. That means that buttons will only work properly as the last
element of the stack.
Decks
A deck element also lays out its children on top of each other much like the stack element,
however decks only display one of their children at a time. This would be useful for a wizard
interface where a series of similar panels are displayed in sequence. Rather than create
separate windows and add navigation buttons to each of them, you would create one window
and use a deck where the content changes.
Like stacks, the direct children of the deck element form the pages of the deck. If there are three
children of the deck element, the deck will have three children. The displayed page of the deck
can be changed by setting an selectedIndex attribute on the deck element. The index is a
number that identifies which page to display. Pages are numbered starting from zero. So, the
first child of the deck is page 0, the second is page 1 and so on.
The following is an example of a deck:
<deck selectedIndex="2">
<description value="This is the first page"/>
<button label="This is the second page"/>
<box>
<description value="This is the third page"/>
<button label="This is also the third page"/>
</box>
</deck>
Three pages exist here, the default being the third one. The third page is a box with two
elements inside it. Both the box and the elements inside it make up the page. The deck will be
as large as the largest child, which here should be the third page.
You can switch pages by using a script to modify the selectedIndex attribute. More on this in the
section on events and the DOM.
Stack Positioning
This section will describe how to position items in a stack.
Normally, the child elements of a stack stretch to fit the size of the stack. However, you may
also place the children at specific coordinates. For example, if a stack has two buttons as
children, one may be placed 20 pixels from the left edge and 50 pixels from the top edge. The
second button can be placed at 100 pixels from the left edge and 5 pixels from the top edge.
The position of a child element may be specified by placing two attributes on the element. For
the horizontal position, use the left attribute and for the vertical position, use the top attribute. If
you don't put these attributes on a child of a stack, the child will stretch to fit the size of the
stack.
<stack>
<button label="Goblins" left="5" top="5"/>
<button label="Trolls" left="60" top="20"/>
<button label="Vampires" left="10" top="60"/>
</stack>
You can use a script to adjust the value of the left and top attributes and thus make the
elements move around. Stacks have the advantage that when one absolutely positioned
element changes its position, the position of the other elements is not affected. If you tried to
move elements in a regular box, other elements might shuffle their positions around.
It is also possible to place the child elements so that they overlap. When drawing the child
elements, the elements are shown in the order that they appear in the stack. That is, the first
child of the stack appears at the back, the next child appears next and so on. The last element
appears on top. You can use the DOM functions to move the order of the elements around.
When responding to mouse events, the elements on top will capture the events first. That
means that if two buttons overlap, the top button will capture a mouse click where it covers the
other one.
Tabboxes
It is common in preference dialogs for tabbed pages to appear. We'll find out how to create
them here.
Tabboxes
Tabboxes are typically used in an application in the preferences window. A series of tabs
appears across the top of a window. The user can click each tab to see a different set of
options. It is useful in cases when you have more options than will fit in one screen.
XUL provides a method to create such dialogs. It involves five new elements, which are
described briefly here and in more detail below.
• tabbox
The outer box that contains the tabs along the top and the tab pages themselves.
• tabs
The inner box that contains the individual tabs. In other words, this is the row of tabs.
• tab
A specific tab. Clicking on the tab brings the tab page to the front.
• tabpanels
The container for the pages.
• tabpanel
The body of a single page. You would place the content for a page within this element.
The first tabpanel corresponds to the first tab, the second tabpanel corresponds to the
second tab and so on.
The tabbox is the outer element. It consists of two children, a tabs element which contains the
row of tabs and a tabpanels elements which contains the tabbed pages.
<tabbox id="tablist">
<tabs>
-- tab elements go here --
</tabs>
<tabpanels>
-- tabpanel elements go here --
</tabpanels>
</tabbox>
The tab elements are placed inside a tabs element which is much like a regular box. The tabs
element itself has been placed inside a tabbox. The tabbox also contains a tabpanels element
which will appear below the tabs due to the vertical orientation on the whole tabbox.
There is really nothing special about the tab elements that make them different than boxes. Like
boxes, tabs can contain any element. The difference is that the tabs render slightly differently
and only one tab panel's contents are visible at once, much like a deck.
The contents of the individual tab pages should go inside each tabpanel element. They do not
go in the tab elements as that is where the contents of the tabs along the top go.
Each tabpanel element becomes a page on the tabbed display. The first panel corresponds to
the first tab, the second element corresponds to the second tab, and so on. There is a one-to-
one relationship between the tab and tabpanel elements.
When determining the size of the tabbox, the size of the largest page is used. That means that if
there are ten textboxes on one tab page and only one on another, the tab page will be sized to
fit the one with the ten on it as this takes up more room. The area taken up by the tab area does
not change when the user switches to a new tab page.
<tabbox>
<tabs>
<tab label="Mail"/>
<tab label="News"/>
</tabs>
<tabpanels>
<tabpanel id="mailtab">
<checkbox label="Automatically check for mail"/>
</tabpanel>
<tabpanel id="newstab">
<button label="Clear News Buffer"/>
</tabpanel>
</tabpanels>
</tabbox>
Here, two tabs have been added, the first labeled 'Mail' and the
other 'News'. When the user clicks on the Mail tab, the contents of
the first tab page will be displayed below the tabs. In this case, the
panel with the check box labeled 'Automatically check for mail' will
appear in the first tab. When the second tab is clicked, the panel
containing the button labeled 'Clear News Buffer' will appear instead.
The currently selected tab element is given an additional selected attribute which is set to true.
This alters the appearance of the currently selected tab to make it look selected. Only one tab
will have a true value for this attribute at a time.
Finally, you can change the position of the tabs so that they appear on any side of the tab
pages. There is no special syntax to do this. You simply rearrange the position of the tabs and
set the orient and dir attributes attribute as necessary. Remember that the tab elements are
much like regular boxes in terms of layout. However, you should probably leave the tabs on top,
otherwise they might not look very good under particular themes.
Moreover, the tabbox element is much like a regular container box with a default vertical
orientation, whereas the tabs element is much like a container box with a default horizontal
orientation.
For example, to place the tabs along the left side, change the orientation of the tabs element to
vertical to make the tabs appear vertically stacked. Next, adjust the tabbox so it has horizontal
orientation. This will make the tabs appear to the left of, not above, the tab pages. Note that
changing the orientation of the tabpanels element will have no effect, since the tabbed pages
are layered on top of each other.
You can place the tabs on the right or bottom side by moving the tabs so that it is after the
tabpanels element. Alternatively, you could set the dir attribute to reverse on the tabbox. Again,
you should probably leave the tabs on top, otherwise they might not look very good under
particular themes.
Let's add a second panel to the find files dialog. We'll create an Options tab that will contain
some options for searching. This may not be the best interface for doing this, but we'll use it to
demonstrate tabs. The label across the top and the search criteria box will need to go on the
first tab. We'll add some options on the second tab. The progress bar and the buttons can stay
on the main dialog, outside of the tabs.
<vbox flex="1">
<tabbox>
<tabs>
<tab label="Search" selected="true"/>
<tab label="Options"/>
</tabs>
<tabpanels>
<tabpanel id="searchpanel" orient="vertical">
<description>
Enter your search criteria below and select the Find button to begin
the search.
</description>
<groupbox orient="horizontal">
<caption label="Search Criteria"/>
<menulist id="searchtype">
<menupopup>
<menuitem label="Name"/>
<menuitem label="Size"/>
<menuitem label="Date Modified"/>
</menupopup>
</menulist>
<spacer style="width: 10px;"/>
<menulist id="searchmode">
<menupopup>
<menuitem label="Is"/>
<menuitem label="Is Not"/>
</menupopup>
</menulist>
</groupbox>
</tabpanel>
</tabpanels>
</tabbox>
The tab elements have been placed around the main content of the window. You can see the
two tabs, Search and Options. Clicking on each one will bring up the respective tab pages. As
shown by the image, the two options appear on the second tab. The first tab looks pretty much
like it did before, apart from the tabs along the top.
Grids
XUL has a set of elements for creating tabular grids.
XUL has a set of elements for doing layout of elements in a grid-like manner using the grid
element. It has some similarities to the HTML table tag. The grid does not display anything
itself; it is used only to position elements in a tabular form with rows and columns.
A grid contains elements that are aligned in rows just like tables. Inside a grid, you declare two
things, the columns that are used and the rows that are used. Just like HTML tables, you put
content such as labels and buttons inside the rows. However, the grid allows either row or
column based organization so you may put content in either rows or in columns. It is most
common to use rows, as with a table. However, you can still use columns to specify the size
and appearance of the columns in a grid. Alternatively, you can put content inside the columns,
and use the rows to specify the appearance. We'll look at the case of organizing elements by
row first.
To declare a set of rows, use the rows tag, which should be a child element of grid. Inside that
you should add row elements, which are used for each row. Inside the row element, you should
place the content that you want inside that row.
Similarly, the columns are declared with the columns element, which should be placed as a
child element of the grid. Inside that go individual column elements, one for each column you
want in the grid.
<grid flex="1">
<columns>
<column flex="2"/>
<column flex="1"/>
</columns>
<rows>
<row>
<button label="Rabbit"/>
<button label="Elephant"/>
</row>
<row>
<button label="Koala"/>
<button label="Gorilla"/>
</row>
</rows>
</grid>
Two rows and two columns have been added to a grid. Each
column is declared with the column tag. Each column has
been given a flex attribute. Each row contains two elements,
both buttons. The first element in each row element is placed
in the first column of the grid and the second element is each
row is placed in the second column. Note that you do not need an element to declare a cell --
there is no equivalent of the HTML td element. Instead, you put the contents of cells directly in
the row elements.
You can use any element besides a button element of course. If you wanted one particular cell
to contain multiple elements, you can use a nested hbox or other box element. An hbox is a
single element but you can put as many elements that you want inside it. For example:
<grid flex="1">
<columns>
<column/>
<column flex="1"/>
</columns>
<rows>
<row>
<label control="doctitle" value="Document Title:"/>
<textbox id="doctitle" flex="1"/>
</row>
<row>
<label control="docpath" value="Path:"/>
<hbox flex="1">
<textbox id="docpath" flex="1"/>
<button label="Browse..."/>
</hbox>
</row>
</rows>
</grid>
If you resize the window of the last example, you will see that the textboxes resize, but no other
elements do. This is because of the flex attributes added to the textboxes and the second
column. The first column does not need to be flexible as the labels do not need to change size.
The initial width of a column is determined by the largest element in the column. Similarly, the
height of a row is determined by the size of the elements in a row. You can use the minwidth
and maxwidth and related attributes to further define the size.
You can also place the elements inside the column elements instead of the rows. If you do this,
the rows are just declared to specify how many rows there are.
<grid>
<rows>
<row/>
<row/>
<row/>
</rows>
<columns>
<column>
<label control="first" value="First Name:"/>
<label control="middle" value="Middle Name:"/>
<label control="last" value="Last Name:"/>
</column>
<column>
<textbox id="first"/>
<textbox id="middle"/>
<textbox id="last"/>
</column>
</columns>
</grid>
This grid has three rows and two columns. The row elements are just placeholders to specify
how many there are. You may add the flex attribute to a row to make it flexible. The content is
placed inside in each column. The first element inside each column element is placed in the first
row, the second element in the second row and the third element is placed in the third row.
If you put content in both the columns and the rows, the content will overlap each other,
although they will align in a grid properly. It creates an effect much like a grid of stack elements.
The order of the elements in the grid determines which is displayed on top and which is placed
underneath. If the rows element is placed after the columns element, the content within the rows
is displayed on top. If the columns element is placed after the rows element, the content within
the columns is displayed on top. Similarly, events such as mouse buttons and keypresses are
sent only to the set on top. This is why the columns were declared after the rows in the above
example. If the columns has been placed first, the rows would have grabbed the events and you
would not be able to type into the fields.
One advantage that grids have over a set of nested boxes is that you can create cells that are
flexible both horizontally and vertically. You can do this by putting a flex attribute on both a row
and a column. The following example demonstrates this:
<grid flex="1">
<columns>
<column flex="5"/>
<column/>
<column/>
</columns>
<rows>
<row flex="10">
<button label="Cherry"/>
<button label="Lemon"/>
<button label="Grape"/>
</row>
<row flex="1">
<button label="Strawberry"/>
<button label="Raspberry"/>
<button label="Peach"/>
</row>
</rows>
</grid>
The first column and both rows have been made flexible. This will result in every cell in the first
column being flexible horizontally. In addition, every cell will be flexible vertically because both
rows are flexible, although the first row is more so. The cell in the first column and first row (the
Cherry button) will be flexible by a factor of 5 horizontally and flexible by a factor of 10 vertically.
The next cell, (Lemon) will only be flexible vertically.
The flex attribute has also been added to the grid element so that the entire grid is flexible,
otherwise it would only grow in one direction.
Column Spanning
There is no means of making a cell span a particular number of multiple columns or rows.
However, it is possible to make a row or column that spans the entire width or height of the grid.
To do this, just add an element inside the rows element that isn't inside a row element. You can
use a box type for example, and put other elements inside it if you want to use several
elements. Here is a simple example:
<grid>
<columns>
<column flex="1"/>
<column flex="1"/>
</columns>
<rows>
<row>
<label value="Northwest"/>
<label value="Northeast"/>
</row>
<button label="Equator"/>
<row>
<label value="Southwest"/>
<label value="Southeast"/>
</row>
</rows>
</grid>
The button will stretch to fit the entire width of the grid as it is not inside a grid row. You can use
a similar technique to add an element in-between two columns. It would stretch to fit the height
of the grid. You could also do both if that is desired.
Content Panels
In this section, we'll look at how to add panels that can display HTML pages or other XUL files.
Adding Child Panels
There may be times when you want to have part of a document loaded from a different page.
Sometimes, you will want to change part of the window. A good example is a step-by-step
wizard that guides you through a number of screens, asking a set of questions. Each time the
user clicks the Next button, the next screen of the wizard is displayed.
You could create a wizard interface by opening a different window for each screen. There are
three problems with this approach. First, each window could appear in a different location
(although there are ways around this). Second, the elements such the Back and Next buttons
are the same throughout the interface. It would be much better if just the content area of the
wizard changed. Third, it would be difficult to co-ordinate scripts when running in different
windows.
Note that XUL does have a wizard element which may be used to create wizard interfaces. This
will be described in a later section.
Another approach is to use the iframe element, which works much like the HTML element of the
same name. It creates a separate document within the window. It has the advantage that it can
be placed anywhere and the contents can be loaded from a different file. Set the URL to appear
in the frame with the src attribute. This URL may point to any kind of file, although it will usually
point to an HTML file or another XUL file. You can use a script to change the contents of the
iframe without affecting the main window.
In the Mozilla browser window, the area where the web page is displayed is created by using an
iframe. When the user enters a URL or clicks on a link in a document, the source of the frame is
changed.
<toolbox>
<toolbar id="nav-toolbar">
<toolbarbutton label="Back"/>
<toolbarbutton label="Forward"/>
<textbox id="urlfield"/>
</toolbar>
</toolbox>
The example here has created a very simple interface for a web browser. A box has been
created containing two elements: a toolbox and an iframe. A Back button, a Forward button and
a field for typing is URLs has been added to the only toolbar. The web pages would appear
inside the iframe. In this case, the file welcome.html would appear by default.
This example isn't functionally complete. Next, we would want to add script which changes the
src attribute at the desired time, for example when the user presses the Enter key.
Browsers
There is a second type of content panel, using the browser tag. You would use this when you
want to create a frame that displays content like a browser. Actually, the iframe can do this too,
but the browser has a variety of additional features. For example, the browser maintains a page
history for use with Back and Forward buttons. The browser can also load pages with referers
and other flags. Essentially, the browser tag should be used when you want to create a browser
like interface, but the iframe may be used when you just need a simple panel.
A similar element, tabbrowser, provides the functionality of browser but also provides a tab bar
for switching between multiple pages. This is the widget used by the Mozilla browser for its
tabbed browsing interface. The tabbrowser element is actually implemened as a tabbox
containing a set of browser elements. Both types of browser offer similar control over pages that
are displayed.
As with the iframe, you can specify the url in a browser using the src attribute. For a tabbrowser,
you cannot set the url directly like this, as it doesn't display just one url. Instead, you must use a
script and call the loadURI function.
There are three classes of browser, depending on the kind of content that you want to display
inside. The type may be specified using the type. The first type is the default and is used if you
don't specify a type. In this case, the content loaded inside the browser is treated as if it was
part of the same application and has access to the outer window. That means that when a script
loaded inside the browser tries to get the topmost window, it will get the outer XUL window.
This would be suitable for a child XUL panel that is part of your application, but this isn't what
you want for a browser that loads a web page. Instead, you would want to restrict the web page
to only getting access to the web page content. You might note that a Mozilla browser window
has XUL content for the toolbars and statusbar and so forth with a tabbrowser forming the main
area. This inner area displays a web page, but the web page cannot access the XUL around it.
This is because it uses the second type of browser, specified by setting the type attribute to the
value content. This prevents the content from traversing up to the XUL window. An example:
It is important that you set the type attribute correctly if you are going to be displaying remote
web sites inside the browser element. The tabbrowser sets the content type automatically on
all tabbed browsers that it creates. So you don't have to set this explicitly for tabbed browsers.
The third type is used to indicate the primary content inside the window. The tabbed browser
sets this automatically to whichever browser is currently visible. But you can set it on a browser
if you have more than one in a window, for example if you have a sidebar displaying some
content as well. Set the type attribute to content-primary to specify the primary content. This
acts just like the content value except that the content inside is accessible using the XUL
window's 'content' property. This makes it easy to access the content of the main browser using
a script. It is especially convenient when using a tabbed browser, as you will always be able to
access the currently visible content in this way.
Splitters
Here, we'll look at how to add splitters to a window.
Splitting a Box
There may be times when you want to have two sections of a window where the user can resize
the sections. An example is the Mozilla browser window, where you can change the size of the
sidebar panel by dragging the bar in-between the two frames. You can also hide the sidebar by
clicking the notch.
This feature is accomplished by using an element called a splitter. It creates a skinny bar
between two sections which allows either side to be resized. You can place a splitter anywhere
you want and it will allow resizing of the elements that come before it and the elements that
come after it in the same box.
When a splitter is placed inside a horizontal box, it will allow resizing horizontally. When a
splitter is placed inside a vertical box, it will allow resizing vertically.
<splitter
id="identifier"
state="open"
collapse="before"
resizebefore="closest"
resizeafter="closest">
• id
The unique identifier of the splitter.
• state
Indicates the state of the splitter. Set this to open, the default, to have the split panel
initially open or set it to collapsed to have one of the panels shrunk down (collapsed)
and the other occupying all of the space.
• collapse
This indicates which side of the panel should collapse when the splitter notch (or grippy)
is clicked or set into a collapsed state. Set this to before for the element before the
splitter, or after for the element after the splitter. If you set this to none, which is also the
default, the splitter grippy does not collapse when clicked.
• resizebefore
When the splitter is dragged, the elements to the left or above resize. This attribute
indicates which element should resize. Set this to closest to have the element
immediately to the left of the splitter resize. Set this to farthest to have the element that
is the farthest away from the splitter to the left resize. (The first element in the box). The
default value is closest.
• resizeafter
When the splitter is dragged, the elements to the right or below resize. This attribute
indicates which element should resize. Set this to closest to have the element
immediately to the right of the splitter resize. Set this to farthest to have the element that
is the farthest away from the splitter to the right resize. (The last element in the box). This
attribute can also be set to grow, in which case the elements to the right of the splitter do
not change size when the splitter is dragged, but instead the entire box changes size.
The default value is closest.
If you set the collapse attribute, you should also add a grippy element inside the splitter which
the user can use to collapse the element.
<hbox flex="1">
<iframe id="content-1" width="60" height="20" src="w1.html"/>
<splitter collapse="before" resizeafter="farthest">
<grippy/>
</splitter>
<iframe id="content-2" width="60" height="20" src="w2.html"/>
<iframe id="content-3" width="60" height="20" src="w3.html"/>
<iframe id="content-4" width="60" height="20" src="w4.html"/>
</hbox>
The splitter has been given a resizeafter value of farthest. This means that when the splitter is
dragged, the farthest element after it will change size. In this case, frame 4 will change size.
A value has not been specified for resizebefore so it will default to a value of closest. In this
case, there is only one frame before the splitter, so frame 1 will change size.
Frames 2 and 3 will only change size if you drag the splitter far enough to the right that frame 4
has reached its minimum size.
An image of the 4 panels with the splitter resized to the right is shown below. Notice how the
middle two panels have not changed size. Only panel 1 and panel 4 have changed size. You
can just see part of the fourth panel. If you continue to drag the splitter to the right, the other two
panels will shrink.
You can use the style properties such as min-width, max-height on the iframes to specify
minimum or maximum widths or heights in the box. If you do, the splitter will detect this and not
allow the user to drag the splitter past the minimum and maximum sizes.
For example, if you specified a minimum width of 30 pixels on panel 4 above, it would not shrink
below that size. The other two panels would have to shrink. If you set the minimum width of
panel 1 to 50 pixels, you would only be able to drag the splitter 10 pixels to the left (as it starts
at 60 pixels wide). You can still collapse the splitter however.
You can also place more than one splitter in a box if you want, in which case you could collapse
different parts of it. Similarly, you do not have to collapse just iframes. Any element can be
collapsed.
Splitter Example
Let's see what the find file dialog looks like with a splitter in it. One possibility would be to add
the results of the search in the dialog. We'll add an area in-between the search criteria and the
buttons along the bottom. A splitter will allow you to collapse, or hide, the search results.
</tabbox>
<iframe src="results.html"/>
<splitter resizeafter="grow"/>
<hbox>
Here, a splitter and an iframe have been added to the dialog. We don't need the spacer after the
tabbox any more so we can remove it. The content of the frame is contained in a file called
'results.html'. Create this file and put whatever you want in it for now. The iframe will be
replaced later with a results list when we know how to create it. For now, it serves to
demonstrate the splitter.
The splitter has been set to a collapse value of before meaning that the element just before the
splitter will collapse. Here, it is the iframe. As the images below show, when the grippy is
clicked, the iframe is collapsed and the buttons shuffle up.
The resizeafter attribute has been set to grow so that the elements after the splitter push
themselves down when the splitter is dragged down. This results in the content of the frame
growing to any size. It should be noted that the window does not resize itself automatically.
You'll also notice that this is a horizontal splitter because it has been placed in a vertical box.
Normal State:
Collapsed State:
Scroll Bars
Now, let's find out to add scroll bars to a window.
A scroll bar is typically used so that a user can move around in a large document. You can also
use it when you need to ask for a value that falls within a certain range. Scroll bars can be
created in a number of ways. In XUL, one can be created using the scrollbar tag. Some
elements, such as text boxes, will also add scroll bars as necessary when the content inside is
too large.
In this section, we'll discuss creating a stand-alone scroll bar. The user will set the value by
adjusting the scroll bar. You probably won't use this too often. A scroll bar is made up of several
parts, the slider, which is the main part of the scroll bar with the adjustable box, and the two
arrow buttons on the end. A scroll bar creates all of these elements automatically.
<scrollbar
id="identifier"
orient="horizontal"
curpos="20"
maxpos="100"
increment="1"
pageincrement="10"/>
• id
The unique identifer of the scroll bar
• orient
This specifies the direction of the scroll bar. The default is horizontal, which creates a
scroll bar that extends from left to right. You can also specify vertical which creates a
scroll bar that extends from top to bottom.
• curpos
This indicates the current position of the scroll bar thumb (the box that you can slide back
and forth.) The value ranges from 0 to the value of maxpos. This value does not need a
unit. The default value is 0.
• maxpos
This indicates the maximum position of the scroll bar thumb. The is a numeric value and
does not have a unit. The default value is 100.
• increment
The value here specifies how much the value of curpos changes by when the user clicks
on one of the scroll bar arrows. The default value is 1.
• pageincrement
The value here specifies how much the value of curpos changes by when the user clicks
pages through the scroll bar, which can be done by clicking on the tray between the box
and the arrows. The default value is 10.
The example given in the syntax above will create a scroll bar that can range from a value of 0
to 100. The value 100 might be the number of lines in a list, but it could be anything you want.
The initial value in this example is 20. When clicking on one of the scroll bar arrows, the value
would change by 1 either up or down. By paging through the scroll bar, the value will change by
10.
When the user clicks the scroll bar arrows, the thumb will move by the amount specified by the
value increment. Increasing the value of this attribute will cause the scroll bar to move farther
with each click. The leftmost or topmost position of the scroll bar has the value 0 and the
rightmost or bottommost position of the scroll bar has the value given by maxpos.
By adjusting the values of the scroll bar, you can have the thumb positioned just as you want it
and the change when the user clicks the arrows just as you want it.
Toolbars
A toolbar is usually placed along the top of a window and contains a number of buttons that
perform common functions. XUL has a method to create toolbars.
Adding a Toolbar
Like a number of elements, XUL toolbars are a type of box. Usually, a row of buttons would
appear in the toolbar, but any element can be placed in a toolbar. For example, the Mozilla
browser window contains a textbox that displays the page URL.
Toolbars may be placed on any side of the window, either horizontally or vertically. Of course
you wouldn't normally put a textbox in a vertical toolbar. Actually, because toolbars are just
boxes they can actually go anywhere you want, even in the middle of a window. Typically,
however, a set of toolbars would appear along the top of a window. When more than one
toolbar is placed next to each other, they are typically grouped together in something called a
toolbox.
Along the left side of the toolbar is a little notch which, if clicked, will collapse the toolbar so that
only the notch is visible. The notch is called a grippy. When multiple toolbars are all placed in
the same toolbox, the grippies will collapse into a single row. This shrinks the total amount of
space that is used. Vertical toolbars have their grippies along their top edge. The user will
usually collapse the toolbar if they want more space for the main window.
Here is a example of a simple toolbar inside a toolbox.
<toolbox>
<toolbar id="nav-toolbar">
<toolbarbutton label="Back"/>
<toolbarbutton label="Forward"/>
</toolbar>
</toolbox>
This has created a toolbar containing two buttons, a Back button and a
Forward button. The one toolbar has been placed inside the toolbox. This has
involved four new tags, which are described here.
• toolbox
A box that contains toolbars.
• toolbar
A single toolbar that contains toolbar items such as buttons. Toolbars can be collapsed
using the grippy on its left or top side.
• toolbarbutton
A button on a toolbar, which has all the same features of a regular button but is usually
drawn differently.
• toolbargrippy
This element creates the notch that is used to collapse and expand the toolbar. You don't
need to use it directly as it is added automatically.
The toolbar is the main element that creates the actual toolbar. Inside it are placed the
individual toolbar items, usually buttons, but they can be other elements. The toolbar should
have an id attribute or the grippy won't be able to collapse or expand the toolbar properly.
In the example above, only one toolbar was created. Multiple toolbars can be created just as
easily by adding more toolbar elements after the first one.
The toolbox is a container for toolbars. In some applications, you will have several toolbars
along the top of the window. You can put them all inside a toolbox.
The grippies on the toolbox are created using another element, a toolbargrippy. It doesn't really
make any sense to use it outside of a toolbar as it will have no special purpose as it will have
nothing to collapse. However, you may wish to style it differently. You can hide the grippy by
adding the grippyhidden attribute to the toolbar element, set to the value true.
Let's add a toolbar to the find files dialog. We don't really need one but we'll add one anyway to
demonstrate its use. Two buttons will be added, an Open button and a Save button.
Presumably, they would allow the user to save search results and re-open them later.
<vbox flex="1">
<toolbox>
<toolbar id="findfiles-toolbar">
<toolbarbutton id="opensearch" label="Open"/>
<toolbarbutton id="savesearch" label="Save"/>
</toolbar>
</toolbox>
<tabbox>
A toolbar with two buttons has been added here. In the image, you can see them appear
horizontally along the top. The grippy also appears on the left side of the toolbar. Notice that the
toolbar has been placed inside the vertical box just above the tabbox. This is because we need
the vertical orientation so that the toolbar will appear above everything else.
Creating a Menu
XUL has a number of different ways of creating menus. The most basic way is to add a menu
bar with a row of menus on it like many applications have. You can also create popup menus.
The menu features of XUL consist of a number of different elements which allow you to create
menu bars or popup menus. The items on the menus can be customized quite easily. We've
already seen part of how to make menus using the menulist. This section will build on that.
Menu bars are usually created much like a toolbar. The menu bar can be placed inside a
toolbox and a grippy will appear on its left side so that it can be collapsed. The menu would
work just like any other toolbar. XUL does have some special menu elements which provide
special functionality typical of menus.
There are five elements associated with creating a menu bar and its menus, which are
explained briefly here and in detail afterwards:
• menubar
The container for the row of menus.
• menu
Despite the name, this is actually only the title of the menu on the menubar. This element
can be placed on a menubar or can be placed separately.
• menupopup
The popup box that appears when you click on the menu title. This box contains the list
of menu commands.
• menuitem
An individual command on a menu. This would be placed in a menupopup.
• menuseparator
A separator bar on a menu. This would be placed in a menupopup.
You can customize the menus on the menubar to have whatever you want on them on all
platforms except the Macintosh. This is because the Macintosh has its own special menu along
the top of the screen controlled by the system. Although you can create custom menus, any
special style rules or non-menu elements that you place on a menu may not be applied. You
should keep this is mind when creating menus.
<toolbox flex="1">
<menubar id="sample-menubar">
<menu id="file-menu" label="File">
<menupopup id="file-popup">
<menuitem label="New"/>
<menuitem label="Open"/>
<menuitem label="Save"/>
<menuseparator/>
<menuitem label="Exit"/>
</menupopup>
</menu>
<menu id="edit-menu" label="Edit">
<menupopup id="edit-popup">
<menuitem label="Undo"/>
<menuitem label="Redo"/>
</menupopup>
</menu>
</menubar>
</toolbox>
You can also create separators on the menus using the menuseparator element. This is used to
separate groups of menuitems.
The menubar is a box containing menus. Note that it has been placed inside a flexible toolbox.
The menubar has no special attributes but it is a type of box. This means that you could create
a vertical menubar by setting the orient attribute to vertical.
The menu element works much like the button element. It accepts some of the same attributes
plus some additional ones:
• id
The unique identifier of the menu title button.
• label
The text to appear on the menu, such as File or Edit.
• disabled
This boolean attribute determines whether the menu is disabled. Although you can,
there's rarely a need to disable an entire menu. This attribute can be set to either true or
false. Of course, the latter is the default.
• accesskey
This is the key that the user can press to activate the menu item. This letter is typically
shown underlined on the menu title. Mozilla will look at the label attribute and add an
underline character to the character specified here. For that reason, you should specify a
character that exists in the text (although the key will still work if it doesn't).
The menupopup element creates the popup window containing the menu commands. It is a
type of box which defaults to a vertical orientation. You could change it to horizontal if you
wanted to and the menuitems would be placed in a row. Normally only menuitems and
menuseparators would be placed on a menupopup. You can place any element on a
menupopup, however they will be ignored on a Macintosh.
The menuitem element is much like the menu element and has some of the same attributes.
• id
The unique identifier of the menu title button.
• label
The text to appear on the menu item, such as Open or Save.
• disabled
This boolean attribute determines whether the menu item is disabled. This attribute can
be set to either true or false where the latter is the default.
• accesskey
This is the key that the user can press to activate the menu item. This letter is typically
shown underlined on the menu title. Mozilla will look at the label attribute and add an
underline character to the character specified here. For that reason, you should specify a
character that exists in the text.
• acceltext
This specifies the shortcut key text to appear next to the menu command text. It does not
associate a key action with the menuitem however. We'll look at how to do this later.
The menuseparator has no special attributes. It just creates a horizontal bar between the
menuitems next to it.
Creating Submenus
You can create submenus inside other menus (nested menus) using the existing elements.
Remember that you can put any element inside a menupopup. We've looked at placing
menuitems and menuseparators in menupopups. However, you can create submenus by simply
placing the menu element inside the menupopup element. This works because the menu
element is valid even when it isn't directly placed inside a menu bar.
The example below creates a simple submenu inside the File menu:
Example 5.3.1: Source View
<toolbox flex="1">
<menubar id="sample-menubar">
<menu id="file-menu" label="File">
<menupopup id="file-popup">
<menu id="new-menu" label="New">
<menupopup id="new-popup">
<menuitem label="Window"/>
<menuitem label="Message"/>
</menupopup>
</menu>
<menuitem label="Open"/>
<menuitem label="Save"/>
<menuseparator/>
<menuitem label="Exit"/>
</menupopup>
</menu>
</menubar>
</toolbox>
Let's add a menu to the find files dialog. We'll just add a few simple commands to a File menu
and an Edit menu. This is similar to the example above.
<toolbox>
<menubar id="findfiles-menubar">
<menu id="file-menu" label="File" accesskey="f">
<menupopup id="file-popup">
<menuitem label="Open Search..." accesskey="o"/>
<menuitem label="Save Search..." accesskey="s"/>
<menuseparator/>
<menuitem label="Close" accesskey="c"/>
</menupopup>
</menu>
<menu id="edit-menu" label="Edit" accesskey="e">
<menupopup id="edit-popup">
<menuitem label="Cut" accesskey="t"/>
<menuitem label="Copy" accesskey="c"/>
<menuitem label="Paste" accesskey="p" disabled="true"/>
</menupopup>
</menu>
</menubar>
<toolbar id="findfiles-toolbar>
Here we have added two menus with various commands on them. Notice how the menu bar
was added inside the toolbox. In the image, you can see the grippy on the menu bar that can be
used to collapse the menu bar. The three dots after Open Search and Save Search are the
usual way that you indicate to the user that a dialog will open when selecting the command.
Access keys have been added for each menu and menu item. You will see in the image that
this letter has been underlined in the menu label. Also, the Paste command has been disabled.
We'll assume that there's nothing to paste.
Many applications have menu items that have checks on them. For example, a feature that is
enabled has a check placed beside the command and a feature that is disabled has no check.
When the user selects the menu, the check state is switched. You may also want to create radio
buttons on menu items.
The checks are created in a similar way to the checkbox and radio elements. This involves the
use of two attributes, type to indicate the type of check and name to group commands together.
The example below creates a menu with a checked item.
<toolbox>
<menubar id="options-menubar">
<menu id="options_menu" label="Options">
<menupopup>
<menuitem id="backups" label="Make Backups" type="checkbox"/>
</menupopup>
</menu>
</menubar>
</toolbox>
The type attribute has been added which is used to make the menu item checkable. By setting
its value to checkbox, the menu item can be checked on and off by selecting the menu item.
In addition to standard checks, you can create the radio style of checks by setting the type to a
value of radio. A radio check is used when you want a group of menu items where only one
item can be checked at once. An example might be a font menu where only one font can be
selected at a time. When another item is selected, the previously selected item is unchecked.
In order to group a set of menu items together, you need to put a name attribute on each one to
group. Set the value to the same string. The example below demonstrates this:
If you try this example, you'll find that of the first three menu items, only one can be checked.
They are grouped together because they all have the same name. The last menu item, Earth, is
a radio button but is not part of this group because it has a different name.
Of course, the grouped items all have to be within the same menu. They don't have to be placed
next to each other in the menu, although it doesn't make as much sense if they aren't.
Popup Menus
In the last section, we looked at creating a menu on a menu bar. XUL also has the capability of
creating popup menus. Popup menus are typically displayed when the user presses the right
mouse button.
XUL has three different types of popups, described below. The main difference is the way in
which they appear.
• Plain Popups
The plain popup is a popup window which appears when the user presses the left mouse
button on an element. They are much like the menus on the menu bar, except that they
can be placed anywhere and can contain any content. A good example is the drop down
menu that appears when you hold the mouse button down over the back and forward
buttons in a browser window.
• Context Popups
The context popup is a popup window which appears when the user presses the context
menu button, which is usually the right mouse button. On some platforms, this may be a
different button - but it is always the button or combination of key press and mouse
button which invokes a context-specific menu. On the Macintosh for example, the user
must either press the Control key and click the mouse button, or hold the mouse button
down for a moment.
• Tooltips
A tooltip popup window will appear when the user hovers over an element with the
mouse. This type of popup is usually used to provide a description of a button in more
detail than can be provided on the button itself.
All three types of popups differ in the way that the user invokes them. They can contain any
content, although menus are common for the plain and context popups and a simple string of
text is common for a tooltip. The type of popup is determined by the element that invokes the
popup.
A popup is described using the popup element. It has no special attributes and is a type of box.
When invoked, it will display a window containing whatever you put inside the popup. However,
you should always put an id attribute on the popup as it used to associate the popup with an
element. We'll see what this means soon. First, an example:
<popupset>
<popup id="clipmenu">
<menuitem label="Cut"/>
<menuitem label="Copy"/>
<menuitem label="Paste"/>
</popup>
</popupset>
As can be seen here, a simple popup menu with three commands on it has been created. The
popup element surrounds the three menu items. It is much like the menupopup element. It is a
type of box and defaults to vertical orientation. You will also notice that the id has been set on
the popup element itself.
The popupset element surrounds the entire popup menu declaration. This is a generic container
for popups, and is optional. It does not draw on screen but instead is used as a placeholder
where you would declare all of your popups. As the name popupset implies, you can put
multiple popup declarations inside it. Juts add additional ones after the first popup element. You
can have more than one popupset in a file, but usually you will have only one.
Now that we've created the popup, it's time to make the popup appear. To do this we need to
associate the popup with an element where it should appear. We do this because we only want
the popup to appear when the user clicks in a certain area of a window. Typically, this will be a
specific button or a box.
To associate the popup with an element, you add one of three attributes to the element. The
attribute you add depends on which type of popup you want to create. For plain popups, add a
popup attribute to the element. For context popups, add a context attribute. Finally, for tooltip
popups, add a tooltip attribute.
The value of the attribute must be set to the id of the popup that you want to have appear. This
is why you must put the id on the popup. That way it's easy to have multiple popups in a file.
In the example above, we want to make the popup a context menu. That means that we need to
use the context attribute and add it to the element which we want to have the popup associated
with. The sample below shows how we might do this:
<popupset>
<popup id="clipmenu">
<menuitem label="Cut"/>
<menuitem label="Copy"/>
<menuitem label="Paste"/>
</popup>
</popupset>
<box context="clipmenu">
<description value="Context click for menu"/>
</box>
Here, the popup has been associated with a box. Whenever you
context-click (right-click) anywhere inside the box, the popup menu will
appear. The popup will also appear even if you click on the children of
the box so it will work if you click on the description element also. The
context attribute has been used to associate the box with a popup with
the same id. In this case, the popup clipmenu will appear. This way,
you can have a number of popups and associate them with different elements.
You could associate multiple popups with the same element by putting more attributes of
different types on an element. You could also associate the same popup with multiple elements
which is one advantage of using the popup syntax. Popups can only be associated with XUL
elements. They cannot be associated with HTML elements.
Tooltips
We'll look at a simple way to create tooltips here. There are two ways to create a tooltip. The
simplest way, which is much more common, is to add a tooltiptext attribute to an element for
which you want to assign a tooltip.
The second method is to use a tooltip element containing the content of a tooltip. This requires
you to have a separate block of content for each tooltip or have a script which sets the content,
however it does allow you to use any content besides text in a tooltip.
<popupset>
<tooltip id="moretip" orient="vertical" style="background-color: #33DD00;">
<description value="Click here to see more information"/>
<description value="Really!" style="color: red;"/>
</tooltip>
</popupset>
These two buttons each have a tooltip. The first uses the default tooltip style. The second uses
a custom tooltip that has a different background color and styled text. The tooltip is associated
with the More button using the tooltip attribute, which is set to the corresponding id of the tooltip
element. Note that the tooltip element is still placed inside a popupset element like other popup
types.
Popup Alignment
By default, the popup and context windows will appear where the mouse pointer is. Tooltips will
be placed slightly below the element so that the mouse pointer does not obscure it. There are
cases however, where you will want to indicate in more detail where the popup appears. For
example, the popup menu that appears when you click the Back button in a browser should
appear underneath the back button, not where the mouse pointer is.
To change the popup position, you can use an additional attribute, position, on the popup. You
can also add it to the menupopup element. This attribute is used to indicate the placement of
the popup relative to the element invoking the popup. It can be set to a number of values, which
are described briefly below:
• after_start
The popup appears below the element with the left edges of the element and the popup
window aligned. If the popup window is larger than the element, is extends to the right.
This is the value used for the drop-down menus associated with the browser's Back and
Froward buttons.
• after_end
The popup appears below the element with the right edges of the element and the popup
window aligned.
• before_start
The popup appears above the element with the left edges of the element and the popup
window aligned.
• before_end
The popup appears above the element with the right edges of the element and the popup
window aligned.
• end_after
The popup appears to the right of the element with the bottom edges of the element and
the popup window aligned.
• end_before
The popup appears to the right of the element with the top edges of the element and the
popup window aligned.
• start_after
The popup appears to the left of the element with the bottom edges of the element and
the popup window aligned.
• start_before
The popup appears to the left of the element with the top edges of the element and the
popup window aligned.
• overlap
The popup appears on top of the element.
• at_pointer
The popup appears at the mouse pointer position.
• after_pointer
The popup appears at the same horizontal position as the mouse pointer but appears
below the element. This is how tooltips appear.
By adding one or both of these attributes to an element, you can specify precisely where the
popup appears. You cannot specify an exact pixel position. The position attribute can be used
with all three popup types, although you probably wouldn't change the value for tooltips.
The example below demonstrates creating a back button with a popup menu:
<popupset>
<popup id="backpopup" position="after_start">
<menuitem label="Page 1"/>
<menuitem label="Page 2"/>
</popup>
</popupset>
Popup Example
Let's add a simple popup menu to the find files dialog. For simplicity, we'll just replicate the
contents of the Edit menu. Let's have the popup appear when clicking over the first tab panel:
<popupset>
<popup id="editpopup">
<menuitem label="Cut" accesskey="t"/>
<menuitem label="Copy" accesskey="c"/>
<menuitem label="Paste" accesskey="p" disabled="true"/>
</popup>
</popupset>
<vbox flex="1">
.
.
.
Here a simple popup that is similar to the edit menu has been added to the first tabpanel. If you
right-click (Control-click on the Macintosh) anywhere on the first panel, the popup will appear.
However, the popup will not appear if you click anywhere else. Note that the textbox has its own
built-in popup menu which will override the one we specified.
Scrolling Menus
This section will describe scrolling menus and how to use the mechanism with other elements.
You might wonder what happens if you create a menu with a lot of commands on it, such that all
the items won't fit on the screen at once. Mozilla will provide a scrolling mechanism that will
allow you to scroll through the items.
If the available space is too small, arrows will appear on each end of the menu. If
you move the mouse over the arrows, the menu will scroll up and down. If the
available space is large enough, the arrows will not appear. Note that the exact
behavior of the scrolling will depend on the current theme.
The arrowscrollbox can be used anywhere a regular box can be used. You don't
have to use it in menus. It is always a vertical box and may contain any elements
inside it. You could use it to implement a list when you don't want it to be a drop-down.
The following example shows how to create a scrolling list of buttons (you will need to resize the
window to see the arrow buttons):
If you try this example, it will first open at full size. However, if you shrink the height of the
window, the scroll arrows will appear. Making the window larger again will cause the arrows to
disappear.
You can set a CSS max-height property on the arrowscrollbox to limit the size of the scrolling
box and thus make the arrows appear all the time.
Using Scripts
To make the find files dialog functional, we need to add some scripts which will execute when
the user interacts with the dialog. We would want to add a script to handle the Find button, the
Cancel button and to handle each menu command. We write this using JavaScript functions
much in the same way as HTML.
You can use the script element to include scripts in XUL files. You can embed the script code
directly in the XUL file in between the opening and closing script tags but it is much better to
include code in a separate file as the XUL window will load slightly faster. The src attribute is
used to link in an external script file.
Let's add a script to the find file dialog. Although it does not matter what the script file is called,
usually it would be the same as the XUL file with a js extension. In this case, findfile.js will be
used. Add the line below just after the opening window tag and before any elements.
<script src="findfile.js"/>
We'll create the script file later when we know what we want to put it in it. We'll define some
functions in the file and we can call them in event handlers.
You can include multiple scripts in a XUL file by using multiple script tags, each pointing to a
different script. You may use relative or absolute URLs. For example, you may use URLs of the
following form:
<script src="findfile.js"/>
<script src="chrome://findfiles/content/help.js"/>
<script src="http://www.example.com/js/items.js"/>
This tutorial does not attempt to describe how to use JavaScript as this is a fairly large topic and
there are plenty of other resources that available for this.
Responding to Events
The script will contain code which responds to various events triggered by the user or other
situations. There are about thirty or so different events that may be handled in several different
ways. A typical event is the user pressing a mouse button or pressing a key. Each XUL element
has the ability to trigger certain events in different situations. Some events are triggered only by
certain elements.
Each event has a name, for example, 'mousemove' is the name of the event that is triggered
when the user moves the mouse over a UI element. XUL uses the same event mechanism as
defined by DOM Events. When an action occurs that would trigger an event, such as the user
moving the mouse, an event object is created corresponding to that event type. Various
properties are set on the event object such as the mouse position, the key that was pressed,
and so forth.
The event is then sent to the XUL in phases. The first phase is the capturing phase, in which the
event is first sent to the window, then to the document, followed by each ancestor of the XUL
element where the event occured downwards until it reaches that element. Then, the event is
sent to that XUL element. Finally, during the bubbling phase, the event is sent to each element
back upwards until it reaches the window again. You can respond to an event during either the
capturing or bubbling phase. Once the event has finished propagating, any default action will
occur, which is the built in behaviour of the element.
For example, when the mouse is moved over a button that is inside a box, a 'mousemove' event
is generated, and sent first to the window, followed by the document, and then the box. That
completes the capturing phase. Next, the 'mousemove' event is sent to the button. Finally, the
bubbling phase causes the event to be sent to the box, document and window. The bubbling
phase is essentially the reverse of the capturing phase. Note that some events don't do the
bubbling phase.
You can attach listeners to each element to listen to the events during each step of event
propagation. Due to the way a single event is passed to all the ancestors, you may attach a
listener to a specific element or to an element higher in the hierarchy. Naturally, an event
attached to an element higher up will receive notification of all elements inside it, whereas an
event attached to a button will only receive events pertaining to that button. This is useful if
there are several elements you would like to handle using the same or similar code.
Once you handle an event, regardless of where in the propagation the event is, you will likely
want to stop the event from being sent to further elements, essentially stopping the capturing or
bubbling phases from continuing. Depending on how you attach the event listener to an
element, there are different ways of doing this.
The most common event used is the 'command' event. The command event is fired when a
user activates an element, for example by pressing a button, changing a checkbox or selecting
an item from a menu. The command event is a useful event since it automatically handles
different ways of activating the element. For example, the command event will occur regardless
of whether the user uses the mouse to click a button, or presses the Enter key.
There are two ways to attach an event listener to an element. First, by using an attribute with
script as its value. Second, by calling an element's addEventListener method. The former may
only handle bubbling events but tends to be simpler to write. The latter can handle events at any
phase and may also be used attach multiple listeners for an event to an element. Using the
attribute form is more common for most events.
To use the attribute form, place an attribute on the element where you want the event listener to
be, the name of which should be the event name preceded by the word 'on'. For example, the
corresponding attribute for the 'command' event is 'oncommand'. The value of the attribute
should be some script that should be executed when the event occurs. Typically, this code will
be short and just call a function defined in a separate script. An example of responding to a
button being pressed:
Since the command event will bubble, it is also possible to place the event listener on an
enclosing element. In the example below, the listener has been placed on a box and will receive
events for both elements.
<vbox oncommand="alert(event.target.tagName);">
<button label="OK"/>
<checkbox label="Show images"/>
</vbox>
In this example, the command event will bubble up from the button or checkbox to the vbox,
where it is handled. If a second listener (the oncommand attribute) were placed on the button,
its code will be called first, followed by the handler on the vbox. Event handlers are passed the
event object as an implied argument called 'event'. This is used to get specific information about
the event. One commonly used property is the 'target' property of the event, which holds the
element where the event actually occured. In the example we display an alert containing the
target's tag name. The target is useful when using a bubbling event so that you could have a set
of buttons which are all handled by a single script.
You might notice that the attribute syntax is similar to that used for events in HTML documents.
In fact, both HTML and XUL share the same event mechanism. One important difference is that
while the 'click' event (or the onclick attribute) is used in HTML to respond to buttons, in XUL the
command event should be used instead. XUL does have a click event, but it only responds to
mouse clicks, not to keyboard usage. Thus, the click event should be avoided in XUL, unless
you have a reason to have an element that can only be handled with a mouse. In addition,
whereas the command event will not be sent if an element is disabled, the click event will be
sent regardless of whether the element is disabled or not.
A command handler can be placed on the Find and Cancel buttons in the find files dialog.
Pressing the Find button should start the search. Because we aren't going to implement this
part yet, we'll leave it out for now. However, pressing the Cancel button should close the
window. The code below shows how to do this. While we're at it, let's add the same code to the
Close menu item.
Two handlers have been added here. The oncommand attribute was added to the Close menu
item. By using this handler, the user will be able to close the window by clicking the menu item
with the mouse or by selecting it with the keyboard. The oncommand handler was also added to
the Cancel button.
DOM Event Listeners
The second way to add an event handler is to call an element's addEventListener method. This
allows you to attach an event listener dynamically and listen for events during the capturing
phase. The syntax is as follows:
<script>
function buttonPressed(event)
{
alert('Button was pressed!');
}
The getElementById function returns the element with a given id, in this case the button. The
addEventListener function is called to add a new capturing event listener. The first argument is
the name of the event to listen to. The second argument is the event listener function which will
be called when the event occurs. Finally, the last argument should be true for capturing
listeners. You can also listen during the bubbling phase by setting the last argument to false.
The event listener function passed as the second argument should take one argument, the
event object, as shown in the declaration for the buttonPressed function above.
Each event handler has a single argument which holds an event object. In the attribute form of
event listener, this event is an implied argument to the script code which can be refered to using
the name 'event'. In the addEventListener form, the first argument to the listener function will be
the event object. The event object has a number of properties which can be examined during an
event. The full list can be found in the object reference.
We already saw the event's target property is the last section. It holds a reference to the
element where the event occured. A similar property currentTarget holds the element that is
currently having its event listeners handled. In the example below, currentTarget is always the
vbox, whereas target would be the specific element, either the button or checkbox, that was
activated.
<vbox oncommand="alert(event.currentTarget.tagName);">
<button label="OK"/>
<checkbox label="Show images"/>
</vbox>
Recall that the capturing phase occurs before the bubbling phase, so any capturing listeners will
trigger before any bubbling listeners. If a capturing event stops the event propagation, none of
the later capturing listeners, nor any of the bubbling listeners will ever receive notification about
the events. To stop event propagation, call the event object's stopPropagation method, as in the
following example.
<hbox id="outerbox">
<button id="okbutton" label="OK"/>
</hbox>
<script>
function buttonPressed(event)
{
alert('Button was pressed!');
}
function boxPressed(event)
{
alert('Box was pressed!');
event.stopPropagation();
}
Here, an event listener has been added to the button and another event listener has been
added to the box. The stopPropagation method has been called in the box's listener, so the
button's listener never gets called. If this call was removed, both listeners would be called and
both alerts would appear.
If no event handlers have been registered for an event, then after completing the capturing and
bubbling phases, the element will handle the event in a default way. What will happen depends
on the event and the type of element. For example, the 'popupshowing' event is sent to a popup
just before it is displayed. The default action is to display the popup. If the default action is
prevented, the popup will not be displayed. The default action can be prevented with the event
object's preventDefault method, as in the example below.
Alternatively, for attribute event listeners, you can just return false from the code. Note that
preventing the default action is not the same as stopping event propagation with the
stopPropagation method. Even if the default action has been prevented, the event will still
continue to propagate. Similarly, calling the stopPropagation method won't prevent the default
action. You must call both methods to stop both from occuring.
Note that once propagation or the default action has been prevented, neither may be re-enabled
again for that event.
The following sections list some of the events that may be used. A full list is provided in the
event reference.
Mouse Events
There are several events which can be used to handle mouse specific actions, listed in the
following table:
Called when a mouse button is pressed down on an element. The event handler
mousedown will be called as soon as a mouse button is pressed, even if it hasn't been
released yet.
Called when the mouse pointer is moved onto an element. You could use this to
highlight the element, however CSS provides a way to do this automatically so
mouseover
you shouldn't do it with an event. You might, however, want to display some help
text on a status bar.
Called when the mouse pointer is moved while over an element. The event may
mousemove be called many times as the user moves the mouse so you should avoid
performing lengthy tasks from this handler.
Called when the mouse pointer is moved off of an element. You might then
mouseout
unhighlight the element or remove status text.
There are also a set of drag related events, which occur when the user holds down a mouse
button and drags the mouse around. Those events are described in a later section on drag and
drop.
When a mouse button event occurs, a number of additional properties are available to
determine which mouse buttons were pressed and the location of the mouse pointer. The
event's button property can be used to determine which button was pressed, where possible
values are 0 for the left button, 1 for the right button and 2 for the middle button. If you've
configured your mouse differently, these values may be different.
The detail property holds the number of times the button has been clicked quickly in sequence.
This allows you to check for single, double or triple clicks. Of course, if you just want to check
for double clicks, you can also use the dblclick event instead. The click event will be fired once
for the first click, again for the second click, and again for the third click, but the dblclick event
will only be fired once for a double click.
The button and detail properties only apply to the mouse button related events, not mouse
movement events. For the mousemove event, for example, both properties will be set to 0.
However, all mouse events will be supplied with properties that hold the coordinates of the
mouse position where the event occured. There are two sets of coordinates. The first is the
screenX and screenY properties and are relative to the top left corner of the screen. The second
set, clientX and clientY, are relative to the top left corner of the document. Here is an example
which displays the current mouse coordinates:
function updateMouseCoordinates(event)
{
var text = "X:" + event.clientX + " Y:" + event.clientY;
document.getElementById("xy").value = text;
}
</script>
<label id="xy"/>
<hbox width="400" height="400" onmousemove="updateMouseCoordinates(event);"/>
In this example, the size of the box has been set explicity so the effect is easier to see. The
event handler gets the clientX and clientY properties and creates a string from them. This string
is then assigned to the value property of the label. Note that the event argument must be
passed to the updateMouseCoordinates function. If you move the mouse quickly across the
border of the box, you might notice that the coordinates don't generally stop right at 400. This is
because the mousemove events occur at intervals depdending on the speed at which the
mouse moves and the mouse is usually moved some distance past the border by the time the
next event fires. Obviously, it would be much too inefficient to send a mousemove event for
every pixel the mouse is moved.
You will often want to get the coordinates of an event relative to the element where the element
occured rather than the entire window. You can do this by subtracting the element's position
from the event position, as in the following code.
XUL elements have a box object that can be retrieved using the boxObject property. We'll learn
more about the box object in a later section, but it holds information pertaining to how the
element is displayed, including the x and y position of the element. In this example code, these
coordinates are subtracted from the event coordinates to get the event position relative to the
element.
Load Events
The load event is sent to the document (the window tag) once the XUL file has finished loading
and just before the content is displayed. This event is commonly used to initialize fields and
perform other tasks that need to be done before the user can use the window. You should use a
load event to do these kinds of things as opposed to adding script at the top level outside a
function. This is because the XUL elements may not have loaded or fully initialized yet, so some
things may not work as expected. To use a load event, place an onload attribute on the window
tag. Call code within the load handler which will initialize the interface as necessary.
There is also an unload event which is called once the window has closed, or in a browser
context, when the page is switched to another URL. You can use this event to save any
changed information, for example.
Keyboard Shortcuts
You could use keyboard event handlers to respond to the keyboard. However, it would be
tedious to do that for every button and menu item.
Creating a Keyboard Shortcut
XUL provides methods in which you can define keyboard shortcuts. We've already seen in the
section on menus that we can define an attribute called accesskey which specifies the key
which a user can press to activate the menu or menu item. In the example below, the File menu
can be selected by pressing Alt and F (or some other key combination for a specific platform).
Once the File menu is open, the Close menu item can be selected by pressing C.
<menubar id="sample-menubar">
<menu id="file-menu" label="File" accesskey="f">
<menupopup id="file-popup">
<menuitem id="close-command" label="Close" accesskey="c"/>
</menupopup>
</menu>
</menubar>
You can also use the accesskey attribute on buttons. When the key is pressed in this case, the
button is selected.
You might want to set up more general keyboard shortcuts however. For example, pressing
Control+C to copy text to the clipboard. Although shortcuts such as this might not always be
valid, they will usually work any time the window is open. Usually, a keyboard shortcut will be
allowed at any time and you can check to see whether it should do something using a script.
For example, copying text to the clipboard should only work when some text is selected.
XUL provides an element, key, which lets you define a keyboard shortcut for a window. It has
attributes to specify the key that should be pressed and what modifier keys (such as Shift or
Control) need to be pressed. An example is shown below:
<keyset>
<key id="sample-key" modifiers="shift" key="R"/>
</keyset>
This sample defines a keyboard shortcut that is activated when the user presses the Shift key
and R. The key attribute (note that it has the same name as the element itself) can be used to
indicate which key should be pressed, in this case R. You could add any character for this
attribute to require that key to be pressed. The modifiers that must be pressed are indicated
with the modifiers attribute. It is a space-separated list of modifier keys, which are listed below.
• alt
The user must press the Alt key.
• control
The user must press the Control key.
• meta
The user must press the Meta key. This is the Command key on the Macintosh.
• shift
The user must press the Shift key.
• accel
The user must press the special accelerator key.
Your keyboard won't necessary have all of the keys, in which case they will be mapped to
modifier keys that you do have.
The key element must be placed inside a keyset element. This element is designed for holding
a set of key elements, which serves to group all of the key definitions in one place in a file. Any
key elements outside of a keyset element will not work.
Each platform generally uses a different key for keyboard shortcuts. For example, Windows
uses the Control key and the Macintosh uses the Command key. It would be inconvenient to
define separate key elements for each platform. Luckily, there is a solution. The modifier accel
refers to the special platform-specific key used for shortcuts. If works just like the other
modifiers, but won't be the same on every platform.
<keyset>
<key id="copy-key" modifiers="control" key="C"/>
<key id="explore-key" modifiers="control alt" key="E"/>
<key id="paste-key" modifiers="accel" key="V"/>
</keyset>
The key attribute is used to specify the key that must be pressed. However, there will also be
cases where you want to refer to keys that cannot be specified with a character (such as the
Enter key or the function keys). The key attribute can only be used for printable characters.
Another attribute, keycode can be used for non-printable characters.
The keycode attribute should be set to a special code which represents the key you want. A
table of the keys is listed below. Not all of the keys are available on all keyboards.
VK_HELP
For example, to create a shortcut that is activated when the user presses Alt and F5, do the
following:
<keyset>
<key id="test-key" modifiers="alt" keycode="VK_F5"/>
</keyset>
<keyset>
<key id="copy-key" modifiers="accel" key="C"/>
<key id="find-key" keycode="VK_F3"/>
<key id="switch-key" modifiers="control alt" key="1"/>
</keyset>
The first key is invoked when the user presses their platform-specific shortcut key and C. The
second is invoked when the user presses F3. The third is invoked on a press of the Control key,
the Alt key and 1. If you wanted to distinguish between keys on the main part of the keyboard
and the numeric keypad, use the VK_NUMPAD keys (such as VK_NUMPAD1).
Using the Keyboard Shortcuts
Now that we know how to define keyboard shortcuts, we'll find out how we can use them. There
are two ways. The first is the simplest and just requires that you use the keypress event handler
on the key element. When the user presses the key, the script will be invoked. An example is
shown below:
<keyset>
<key id="copy-key" modifiers="accel" key="C" onkeypress="DoCopy();"/>
</keyset>
The function DoCopy will be called when the user presses the keys specified by the key
element, which in this example, are the keys for copying to the clipboard (such as Control+C).
This will work as long as the window is open. The DoCopy function should check to see if text is
selected and then copy the text to the clipboard. Note that textboxes have the clipboard
shortcuts built-in so you don't have to implement them yourself.
If you are assigning a keyboard shortcut that performs a command that also exists on a menu,
you can associate the key element directly with the menu command. To do this, add a key
attribute on the menuitem. Set its value to the id of the key that you want to use. The example
below demonstrates this.
<keyset>
<key id="paste-key" modifiers="accel" key="V"
oncommand="alert('Paste invoked')"/>
</keyset>
<menubar id="sample-menubar">
<menu id="edit-menu" label="Edit" accesskey="e">
<menupopup id="edit-popup">
<menuitem id="paste-command" accesskey="p" key="paste-key" label="Paste"
oncommand="alert('Paste invoked')"/>
</menupopup>
</menu>
</menubar>
The menuitem's key attribute, which here is paste-key is equal to the id of the defined key. You
can use this for additional keys as well to define keyboard shortcuts for any number of menu
items.
You'll also notice in the image that text has been placed next to the Paste
menu command to indicate that Control and the V key can be pressed to
invoke the menu command. This is added for you based on the modifiers on
the key element. Keyboard shortcuts attached to menus will work even if
the menu is not open.
One additional feature of key definitions is that you can disable them easily. To do this add a
disabled attribute to the key element and set it to the value true. This disables the keyboard
shortcut so that it cannot be invoked. It is useful to change the disabled attribute using a script.
Key Example
Let's add keyboard shortcuts to the find files dialog. We'll add four of them, one for each of the
Cut, Copy, and Paste commands and also one for the Close command when the user presses
Escape.
<keyset>
<key id="cut_cmd" modifiers="accel" key="X"/>
<key id="copy_cmd" modifiers="accel" key="C"/>
<key id="paste_cmd" modifiers="accel" key="V"/>
<key id="close_cmd" keycode="VK_ESCAPE" oncommand="window.close();"/>
</keyset>
<vbox flex="1">
<toolbox>
<menubar id="findfiles-menubar">
<menu id="file-menu" label="File" accesskey="f">
<menupopup id="file-popup">
<menuitem label="Open Search..." accesskey="o"/>
<menuitem label="Save Search..." accesskey="s"/>
<menuseparator/>
<menuitem label="Close" accesskey="c" key="close_cmd"
oncommand="window.close();"/>
</menupopup>
</menu>
<menu id="edit-menu" label="Edit" accesskey="e">
<menupopup id="edit-popup">
<menuitem label="Cut" accesskey="t" key="cut_cmd"/>
<menuitem label="Copy" accesskey="c" key="copy_cmd"/>
<menuitem label="Paste" accesskey="p" key="paste_cmd" disabled="true"/>
</menupopup>
</menu>
Now we can use those shortcuts to activate the commands. Of course, the clipboard commands
don't do anything anyway, as we haven't written those scripts.
Key Events
There are three keyboard events that may be used if the key related features described above
aren't suitable. These events are listed in the following table:
Called when a key is pressed and released when an element has the focus. You
keypress
might use this to check for allowed characters in a field.
Called when a key is pressed down while an element has the focus. Note that the
keydown
event will be called as soon as the key is pressed, even if it hasn't been released yet.
keyup Called when a key is released while an element has the focus.
Key events are only sent to the element that has the focus. Typically, this will include textboxes,
buttons, checkboxes and so forth. If no element is focused, the key event will instead be
targeted at the XUL document itself. In this case, you can add an event listener to the window
tag. Normally though, if you want to respond to keys globally, you will use a keyboard shortcut
as described earlier.
The key event object has two properties which hold the key that was pressed. The keyCode
property holds the key code and may be compared to one of the constants from the key table
earlier in this section. The charCode is used for printable characters and will hold the character
code for the key that was pressed.
The focused element refers to the element which currently receives input events. If there are
three textboxes on a window, the one that has the focus is the one that the user can currently
enter text into. Only one element per window has the focus at a time.
The user can change the focus by clicking an element with the mouse or by pressing the TAB
key. When the TAB key is pressed, the next element is given the focus. To step backwards, the
Shift key and Tab key can be pressed.
You can change the order in which elements are focused when the user presses the TAB key
by adding a tabindex attribute to an element. This attribute should be set to a number. When the
user presses TAB, the focus will shift to the element with the next highest tab index. That
means that you can order the elements by setting indices on elements in sequence. Usually,
however, you would not set the tabindex attribute. If you do not, pressing TAB will set the focus
to the next displayed element. You only need to set tab indices if you wish to use a different
order. Here is an example:
The focus event is used to respond when the focus is given to an element. The blur event is
used to respond when the focus is removed from an element. You can respond to focus
changes by adding an onfocus or onblur attribute on an element. They work just like their HTML
counterparts. You might use these event handlers to highlight the element or display text on a
status bar. The following example can be used to apply a function to handle a focus event.
<script>
function displayFocus()
{
var elem=document.getElementById('sbar');
elem.setAttribute('value','Enter your phone number.');
}
</script>
<textbox id="tbox1"/>
<textbox id="tbox2" onfocus="displayFocus();"/>
<description id="sbar" value=""/>
The focus event, when it occurs, will call the displayFocus function. This function will change the
value of the text label. We could extend this example to remove the text when the blur event
occurs. Typically, you will use focus and blur events to update parts of the interface as the user
selects elements. For instance, you might update a total as the user enters values in other
fields, or use focus events to validate certain values. Don't display an alert during a focus or blur
event as this will be distracting for the user and is poor user interface design.
You can also add event handlers dynamically using the DOM function addEventListener. You
can use it for any element and event type. It takes three parameters, the event type, a function
to execute when the event occurs and a boolean indicating whether to capture or not.
The currently focused element is held by an object called a command dispatcher, of which there
is only one for the window. The command dispatcher is responsible for keeping track of the
focused element as the user uses the interface. The command dispatcher has other roles,
which will be discussed in a later section on commands. For now, we'll look at some of the focus
related features of the command dispatcher.
You can retrieve the command dispatcher from a window using the document's
commandDispatcher property. From there, you can get the focused element with the
dispatcher's focusedElement property. The example below shows this.
<script>
function init()
{
addEventListener("focus",setFocusedElement,true);
}
function setFocusedElement()
{
var focused = document.commandDispatcher.focusedElement;
document.getElementById("focused").value = focused.tagName;
}
</script>
<hbox>
<label control="username" value="User Name:"/>
<textbox id="username"/>
</hbox>
<button label="Hello"/>
<checkbox label="Remember This Decision"/>
</window>
In this example, a focus event handler is attached the window. We want to use a capturing
event handler, so the addEventListener method needs to be used. It registers a capturing event
handler with the window which will call the setFocusedElement method. This method gets the
focused element from the command dispatcher and sets a label to its tag name. As the focused
element is changed, the label will show the tagname of the element. A few things to note. First,
when the textbox is focused, the tag name is 'html:input', not 'textbox' as we might expect. This
is because XUL text boxes are implemented using the HTML input widget, so the focus event is
received for that element instead. Second, clicking the textbox's label changes the focus to the
textbox. This is because the label has a control attribute pointing to the id of the textbox. Finally,
the other label which displays the tag name has no control attribute, so clicking it has no effect
on the focused element. Only focusable elements can be focused.
If you were creating custom elements, you might have a need to change whether an element
can have the focus or not. For this, you can use a special style property -moz-user-focus. This
property controls whether an element can be focused. For instance, you could make a label
focusable, as in the example below.
The style property is set to normal. You can also set it to ignore to turn off the focus for an
element. This shouldn't be used for disabling an element, however; the disabled attribute or
property should be used instead, since that is what it is designed for. Once the label in the
example is focused, it can respond to key presses. Naturally, the label gives no indication that it
is focused, since it isn't normally expected to ever be focused.
There are several ways to change the currently focused element. The simplest is to call the
focus method of a the XUL element that you wish to set the focus to. The blur method can be
used to remove the focus from an element. The following example demonstrates this:
<textbox id="addr"/>
Or, you can use the methods advanceFocus and rewindFocus on the command dispatcher.
These methods move the focus to the next element is sequence or the previous element
respectively. This is what happens when the user presses TAB or Shift+Tab.
For textboxes, a special attribute, focused is added whenever the element has the focus. You
can check for the presence of this attribute to determine if the element has the focus, either
from a script or within a style sheet. It will have the value true. if the textbox has the focus and,
if the textbox does not have the focus, the attribute will not be present.
There are two events that can used when the user changes the value of a textbox. Naturally,
these events will only be sent to the textbox that has the focus. The input event is fired
whenever the text is modified in the field. The new value will be different than the old value. You
may want to use this event instead of using key events, as some keys such as the shift key
don't change the value. Also, the input event would not fire if a letter key was pressed and there
were already more characters than will fit in the textbox.
The change event is similar in that it fires only when the field is changed. However it only fires
once the textbox loses the focus, thus, only once per set of changes.
Text Selection
When working with a textbox, you may wish to retrieve not the entire contents of a field but only
what the user has selected. Or, you may wish to change the current selection.
XUL textboxes support a way to retrieve and modify the selection. The simplest one is to select
all of the text in a textbox. This involves using the select method of the textbox.
tbox.select();
However, you may wish to select only part of the text. To do this you can use the
setSelectionRange function. It takes two parameters, the first is the starting character and the
second is the character after the last one that you want to have selected. Values are zero-
based, so the first character is 0, the second is 1 and so on.
tbox.setSelectionRange(4,8);
This example will select the fifth character displayed, as well as the sixth, seventh and eighth. If
there were only six characters entered into the field, only the fifth and sixth characters would be
selected. No error would occur.
If you use the same value for both parameters, the start and end of the selection changes to the
same position. This results in changing the cursor position within the textbox. For example, the
line below can be used to move the cursor to the beginning of the text.
tbox.setSelectionRange(0,0);
You can retrieve the current selection by using the selectionStart and selectionEnd properties.
These properties are set to the starting and ending positions of the current selection
respectively. If both are set to the same value, no text is selected, but the values will be set to
the current cursor position. Once you have the start and end positions, you can pull out the
substring from the whole text.
You can retrieve and modify the contents of the textbox by using the value property.
One additional useful property of textboxes is the textLength property, which holds the total
number of characters in the field.
Commands
A command is an operation which may be invoked.
Command Elements
The command element is used to create commands which can be used to carry out operations.
You don't need to use commands, since you can just call a script to handle things. However, a
command has the advantage that it can be disabled automatically when needed and can be
invoked externally without needing to know about the details of its implementation. Commands
provide a suitable way to abstract out operations from the code. Commands become useful for
larger applications.
For instance, in order to implement the clipboard menu commands, cut, copy and paste, you
can use commands. If you did not use commands, you would need to figure out which field has
the focus, then check to ensure that the operation is suitable for that element. In addition, the
menu commands would need to be enabled and disabled depending on whether the focused
element had selected text or not, and for paste operations, whether there is something suitable
on the clipboard to paste. As you can see, this becomes complicated. By using commands,
much of the work is handled for you.
You can use a command for any operation. Mozilla uses them for almost every menu
command. In addition, text fields and other widgets have a number of commands which they
already support that you can invoke. You should use them when the operation depends on
which element is focused.
A command is identified by its id attribute. Mozilla uses the convention that command id's start
with 'cmd_'. You will probably want to use the same id if a command is already being used,
however, for your own commands, you can use any command id you wish. To avoid conflicts,
you may wish to include the application name in the command id. A simple way of using
commands is as follows:
In this example, instead of placing the oncommand attribute on the button, we instead place it
on a command element. The two are then linked using the command attribute, which has the
value of the command's id. The result is that when the button is pressed, the command is
invoked.
There are two advantages to using this approach. First, it moves all your operations onto
commands which can all be grouped together in one section of the XUL file. This means that
code is all together and not scattered throughout the UI code. The other advantage is that
several buttons or other UI elements can be hooked up to the same command. For instance,
you might have a menu item, a toolbar button and a keyboard shortuct all for the same
operation. Rather than repeat the code three times, you can hook all three up to the same
command. Normally, you would only hook up elements that would send a command event.
If you set the disabled attribute on the command, the command will be disabled and it will not be
invoked. In addition, any buttons and menu items hooked up to it will be disabled automatically.
If you re-enable the command, the buttons will become enabled again.
<button label="Disable"
oncommand="document.getElementById('cmd_openhelp').setAttribute('disabled','t
rue');"/>
<button label="Enable"
oncommand="document.getElementById('cmd_openhelp').removeAttribute('disabled'
);"/>
In this example, both buttons use the same command. When the Disable button is pressed, the
command is disabled by setting its disabled attribute, and both buttons will be disabled as well.
It is normal to put a group of commands inside a commandset element, together near the top of
the XUL file, as in the following:
<commandset>
<command id="cmd_open" oncommand="alert('Open!');"/>
<command id="cmd_help" oncommand="alert('Help!');"/>
</commandset>
A command is invoked when the user activates the button or other element attached to the
command. You can also invoke a command by calling the doCommand method either of the
command element or an element attached to the command such as a button.
Command Dispatching
You can also use commands without using command elements, or at least, without adding a
oncommand attribute to the command. In this case, the command will not invoke a script
directly, but instead, find an element or function which will handle the command. This function
may be separate from the XUL itself, and might be handled internally by a widget. In order to
find something to handle the command, XUL uses an object called a command dispatcher. This
object locates a handler for a command. A handler for a command is called a controller. So,
essentially, when a command is invoked, the command dispatcher locates a controller which
can handle the command. You can think of the command element as a type of controller for the
command.
The command dispatcher locates a controller by looking at the currently focused element to see
if it has a controller which can handle the command. XUL elements have a controllers property
which is used to check. You can use the controllers property to add your own controllers. You
might use this to have a listbox respond to cut, copy and paste operations, for instance. An
example of this will be provided later. By default, only textboxes have a controller that does
anything. The textbox controller handles clipboard operations, selection, undo and redo as well
as some editing operations. Note that an element may have multiple controllers, which will all be
checked.
If the currently focused element does not have a suitable controller, the window is checked next.
The window also has a controllers property which you can modify if desired. If the focus is
inside a frame, each frame leading to the top-level window is checked as as well. This means
that commands will work even if the focus is inside a frame. This works well for a browser, since
editing commands invoked from the main menu will work inside the content area. Note that
HTML also has a commands and controller system although you can't use it on unprivileged
web pages, but you may use it from, for example, a browser extension. If the window doesn't
provide a controller capable of handling the command, nothing will happen.
You can get the command dispatcher using the document's commandDispatcher property, or
you can retrieve it from the controllers list on an element or a window. The command dispatcher
contains methods for retrieving controllers for commands and for retrieving and modifying the
focus.
You can implement your own controllers to respond to commands. You could even override the
default handling of a command with careful placement of the controller. A controller is expected
to implement four methods, which are listed below:
Let's assume that we want to implement a listbox that handles the delete command. When the
user selects Delete from the menu, the listbox deletes the selected row. In this case, you just
need to attach a controller to a listbox which will perform the action.doCommand method
Try opening the example below in a browser window and selecting items from the list. You'll
notice that the Delete command on the browser's Edit menu is enabled and that selecting it will
delete a row. The example below isn't completely polished. Really, we should ensure that the
selection and focus is adjusted appropriately after a deletion.
<script>
function init()
{
var list = document.getElementById("theList");
var listController = {
supportsCommand : function(cmd){ return (cmd == "cmd_delete"); },
isCommandEnabled : function(cmd){
if (cmd == "cmd_delete") return (list.selectedItem != null);
return false;
},
doCommand : function(cmd){
list.removeItemAt(list.selectedIndex);
},
onEvent : function(evt){ }
};
list.controllers.appendController(listController);
}
</script>
<listbox id="theList">
<listitem label="Ocean"/>
<listitem label="Desert"/>
<listitem label="Jungle"/>
<listitem label="Swamp"/>
</listbox>
</window>
The controller (listController) implements the four methods described above. The
supportsCommand method returns true for the 'cmd_delete' command, which is the name of the
command used when the Delete menu item is selected. For other commands, false should be
returned since the controller does not handle any other commands. If you wanted to handle
more commands, check for them here, since you will often use a single controller for multiple
related commands.
The isCommandEnabled method returns true if the command should be enabled. In this case,
we check if there is a selected item in the listbox and return true if there is. If there is no
selection, false is returned. If you delete all the rows in the example, the Delete command will
become disabled. You may have to click the listbox to update the menu in this simple example.
The doCommand method will be called when the Delete menu item is selected, and this will
cause the selected row in the listbox to be deleted. Nothing needs to happen for the onEvent
method, so no code is added for this method.
We attach this controller to the listbox by calling the appendController method of the listbox's
controllers. The controller object has a number of methods that may be used to manipulate the
controllers. For instance, there is also an insertControllerAt method which inserts a controller
into an element before other ones. This might be useful to override commands. For example,
the following example will disable pasting into a textbox.
var tboxController = {
supportsCommand : function(cmd){ return (cmd == "cmd_paste"); },
isCommandEnabled : function(cmd){ return false; },
doCommand : function(cmd){ },
onEvent : function(evt){ }
};
document.getElementById("tbox").controllers.insertControllerAt(0,tboxController);
In this example, we insert the controller at index 0, which means before any others. The new
controller supports the 'cmd_paste' command and always indicates that the command is
disabled. The default textbox controller never gets called because the command dispatcher
found the controller above to handle the command first.
Updating Commands
In this section, we will look at how to update commands.
Invoking Commands
If a command has an oncommand attribute, you can just invoke it by using the doCommand
method of the command or an element which is attached to it. For other commands, you will
need to use a couple of additional lines of code. You will need to use these extra steps when
invoking commands implemented by a controller. In addition, you will need to do this when
creating your own menu commands, for instance to implement the edit menu commands in your
own application.
Fortunately, the extra code is fairly simple. All we need to do is get the needed controller and
call the command. A simple way of doing this is the following:
The code above first retrieves the controller for the 'cmd_paste' command from the command
dispatcher. Then, it checks to see whether the command is enabled, and then executes the
command using the doCommand method of the controller. Note that we don't need to figure out
which element to use or which controller to use. The command dispatcher handles that part.
Also, we could just call doCommand without checking if the command was enabled or not,
although we probably shouldn't do that.
Th code above is generic enough that it can be a function that takes a command as an
argument and executes that command. This function could then be reused for all commands. In
fact, this is common enough that Mozilla includes a library which does just that. If you include
the script 'chrome://global/content/globalOverlay.js' in a XUL file, you can call the
goDoCommand method which executes the command passed as the argument. The code for
this function is only a few lines long so you could include it directly in your code if for some
reason you didn't want to include the library.
<script src="chrome://global/content/globalOverlay.js"/>
The example above will implement a Paste button. It is attached to the command which will
invoke the command on the necessary controller when called. The code above is all you need
to implement the functionality of the paste command in your application. The only other thing
you need to do is ensure that the enabled status of the paste command, and therefore the
button, is updated at the right time, which is described below.
Command Updaters
A command updater is an extra feature of the <commandset> element which allows it to update
the enabled status of one or more commands when certain events happen. You will need to
think about when a command is valid and when it is not. In addition, you will need to consider
when the state could change and when the commands should be updated.
For example, the paste command is valid when a textbox has the focus and there is something
on the clipboard to paste. The command will become enabled whenever a textbox is focused
and when the clipboard contents change. A command updater will listen for these situations and
code can be executed which enables and disables commands as necessary.
A command updater is indicated by using the commandupdater attribute, which should be set to
true. The events attribute is used to list the events that the command updater listens for. You
can specify multiple events by separating them with commas. In the example above, the
command updater listens for the focus event. This causes commands to be updated when an
element receives the focus.
When a focus event occurs, the code in the oncommandupdate attribute is called. In the
example, the goUpdateCommand method is called which is a function provided by the
globalOverlay.js script described earlier. It will update the command and enable or disable
necessary buttons and menu items. The code behind it is fairly simple. It just gets the necessary
controller, calls its isCommandEnabled method, and then enables or disables the command. If
you have several commands to update, call the goUpdateCommand method once for each
command.
Note that the command updater will receive notifications about all focus events on all elements,
even if other event handlers respond to the event. Essentially, a command updater is like a
global event handler.
Command updaters have a number of events which they can respond to which are listed below.
It is also possible to create your own.
The following example shows the command updaters used in the Mozilla browser to update the
edit menu commands. The functions used can be found in the
'chrome://communicator/content/utilityOverlay.js' script.
<commandset id="globalEditMenuItems"
commandupdater="true"
events="focus"
oncommandupdate="goUpdateGlobalEditMenuItems()"/>
<commandset id="selectEditMenuItems"
commandupdater="true"
events="select"
oncommandupdate="goUpdateSelectEditMenuItems()"/>
<commandset id="undoEditMenuItems"
commandupdater="true"
events="undo"
oncommandupdate="goUpdateUndoEditMenuItems()"/>
<commandset id="clipboardEditMenuItems"
commandupdater="true"
events="clipboard"
oncommandupdate="goUpdatePasteMenuItems()"/>
We've already seen that elements such as buttons can be hooked up to commands. In addition,
if you place the disabled attribute on the command element, any elements hooked up to it will
also become disabled automatically. This is useful way to simplify the amount of code you need
to write. The technique also works for other attributes as well. For instance, if you place a label
attribute on a command element, any buttons attached to the command will share the same
label.
<button command="my_command"/>
<checkbox label="Open in a new window" command="my_command"/>
In this example, the button does not have a label attribute, however it is attached to a command
that does. The button will share the label with the command. The checkbox already has a label,
however, it will be overridden by the command's label. The result is that both the button and the
checkbox will have the same label 'Open'.
If you were to modify the command's label attribute, the label on the button and checkbox will
adjust accordingly. We saw something like this in a previous section where the disabled
attribute was adjusted once and propagated to other elements.
This attribute forwarding is quite useful for a number of purposes. For instance, let's say that we
want the Back action in a browser to be disabled. We would need to disable the Back command
on the menu, the Back button on the toolbar, the keyboard shortcut (Alt+Left for example) and
any Back commands on popup menus. Although we could write a script to do this, it is quite
tedious. It also has the disadvantage that we would need to know all of the places where a Back
action could be. If someone added a new Back button using an extension, it wouldn't be
handled. It is convenient to simply disable the Back action and have all the elements that issue
the Back action disable themselves. We can use the attribute forwarding of commands to
accomplish this.
Broadcasters
There is a similar element called a broadcaster. Broadcasters support attribute forwarding in the
same way that commands do. They work the same as commands except that a command is
used for actions, while a broadcaster is instead used for holding state information. For example,
a command would be used for an action such as Back, Cut or Delete. A broadcaster would be
used to hold, for instance, a flag to indicate whether the user was online or not. In the former
case, menu items and toolbar buttons would need to be disabled when there was no page to go
back to, or no text to cut or delete. In the latter case, various UI elements might need to update
when the user switches from offline mode to online mode.
The simplest broadcaster is shown below. You should always use an id attribute so that it can
be referred to by other elements.
<broadcasterset>
<broadcaster id="isOffline" label="Offline"/>
</broadcasterset>
Any elements that are watching the broadcaster will be modified automatically whenever the
broadcaster has its label attribute changed. This results in these elements having a new label.
Like other non-displayed elements, the broadcasterset element serves as a placeholder for
broadcasters. You should declare all your broadcasters inside a broadcasterset element so that
they are all kept together.
Elements that are watching the broadcaster are called observers because they observe the
state of the broadcaster. To make an element an observer, add an observes attribute to it. This
is analogous to using the command attribute when attaching an element to a command
element. For example, to make a button an observer of the broadcaster above:
The observes attribute has been placed on the button and its value has been set to the value of
the id on the broadcaster to observe. Here the button will observe the broadcaster which has
the id isOffline, which is the one defined earlier. If the value of the label attribute on the
broadcaster changes, the observers will update the values of their label attributes also.
We could continue with additional elements. As many elements as you want can observe a
single broadcaster. You can also have only one if you wanted to but that would accomplish very
little since the main reason for using broadcasters is to have attributes forwarded to multiple
places. You should only use broadcasters when you need multiple elements that observe an
attribute. Below, some additional observers are defined:
<keyset>
<key id="goonline_key" observes="offline_command" modifiers="accel" key="O"/>
<keyset>
<menuitem id="offline_menuitem" observes="offline_command"/>
<toolbarbutton id="offline_toolbarbutton" observes="offline_command"/>
In this example, both the label and the accesskey will be forwarded from the broadcaster to the
key, menu item and the toolbar button. The key won't use any of the received attributes for
anything, but it will be disabled when the broadcaster is disabled.
You can use a broadcaster to observe any attribute that you wish. The observers will grab all
the values of any attributes from the broadcasters whenever they change. Whenever the value
of any of the attributes on the broadcaster changes, the observers are all notified and they
update their own attributes to match. Attributes of the observers that the broadcaster doesn't
have itself are not modified. The only attributes that are not updated are the id and persist
attributes; these attributes are never shared. You can also use your own custom attributes if you
wish.
Broadcasters aren't used frequently as commands can generally handle most uses. One thing
to point out is that there really is no difference between the command element and the
broadcaster element. They both do the same thing. The difference is more semantic. Use
commands for actions and use broadcasters for state. In fact, any element can act as
broadcaster, as long as you observe it using the observes attribute.
There is also a way to be more specific about which attribute of the broadcaster to observe.
This involves an observes element. Like its attribute counterpart, it allows you to define an
element to be an observer. The observes element should be placed as a child of the element
that is to be the observer. An example is shown below:
<broadcasterset>
<broadcaster id="isOffline" label="Offline" accesskey="f"/>
</broadcasterset>
<button id="offline_button">
<observes element="isOffline" attribute="label"/>
</button>
Two attributes have been added to the observes element. The first, element specifies the id of
the broadcaster to observe. The second, attribute, specifies the attribute to observe. The result
here is that the button will receive its label from the broadcaster, and when the label is changed,
the label on the button is changed. The observes element does not change but instead the
element it is inside changes, which in this case is a button. Notice that the accesskey is not
forwarded to the button, since it is not being obseved. If you wanted it to be, another observes
element would need to be added. If you don't use any observes elements, and instead use the
observes attribute directly on the button, all attributes will be observed.
There is an additional event handler that we can place on the observes element which is
onbroadcast. The event is called whenever the observer notices a change to the attributes of
the broadcaster that it is watching. An example is shown below.
<broadcasterset>
<broadcaster id="colorChanger" style="color: black"/>
</broadcasterset>
<button label="Test">
<observes element="colorChanger" attribute="style" onbroadcast="alert('Color
changed');"/>
</button>
<button label="Observer"
oncommand="document.getElementById('colorChanger').setAttribute('style','color:
red');"
/>
Two buttons have been created, one labeled Test and the other labeled Observer. If you click
on the Test button, nothing special happens. However, if you click on the Observer button, two
things happen. First, the button changes to have red text and, second, an alert box appears with
the message 'Color changed'.
What happens is the oncommand handler on the second button is called when the user presses
on it. The script here gets a reference to the broadcaster and changes the style of it to have a
color that is red. The broadcaster is not affected by the style change because it doesn't display
on the screen. However, the first button has an observer which notices the change in style. The
element and the attribute on the observes tag detect the style change. The style is applied to
the first button automatically.
Next, because the broadcast occured, the event handler onbroadcast is called. This results in
an alert message appearing. Note that the broadcast only occurs if the attributes on the
broadcaster element are changed. Changing the style of the buttons directly will not cause the
broadcast to occur so the alert box will not appear.
If you tried duplicating the code for the first button several times, you would end up with a series
of alert boxes appearing, one for each button. This is because each button is an observer and
will be notified when the style changes.
7. Document Object Model
DOM Introduction
The Document Object Model (DOM) is used to store the tree of XUL nodes. When a XUL file is
loaded, the tags are parsed and converted into a hierarchical document structure of nodes, one
for each tag and block of text. The DOM structure may be examined and modified using various
methods provided for this purpose. Specific XUL elements also provide additional functions
which may be used.
Each XUL file that is loaded will have its own document displayed in a window or frame.
Although there is only ever one document associated with a window at a time, you may load
additional documents using various methods.
In Mozilla, the DOM may be accessed and manipulated using JavaScript. The various DOM
objects have functions which may be accessed in script, however, it is important to note that the
DOM is an API that is accessible by JavaScript. JavaScript itself is just a scripting language that
can access these objects because Mozilla provides these objects for use.
In JavaScript, there is always a single global object that is always available. You can refer to the
properties and methods of the global object without having to qualify them with an object. For
example, if the global object has a 'name' property, you can change the name with the code
'name = 7', without having to specify any object to use. In a browser context, the window is the
global object, and the same is also true for XUL. Naturally, this global object will be different for
each window. Each frame will also have a separate window object.
The window is often referred to using the 'window' property, although this is optional.
Sometimes, this is done just to clarify the scope of the method you are refering to. For example,
the following two lines which open a new window are functionally equivalent:
window.open("test.xul","_new");
open("test.xul","_new");
When you declare a function or a variable at the top level of a script, that is outside another
function, you are actually declaring a property of the global object. In XUL, each function you
declare will be set as a property of the window object. For example, the following code will
display the text 'Message' in an alert twice.
function getText()
{
return "Message";
}
alert(getText());
alert(window.getText());
Thus, if you want to access variables or call a function declared in a script used by another
window, you just need to access it using the other window's window object. For example, if we
combined the last two examples into a single file, we might want to call the getText function
from within the other window (the test.xul window). To do this, we can do the following:
alert(window.opener.getText());
Each window has an opener property which holds the window object that opened this one. In
this example, we retrieve the opener window and call the getText function declared in a script
used there. Note that we qualified the property with the 'window' identifier just to be clearer.
The window's open method also returns a reference to the new window so you can call
functions of the new window from the opener. It is important to note, however, that the open
method returns before the window has fully loaded, so functions will not typically be available
yet.
The window object isn't defined by any DOM specification, but in Mozilla is sometimes
considered part of DOM Level 0, a name used by some developers to refer to the DOM-like
functions before they were added to specifications. The actual document displayed in a window
can be retrieved using the window's document property. Since it is one of the most commonly
referenced properties of the window, the document property is usually referenced without the
'window' qualifier.
Mozilla provides several different document objects depending on the kind of document. The
three main documents are the HTMLDocument, XMLDocument, and XULDocument, for HTML,
XML and XUL documents respectively. Obviously, it is this latter type of document used for
XUL. The three document types are very similar, in fact they all share the same base
implementation. However, there are a few functions that are specific to one document type or
the other.
Retrieving Elements
The most common method to retrieve an element in a document is to give the element an id
attribute and the use the document's getElementById method. We've added the id attribute to a
number of elements in the find file dialog. For example, we could get the state of a check box by
using the code below:
The value casecheck corresponds to the id of the case sensitive check box. Once we have an
indication of whether it is checked or not, we can use the state to perform the search. We could
do something similar for the other check box, or any other element that has an id. We'll need to
get the text in the input field for example.
It doesn't make sense to have the progress bar and the dummy tree data displayed when the
find files dialog is first displayed. These were added so that we could see them. Let's take them
out now and have them displayed when the Find button is pressed. First, we need to make
them initially invisible. The hidden attribute is used to control whether an element is visible or
not.
We'll change the progress meter so that is initially hidden. Also, we will add an id attribute so
that we can refer to it in a script to show and hide it. While we're at it, let's also hide the splitter
and results tree as we only need to show them after a search is performed.
<hbox>
<progressmeter id="progmeter" value="50%"
style="margin: 4px;" hidden="true"/>
We've added the hidden attribute and set the value to true. This causes the element to be
hidden when it first appears.
Next, let's add a function that is called when the Find button is pressed. We'll put scripts in a
separate file findfile.js. In the last section, we added the script element in the XUL file. If you
haven't done this, do that now, as shown below. An oncommand handler will also be added to
the Find button.
<script src="findfile.js"/>
.
.
.
<button id="find-button" label="Find"
oncommand="doFind();"/>
Now, create another file called findfile.js in the same directory as findfile.xul. We'll add the
doFind() function is this file. The script tag does allow code to be contained directly inside of it.
However, for various reasons, including better performance, you should always put scripts in
separate files, except for short fragments which can be put directly in the event handler.
function doFind()
{
var meter = document.getElementById('progmeter');
meter.hidden = false;
}
This function first gets a reference to the progress meter using its id, progmeter. The second
line of the function body changes the hidden state so that the element is visible again.
Finally, let's have an alert box pop up that displays what will be searched for. Of course, we
wouldn't want this in the final version but we'll add it to reassure us that something would
happen.
function doFind()
{
var meter=document.getElementById('progmeter');
meter.hidden = false;
var searchtext=document.getElementById('find-text').value;
alert("Searching for \""+searchtext+"\"");
}
Now, with that alert box in there, we know what should happen when we click the Find button.
We could add additional code to get the selections from the drop-down boxes too.
Every XUL element has a set of attributes, a set of properties and a set of children. The
attributes are declared in the source, for example, flex="1", is a flex attribute set to the value 1.
Properties are available in JavaScript using the dot syntax. For example, element.hidden refers
to the hidden property of an element. The children are the child tags of the element and will be
nested inside the element in the source. It is possible to manipulate the attributes, properties
and children of an element dynamically using DOM methods.
It is important to note that attributes and properties are separate things. Just because there is
an attribute with a given name does not mean that there is a corresponding property with the
same name. However, it will often be the case that such a property will exist. For example, to
get the flex of an element, you can use the flex property. In this case, the underlying code just
returns the value of the attribute. For other properties, XUL will perform more complex
calculations.
You can manipulate the attributes of an element using any of the following methods:
• getAttribute ( name )
Return the value of the attribute with the given name.
• hasAttribute ( name )
Return true if the attribute with the given name has a value.
• setAttribute ( name , value )
Set the value of the attribute with the given name to the given value.
• removeAttribute ( name )
Remove the attribute with the given name.
These functions allow you to get and change the value of an attribute at any time. For example,
to use the value of the flex attribute, you might use code like the following:
However, the flex attribute has a corresponding script property which can be used instead. It
isn't any more efficient, but it does mean slightly less typing. The following example
accomplishes the same as above using the flex property instead.
Once you have a reference to an element, you can call the properties of that element. For
example, to get the hidden property for an element, you can use the syntax element.hidden
where 'element' is a reference to the element. You might note that most of the properties listed
in the reference correlate to common attributes on elements. There are differences, of course,
for example, while getAttribute("hidden") will return the string 'true' for a hidden element, the
hidden property returns a boolean true value. In this case, the type conversion is done for you
so the property is more convenient.
As with each document, there is a different element object for XUL elements as for HTML and
XML elements. Every XUL element implements the XULElement interface. A XUL element is
any element declared with the XUL namespace. So, XUL elements will have that interface even
if added to other XML documents, and non-XUL elements will not have that interface. The
XULElement interface has a number of properties and methods specific to XUL elements, many
inherited from the generic DOM Element interface.
A namespace is a URI which specifies the kind of element. Here are some examples:
<button xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"/>
<button xmlns="http://www.w3.org/1999/xhtml"/>
<html:button xmlns:html="http://www.w3.org/1999/xhtml"/>
<html:button
xmlns:html="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"/>
Namespaces are specified using the xmlns attribute. The first button is a XUL element as it has
been placed in the XUL namespace. The second element is an XHTML element as it has been
given the XHTML namespace. You can also use the prefix syntax with a colon to use a specific
namespace. This is used when you are using several namespaces in a document and you need
to be more precise with which namespace is meant. In the third example, the prefix 'html' is
mapped to the namespace 'http://www.w3.org/1999/xhtml'. This causes an XHTML button to be
created for this case. The fourth button is a little confusing, but might clarify that it is the URI
that is important and not the prefix. The fourth example is a XUL button, not an HTML button,
despite what the prefix might say. This is an important distinction. In fact, the actual text used
for the prefix is irrelevant when determining what kind of element is used.
The DOM provides a number of namespace related functions similar to the non-namespace
ones. For example, the getAttributeNS function is similar to the the getAttribute function except
an additional argument may be supplied to specify an attribute in a specific namespace.
Many XUL elements have their own properties that are unique to that element. Refer to the
reference for a full guide to the attributes and properties available for an element.
The DOM is a tree structure with a single root node with children. You can get a reference to the
root node using the document's documentElement property. The root node is always an
element, but this is not the case for other nodes in the tree. An element corresponds to a tag is
the XUL source, but you may also find text nodes, comment nodes and a few other types in a
document tree. In the case of XUL, the root element will be the window tag in the XUL
document. Each node in the tree may have children and those children may have child nodes of
their own. Since the DOM is a tree structure, you can navigate through the tree using a variety
of properties. Some common methods are listed below:
These properties allow you to navigate through a document is various ways. For example, you
might get the first child of an element using the firstChild property and then navigate through the
children using the nextSibling property. Or, you might accomplish the same thing by iterating
through the items in the childNodes list. In Mozilla, the latter method is more efficient.
The following example shows how to iterate over the children of the root node:
The childNodes variable will hold the children of the document root element. We then use a for
loop to iterate over the children, accessing each item like an array.
You can create new elements using the createElement function of the document. It takes one
argument, the tag name of the element to create. You can then set attributes of the element
using the setAttribute function and append it to the XUL document using the appendChild
function. For example, the following will add a button to a XUL window:
<script>
function addButton()
{
var aBox = document.getElementById("aBox");
This script first gets a reference to the box, which is the container to add a new button to. The
createElement function creates a new button. We assign a label 'A Button' to the button using
the setAttribute function. The appendChild function of the box is called to add the button to it.
The createElement function will create the default type of element for the document. For XUL
documents, this generally means that a XUL element will be created. For an HTML document,
an HTML element will be created, so it will have the features and functions of an HTML element
instead. The createElementNS function may be used to create elements in a different
namespace.
The appendChild function is used to add an element as a child of another element. Three
related functions are the insertBefore, replaceChild and removeChild functions. The syntax of
these functions is as follows:
parent.appendChild(child);
parent.insertBefore(child, referenceChild);
parent.replaceChild(newChild, oldChild);
parent.removeChild(child);
It should be fairly straightforward from the function names what these functions do. The
insertBefore function inserts a new child node before an existing one. This is used to insert into
the middle of a set of children instead of at the end like appendChild does. The replaceChild
function removes an existing child and adds a new one in its place at the same position. Finally
the removeChild function removes a node. Note that for all these functions, the reference child
or child to remove must already exist or an error occurs.
It is often the case that you want to remove an existing element and add it somewhere else. If
so, you can just add the element without removing it first. Since a node may only be in one
place at a time, the insertion call will always remove the node from its existing location first. This
is a convenient way to move nodes around in the document.
To copy nodes however, you may call the cloneNode function. This function makes a copy of an
existing node so that you can add it somewhere else. The original node will stay where it is. It
takes one boolean argument which indicates whether to copy all of the node's children or not. If
false, only the node is copied, such that the copy won't have any children. If true, all of the
children are copied as well. This is done recursively, so for large tree structures make sure that
this is desired before passing true to the cloneNode function. Here is an example:
<hbox height="400">
<button label="Copy"
oncommand="this.parentNode.appendChild(this.nextSibling.cloneNode(true));"/
>
<vbox>
<button label="First"/>
<button label="Second"/>
</vbox>
</hbox>
When the Copy button is pressed, we retrieve the next sibling of the button, which will be the
vbox element. A copy of this element is made using the cloneNode function and the copy is
appended.
Note that some elements, such as listbox and menulist provide some additional specialized
modification functions which you should use instead when you can. These are described in the
next section.
The main XUL elements such as buttons, checkboxes and radio buttons may be manipulated
using a number of script properties. The properties available are listed in the element reference
as those available are different for each element. Common properties that you will manipulate
include the label, value, checked and disabled properties. They set or clear the corresponding
attribute as necessary. Here is a simple example which changes the label on a button:
When the button is pressed, the label is changed. This technique will work for a variety of
different elements that have labels. For a textbox, you can do something similar for the value
property.
This example adds a '1' to the textbox each time the button is pressed. The nextSibling property
navigates from the button (this) to the next element, the textbox. The += operator is used to add
to the current value so a 1 will be added onto the end of the existing text. Note that you can still
enter text into the textbox. You can also retrieve the current label or value using these
properties, as in the following example:
Checkboxes have a checked property which may be used to check or uncheck the checkbox. It
should be easy to determine how this is used. In this next example, we reverse the state of the
checked property whenever the button is pressed. Note that while the label and value properties
are strings, the checked property is a boolean property which will be set either true or false.
Radio buttons may be selected as well using properties, however since only one in a group may
be selected at a time, the others must all be unchecked when one is checked. You don't have to
do this manually of course. The radiogroup's selectedIndex property may be used to do this.
The selectedIndex property may be used to retrieve the index of the selected radio button in the
group and well as change it.
It is common to disable particular fields that don't apply in a given situation. For example, in a
preferences dialog, one might have the choice of several possibilities, but one choice allows
additional customization. Here is an example of how to create this type of interface.
<script>
function updateState()
{
var name = document.getElementById("name");
var sindex = document.getElementById("group").selectedIndex;
if (sindex == 0) name.disabled = true;
else name.disabled = false;
}
</script>
In this example a function updateState is called whenever a select event is fired on the radio
group. This will happen whenever a radio button is selected. This function will retrieve the
currently selected radio element using the selectedIndex property. Note that even though one of
the radio buttons is inside an hbox, it is still part of the radio group. If the first radio button is
selected (index of 0), the textbox is enabled by setting its disabled property to true. If the second
radio button is selected, the textbox is enabled.
Manipulating Lists
The XUL listbox provides a number of specialized methods.
List Manipulation
The listbox element provides numerous methods to retrieve and manipulate its items. Although
listboxes may be manipulated using the standard DOM functions as well, it is recommended
that the specialized listbox functions be used instead when possible. These functions are bit
simpler and will do the right thing.
The appendItem function is used to append a new item to the end a list. Similar to the DOM
appendChild function except that it takes a string label, and you do not have to worry about
where to add it in the list structure. Here is an example:
<script>
function addItem()
{
document.getElementById('thelist').appendItem("Thursday", "thu");
}
</script>
<listbox id="thelist"/>
The appendItem takes two arguments, the label, in this case 'Thursday', and a value 'thu'. The
two arguments correspond to the label attribute and the value attribute on the listitem element.
The value is used only as an extra optional value associated with an item which you might wish
to use in a script.
Similarly, there is also an insertItemAt and a removeItemAt function which inserts a new item
and removes an existing item respectively. The syntax is as follows:
The insertItemAt function takes an additional argument, the position to insert the new item. The
new item is inserted at this index, so, in the example, the new item will be added at position 3
while the item previously at that position will now be at position 4. Remember that the first item
is 0. The removeItemAt function will remove the item at a specific index.
These three methods are also available for several other XUL elements and work in the same
manner. In fact, these methods are part of the nsIDOMXULSelectControlElement interface so
any XUL elements which implement this interface have these methods. This includes the
menulist, radiogroup and tabs elements. For example, to add a new item to a menulist, you can
use the same syntax as for a listbox. The right kind of element will be appended in each
situation.
List Selection
These two properties are commonly inspected during a select event, as in the following
example:
The select event is also fired when a radio button in a radiogroup is selected and when a tab is
selected in a tabs element. However, menulists do not fire the select event; instead you can
listen to the command event to handle when an item is selected.
For the tabs element, it is often more convenient to use functions of the tabbox element instead.
It also has a selectedIndex function which will return the index of the selected tab. However, to
get the selected item, use the tabbox's selectedTab function instead. Or, use the selectedPanel
function to get the selected panel, that is, return the content associated with the tab.
All of the selection related properties described above may be also be assigned a new value to
change the selection. In the next example, the selectedIndex property of a radiogroup element
is changed based on the value entered in a textbox. This code isn't foolproof though; for
example it doesn't check if the value entered is out of range. You will want to make sure that
you add this kind of error checking.
<script>
function doSelect()
{
var val = document.getElementById('number').value;
val = Number(val);
if (val != null)
document.getElementById('level').selectedIndex = val - 1;
}
</script>
<hbox align="center">
<label value="Enter a number from 1 to 3:"/>
<textbox id="number"/>
<button label="Select" oncommand="doSelect();"/>
</hbox>
<radiogroup id="level">
<radio label="Excellent"/>
<radio label="Good"/>
<radio label="Poor"/>
</radiogroup>
The following example shows a method of deleting the selected items properly:
<script>
function deleteSelection()
{
var list = document.getElementById('thelist');
var count = list.selectedCount;
while (count--){
var item = list.selectedItems[0];
list.removeItemAt(list.getIndexOfItem(item));
}
}
</script>
Inside the while loop, we first get the selected item at index 0. The first selected item is always
retrieved as the size of the array will decrease as the items are removed. Next, we remove the
item using the removeItemAt function. Since this function requires an index, we can convert
between an item and an index using the getIndexOfItem function. There is also a corresponding
getItemAtIndex function to do the reverse.
List Scrolling
If there are more rows in the listbox than can be displayed, a scroll bar will appear to allow the
user to scroll the list. The scroll position may be adjusted using a couple of listbox methods.
The scrollToIndex method scrolls to a given row. This listbox will scroll such that the row will be
the top row visible, unless the row is near the end of the list of items. The ensureIndexIsVisible
method is similar in that it also scrolls to show a row, but this method does not scroll if the item
is already visible. Thus, the former function is used to scroll to a specific row while the latter is
used just to make sure that a row is visible. There is also an ensureItemIsVisible that takes an
item as an argument instead of an index. Compare the effect of both functions at different scroll
positions in this example:
<button label="scrollToIndex"
oncommand="document.getElementById('thelist').scrollToIndex(4);"/>
<button label="ensureIndexIsVisible"
oncommand="document.getElementById('thelist').ensureIndexIsVisible(4);"/>
Box Objects
This section describes the box object, which holds display and layout related information about
a XUL box as well as some details about XUL layout.
Mozilla divides things into two sets of trees, the content tree and the layout tree. The content
tree stores the nodes as they are found in the source code. The layout tree holds a different tree
of nodes for each individual component that can be displayed. The layout tree holds the
structure as the nodes are expected to be displayed There is not necessarily a one to one
relationship between content and layout nodes. Some content nodes may have several layout
objects, for example, each line of a paragraph has a separate layout object. Conversely, some
content nodes have no layout objects at all. For instance, the key element doesn't have any
layout objects since it isn't displayed in any way. Similarly, any element that has been hidden
will not have a layout object either.
The DOM is generally used only to get and modify information pertaining to the content or
structure of the document. It's relatively simple to determine what kind of content tree node will
be created for a given element. A XUL element, for example, will have a XULElement type of
content node.
The layout objects that will be created are determined in a more complex manner. Various
pieces of information are used such as the tag name, the attributes on an element, various CSS
properties, the elements and layout objects around the element, and the XBL associated with
an element (XBL is described in a later section). Unless you change the style for an element,
most XUL elements will usually use the box layout object or one of its subtypes. Recall that all
XUL elements are types of boxes, that is the box is the base of all displayed XUL elements.
However, there are a number of subtypes, about 25 or so, for specific XUL elements. Some of
these subtypes, such as the stack or listbox are needed for more complex layouts than the
basic box, while others such as the button are used only to add extra mouse and key event
handling.
The layout object associated with an element can be removed and a completely different type of
object created just by changing the CSS display property, among others. For example changing
the display property of an button element to block will cause the button layout object to be
deleted and a block object to be created instead. Naturally, this will change the appearance and
function of the element.
It isn't necessary to know the details of how the layout objects are constructed but it is quite
useful to at least have at least the knowledge of what is described above of XUL layout for more
advanced XUL development.
Box Objects
The layout objects are not accessible to the developer for manipulating. They are internal to the
XUL layout components. However, XUL provides some helper objects, called box objects,
which can provide some layout related information. As the name implies, they are available for
all box-based elements.
There are several subtypes of box object, although only a couple of them are generally used.
The others have functions which are more easily accessible by methods mapped directly onto
the element, since those types are generally only used with one particular element. The base
box object, or the interface BoxObject, however, has a number of properties which are quite
useful for XUL development.
The base box object has two useful features. First, you can retrieve the position and size of the
XUL element as displayed, and second, you can determine the order of the elements in the box
as displayed, instead of their order as they are stored in the DOM.
The box object provides four properties, x, y, width and height, for determining the position and
size of an element. The x and y coordinates are relative to the top left corner of the document in
the window (excluding the window border and title) and are measured in pixels. The width and
height properties are also measured in pixels and return the width and height of the element
including CSS padding and border, if any.
The values are always the position and sizes as currently displayed, so the values will be
different if the element is moved or resized. For example, a flexible element will change in size
and the box object dimensions will update accordingly. The following example shows this.
You can use the width and height attributes to specify the width and height of an element,
respectively. Normally, these attributes would not be used so the element would determine a
suitable size to fit its contents. Thus, these attributes override the default size and use a specific
size. The corresponding width and height properties may be used to adjust the dimensions of
an element at any time, if you wish to display an element at a specific size. Retrieving the
values of these properties will return the size if it was explicitly specified. Note that these
properties will return an empty string if the width or height attributes or properties were not set
already. That is, you cannot get the current size with these properties; instead you must use the
box object properties.
This may be a bit confusing, but, the key is to remember that the width and height properties on
an element return the size that was set in the XUL while the width and height properties of the
box object return the current size.
The hidden attribute will hide an element such that it will not be displayed. Since it is not
displayed, all four position and size properties of the box object will have the value 0. When an
element is hidden, it is removed from the display and the layout objects are removed for it.
Thus, since it isn't displayed anywhere, it will have no position or size.
The collapsed attribute will have the same effect to the user element visually, except that it
leaves the element on screen and keeps the layout objects intact, but changes the size of the
element to 0. This means that while both hidden and collapsed elements have 0 width and
height, hidden elements have a x and y position of 0, while collapsed elements will retain their
position in the window.
To retrive or modify the hidden or collapsed state use the corresponding properties as in the
following example.
<button label="Hide"
oncommand="document.getElementById('thelabel').hidden = true;"/>
<button label="Show"
oncommand="document.getElementById('thelabel').hidden = false;"/>
<button label="Collapse"
oncommand="document.getElementById('thelabel').collapsed = true;"/>
<button label="Uncollapse"
oncommand="document.getElementById('thelabel').collapsed = false;"/>
<button label="Show Position/Size"
oncommand="showPositionAndSize();"/>
<label id="thelabel" value="I am a label"/>
Note that if you hide and collapse the label, it will be treated as hidden. You will then have to
unhide and uncollapse the label for it to appear again.
The box object may also be used to determine the display order of elements, which may not be
the same as the order in the source. Recall that DOM properties such as childNodes, firstChild,
and nextSibling may be used to navigate the document tree. The box object also allows
navigation in a similar means but retrieves the elements in display order.
The box object provides several properties, firstChild, lastChild, nextSibling, previousSibling,
and parentBox. Their function should be self explanatory from their names. These properties
return elements, for example, the firstChild property returns the first displayed child element.
There is no corresponding childNodes property for box navigation; instead you must navigate by
sibling using the nextSibling or previousSibling properties.
Unlike with navigating the DOM tree, hidden elements are not included when navigating by box
objects. That means that for a box with six children where the first two are hidden, the firstChild
property will return the third element. However, collapsed elements are included since they are
still displayed but have no size. For example, the next box sibling of button 1 is this next
example will be button 3, because button 2 is hidden. But the next box sibling of button 3 will be
button 4 because it is only collapsed.
<hbox>
<button label="Button 1"
oncommand="alert('Next is: ' + this.boxObject.nextSibling.label);"/>
<button label="Button 2" hidden="true"/>
<button label="Button 3"
oncommand="alert('Next is: ' + this.boxObject.nextSibling.label);"/>
<button label="Button 4" collapsed="true"/>
</hbox>
When a XUL box is laid out on a window, the elements are ordered according to a number of
properties, for instance the orientation, their ordinal group and their direction. The orientation is
commonly modified using the orient attribute. There is also a corresponding CSS property -moz-
box-orient which may be used instead, depending on the situation. This attribute was explained
earlier in the section on boxes.
The ordinal attribute on an element may be used to specify the ordinal group of the element, or
you can use the CSS property -moz-box-ordinal-group.
Elements with a lower ordinal value are placed in the box before elements with a higher ordinal
value. For example, if one element has an ordinal of 2, it would be placed before an element
with ordinal 3 or higher but after one with ordinal 1. The default value if the ordinal is not
specified is 1. This means that if you want to change the displayed order of elements, you will
often need to adjust the ordinals of several elements.
Adjusting the ordinal of an element is not commonly done as you would usually just place the
elements in a different order in the source. It might be used to rearrange items later without
adjusting the DOM. The following example demonstrates this.
<hbox>
<button label="One" oncommand="this.ordinal++;"/>
<button label="Two" oncommand="this.ordinal++;"/>
<button label="Three" oncommand="this.ordinal++;"/>
</hbox>
If you press the first button, its ordinal will increase by one, from 1 to 2. It will appear at the end
of the row since the other buttons have an ordinal of 1. If you press the second button, its
ordinal will increase by one and will be moved to the end of the row. Items with the same ordinal
value appear in the same order as in the source. If you then press the button labeled One
again, its ordinal will increase to 3 and will move to the end. Finally, pressing the button labeled
Three will increase its ordinal to 2 and it will appear in between the other two buttons.
The final box ordering attribute is the dir attribute, or the -moz-box-direction CSS property. If this
is set to reverse, all of the children in the box or displayed in reverse order. In a horizontal box,
the elements will be displayed from right to left and in a vertical box, the elements will be
displayed from bottom to top. Here is an example:
<hbox dir="reverse">
<button label="Left"/>
<button label="Center"/>
<button label="Right"/>
</hbox>
Navigation through the nodes using the box object ordering will return the elements in the order
they are displayed accounting for the ordinal adjustments made. Thus, if you change the ordinal
of an element, it will have a different position in the box order. Reversing the direction, however,
does not change the box order.
XPCOM Interfaces
In this section, we'll take a brief look at XPCOM (Cross-platform Component Object Model),
which is the Object system that Mozilla uses.
Calling Native Objects
By using XUL we can build a complex user interface. We can attach scripts which modify the
interface and perform tasks. However, there are quite a number of things that cannot be
performed directly with JavaScript. For example, if we wanted to create a mail application, we
would need to write scripts which would connect to mail servers to retrieve and send mail.
JavaScript does not have the capability to do such things.
The only way to handle this would be to write native code that would get mail. We also need to
have a way for our scripts to call the native code easily. Mozilla provides such a method which
involves using XPCOM (Cross-platform Component Object Model).
About XPCOM
Mozilla is constructed from a collection of components, each of which performs a certain task.
For example, there is a component for each menu, button and element. The components are
constructed from a number of definitions called interfaces.
Let's take an example of a file component. An interface would need to be created which
describes properties and functions that can be performed on files. A file would need properties
for its name, modification date and its size. Functions of a file would include moving, copying
and deleting it.
The File interface only describes the characteristics of a file, it does not implement it. The
implementation of the File interface is left to a component. The component will have code which
can retrieve the file's name, date and size. In addition, it will have code which copies and
renames it.
We don't care how the component implements it, as long as it implements the interface
correctly. Of course, we'll have different implementations anyway, one for each platform. The
Windows and Macintosh versions of a file component would be significantly different. However,
they would both implement the same interface. Thus, we can use a component by accessing it
using the functions we know from the interface.
In Mozilla, interfaces are preceded by 'nsI' so that they are easily recognized as interfaces. For
example, the nsIAddressBook is the interface for interacting with an address book, nsISound is
used for playing files and nsILocalFile is used for files.
XPCOM components are typically implemented natively, which means that they generally do
things that JavaScript cannot do itself. However, there is a way in which you can call them,
which we will see shortly. We can call any of the functions provided by the component as
described by the interfaces it implements. For example, once we have a component, we can
check if it implements nsISound, and, if so, we can play sound through it.
The process of calling XPCOM from a script is called XPConnect, which is a layer which
translates script objects into native objects.
Creating XPCOM Objects
1. Get a component
2. Get the part of the component that implements the interface that we want to use.
3. Call the function we need
Once you've done the first two steps, you can repeat the last step as often as necessary. Let's
say we want to rename a file. For this we can use the nsILocalFile interface. The first step is
getting a file component. Second, we query the file component and get the portion of it that
implements the nsILocalFile interface. Finally, we call functions provided by the interface. This
interface is used to represent a single file.
We've seen that interfaces are always named starting with 'nsI'. Components, however, are
referred to using a URI syntax. Mozilla stores a list of all the components that are available in its
own registry. A particular user can install new components as needed. It works much like plug-
ins.
Mozilla provides a file component, that is, a component that implements nsILocalFile. This
component can be referred to using the URI '@mozilla.org/file/local;1'. The component: URI
scheme is used to specify a component. Other components can be referred to in a similar way.
The URI of the component can be used to get the component. You can get a component using
JavaScript code like that below:
The file component is retrieved and stored in the aFile variable. Components in the example
above refers to a general object that provides some component related functions. Here, we get
a component class from the classes property. The classes property is an array of all of the
available components. To get a different component, just replace the URI inside the square
brackets with the URI of the component you want to use. Finally, an instance is created with the
createInstance function.
You should check the return value of createInstance to ensure that it is not null, which would
indicate that the component does not exist.
However, at this point, we only have a reference to the file component itself. In order to call the
functions of it we need to get one of its interfaces, in this case nsILocalFile. A second line of
code needs to be added as follows:
The function QueryInterface is a function provided by all components which can be used to get
a specific interface of that component. This function takes one parameter, the interface that you
want to get. The interfaces property of the Components object contains a list of all the interfaces
that are available. Here, we use the nsILocalFile interface and pass it as a parameter to
QueryInterface. The result is that aFile will be a reference to the part of the component that
implements the nsILocalFile interface.
The two JavaScript lines above can be used to get any interface of any component. Just
replace the component name with the name of the component you want to use and change the
interface name. You can also use any variable names of course. For example, to get a sound
interface, you can do the following:
var sound = Components.classes["@mozilla.org/sound;1"].createInstance();
if (sound) sound.QueryInterface(Components.interfaces.nsISound);
XPCOM interfaces can inherit from other interfaces. The interfaces that inherit from others have
their own functions and the functions of all the interfaces that they inherit from. All interfaces
inherit from a top-level interface called nsISupports. It has one function supplied to JavaScript,
QueryInterface, which we have already seen. Because the interface nsISupports is
implemented by all components, the function QueryInterface function is available in every
component.
Several components may implement the same interface. Typically, they might be subclasses of
the original but not necessarily. Any component may implement the functionality of nsILocalFile.
In addition, a component may implement several interfaces. It is for these reasons that two
steps are involved in getting an interface to call functions through.
However, there is a shortcut we can use because we'll often use both of these lines together:
var aLocalFile =
Components.classes["@mozilla.org/file/local;1"].createInstance(Components.interfaces.
nsILocalFile);
This will do the same thing as the two lines but in one line of code. It eliminates the need to
create the instance and then query it for an interface in two separate steps.
If you call QueryInterface on an object and the requested interface is not supported by an
object, an exception is thrown. If you're not sure that an interface is supported by a component,
you can use the instanceof operator to check:
The instanceof operator returns true if aFile implements the nsILocalFile interface. This also has
the side effect of calling QueryInterface, so aFile will be a valid nsILocalFile afterwards.
Now that we have an object that refers to a component with the nsILocalFile interface, we can
call the functions of nsILocalFile through it. The table below shows some of the properties and
methods of the nsILocalFile interface.
This method is used to initialize the path and filename for the nsILocalFile.
initWithPath
The first parameter should be the file path, such as '/usr/local/mozilla'
Deletes a file. If the recursive parameter is true, a directory and all of its
remove(recursive)
files and subdirectories will also be deleted.
copyTo(directory,ne Copies a file to another directory, optionally renaming the file. The
wname) directory should be a nsILocalFile holding the directory to copy the file to.
moveTo(directory,n Moves a file to another directory, or renames a file. The directory should
ewname) be a nsILocalFile holding the directory to move the file to.
In order to delete a file we first need to assign a file to the nsILocalFile. We can call the method
initWithPath to indicate which file we mean. Just assign the path of the file to this property. Next,
we call the remove function. It takes one parameter which is whether to delete recursively. The
code below demonstrates these two steps:
This code will take the file at /mozilla/testfile.txt and delete it. Try this example by adding this
code to an event handler. You should change the filename to an existing file that you have that
you would like to delete.
In the functions table above, you will see two functions copyTo and moveTo. These two
functions can be used to copy files and move files respectively. Note that they do not take a
string parameter for the directory to copy or move to, but instead take an nsILocalFile. That
means that you'll need to get two file components. The example below shows how to copy a file.
function copyFile(sourcefile,destdir)
{
// get a component for the file to copy
var aFile = Components.classes["@mozilla.org/file/local;1"]
.createInstance(Components.interfaces.nsILocalFile);
if (!aFile) return false;
copyFile("/mozilla/testfile.txt","/etc");
XPCOM Services
Some XPCOM components are special components called services. You do not create
instances of them because only one should exist. Services provide general functions which
either get or set global data or perform operations on other objects. Instead of calling
createInstance, you call getService to get a reference to the service component. Other than
that, services are not very different from other components.
One such service provided with Mozilla is a bookmarks service. It allows you to add bookmarks
to the user's current bookmark list. An example is shown below:
var bmarks = Components.classes["@mozilla.org/browser/bookmarks-
service;1"].getService();
bmarks.QueryInterface(Components.interfaces.nsIBookmarksService);
bmarks.addBookmarkImmediately("http://www.mozilla.org","Mozilla",0,null);
XPCOM Examples
This section provides some examples of using XPCOM along with some additional interfaces.
Window Management
The list of currently open Mozilla windows can be used as an RDF datasource. This allows you
to create a Window menu with a list of the currently open windows in the application. The
datasource for this is rdf:window-mediator. We can use this as in the following example:
<toolbox>
<menubar id="windowlist-menubar">
<menu label="Window">
<menupopup id="window-menu" datasources="rdf:window-mediator"
ref="NC:WindowMediatorRoot">
<template>
<rule>
<menuitem uri="rdf:*" label="rdf:http://home.netscape.com/NC-rdf#Name"/>
</rule>
</template>
</menupopup>
</menu>
</menubar>
</toolbox>
A Window menu will be created with a list of all the open windows. Try this example by opening
a number of browser windows and you'll see that they are all listed on the menu. This is fine for
displaying a list of open windows, but we would like to enhance this so that clicking on the menu
item will switch to that window. This is accomplished by using the window mediator component.
It implements the interface nsIWindowDataSource. The code below shows how to get a
component which implements it:
This code retrieves a window mediator data source component. The component used here is
the same one that handles the window-mediator RDF datasource. You can also get this
component through the RDF service, which is another service that manages RDF datasources.
<toolbox>
<menubar id="windowlist-menubar">
<menu label="Window" oncommand="switchFocus(event.target);">
<menupopup id="window-menu" datasources="rdf:window-mediator"
ref="NC:WindowMediatorRoot">
<template>
<rule>
<menuitem uri="rdf:*" label="rdf:http://home.netscape.com/NC-rdf#Name"/>
</rule>
</template>
</menupopup>
</menu>
</menubar>
</toolbox>
function switchFocus(elem)
{
var mediator = Components.classes["@mozilla.org/rdf/datasource;1?name=window-
mediator"].getService();
mediator.QueryInterface(Components.interfaces.nsIWindowDataSource);
if (switchwindow){
switchwindow.focus();
}
}
A command handler was added to the menu element which calls the function with a parameter
of the element that was selected from the menu. The function switchFocus first gets a reference
to a component which implements the window mediator interface. Next, we get the id attribute
for the element. We can use the value of the id attribute as the resource. The function
getWindowForResource takes the resource and returns a window that matches it. This window,
stored in the switchwindow variable, is the same as the JavaScript window object. This means
that you can call any of the functions provided by it, one of which is the focus function.
Cookies
Next, we will get a list of cookies that have been saved in the browser. The cookie service can
be used for such a purpose. It implements the nsICookieManager interface which can be used
to enumerate over all of the cookies. Here is an example which populates a menu list with the
names of all of the cookies set from MozillaZine.
<script>
function getCookies()
{
var menu = document.getElementById("cookieMenu");
menu.removeAllItems();
var cookieManager = Components.classes["@mozilla.org/cookiemanager;1"]
.getService(Components.interfaces.nsICookieManager);
<hbox>
<menulist id="cookieMenu" onpopupshowing="getCookies();"/>
</hbox>
The getCookies function will be called whenever the menu is opened, as indicated by the
onpopupshowing attribute on the menulist. The first two lines of getCookies get the menulist
and remove all of the existing items in the menu. This is done because getCookies is called
every time the menu is opened and we don't want to leave the old items there each time.
Next, the cookie manager is retrieved. The cookie manager has an enumerator method which
returns an object which implements nsISimpleEnumerator. This can be used to iterate over all
of the cookies. An enumerator has a hasMoreElements method which will return true until we
get to the last cookie. The getNext method gets a cookie and moves the enumerator index to
the next cookie. Since an enumerator just returns a generic object, we need to QueryInterface it
to an nsICookie before we can use it. In this case, we just use the instanceof operator to
accomplish this.
Finally, an item is added to the menu for the cookie. The host, name and value properties of the
cookie are used for this. Menus have an appendItem function which can be used to add an item
to the menu, given a label and a value.
8. Trees
Trees
XUL provides a way to create tabular or hierarchical lists using a tree.
The Tree
One of the more complex elements in XUL is the tree. A tree may be used to display rows of
text in columns. It can be used with rows either in a flat list or arranged into a hierarchy. A tree
also allows the user to rearrange, resize and hide individual columns. Some examples of trees
include the list of messages in a mail application, or the Bookmarks window in Mozilla.
In some ways, a tree has some similarities with the listbox. Both can be used to create tables of
data with multiple rows and columns, and both may contain column headers. The tree also
supports nested rows, whereas the listbox does not. However, listboxes may contain any type
of content, whereas trees may only contain text and images.
A tree consists of two parts, the set of columns, and the tree body. The set of columns is
defined by a number of treecol elements, one for each column. Each column will appear as a
header at the top of the tree. The second part, the tree body, contains the data to appear in the
tree and is created with a treechildren tag.
The tree is unique in that the body of the tree consists only of a single widget which draws all of
the data in the tree. This contrasts with the listbox, where individual listitem and listcell tags are
used to specify the rows in the listbox. In a tree, all of the data to be displayed is supplied by a
separate object, called a tree view. When it comes time to display a cell, the tree widget will call
out to this tree view to determine what to display, which in turn will be drawn by the tree. The
tree is smart enough to only ask for information from the view for those rows that need to be
displayed. This allows the view to be optimized such that it only needs to load the data for
displayed content. For instance, a tree might have thousands of rows, yet most of them will be
scrolled off the border of the tree, hidden from view. This means that the tree is scalable to any
number of rows without any performance problems. Of course, this is independant of the
performance of the view object itself.
A tree view is an object which implements the nsITreeView interface. This interface contains
thirty properties and functions which you may implement. These functions will be called by the
tree as necessary to retrieve data and state about the tree. For instance, the getCellText
function will be called to get the label for a particular cell in the tree.
An advantage of using a view is that it allows the view to store the data in a manner which is
more suitable for the data, or to load the data on demand as rows are displayed. This allows
more flexibility when using trees.
Naturally, having to implement a tree view with thirty or so properties and methods for every
tree can be very cumbersome, especially for simple trees. Fortunately, XUL provides a couple
of built-in view implementations which do most of the hard work for you. For most trees,
especially when you first start to use trees, you will use one of these built-in types. However,
you can create a view entirely from scratch if necessary. If you do, you might store the data in
an array or JavaScript data structure, or load the data from an XML file.
Since the entire body of the tree is a single widget, you can't change the style of individual rows
or cells in the normal way. This is because there are no elements that display the individual
cells, like there is with the listbox. Instead, all drawing is done by the tree body using data
supplied by the view. This is an important point and many XUL developers have trouble
understanding this aspect. To modify the appearance of a tree cell, the view must instead
associate a set of keywords for a row and cell. A special CSS syntax is used which styles
components of the tree body with those keywords. In a sense, it is somewhat like using CSS
classes. Tree styling will be discussed in detail in a later section.
Tree Elements
Trees can be created with the tree element, which is described in the following sections. There
are also two elements used to define the columns to be displayed in the tree.
• tree
This is the outer element of a tree.
• treecols
This element is a placeholder for a set of treecol elements.
• treecol
This is used to declare a column of the tree. By using this element, you can specify
additional information about how the data in the columns are sorted and if the user can
resize the columns. You should always place an id attribute on a column, as Mozilla uses
the ids to identify the columns when rearranging and hiding them. This is no longer
required in Mozilla 1.8 and later, but it is still a good idea to use ids on columns.
• treechildren
This contains the main body of the tree where the individual rows of data will be
displayed.
<tree flex="1">
<treecols>
<treecol id="nameColumn" label="Name" flex="1"/>
<treecol id="addressColumn" label="Address" flex="2"/>
</treecols>
<treechildren/>
</tree>
First, the entire table is surrounded with a tree element. This declares an element that is used
as a table or tree. As with HTML tables, the data in a tree is always organized into rows. The
columns are specified using the treecols tag.
You may place as many columns as you wish in a tree. As with listboxes, a header row will
appear with column labels. A drop-down menu will appear in the upper-right corner of the tree,
which the user may use to show and hide individual columns. Each column is created with a
treecol element. You can set the header label using the label attribute. You may also want to
make the columns flexible if your tree is flexible, so that the columns stretch as the tree does. In
this example, the second column will be approximately twice as wide as the first column. All of
the columns should be placed directly inside a treecols element.
In this case we haven't specified a view to supply the tree's data, so we'll only see column
headers and an empty tree body. You may have to resize the window to see anything since
there isn't any data to display. Since the tree has been marked as flexible, the body will stretch
to fit the available space. Making a tree flexible is quite commonly done, as it is often the case
that the data in the tree is the most significant information displayed, so it makes sense to make
the tree grow to fit. However, you may specify a specific number of rows to appear in a tree by
setting the rows attribute on the tree element. This attribute specifies how many rows are
displayed in the user interface, not how many rows of data there are. The total number of rows
is supplied by the tree view. If there are more rows of data in the tree, a scrollbar will appear to
allow the user to see the rest of them. If you don't specify the rows attribute, the default value is
0, which means that none of the rows will appear. In this case, you would make the tree flexible.
If your tree is flexible, it doesn't need a rows attribute since it will grow to fit the available space.
Having said that the data to be displayed in a tree comes from a view and not from XUL tags,
there happens to be a built-in tree view which gets its data from XUL tags. This may be a bit
confusing, but essentially, one of the built-in tree views uses a set of tags which can be used to
specify information about the data in the tree. The following tags are used:
• treeitem
This contains a single parent row and all its descendants. This element also serves as
the item which can be selected by the user. The treeitem tag would go around the entire
row so that it is selectable as a whole.
• treerow
A single row in the tree, which should be placed inside a treeitem tag.
• treecell
A single cell in a tree. This element would go inside a treerow element.
Conveniently, these tags may be placed directly inside the treechildren tag, nested in the order
above. The tags define the data to be displayed in the tree body. In this case, the tree uses the
built-in tree view, called a content tree view, which uses the labels and values specified on
these elements as the data for the tree. When the tree needs to display a row, the tree asks the
content tree view for a cell's label by calling the view's getCellText function, which in turn gets
the data from the label of the appropriate treecell.
However, the three elements listed above are not displayed directly. They are used only as the
source for the data for the view. Thus, only a handful of attributes apply to the treeitem and
related elements. For instance, you cannot change the appearance of the tree rows using the
style attribute or with other CSS properties and the box related features such as flexibility and
orientation cannot be used.
In fact, apart from some tree specific attributes, the only attributes that will have any effect will
be the label attribute to set a text label for a cell and the src attribute to set an image. However,
there are special ways of styling the tree and setting other features which we will see in later
sections.
Also, events do not get sent to treeitem element and their children; instead they get sent to the
treechildren element.
That the treeitems are unlike other XUL elements is a common source of confusion for XUL
developers. Essentially, the tree content view is a view where the data for the cells is supplied
from tags placed inside the tree. Naturally, if you are using a different kind of view, the data will
be supplied from another source, and there won't be any treeitem elements at all.
Let's start by looking at how to create a simple tree with multiple columns using the tree content
view. This could be used to create a list of mail messages. There might be multiple columns,
such as the sender and the subject.
<tree flex="1">
<treecols>
<treecol id="sender" label="Sender" flex="1"/>
<treecol id="subject" label="Subject" flex="2"/>
</treecols>
<treechildren>
<treeitem>
<treerow>
<treecell label="[email protected]"/>
<treecell label="Top secret plans"/>
</treerow>
</treeitem>
<treeitem>
<treerow>
<treecell label="[email protected]"/>
<treecell label="Let's do lunch"/>
</treerow>
</treeitem>
</treechildren>
</tree>
As can be seen in the image, the tree has been created with two rows of data.
This tree has two columns, the second of
which will take up more space than the
first. You will usually make the columns
flexible. You can also supply widths with
the width attribute. You should include
the same number of treecol elements as
there are columns in the tree. Otherwise strange things might happen.
The header row is created automatically. The button in the upper right corner can be used to
hide and show the columns. You can place a hidecolumnpicker attribute on the tree and set it to
true if you would like to hide this button. If this button is hidden, the user will not be able to hide
columns.
Make sure that you set an id attribute on each column or the hiding and showing of columns will
not work in all versions of Mozilla.
The treechildren element surrounds all of the rows. Inside the body are individual rows, which
may in turn contain other rows. For a simpler tree, each row is created with the treeitem and
treerow elements. The treerow element surrounds all of the cells in a single row, while a
treeitem element would surround a row and all of its children. Trees with nested rows are
described in the next section.
Inside the rows, you will put individual tree cells. These are created using the treecell element.
You can set the text for the cell using the label attribute. The first treecell in a row determines
the content that will appear in the first column, the second treecell determines the content that
will appear in the second column, and so on.
The user can select the treeitems by clicking on them with the mouse, or by highlighting them
with the keyboard. The user can select multiple items by holding down the Shift or Control keys
and clicking additional rows. To disable multiple selection, place a seltype attribute on the tree,
set to the value single. With this, the user may only select a single row at a time.
Tree Example
Let's add a tree to the find files window where the results of the search would be displayed. The
tree will use a content tree view. The following code should be added in place of the iframe.
<tree flex="1">
<treecols>
<treecol id="name" label="Filename" flex="1"/>
<treecol id="location" label="Location" flex="2"/>
<treecol id="size" label="Size" flex="1"/>
</treecols>
<treechildren>
<treeitem>
<treerow>
<treecell label="mozilla"/>
<treecell label="/usr/local"/>
<treecell label="2520 bytes"/>
</treerow>
</treeitem>
</treechildren>
</tree>
We've added a tree with three columns for the filename, the location and the file size. The
second column will appear twice as wide due to the larger flexibility. A single row has been
added to demonstrate what the table would look like with a row. In a real implementation, the
rows would be added by a script as the search was performed, or a custom view would be
created to hold the data.
Hierarchical trees
The tree element is also used to create hierarchical lists, like that found in a file manager or a
browser's bookmarks list. The tree view has a number of functions which specify the hierarchy
of the items in a tree. Each item in the tree has a level starting at 0. The topmost items in the
tree will have a level of 0, the children of those items will have a level of 1, the children below
that will have a level of 2, and so on. The tree will query the view for the level of each item in
order to determine how to draw the rows.
The tree will draw the open and close arrows to open and close a parent item as well as lines
connecting the children to their parents. The tree will also handle drawing the rows with the right
level of indenting. However, the view will need to make sure it keeps track of the level of the
rows as necessary. This can sometimes be quite tricky, but fortunately, the built-in content tree
view does all of the hard work for us.
To create a set of nested rows, all we need to do is add a second treechildren element inside
the parent treeitem. You can then add items inside that to specify the child rows of an item.
Don't put the inner treechildren element inside the treerow as this won't work.
You can repeat this process to create deeply nested trees. Essentially, a treeitem element can
contain either single rows which are declared with the treerow element or a set of rows which
are declared with the treechildren element.
There are two other things you need to do to make sure the hierarchy works properly. First, you
need to mark the treeitem element that has children as a container. You do this by adding the
container attribute to it as follows:
<treeitem container="true"/>
This allows the user to double-click on the treeitem to expand and collapse the inner rows. You
can have the child rows initially displayed by adding the open attribute. When the user expands
and collapses the parent, the view's toggleOpenState function will be called to toggle the item
between open and closed. For a content tree view, this will set the open attribute to reflect the
current state.
The second change is that you must put the primary attribute on the first column. This causes a
small triangle or plus sign to appear before the cells in that column to indicate the cells that can
be expanded. In addition, child rows are indented. Note also that the user cannot hide the
primary column using the drop down to the right of the columns.
<tree rows="6">
<treecols>
<treecol id="firstname" label="First Name" primary="true" flex="3"/>
<treecol id="lastname" label="Last Name" flex="7"/>
</treecols>
<treechildren>
<treeitem container="true" open="true">
<treerow>
<treecell label="Guys"/>
</treerow>
<treechildren>
<treeitem>
<treerow>
<treecell label="Bob"/>
<treecell label="Carpenter"/>
</treerow>
</treeitem>
<treeitem>
<treerow>
<treecell label="Jerry"/>
<treecell label="Hodge"/>
</treerow>
</treeitem>
</treechildren>
</treeitem>
</treechildren>
</tree>
The outer treeitem element contains a single treerow element and a treechildren element. The
former creates the data for the parent row and the latter contains the child items.
You can nest rows deeper as well. Remember that you must use the container attribute on rows
which contain child rows. The simple presence of child rows isn't sufficient to indicate this, as
you may have a container that has no children but should still be treated like a container. For
example, a directory with no files in it should still be treated like a container whereas a file
should not.
One additional attribute you can add to the tree is enableColumnDrag. (Note the mixed case.) If
set to true, the user may drag the column headers around to rearrange the order of the
columns.
A user will also likely want to change the column widths. You can do this by placing a splitter
element in between each treecol element. A small notch will appear in between each column
header which the user may drag to change the width of a column. You can use the style class
tree-splitter to hide the notch, although the column may still be resized.
You can set a minimum or maximum width of a column using the minwidth or maxwidth
attributes.
You can set the hidden attribute on a column to true to have the column hidden by default. The
user can choose to show the column by selecting it from the drop-down on the end of the
header row.
As with all elements, the persist attribute can be used to save the state of the columns in-
between sessions. Thus, once the user has decided on a column layout they like, it will
automatically be saved for next time. You will need to save a number of attributes as indicated
in the example below:
<treechildren>
<treeitem>
<treerow>
<treecell label="Joshua Granville"/>
<treecell label="Vancouver"/>
<treecell label="7:06:00"/>
<treecell label="9:10:26"/>
</treerow>
</treeitem>
<treeitem>
<treerow>
<treecell label="Robert Valhalla"/>
<treecell label="Seattle"/>
<treecell label="7:08:00"/>
<treecell label="9:15:51"/>
</treerow>
</treeitem>
</treechildren>
</tree>
Three attributes of the columns must be persisted, the width attribute to save the column widths,
the ordinal attribute which holds the position of the column, and the hidden attribute which holds
whether the column is hidden or visible.
Tree Selection
The section will describe how to get and set the selected items in a tree.
Each treeitem element in a tree may be selected individually. If you add the seltype attribute to
the tree, set to the value single, the user may only select one row at a time. Otherwise, the user
may select multiple rows, which will not necessarily be contiguous. The tree provides a number
of functions which can be used to determine whether an item is selected.
First, let's see how we can determine when an item is selected. The onselect event handler may
be added to the tree element. When the user selects an item from the tree, the event handler is
called. The user may also change the selection by using the cursor keys. If the user holds down
the cursor key to rapidly scroll through the items, the event handler is not called until the user
stops. This results in a performance improvement. This also means that the highlight will appear
on several items even though the select event is never fired for those items.
The tree has a property currentIndex, which can be used to get the currently selected item,
where the first row is 0.
Child items are included in the count just after their parents. This means that if there are 3 top-
level items and each has two child items, there will be a total of 9 items. The first item (at index
0) will be the first top-level item. The next item at index 1 will be its first child. The second child
will be at index 2 and the second parent item will be at position 3 and so on.
Because the selected items in a multiple selection tree are not necessarily contiguous, you can
retrieve each block of contigous selections using the getRangeCount and getRangeAt functions.
The first function returns the number of selection ranges there are. If only one value is selected,
this value will be 1. You would then write a loop for the number of ranges, calling getRangeAt to
get the actual indices of the start and end of the range.
The getRangeAt function takes three arguments. The first is the range index. The second is an
object which will be filled in by the function with the index of the first selected item. The third
argument is an object which will be filled in with the index of the last selected item.
For example:
If you just want to find out if a specific row is selected, use can use the isSelected function. It
takes a row index as an argument and returns true if that row is selected.
alert(tree.view.selection.isSelected(3));
The selection object has a number of functions which may be used to change the selection. The
simplest function is the select function, which deselects any rows that are currently selected and
selects one specific row. For example, the following code will select the row at index 5:
tree.view.selection.select(5);
Note that you should not just change the tree's currentIndex property to change the selection.
Instead, you should use the selection's select function as in the example above. You can select
all rows with the selectAll function. Note that rows nested inside containers that are not open will
not be selected. Naturally, this will only have any effect for trees that use multiple selection.
There is also a clearSelection function to clear the selection, and an invertSelection function to
reverse the selection, that is, deselect all selected rows and select all unselected rows.
To select specific rows, use the rangedSelect function which selects all rows in between two
indices. Here is an example which selects rows between index 2 and 7. Note that rows 2 and 7
will also be selected.
tree.view.selection.rangedSelect(2,7,true);
The last argument indicates whether to add to the current selection or not. If true, the range will
be added to the existing selection. If false, all existing selected rows will be deselected first.
Finally, the clearRange function may be used to deselect a range of rows, leaving rows outside
the range unaffected.
tree.view.selection.clearRange(2,7);
So far, we have only been using the built-in content tree view. In this section, we will look at
creating a custom view. This is necessary when the amount of data is large or arranged in a
complex way. For instance, it just wouldn't be viable performance-wise to use treeitems when
there are several thousand rows. You might also implement a custom view when you want to
perform computations on the data to be displayed. Since the view can store and retrieve the
data in the most suitable manner for the kind of data used, the tree can be used even when
there are hundreds of thousands of rows to be displayed.
To implement a custom view, you will need to create an object which implements the
nsITreeView interface. You can create these objects in JavaScript, but you will need a separate
object for each tree. Naturally, since a custom tree view is being used, the content tree view will
not be used, so the treeitem, treerow, and treecell elements will have no purpose since the
custom view will get its data from elsewhere. Thus, you can just leave the treechildren element
empty. The following example shows this:
To assign data to be displayed in the tree, the view object needs to be created which is used to
indicate the value of each cell, the total number of rows plus other optional information. The tree
will call methods of the view to get the information that it needs to display.
In general, although the tree view has thirty or so functions that may be implemented, you only
need to implement the ones that the tree will call. Three methods that you should implement are
listed below.
• rowCount
This property should be set to the total number of rows in the tree.
• getCellText( row , column )
This method should return the text contents at the specified row and column. This will be
called to display data for each cell. The rows are supplied as numeric values starting at 0.
The columns are supplied as the values of the id attribute on the columns. In Mozilla 1.8
and later, the column will instead be a TreeColumn object.
• setTree( tree )
This method is called once to set the tree element on the view.
Here is an example of defining such as object, which can be called whatever you want:
var treeView = {
rowCount : 10000,
getCellText : function(row,column){
if (column.id == "namecol") return "Row "+row;
else return "February 18";
},
setTree: function(treebox){ this.treebox = treebox; },
isContainer: function(row){ return false; },
isSeparator: function(row){ return false; },
isSorted: function(){ return false; },
getLevel: function(row){ return 0; },
getImageSrc: function(row,col){ return null; },
getRowProperties: function(row,props){},
getCellProperties: function(row,col,props){},
getColumnProperties: function(colid,col,props){}
};
The functions in the example not described above do not need to perform any action, but they
must be implemented as the tree calls them to gather additional information.
This example can be used for a tree with 10000 rows. The contents of the cells in the first
column will be set to the text 'Row X' where X is the row number. The contents of the cells in the
second column will be set to 'February 18'. The if statement in the function getCellText
compares the column to the text 'namecol'. This text 'namecol' corresponds to the id of the first
treecol in the example above. This example is very simple of course -- in reality you would have
more complex data in each cell.
The final step is to associate the view object with the tree. The tree has a property view, which
can be assigned to the view object declared above. We can assign a value to this property at
any time to set or change the view.
function setView()
{
document.getElementById('my-tree').view = treeView;
}
The following presents the example together. An inline script has been used here to simplify the
example. Normally, you would put the script in an external script file.
<?xml version="1.0"?>
<script>
var treeView = {
rowCount : 10000,
getCellText : function(row,column){
if (column.id == "namecol") return "Row "+row;
else return "February 18";
},
setTree: function(treebox){ this.treebox = treebox; },
isContainer: function(row){ return false; },
isSeparator: function(row){ return false; },
isSorted: function(){ return false; },
getLevel: function(row){ return 0; },
getImageSrc: function(row,col){ return null; },
getRowProperties: function(row,props){},
getCellProperties: function(row,col,props){},
getColumnProperties: function(colid,col,props){}
};
function setView()
{
document.getElementById('my-tree').view=treeView;
}
</script>
</window>
In the image, you can see two columns, each with data taken from the getCellText function. The
setView function has been called in the onload handler for the window, but you could also set
the view later if you wish. You can change the view at any time.
One thing to note is that the getCellText function is only
called when necessary to display the contents. In the 10000
row example above, getCellText is only called for the cells
that are currently displayed. In the image, only seven rows
are displayed, the last only partially, so getCellText will be
called only 14 times, one for each row and column. It is called
for other rows when the user scrolls through them. This
makes the tree much more efficient.
Note that the view object is also available for trees using the built-in content view. You can use
this to get the cell labels and other information.
The nsITreeView interface lists all of the properties and methods that you can implement for the
tree view. We'll look at more of these in the next section.
In the last section, we created a simple tree view that implemented only a minimum amount of
functionality. Next, let's look at some additional functions that views may implement. Here, we
will examine how to create a hierarchical set of items using the view. This is a fairly tricky
process as it involves keeping track of which items have children and also which rows are open
and closed.
Every row in the tree has a nesting level. The topmost rows are at level 0, the children of those
rows are at level 1, their children at level 2 and so on. The tree will query the view for each row
by calling its getLevel method to find out the level of that row. The view will need to return 0 for
the outermost rows and higher values for inner rows. The tree will use this information to
determine the hierarchical structure of the rows.
In addition to the getLevel method, there is a hasNextSibling function which, given a row, should
return true if there is another row afterwards at the same level. This function is used,
specifically, to draw the nesting lines along the side of the tree.
The getParentIndex method is expected to return the parent row of a given row, that is, the row
before it with a lower nesting value. All of these methods must be implemented by the view for
children to be handled properly.
There are also three functions, isContainer, isContainerEmpty and isContainerOpen that are
used to handle a parent item in the tree. Naturally, the isContainer method should return true if
a row is a container and might contain children. The isContainerEmpty method should return
true if a row is an empty container, for instance, a directory with no files in it. The view is
required to keep track of which items are opened and closed, so the isContainerOpen method is
used to determine this. The tree will call this method to determine which containers are open
and which are closed. Note that the tree will call neither isContainerEmpty nor isContainerOpen
for rows that are not containers as indicated by the return value of the isContainer method.
A container may be rendered differently than a non-container. For instance, a container may
have a folder icon beside it. A style sheet may be used to style items based on various
properties such as whether a row is open. This is described in a later section. A non-empty
container will be displayed with a twisty next to it so that the user may open and close the row to
see child items. Empty containers will not have a twisty, but will still be treated like a container.
When the user clicks the twisty to open a row, the tree will call the view's toggleOpenState
method. The view should then perform any necessary operations to retrieve the child rows and
then update the tree with the new rows.
getLevel(row)
hasNextSibling(row, afterIndex)
getParentIndex(row)
isContainer(row)
isContainerEmpty(row)
isContainerOpen(row)
toggleOpenClose(row)
The afterIndex argument to hasNextSibling function is used as optimization to only start looking
for the next sibling after that point. For instance, the caller might already know where the next
sibling might possibly be. Imagine a situation where a row had subrows and those subrows had
child rows of their own and several are open. In could take a while in some implementations to
determine what next sibling's row index in this case.
Let's put this together into a simple example that takes an array and constructs a tree from it.
We'll examine it piece by piece.
<window onload="init();"
xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
</window>
We use a simple tree here with no data in the treechildren. The 'init' function is called when the
window is loaded to initialize the tree. It simply sets the custom view by retrieving the tree and
setting its 'view' property. We will define 'treeView' next.
function init() {
document.getElementById("elementList").view = treeView;
}
The custom tree view will need to implement a number of methods, of which the important ones
will be examined individually. This tree will only support a single parent level with an inner child
level, but it could be extended to support additional levels without too much effort. First we'll
define two structures to hold the data for the tree, the first will hold a map between parents and
the children that may contain, and the second will hold an array of the visible items. Remember
that a custom view must keep track of which items are visible itself.
var treeView = {
childData : {
Solids: ["Silver", "Gold", "Lead"],
Liquids: ["Mercury"],
Gases: ["Helium", "Nitrogen"]
},
visibleData : [
["Solids", true, false],
["Liquids", true, false],
["Gases", true, false]
],
The childData structure holds an array of the children for each of the three parent nodes. The
visibleData array begins with only three items visible, the three top level items. Items will be
added and removed from this array when items are opened or closed. Essentially, when a
parent row is opened, the children will be taken from the childData map and inserted into the
visibleData array. For example, if the Liquids row was opened, the corresponding array from
childData, which in this case contains only the single Mercury child, will be inserted into the
visibleData array after Liquids but before Gases. This will increase the array size by one. The
two booleans in each row in the visibleData structure indicate whether a row is a container and
whether it is open respectively. Obviously, the new inserted child items will have both values set
to false.
Next, we need to implement the tree view interface. First, the simple functions:
treeBox: null,
selection: null,
The rowCount function will return the length of the visibleData array. Note that it should return
the current number of visible rows, not the total. So, at first, only three items are visible and the
rowCount should be three, even though six rows are hidden.
The setTree function will be called to set the tree's box object. The tree box object is a
specialized type of box object specific to trees and will be examined in detail in the next section.
It is used to aid in drawing the tree. In this example, we will only need one function of the box
object, to be able to redraw the tree when items are added or removed.
The getCellText, isContainer and isContainerOpen functions just return the corresponding
element from the visibleData array. Finally, the remaining functions can just return false since
we don't need those features. If we had a row that had no children we would want to implement
the isContainerEmpty function so that it returned true for those elements.
getParentIndex: function(idx) {
if (this.isContainer(idx)) return -1;
for (var t = idx - 1; t >= 0 ; t--) {
if (this.isContainer(t)) return t;
}
},
The getParentIndex will need to find the parent of a given index. In our simple example, there is
only two levels, so we know that containers don't have parents, so -1 is returned for these items.
Otherwise, we just iterate backwards through the rows looking for one that is a container. Next,
the getLevel function.
getLevel: function(idx) {
if (this.isContainer(idx)) return 0;
return 1;
},
The getLevel function is simple. It just returns 0 for container rows and 1 for non-containers. If
we wanted to add an additional level of children, those rows would have a level of 2.
The hasNextSibling function needs to return true if there is a row at the same level after a given
row. The code above uses a brute force method which simply iterates over the rows looking for
one, returning true if a row exists with the same level and false once it finds a row that has a
lower level. In this simple example, this method is fine, but a tree with a larger set of data will
want to use a more optimal method of determining whether a later sibling exists.
The final function of note is toggleOpenState, which is the most complex. It needs to modify the
visibleItems array when a row is opened or closed.
toggleOpenState: function(idx) {
var item = this.visibleData[idx];
if (!item[1]) return;
if (item[2]) {
item[2] = false;
First we will need to check if the row is a container. If not, the function just returns since non-
containers cannot be opened or closed. Since the third element in the item array (with an index
of 2) holds whether the row is open or not, we use two code paths, the first to close a row and
the second to open a row. Let's examine each block of code, but let's look at the second block
for opening a row first.
item[2] = true;
The first line makes the row open in the array so that we will know the next time the
toggleOpenState function is called that the row will need to be closed instead. Next, we look up
the data in the childData map for the row. The result is that 'toinsert' will be set to one of the
child arrays, for example ["Silver", "Gold", "Lead"] if the Solids row is the one being opened.
Next, we use the array's splice function to insert a new row for each item. For Solids, three
items will be inserted.
Finally, the tree box's rowCountChanged function needs to be called. Recall that treeBox is a
tree box object and was set earlier by a call to the setTree function. The tree box object will be
created by the tree for you and you can call its functions. In this case, we use the
rowCountChanged function to inform the tree that some rows were added to the underlying
data. The tree will then redraw the tree as needed and the result is that the child rows will
appear inside the container. The various other functions implemented above such as getLevel
and isContainer are used by the tree to determine how to draw the tree.
The rowCountChanged function takes two arguments, the index where the first row was
inserted and the number of rows to insert. In the code above we indicate that the starting row is
the value of 'idx' plus one, which will be the first child under the parent. The tree will use this
information and add space for the appropriate number of rows and push the rows afterwards
down. Make sure to pass the right number or the tree might redraw incorrectly or try to draw
more rows than necessary.
item[2] = false;
First, the item is set closed in the array. Then, we scan along the rows until we come to one that
is at the same level. All those that have a higher level will need to be removed, but a row at the
same level will be the next container which should not be removed.
Finally, we use the splice function to remove the rows from the visibleData array and call the
rowCountChanged function to redraw the tree. When deleting rows, you will need to supply a
negative count of the number of rows to delete.
There are several other view functions we can implement but they don't need to do anything in
this example, so we can create functions that do nothing for those. They are added near the
end of the complete example, shown here:
<?xml version="1.0"?>
<?xml-stylesheet href="chrome://global/skin/" type="text/css"?>
<window onload="init();"
xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
<script>
<![CDATA[
var treeView = {
childData : {
Solids: ["Silver", "Gold", "Lead"],
Liquids: ["Mercury"],
Gases: ["Helium", "Nitrogen"]
},
visibleData : [
["Solids", true, false],
["Liquids", true, false],
["Gases", true, false]
],
treeBox: null,
selection: null,
getParentIndex: function(idx) {
if (this.isContainer(idx)) return -1;
for (var t = idx - 1; t >= 0 ; t--) {
if (this.isContainer(t)) return t;
}
},
getLevel: function(idx) {
if (this.isContainer(idx)) return 0;
return 1;
},
hasNextSibling: function(idx, after) {
var thisLevel = this.getLevel(idx);
for (var t = idx + 1; t < this.visibleData.length; t++) {
var nextLevel = this.getLevel(t)
if (nextLevel == thisLevel) return true;
else if (nextLevel < thisLevel) return false;
}
},
toggleOpenState: function(idx) {
var item = this.visibleData[idx];
if (!item[1]) return;
if (item[2]) {
item[2] = false;
function init() {
document.getElementById("elementList").view = treeView;
}
]]></script>
</window>
Box objects were described in an earlier section. The tree box object is a specialized box object
used specifically for trees. The tree box implements the TreeBoxObject interface.
We already saw the rowCountChanged function of the tree box object in the previous section. It
is used to indicate that one or more rows have been added to the tree or removed from the tree.
The tree will redraw the affected area. You don't need to call the rowCountChanged function
when a row has simply changed in some way, for example if a cell's label changes. In this case,
there are other drawing functions that can be used. The simplest is to call invalidateRow which
will redraw a specific row in the tree. The tree will call the view to get the updated data and
update the contents of the tree on screen.
Other redrawing functions are invalidateCell to redraw only a single cell, invalidateColumn to
redraw a column, invalidateRange to redraw a range of rows, or the invalidate function to
redraw the entire tree. Note that redrawing does not occur until the calling script ends since
Mozilla does not redraw in the background.
You can also scroll the tree using four different methods, similar to those available for listboxes.
The scrollToRow function may be used to scroll to a particular row. Here is a simple example.
<hbox align="center">
<label value="Scroll to row:"/>
<textbox id="tbox"/>
<button label="Scroll" oncommand="doScroll();"/>
</hbox>
Note that we use the rows attribute on the tree to specify that only four rows are displayed at a
time. This makes it easier to see how the example works. Also, notice that the first row is 0.
The doScroll function gets the box object and calls the scrollToRow function with an argument
set to the value of the textbox. You might notice that the tree box object can be retieved in the
same way as other box objects using the boxObject property, however we need to call
QueryInterface to cast the box object to the more specific tree box object. The functions of the
more general box object are also available to trees.
Note that in Firefox 1.0 and Mozilla 1.7 and earlier, the getPageLength function is called
getPageCount instead. The name was changed to getPageLength since it was confusing before
since it doesn't return the number of pages, but the size of each page. You could determine the
number of pages by dividing the total number of rows by the page length.
The ensureRowIsVisible function will scroll to a row just as scrollToRow does, but does not
scroll if the row is already visible.
Cell Coordinates
Some of the most interesting functions of the tree box object are several functions which may
be used to get the parts of the tree at specific coordinates and vice versa. The getCellAt
function may be used to get the cell at specific pixel location while the getRowAt function may
be used to get a row at a specific location. The getRowAt function takes two arguments, the x
and y coordinates to use.
This example will return the index of the row with a horizontal position of 50 and a vertical
position of 100. Naturally, it doesn't really matter what the value of the x coordinate is since
rows always take up the entire horizontal space of the tree. One important thing to note is that
the coordinates are measured from the upper left corner of the containing document, not the
edge of the tree itself. This makes it easy to pass event coordinates directly to these functions,
as in the following example of the getCellAt function.
<script>
function updateFields(event)
{
var row = {}, column = {}, part = {};
var tree = document.getElementById("thetree");
document.getElementById("row").value = row.value;
document.getElementById("column").value = column.value;
document.getElementById("part").value = part.value;
}
</script>
<label value="Row:"/>
<label id="row"/>
<label value="Column:"/>
<label id="column"/>
<label value="Child Type:"/>
<label id="part"/>
The getCellAt function takes five arguments, the coordinates to look up and three out
parameters. An out parameter is used since the function needs to return more that one value.
You will see a number of interfaces that use out parameters in the object reference. These are
indicated by the word 'out' before the argument. For these, you will need to supply an empty
object and the function will fill in the 'value' property with the necessary value.
The three out parameters will be filled in with the row, the column and a child type. The row is
the index of the row the mouse is over, since we call it with the event coordinates of a
mousemove event. If the coordinate is not over a row, the row value will be set to -1. The
column is a column object in Mozilla 1.8 and later. In earlier versions, columns are identified
with a string, the id of the column. In later versions, a separate column object exists, which can
be queried for column data.
The following line is used so that the example above will work in all versions.
If the column is a string, we are running on Mozilla 1.7 or earlier, but for later versions we get
the column id from the column object. If you are writing code for multiple versions, you should
check for this as above.
The last argument to getCellAt is the child type which is filled in with a string depending on what
part of the cell the coordinate is at. If you move the mouse around in the previous example, you
might notice the label change between 'text' and 'cell'. The value 'text' indicates the area where
the text would be drawn and the value 'cell' indicates the area around the text, for example, the
margin on the left side where the open and close twisties are normally drawn. If there was a
twisty, however, the value would be 'twisty' instead. This is convenient since you could
determine whether the user clicked on a twisty instead of another part of the row. In fact, this is
what the underlying tree code does when the user double clicks the twisty. The final value that
may be returned is 'image' if there is an image in the tree cell and the coordinate corresponds to
a location where the image is. Of course, in many cases you may not care what part of the cell
the coordinate is on and just want the row and column.
To go in reverse and get the cell at a specific coordinate, use the getCoordsForCellItem
function. It takes seven arguments, as described below.
The row, column, and part arguments are similar to those returned from the getCellAt function.
Again, the column should be either a string or a column object depending on which version you
are using. The cell part type may be used to get the coordinates of either the text, the entire cell,
the twisty or the image in the cell. The same values as the getCellAt function are used. The
getCoordsForCellItem function returns the x and y coordinates in addition to the width and
height, all as out parameters.
9. RDF and Templates
Introduction to RDF
In this section, we'll look at RDF (Resource Description Framework).
We can use the tree elements to display a set of data, such as bookmarks or mail messages.
However, it would be inconvenient to do so by entering the data directly into the XUL file. It
would make it very difficult to modify the bookmarks if they were directly in the XUL file. The
way to solve this is by using RDF datasources.
RDF (Resource Description Framework) is a format that can be used to store resources such as
bookmarks or mail. Alternatively, data in other formats can be used and code written that will
read the file and create RDF data from it. This is how Mozilla works when it reads data such as
bookmarks, the history or mail messages. Mozilla provides datasources for this common data
so that you can easily use them.
You can use any of the provided RDF datasources to populate trees with data or you can point
to an RDF file stored in XML which contains the data. This makes it very convenient to display
trees with lots of rows in them. RDF can also populate other XUL elements as well such as
listboxes and menus. We'll see this in the next section.
A very brief overview of RDF will be provided here. For a more detailed guide to RDF, read
Introduction to the RDF Model It is recommended that you read this guide if you are new to
RDF. To see some example RDF/XML files, look at those provided with Mozilla. They have the
extension rdf.
<?xml version="1.0"?>
<RDF:RDF
xmlns:RDF="http://www.w3.org/1999/02/22-rdf-syntax-ns#">
...
</RDF:RDF>
This has some similarities to the XUL header. Instead of the window element, the RDF element
is used. You can see the namespace for RDF was declared so that the RDF elements are
recognized properly. Inside, the RDF element, you will place the data.
A brief description of RDF will be given here. For more information about RDF, see the RDF
specification. Let's take the example of a bookmarks list generated from RDF. A bookmarks list
contains a set of records, each with a set of data associated with it, such as a URL, a bookmark
title and a visited date.
Think of the bookmarks as a database, which is stored as a large table with numerous fields. In
the case of RDF however, the lists may be hierarchical as well. This is necessary so that we
can have folders or categories of bookmarks. Each of the fields in an RDF database is a
resource, each with a name associated with it. The name is described by a URI.
For example, a selection of the fields in the Mozilla bookmark list is described by the URIs
below:
http://home.netscape.com/WEB-
Last Visited Date of last visit
rdf#LastVisitDate
These are generated by taking a namespace name and appending the field name. In the next
section, we'll look at how we can use these to fill in the field values automatically. Note that the
last modified date has a slightly different namespace than the other three.
Below, a sample RDF/XML file is shown, listing a table with three records and three fields.
<RDF:RDF xmlns:RDF="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:ANIMALS="http://www.some-fictitious-zoo.com/rdf#">
<RDF:Seq about="http://www.some-fictitious-zoo.com/all-animals">
<RDF:li>
<RDF:Description about="http://www.some-fictitious-zoo.com/mammals/lion">
<ANIMALS:name>Lion</ANIMALS:name>
<ANIMALS:species>Panthera leo</ANIMALS:species>
<ANIMALS:class>Mammal</ANIMALS:class>
</RDF:Description>
</RDF:li>
<RDF:li>
<RDF:Description about="http://www.some-fictitious-
zoo.com/arachnids/tarantula">
<ANIMALS:name>Tarantula</ANIMALS:name>
<ANIMALS:species>Avicularia avicularia</ANIMALS:species>
<ANIMALS:class>Arachnid</ANIMALS:class>
</RDF:Description>
</RDF:li>
<RDF:li>
<RDF:Description about="http://www.some-fictitious-
zoo.com/mammals/hippopotamus">
<ANIMALS:name>Hippopotamus</ANIMALS:name>
<ANIMALS:species>Hippopotamus amphibius</ANIMALS:species>
<ANIMALS:class>Mammal</ANIMALS:class>
</RDF:Description>
</RDF:li>
</RDF:Seq>
</RDF:RDF>
Here, three records have been described, one for each animal. Each RDF:Description tag
describes a single record. Within each record, three fields are described, name, species and
class. It isn't necessary for all records to have the same fields but it makes more sense to have
them all the same.
Each of three fields have been given a namespace of ANIMALS, the URL of which has been
declared on the RDF tag. The name was selected because it has meaning in this case, but we
could have selected something else. The namespace feature is useful because the class field
might conflict with that used for styles.
The Seq and li elements are used to specify that the records are in a list. This is much like how
HTML lists are declared. The Seq element is used to indicate that the elements are ordered, or
in sequence. Instead of the Seq element, you can also use Bag to indicate unordered data, and
Alt to indicate data where each record specifies alternative values (such as mirror URLs).
The resources can be referred to in a XUL file by combining the namespace URL followed by
the field name. In the example above, the following URIs are generated which can be used to
refer to the specific fields:
Name http://www.some-fictitious-zoo.com/rdf#name
http://www.some-fictitious-
Species
zoo.com/rdf#species
Class http://www.some-fictitious-zoo.com/rdf#class
Templates
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 next
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 template element. Inside it, you can place the elements that
you want to use for each constructed element. The 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 tree element.
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 then enter the chrome URL into the browser URL field.
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
button. 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.
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.)
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.
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 is 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.
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.
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 multiple resource values in the same attribute by separating them with a
space, as in the example below. More about resource syntax.
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. It is this object that 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.
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 label elements
would be created and added as children of the vbox 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 template itself is not displayed, although it still exists the 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 hbox will be created and it will be filled with a label for each item.
<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.
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 treeitem element and
related elements will be created for each row.
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 rule 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 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.
The following example demonstrates the earlier example with two rules:
<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">
<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>
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 spacer 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.
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 window 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.
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.
When using a tree, you will often use a template to build its content, to handle a large amount of
hierarchial data. Using a template with a tree uses very much the same syntax as with other
elements. You need to add a datasources and a ref attribute to the tree element, which specify
the datasource and root node to display. Multiple rules can be used to indicate different content
for different types of data.
As described in the previous section, the tree may use a tree builder for template generation
instead of the normal content builder. This means that elements will not be created for every
row in the tree, making it more efficient. The flags attribute set to the value dont-build-content,
as used in the example above, indicates that the tree builder should be used. If you leave the
attribute out, the content builder will be used. You can see the difference by using Mozilla's
DOM Inspector on a tree with and without the flag.
If you do use a content builder instead, note that the content won't generally get built until it is
needed. With hierarchical trees, the children don't get generated until the parent nodes have
been opened by the user.
In the template, there will be one treecell for each column in the tree. The cells should have a
label attribute to set the label for the cell. This would normally be set to an RDF property so that
the label is pulled from the datasource.
The following example demonstrates a template-built tree, in this case for the file system.
<template>
<rule>
<treechildren>
<treeitem uri="rdf:*">
<treerow>
<treecell label="rdf:http://home.netscape.com/NC-rdf#Name"/>
<treecell label="rdf:http://home.netscape.com/WEB-
rdf#LastModifiedDate"/>
</treerow>
</treeitem>
</treechildren>
</rule>
</template>
</tree>
Here, a tree is created with two columns, for the name and date of a file. The tree should
display a list of the files in the root directory. Only one rule is used, but you may add others if
needed. Like with other templates, the uri attribute on an element indicates where to start
generating content. The two cells grab the name and date from the datasource and place the
values in the cell labels.
This example shows why the uri attribute becomes useful. Notice how it has been placed on the
treeitem in the example, even though it is not a direct descendant of the rule element. We need
to put this attribute on only those elements that we want repeated for each resource. Because
we don't want multiple treechildren elements, we don't put it there. Instead we put the uri
attributes on the treeitem elements. Effectively, the elements outside (or above) the element
with the uri attribute are not duplicated whereas the element with the uri attribute and the
elements inside it are duplicated for each resource.
Note in the image that additional child elements below the top-level elements have been added
automatically. XUL knows how to add child elements when the templates or rules contain tree
elements or menu elements. It will generate tree elements as nested as necessary based on
the available RDF data.
An interesting part of RDF datasources is that the resource values are only determined when
the data is needed. This means that values that are deeper in the resource hierarchy are not
determined until the user navigates to that node in the tree. This becomes useful for certain
datasources where the data is determined dynamically.
Sorting Columns
If you try the previous example, you might note that the list of files is not sorted. Trees which
generate their data from a datasource have the optional ability to sort their data. You can sort
either ascending or descending on any column. The user may change the sort column and
direction by clicking the column headers. This sorting feature is not available for trees with static
content, although you can write a script to sort the data.
Sorting involves three attributes, which should be placed on the columns. The first attribute,
sort, should be set to an RDF property that is used as the sort key. Usually, this would be the
same as that used in the label of the cell in that column. If you set this on a column, the data will
be sorted in that column. The user can change the sort direction by clicking the column header.
If you do not set the sort attribute on a column, the data cannot be sorted by that column.
The sortDirection attribute (note the mixed case) is used to set the direction in which the column
will be sorted by default. Three values are possible:
The final attribute, sortActive should be set to true for one column, the one that you would like
to be sorted by default.
Although the sorting will function correctly with only those attributes, you may also use the style
class sortDirectionIndicator on a column that can be sorted. This will cause a small triangle to
appear on the column header that indicates the direction of the sort. If you don't do this, the user
may still sort the columns but will have no indication as to which column is currently sorted.
The following example changes the columns in the earlier example to incorporate the extra
features:
<treecols>
<treecol id="Name" label="Name" flex="1" primary="true"
class="sortDirectionIndicator" sortActive="true"
sortDirection="ascending"
sort="rdf:http://home.netscape.com/NC-rdf#Name"/>
<splitter/>
<treecol id="Date" label="Date" flex="1" class="sortDirectionIndicator"
sort="rdf:http://home.netscape.com/WEB-rdf#LastModifiedDate"/>
</treecols>
One additional thing you might want to do is persist which column is currently sorted, so that it is
remembered between sessions. To do this, we use the persist attribute on each treecol
element. There are five attributes of columns that need to be persisted, to save the column
width, the column order, whether the column is visible, which column is currently sorted and the
sort direction. The following example shows a sample column:
There are two additional attributes that can be added to the rule element that allow it to match in
certain special circumstances. Both are boolean attributes.
• iscontainer
If this attribute is set to true, then the rule will match all resources that have children. For
example, we could use this rule to match bookmark folders. This is convenient as the
RDF datasource does not need to include any special attributes to indicate this.
• isempty
If this attribute is set to true, then the rule will match all resources that have no children.
The two attributes above are really the reverse of each other. A resource might be a container
and be an empty one as well. However, this is different from a resource that is not a container.
For example, a bookmark folder is a container but it might or might not have children. However
a single bookmark or separator is not a container.
You can combine these two elements with other attribute matches for more specific rules.
RDF Datasources
Here, we'll look at additional datasources and how to use your own RDF files as datasources.
Mozilla provides a number of other built-in datasources. Some of them are listed here with a few
examples. They work very similarly to the bookmarks, although the fields will be different in
each case.
The history datasource provides access to the user's history list which is the list of URLs the
user has visited recently. The resource can be referred to using rdf:history as the datasource.
The table below shows the resources (or fields) that you can retrieve from the history
datasource. Put the URL values below where you want the value of the resource to be used.
http://home.netscape.com/NC-
Visit Count Number of page visits
rdf#VisitCount
A typical history list will display a tree with a selection of these fields. To use them, just put the
URL values above in the label attributes of the buttons or treecells. You can use
NC:HistoryRoot as the value of the ref attribute. You can also use the value
NC:HistoryByDate to get the history sorted into days.
Let's see an example of displaying the history list. We'll display the history in a tree with three
columns, the Name, the Page and the Date.
<treecols>
<treecol id="name" label="Name" flex="1"/>
<treecol id="url" label="URL" flex="1"/>
<treecol id="date" label="Date" flex="1"/>
</treecols>
<template>
<rule>
<treechildren>
<treeitem uri="rdf:*">
<treerow>
<treecell label="rdf:http://home.netscape.com/NC-rdf#Name"/>
<treecell label="rdf:http://home.netscape.com/NC-rdf#URL"/>
<treecell label="rdf:http://home.netscape.com/NC-rdf#Date"/>
</treerow>
</treeitem>
</treechildren>
</rule>
</template>
</tree>
Other Datasources
The tables below list some of the other datasources available with Mozilla. You can use any of
the resources that you want.
Bookmarks (rdf:bookmarks): The bookmarks are generated from the user's bookmark list.
Resources
http://home.netscape.com/WEB-
Last Modified Date of last modification
rdf#LastModifiedDate
Resources
http://home.netscape.com/NC-
Name Name of the file
rdf#Name
NC:FilesRo
Top level of the filesystem (usually the list of drives)
ot
By using a file URL for the ref attribute, you can select a specific directory to be
A file URL
returned. For example, you might use file:///windows or files:///usr/local.
The files datasource is an example of a datasource that determines its resources only when
necessary. We don't want every file in the filesystem to be determined before the data is
displayed. Instead, only the files and directories that the tree element (or other elements) will
need to display at a given time will be determined.
Composite Datasources
You can specify multiple datasources in the datasources attribute by separating them with
whitespace as in the example below. This has the effect of reading the data from all the
datasources mentioned.
This example reads the resources from the bookmarks, history and the animals.rdf file. They
are combined into a single composite datasource and can be used as if they were one.
The special datasource rdf:null corresponds to nothing. You can use this datasource if you want
to dynamically set the datasource using a script, but don't want one initially or don't know its
exact URL.
You can use any of the above internal datasources if you wish. There are several others for
mail, address books and searching and so on. However, you might want to use your own RDF
datasource stored in an RDF file. The file can be either a local file or a remote file. Just put the
URL of the RDF file in the datasources attribute.
Using RDF files provides just as much functionality as any of the internal datasources. You can
use rules to match specific types of content. The attributes on the rule element will match if they
match the attributes on an RDF Description element. You can also create RDF files that are
hierarchical.
The following is an example of how an RDF file can be used as a datasource. The RDF file is
fairly large and can be viewed separately: Source RDF
<treecols>
<treecol id="name" label="Name" primary="true" flex="1"/>
<treecol id="species" label="Species" flex="1"/>
</treecols>
<template>
<rule>
<treechildren>
<treeitem uri="rdf:*">
<treerow>
<treecell label="rdf:http://www.some-fictitious-zoo.com/rdf#name"/>
<treecell label="rdf:http://www.some-fictitious-zoo.com/rdf#species"/>
</treerow>
</treeitem>
</treechildren>
</rule>
</template>
</tree>
Here, the data has been generated from the file. The ref attribute has been set to the root
element in the RDF file, which is the top-level Seq. This will give us a complete list of animals. If
we wanted to, we could set the ref attribute to any of the other about attribute values to limit the
set of data that is returned. For example, to display only the reptiles, use a value of
http://www.some-fictitious-zoo.com/reptiles.
The example below shows how to display a particular piece of an RDF datasource by setting
the ref attribute.
<button label="Click here to see the mammals the zoo has" type="menu"
datasources="animals.rdf" ref="http://www.some-fictitious-zoo.com/mammals">
<template>
<rule ANIMALS:specimens="0"></rule>
<rule>
<menupopup>
<menuitem uri="rdf:*" label="rdf:http://www.some-fictitious-
zoo.com/rdf#name"/>
</menupopup>
</rule>
</template>
</button>
</window>
In this case only the mammals are desired, so we select the URI of the mammals list. You will
notice that the value of the ref attribute in the example is http://www.some-fictitious-
zoo.com/mammals which corresponds to one of the Seq elements in the RDF file. This causes
only the descendants of this list to be returned.
Two rules have been used here. The first rule catches all the resources that have their
ANIMALS:specimens attribute set to 0. You can see this attribute in the RDF file on each of the
Description elements. Some of them have a value of 0. So in these cases, rule one will match.
Because rule one has no content, nothing will be displayed for these ones. This is an effective
way to hide data that we don't want to display.
The second rule applies to all other resources and creates a row in a popup menu. The end
effect is that we get a popup menu containing all the mammals which have a specimen that is
not 0.
Advanced Rules
This section describes the more advanced rule syntax.
The rule syntax described so far is useful for some datasources but sometimes you will need to
display data in more complicated ways. The simple rule syntax is really just a shortcut for the
full rule syntax which is described below. Like the simple rules, full rules are placed within the
rule tag.
Full rules contain three child tags, a conditions tag, a bindings tag and an action tag, although
the bindings tag is not always needed.
The conditions element is used to specify the criteria for matching a given resource. You can
specify a number of conditions, all of which must match. In the simple rule syntax, the
conditions are placed directly on the rule element itself.
If the conditions match for a resource, the content placed within the actions tag is generated. In
the simple syntax, the content is placed directly inside the rule.
Rule Conditions
When a tree, menu or other element with a datasource generates content, the template builder
first finds the resource referred to by the ref attribute. It then iterates over all that resource's
child resources. It applies each resource to the conditions. If the conditions match for that
resource, the content in the actions element is generated for that resource. If the conditions do
not match, no content is generated.
The conditions element can contain three elements. The first is the content element, which
should always exist once and only once. It serves as a placeholder as the template builder
iterates through the resources. It specifies the name of a variable in which is placed a reference
to the root resource while the conditions are analyzed for a match. The root resource is the one
specified by the ref attribute on the element containing the template.
<content uri="?var"/>
The question mark indicates that the text following it is a variable. You can then use the variable
'var' within the remainder of the conditions. Of course, you can name the variable whatever you
want.
The next element is the member element, which is used to iterate through a set of child
resources. In RDF terms, that means a container such a Seq, Bag or Alt. Let's say you have a
list of cities described in the following RDF/XML fragment:
<RDF:Seq about="http://www.xulplanet.com/rdf/weather/cities">
<RDF:li resource="http://www.xulplanet.com/rdf/weather/city/Paris"/>
<RDF:li resource="http://www.xulplanet.com/rdf/weather/city/Manchester"/>
<RDF:li resource="http://www.xulplanet.com/rdf/weather/city/Melbourne"/>
<RDF:li resource="http://www.xulplanet.com/rdf/weather/city/Kiev"/>
</RDF:Seq>
<RDF:Description about="http://www.xulplanet.com/rdf/weather/city/Paris">
<cityset:name>Paris</cityset:name>
</RDF:Description>
.
.
.
You want to display a row in a tree for each city. To do this, use the member element as in the
following:
The template builder starts by grabbing the value of the ref attribute, which in this case is
http://www.xulplanet.com/rdf/weather/cities. This resource will be placed in the 'list' variable
as specified by the content tag. We can then get related resources to the root resource by using
the 'list' variable.
The template builder then sees the member element. It causes the builder to iterate over the
children of an element. The parent is specified by the container attribute and the children are
specified by the child attribute. In the example above, the value of the container attribute is the
variable 'list'. Thus the parent will be the value of the list variable, which has been set to the root
resource 'http://www.xulplanet.com/rdf/weather/cities'. The effect will be to iterate through the
list of children of 'http://www.xulplanet.com/rdf/weather/cities'.
If you look at the RDF above, the 'http://www.xulplanet.com/rdf/weather/cities' resource has four
children, one for each different city. The template builder iterates through each one, matching
the child against the value of the child attribute. In this case, it is just set to the variable 'city'. So
the builder will set the 'city' variable to the each child resource in turn.
Because there are no more conditions, the condition matches for each of those four resources
and the builder will generate content for each of the four. Of course, the example above doesn't
have any content. We'll add that later.
The next element is the triple element. It is used to check for the existence of a given triple (or
assertion) in the RDF datasource. A triple is like a property of a resource. For example, a triple
exists between a bookmark and its URL. This might be expressed as follows:
This means that there is a triple between the bookmark 'A Bookmark to mozilla.org' and
'www.mozilla.org' by the URL property. The first part of this expression is called the subject, the
second part is called the predicate and the last part is called the object. As a triple element, it
would be expressed as follows:
This has been simplified a bit from the real thing. The predicate would normally include the
namespace, and the subject would be the bookmark's resource id, not the bookmark's title as
used here. In fact, the bookmark's title would be another triple in the datasource using the Name
predicate.
You can replace the subject and object on the triple element with variable references, in which
case values will be substituted for the variables. If no value is defined for a variable yet, the
template builder will look up the value in the datasource and assign it to the variable.
Let's say we wanted to add a weather prediction to the city datasource. The following conditions
might be used:
<conditions>
<content uri="?list"/>
<member container="?list" child="?city"/>
<triple subject="?city"
predicate="http://www.xulplanet.com/rdf/weather#prediction"
object="?pred"/>
</conditions>
The template builder will iterate over each city as before. When it gets to the triple, it will look for
an assertion in the RDF datasource for a city's weather prediction. The variable 'pred' will be
assigned the prediction. The builder will repeat this for each of the four cities. A match occurs
and the builder will generate content for each city that has a prediction. If the city has no
prediction resource, the condition does not match and no content will be generated for that city.
Note that you do not need to put 'rdf:' at the beginning of the predicate, as that part is assumed.
We could also replace the object with an in-line value. For example:
<conditions>
<content uri="?city"/>
<triple subject="?city"
predicate="http://www.xulplanet.com/rdf/weather#prediction"
object="Cloudy"/>
</conditions>
This example is similar but we specify that we want to match on 'Cloudy'. The result is that the
conditions will only match for cities where the prediction is 'Cloudy'.
We can add more triples to require more matches. For example, in the example above, we
might want to check for the temperature and the wind speed. To do this just add another triple
which checks for the additional resource. The condition will match if all of the triples provide
values.
The example below will check for an extra triple for the name of the city. It will be assigned to
the 'name' variable. The condition will only match if the city has both a name and a prediction.
<conditions>
<content uri="?list"/>
<member container="?list" child="?city"/>
<triple subject="?city"
predicate="http://www.xulplanet.com/rdf/weather#name"
object="?name"/>
<triple subject="?city"
predicate="http://www.xulplanet.com/rdf/weather#prediction"
object="?pred"/>
</conditions>
Generating Content
The content to generate for a rule is specified inside the action element. This should be the
content for the rows of the tree, menu items, or whatever content you want to generate. Within
the content, you can refer to variables that were defined in the conditions. Thus, in the weather
example above, you could use the variables 'name' or 'pred' to display the city or prediction. You
can use the 'list' or 'city' variables also, but they hold resources, not text, so they won't likely
have meaningful values to users.
In the simple rule syntax, you use the syntax uri='rdf:*' to indicate where content should be
generated. In the full syntax, you set the value of the uri attribute to a variable which you used in
the conditions. Usually, this will be the variable assigned in the child attribute of the member
element.
The following example shows a complete tree with conditions and an action. You can view the
RDF file separately. Source RDF
<template>
<rule>
<conditions>
<content uri="?list"/>
<member container="?list" child="?city"/>
<triple subject="?city"
predicate="http://www.xulplanet.com/rdf/weather#name"
object="?name"/>
<triple subject="?city"
predicate="http://www.xulplanet.com/rdf/weather#prediction"
object="?pred"/>
</conditions>
<action>
<treechildren>
<treeitem uri="?city">
<treerow>
<treecell label="?name"/>
<treecell label="?pred"/>
</treerow>
</treeitem>
</treechildren>
</action>
</rule>
</template>
</tree>
Two columns appear in this tree, one which displays the value of the name for each row and the
other which displays the value of the prediction.
If using the dont-build-content flag on a tree, replace the content element with a treeitem
element.
The final element you can add inside a rule is the bindings element. Inside it, you place one or
more binding elements. A binding in a rule has the same syntax as a triple and performs almost
the same function. For example, in the weather example above we could add the following
binding:
<bindings>
<binding subject="?city"
predicate="http://www.xulplanet.com/rdf/weather#temperature"
object="?temp"/>
</bindings>
This binding will grab the temperature resource of each city and assign it to the 'temp' variable.
This is similar to what a triple does. The difference is that a binding is not examined when
attempting to check the conditions. This means that the city must have a name and prediction to
be displayed, yet it does not matter if it has a temperature. However, if it does, it will be placed
in the 'temp' variable so it can be used in the action. If a city does not have a temperature, the
'temp' variable will be set to an empty string.
Persistent Data
This section describes how to save the state of a XUL window.
Remembering State
When building a large application, you will typically want to be able to save some of the state of
a window across sessions. For example, the window should remember which toolbars are
collapsed even after the user exits.
One possibility would be to write a script to collect information about what you would like to save
and then save it to a file. However, that would be a pain to do for every application.
Conveniently, XUL provides such a mechanism to save the state of a window.
The information is collected and stored in a RDF file (localstore.rdf) in the same directory as
other user preferences. It holds state information about each window. This method has the
advantage that it works with Mozilla user profiles, so that each user can have different settings.
XUL allows you to save the state of any element. You will typically want to save toolbar states,
window positions and whether certain panels are displayed or not, but you can save almost
anything.
To allow the saving of state, you simply add a persist attribute to the element which holds a
value you want to save. The persist attribute should be set to a space-separated list of attributes
of the element that you want to save. The element must also have an id attribute in order to
identify it.
For example, to save the position of a window, you would do the following:
<window
id="someWindow"
width="200"
height="300"
persist="width height"
.
.
.
The two attributes of the window element, the width and the height will be saved. You could add
additional attributes by adding a space and another attribute name to the persist attribute. You
can add the persist attribute to any element and store any attribute. You might use unusual
values if you adjust attributes using a script.
Let's add the persist attribute to some of the elements in the find files dialog. To save the
position of the window. To do this, we need to modify the window.
<window
id="findfile-window"
title="Find Files"
persist="screenX screenY width height"
orient="horizontal"
xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
This will cause the x and y position of the window and the width and height of the window to be
saved. We could extend it further to save the collapsed state of the splitter. It doesn't really
make sense to save the current tab state.
Style Sheets
A style sheet is a file which contains style information for elements. It was originally designed for
HTML elements but can be applied to XUL elements also, or to any XML for that matter. The
style sheet contains information such as the fonts, colors, borders, and size of elements.
Mozilla applies a default style sheet to each XUL window. The user's default theme is applied
using the style sheet reference we have seen in a number of examples:
This line indicates that we want to use the style provided by chrome://global/skin/. In Mozilla,
this will be translated as the file global.css, which contains default style information for XUL
elements. If you leave the line out, the elements will still work, however they will look fairly plain.
The style sheet applies theme-specific fonts, colors and borders to make the elements look
more suitable.
Creating themes is not described in detail in this tutorial, however, if you wish to create a theme,
you would create a style sheet, or set of style sheets for all the XUL elements, and include any
images necessary. CSS selectors would be used to match particular elements.
Mozilla browsers all provide a standard theme that uses the platform's native appearance to
determine how to draw the widgets. A special CSS property, -moz-appearance, may be used to
specify that a widget should take on the appearance necessary for the user's platform. In this
case, other CSS properties that define colors, fonts and sizes may be ignored because the
platform defines these characteristics. For other themes, the -moz-appearance property is not
usually used and the theme must define all of the appearance characteristics in the style
sheets.
It is recommended that you do not change the appearance of any widget unless you are
creating a theme, or in particular circumstances where such a change is needed to convey a
specific meaning. Some CSS properties do not affect the appearance of a widget, such as
those that change the size or margins. In XUL, flexible boxes should be used instead of using
specific sizes, or for listboxes or trees, the rows attribute may be used to specify the number of
rows to appear. However, there are often cases where a hardcoded size is useful, for instance
to indicate the default size of a window.
The margin and other spacing properties are often used to adjust the position of elements as
needed. Often, however, other elements or attributes may be used instead of this. For example,
if you need to place a small gap in-between several elements, use the separator element. The
separator includes a number of built-in classes, listed in the element reference, that may be
used to alter the separator's appearance. For instance, the following creates a separator with a
grooved line:
<separator class="groove">
Some of the other elements have other built-in classes that may be used. For instance, a label
using the class small-margin creates a label with a smaller margin around it than normal. A box
using the outset class will be displayed with an outset border. The element reference lists all of
the classes available. Some of these classes may be applied to any element.
One common class to use is the plain class, which causes an element to appear without any
borders, margins or other style information. Typically, this is used to create textboxes that blend
into the background, like a label. If the textbox is also readonly, then you can create text that
appears like a label, however, the text may be still be selected, and a context menu still applies.
Applications will often need a style sheet. In general, you will associate a single style sheet with
each XUL file, in addition to the global style sheet. You can place a style sheet anywhere you
wish. If your XUL file is stored remotely and accessed via an HTTP URL, you can store the style
sheet remotely as well. If you are creating a XUL package to be installed as part of the chrome
system, you have two choices. First, you could store the style sheet in the same directory as the
XUL file. This method has the disadvantage that your application will not be themeable. A better
method involves placing your files as part of a theme.
Let's assume that we are building the find files dialog for themeability, because the find files
dialog can be referred to with the URL chrome://findfile/content/findfile.xul so the style sheet file
will be stored in chrome://findfile/skin/findfile.css.
However, there will be times when the default look of elements will not give the look that is
desired. For this, we will need to add a style sheet of our own. So far, we have been applying
styles using the style attribute on elements. Although this works, it is not really the best thing to
do. It is much better to create a separate style sheet. The reason is so that different looks, or
skins, can be applied easily.
There may be certain cases where the style attribute is acceptable. An example would be when
a script changes the style, or where a difference in layout might change the meaning of the
element. However you should avoid this as much as possible.
For installed files, you'll have to create or modify a manifest file and install the skin.
Let's modify the find files dialog so that its style comes from a separate style file. First, the
modifed lines of findfile.xul:
<menulist id="searchtype">
<menupopup>
<menuitem label="Name"/>
<menuitem label="Size"/>
<menuitem label="Date Modified"/>
</menupopup>
</menulist>
<spacer class="springspace"/>
<menulist id="searchmode">
<menupopup>
<menuitem label="Is"/>
<menuitem label="Is Not"/>
</menupopup>
</menulist>
<spacer class="springspace"/>
<menulist id="find-text" flex="1"
editable="true"
datasources="file:///mozilla/recents.rdf"
ref="http://www.xulplanet.com/rdf/recent/all"/>
...
<spacer class="titlespace"/>
<hbox>
The new xml-stylesheet line is used to import the style sheet. It will contain the styles instead of
having them directly in the XUL file. You can include any number of style sheets in a similar
way. Here the style sheet is placed in the same directory as findfile.xul.
Some of the styles in the code above have been removed. One that wasn't was the display
property on the progressmeter. This will be changed by a script so it was left in, as it doesn't
really make sense to have the progress bar visible initially. You could still put this in a separate
style sheet if you really wanted to. A class was added to the spacers so that they can be
referred to.
A style sheet also needs to be created. Create a file findfile.css in the same directory as the
XUL file. (It would normally be put into a separate skin). In this file, we'll add the style
declarations, as shown below:
#find-text {
min-width: 15em;
}
#progmeter {
margin: 4px;
}
.springspace {
width: 10px;
}
.titlespace {
height: 10px;
}
Notice how these styles are equivalent to the styles we had before. However, it is much easier
for someone to change the look of the find files dialog now because they could add or modify
the style declarations by either modifying the file or by changing the skin. If the user changes
the interface skin, the files in a directory other than default will be applied.
We've already seen how to import style sheets for use. An example is shown below:
This might be the first lines of a bookmarks window. It imports the bookmarks style sheet, which
is bookmarks.css. Mozilla's skin system is smart enough to figure out which style sheet to use,
because the specific filename was not indicated here. We have done a similar thing with the
global style sheet file (chrome://global/skin).
A style sheet may import styles from another style sheet using the import directive. Normally,
you will only import one style sheet from each XUL file. The global style sheet can be imported
from within the style sheet associated with the XUL file. This can be done with the code below,
allowing you to remove the import from the XUL file:
The second syntax is preferred because it reduces the number of dependencies within the XUL
file itself.
Remove the global style sheet import from findfile.xul and add the import to findfile.css.
All elements can be styled using CSS. You can use selectors to select the element that you
wish to have styled. (The selector is the part before the curly brace in a style rule). The following
table summarizes some of the selectors available:
toolbar > button Matches all buttons that are directly inside toolbar elements.
toolbar > Matches all button elements with a class of bigbuttons that are
button.bigbuttons directly inside toolbar elements.
Matches all button elements with a class of bigbuttons but only while
button.bugbuttons:hover
the mouse is over them.
Matches all box elements that have an orient attribute that is set to
box[orient="horizontal"]
horizontal.
You can combine these rules in any way that you wish. It is always a good idea to be as precise
as possible when specifying what gets styled. It is more efficient and it also reduces the
likelihood that you'll style the wrong thing.
Styling a Tree
The following describes how to style a tree.
Styling the Tree
You can style the tree border and the column headers in the same way as other elements. Style
added to the tree element will apply to the entire tree. Adding a style to the treecol element does
not cause the style to be applied to the column but only to the header.
The body of the tree must be styled in a somewhat different way than other elements. This is
because the tree body is stored in a different way to other elements. The outer treechildren is
the only real element in the tree body. The inner elements are just placeholders.
Instead, you must use the properties attribute on the rows or cells to set one or more named
properties. This can be used with trees with static content, RDF built content or with those with
a custom view. Let's say we want to give a particular row have a blue background color. This
would be used to implement Mozilla Mail's labels feature. We'll use a property called
'makeItBlue'. You can use whatever name you want. You can set multiple properties by
separating them with spaces.
<treerow properties="makeItBlue">
The style sheet can take this property and use it to change the appearance of the row for
unread messages or labels. You can think of the properties as functioning much like style
classes, although they require a somewhat more complex syntax to use in a style sheet. This is
because you can specify the style for a number of parts of the cell individually. You can style not
only the cell and its text, but the twisty and indentation. The following is the syntax that needs to
be used:
treechildren::-moz-tree-row(makeItBlue)
{
background-color: blue;
}
This extra pseudostyle is used to style the background color of rows that have the 'makeItBlue'
property. This special syntax is needed because the cells themselves are not separate
elements. All of the content inside the tree's body is rendered by the treechildren element. (Note
the treechildren is being styled in the rule above.) The pseudostyle sets style rules for particular
parts of what it displays. This style rule means, inside a treechildren element, set the
background color to blue for all tree rows that have the 'makeItBlue' property.
The text '::-moz-tree-row' specifies what content area is desired, which in this case is a row. You
can also use the following values:
You can check for multiple properties by separating them with commas. The example below
sets the background color to grey for rows that have the 'readonly' and 'unread' properties. For
properties that are 'readonly', it adds a red border around the row. Note that the first rule will
apply to any row that is 'readonly' regardless of whether other properties such as 'unread' are
set.
treechildren::-moz-tree-row(readonly)
{
border: 1px solid red;
}
treechildren::-moz-tree-row(readonly, unread)
{
background-color: rgb(80%, 80%, 80%);
}
The properties list for tree elements contain a small number of default properties, which you can
also use in a style sheet. You can use these extra properties to set the appearance of
containers or selected rows. The following properties are automatically set as needed:
• focus: this property is set if the tree currently has the focus.
• selected: this property is set for rows or cells that are currently selected.
• current: this property is set if the cursor is at the row. Only one row will have this
property set at a time.
• container: this property is set for rows or cells that have child rows.
• leaf: this property is set for rows or cells that do not have child rows.
• open: this property is set for rows or cells which are expanded.
• closed: this property is set for rows or cells which are collapsed.
• primary: this property is set for cells in the primary column.
• sorted: this property is set for cells in the current sorted column.
• even: this property is set for even numbered rows.
• odd: this property is set for odd numbered rows. This property, along with the even
property allow you to set, for example, alternating colors for each row.
• dragSession: this property is set if something is currently being dragged.
• dropOn: if a drag is occuring over the tree, this property is set for the row currently being
dragged over, as long as the mouse pointer is hovering over the row.
• dropBefore: this property is set if the mouse pointer is hovering before the row currently
being dragged over.
• dropAfter: this property is set if the mouse pointer is hovering after the row currently
being dragged over.
• progressNormal: this property is set for progress meter cells.
• progressUndetermined: this property is set for undeterminate progress meter cells.
• progressNone: this property is set for non-progress meter cells.
The properties are set for rows or cells in rows with the necessary state. For columns and cells,
one additional property, the id of the column or column the cell is in will be set.
For RDF-built trees, you can use the same syntax. However, you will often set the properties
based on values in the datasource.
For trees with a custom view script, you can set properties by supplying the functions
'getRowProperties', 'getColumnProperties' and 'getCellProperties' in the view. These return
information about an individual row, column and cell. Arguments to these functions indicate
which row and/or column. The last argument to each of these functions is a properties list which
the view is expected to fill with a list of properties. The function 'getColumnProperties' also
supplies the corresponding treecol element for the column.
getRowProperties : function(row,prop){}
getColumnProperties : function(column,columnElement,prop){}
getCellProperties : function(row,column,prop){}
Let's look at an example of changing a specific cell. Let's make every fourth row have blue text,
using the example from a previous section. We'll need to add code to the getCellProperties
function, to add a property 'makeItBlue' for cells in every fourth row. (We don't use
getRowProperties as the text color will not be inherited into each cell.)
The properties object that is passed as the last argument to the getCellProperties is an XPCOM
object that implements nsISupportsArray. It is really just an XPCOM version of an array. It
contains a function AppendElement which can be used to add an element to the array. We can
use the interface nsIAtomService to constuct string atoms for the properties.
getCellProperties: function(row,col,props){
if ((row %4) == 0){
var aserv=Components.classes["@mozilla.org/atom-service;1"].
getService(Components.interfaces.nsIAtomService);
props.AppendElement(aserv.getAtom("makeItBlue"));
}
}
This function would be defined as part of a view object. It first checks to see which row is being
requested and sets a property for cells in every fourth row. The properties list requires an array
of atom objects, which can be thought of as constant strings. We create them using the XPCOM
interface nsIAtomService and add them to the array using the AppendElement function. Here,
we create an atom 'makeItBlue'. You can call AppendElement again to add additional
properties.
Skin Basics
A skin is a set of style sheets, images and behaviors that are applied to a XUL file. By applying
a different skin, you can change the look of a window without changing its functionality. Mozilla
provides two skins by default, Classic and Modern, and you may download others. The XUL for
both is the same, however the style sheets and images used are different.
For a simple personalized look to a Mozilla window, you can easily change the style sheets
associated with it. Larger changes can be done by creating an entirely new skin. Mozilla's
preferences window has a panel for changing the default skin.
A skin is described using CSS, allowing you to define the colors, borders and images used to
draw elements. The files classic.jar and modern.jar contain the skin definitions. The global
directory within these archives contain the main style definitions for how to display the various
XUL elements. By changing these files, you can change the look of the XUL applications.
If you place a file called 'userChrome.css' in a directory called 'chrome' inside your user profile
directory, you can override settings without changing the archives themselves. This directory
should be created when you create a profile and some examples placed there. The file
'userContent.css' customizes Web pages, whereas 'userChrome.css' customizes chrome files.
For example, by adding the following to the end of the file, you can change all menubar
elements to have a red background.
menubar {
background-color: red;
}
If you open any Mozilla window after making this change, the menu bars will be red. Because
this change was made to the user style sheet, it affects all windows. This means that the
browser menu bar, the bookmarks menu bar and even the find files menu bar will be red.
To have the change affect only one window, change the style sheet associated with that XUL
file. For example, to add a red border around the menu commands in the address box window,
add the following to addressbook.css in the modern.jar or classic.jar archive.
menuitem {
border: 1px solid red;
}
If you look in one of the skin archives, you will notice that each contain a number of style sheets
and a number of images. The style sheets refer to the images. You should avoid putting
references to images directly in XUL files if you want your content to be skinnable. This is
because a particular skin's design might not use images and it may need some more complex
design. By referring to the images with CSS, they are easy to remove. It also removes the
reliance on specific image filenames.
You can assign images to a button, checkbox and other elements by using the list-style-image
property as in the following:
checkbox {
list-style-image: url("chrome://findfile/skin/images/check-off.jpg");
}
checkbox[checked="true"] {
list-style-image: url("chrome://findfile/skin/images/check-on.jpg");
}
This code changes the image associated with a checkbox. The first style sets the image for a
normal checkbox and the second style sets the image for a checked checkbox. The modifier
'checked=true' makes the style only apply to elements which have their checked attributes set to
true.
Creating a Skin
This section describes how to create a simple skin. For simplicity, we'll only apply it to the find
files dialog.
A Simple Skin
The image below shows the current find files dialog. Let's create a skin that we can apply to it.
Normally, a skin would apply to the enitre application, but we'll focus on just the find files dialog
to make it easier. For this reason, we'll modify only the file findfile.css rather than the global.css
file. This section assumes that you are starting with the Classic skin. You may wish to make a
copy of the files used by the find files dialog before editing.
You need to create a file 'findfile.css' in a custom skin. Or, you can temporarily place it in the
content directory and refer to it using a stylesheet directive. You can modify the existing
findfile.css directly to see what it looks like, or you can create a custom skin and link to that. To
create a skin, do the following:
1. Create a directory somewhere where you want to place the skin files.
2. Copy a manifest file (contents.rdf) from the Classic or Modern skin into this new directory.
3. Modify the references in the manifest file to a custom name for your skin. For example,
change references of 'classic/1.0' to 'blueswayedshoes/1.0'.
4. Add a line to the file 'chrome/installed-chrome.txt of the following form:
skin,install,url,file:///stuff/blueswayedshoes/
where the last part points to the directory you created. Make sure to add a slash at the
end.
Copy the original findfile.css into the new directory. We'll use this as a basis for the new skin.
We can then refer to it using the URL 'chrome://findfile/skin/findfile.css'. First, let's decide what
kind of changes we want to make. We'll make some simple color changes, modify the button
styles, and modify the spacing a bit. Let's start with the menus, toolbars and the overall tab
pabel.
The following style rules added to findfile.css will cause the changes shown in the
accompanying image.
menubar,menupopup,toolbar,tabpanels {
background-color: lightblue;
border-top: 1px solid white;
border-bottom: 1px solid #666666;
border-left: 1px solid white;
border-right: 1px solid #666666;
}
caption {
background-color: lightblue;
}
The inner box of the window (which actually surrounds all of the window content) has been
changed to have a medium blue color. You can see this blue behind the tab strip and along the
bottom of the window. Four elements, the menubar, the menupopup, the toolbar and the
tabpanels appear in light blue. The border around these four elements has been changed to
give a heavier 3D appearance. You can see this if you look closely. The background color of the
caption has also been changed to match the background.
The first rule above (for 'window > box') specifies that the child box of the window has a different
color. This probably isn't the best way to do this. We should really change this to use a style
class. Let's do this. That way, we can modify the XUL without needing to keep the box as the
first child of the window.
.findfilesbox {
background-color: #0088CC;
}
XUL:
Next, let's modify the tabs. We will make the selected tab bold and change the rounding on the
tabs.
tab:first-child {
-moz-border-radius: 4px 0px 0px 0px;
}
tab:last-child {
-moz-border-radius: 0px 4px 0px 0px;
}
tab[selected="true"] {
color: #000066;
font-weight: bold;
text-decoration: underline;
}
Two rules change the normal tab appearance, the first sets the rounding on the first tab and the
second sets the rounding on the last tab. Used here is a special Mozilla style rule, -moz-border-
radius, that creates rounded border corners. The upper left border of the first tab and the upper
right border of the second tab are rounded by four pixels and the other corners have a round
corner of zero pixels, which is equivalent to no rounding. Increase the values here for more
rounding and decrease them for a more rectangular look.
The last rule only applies to tabs that have their selected attribute set to true. It makes the text
in the selected tab appear bold, underlined and dark blue. Note in the image
that this style has applied only to the first tab, because it is the selected one.
It is somewhat difficult to distinguish the buttons on the toolbar from the commands on the
menu. We could add some icons to the buttons to make them clearer. Mozilla Composer
provides some icons for open and save buttons, which we'll just use here to save time. We can
set the image for a button using the list-style-image CSS property.
#opensearch {
list-style-image: url("chrome://editor/skin/icons/btn1.gif");
-moz-image-region: rect(48px 16px 64px 0);
-moz-box-orient: vertical;
}
#savesearch {
list-style-image: url("chrome://editor/skin/icons/btn1.gif");
-moz-image-region: rect(80px 16px 96px 0);
-moz-box-orient: vertical;
}
Mozilla provides a custom style property -moz-image-region which can be used to make an
element use part of an image. You can think of it as a clip region for the image. You set the
property to a position and size within an image and the button will display only that section of
the image. This allows you to use the same image for multiple buttons and set a different region
for each one. When you have lots of buttons, with states for hover, active and disabled, this
saves space that would normally be occupied by multiple images. In the code above, we use
the same image for each button, but set a different image region each one. If you look at this
image (btn1.gif), you will notice that it contains a grid of smaller images, each one 16 by 16
pixels.
The -moz-box-orient property is used to orient the button vertically, so that the
image appears above the label. This property has the same meaning as the orient
attribute. This is convenient because the skin cannot change the XUL. Most of the
box attributes have corresponding CSS properties.
Next, we'll make a couple of changes to the buttons along the bottom, again reusing some icons
from Mozilla to save time. If creating your own skin, you will need to create new icons or copy
the icons to new files. If following the example in this section, just copy the files to your new skin
and change the URLs accordingly.
#find-button {
list-style-image: url("chrome://global/skin/checkbox/images/cbox-check.jpg");
font-weight: bold;
}
#cancel-button {
list-style-image: url("chrome://global/skin/icons/images/close-button.jpg");
}
button:hover {
color: #000066;
}
We add some images to the buttons and make the Find button have
bold text to indicate that it is the default button. The last rule applies to
buttons when the mouse is hovering over them. We set the text color to dark blue in this case.
Finally, some minor changes to the spacing around the items, by setting margins:
tabbox {
margin: 4px;
}
toolbarbutton {
margin-left: 3px;
margin-right: 3px;
}
After those changes, the find files dialog now looks like the following:
As you can see, some simple changes to the style rules has resulted in quite a different
appearance to the find files dialog. We could continue by changing the menus, the grippies on
the toolbar and the input and checkbox elements.
The skin created above is simple and only applies to the find files dialog. Some of the changes
made to the skin could be placed in the global style sheets (those in the global directory of the
skin) to be applied to all applications. For example, having different images for the check boxes
in the find files dialog as other windows looks a little odd. This change should really be moved
into the global style sheet.
Try moving the CSS styles from findfile.css into global.css and then look at some of the dialogs
in Mozilla. (The cookie viewer is a good example.) You will notice that it has adopted the rules
that we have added. Some of the rules conflict with those already in the global stylesheets. For
example, rules are already defined for buttons and tabs and so on and we defined additional
rules for them. When changing the global skin, you would need to merge the changes into the
existing rules.
For the best skinnability, it is best to declare appearance related style rules in the global
directory rather than in individual style files. This includes colors, fonts and general widget
appearances. If you change the color of something in a local skin file (such as findfile.css), the
dialog may look odd if the user changes their global skin. Don't expect the user to be using the
default one.
Localization
XUL and XML provide entities which are a convenient way of allowing localization.
Entities
Many applications are built such that translating the interface into a different language is as
simple as possible. Usually, a table of strings is created for each language. Instead of hard-
coding text directly into an application, each piece of text is only a reference into the string table.
XML provides entities which can be used for a similar purpose.
You should already be familiar with entities if you have written HTML. The codes < and >
are examples of entities which can be used to place less than and greater than signs into the
text. XML has a syntax which allows you to declare custom entities. You can use these so that
the entity is replaced with its value, which can be a string of text. Entities may be used
whenever text occurs, including the values of attributes. The example below demonstrates the
use of an entity in a button.
<button label="&findLabel;"/>
The text that will appear on the label will be the value that the entity &findLabel has. A file is
created which contains the entity declarations for each supported language. In English, the
&findLabel entity will probably be declared to have the text 'Find'.
DTD Files
Entities are declared in DTD (Document Type Declaration) files. These types of files are
normally used to declare the syntax and sematics of a particular XML file, but they also allow
you to declare entities. In the Mozilla chrome system, you will find DTD files located in the
locales subdirectory. You would normally have one DTD file (with an extension dtd) per XUL
file.
If you look in the chrome directory, you should see an archive for your language. (en-US.jar is
the default for English.) You might have locale files in multiple languages, for example, US
English (en-US) and French (fr). Inside these archives, you will find the files that hold the
localized text for each window. The structure of the archives is very similar to the directory
structure used for skins.
Inside the archives, you would place your DTD files in which you declare entities. Typically, you
will have one DTD file for each XUL file, usually with the same filename except with a .dtd
extension. So, for the find files dialog, we will need a file called findfile.dtd.
For non-installed chrome files, you can just put the DTD file in the same directory as the XUL
file.
Once you have created a DTD file for your XUL, you will need to add a line to the XUL file which
indicates that you want to use the DTD file. Otherwise, errors will occur as it won't be able to
find the entities. To do this, add a line of the following form somewhere near the top of the XUL
file:
This line specifies that the URL indicated is to be used as a DTD for the file. In this case, we
have declared that we want to use the findfile.dtd DTD file. This line is typically placed just
before the window element.
Declaring Entities
This example creates an entity with the name findLabel and the value Find. This means that
wherever the text &findLabel appears in the XUL file, it will be replaced with the text Find. In the
DTD file for a different language, the text for that language will be used instead. Note that entity
declarations do not have a trailing slash at the end of them.
<description value="&findLabel;"/>
is translated as:
<description value="Find"/>
You would declare an entity for each label or string of text that you use in your interface. You
should not have any directly displayed text in the XUL file at all.
In addition to using entities for text labels, you should use them for any value that could be
different in a different language. Access keys and keyboard shortcuts for example.
The example above uses two entities, one for the label on the Undo menu item and the second
for the access key.
Let's take a look at how we would put all of this together by modifying the find files dialog so that
it uses a DTD file for all of its text strings. The entire XUL file is shown below with the changes
shown in red.
<?xml version="1.0"?>
<window
id="findfile-window"
title="&findWindow.title;"
persist="screenX screenY width height"
orient="horizontal"
onload="initSearchList()"
xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
<script src="findfile.js"/>
<popupset>
<popup id="editpopup">
<menuitem label="Cut" accesskey="&cutCmd.accesskey;"/>
<menuitem label="Copy" accesskey="©Cmd.accesskey;"/>
<menuitem label="Paste" accesskey="&pasteCmd.accesskey;" disabled="true"/>
</popup>
</popupset>
<keyset>
<key id="cut_cmd" modifiers="accel" key="&cutCmd.commandkey;"/>
<key id="copy_cmd" modifiers="accel" key="©Cmd.commandkey;"/>
<key id="paste_cmd" modifiers="accel" key="&pasteCmd.commandkey;"/>
<key id="close_cmd" keycode="VK_ESCAPE" oncommand="window.close();"/>
</keyset>
<vbox flex="1">
<toolbox>
<menubar id="findfiles-menubar">
<menu id="file-menu" label="&fileMenu.label;"
accesskey="&fileMenu.accesskey;">
<menupopup id="file-popup">
<menuitem label="&openCmd.label;"
accesskey="&openCmd.accesskey;"/>
<menuitem label="&saveCmd.label;"
accesskey="&saveCmd.accesskey;"/>
<menuseparator/>
<menuitem label="&closeCmd.label;"
accesskey="&closeCmd.accesskey;" key="close_cmd"
oncommand="window.close();"/>
</menupopup>
</menu>
<menu id="edit-menu" label="&editMenu.label;"
accesskey="&editMenu.accesskey;">
<menupopup id="edit-popup">
<menuitem label="&cutCmd.label;"
accesskey="&cutCmd.accesskey;" key="cut_cmd"/>
<menuitem label="©Cmd.label;"
accesskey="©Cmd.accesskey;" key="copy_cmd"/>
<menuitem label="&pasteCmd.label;"
accesskey="&pasteCmd.accesskey;" key="paste_cmd" disabled="true"/>
</menupopup>
</menu>
</menubar>
<toolbar id="findfiles-toolbar">
<toolbarbutton id="opensearch" label="&openCmdToolbar.label;"/>
<toolbarbutton id="savesearch" label="&saveCmdToolbar.label;"/>
</toolbar>
</toolbox>
<tabbox>
<tabs>
<tab label="&searchTab;" selected="true"/>
<tab label="&optionsTab;"/>
</tabs>
<tabpanels>
<description>
&findDescription;
</description>
<spacer class="titlespace"/>
<groupbox orient="horizontal">
<caption label="&findCriteria;"/>
<menulist id="searchtype">
<menupopup>
<menuitem label="&type.name;"/>
<menuitem label="&type.size;"/>
<menuitem label="&type.date;"/>
</menupopup>
</menulist>
<spacer class="springspace"/>
<menulist id="searchmode">
<menupopup>
<menuitem label="&mode.is;"/>
<menuitem label="&mode.isnot;"/>
</menupopup>
</menulist>
<spacer class="springspace"/>
</groupbox>
</tabpanel>
</tabpanels>
</tabbox>
<treechildren>
<treeitem>
<treerow>
<treecell label="mozilla"/>
<treecell label="/usr/local"/>
<treecell label="&bytes.before;2520&bytes.after;"/>
</treerow>
</treeitem>
</treechildren>
</tree>
<spacer class="titlespace"/>
<hbox>
<progressmeter id="progmeter" value="50%" style="display: none;"/>
<spacer flex="1"/>
<button id="find-button" label="&button.find;"
oncommand="doFind()"/>
<button id="cancel-button" label="&button.cancel;"
oncommand="window.close();"/>
</hbox>
</vbox>
</window>
Each text string has been replaced by an entity reference. A DTD file has been included near
the beginning of the XUL file. Each entity that was added should be declared in the DTD file.
The window will not be displayed if an entity is found in the XUL file that hasn't been declared.
Note that the name of the entity is not important. In the example above, words in entities have
been separated with periods. You don't have to do this. The entity names here follow similar
conventions as the rest of the Mozilla code.
You might notice that the text '2520 bytes' has been replaced by two entities. This is because
the phrase structure may be different in another locale. For example, the number might need to
appear before the equivalent of 'bytes' instead of after. Of course, this might need to be more
complicated in order to display KB or MB as needed.
The access keys and keyboard shortcuts have also been translated into entities because they
will likely be different in a different locale.
Now, to change a language all you need to do is create another DTD file. By using the chrome
system to add the DTD file to a different locale, the same XUL file can be used in any language.
Property Files
In a script, entities cannot be used. Property files are used instead.
Properties
DTD files are suitable when you have text in a XUL file. However, a script does not get parsed
for entities. In addition, you may wish to display a message which is generated from a script, if,
for example, you do not know the exact text to be displayed. For this purpose, property files can
be used.
A property file contains a set of strings . You will find property files alongside the DTD files with
a .properties extension. Properties in the file are declared with the syntax name=value. An
example is shown below:
Here, the property file contains two properties. These would be read by a script and displayed to
the user. You could write the code to read properties yourself, however XUL provides the
stringbundle element which does this for you. The element has a number of functions which can
be used to get strings from the property file and get other locale information. This element reads
in the contents of a property file and builds a list of properties for you. You can then look up a
particular property by name.
Including this element will read the properties from the file 'strings.properties' in the same
directory as the XUL file. Use a chrome URL to read a file from the locale.
This stringbundle element has a number of properties. The first is getString which can be used
in a script to read a string from the bundle.
var strbundle=document.getElementById("strings");
var nofilesfound=strbundle.getString("notFoundAlert");
alert(nofilesfound);
This example first gets a reference to the bundle using its id. Then, it looks up the string
'notFoundAlert' in the property file. The function getString returns the value of the string or null if
the string does not exist. Finally, the string is displayed in an alert box.
11. Bindings
Introduction to XBL
XUL has a sister language, XBL (eXtensible Bindings Language). This language is used for
declaring the behavior of XUL widgets.
Bindings
You can use XUL to define the layout of a user interface for an application. You can customize
the look of elements by applying styles to them. You can also create new skins by changing the
styles. The basic appearance of all elements, such as scroll bars and check boxes may be
modified by adjusting the style or by setting attributes on the element. However, XUL provides
no means in which you can change how an element works. For example, you might want to
change how the pieces of a scroll bar function. For this, you need XBL.
An XBL file contains a set of bindings. Each binding describes the behavior of a XUL widget.
For example, a binding might be attached to a scroll bar. The behavior describes the properties
and methods of the scroll bar in addition to describing the XUL elements that make up a scroll
bar.
Like XUL, XBL is an XML language, so it has similar syntax rules. The following example shows
the basic skeleton of an XBL file:
<?xml version="1.0"?>
<bindings xmlns="http://www.mozilla.org/xbl">
<binding id="binding1">
<!-- content, property, method and event descriptions go here -->
</binding>
<binding id="binding2">
<!-- content, property, method and event descriptions go here -->
</binding>
</bindings>
The bindings element is the root element of an XBL file and contains one or more binding
elements. Each binding element declares a single binding. The id attribute can be used to
identify the binding, as in the example above. The template has two bindings, one called
binding1 and the other called binding2. One might be attached to a scroll bar and the other to
a menu. A binding can be attached to any XUL element. If you use CSS classes, you can use
as many different bindings as you need. Note the namespace on the bindings element in the
template above. This declares that we are using XBL syntax.
You assign a binding to an element by setting the CSS property -moz-binding to the URL of the
bindings file. For example:
scrollbar {
-moz-binding: url('chrome://findfile/content/findfile.xml#binding1');
}
The URL points to the binding with the id 'binding1' in the file
'chrome://findfile/content/findfile.xml'. The '#binding1' syntax is used to point to a specific
binding, much like how you would point to an anchor in an HTML file. You will usually put all of
your bindings in a single file. The result in this example, is that all scrollbar elements will have
their behavior described by the binding 'binding1'. If you don't use an anchor in the -moz-binding
URL, the first binding in the XBL file is used.
1. Content: child elements that are added to the element that the binding is bound to.
2. Properties: properties added to the element. They can be accessed through a script.
3. Methods: methods added to the element. They can be called from a script.
4. Events: events, such as mouse clicks and keypresses that the element will respond to.
The binding can add scripts to provide default handling. In addition new events can be
defined.
5. Style: custom style properties that the XBL defined element has.
Binding Example
The box is generic enough that you can use it to create custom widgets (although you can use
any element, even one you make up yourself). By assigning a class to a box tag, you can
associate a binding to only those boxes that belong to that class. The following example
demonstrates this.
XUL (example.xul):
<?xml version="1.0"?>
<?xml-stylesheet href="chrome://global/skin/" type="text/css"?>
<?xml-stylesheet href="chrome://example/skin/example.css" type="text/css"?>
<window
xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
<box class="okcancelbuttons"/>
</window>
CSS (example.css):
box.okcancelbuttons {
-moz-binding: url('chrome://example/skin/example.xml#okcancel');
}
XBL (example.xml):
<?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="okcancel">
<content>
<xul:button label="OK"/>
<xul:button label="Cancel"/>
</content>
</binding>
</bindings>
This example creates a window with a single box. The box has been declared to have a class of
okcancelbuttons. The style sheet associated with the file says that boxes with the class
okcancelbuttons have a specialized binding, defined in the XBL file. You may use other
elements besides the box, even your own custom tags.
We'll look more at the details of the XBL part in the next section. However, to summarize, it
causes two buttons to be added automatically inside the box, one an OK button and the other a
Cancel button.
Anonymous Content
In this section we'll look at creating content with XBL.
XBL Content
XBL can be used to automatically add a set of elements inside another element. The XUL file
only needs to specify the outer element while the inner elements are described in the XBL. This
is useful for creating a single widget that is made up of a set of other widgets, but can be
referred to as only a single widget. Mechanisms are provided for adding attributes to the inner
elements that were specified on the outer element.
The example below shows how a scrollbar might be declared (It has been simplified a bit from
the real thing):
<bindings xmlns="http://www.mozilla.org/xbl"
xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
<binding id="scrollbarBinding">
<content>
<xul:scrollbarbutton type="decrement"/>
<xul:slider flex="1">
<xul:thumb/>
<xul:/slider>
<xul:scrollbarbutton type="increment"/>
</content>
</binding>
</bindings>
This file contains a single binding, declared with the binding element. The id attribute should be
set to the identifier of the binding. This way it can be referred to through the CSS -moz-binding
property.
The content tag is used to declare anonymous content that will be added to the scroll bar. All of
the elements inside the content tag will be added inside the element that the binding is bound
to. Presumably this binding would be bound to a scroll bar, altough it doesn't have to be. Any
element that has its CSS -moz-binding property set to the URI of the binding will use it.
The result of using the above binding is that the line of XUL below will be expanded as follows,
assuming that the scrollbar is bound to the XBL above:
<scrollbar>
expands to:
<scrollbar>
<xul:scrollbarbutton type="decrement"/>
<xul:slider flex="1"/>
<xul:thumb/>
</xul:slider>
<xul:scrollbarbutton type="increment"/>
</scrollbar>
The elements within the content tag are added to the scroll bar anonymously. Although
anonymous content is displayed on screen, you cannot get to it through a script in the normal
way. To the XUL, it's as if there was only one single element, even though it is really made up of
a number of elements.
If you look at a scroll bar in a Mozilla window, you will see that it is made up of an arrow button,
a slider, a thumb inside it and a second arrow button at the end, which are the elements that
appear in the XBL above. These elements would in turn be bound to other bindings that use the
base XUL elements. Notice that the content elements need the XUL namespace (they appear
preceded with xul:), because they are XUL elements and aren't valid in XBL. This namespace
was declared on the bindings tag. If you don't use the namespace on XUL elements, Mozilla will
assume that the elements are XBL, not understand them, and your elements won't work
correctly.
<binding id="fileentry">
<content>
<textbox/>
<button label="Browse..."/>
</content>
</binding>
Attaching this binding to an element will cause it to contain a field for entering text, followed by a
Browse button. This inner content is created anonymously and cannot be seen using the DOM.
<scrollbar/>
<scrollbar>
<button label="Overridden"/>
</scrollbar>
The first scroll bar, because it has no content of its own, will have its content generated from a
binding definition declared in an XBL file. The second scroll bar has its own content so it will use
that instead of the XBL content, resulting in something that isn't much of a scroll bar at all. Note
that the built-in elements such as scroll bars, get their XBL from the files in the bindings
directory in the toolkit package.
This only applies to the elements defined within the content tag. Properties, methods and other
aspects of XBL are still available whether the content is from XBL or whether the XUL provides
its own content.
There may be times when you want both the XBL content and the content provided by the XUL
file to be displayed. You can do this by using the children element. The children added in the
XUL are added in place of the children element. This is handy when creating custom menu
widgets. For example, a simplified version of an editable menulist element, might be created as
follows:
XUL:
<menu class="dropbox">
<menupopup>
<menuitem label="1000"/>
<menuitem label="2000"/>
</menupopup>
</menu>
CSS:
menu.dropbox {
-moz-binding: url('chrome://example/skin/example.xml#dropbox');
}
XBL:
<binding id="dropbox">
<content>
<children/>
<xul:textbox flex="1"/>
<xul:button src="chrome://global/skin/images/dropbox.jpg"/>
</content>
</binding>
This example creates an input field with a button beside it. The menupopup will be added to the
content in the location specified by the children element. Note that to DOM functions, the
content will appear as it was in the XUL file, so the menupopup will be a child of the menu. The
XBL content is hidden away so the XUL developer doesn't need to even know it is there.
In some cases, you may wish to only include specific types of content and not others. Or, you
may wish to place different types of content in different places. The includes attribute can be
used to allow only certain elements to appear in the content. Its value should be set to a single
tag name, or to a list of tags separated by vertical bars ( The | symbol ).
<children includes="button">
This line will add all buttons that are children of the bound element in place of the children tag.
Other elements will not match this tag. You can place multiple children elements in a binding to
place different types of content in different places. If an element in the XUL does not match any
of the children elements, that element (and any others that don't match) will be used instead of
the bound content.
Here is another example. Let's say that we wanted to create a widget that displayed an image
with a zoom in and zoom out button on each side of it. This would be created with a box to hold
the image and two buttons. The image element has to placed outside the XBL as it will differ
with each use.
XUL:
<box class="zoombox">
<image src="images/happy.jpg"/>
<image src="images/angry.jpg"/>
</box>
XBL:
<binding id="zoombox">
<content>
<xul:box flex="1">
<xul:button label="Zoom In"/>
<xul:box flex="1" style="border: 1px solid black">
<children includes="image"/>
</xul:box>
<xul:button label="Zoom Out"/>
</xul:box>
</content>
</binding>
The explicit children in the XUL file will be placed at the location of the children tag. There are
two images, so both will be added next to each other. This results in a display that is equivalent
to the following:
<binding id="zoombox">
<content>
<xul:box flex="1">
<xul:button label="Zoom In"/>
<xul:box flex="1" style="border: 1px solid black">
<image src="images/happy.jpg"/>
<image src="images/angry.jpg"/>
</xul:box>
<xul:button label="Zoom Out"/>
</xul:box>
</content>
</binding>
From the DOM's perspective, the child elements are still in their original location. That is, the
outer XUL box has two children, which are the two images. The inner box with the border has
one child, the children tag. This is an important distinction when using the DOM with XBL. This
also applies to CSS selector rules.
You can also use multiple children elements and have certain elements be placed in one
location and other elements placed in another. By adding a includes attribute and setting it to a
vertical bar-separated list of tags, you can make only elements in that list be placed at that
location. For example, the following XBL will cause text labels and buttons to appear in a
different location than other elements:
<binding id="navbox">
<content>
<xul:vbox>
<xul:label value="Labels and Buttons"/>
<children includes="label|button"/>
</xul:vbox>
<xul:vbox>
<xul:label value="Other Elements"/>
<children/>
</xul:vbox>
</content>
</binding>
The first children element only grabs the label and button elements, as indicated by its includes
attribute. The second children element, because it has no includes attribute, grabs all of the
remaining elements.
Inherited Attributes
XBL allows us to build composite widgets while hiding their actual implementation. However,
with the features mentioned so far, the anonymous content is always created in the same way.
It would be useful to add attributes to the bound elements that modify the inner elements. For
example:
XUL:
<searchbox/>
XBL:
<binding id="searchBinding">
<content>
<xul:textbox/>
<xul:button label="Search"/>
</content>
</binding>
In the example, the label attribute has been placed directly on the button element. The problem
with this is that the label would be the same every time the binding was used. In this case, it
would be preferable if the attribute could be specified on the searchbox instead. XBL provides
an inherits attribute which can be used to inherit attributes from the bound element. It should be
placed on the element that should inherit an attribute from the outer element, in this case the
button. Its value should be set to a comma-separated list of attribute names that are to be
inherited.
<xul:textbox xbl:inherits="flex"/>
<xul:button xbl:inherits="label"/>
When the content is generated, the textbox grabs the flex attribute from the searchbox and the
button grabs the label attribute from the searchbox. This allows both the flexibility of the textbox
and the label of the button to be different for each use of the binding. In addition, changing the
value of the attributes on the searchbox with a script will update the textbox and button also.
You can add the inherits attribute to as many elements as you wish, to inherit any number of
attributes.
Note how the inherits attribute has been placed in the XBL namespace, by prefixing it with 'xbl:'.
The namespace should be declared somewhere earlier, usually on the bindings element. The
next example demonstrates this.
<bindings xmlns:xbl="http://www.mozilla.org/xbl"
xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
<xbl:binding id="buttonBinding">
<xbl:content>
<xul:button label="OK" xbl:inherits="label"/>
</xbl:content>
</xbl:binding>
In this example, the button inherits the label attribute, but this attribute is also given a value
directly in the XBL. This technique is used to set the default value if the attribute is not present.
This button will inherit its label attribute from the outer element. However, if no label is present,
it will be given a default value of OK.
There may be times where two generated elements need to inherit from an attribute that has the
same name. For example, to create a labeled textbox (a textbox with a text description beside
it) out of a label and a textbox element, the label will need to inherit its text from the value
attribute and the textbox will also need to inherit its default value from the value attribute as well.
To solve this, we will need to use a different attribute and map it to the same one. The following
demonstrates this:
XUL:
CSS:
box.labeledtextbox {
-moz-binding: url('chrome://example/skin/example.xml#labeledtextbox');
}
XBL:
<binding id="labeledtextbox">
<content>
<xul:label xbl:inherits="value=title"/>
<xul:textbox xbl:inherits="value"/>
</content>
</binding>
The textbox inherits the value attribute directly. To set the value attribute on the label, we need
to use a different attribute name and map it to the value. The inherits attribute on the label grabs
the title attribute from the labeledtextbox and maps it to the value attribute of the label element.
The syntax <inner attribute>=<outer attribute> is used to map one attribute to another. Here is
another example:
XUL:
CSS:
box.okcancel {
-moz-binding: url('chrome://example/skin/example.xml#okcancel');
}
XBL:
<binding id="okcancel">
<content>
<xul:button xbl:inherits="label=oktitle,image"/>
<xul:button xbl:inherits="label=canceltitle"/>
</content>
</binding>
The value of the oktitle attribute is mapped to the label attribute of the first button. The
canceltitle attribute is mapped to the label attribute of the second button. The first button also
inherits the image attribute. The result is as follows:
Note that the attributes are duplicated on the inner (anonymous) content. Changing the
attributes on the box with the okcancel class will automatically change the values on the
buttons. You may also have noticed that we just made up our own attribute names. This is valid
in XUL.
Adding Properties
Next, we'll find out how to add custom properties to XBL-defined elements.
JavaScript and the DOM provide access to get and set the properties of elements. With XBL,
you can define your own properties for the elements you create. You can also add methods that
can be called. That way, all you need is to get a reference to the element (using
GetElementById or a similar function) and then get or set the additional properties and call the
methods on it.
There are three types of items you can add. Fields are used to hold a simple value. Properties
can also be used to hold a value but may have code execute when an attempt is made to
retrieve or modify the value. Methods are functions which may be executed.
All three are defined within an implementation element, which should be a child of the binding
element. Within the implementation, you define individual field, property, and method elements,
one for each one that you want. The general syntax is as follows:
<binding id="element-name">
<content>
-- content goes here --
</content>
<implementation>
<field name="field-name-1"/>
<field name="field-name-2"/>
<field name="field-name-3"/>
<property name="property-name-1"/>
<property name="property-name-2"/>
<property name="property-name-3"/>
.
.
.
<method name="method-name-1/>
-- method content goes here --
</method>
.
.
.
</implementation>
</binding>
Fields
Each field is defined using the field element. Often, fields would correspond to an attribute
placed on the element such as label or disabled, but they do not have to.
The name attribute on the field element is used to indicate the name of the field. You can use
the name from a script to get and set the value. The example below creates a button which
generates and stores a random number. You can retrieve this same number multiple times by
getting the number property from the button. Most of the work here is done in the oncommand
handlers. Later, we'll find out how to move this to XBL.
XUL:
<button label="Generate"
oncommand="document.getElementById('random-box').number=Math.random();"/>
<button label="Show"
oncommand="alert(document.getElementById('random-box').number)"/>
XBL:
<binding id="randomizer">
<implementation>
<field name="number"/>
</implementation>
</binding>
A number field has been defined in the binding, which stores the random number. The two extra
buttons set and get the value of this field. The syntax is very similar to getting and setting the
properties of HTML elements. In this example, no content has been placed inside either the
XUL box or its definition in XBL, which is perfectly valid.
This example isn't quite correct because the field is not assigned a default value. To do this, add
the default value as the content of the field tag. For example:
<field name="number">
25
</field>
This will assign the value 25 as the default value of the number field. Actually, you can instead
place a script inside the field tag that evaluates to the default value. That might be necessary if
the value needs to be computed. For example, the following field is given a default value equal
to the current time:
<field name="currentTime">
new Date().getTime();
</field>
Properties
Sometimes you will want to validate the data that is assigned to a property. Or, you may want
the value to be calculated dynamically as it's asked for. For example, if you want a property that
holds the current time, you would want to have its value generated as needed. In these cases,
you need to use a property tag instead of a field tag. Its syntax is similar but has additional
features.
You can use the onget and onset attributes to have code execute when the property is retrieved
or modified. Add each to the property element and set its value to a script which either gets or
sets the value of the property.
For example, you could assign a script to the value of onget to calculate the current time.
Whenever a script attempts to access the value of the property, the onget script will be called to
retrieve the value. The script should return the value that should be treated as the value of that
property.
The onset handler is similar but is called whenever a script attempts to assign a new value to
the property. This script should store the value somewhere, or validate the value. For example,
some properties might only be able to store numbers. Attempting to assign alphabetic text to
such a property should fail.
<property name="size"
onget="return 77;"
onset="alert('Changed to:'+val); return val;"/>
This property will always return 77 when retrieved. When set, an alert will be displayed which
displays the value to assign to the property. The special variable val holds the value that the
property should be assigned to. Use this to validate it or store it. The onset code should also
return the new value.
There are two elements, one called 'banana' and the other 'orange'. They each have a custom
property called 'size'. When the following line of script is executed:
banana.size = orange.size;
1. The onget script is called for the size property of the orange. The script calculates the
value and returns it.
2. The onset handler of the size property of the banana is called. This script uses the value
passed in the val variable and assigns it to the size property of the banana in some
manner.
Note that unlike a field, a property does not hold a value. Attempting to set a property that does
not have an onset handler will generate an error. You will often use a separate field to hold the
actual value of the property. It is also common to have the properties match an attribute on the
XBL-defined element. The following example maps a property to an attribute on an element.
<property name="size"
onget="return this.getAttribute('size');"
onset="return this.setAttribute('size',val);"
/>
Whenever a script attempts to get the value of the property, it is grabbed instead from the
attribute on the element with the same name. Whenever a script attempts to set the value of a
property, it is set as an attribute on the element. This is convenient because then you can
modify the property or the attribute and both will have the same value.
You can use an alternate syntax for the onget and onset attributes that is useful if the scripts are
longer. You can replace the onget attribute with a child element called getter. Similarly, you can
replace the onset attribute with a setter element. The example below shows this:
<property name="number">
<getter>
return this.getAttribute('number');
</getter>
<setter>
var v = parseInt(val,10);
if (!isNaN(v)) return this.setAttribute('number',''+v);
else return this.getAttribute('number');"
</setter>
</property>
The property in this example will only be able to hold integer values. If other characters are
entered, they are stripped off. If there are no digits, the value is not changed. This is done in the
code inside the setter element. The real value of the property is stored in the number attribute.
You can use either syntax for creating get and set handlers.
You can make a field or property read-only by adding a readonly attribute to the field tag or
property tag and setting it to true. Attempting to set the value of a read-only property will fail.
Adding Methods
Next, we'll find out how to add custom methods to XBL-defined elements.
Methods
In addition to adding script properties to the XBL-defined element, you can also add methods.
These methods can be called from a script. Methods are the functions of objects, such as
'window.open()'. You can define custom methods for your elements using the method element.
The general syntax of methods is as follows:
<implementation>
<method name="method-name">
<parameter name="parameter-name1"/>
<parameter name="parameter-name2"/>
.
.
.
<body>
-- method script goes here --
</body>
</method>
</implementation>
A method declaration goes inside the implementation element, like the fields and properties do.
The method element contains two type of child elements, parameter elements which describe
the parameters to the method and body which contains the script for the method.
The value of the name attribute becomes the name of the method. Similarly, the name attributes
on the parameter elements become the names of each parameter. Each parameter element is
used to declare one parameter for the method. For example, if the method had three
parameters, there would be three parameter elements. You do not need to have any though, in
which case the method would take no parameters.
The body element contains the script that is executed when the method is called. The names of
the parameters are defined as variables in the script as if they had been passed as parameters.
For example, the following JavaScript function would be written as an XBL method like so:
function getMaximum(num1,num2)
{
if (num1<=num2) return num2;
else return num1;
}
XBL:
<method name="getMaximum">
<parameter name="num1"/>
<parameter name="num2"/>
<body>
if (num1<=num2) return num2;
else return num1;
</body>
<method>
This function, getMaximum, returns the largest of the values, each passed as an parameter to
the method. Note that the less-than symbol has to be escaped because otherwise it would look
like the start of a tag. You can also use a CDATA section to escape the entire block of code.
You can call the method by using code such as 'element.getMaximum(5,10)' where element is a
reference to an element defined by the XBL containing the getMaximum method. (The bound
element.)
The parameter tag allows you to define parameters for a method. Because Mozilla uses
JavaScript as its scripting language, and JavaScript is a non-typed language, you do not need
to specify the types of the parameters. However, in the future, other languages may be used
with XBL.
There may be times when you want to modify some aspect of the elements defined in the
content element, either from a method body or elsewhere. These elements are created
anonymously and are not accessible from the regular DOM functions. They are hidden so that
developers do not need to know how the element is implemented to use it. However, there is a
special way of getting this anonymous content.
Elements with an XBL behavior attached to them have a special property which holds an array
of the anonymous child elements inside it. Each element of the array stores each direct child
element of the XBL-defined element. This special property cannot be accessed directly. Instead,
you must call the document's getAnonymousNodes method:
var value=document.getAnonymousNodes(element);
Here, 'element' should be set a to reference to the element that you want to get the anonymous
content of. The function returns an array of elements, which is the anonymous content. To get
elements below that, you can use the regular DOM functions because they aren't hidden. Note
that it is possible for an XBL-bound element to be placed inside another one, in which case you
will have to use the getAnonymousNodes function again.
<binding id="buttonrow">
<content>
<button label="Yes"/>
<button label="No"/>
<button label="Sort Of"/>
</content>
</binding>
To refer to each button, you can use the getAnonymousNodes function, passing it a reference
to the element the binding is bound to as the parameter. In the returned array, the first button is
stored in the first array element ('getAnonymousNodes(element)[0]'), the second button is
stored in the second array element and the third button is stored in the third array element. For
code inside a binding method, you can pass 'this' as the parameter to getAnonymousNodes.
The next example can be used to create text with a label. The method 'showTitle' can be used
to show or hide the label. It works by getting a reference to the title element using the
anonymous array and changing the visibility of it.
XUL:
XBL:
<binding id="labeledbutton">
<content>
<xul:label xbl:inherits="value=title"/>
<xul:label xbl:inherits="value"/>
</content>
<implementation>
<method name="showTitle">
<parameter name="state"/>
<body>
if (state) document.getAnonymousNodes(this)[0].
setAttribute("style","visibility: visible");
else document.getAnonymousNodes(this)[0].
setAttribute("style","visibility: collapse");
</body>
</method>
</implementation>
</binding>
Two buttons added to the XUL have oncommand handlers which are used to change the
visibility of the label. Each calls the 'showTitle' method. This method checks to see whether the
element is being hidden or shown from the 'state' parameter that is passed in. In either case, it
grabs the first element of the anonymous array. This refers to the first child in the content
element, which here is the first label widget. The visibility is changed by modifying the style on
the element.
To go the other way, and get the bound element from inside the anonymous content, use the
DOM 'parentNode' property. This gets the parent element of an element. For example, we could
move the Show and Hide buttons into the XBL file and do the following:
<binding id="labeledbutton">
<content>
<xul:label xbl:inherits="value=title"/>
<xul:label xbl:inherits="value"/>
<xul:button label="Show" oncommand="parentNode.showTitle(true);"/>
<xul:button label="Hide" oncommand="parentNode.showTitle(false);"/>
</content>
<implementation>
<method name="showTitle">
<parameter name="state"/>
<body>
if (state)
document.getAnonymousNodes(this)[0].setAttribute("style","visibility: visible");
else document.getAnonymousNodes(this)[0].setAttribute("style","visibility:
collapse");
</body>
</method>
</implementation>
</binding>
The oncommand handlers here first get a reference to their parent element. This is not the
content element but the XUL element that the XBL is bound to. (In this example, it is the box
with the labeledbutton class). Then, the 'showTitle' method is called, which functions as it did
before.
Custom properties and methods are added only to the outer XUL element the XBL is bound to.
None of the elements declared inside the content tag have these properties or methods. This is
why we have to get the parent first.
The children of an element placed in the XUL file can be retrieved in the normal way and don't
move even if you use the children tag. For example:
XUL:
XBL:
<binding id="labeledbutton">
<content>
<description value="A stack:"/>
<stack>
<children/>
</stack>
</content>
</binding>
If you use the DOM functions such as 'childNodes' to get the children of an element, you'll find
that the XUL box, the one with the id of outer, has 4 children. These correspond to its four
buttons, even through those buttons are drawn inside the stack. The stack has only one child,
the children element itself. The length of the anonymous array of the outer box is two, the first
element the description element and the second the stack element.
XBL supports two special methods created with separate tags, constructor and destructor. A
constructor is called whenever the binding is attached to an element. It is used to initialize the
content such as loading preferences or setting the default values of fields. The destructor is
called when a binding is removed from an element. This might be used to save information.
There are two points when a binding is attached to an element. The first occurs when a window
is displayed. All elements that have XBL-bound content will have their constructors invoked.
The order that they are called in should not be relied upon, as they are loaded from various
files. The window's onload handler is not called until after all the bindings have been attached
and their constructors finished. The second point a binding is attached is if you change the
-moz-binding style property of an element. The existing binding will be removed, after its
destructor is called. Then, the new binding will be added in its place and its constructor invoked.
The script for a constructor or destructor should be placed directly inside the appropriate tag.
There should only be at most one of each per binding and they take no arguments. Here are
some examples:
<constructor>
if (this.childNodes[0].getAttribute("open") == "true"){
this.loadChildren();
}
</constructor>
<destructor action="saveMyself(this);"/>
Event Handlers
As you might expect, mouse clicks, key presses and other events are passed to each of the
elements inside the content. However, you may wish to trap the events and handle them in a
special way. You can add event handlers to the elements inside the content if needed. The last
example in the previous section demonstrated this. In that example, oncommand handlers were
added to some buttons.
However, you may want to add an event handler to the entire contents, that is, all the elements
defined in the content tag. This could be useful when trapping the focus and blur events. To
define an event handler, use the handler element. Each will describe the action taken for a
single event handler. You can use more than one handler if necessary. If an event does not
match any of the handler events, it is simply passed to the inner content as usual.
<binding id="binding-name">
<handlers>
<handler event="event-name" action="script"/>
</handlers>
</binding>
Place all of your handlers within the handlers element. Each handler element defines the action
taken for a particular event specified by its event attribute. Valid event types are those
supported by XUL and JavaScript, such as click and focus. Use the event name without the
'on' in front of it.
A common reason to set handlers is to modify the custom properties when an event occurs. For
example, a custom checkbox might have a checked property which needs to be changed when
the user clicks the checkbox:
<handlers>
<handler event="mouseup" action="this.checked=!this.checked"/>
</handlers>
When the user clicks and releases the mouse button over the check box, the mouseup event is
sent to it, and the handler defined here is called, causing the state of the checked property to be
reversed. Similarly, you may wish to change a property when the element is focused. You will
need to add handlers to adjust the properties whenever input from the mouse or keyboard
would require it.
For mouse events, you can use the button attribute to have the handler only trap events that
occur from a certain button. Without this attribute, the handler traps all events regardless of the
button that was pressed. The button attribute should be set to either 0 for the left mouse button,
1 for the middle mouse button or 2 for the right mouse button.
<handlers>
<handler event="click" button="0" action="alert('Left button pressed');"/>
<handler event="mouseup" button="1" action="alert('Middle button pressed')"/>
<handler event="click" button="2" action="alert('Right button pressed');"/>
</handlers>
For key events, you can use a number of attributes similar to those for the key element to match
a specific key and match only when certain modifer keys are pressed. The previous example
could be extended so that the checked property of the check box is changed when the space
bar is pressed.
<handlers>
<handler event="keypress" key=" " action="this.checked=!checked"/>
</handlers>
You can also use the keycode attribute to check for non-printable keys. The section on
keyboard shortcuts provides more information. The modifier keys can be checked by adding a
modifiers attribute. This should be set to one of the values set below:
• alt
The user must press the Alt key.
• control
The user must press the Control key.
• meta
The user must press the Meta key.
• shift
The user must press the Shift key.
• accel
The user must press the special modifier key that is usually used for keyboard shortcuts
on their platform.
If set, the handler is only called when the modifier is pressed. You can require multiple modifier
keys by separating them with spaces.
The following alternate syntax can be used when the code in a handler is more complex:
<binding id="binding-name">
<handlers>
<handler event="event-name">
-- handler code goes here --
</handler>
</handlers>
</binding>
Handlers Example
The following example adds some key handlers to create a very primitive local clipboard:
<binding id="clipbox">
<content>
<xul:textbox/>
</content>
<implementation>
<field name="clipboard"/>
</implementation>
<handlers>
<handler event="keypress" key="x" modifiers="control"
action="this.clipboard=document.getAnonymousNodes(this)[0].value;
document.getAnonymousNodes(this)[0].value='';"/>
<handler event="keypress" key="c" modifiers="control"
action="this.clipboard=document.getAnonymousNodes(this)[0].value;"/>
<handler event="keypress" key="v" modifiers="control"
action="document.getAnonymousNodes(this)[0].value=this.clipboard ?
this.clipboard : '';"/>
</handlers>
</binding>
The content is a single textbox. A field clipboard has been added to it to store the clipboard
contents. This does mean that the clipboard operations are limited to this single textbox.
However, each one will have its own buffer.
Three handlers have been added, one for cut, one for copy and the other for paste. Each has its
own keystroke that invokes it. The first handler is the cut operation and is invoked when the
Control key is pressed along with the x key. The script within the action attribute is used to cut
the text from the textbox and put it into the clipboard field. For simplicity, the entire text is cut
and not just the selected text. The code works as follows:
1. this.clipboard=document.getAnonymousNodes(this)[0].value;
The first element of the anonymous content array is retrieved which gives a reference to
the textbox element, which happens to be the first (and only) element within the content
element. The value property is retrieved which will provide the text within the textbox.
This is then assigned to the clipboard field. The result is copying the text in the textbox
into this special clipboard.
2. document.getAnonymousNodes(this)[0].value=''
The text of the textbox is then assigned a value of a null string. This effectively clears the
text in the textbox.
A copy operation is similar but does not the clear the text afterwards. Pasting is the opposite
where the value of the textbox is assigned from the value in the clipboard field. If we were
creating a real implementation of these clipboard keyboard shortcuts, we would probably use
the real clipboard interface and handle the current selection as well.
XBL Inheritance
In this section, we'll look at how to extend existing XBL definitions.
Inheritance
Sometimes you may want to create an XBL widget that is similar to an existing one. For
example, let's say you want to create an XBL button with a popup. One way to create this is to
duplicate the existing XBL code for buttons. However, it would be better to simply extend the
existing button code.
Any binding can be extended with another. The child binding can add properties, methods and
event handlers. The child binding will have all the features it defines in addition to the features
from the binding it inherits from (and any that binding inherits from and so on up the tree).
To extend an existing binding, add an extends attribute on to the binding tag. For example, the
following binding creates a textbox which adds the text 'http://www' to the beginning of its value
when the F4 key is pressed.
<binding id="textboxwithhttp"
extends="chrome://global/content/bindings/textbox.xml#textbox">
<handlers>
<handler event="keypress" keycode="VK_F4">
this.value="http://www"+value;
</handler>
</handlers>
</binding>
The XBL here extends from the XUL textbox element. The URL given in the extends attribute
above is the URL of the binding of the textbox binding. This means that we inherit all of the
content and behavior provided by the textbox binding. In addition, we add a handler which
responds to the keypress event.
Autocompleting TextBoxes
The example above is similar to how the URL autocomplete feature works in Mozilla. A textbox
that supports autocomplete is just one with a XBL binding that extends the basic textbox.
The autocomplete textbox adds extra event handling so that when a URL is typed, a menu will
pop up with possible completions. You can use it in your own applications too. Just create a
textbox with two extra attributes.
Set the type to autocomplete to add the autocomplete feature to an existing textbox. Set the
searchSessions to indicate what type of data to look up. In this case, the value history is used,
which looks up URLs in the history. (You can also use the value addrbook to look up
addresses in the address book.)
XBL Example
This section will describe an example XBL element.
A Slideshow Element
Let's construct a full example of an XBL element. This will be a widget that stores a deck of
objects, each displayed one at a time. Navigation buttons along the bottom will allow the user to
cycle through the objects while a text widget between the buttons will display the current page.
You could put anything within the pages, however, this widget might be useful for a set of
images. We'll call this a slideshow element.
First, let's determine what elements need to go in the XBL content. Because we want page
flipping, a deck element would be the most suitable to hold the page content. The content of the
pages will be specified in the XUL file, not in XBL, but we'll need to add it inside the deck. The
children tag will need to be used. Along the bottom, we'll need a button to go the previous page,
a text widget to display the current page number, and a button to go to the next page.
<binding id="slideshow">
<content>
<xul:vbox flex="1">
<xul:deck xbl:inherits="selectedIndex" selectedIndex="0" flex="1">
<children/>
</xul:deck>
<xul:hbox>
<xul:button xbl:inherits="label=previoustext"/>
<xul:label flex="1"/>
<xul:button xbl:inherits="label=nexttext"/>
</xul:hbox>
</xul:vbox>
</content>
</binding>
This binding creates the slideshow structure that we want. The flex attribute has been added to
a number of elements so that it stretches in the right way. The label attributes on the two
buttons inherit their values from the bound element. Here, they inherit from two custom
attributes, previoustext and nexttext. This makes it easy to change the labels on the buttons.
The children of the element that the XBL is bound to will be placed inside the deck. The
selectedIndex is inherited by the deck, so we may set the initial page in the XUL.
.slideshow {
-moz-binding: url("slideshow.xml#slideshow");
}
The first button, 'Button 1' has been used as the first page of the
deck. The label widget has not appeared as no value has been
specified for it. We could set a value, but instead it will calculated
later.
Next, a property that holds the current page will be added. When getting this custom property, it
will need to retrieve the value of the selectedIndex attribute of the deck, which holds the number
of the currently displayed page. Similarly, when setting this property, it will need to change the
selectedIndex attribute of the deck. In addition, the text widget will need to be updated to display
which page is the current one.
<property name="page"
onget="return
parseInt(document.getAnonymousNodes(this)[0].childNodes[0].getAttribute('selectedInde
x'));"
onset="return this.setPage(val);"/>
The 'page' property gets its value by looking at the first element of the anonymous array. This
returns the vertical box, so to get the deck, we need to get the first child node of the box. The
anonymous array isn't used as the deck is not anonymous from the box. Finally, the value of the
selectedIndex attribute is retrieved. To set the page, a method 'setPage' is called which will be
defined later.
An oncommand handler will need to be added to the Previous and Next buttons so that the
page is changed when the buttons are pressed. Conveniently, we can change the page using
the custom 'page' property that was just added:
<xul:button xbl:inherits="label=previoustext"
oncommand="parentNode.parentNode.parentNode.page--;"/>
<xul:description flex="1"/>
<xul:button xbl:inherits="label=nexttext"
oncommand="parentNode.parentNode.parentNode.page++;"/>
Because the 'page' property is only on the outer XUL element, we need to to use the
parentNode property to get to it. The first parentNode returns the parent of the button which is
the horizontal box, the second its parent, the vertical box, and finally, its parent which is the
outer box. The 'page' property is incremented or decremented. This will call the onget script to
get the value, increment or decrement the value by one, and then call the onset handler to set
the value.
Now let's define the 'setPage' method. It will take one parameter, the page number to set the
page to. It will need to make sure the page is not out of range and then modify the deck's
selectedIndex attribute and the text widget's label attribute.
<method name="setPage">
<parameter name="newidx"/>
<body>
<![CDATA[
var thedeck=document.getAnonymousNodes(this)[0].childNodes[0];
var totalpages=this.childNodes.length;
if (newidx<0) return 0;
if (newidx>=totalpages) return totalpages;
thedeck.setAttribute("selectedIndex",newidx);
document.getAnonymousNodes(this)[0].childNodes[1].childNodes[1]
.setAttribute("value",(newidx+1)+" of "+totalpages);
return newidx;
]]>
</body>
</method>
This function is called 'setPage' and takes one parameter 'newidx'. The body of the method has
been enclosed inside '<![CDATA[' and ']]>'. This is the general mechanism in all XML files that
can be used to escape all of the text inside it. That way, you don't have to escape every less-
than and greater-than sign inside it.
• var thedeck=document.getAnonymousNodes(this)[0].childNodes[0];
Get the first element of the anonymous content array, which will be the vertical box, then
get its first child, which will be the deck element.
• var totalpages=this.childNodes.length;
Get the number of children that the bound box has. This will give the total number of
pages that there are.
• if (newidx<0) return 0;
If the new index is before the first page, don't change the page and return 0. The page
should not change to a value earlier than the first page.
• if (newidx>=totalpages) return totalpages;
If the new index is after the last page, don't change the page and return the last page's
index. The page should not change to one after the last page.
• thedeck.setAttribute("selectedIndex",newidx);
Change the selectedIndex attribute on the deck. This causes the requested page to be
displayed.
• document.getAnonymousNodes(this)[0].childNodes[1].childNodes[1].setAttribute("v
alue",(newidx+1)+" of "+totalpages);
This line modifies the label element so that it displays the current page index. The label
element can be retrieved by getting the first element of anonymous content (the vertical
box), the second child of that label element (the horizontal box), and then the second
element of that box. The value attribute is changed to read '1 of 3' or something similar.
Note that one is added to the index because indicies start at 0.
We will also need a constructor to initialize the label element so that it displays correctly when
the slideshow is first displayed. We use similar code as to the method above to set the page
number. The reference to 'this.page' will call the onget script of the page property, which in turn
will retrieve the initial page from the selectedIndex attribute.
<constructor>
var totalpages=this.childNodes.length;
document.getAnonymousNodes(this)[0].childNodes[1].childNodes[1]
.setAttribute("value",(this.page+1)+" of "+totalpages);
</constructor>
We can add some additional features as well. Some keyboard shortcuts could be used for the
Previous and Next buttons, (say backspace and the Enter key). First and Last buttons could be
added to go to the first and last pages. The label element could be changed to a field where the
user could enter the page to go to, or a popup could be added to allow selection of the page
from a menu. We could also add a border around the deck with CSS to make it look a bit nicer.
<binding id="slideshow">
<content>
<xul:vbox flex="1">
<xul:deck xbl:inherits="selectedIndex" selectedIndex="0" flex="1">
<children/>
</xul:deck>
<xul:hbox>
<xul:button xbl:inherits="label=previoustext"
oncommand="parentNode.parentNode.parentNode.page--;"/>
<xul:description flex="1"/>
<xul:button xbl:inherits="label=nexttext"
oncommand="parentNode.parentNode.parentNode.page++;"/>
</xul:hbox>
</xul:vbox>
</content>
<implementation>
<constructor>
var totalpages=this.childNodes.length;
document.getAnonymousNodes(this)[0].childNodes[1].childNodes[1]
.setAttribute("value",(this.page+1)+" of "+totalpages);
</constructor>
<property name="page"
onget="return
parseInt(document.getAnonymousNodes(this)[0].childNodes[0].getAttribute('selectedInde
x'));"
onset="return this.setPage(val);"/>
<method name="setPage">
<parameter name="newidx"/>
<body>
<![CDATA[
var thedeck=document.getAnonymousNodes(this)[0].childNodes[0];
var totalpages=this.childNodes.length;
if (newidx<0) return 0;
if (newidx>=totalpages) return totalpages;
thedeck.setAttribute("selectedIndex",newidx);
document.getAnonymousNodes(this)[0].childNodes[1].childNodes[1]
.setAttribute("value",(newidx+1)+" of "+totalpages);
return newidx;
]]>
</body>
</method>
</implementation>
</binding>
Features of a Window
We've already seen some features of windows. We'll look at some more in this section.
You can create a second window for your application in the same manner as you would create
the first one. Just create a second XUL file with the window code in it. As in HTML, you can use
the window.open function to open the second window. This function will return a reference to
the newly opened window. You can use this reference to call functions of the other window.
The open function takes three arguments. The first is the URL of the file you wish to open. The
second is an internal name of the window. The third is a list of display flags. The flag 'chrome' is
important to open the window as a chrome file. If you do not add the 'chrome' flag, the file will
open up as the content in a browser window.
For example:
window.open("chrome://findfile/content/findfile.xul","findfile","chrome");
You should have noticed that whenever elements were added to a window, the window's width
expanded to fit the new elements. The window is really just a box which is flexible and defaults
to vertical orientation. You can also specify the width and height directly on the window tag.
This, of course, causes the window to be displayed in a specific size. If you leave it out, the size
is determined by the elements that are in it.
<window
id="findfile-window"
title="Find Files"
width="400"
height="450"
xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
In this example, the window will open with a width of 400 pixels and a height of 450 pixels. Even
if there aren't enough elements to fit this size, the window will still open at this size and there will
be blank space in the remaining area. If there are too many elements, the window will not be
large enough to fit the elements. The user will have to resize the dialog. You have to be careful
when specifying a width and height that the window is not too small or too big.
Note that you must specify both the width and the height. If you only specify one, the other will
be set to 0. To have the window set its size automatically, leave both the width and height out.
The width and height only specify the initial size of the window. The user may still resize the
window to another size, assuming that the window is resizable.
The flags below can be passed as part of the third argument to the window.open function. Your
operating system may not support all of them. You can also use any of the pre-existing flags,
which you should find in a JavaScript reference. You may disable a feature by setting it to 'no',
for example 'dialog=no'.
• alwaysLowered
The window will always appear behind other windows.
• alwaysRaised
The window will always appear above other windows.
• centerscreen
The window will be centered on the screen when it is opened.
• dependent
The window will always appear relative to the window that opened it. If the window that
opened the new window is moved, the new window is moved relative to it.
• dialog
The window is a dialog box, which may appear differently.
• modal
The dialog is modal. The window that opened the modal window can't be interacted with
until the modal window is closed.
• resizable
The user can resize the window.
Creating Dialogs
A XUL application will often require dialogs to be displayed. This section describes how one
might construct them.
Creating a Dialog
The open function is used to open a window. A related function is openDialog. This function has
several main differences. It will display a dialog instead of a window which implies that it is
asking something of the user. It may have subtle differences in the way it works and appears to
the user. These differences will vary on each platform.
In addition, the openDialog function can take additional arguments beyond the first three. These
arguments are passed to the new dialog and placed in an array stored in the new window's
arguments property. You can pass as many arguments as necessary. This is a convenient way
to supply default values to the fields in the dialog.
var somefile=document.getElementById('enterfile').value;
window.openDialog("chrome://findfile/content/showdetails.xul","showmore",
"chrome",somefile);
In this example the dialog 'showdetails.xul' will be displayed. It will be passed one argument,
'somefile', which was taken from the value of an element with the id enterfile. In a script used
by the dialog, we can then refer to the argument using the window's arguments property. For
example:
var fl=window.arguments[0];
document.getElementById('thefile').value=fl;
This is an effective way to pass values to the new window. You can pass values back from the
opened window to the original window in one of two ways. First, you could use the
window.opener property which holds the window that opened the dialog. Second, you could
pass a function or object as one of the arguments, and then call the function or modify the
object in the opened dialog.
The dialog element should be used in place of the window element when creating a dialog. It
provides the useful capability to construct up to four buttons along the bottom of the dialog for
OK, Cancel and so on. You do not need to include the XUL for each button but you do need to
supply code to handle when the user presses each button. This mechanism is necessary
because different platforms have a specific order in which the buttons appear.
<script>
function doOK()
{
alert("You pressed OK!");
return true;
}
function doCancel()
{
alert("You pressed Cancel!");
return true;
}
</script>
</dialog>
You may place any elements that you wish in a dialog. The dialog element has some additional
attributes that windows do not have. The buttons attribute is used to specify which buttons
should appear in the dialog. The following values may be used, seperated by commas:
• accept - an OK button
• cancel - a Cancel button
• help - a Help button
• disclosure - a disclosure button, which is used for showing more information.
You can set code to execute when the buttons are pressed using the ondialogaccept,
ondialogcancel, ondialoghelp and ondialogdisclosure attributes. If you try the example above,
you will find that the doOK function is called when the OK button is pressed and the doCancel
function is called when the Cancel button is pressed.
The two functions doOK and doCancel return true which indicate that the dialog should be
closed. If false was returned, the dialog would stay open. This would be used if an invalid value
was entered in a field in the dialog.
File Pickers
A file picker is a dialog that allows the user to select a file. It is most commonly used for the
Open and Save As menu commands, but you can use it any place in which the user needs to
select a file. The XPCOM interface nsIFilePicker is used to implement a file picker.
The appearance of the dialog will be different for each type and will vary on each platform. Once
the user selects a file or folder, it can be read from or written to.
The file picker interface nsIFilePicker is responsible for displaying a dialog in one the three
modes. You can set a number a features of the dialog by using the interface. When the dialog is
closed, you can use the interface functions to get the file that was selected.
To begin, you need to create a file picker component and initialize it.
First, a new file picker object is created and stored in the variable 'fp'. The 'init' function is used
to initialize the file picker. This function takes three arguments, the window that is opening the
dialog, the title of the dialog and the mode. The mode here is 'modeOpen' which is used for an
Open dialog. You can also use 'modeGetFolder' and 'modeSave' for the other two modes.
These modes are constants of the nsIFilePicker interface.
There are two features you can set of the dialog before it is displayed. The first is the default
directory that is displayed when the dialog is opened. The second is a filter that indicates the list
of file types that are displayed in the dialog. This would be used, for example, to hide all but
HTML files.
You can set the default directory by setting the displayDirectory property of the file picker object
to a directory. The directory should be an nsILocalFile object. If you do not set this, a suitable
default will be selected for you. To add filters, call the appendFilters function to set the file types
that you wish to have displayed.
fp.appendFilters(nsIFilePicker.filterHTML | nsIFilePicker.filterImages);
fp.appendFilters(nsIFilePicker.filterText | nsIFilePicker.filterAll);
The first example will add filters for HTML and for image files. The user will only be able to
select those types of files. The manner in which this is done is platform specific. On some
platforms, each filter will be separate and the user can choose between HTML files and image
files. The second example will add filters for text files and for all files. The user therefore has the
option to display text files only or all files.
You can also use 'filterXML' and 'filterXUL' to filter for XML and XUL files. If you would like to
filter for custom files, you can use the appendFilter function to do this:
This line will add a filter for Wave and MP3 audio files. The first argument is the title of the file
type and the second is a semicolon-seperated list of file masks. You can add more masks or
fewer masks as needed. You can call appendFilter as many times as necessary to add
additional filters. The order you add them determines their priority. Typically, the first one added
is selected by default.
Finally, you can show the dialog by calling the show function. It takes no arguments but returns
a status code that indicates what the user selected. Note that the function does not return until
the user has selected a file. The function returns one of three constants:
• returnOK: the user selected a file and pressed OK. The file the user selected will be
stored in the file picker's file property.
• returnCancel: the user pressed Cancel.
• returnReplace: in the save mode, this return value identifies that the user selected a file
to be replaced. (returnOK will be returned when the user entered the name of a new file.)
You should check the return value and then get the file object from the file picker using the file
property.
Creating a Wizard
Many applications use wizards to help the user through complex tasks. XUL provides a way to
create wizards easily.
The Wizard
A wizard is a special type of dialog that contains a number of pages. Navigation buttons appear
on the bottom of the dialog to switch between pages. The wizards are usually used to help the
user perform a complex task. Each page contains a single question or a set of related
questions. After the last page, the operation is carried out.
XUL provides a wizard element which can be used to create wizards. The contents inside the
wizard element include all the content of every page of the wizard. Attributes placed on the
wizard are used to control the wizard navigation. When creating a wizard, use the wizard tag
instead of the window tag.
Note that wizards currently only work properly from chrome URLs.
The wizard consists of several sections, although the exact layout will vary for each platform.
The wizard will generally be displayed like those on the user's platform. A typical layout will
include a title across the top, a set of navigation buttons across the bottom and the page
contents in between.
The title across the top is created using the title attribute, much like one would do for regular
windows. The navigation buttons are created automatically. The pages of the wizard are
created using the wizardpage element. You can place whatever content you want inside each
wizardpage. Here is an example wizard:
<?xml version="1.0"?>
<wizardpage>
<description>
This wizard will help you select the type of dog that is best for you."
</description>
<label value="Why do you want a dog?"/>
<menulist>
<menupopup>
<menuitem label="To scare people away"/>
<menuitem label="To get rid of a cat"/>
<menuitem label="I need a best friend"/>
</menupopup>
</menulist>
</wizardpage>
</wizard>
This wizard has two pages, one that has a drop-dowm menu and the other with a set of radio
buttons. The wizard will be formatted automatically, with a title across the top and a set of
buttons along the bottom. The user can navigate between the pages of the wizard with the Back
and Next buttons. These buttons will enable and disable themselves at the appropriate
moments. In addition, on the last page, the Finish button will appear. All of this is automatic, so
you don't have to do anything to manipulate the pages.
The description attribute may optionally placed on a wizardpage element to provide a sub-
caption for that page. In the example above, it has been placed on the second page, but not the
first page.
You will generally want to do something once the Finish button is pressed. You can set an
attribute onwizardfinish on the wizard element to accomplish this. Set it to a script which
performs whatever task you want and then returns true. This script might be used to save the
information that the user entered during the wizard.
For example:
When the user clicks the Finish button, the function 'saveDogInfo' will be called, which would be
defined in a script file to save the information that was entered. If the function returns true, the
wizard closes. If it returns false, then the wizard does not close, which might occur if the
function 'saveDogInfo' encountered invalid input, for example.
There are also related onwizardback, onwizardnext and onwizardcancel attributes, which are
called when the Back, Next and Cancel buttons are pressed. These functions are called
regardless of which page is currently displayed.
To have different code called depending on which page you are on, use the onpagerewound or
onpageadvanced attributes on a wizardpage element. They work similar to the other functions
except that you can use different code for each page. This allows you to validate the input
entered on each page before the user continues.
A third method is to use the onpagehide and onpageshow attributes on the wizardpage
element. They will be called when the page is hidden or shown, regardless of which button was
pressed (except when Cancel is pressed -- you need to use onwizardcancel to check for this.)
These three methods should provide enough flexibility to handle navigation as you need to. The
following is a summary of attribute functions that are called when the user presses Next, in the
order that they will be checked. As soon as one returns false, the navigation will be cancelled.
More Wizards
This section describes some additional features of wizards.
Normally, a wizard displays each wizardpage in the order that you place them in the XUL file. In
some cases however, you may want to have different pages of the wizard appear depending on
what the user selects in earlier pages.
In this case, place a pageid attribute on each of the pages. This should be set to an indentifer
for each page. Then, to navigate to a page, use one of two methods:
1. Set the next attribute on each page to the page ID of the next page to go to. You can
change these attributes as needed to navigate to different pages.
2. Call the wizard's goTo method. It takes one argument, the page ID of a page to go to.
You might call this method in the onpageadvanced or onwizardnext handlers. Remember
to return false in this case, because you have already changed the page yourself. Note
that the goTo method, because it causes a page change, will fire the events again, so
you'll have to make sure you handle this case.
For example, here are a set of wizard pages (the inner content has been omitted):
The wizard always starts at the first page, which in this case has the page ID type. The next
page is the one with the page ID font, so the wizard will navigate to that page next. On the page
with the page ID font, we can see that the next page is done, so that page will be displayed
afterwards. The page with the page ID done has no next attribute, so this will be the last page.
A script will adjust the next attributes as necessary to go to the page with the page ID color
when needed.
Wizard Functions
The wizard works much like a tabbed panel, except that the tabs are not displayed and the user
navigates between pages by using the buttons along the bottom. Because all of the pages are
part of the same file, all of the values of the fields on all pages will be remembered. Thus, you
do not have to load and save information between pages.
However, you may want to do some validation of each field on each page. For this, use the
handlers described in the previous section. If a field is invalid, you might display an alert. In
some cases, it would be more convenient to disable the Next button until valid input has been
entered.
The wizard has a property canAdvance, which can be set to true to indicate that the Next button
should be enabled. If set to false, the Next button is disabled. You can change the property
when invalid or valid data has been entered.
In the following example, the user must enter a secret code into a textbox on the first page of
the wizard. The function checkCode is called whenever the first page is shown as indicated by
the onpageshow attribute. It is also called whenever a key is pressed in the textbox, to
determine whether the Next button should be enabled again.
<?xml version="1.0"?>
<script>
function checkCode()
{
document.getElementById('theWizard').canAdvance=
(document.getElementById('secretCode').value == "cabbage");
}
</script>
<wizardpage onpageshow="checkCode();">
<label value="Enter the secret code:"/>
<textbox id="secretCode" onkeyup="checkCode();"/>
</wizardpage>
<wizardpage>
<label value="That is the correct secret code."/>
</wizardpage>
</wizard>
There is also a corresponding canRewind property that you can use to enable or disable the
Back button. Both properties are adjusted automatically as you switch pages. Thus, the Back
button will be disabled on the first page so you don't have to set it yourself.
Another useful property of the wizard is currentPage, which holds a reference to the currently
displayed wizardpage. You can also modify the current page by changing this property. If you
do change it, the various page change events will still be fired.
Overlays
This section will describe overlays which can be used to separate common content.
Using Overlays
In a simple application with only one window, you will generally have only one XUL file, along
with a script file, a style sheet, a DTD file and perhaps some images. Some applications will
have a number of dialogs associated with them also. These will be stored in separate XUL files.
More sophisticated applications will contain many windows and dialogs.
An application that has several windows will have numerous elements or parts of the user
interface that are common between each window. For example, each of Mozilla's components
share some common elements. Some of the menus are similar, such as the Tools and Help
menus, the sidebar is similar, and each window shares some common global keyboard
shortcuts.
One could handle this by re-implementing the similar elements and functions in each file that
are needed. However, this would be difficult to maintain. If you decide to change something, you
would have to change it in numerous places. Instead, it would be better to use a mechanism
that allows you to separate the common elements and have them shared between windows.
You can do this with overlays.
Within an overlay, you may place elements that are shared between all windows that use that
overlay. Those elements are added into the window at locations determined by their ids.
For example, let's say you want to create a help menu that is shared between several windows.
The help menu will be placed in an overlay, using the same XUL that you would use normally.
The menu will be given an id attribute to identify it. Each window will import the overlay using a
directive which will be described in a moment. To use the help menu as defined in the overlay,
you only need to add a single menu element with the same value for its id attribute as used in
the overlay. This menu does not need to contain any children as those will be placed in the
overlay.
When a window with an overlay is opened, the elements in both the window and the overlay
with the same ids are combined together. The children of matching elements are added to the
end of the set of children in the window's element. Attributes that are present on the overlay's
elements will be applied to the window's elements. These details will be explained in more detail
later.
To import an overlay into a window, use the syntax described below. Let's add this near the top
of the findfiles dialog XUL file.
<?xul-overlay href="chrome://findfile/content/helpoverlay.xul"?>
This line should be added somewhere near the top of the file, usually just before any DTDs are
declared. In the example above, the window is importing an overlay stored in the file
helpoverlay.xul.
The overlay itself is a XUL file that contains an overlay element instead of a window element.
Other than that, it is much the same. You can import overlays from inside other overlays.
Overlays can also have their own stylesheets, DTDs and scripts. The example below shows a
simple Help menu stored in an overlay.
<?xml version="1.0"?>
<overlay id="toverlay"
xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
<menu id="help-menu">
<menupopup id="help-popup">
<menuitem id="help-contents" label="&contentsCmd.label;"
accesskey="&contentsCmd.accesskey;"/>
<menuitem id="help-index" label="&indexCmd.label;"
accesskey="&indexCmd.accesskey;"/>
<menuitem id="help-about" label="&aboutCmd.label;"
accesskey="&aboutCmd.accesskey;"/>
</menupopup>
</menu>
</overlay>
The overlay element surrounds the overlay content. It uses the same namespace as XUL
window files. Defined within the overlay is a single menu with three items in it. The id of this
menu is help-menu. This means that its content will be added to the window where a similar
element exists with the same id value. If such an element does not exist, that part of the overlay
is ignored. The overlay can contain as many elements as necessary. Note that the overlay
needs to include the DTD file also. We use the same one as the main window here, but
normally you would create a separate DTD file for each overlay.
Next, we need to add the help menu to the findfiles dialog window. To do this just add a menu
with the same id in the right location. The most likely place is just after the edit menu.
Here, the help menu element contains no content. The items from the menu are taken from the
overlay because the ids match. We can then import the overlay in other windows and only have
the contents of the help menu defined in one place. We need to add some lines to the DTD file
as well:
We can further reduce the amount of code within the window by putting the attributes on the
help menu (label and accesskey in this example) in the overlay instead. Those attributes will be
inherited by the element. If both the element and the window specify the same attribute, the
value in the overlay will override the element's value.
findfile.xul:
<menu id="help-menu"/>
helpoverlay.xul:
If there is content inside both the XUL window and in the overlay, the window's content will be
used as is and the overlay's content will be appended to the end. This following example
demonstrates this:
stopandgo.xul:
<?xml version="1.0"?>
<box id="singlebox">
<button id="gobutton" label="Go"/>
<button id="stopbutton" label="Stop"/>
</box>
</window>
toverlay.xul:
<?xml version="1.0"?>
<overlay id="toverlay"
xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
<box id="singlebox">
<button id="backbutton" label="Back"/>
<button id="forwardbutton" label="Forward"/>
</box>
</overlay>
In this example, the box with the identifier singlebox contains its own content. The elements
are combined and the two buttons from the overlay are added to the end of the box.
findfile.xul:
<menu id="help-menu">
<menupopup id="help-popup">
<menuitem id="help-findfiles" label="&findfilehelpCmd.label;"
accesskey="&findfilehelpCmd.accesskey;"/>
</menupopup>
</menu>
</menubar>
The id attribute of the menupopup element also matches the one in the overlay. This will cause
the items to be merged into the same popup. Overlays will merge items with the same ids even
if they are inside of other elements.
However, we may have wanted to have the menu items from the overlay in the previous
example placed at the beginning of the menu instead of at the end. XUL provides a mechanism
that allows you to not only place them at the beginning but to put some of the items at the top
and others at the bottom (or anywhere in-between). This allows you to overlay menus, toolbars
and other widgets at the exact location that you wish.
To do this, use the insertbefore attribute on the menuitems. Its value should be the id of an
element that you want to insert the item before. Alternatively, you can use the insertafter
attribute to indicate which element to insert after. These attributes only affect the element the
attributes are added to. If one element is 'inserted before', the remaining elements are still
added to the end. If you want to have all the elements appear before, you must put the
insertbefore attribute on all elements.
In addition, you can use the position attribute if you want to specify a specific index position.
The first position is 1.
Let's say that we wanted the Contents and Index items from the previous example to appear
before the Find files help item and the About item to appear after. To do this we add the
insertbefore attribute to both the Contents and Index menu items. For completeness, you could
add an insertafter attribute on the About menu too, but it isn't necessary because it appears at
the end by default.
In the help menu example above, the id of the menu item is help-findfiles. Thus, we need to
set the insertbefore attributes to this id. The example below shows the changes:
<menupopup id="help-popup">
<menuitem id="help-contents" label="Contents" insertbefore="help-findfiles"/>
<menuitem id="help-index" label="Index" insertbefore="help-findfiles"/>
<menuitem id="help-about" label="About..."/>
</menupopup>
Now, when a window using the help overlay (such as the find files dialog) is opened, the
following will occur:
1. For all of the items directly in the overlay, that is all the children of the overlay element,
an element in the base window is found with the same id. If not found, that element in the
overlay is ignored. In this example, the elements with the ids of help-menu and help-
popup are found.
2. If found, the attributes on the overlay's element are copied to the found element.
3. The children of the overlay's element, in this case each menuitem, are inserted as
children of the base window's element.
• If the overlay's element contains an insertafter attribute, the element is added just
after the element in the base window with the id that matches the value of this
attribute.
4. If the overlay's element contains an insertbefore attribute, the element is added just
before the element in the base window with the id that matches the value of this attribute.
5. If the overlay's element contains an position attribute, the element is added at the one-
based index specified in this attribute.
6. Otherwise, the element is added as the last child.
Actually, the values of insertbefore and insertafter can be comma-separated lists, in which case
the first id in the list that is found in the window is used to determine the position.
Overlays have another very useful feature. In the examples in the previous section, the overlays
were imported by the window. You can also go the other way and have the overlays specify
which windows that they apply to. You specify this by modifying the contents.rdf file for your
package. This is useful because the overlay can modify the user interface of another package
without changing the other package. For example, you could add menu items or toolbars to the
Mozilla browser window.
We'll use this feature to add a toolbar to the Mozilla browser window. The Mozilla Mail
application uses overlays to add content to the browser window. For example, if Mail is not
installed, there will be no New Message command. However, if Mail is installed, an overlay will
be applied to the menu to add the New Message command. Below, we'll add a find files toolbar
to the browser. It probably wouldn't be useful to have this feature, but we'll do it anyway.
Mozilla allows you to add a list of overlays to the contents.rdf file that you use to list chrome
packages, skins and locales. Once you have created an overlay, you can add it to the
contents.rdf file. Then add items, one for each window that you want the overlay to apply to.
First, let's create a simple overlay. It will just have a few fields for entering a filename and
directory to search. Call the file foverlay.xul and add it to the findfile directory along with
findfile.xul.
<?xml version="1.0"?>
<overlay
xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
<toolbox id="navigator-toolbox">
<toolbar id="findfile_toolbar">
<label control="findfile_filename" value="Search for files named:"/>
<textbox id="findfile_filename"/>
<label control="findfile_dir" value="Directory:"/>
<textbox id="findfile_dir"/>
<button label="Browse..."/>
</toolbar>
</toolbox>
</overlay>
You can view this by changing the overlay to a window. The only thing that is special here is the
id used on the toolbox. This value (navigator-toolbox) is the same as the identifier of the
toolbox in the browser window (navigator.xul). This means that the overlay will apply to the
toolbox in the browser window and the content will be added as an extra toolbar.
To add this overlay to the manifest file, we need to add two resources. First, we add one for
each window that we are applying an overlay to. The code below should be added into
content.rdf just before the closing RDF tag.
<RDF:Seq about="urn:mozilla:overlays">
<RDF:li resource="chrome://navigator/content/navigator.xul"/>
</RDF:Seq>
This declares that we are adding a overlay window, a child of the root overlay node
(urn:mozilla:overlays). You can add additional nodes for any other windows that you want to
apply overlays to by adding additional li nodes.
Next, we add a node for each overlay to apply to the window. In this case, we only have one,
but we could apply others also. Add these lines just after the previous lines.
<RDF:Seq about="chrome://navigator/content/navigator.xul">
<RDF:li>chrome://findfile/content/foverlay.xul</RDF:li>
</RDF:Seq>
Mozilla reads this information and builds a list of overlays that are applied to other windows. It
stores this information in the chrome/overlayinfo directory. You do not need to manually modify
the files in this directory. It is automatically generated and modified when Mozilla is first run or
when new packages are installed. However, you can force the data to be rebuilt by deleting this
directory and the chrome.rdf file.
As a side note, you can use a similar technique to apply extra style sheets. The following
example shows how:
<RDF:Seq about="urn:mozilla:stylesheets">
<RDF:li resource="chrome://messenger/content/messenger.xul"/>
</RDF:Seq>
<RDF:Seq about="chrome://messenger/content/messenger.xul">
<RDF:li>chrome://blueswayedshoes/skin/myskinfile.css</RDF:li>
</RDF:Seq>
13. Installation
Creating an Installer
This section will describe packaging a XUL application into an installer.
XPInstall Packages
Mozilla provides a mechanism which can be used to package XUL windows, scripts, skins and
other files into single file installers. You can place this installer file somewhere for users to
download. A simple script can be used to have the package downloaded and installed. This
mechanism is called XPInstall (Cross platform Install).
XPInstall installers are packaged into JAR files. Inside the JAR file, you can add all the various
files that you want to have installed. In addition, installers should contain an install script (a file
named install.js) which can be used to script the installation process. This script has access to
various install functions which can be used to install files and components.
The JAR file installers typically have the extension .xpi (pronounced zippy) to distinguish them
from other archives. The installers will be usually used to install Mozilla components such as
new skins, plugins and new packages.
There are several steps involved in launching an installer and installing components. These are
described step by step below.
1. Create a Web page from which the user can download the software to be installed. This
page will contain an install trigger which is a small piece of script which launches the
install.
2. The user is presented with a dialog which indicates the package being installed. It is
possible for the install trigger to launch multiple installers. In this case, they will be
presented in a list. The user may choose to continue or cancel.
3. If the user chooses to continue, the installer XPI file is downloaded. A progress bar is
displayed to the user during this process.
4. The file install.js is extracted from the install archive and executed. This script will call
install functions which will indicate which files from the archive should be installed.
5. Once the script is complete, the new package has been installed. If multiple packages
are being installed, their scripts will run in sequence.
Install Triggers
As indicated above, the install process is started by an install trigger. This involves the use of
the special global object InstallTrigger. It contains a number of methods which can be used to
start an installation. You can use this object in local or remote content, meaning that it is
suitable for a download from a Web site.
Let's create an example install trigger. This involves the use of the function InstallTrigger.install.
This function takes two arguments, the first is a list of packages to install, and the second is a
callback function which will be called when the installation is complete. Here is an example:
First, we define a callback function doneFn which will be called when the install is complete.
You can name the function whatever you like of course. This function has two arguments. The
first is the name of the package that was just installed. This is important if you are installing
multiple components. The second argument is a result code. If the result is 0, the installation
completed successfully. If the result is non-zero, an error occured and the value is an error
code. The function doneFn here just displays an alert box to the user.
Next, we create an array xpi which will hold the name (Calendar) and URL (calendar.xpi) of the
installer. You can add an additional similar such line for each package you wish to have
installed. Finally, we call the install function.
When this section of script is executed, the file calendar.xpi will be installed.
The installer XPI file is required to contain one file called install.js which is a JavaScript file
which is executed during the installation. The remaining files are the files to be installed. These
files will typically be placed inside a directory in the archive but they do not have to be. For
chrome files, they might be structured like the chrome directory.
Often, the only files placed in an XPI archive will be the install script (install.js) and a JAR file.
This JAR file contains all of the files used by your application. The components provided with
Mozilla are stored in this manner.
Because the XPI file is just a special ZIP file, you can create it and add files to it using a zip
utility.
For the find files dialog, we'll create a structure in the archive much like the following:
install.js
findfile
content
contents.rdf
findfile.xul
findfile.js
skin
contents.rdf
findfile.css
locale
contents.rdf
findfile.dtd
A directory has been added for each part of the package, the content, the skin and the locale.
The contents.rdf files have also been added because they will be needed to register the chrome
files.
Install Scripts
This section describes the install script.
You will usually want some form of control over your install process. For example, you may wish
to check versions of files and only install updated files, or perhaps you wish to apply patches to
existing files. The install script is even flexible enough to allow you to uninstall files. For this
reason, installers include an install script to handle the installation process.
The installer script must be called install.js and must be placed at the top level of the installer
archive. The script will contain JavaScript code which calls a number of install functions.
In an HTML document, or a XUL document, the window object is the root global object. That
means that you can call the methods of the window object with the qualifier before it, which
means that window.open(...) can simply be written open(...). In an install script, there is no
associated window, however the global object will be an Install object which contains a number
of functions to customize the install process. Some of the Install object's functions will be
described below.
1. Initialize the installation by specifying what package and version is being installed.
2. Use the Install functions to indicate what files and directories need to be installed. You
can also set files to be moved and deleted.
3. Start the process of installing the necessary files.
It is important to note that during step two, you only indicate which files should be installed and
any other operations you wish to have happen. No files get copied until step three. Because of
this, you can easily specify a number of files to be installed, come across some kind of error,
and abort the whole process without modifying the user's system.
Mozilla maintains a file which is a registry of all the components that are currently installed.
Components include new chrome packages, skins and plugins. When a new component is
installed, the registry gets updated. The registry also stores the set of files and version
information about the installed components. That way, it is easier to check if a version of your
component is already present and only update it if necessary.
The component registry works somewhat like the Windows registry does. It consists of a
hierarchy of keys and values. You don't need to know much about it to create XUL applications
unless you are creating your own XPCOM components.
What you do need to know for an installation is that the registry stores a set of information about
your application, such as the file list and versions. All of this information is stored in a key (and
within subkeys) that you provide in the installation script (in step 1 mentioned above).
/Author/Package Name
Replace the word Author with your name and replace the Package Name with the name of the
package that you are installing. For example:
/Xulplanet/Find Files
The first example is what we'll use for the find files dialog. The second is the key used for the
Personal Security Manager.
Install Initialization
The Install object has a function, initInstall which can be used to initialize for the installation. It
should be called at the beginning of your installation script. The syntax of this function is as
follows:
Example:
Next, we need to set the directory where the files will be installed. There are two ways to do
this. The simple method assigns an install directory and installs all files into it. The second
method allows you to assign a destination on a per-file (or directory) basis. The first method is
described below.
The function setPackageFolder assigns an installation directory. For the find files dialog, we will
install the files into the chrome directory. (We could actually put them anywhere though.) The
setPackageFolder takes one argument, the directory to install to. For maximum portability, you
can't specify a string name for the directory. Instead, you specify an identifier of a known
directory and get subdirectories of it. Thus, if your application needed to install some system
libraries, you don't need to know the name of those directories.
The directory identifiers are listed in the reference. For the chrome directory, the directory
identifier is 'Chrome'. The getFolder function can be used to get one of these special directories.
This function takes two arguments, the first is the identifier and the second is a subdirectory. For
example:
findDir = getFolder("Chrome","findfile");
setPackageFolder(findDir);
Here, we get findfile folder in the Chrome folder and pass it directly to the setPackageFolder
function. The second argument to getFolder is the subdirectory which we are going to install
into, which doesn't have to exist. You can leave this argument out entirely if you don't need one.
Next, you need to specify which files should be installed. This involves the use of two functions,
addDirectory and addFile. The addDirectory function tells the installer that a directory from the
XPI archive (and all of its contents) should be installed to a particular location. The addFile is
similar but for a single file.
Both the addDirectory and addFile functions have various forms. The simplest takes only one
argument, the directory from the installer to install to the assigned installation directory.
addDirectory ( dir );
addFile ( dir );
Example:
addDirectory("findfile");
The example above will specify that the findfile directory from the installer archive should be
installed. We can call these functions multiple times to install other files.
Next, we'll want to register the find files in the chrome system so that it can be used with a
chrome URL. This can be done with the registerChrome function. It takes two arguments, the
first is the type of chrome to register (content, skin or locale). The second is the directory
containing the contents.rdf file to register. Because the find files dialog contains content, a skin
file and a locale file, registerChrome will need to be called three times.
The DELAYED_CHROME flag is used to indicate that the chrome should be installed the next
time Mozilla is run.
Installation Completion
The addDirectory and addFile functions don't copy any files. They only state which files should
be installed. Similarly, registerChrome only states that chrome should be registered. To
complete the process and begin copying files, call the performInstall function. It takes no
arguments.
The final script for installing the find files component is shown below:
findDir = getFolder("Chrome","findfile");
setPackageFolder(findDir);
addDirectory("findfile");
performInstall();
The previous section described a basic installer. You may wish to perform some more elaborate
processing during the installation. For example, you may want to install a package only when
certain conditions are met, such as having a particular library installed.
In addition to the Install object, a File object is also available during an installation script. It
provides some functions which can be used to examine and modify files on disk. You can use
these to move, copy or delete files before or after the files are installed. For example, you might
want to make a backup of some files first.
The following code will make a copy of the file "/bin/grep" and put it in the directory "/main".
var binFolder=getFolder("file:///","bin");
var grep=getFolder(binFolder,"grep");
var mainFolder=getFolder("file:///","main");
File.copy(grep,mainFolder);
The first line will retrieve a reference to the /bin directory. The text 'file:///' is a special string
which means the root of the filesystem. From there, we get the file 'grep' which is contained
inside the 'bin' directory. If this file does not exist, an error will occur during the installation. Next,
we get the 'main' folder, again from the file system root. Finally, we call the File.copy function
which copies the source file to the destination.
Functions also exist to move, rename and execute files. Thus, you can move files that might
conflict with your package out of the way.
Handling Errors
You will likely want to handle errors gracefully. This will occur if a file or directory cannot be
found, there is insufficient disk space or for a number of other reasons.
You can use the getLastError function to determine whether an error occured. If it returns
SUCCESS, no error occured. Otherwise, the number will be an error code which indicates the
type of error that occured. You can call this function at any point during the installation script to
determine whether an error occured during the last operation.
If an error occurs, you will likely want to abort the installation. You may also want to display an
error message to the user. For example, you might put the following as the last section of your
script:
if (getLastError() == SUCCESS){
performInstall();
}
else {
cancelInstall();
}
Error codes that could be returned by getLastError are listed in the Mozilla source file
nsInstall.h. During installation, a log file is created that contains the operations that are
performed. It will also show any errors that occured. The log file can be found in the file
'install.log' in the Mozilla installation directory. A block of text will be added to this file for each
installation that occurs.
The logComment function can be used to write a string of text to the log file. It takes one
argument, the text to write.