XUL Tutorial

223
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. What is XUL and why was it created? 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. What kinds of user-interfaces can be made with XUL? XUL provides the ability to create most elements found in modern graphical interfaces. Some elements that can be created are: Input controls such as textboxes and checkboxes Toolbars with buttons or other content Menus on a menu bar or pop up menus Tabbed dialogs Trees for hierarchical or tabular information Keyboard shortcuts 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

Transcript of XUL Tutorial

Page 1: XUL Tutorial

XUL Tutorial

1. Introduction

IntroductionThis 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.

What is XUL and why was it created?

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.

What kinds of user-interfaces can be made with XUL?

XUL provides the ability to create most elements found in modern graphical interfaces. Some elements that can be created are:

• Input controls such as textboxes and checkboxes • Toolbars with buttons or other content • Menus on a menu bar or pop up menus • Tabbed dialogs • Trees for hierarchical or tabular information • Keyboard shortcuts

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

Page 2: XUL Tutorial

with your own data supplied in an RDF file.

There are several ways that XUL applications are created:

• 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.

What do I need to know to use this tutorial?

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.

Page 3: XUL Tutorial

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 StructureWe'll begin by looking at how the XUL is handled in Mozilla.

How XUL is Handled

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

Page 4: XUL Tutorial

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.

To summarize the points made above:

• 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

Page 5: XUL Tutorial

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.

The three types of chrome packages are:

• Content - Windows and scriptsThe declarations of the windows and the user interface elements contained within them. These are stored in XUL files, which have a xul extension. A content package can have

Page 6: XUL Tutorial

multiple XUL files, but the main window should have a filename that is the same as the package name. For example, the editor package will have a file within it called editor.xul. Scripts are placed in separate files alongside the XUL files.

• Skin - Style sheets, images and other theme specific filesStyle sheets describe details of the appearance of a window. They are stored separately from XUL files to facilitate modifying the skin (theme) of an application. Any images used are stored here also.

• Locale - Locale specific filesAll the text that is displayed within a window is stored separately. This way, a user can have a set for their own language.

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.

Page 7: XUL Tutorial

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.

Page 8: XUL Tutorial

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.

The Chrome URLThe following section will describe how to refer to XUL documents and other chrome files.

The Chrome URL

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.

The basic syntax of a chrome URL is as follows:

chrome://<package name>/<part>/<file.xul>

The text <package name> is the package name, such as messenger or editor. The <part> is

Page 9: XUL Tutorial

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.xulchrome://messenger/content/attach.jschrome://messenger/skin/icons/folder-inbox.pngchrome://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.

Page 10: XUL Tutorial

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 FilesIn 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:

content tests file:///C:/testfiles/

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:

content <packagename> <filepath>

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

Page 11: XUL Tutorial

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.

The browser.manifest file used by Firefox looks like this:

content branding jar:browser.jar!/content/branding/ xpcnativewrappers=yescontent browser jar:browser.jar!/content/browser/ xpcnativewrappers=yesoverlay chrome://global/content/viewSource.xul chrome://browser/content/viewSourceOverlay.xuloverlay chrome://global/content/viewPartialSource.xul chrome://browser/content/viewSourceOverlay.xuloverlay chrome://browser/content/pageInfo.xul chrome://pippki/content/PageInfoOverlay.xul

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.

Themes and Locales

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:

skin browser classic/1.0 jar:classic.jar!/skin/classic/browser/locale browser en-US jar:en-US.jar!/locale/browser/

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

Page 12: XUL Tutorial

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:

content findfiles file:///findfiles/content/skin findfiles classic/1.0 file:///findfiles/skin/locale findfiles en-US findfiles file:///findfiles/locale/

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.

For more information about creating extensions, see http://developer.mozilla.org/en/docs/Extensions. For more information about XULRunner, see http://developer.mozilla.org/en/docs/XULRunner

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

Page 13: XUL Tutorial

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.

Page 14: XUL Tutorial

• 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 WindowWe'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.

Creating 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.

Page 15: XUL Tutorial

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. <windowThis 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);

where the flags contains the flag "chrome".

Example:window.open("chrome://navigator/content/navigator.xul", "bmarks", "chrome,width=600,height=300");

Page 16: XUL Tutorial

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:

mozilla -chrome chrome://findfile/content/findfile.xul

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:

mozilla -chrome chrome://communicator/content/bookmarks/bookmarksManager.xul

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 ButtonsIn this section, we will look at how to add some simple buttons to a window.

Adding 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.

The button tag has the following syntax:

<button id="identifier" class="dialog" label="OK" image="images/image.jpg" disabled="true" accesskey="t"/>

The attributes are as follows, all of which are optional:

• id

Page 17: XUL Tutorial

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.

• classThe 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.

• labelThe label that will appear on the button. For example, OK or Cancel. If this is left out, no text appears.

• imageThe 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.

• disabledIf 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.

• accesskeyThis 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:

Example 2.2.1: Source View

<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.

<button id="find-button" label="Find"/>

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">

Page 18: XUL Tutorial

<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.

Adding Labels and ImagesThis section describes a way to add labels and images to a window. In addition, we look at how to include elements into groups.

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:

Example 2.3.1: Source View

<label value="This is some text"/>

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.

Example 2.3.2: Source View

<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.

Example 2.3.3: Source View

<label value="Click here:" control="open-button"/>

Page 19: XUL Tutorial

<button id="open-button" label="Open"/>

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 ControlsXUL has elements that are similar to the HTML form controls.

Text Entry Fields

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:

• idA unique identifier so that you can identify the textbox.

• classThe style class of the textbox.

• valueIf you want the textbox to have default text, supply it with the value attribute.

• disabledSet to true to have text entry disabled.

• typeYou can set this attribute to the special value password to create a textbox that hides

Page 20: XUL Tutorial

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:

Example 2.4.1: Source View

<label control="some-text" value="Enter some text"/><textbox id="some-text"/><label control="some-password" value="Enter a password"/><textbox id="some-password" type="password" maxlength="8"/>

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:

Example 2.4.2: Source View

<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.

<label value="Search for:" control="find-text"/><textbox id="find-text"/>

<button id="find-button" label="Find"/>

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.

Notice that the label and the text input field have now appeared in the window. The textbox is fully functional and you can type into it and select text. Note that the control attribute has been used so that the textbox is selected when the label is clicked.

Checkboxes and Radio Buttons

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.

<checkbox id="case-sensitive" checked="true" label="Case sensitive"/>

Page 21: XUL Tutorial

<radio id="orange" label="Orange"/><radio id="violet" selected="true" label="Violet"/><radio id="yellow" label="Yellow"/>

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.

Example 2.4.3: Source View

<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:

• labelThe label on the check box or radio button.

• disabledSet this to either true or false to disable or enable the check box or radio button.

• accesskeyThe shortcut key that can be used to select the element. The letter specified is usually drawn underlined in the label.

List ControlsXUL 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.

Example 2.5.1: Source View

<listbox> <listitem label="Butter Pecan"/>

Page 22: XUL Tutorial

<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.

The following example demonstrates these additional features:

Example 2.5.2: Source View

<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.

Multi-Column List Boxes

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:

Example 2.5.3: Source View

<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>

Page 23: XUL Tutorial

<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.

Here is the earlier example with a header row:

Example 2.5.4: Source View

<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>

In this example, the flex attribute is used to make the column flexible. This attribute will be described in a later section, but here it allows the column to fill all of the remaining space horizontally. You can resize the window to see that the column stretches as the window does. If you shrink the size horizontally, the labels on the cells will crop themselves automatically using an ellipsis. You can use the crop attribute on the cells or items set to the value none to disable the ellipsis.

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

Page 24: XUL Tutorial

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.

Its syntax is best described with the example below:

Example 2.5.5: Source View

<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.

To create an editable menulist, add the editable attribute as follows:

Example 2.5.6: Source View

<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 MetersIn this section, we'll look at creating progress meters.

Adding a Progress Meter

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:

Page 25: XUL Tutorial

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.

Determinate progress meter: Undeterminate progress meter:

The progress meter has the following syntax:

<progressmeter id="identifier" mode="determined" value="0%"/>

The attributes are as follows:

• idThe unique identifer of the progress meter

• modeThe 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.

• valueThe 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>

<progressmeter value="50%" style="margin: 4px;"/>

<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,

Page 26: XUL Tutorial

we only want the progress bar to be displayed while the search was occuring. A script will show and hide it as necessary.

Adding HTML ElementsNow that we've added some buttons, let's add some other elements.

Adding HTML Elements to a Window

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.

Here is an example as it might be added to the find file window:

<?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:input type="checkbox" value="true"/>

<html:table> <html:tr> <html:td>

Page 27: XUL Tutorial

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.

Example 2.7.1: Source View

<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.

Examples of HTML elements

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.

1. A dialog with a check box

Example 2.7.2: Source View

<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.

Page 28: XUL Tutorial

2. Text outside of HTML blocks

Example 2.7.3: Source View

<html:div> Would you like to save the following documents? <html:hr/></html:div> Expense Report 1What 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.

3. Invalid HTML elements

<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 SpacersIn 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.)

Page 29: XUL Tutorial

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"/>

<button id="find-button" label="Find"/>

More About Flexibility

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

Page 30: XUL Tutorial

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.

Let's look at some examples:

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"/>

Page 31: XUL Tutorial

<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.

More Button FeaturesIn this section, we will look at some additional features of buttons.

Adding an Image

You can add an image to a button by specifying a URL in the image attribute. The image is

Page 32: XUL Tutorial

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.

Example 2.9.1: Source View

<button label="Help" image="happy.png"/>

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.

Example 2.9.2: Source View

<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.

Positioning the Images

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.

Example 2.9.3: Source View

<button label="Left" image="happy.png"/><button label="Right" image="happy.png" dir="reverse"/><button label="Above" image="happy.png" orient="vertical"/><button label="Below" image="happy.png" orient="vertical" dir="reverse"/>

Page 33: XUL Tutorial

The example here shows all four types of alignment of buttons. Note that the two attributes are not specified when the default value can be used.

Buttons with Extra Content

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:

Example 2.9.4: Source View

<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.

Example 2.9.5: Source View

<button type="menu" label="Device"> <menupopup> <menuitem label="Printer"/> <menuitem label="Mouse"/> <menuitem label="Keyboard"/> </menupopup></button>

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

Page 34: XUL Tutorial

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.

3. The Box Model

The Box ModelIn this section, we'll look at how XUL handles layout.

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.

The basic syntax of a box is as follows:

<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.

Thus, the two lines below are equivalent:

<vbox>

<box orient="vertical">

The following example shows how to place three buttons vertically.

Example 3.1.1: Source View

Page 35: XUL Tutorial

<vbox> <button id="yes" label="Yes"/> <button id="no" label="No"/> <button id="maybe" label="Maybe"/></vbox>

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:

Example 3.1.2: Source View

<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>

Here four elements have been oriented vertically, two inner hbox tags and two button elements. Notice that only the elements that are direct descendants of the box are oriented vertically. The labels and textboxes are inside the inner hbox elements, so they are oriented according to those boxes, which are horizontal. You can see in the image that each label and textbox is oriented horizontally.

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.

Example 3.1.3: Source View

<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.

Page 36: XUL Tutorial

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.

The issue now is that the 'Password' label is too high. We should really use the grid element here to fix this which we'll learn about in a later section.

Boxes in the Find Files Dialog

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"/>

<button id="find-button" label="Find"/> <button id="cancel-button" label="Cancel"/> </hbox></vbox>

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 PositioningHere we'll look at controlling the position and size of an element.

Box Element Positioning

So far, we know how to position elements either horizontally or vertically inside a box. We will

Page 37: XUL Tutorial

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:

Example 3.2.1: Source View

<button label="OK" width="100" height="40"/>

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.

• widthThis specifies the width of the element.

• heightThis 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:

Example 3.2.2: Source View

<window orient="horizontal"

Page 38: XUL Tutorial

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.

Setting Minimum and Maximum Sizes

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:

• minwidthThis specifies the minimum width that the element can be.

• minheightThis specifies the minimum height that the element can be.

• maxwidthThis specifies the maximum width that the element can be.

• maxheightThis 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:

Page 39: XUL Tutorial

<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.

<textbox id="find-text" flex="1" style="min-width: 15em;"/>

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:

Example 3.2.3: Source View

<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

Page 40: XUL Tutorial

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:

• startThis positions elements at the left edge for horizontal boxes and at the top edge for vertical boxes. This is the default value.

• centerThis centers the child elements in the box.

• endThis 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.

We can change the earlier example to center the elements as follows:

Example 3.2.4: Source View

<box flex="1" pack="center"> <button label="Happy"/> <button label="Sad"/></box>

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.

• startThis aligns elements along the top edge for horizontal boxes and along the left edge for vertical boxes.

• centerThis centers the child elements in the box.

• endThis aligns elements along the bottom edge for horizontal boxes and along the right edge for vertical boxes.

• baselineThis aligns the elements so that the text lines up. This is only useful for horizontal boxes.

• stretchThis 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.

Page 41: XUL Tutorial

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.

Example 3.2.5: Source View

<?xml version="1.0"?><?xml-stylesheet href="chrome://global/skin/" type="text/css"?>

<window id="yesno" title="Question" orient="horizontal" xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">

<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.

Cropping Text and Buttons

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:

• leftThe text is cropped on its left side

• rightThe text is cropped on its right side

• centerThe text is cropped in the middle.

• noneThe 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

Page 42: XUL Tutorial

following shows this attribute in use:

Example 3.2.6: Source View

<button label="Push Me Please!" crop="right" flex="1"/>

Notice how the text on the button has had the right side of it cropped after the window is made smaller.

Box Model DetailsWe've seen a lot of features of the box model. Here, we'll find out some more details with some examples.

More Layout Details

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.

Example 3.3.1: Source View

<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.

Example 3.3.2: Source View

<hbox flex="1" align="top"> <button label="Left" style="min-width: 100px;" flex="1"/> <spacer flex="1"/> <button label="Right" style="min-width: 100px;" flex="1"/></hbox>

Page 43: XUL Tutorial

To achieve complicated layouts, you will generally need to add nested boxes, specify minimum and maximum sizes on some elements, and make certain elements flexible. The best interface is one that can be displayed at various sizes without problems. The box model may be difficult to understand without trying various various things out for yourself.

The following is an outline of both types of boxes:

• 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

Example 3.3.3: Source View

<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

Example 3.3.4: Source View

<hbox pack="center" align="center" flex="1"> <button label="Look at Me!"/> <button label="Push Me!"/></hbox>

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.

3. A Find Text Dialog

Example 3.3.5: Source View

Page 44: XUL Tutorial

<?xml version="1.0"?><?xml-stylesheet href="chrome://global/skin/" type="text/css"?>

<window id="findtext" title="Find Text" orient="horizontal" xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">

<vbox flex="3"> <label control="t1" value="Search Text:"/> <textbox id="t1" style="min-width: 100px;" flex="1"/> </vbox>

<vbox style="min-width: 150px;" flex="1" align="start"> <checkbox id="c1" label="Ignore Case"/> <spacer flex="1" style="max-height: 30px;"/> <button label="Find"/> </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.

GroupboxesThis 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

Page 45: XUL Tutorial

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.

The example below shows a simple groupbox:

Example 3.4.1: Source View

<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.

Example 3.4.2: Source View

<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>

In this example, a checkbox has been used as a caption. We might use a script to enable and disable the contents of the groupbox when the checkbox is checked and unchecked. The groupbox contains a horizontal box with a label and textbox. Both the textbox and groupbox have been made flexible so the textbox expands when the window is expanded. The additional checkbox appears below the textbox because of the vertical orientation of the groupbox. We'll add a groupbox to the find files dialog in the next section.

Page 46: XUL Tutorial

Radio Groups

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:

Example 3.4.3: Source View

<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.

Adding More ElementsWe'll conclude the discussion of boxes by adding some boxes to the find files dialog.

Adding some Additional Elements

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.

Page 47: XUL Tutorial

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.

<spacer style="height: 10px"/><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>

<spacer style="height: 10px" flex="1"/>

<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.

Let's change the box into a groupbox:

<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>

Page 48: XUL Tutorial

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.

4. More Layout Elements

Stacks and DecksThere may be need to display elements as a set of overlapping cards. The stack and deck elements can be used for this purpose.

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

Page 49: XUL Tutorial

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:

Example 4.1.1: Source View

<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:

Example 4.1.2: Source View

<stack style="background-color: #C0C0C0"> <description value="Disabled" style="color: white; padding-left: 1px; padding-top: 1px;"/> <description value="Disabled" style="color: grey;"/></stack>

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.

Page 50: XUL Tutorial

The following is an example of a deck:

Example 4.1.3: Source View

<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 PositioningThis section will describe how to position items in a stack.

Placement of Stack Children

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.

Example 4.2.1: Source View

<stack> <button label="Goblins" left="5" top="5"/> <button label="Trolls" left="60" top="20"/> <button label="Vampires" left="10" top="60"/></stack>

The stack here contains three elements, each positioned at the coordinates given by the left and top attributes. Here, all three children are buttons, but the elements do not have to be same type. They may be any element, including boxes and other stacks.

The size of a stack is determined by the positions of the child elements. It is always sized so that all of the child elements are visible. So if you set a

left attribute to 400, the stack will have a width around 400 pixels plus the width of the element. You can override this size with the various style properties such as width and max-width.

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

Page 51: XUL Tutorial

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.

TabboxesIt 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.

• tabboxThe outer box that contains the tabs along the top and the tab pages themselves.

• tabsThe inner box that contains the individual tabs. In other words, this is the row of tabs.

• tabA specific tab. Clicking on the tab brings the tab page to the front.

• tabpanelsThe container for the pages.

• tabpanelThe 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.

Shown below is the general syntax of a tabbox:

<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

Page 52: XUL Tutorial

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.

Let's look at an example:

Example 4.3.1: Source View

<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

Page 53: XUL Tutorial

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.

Adding Tabs to the Find Files Dialog

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>

<spacer style="height: 10px"/>

<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>

<spacer style="height: 10px"/> <textbox id="find-text" flex="1" style="min-width: 15em;"/>

</groupbox> </tabpanel>

<tabpanel id="optionspanel" orient="vertical"> <checkbox id="casecheck" label="Case Sensitive Search"/> <checkbox id="wordscheck" label="Match Entire Filename"/>

Page 54: XUL Tutorial

</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.

GridsXUL has a set of elements for creating tabular grids.

XUL Tabular Layout

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.

This should be easier to understand with an example.

Example 4.4.1: Source View

<grid flex="1">

Page 55: XUL Tutorial

<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:

Example 4.4.2: Source View

<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>

Notice in the image how the first column of elements containg the labels has only a single element in each row. The second column contains a box in its second row, which in turn contains two elements, a textbox and a button. You could add additional nested boxes or even another grid inside inside a single cell.

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.

Page 56: XUL Tutorial

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.

Example 4.4.3: Source View

<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:

Example 4.4.4: Source View

<grid flex="1"> <columns> <column flex="5"/> <column/> <column/> </columns> <rows> <row flex="10">

Page 57: XUL Tutorial

<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:

Example 4.4.5: Source View

<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 PanelsIn this section, we'll look at how to add panels that can display HTML pages or other XUL files.

Page 58: XUL Tutorial

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.

The following is an example of using an iframe:

Example 4.5.1: Source View

<toolbox> <toolbar id="nav-toolbar"> <toolbarbutton label="Back"/> <toolbarbutton label="Forward"/> <textbox id="urlfield"/> </toolbar></toolbox>

<iframe id="content-body" src="http://www.mozilla.org" flex="1"/>

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

Page 59: XUL Tutorial

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.

Here is an example browser:

Example 4.5.2: Source View

<browser src="http://www.mozilla.org" flex="1"/>

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:

<browser src="http://www.mozilla.org" type="content" flex="1"/>

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.

SplittersHere, we'll look at how to add splitters to a window.

Page 60: XUL Tutorial

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.

The syntax of a splitter is as follows:

<splitter id="identifier" state="open" collapse="before" resizebefore="closest" resizeafter="closest">

The attributes are as follows:

• idThe unique identifier of the splitter.

• stateIndicates 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.

• collapseThis 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.

• resizebeforeWhen 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.

• resizeafterWhen 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.

An example would be helpful here:

Page 61: XUL Tutorial

Example 4.6.1: Source View

<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>

Here, four iframes have been created and a splitter has been placed in-between the first and second one. The collapse has been set to a value of before, meaning that if the splitter grippy is clicked on, the first frame would disappear and the splitter and the remaining frames would shuffle to the left. The splitter grippy is drawn centered inside the splitter.

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.

The 4 panels with the splitter in a collapsed state:

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

Page 62: XUL Tutorial

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:

Page 63: XUL Tutorial

Collapsed State:

Scroll BarsNow, let's find out to add scroll bars to a window.

Adding Scroll Bars

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.

The syntax of a scroll bar is as follows:

<scrollbar id="identifier" orient="horizontal" curpos="20" maxpos="100" increment="1" pageincrement="10"/>

The attributes are as follows:

• idThe unique identifer of the scroll bar

• orientThis 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.

• curposThis 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.

Page 64: XUL Tutorial

• maxposThis 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.

• incrementThe 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.

• pageincrementThe 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.

5. Toolbars and Menus

ToolbarsA 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.

Page 65: XUL Tutorial

Here is a example of a simple toolbar inside a toolbox.

Example 5.1.1: Source View

<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.

• toolboxA box that contains toolbars.

• toolbarA single toolbar that contains toolbar items such as buttons. Toolbars can be collapsed using the grippy on its left or top side.

• toolbarbuttonA button on a toolbar, which has all the same features of a regular button but is usually drawn differently.

• toolbargrippyThis 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.

You do not have to put toolbar elements 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.

A toolbox with three toolbars in it:

The same set of toolbars but two have been collapsed.

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.

Page 66: XUL Tutorial

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.

Simple Menu BarsIn this section, we'll see how to create a menu bar with menus on it.

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:

• menubarThe container for the row of menus.

• menuDespite 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.

• menupopupThe popup box that appears when you click on the menu title. This box contains the list of menu commands.

• menuitemAn individual command on a menu. This would be placed in a menupopup.

• menuseparator

Page 67: XUL Tutorial

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.

Here is an example of a simple menu bar:

Example 5.2.1: Source View

<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>

Here, a simple menu bar is created using the menubar element. It will create a row for menus to be placed on. Two menus, File and Edit have been created here. The menu element creates the title at the top of the menu, which appears on the menu bar. The popups are created using the menupopup element. It will pop up when the user clicks on the parent menu title. The size of the popup will be large enough to fit the commands inside it. The commands themselves are created using the menuitem element. Each one represents a single command on the menu popup.

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 is normally placed on a menubar, although it does not have to be. However, it will be given a different look. The image here shows what the earlier example would look like without the menu bar.

The menu element works much like the button element. It accepts some of the same attributes plus some additional ones:

• idThe unique identifier of the menu title button.

Page 68: XUL Tutorial

• labelThe text to appear on the menu, such as File or Edit.

• disabledThis 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.

• accesskeyThis 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.

• idThe unique identifier of the menu title button.

• labelThe text to appear on the menu item, such as Open or Save.

• disabledThis 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.

• accesskeyThis 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.

• acceltextThis 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.

More Menu FeaturesIn this section, we'll look at creating submenus and checked menus

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:

Page 69: XUL Tutorial

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>

Adding a Find Files Menu

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>

Page 70: XUL Tutorial

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.

Adding Checkmarks to Menus

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.

Example 5.3.2: Source View

<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:

Example 5.3.3: Source View

Page 71: XUL Tutorial

<toolbox> <menubar id="planets-menubar"> <menu id="planet-menu" label="Planet"> <menupopup> <menuitem id="jupiter" label="Jupiter" type="radio" name="ringed"/> <menuitem id="saturn" label="Saturn" type="radio" name="ringed"/> <menuitem id="uranus" label="Uranus" type="radio" name="ringed"/> <menuitem id="earth" label="Earth" type="radio" name="inhabited"/> </menupopup> </menu> </menubar></toolbox>

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 MenusIn 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.

Creating a Popup Menu

XUL has three different types of popups, described below. The main difference is the way in which they appear.

• Plain PopupsThe 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 PopupsThe 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.

• TooltipsA 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,

Page 72: XUL Tutorial

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:

Example 5.4.1: Source View

<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,

Page 73: XUL Tutorial

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.

Example 5.4.2: Source View

<button label="Save" tooltiptext="Click here to save your stuff"/>

<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>

<button label="More" tooltip="moretip"/>

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_startThe 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_endThe popup appears below the element with the right edges of the element and the popup

Page 74: XUL Tutorial

window aligned. • before_start

The popup appears above the element with the left edges of the element and the popup window aligned.

• before_endThe popup appears above the element with the right edges of the element and the popup window aligned.

• end_afterThe popup appears to the right of the element with the bottom edges of the element and the popup window aligned.

• end_beforeThe popup appears to the right of the element with the top edges of the element and the popup window aligned.

• start_afterThe popup appears to the left of the element with the bottom edges of the element and the popup window aligned.

• start_beforeThe popup appears to the left of the element with the top edges of the element and the popup window aligned.

• overlapThe popup appears on top of the element.

• at_pointerThe popup appears at the mouse pointer position.

• after_pointerThe 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:

Example 5.4.3: Source View

<popupset> <popup id="backpopup" position="after_start"> <menuitem label="Page 1"/> <menuitem label="Page 2"/> </popup></popupset>

<button label="Pop Me Up" popup="backpopup"/>

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">

Page 75: XUL Tutorial

.

.

.

<tabpanel id="searchpanel" orient="vertical" context="editpopup">

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 MenusThis section will describe scrolling menus and how to use the mechanism with other elements.

Creating a Large Menu

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.

This behavior is automatic. You do not have to do anything in order to get scrolling menus. It will apply to menus on menubars, in popups or menulists. It is implemented using an arrowscrollbox element. This element can be used to create a scrolling box with arrows.

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):

Example 5.5.1: Source View

<arrowscrollbox orient="vertical" flex="1"> <button label="Red"/> <button label="Blue"/> <button label="Green"/> <button label="Yellow"/> <button label="Orange"/> <button label="Silver"/> <button label="Lavender"/> <button label="Gold"/> <button label="Turquoise"/> <button label="Peach"/> <button label="Maroon"/> <button label="Black"/></arrowscrollbox>

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

Page 76: XUL Tutorial

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.

The arrowscrollbox is mainly useful in menus and popups however.

6. Events and Scripts

Adding Event HandlersThe find files dialog so far looks quite good. We haven't cleaned it up much but we have created a simple user interface easily. Next, we will show how to add scripts to it.

s

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.

Page 77: XUL Tutorial

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

Page 78: XUL Tutorial

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:

Example 6.1.1: Source View

<button label="OK" oncommand="alert('Button was 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.

Example 6.1.2: Source View

<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.

<menuitem label="Close" accesskey="c" oncommand="window.close();"/>... <button id="cancel-button" label="Cancel" oncommand="window.close();"/>

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.

Page 79: XUL Tutorial

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:

Example 6.1.3: Source View

<button id="okbutton" label="OK"/>

<script>function buttonPressed(event){ alert('Button was pressed!');}

var button = document.getElementById("okbutton");button.addEventListener('command', buttonPressed, true);</script>

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.

More Event HandlersIn this section, the event object is examined and additional events are described.

The Event Object

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.

Example 6.2.1: Source View

<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

Page 80: XUL Tutorial

the events. To stop event propagation, call the event object's stopPropagation method, as in the following example.

Example 6.2.2: Source View

<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();}

var button = document.getElementById("okbutton");button.addEventListener('command',buttonPressed,true);

var outerbox = document.getElementById("outerbox");outerbox.addEventListener('command',boxPressed,true);</script>

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.

Example 6.2.3: Source View

<button label="Types" type="menu"> <menupopup onpopupshowing="event.preventDefault();"> <menuitem label="Glass"/> <menuitem label="Plastic"/> </menupopup></button>

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.

Page 81: XUL Tutorial

Mouse Events

There are several events which can be used to handle mouse specific actions, listed in the following table:

click Called when the mouse is pressed and released on an element.

dblclick Called when the a mouse button is double clicked.

mousedown Called when a mouse button is pressed down on an element. The event handler will be called as soon as a mouse button is pressed, even if it hasn't been released yet.

mouseup Called when a mouse button is released on an element

mouseover

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 you shouldn't do it with an event. You might, however, want to display some help text on a status bar.

mousemove Called when the mouse pointer is moved while over an element. The event may be called many times as the user moves the mouse so you should avoid performing lengthy tasks from this handler.

mouseout Called when the mouse pointer is moved off of an element. You might then 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:

Example 6.2.4: Source View

Page 82: XUL Tutorial

<script>

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.

var element = event.target;var elementX = event.clientX - element.boxObject.x;var elementY = event.clientY - element.boxObject.y;

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 ShortcutsYou could use keyboard event handlers to respond to the keyboard. However, it would be tedious to do that for every button and menu item.

Page 83: XUL Tutorial

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.

Example 6.3.1: Source View

<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.

• altThe user must press the Alt key.

• controlThe user must press the Control key.

• metaThe user must press the Meta key. This is the Command key on the Macintosh.

• shiftThe user must press the Shift key.

• accelThe 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.

Page 84: XUL Tutorial

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.

Here are some additional examples:

<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_CANCEL VK_BACK VK_TAB VK_CLEAR

VK_RETURN VK_ENTER VK_SHIFT VK_CONTROL

VK_ALT VK_PAUSE VK_CAPS_LOCK VK_ESCAPE

VK_SPACE VK_PAGE_UP VK_PAGE_DOWN VK_END

VK_HOME VK_LEFT VK_UP VK_RIGHT

VK_DOWN VK_PRINTSCREEN VK_INSERT VK_DELETE

VK_0 VK_1 VK_2 VK_3

VK_4 VK_5 VK_6 VK_7

VK_8 VK_9 VK_SEMICOLON VK_EQUALS

VK_A VK_B VK_C VK_D

VK_E VK_F VK_G VK_H

VK_I VK_J VK_K VK_L

VK_M VK_N VK_O VK_P

VK_Q VK_R VK_S VK_T

VK_U VK_V VK_W VK_X

Page 85: XUL Tutorial

VK_Y VK_Z VK_NUMPAD0 VK_NUMPAD1

VK_NUMPAD2 VK_NUMPAD3 VK_NUMPAD4 VK_NUMPAD5

VK_NUMPAD6 VK_NUMPAD7 VK_NUMPAD8 VK_NUMPAD9

VK_MULTIPLY VK_ADD VK_SEPARATOR VK_SUBTRACT

VK_DECIMAL VK_DIVIDE VK_F1 VK_F2

VK_F3 VK_F4 VK_F5 VK_F6

VK_F7 VK_F8 VK_F9 VK_F10

VK_F11 VK_F12 VK_F13 VK_F14

VK_F15 VK_F16 VK_F17 VK_F18

VK_F19 VK_F20 VK_F21 VK_F22

VK_F23 VK_F24 VK_NUM_LOCK VK_SCROLL_LOCK

VK_COMMA VK_PERIOD VK_SLASH VK_BACK_QUOTE

VK_OPEN_BRACKET VK_BACK_SLASH VK_CLOSE_BRACKET VK_QUOTE

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>

The example below demonstrates some more keyboard shortcuts:

<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).

Page 86: XUL Tutorial

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.

Example 6.3.2: Source View

<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.

Page 87: XUL Tutorial

<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:

keypress Called when a key is pressed and released when an element has the focus. You might use this to check for allowed characters in a field.

keydown Called when a key is pressed down while an element has the focus. Note that the 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.

Focus and SelectionThe section will describe how to handle the focus and selection of elements.

Page 88: XUL Tutorial

Focused Elements

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:

Example 6.4.1: Source View

<button label="Button 1" tabindex="2"/><button label="Button 2" tabindex="1"/><button label="Button 3" tabindex="3"/>

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.

Example 6.4.2: Source View

<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

Page 89: XUL Tutorial

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.

Example 6.4.3: Source View

<window id="focus-example" title="Focus Example" onload="init();" xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">

<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"/>

<label id="focused" value="-No focus-"/>

</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.

Example 6.4.4: Source View

<label id="focused" style="-moz-user-focus: normal;" onkeypress="alert('Label Focused');" value="Focus Me"/>

The style property is set to normal. You can also set it to ignore to turn off the focus for an

Page 90: XUL Tutorial

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:

Example 6.4.5: Source View

<textbox id="addr"/>

<button label="Focus" oncommand="document.getElementById('addr').focus()"/>

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.

Handling Text Changes

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

Page 91: XUL Tutorial

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.

CommandsA 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:

Example 6.5.1: Source View

<command id="cmd_openhelp" oncommand="alert('Help!');"/>

Page 92: XUL Tutorial

<button label="Help" command="cmd_openhelp"/>

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.

Example 6.5.2: Source View

<command id="cmd_openhelp" oncommand="alert('Help');"/><button label="Help" command="cmd_openhelp"/><button label="More Help" command="cmd_openhelp"/>

<button label="Disable" oncommand="document.getElementById('cmd_openhelp').setAttribute('disabled','true');"/><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.

Page 93: XUL Tutorial

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:

• supportsCommand (command): this method should return true if the controller supports a command. If you return false, the command is not handled and command dispatcher will look for another controller. A single controller may support multiple commands.

• isCommandEnabled (command): this method should return true if the command is enabled, or false if it is disabled. Corresponding buttons will be disabled automatically.

• doCommand (command): execute the command. This is where you would put the code to handle the command.

• onEvent (event): this method handles an event.

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.

<window id="controller-example" title="Controller Example" onload="init();" xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">

<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);

Page 94: XUL Tutorial

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 CommandsIn this section, we will look at how to update commands.

Page 95: XUL Tutorial

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:

var controller = document.commandDispatcher.getControllerForCommand("cmd_paste");if (controller && controller.isCommandEnabled("cmd_paste")) controller.doCommand(command);}

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"/>

<command id="cmd_paste" oncommand="goDoCommand('cmd_paste');"/><button label="Paste" command="cmd_paste"/>

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 simple command updater looks like this:

Page 96: XUL Tutorial

<commandset id="updatePasteItem" commandupdater="true" events="focus" oncommandupdate="goUpdateCommand('cmd_paste');"/>

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.

• focus: occurs when the focused element changes. • select: occurs when the selected text changes. • undo: occurs when the undo buffer changes. • clipboard: occurs when the contents of the clipboard changes.

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()"/>

Broadcasters and ObserversThere may be times when you want several elements to respond to events or changes of state easily. To do this, we can use broadcasters.

Page 97: XUL Tutorial

Command Attribute Forwarding

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.

Example 6.7.1: Source View

<command id="my_command" label="Open"/>

<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

Page 98: XUL Tutorial

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:

<button id="offline_button" observes="isOffline"/>

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:

<broadcaster id="offline_command" label="Offline" accesskey="f"/>

<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.

The Observes Element

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:

Example 6.7.2: Source View

<broadcasterset>

Page 99: XUL Tutorial

<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.

Example 6.7.3: Source View

<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.

Page 100: XUL Tutorial

7. Document Object Model

Document Object ModelThe Document Object Model (DOM) can be used with XUL elements to get information about them or modify them.

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:

Page 101: XUL Tutorial

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:

var state = document.getElementById('casecheck').checked;

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.

<tree id="results" hidden="true" flex="1"> . . .<splitter id="splitbar" resizeafter="grow" hidden="true"/>

<hbox>

Page 102: XUL Tutorial

<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.

XUL Element DOM

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

Page 103: XUL Tutorial

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:

var box = document.getElementById('somebox');var flex = box.getAttribute("flex");

var box2 = document.getElementById('anotherbox');box2.setAttribute("flex", "2");

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.

var box = document.getElementById('somebox');var flex = box.flex;

var box2 = document.getElementById('anotherbox');box2.flex = 2;

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

Page 104: XUL Tutorial

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.

Navigating the DOM

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:

• firstChild: reference to the first child node of an element • lastChild: reference to the last child node of an element • childNodes: holds a list of the children of an element • parentNode: reference to the parent of an node • nextSibling: reference to the next sibling in sequence • previousSibling: reference to the previous sibling in sequence

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:

var childNodes = document.documentElement.childNodes;for (var i = 0; i < childNodes.length; i++) { var child = childNodes[i]; // do something with child}

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.

Modifying a XUL InterfaceThe DOM provides various functions to modify the document.

Page 105: XUL Tutorial

Creating New Elements

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:

Example 7.2.1: Source View

<script>function addButton(){ var aBox = document.getElementById("aBox");

var button = document.createElement("button"); button.setAttribute("label","A Button"); aBox.appendChild(button);}</script>

<box id="aBox" width="200"> <button label="Add" oncommand="addButton();"/></box>

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

Page 106: XUL Tutorial

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:

Example 7.2.2: Source View

<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.

Manipulating Basic Elements

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:

Example 7.2.3: Source View

<button label="Hello" oncommand="this.label = 'Goodbye';"/>

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.

Example 7.2.4: Source View

<button label="Add" oncommand="this.nextSibling.value += '1';"/><textbox/>

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:

Example 7.2.5: Source View

<button label="Hello" oncommand="alert(this.label);"/>

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

Page 107: XUL Tutorial

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.

Example 7.2.6: Source View

<button label="Change" oncommand="this.nextSibling.checked = !this.nextSibling.checked;"/><checkbox label="Check for messages"/>

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.

Example 7.2.7: Source View

<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>

<radiogroup id="group" onselect="updateState();"> <radio label="Random name" selected="true"/> <hbox> <radio label="Specify a name:"/> <textbox id="name" value="Jim" disabled="true"/> </hbox></radiogroup>

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 ListsThe 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.

Page 108: XUL Tutorial

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:

Example 7.3.1: Source View

<script>function addItem(){ document.getElementById('thelist').appendItem("Thursday", "thu");}</script>

<listbox id="thelist"/>

<button label="Add" oncommand="addItem();"/>

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:

list.insertItemAt(3, "Thursday", "thu");list.removeItemAt(3);

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

The nsIDOMXULSelectControlElement interface provides two additonal properties, selectedIndex and selectedItem. The former returns the index of the selected item while the latter returns the selected element. For instance the selectedItem of a menulist will be the menuitem that is selected. If no item is selected, selectedIndex will return -1, while selectedItem will return null.

These two properties are commonly inspected during a select event, as in the following example:

Example 7.3.2: Source View

<listbox id="thelist" onselect="alert(this.selectedItem.label);"> <listitem label="Short"/> <listitem label="Medium"/> <listitem label="Tall"/></listbox>

Page 109: XUL Tutorial

The select event is fired for a listbox when an item in the list is selected. The select handler here displays an alert containing the label of the selected item in the list. Since the select event fired we can assume that an item is selected. In other cases, you may wish to check to ensure that selectedItem is not null before continuing.

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.

Example 7.3.3: Source View

<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>

Listboxes also support multiple selection and the functions of the nsIDOMXULMultiSelectControlElement interface. This interface provides a number of functions specifically for handling multiple selection. For example the selectedItems property holds a list of the items that are selected, while the selectedCount property holds the number of items selected. Typically, you will use these properties to iterate over the list and perform some operation for each item. Be careful when iterating over the selected items; if you modify the items in the list while iterating, the list will change and the selection properties may return different values. This is one reason why it is useful to manipulate the list by the item rather than by index.

The following example shows a method of deleting the selected items properly:

Example 7.3.4: Source View

<script>function deleteSelection()

Page 110: XUL Tutorial

{ var list = document.getElementById('thelist'); var count = list.selectedCount; while (count--){ var item = list.selectedItems[0]; list.removeItemAt(list.getIndexOfItem(item)); }}</script>

<button label="Delete" oncommand="deleteSelection();"/>

<listbox id="thelist" seltype="multiple"> <listitem label="Cheddar"/> <listitem label="Cheshire"/> <listitem label="Edam"/> <listitem label="Gouda"/> <listitem label="Havartie"/></listbox>

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.

The nsIDOMXULMultiSelectControlElement interface also provides methods for modifying the selected items. For instance, the addItemToSelection function adds a new item to the set of selected items, without clearing the existing selection. The removeItemFromSelection function removes a single item from the selection.

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:

Example 7.3.5: Source View

<button label="scrollToIndex" oncommand="document.getElementById('thelist').scrollToIndex(4);"/><button label="ensureIndexIsVisible" oncommand="document.getElementById('thelist').ensureIndexIsVisible(4);"/>

<listbox id="thelist" rows="5"> <listitem label="1"/> <listitem label="2"/> <listitem label="3"/> <listitem label="4"/> <listitem label="5"/> <listitem label="6"/> <listitem label="7"/> <listitem label="8"/> <listitem label="9"/>

Page 111: XUL Tutorial

<listitem label="10"/> <listitem label="11"/> <listitem label="12"/></listbox>

Box ObjectsThis section describes the box object, which holds display and layout related information about a XUL box as well as some details about XUL layout.

About Mozilla 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

Page 112: XUL Tutorial

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.

Example 7.4.1: Source View

<button label="Click Me" oncommand="alert('The width is ' + this.boxObject.width);"/>

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.

Example 7.4.2: Source View

Page 113: XUL Tutorial

<script>function showPositionAndSize(){ var labelbox = document.getElementById('thelabel').boxObject;

alert("Position is (" + labelbox.x + "," + labelbox.y + ") and size is (" + labelbox.width + "," + labelbox.height + ")");}</script>

<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.

XUL Element Ordering

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.

Example 7.4.3: Source View

<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-

Page 114: XUL Tutorial

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.

Example 7.4.4: Source View

<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:

Example 7.4.5: Source View

<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 InterfacesIn this section, we'll take a brief look at XPCOM (Cross-platform Component Object Model), which is the Object system that Mozilla uses.

Page 115: XUL Tutorial

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.

An interface in Mozilla is a definition of a set of functionality that could be implemented by components. Components are what implement the code in Mozilla that does things. Each component implements the functionality as described by interfaces. A single component might implement multiple interfaces. And multiple components might implement the same interface.

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.

Page 116: XUL Tutorial

Creating XPCOM Objects

There are three steps to calling an XPCOM component.

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:

var aFile = Components.classes["@mozilla.org/file/local;1"].createInstance();

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:

var aFile = Components.classes["@mozilla.org/file/local;1"].createInstance();if (aFile) aFile.QueryInterface(Components.interfaces.nsILocalFile);

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:

Page 117: XUL Tutorial

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:

var aFile = Components.classes["@mozilla.org/file/local;1"].createInstance();if (aFile instanceof Components.interfaces.nsILocalFile){ // do something}

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.

Calling the Functions of an Interface

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.

initWithPath This method is used to initialize the path and filename for the nsILocalFile. The first parameter should be the file path, such as '/usr/local/mozilla'

leafName The filename without the directory part

fileSize The size of the file

isDirectory() Returns true if the nsILocalFile represents a directory

remove(recursive) Deletes a file. If the recursive parameter is true, a directory and all of its files and subdirectories will also be deleted.

Page 118: XUL Tutorial

copyTo(directory,newname)

Copies a file to another directory, optionally renaming the file. The directory should be a nsILocalFile holding the directory to copy the file to.

moveTo(directory,newname)

Moves a file to another directory, or renames a file. The directory should 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:

var aFile = Components.classes["@mozilla.org/file/local;1"].createInstance();if (aFile instanceof Components.interfaces.nsILocalFile){ aFile.initWithPath("/mozilla/testfile.txt"); aFile.remove(false);}

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;

// get a component for the directory to copy to var aDir = Components.classes["@mozilla.org/file/local;1"] .createInstance(Components.interfaces.nsILocalFile); if (!aDir) return false;

// next, assign URLs to the file components aFile.initWithPath(sourcefile); aDir.initWithPath(destdir);

// finally, copy the file, without renaming it aFile.copyTo(aDir,null);}

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:

Page 119: XUL Tutorial

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);

First, the component "@mozilla.org/browser/bookmarks-service;1" is retrieved and its service is placed in the variable bmarks. We use QueryInterface to get the nsIBookmarksService interface. The function addBookmarkImmediately provided by this interface can be used to add bookmarks. The first two parameters to this function are the bookmark's URL and its title. The third parameter is the bookmark type which will normally be 0 and the last parameter is the character encoding of the document being bookmarked, which may be null.

XPCOM ExamplesThis 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:

Example 7.6.1: Source

<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:

var wmdata = Components.classes["@mozilla.org/rdf/datasource;1?name=window-mediator"].getService();wmdata.QueryInterface(Components.interfaces.nsIWindowDataSource);

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.

The nsIWindowDataSource interface has a function getWindowForResource, which can be used to get the window given a resource. In the earlier example, we generated the list of windows and added it to a menu via a template. The template generates an id attribute on each

Page 120: XUL Tutorial

menuitem element. The value of this attribute can be used as the resource. That means that in order to switch the window focus, we need to do the following:

1. Determine the element that the user clicked on. 2. Get the value of the id attribute from the element. 3. Pass this value to getWindowForResource to get a window object. 4. Switch the focus to this window.

The example below shows how we might do this:

<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);

var resource = elem.getAttribute('id'); switchwindow = mediator.getWindowForResource(resource);

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();

Page 121: XUL Tutorial

var cookieManager = Components.classes["@mozilla.org/cookiemanager;1"] .getService(Components.interfaces.nsICookieManager);

var iter = cookieManager.enumerator; while (iter.hasMoreElements()){ var cookie = iter.getNext(); if (cookie instanceof Components.interfaces.nsICookie){ if (cookie.host == "www.mozillazine.org") menu.appendItem(cookie.name,cookie.value); } }}</script>

<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

TreesXUL 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.

Page 122: XUL Tutorial

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.

• treeThis is the outer element of a tree.

• treecolsThis element is a placeholder for a set of treecol elements.

• treecolThis 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

Page 123: XUL Tutorial

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.

• treechildrenThis contains the main body of the tree where the individual rows of data will be displayed.

Here is an example of a tree with two columns:

Example 8.1.1: Source View

<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.

The Content Tree View

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:

• treeitemThis 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.

• treerowA single row in the tree, which should be placed inside a treeitem tag.

Page 124: XUL Tutorial

• treecellA 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.

Example 8.1.2: Source View

<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.

Page 125: XUL Tutorial

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>

<splitter collapse="before" resizeafter="grow"/>

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

Page 126: XUL Tutorial

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.

More Tree FeaturesHere, we'll see more features of trees.

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.

The following is a simple example:

Example 8.2.1: Source View

<tree rows="6"> <treecols>

Page 127: XUL Tutorial

<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>

This has created a hierarchical tree. As can be seen in the image, a small plus or minus sign (often called a twisty) has appeared next to the first row, indicating that it contains child rows. By double-clicking the row, the user can open and close the list. The child rows are indented. Notice how the Guys row only needs one column as it is a placeholder item for its children.

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.

More about Tree Columns

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

Page 128: XUL Tutorial

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:

Example 8.2.2: Source View

<tree enableColumnDrag="true" flex="1"> <treecols> <treecol id="runner" label="Runner" flex="2" persist="width ordinal hidden"/> <splitter class="tree-splitter"/> <treecol id="city" label="Home City" flex="2" persist="width ordinal hidden"/> <splitter class="tree-splitter"/> <treecol id="starttime" label="Start Time" flex="1" persist="width ordinal hidden"/> <splitter class="tree-splitter"/> <treecol id="endtime" label="End Time" flex="1" persist="width ordinal hidden"/> </treecols>

<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 SelectionThe section will describe how to get and set the selected items in a tree.

Getting the Selected Tree Items

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

Page 129: XUL Tutorial

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 syntax of the onselect event handler is shown below.

<tree id="treeset" onselect="alert('You selected something!');">

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.

In the image to the right, there are eight rows displayed, of which two are selected. The first selected row has an index of 4 and the second has an index of 7. The rows that are not displayed are not included in the index count.

For trees that allow multiple selection, getting the list of selected rows is a bit more complicated. The tree's view has a selection property which holds information about the selected rows. This selection will be a TreeSelection object. The view doesn't need to implement this object itself, the tree will assign a selection object to the view's selection property when the view is attached to a tree. From the tree, you can get the selection using the tree's view property and then retrieve the view's selection property. You can use the methods of the selection object to retrieve the set of selected items or modify the selection.

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:

var start = new Object();var end = new Object();var numRanges = tree.view.selection.getRangeCount();

for (var t=0; t<numRanges; t++){ tree.view.selection.getRangeAt(t,start,end); for (var v=start.value; v<=end.value; v++){ alert("Item "+v+" is selected."); }}

Page 130: XUL Tutorial

We create two objects called 'start' and 'end'. Then, we iterate over the set of ranges, the number of which is returned by the getRangeCount function. The getRangeAt function is called passing the range index and the start and end objects. This function will fill in the start and end indicies by assigning them to the 'value' property. So if the first range is from the third item to the seventh item, 'start.value' will be 2 (remember that indices start with 0, so we subtract one.) and 'end.value' will be 6. An alert is displayed for each index.

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));

Modifying the Tree Selection

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);

Custom Tree ViewsThe tree view holds the data to be displayed in the tree,

Creating a Custom View

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.

Page 131: XUL Tutorial

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:

<tree id="my-tree" flex="1"> <treecols> <treecol id="namecol" label="Name" flex="1"/> <treecol id="datecol" label="Date" flex="1"/> </treecols> <treechildren/></tree>

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.

• rowCountThis 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

Page 132: XUL Tutorial

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.

Example 8.4.1: Source

<?xml version="1.0"?>

<?xml-stylesheet href="chrome://global/skin/" type="text/css"?>

<window title="Tree Example" id="tree-window" xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" onload="setView();">

<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>

<tree id="my-tree" flex="1"> <treecols> <treecol id="namecol" label="Name" flex="1"/> <treecol id="datecol" label="Date" flex="1"/> </treecols> <treechildren/></tree>

</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.

Page 133: XUL Tutorial

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.

Tree View DetailsThis section will describe some more features of tree views.

Creating a Hierarchical Custom View

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

Page 134: XUL Tutorial

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.

Here is a review of the methods needed to implement hierarchical views:

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">

<tree id="elementList" flex="1"> <treecols> <treecol id="element" label="Element" primary="true" flex="1"/> </treecols> <treechildren/></tree>

</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],

Page 135: XUL Tutorial

["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,

get rowCount() { return this.visibleData.length; }, setTree: function(treeBox) { this.treeBox = treeBox; }, getCellText: function(idx, column) { return this.visibleData[idx][0]; }, isContainer: function(idx) { return this.visibleData[idx][1]; }, isContainerOpen: function(idx) { return this.visibleData[idx][2]; }, isContainerEmpty: function(idx) { return false; }, isSeparator: function(idx) { return false; }, isSorted: function() { return false; }, isEditable: function(idx, column) { return false; },

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; },

Page 136: XUL Tutorial

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.

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; } },

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;

var thisLevel = this.getLevel(idx); var deletecount = 0; for (var t = idx + 1; t < this.visibleData.length; t++) { if (this.getLevel(t) > thisLevel) deletecount++; else break; } if (deletecount) { this.visibleData.splice(idx + 1, deletecount); this.treeBox.rowCountChanged(idx + 1, -deletecount); } } else { item[2] = true;

var label = this.visibleData[idx][0]; var toinsert = this.childData[label]; for (var i = 0; i < toinsert.length; i++) { this.visibleData.splice(idx + i + 1, 0, [toinsert[i], false]); } this.treeBox.rowCountChanged(idx + 1, toinsert.length); } },

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;

var label = this.visibleData[idx][0]; var toinsert = this.childData[label]; for (var i = 0; i < toinsert.length; i++) { this.visibleData.splice(idx + i + 1, 0, [toinsert[i], false]); }

Page 137: XUL Tutorial

this.treeBox.rowCountChanged(idx + 1, toinsert.length);

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.

The following code is used to delete rows when a row is closed.

item[2] = false;

var thisLevel = this.getLevel(idx); var deletecount = 0; for (var t = idx + 1; t < this.visibleData.length; t++) { if (this.getLevel(t) > thisLevel) deletecount++; else break; } if (deletecount) { this.visibleData.splice(idx + 1, deletecount); this.treeBox.rowCountChanged(idx + 1, -deletecount); }

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">

<tree id="elementList" flex="1"> <treecols> <treecol id="element" label="Element" primary="true" flex="1"/>

Page 138: XUL Tutorial

</treecols> <treechildren/></tree>

<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,

get rowCount() { return this.visibleData.length; }, setTree: function(treeBox) { this.treeBox = treeBox; }, getCellText: function(idx, column) { return this.visibleData[idx][0]; }, isContainer: function(idx) { return this.visibleData[idx][1]; }, isContainerOpen: function(idx) { return this.visibleData[idx][2]; }, isContainerEmpty: function(idx) { return false; }, isSeparator: function(idx) { return false; }, isSorted: function() { return false; }, isEditable: function(idx, column) { return false; },

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;

var thisLevel = this.getLevel(idx); var deletecount = 0; for (var t = idx + 1; t < this.visibleData.length; t++) { if (this.getLevel(t) > thisLevel) deletecount++; else break; } if (deletecount) { this.visibleData.splice(idx + 1, deletecount); this.treeBox.rowCountChanged(idx + 1, -deletecount); } }

Page 139: XUL Tutorial

else { item[2] = true;

var label = this.visibleData[idx][0]; var toinsert = this.childData[label]; for (var i = 0; i < toinsert.length; i++) { this.visibleData.splice(idx + i + 1, 0, [toinsert[i], false]); } this.treeBox.rowCountChanged(idx + 1, toinsert.length); } },

getImageSrc: function(idx, column) {}, getProgressMode : function(idx,column) {}, getCellValue: function(idx, column) {}, cycleHeader: function(col, elem) {}, selectionChanged: function() {}, cycleCell: function(idx, column) {}, performAction: function(action) {}, performActionOnCell: function(action, index, column) {}, getRowProperties: function(idx, column, prop) {}, getCellProperties: function(idx, column, prop) {}, getColumnProperties: function(column, element, prop) {},};

function init() { document.getElementById("elementList").view = treeView;}

]]></script>

</window>

Tree Box ObjectsThis section will describe the tree box object used to handle how a tree is displayed.

About the Tree Box Object

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.

Example 8.6.1: Source View

Page 140: XUL Tutorial

<script>function doScroll(){ var value = document.getElementById("tbox").value; var tree = document.getElementById("thetree");

var boxobject = tree.boxObject; boxobject.QueryInterface(Components.interfaces.nsITreeBoxObject); boxobject.scrollToRow(value);}</script>

<tree id="thetree" rows="4"> <treecols> <treecol id="row" label="Row" primary="true" flex="1"/> </treecols> <treechildren> <treeitem label="Row 0"/> <treeitem label="Row 1"/> <treeitem label="Row 2"/> <treeitem label="Row 3"/> <treeitem label="Row 4"/> <treeitem label="Row 5"/> <treeitem label="Row 6"/> <treeitem label="Row 7"/> <treeitem label="Row 8"/> <treeitem label="Row 9"/> </treechildren></tree>

<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.

Additional scroll methods include the scrollByLines, scrollByPages and ensureRowIsVisible functions. The scrollByLines scrolls up or down by a certain number of rows. Use a positive number to go down and a negative number to go up. The scrollByPages function scrolls by a number of pages and is called automatically when the user presses the page up or page down keys while the tree is focused. A page is equal to the number of visible rows. For example if the tree shows 10 rows at a time, a page will be equivalent to 10 rows. This is a convenient method since when the user resizes a flexible tree, the page size will grow and shrink, so you don't need to calculate the page size manually. This isn't too hard to calculate manually anyway since the tree box object also provides a getPageLength function which returns the number of rows in a page. In the scrolling example above, getPageLength would return four.

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

Page 141: XUL Tutorial

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.

tree.boxObject.getRowAt( 50, 100 );

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.

Example 8.6.2: Source View

<script>function updateFields(event){ var row = {}, column = {}, part = {}; var tree = document.getElementById("thetree");

var boxobject = tree.boxObject; boxobject.QueryInterface(Components.interfaces.nsITreeBoxObject); boxobject.getCellAt(event.clientX, event.clientY, row, column, part);

if (typeof column.value != "string") column.value = column.value.id;

document.getElementById("row").value = row.value; document.getElementById("column").value = column.value; document.getElementById("part").value = part.value;}</script>

<tree id="thetree" flex="1" onmousemove="updateFields(event);"> <treecols> <treecol id="utensil" label="Utensil" primary="true" flex="1"/> <treecol id="count" label="Count" flex="1"/> </treecols> <treechildren> <treeitem> <treerow> <treecell label="Fork"/> <treecell label="5"/> </treerow> </treeitem> <treeitem> <treerow> <treecell label="Knife"/> <treecell label="2"/> </treerow> </treeitem> <treeitem> <treerow> <treecell label="Spoon"/> <treecell label="8"/> </treerow> </treeitem>

Page 142: XUL Tutorial

</treechildren></tree>

<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 (typeof column.value != "string") column.value = column.id;

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.

var x = {}, y = {}, width = {}, height = {};tree.boxObject.getCoordsForCellItem( row, column, part, x, y, width, height );

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.

Page 143: XUL Tutorial

9. RDF and Templates

Introduction to RDFIn this section, we'll look at RDF (Resource Description Framework).

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.

RDF consists of a model, which is a graph representation of data. RDF/XML is an XML language which can be used to represent RDF data. It contains a fairly simple set of elements. The sample below shows a simple RDF template.

<?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

Page 144: XUL Tutorial

below:

Name http://home.netscape.com/NC-rdf#Name Bookmark name

URL http://home.netscape.com/NC-rdf#URL URL to link to

Description http://home.netscape.com/NC-rdf#Description Bookmark description

Last Visited http://home.netscape.com/WEB-rdf#LastVisitDate Date of last visit

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

Page 145: XUL Tutorial

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

Species http://www.some-fictitious-zoo.com/rdf#species

Class http://www.some-fictitious-zoo.com/rdf#class

TemplatesIn 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.

Example 9.2.1: Source

Page 146: XUL Tutorial

<vbox datasources="rdf:bookmarks" ref="NC:BookmarksRoot" flex="1"> <template> <button uri="rdf:*" label="rdf:http://home.netscape.com/NC-rdf#Name"/> </template></vbox>

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

Page 147: XUL Tutorial

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.

Example 9.2.2: Source

<vbox datasources="rdf:bookmarks" ref="NC:BookmarksRoot" flex="1"> <template> <vbox uri="rdf:*"> <button label="rdf:http://home.netscape.com/NC-rdf#Name"/> <label value="rdf:http://home.netscape.com/NC-rdf#URL"/> </vbox> </template></vbox>

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.

Example 9.2.3: Source

<vbox datasources="rdf:bookmarks" ref="NC:BookmarksRoot" flex="1"> <template> <label uri="rdf:*" value="rdf:http://home.netscape.com/NC-rdf#Name rdf:http://home.netscape.com/NC-rdf#URL"/> </template></vbox>

How Templates are Built

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

Page 148: XUL Tutorial

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.

Page 149: XUL Tutorial

The following example demonstrates the earlier example with two rules:

Example 9.2.4: Source

<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">

<vbox datasources="rdf:bookmarks" ref="NC:BookmarksRoot" flex="1"> <template>

<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>

By using two rules, we have allowed the contents of the template to be selectively generated. In the first rule, bookmark separators are selected, as can be seen by the rdf:type attribute. The second rule does not have any attributes so all data matches it.

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,

Page 150: XUL Tutorial

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.

Trees and TemplatesThe following describes how to use a template with a tree.

Adding Datasources to Trees

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.

The following example uses the history datasource:

<tree datasources="rdf:history" ref="NC:HistoryByDate" flags="dont-build-content">

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.

Example 9.3.1: Source

<tree id="my-tree" flex="1" datasources="rdf:files" ref="file:///" flags="dont-build-content"> <treecols> <treecol id="Name" label="Name" primary="true" flex="1"/> <splitter/> <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/WEB-

Page 151: XUL Tutorial

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

Page 152: XUL Tutorial

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:

• ascending: the data is displayed is ascending order. • descending: the data is displayed is descending order. • natural: the data is displayed in natural order, which means the order the data is stored

in the RDF datasource.

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>

Persisting Column State

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:

<treecol id="Date" label="Date" flex="1" class="sortDirectionIndicator" persist="width ordinal hidden sortActive sortDirection" sort="rdf:http://home.netscape.com/WEB-rdf#LastModifiedDate"/>

Additional Rule Attributes

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.

• iscontainerIf 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.

• isemptyIf this attribute is set to true, then the rule will match all resources that have no children.

Page 153: XUL Tutorial

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 DatasourcesHere, we'll look at additional datasources and how to use your own RDF files as datasources.

Other Mozilla 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 List

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.

Date http://home.netscape.com/NC-rdf#Date Date of last visit

Name http://home.netscape.com/NC-rdf#Name Title of the page

Page http://home.netscape.com/NC-rdf#Page Page name

Referrer http://home.netscape.com/NC-rdf#Referrer Referrer of the page

URL http://home.netscape.com/NC-rdf#URL URL of the page

Visit Count http://home.netscape.com/NC-rdf#VisitCount Number of page visits

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.

Example 9.4.1: Source

<tree flex="1" datasources="rdf:history" ref="NC:HistoryRoot">

<treecols> <treecol id="name" label="Name" flex="1"/>

Page 154: XUL Tutorial

<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

Added Date http://home.netscape.com/NC-rdf#BookmarkAddDate

Date the bookmark was added

Description http://home.netscape.com/NC-rdf#Description Bookmark description

Last Modified http://home.netscape.com/WEB-rdf#LastModifiedDate Date of last modification

Last Visited http://home.netscape.com/WEB-rdf#LastVisitDate Date of last visit

Name http://home.netscape.com/NC-rdf#Name Bookmark name

Shortcut URL http://home.netscape.com/NC-rdf#ShortcutURL Custom keywords field

URL http://home.netscape.com/NC-rdf#URL The URL to link to

Possible Bookmarks Roots

NC:BookmarksRoot The top level of the bookmarks hierarchy

NC:IEFavoritesRoot The bookmark folder that corresponds to the user's IE favorites.

Page 155: XUL Tutorial

NC:PersonalToolbarFolder The bookmark folder that corresponds to the personal toolbar folder

Files (rdf:files): A view of the user's files.

Resources

Name http://home.netscape.com/NC-rdf#Name Name of the file

URL http://home.netscape.com/NC-rdf#URL URL of the file

Possible Files Roots

NC:FilesRoot Top level of the filesystem (usually the list of drives)

A file URL By using a file URL for the ref attribute, you can select a specific directory to be 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.

<tree datasources="rdf:bookmarks rdf:history animals.rdf" ref="NC:BookmarksRoot">

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.

Custom RDF Datasources

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

Page 156: XUL Tutorial

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

Example 9.4.2: Source View

<tree flex="1" width="200" height="200" datasources="animals.rdf" ref="http://www.some-fictitious-zoo.com/all-animals">

<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.

Example 9.4.3: Source View

Page 157: XUL Tutorial

<window id="example-window" title="History List" xmlns:ANIMALS="http://www.some-fictitious-zoo.com/rdf#" xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">

<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 RulesThis section describes the more advanced rule syntax.

The Full 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.

Page 158: XUL Tutorial

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.

The syntax of the content element is as follows:

<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:

<tree id="citiesTree" datasources="weather.rdf" ref="http://www.xulplanet.com/rdf/weather/cities"> <template> <rule> <conditions> <content uri="?list"/> <member container="?list" child="?city"/> </conditions> <rule> <template></tree>

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.

Page 159: XUL Tutorial

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:

A Bookmark to mozilla.org -> URL -> www.mozilla.org

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:

<triple subject="A Bookmark to mozilla.org" predicate="URL" object="www.mozilla.org"/>

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.

Page 160: XUL Tutorial

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

Example 9.5.1: Source

<tree id="weatherTree" flex="1" datasources="weather.rdf" ref="http://www.xulplanet.com/rdf/weather/cities"> <treecols> <treecol id="city" label="City" primary="true" flex="1"/> <treecol id="pred" label="Prediction" flex="1"/> </treecols>

<template> <rule> <conditions>

Page 161: XUL Tutorial

<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.

Adding Additional Bindings

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 DataThis 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

Page 162: XUL Tutorial

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.

10. Skins and Locales

Adding Style SheetsStyle sheets may be used both for creating themes, as well as tailoring elements for a more

Page 163: XUL Tutorial

precise user interfaces. XUL uses CSS (Cascading Style Sheets) for this.

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:

<?xml-stylesheet href="chrome://global/skin/" type="text/css"?>

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.

Built-in Style Classes

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.

Page 164: XUL Tutorial

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.

Example 10.1.1: Source View

<textbox class="plain" readonly="true">

Adding a style sheet

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.

Changing the Styles

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:

<?xml-stylesheet href="chrome://global/skin/" type="text/css"?><?xml-stylesheet href="findfile.css" type="text/css"?> ...<spacer class="titlespace"/> <groupbox orient="horizontal"> <caption label="Search Criteria"/>

<menulist id="searchtype"> <menupopup> <menuitem label="Name"/> <menuitem label="Size"/> <menuitem label="Date Modified"/> </menupopup> </menulist> <spacer class="springspace"/> <menulist id="searchmode"> <menupopup>

Page 165: XUL Tutorial

<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>

<progressmeter id="progmeter" value="50%" style="display:none;"/>

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.

Importing Style Sheets

We've already seen how to import style sheets for use. An example is shown below:

<?xml-stylesheet href="chrome://bookmarks/skin/" type="text/css"?>

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).

Page 166: XUL Tutorial

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:

Style import from XUL:<?xml-stylesheet href="chrome://global/skin/" type="text/css"?>

Style import from CSS:@import url(chrome://global/skin/);

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:

button Matches all button tags

#special-button Matches the element with an id of special-button

.bigbuttons Matches all elements with a class of bigbuttons

button.bigbuttons Matches all button elements with a class of bigbuttons

toolbar > button Matches all buttons that are directly inside toolbar elements.

toolbar > button.bigbuttons

Matches all button elements with a class of bigbuttons that are directly inside toolbar elements.

button.bugbuttons:hover Matches all button elements with a class of bigbuttons but only while the mouse is over them.

button#special-button:active

Matches all button elements with an id of special-button but only while they are active (being clicked on).

box[orient="horizontal"] Matches all box elements that have an orient attribute that is set to 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 TreeThe following describes how to style a tree.

Page 167: XUL Tutorial

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.

Set the property on a row or cell, as in the following example:

<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:

• ::-moz-tree-cell: a cell. Use this to set borders and background colors. • ::-moz-tree-cell-text: the text in a cell. Use this to set the font and text color. • ::-moz-tree-twisty: the appearance of the twisty used to expand and collapse child rows. • ::-moz-tree-image: the image for a cell. You can set the image with the list-style-image

property. • ::-moz-tree-row: a row. Use this to set the background color of a row. • ::-moz-tree-indentation: the indentation to the left of rows that are children of other

rows. • ::-moz-tree-column: a column. • ::-moz-tree-line: the lines that are drawn to connect child rows to parent rows. • ::-moz-tree-separator: a separator in a tree. • ::-moz-tree-progressmeter: content for progressmeter cells. You can create a

progressmeter column by setting the type attribute on the column to progressmeter. • ::-moz-tree-drop-feedback: the drag and drop feedback.

Page 168: XUL Tutorial

• ::-moz-tree-checkbox: the image to use for checkbox columns.

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

Page 169: XUL Tutorial

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.

Modifying the Default SkinThis section describes how to modify the skin of a window.

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.

Page 170: XUL Tutorial

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 SkinThis 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.

Page 171: XUL Tutorial

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.

window > box { background-color: #0088CC; }

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;}

Page 172: XUL Tutorial

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:<vbox class="findfilesbox" orient="vertical" flex="100%"><toolbox>

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

Page 173: XUL Tutorial

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;}

Page 174: XUL Tutorial

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.

Creating a Global Skin

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.

LocalizationXUL and XML provide entities which are a convenient way of allowing localization.

Page 175: XUL Tutorial

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 &lt; and &gt; 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:

<!DOCTYPE window SYSTEM "chrome://findfile/locale/findfile.dtd">

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.

Page 176: XUL Tutorial

Declaring Entities

The entities are declared using a simple syntax as shown below:

<!ENTITY findLabel "Find">

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.

For example, the following text:

<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.

<menuitem label="&undo.label;" accesskey="&undo.key;"/><!ENTITY undo.label "Undo"><!ENTITY undo.key "u">

The example above uses two entities, one for the label on the Undo menu item and the second for the access key.

Changing the Find Files Dialog

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"?>

<?xml-stylesheet href="chrome://global/skin/" type="text/css"?><?xml-stylesheet href="findfile.css" type="text/css"?>

<!DOCTYPE window SYSTEM "chrome://findfile/locale/findfile.dtd">

<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="&copyCmd.accesskey;"/> <menuitem label="Paste" accesskey="&pasteCmd.accesskey;" disabled="true"/>

Page 177: XUL Tutorial

</popup></popupset>

<keyset> <key id="cut_cmd" modifiers="accel" key="&cutCmd.commandkey;"/> <key id="copy_cmd" modifiers="accel" key="&copyCmd.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="&copyCmd.label;" accesskey="&copyCmd.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>

<tabpanel id="searchpanel" orient="vertical" context="editpopup">

<description> &findDescription; </description>

<spacer class="titlespace"/>

<groupbox orient="horizontal"> <caption label="&findCriteria;"/>

<menulist id="searchtype"> <menupopup> <menuitem label="&type.name;"/>

Page 178: XUL Tutorial

<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"/>

<menulist id="find-text" flex="1" editable="true" datasources="file:///mozilla/recents.rdf" ref="http://www.xulplanet.com/rdf/recent/all"> <template> <menupopup> <menuitem label="rdf:http://www.xulplanet.com/rdf/recent#Label" uri="rdf:*"/> </menupopup> </template> </menulist>

</groupbox>

</tabpanel>

<tabpanel id="optionspanel" orient="vertical"> <checkbox id="casecheck" label="&casesensitive;"/> <checkbox id="wordscheck" label="&matchfilename;"/> </tabpanel>

</tabpanels> </tabbox>

<tree id="results" style="display: none;" flex="1"> <treecols> <treecol id="name" label="&results.filename;" flex="1"/> <treecol id="location" label="&results.location;" flex="2"/> <treecol id="size" label="&results.size;" flex="1"/> </treecols>

<treechildren> <treeitem> <treerow> <treecell label="mozilla"/> <treecell label="/usr/local"/> <treecell label="&bytes.before;2520&bytes.after;"/> </treerow> </treeitem> </treechildren> </tree>

<splitter id="splitbar" resizeafter="grow" style="display: none;"/>

<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>

Page 179: XUL Tutorial

</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.

Next, the DTD file (findfile.dtd):

<!ENTITY findWindow.title "Find Files"><!ENTITY fileMenu.label "File"><!ENTITY editMenu.label "Edit"><!ENTITY fileMenu.accesskey "f"><!ENTITY editMenu.accesskey "e"><!ENTITY openCmd.label "Open Search..."><!ENTITY saveCmd.label "Save Search..."><!ENTITY closeCmd.label "Close"><!ENTITY openCmd.accesskey "o"><!ENTITY saveCmd.accesskey "s"><!ENTITY closeCmd.accesskey "c"><!ENTITY cutCmd.label "Cut"><!ENTITY copyCmd.label "Copy"><!ENTITY pasteCmd.label "Paste"><!ENTITY cutCmd.accesskey "t"><!ENTITY copyCmd.accesskey "c"><!ENTITY pasteCmd.accesskey "p"><!ENTITY cutCmd.commandkey "X"><!ENTITY copyCmd.commandkey "C"><!ENTITY pasteCmd.commandkey "V"><!ENTITY openCmdToolbar.label "Open"><!ENTITY saveCmdToolbar.label "Save"><!ENTITY searchTab "Search"><!ENTITY optionsTab "Options"><!ENTITY findDescription "Enter your search criteria below and select the Find button to begin the search."><!ENTITY findCriteria "Search Criteria"><!ENTITY type.name "Name"><!ENTITY type.size "Size"><!ENTITY type.date "Date Modified"><!ENTITY mode.is "Is"><!ENTITY mode.isnot "Is Not"><!ENTITY casesensitive "Case Sensitive Search"><!ENTITY matchfilename "Match Entire Filename"><!ENTITY results.filename "Filename"><!ENTITY results.location "Location"><!ENTITY results.size "Size"><!ENTITY bytes.before ""><!ENTITY bytes.after "bytes"><!ENTITY button.find "Find"><!ENTITY button.cancel "Cancel">

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.

Page 180: XUL Tutorial

Property FilesIn 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:

notFoundAlert=No files were found matching the criteria.deleteAlert=Click OK to have all your files deleted.

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.

<stringbundle id="strings" src="strings.properties"/>

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 XBLXUL has a sister language, XBL (eXtensible Bindings Language). This language is used for declaring the behavior of XUL widgets.

Page 181: XUL Tutorial

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.

A binding has five types of things that it declares:

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.

Page 182: XUL Tutorial

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 ContentIn 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

Page 183: XUL Tutorial

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.

Another example, this time for a field for entering a filename:

<binding id="fileentry"> <content> <textbox/> <button label="Browse..."/> </content></binding>

Page 184: XUL Tutorial

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.

The anonymous content is created automatically whenever a binding is attached to an element. If you place child elements inside the XUL, they will override the elements provided by the binding. For example, take this XUL fragment, assuming it is bound to the scrollbar XBL earlier:

<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.

The resulting content would be:

Page 185: XUL Tutorial

<menu class="dropbox"> <menupopup> <menuitem label="1000"/> <menuitem label="2000"/> </menupopup> <textbox flex="1"/> <button src="chrome://global/skin/images/dropbox.jpg"/></menu>

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>

Page 186: XUL Tutorial

</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.

Multiple Children Elements

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:

Example 11.2.1: Source

<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.

XBL Attribute InheritanceIn this section we'll see how attributes can be inherited.

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>

Page 187: XUL Tutorial

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:<box class="labeledtextbox" title="Enter some text:" value="OK"/>

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>

Page 188: XUL Tutorial

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:<box class="okcancel" oktitle="OK" canceltitle="Cancel" image="happy.png"/>

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:

<box class="okcancel" oktitle="OK" canceltitle="Cancel" image="happy.png"> <button label="OK" image="happy.png"/> <button label="Cancel"/></box>

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 PropertiesNext, we'll find out how to add custom properties to XBL-defined elements.

The XBL Interface

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,

Page 189: XUL Tutorial

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:<box id="random-box" class="randomizer"/>

<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:

Page 190: XUL Tutorial

<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.

The following decribes what happens in a typical case:

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.

Page 191: XUL Tutorial

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 MethodsNext, 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"/> . . .

Page 192: XUL Tutorial

<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&lt;=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.

Accessing the Anonymous Content

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

Page 193: XUL Tutorial

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.

The following example creates a row of buttons:

<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:<box id="num" class="labeledbutton" title="Number of Things:" value="52"/>

<button label="Show" oncommand="document.getElementById('num').showTitle(true)"/><button label="Hide" oncommand="document.getElementById('num').showTitle(false)"/>

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

Page 194: XUL Tutorial

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:

Example 11.5.1: Source

<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:<box id="outer" class="container"> <button label="One"/> <button label="Two"/> <button label="Three"/> <button label="Four"/></box>

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

Page 195: XUL Tutorial

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.

Constructors and Destructors

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);"/>

Adding Event HandlersNext, we'll find out how to add event handlers to XBL-defined elements.

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.

The general handler syntax is as follows:

<binding id="binding-name"> <handlers> <handler event="event-name" action="script"/>

Page 196: XUL Tutorial

</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:

• altThe user must press the Alt key.

• controlThe user must press the Control key.

• metaThe user must press the Meta key.

• shiftThe user must press the Shift key.

• accelThe user must press the special modifier key that is usually used for keyboard shortcuts on their platform.

Page 197: XUL Tutorial

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:

Example 11.6.1: Source

<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.

Page 198: XUL Tutorial

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 InheritanceIn 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.

Example 11.7.1: Source

<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.

<textbox type="autocomplete" searchSessions="history"/>

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

Page 199: XUL Tutorial

addresses in the address book.)

XBL ExampleThis 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.

Example 11.8.1: Source

<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.

The following XUL file produces the result in the image.

<box class="slideshow" previoustext="Previous" nexttext="Next" flex="1"> <button label="Button 1"/> <checkbox label="Checkbox 2"/> <textbox/></box>

The style sheet used here is:

.slideshow { -moz-binding: url("slideshow.xml#slideshow");}

Page 200: XUL Tutorial

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('selectedIndex'));" 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>

Page 201: XUL Tutorial

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.

Let's break down the code piece by piece.

• 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("value",(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.

The final code is as follows:

Example 11.8.2: Source

<binding id="slideshow"> <content>

Page 202: XUL Tutorial

<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('selectedIndex'));" 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>

12. Specialized Window Types

Features of a WindowWe've already seen some features of windows. We'll look at some more in this section.

Creating Another Window

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

Page 203: XUL Tutorial

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");

Specifying the Width and Height

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.

Other Window Features

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'.

• alwaysLoweredThe window will always appear behind other windows.

• alwaysRaisedThe window will always appear above other windows.

• centerscreenThe window will be centered on the screen when it is opened.

• dependentThe 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

Page 204: XUL Tutorial

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.

• resizableThe user can resize the window.

Creating DialogsA 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

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.

Example 12.2.1: Source View

Page 205: XUL Tutorial

<?xml version="1.0"?>

<?xml-stylesheet href="chrome://global/skin/global.css" type="text/css"?>

<dialog id="donothing" title="Do Nothing" xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" buttons="accept,cancel" ondialogaccept="return doOK();" ondialogcancel="return doCancel();">

<script>function doOK(){ alert("You pressed OK!"); return true;}

function doCancel(){ alert("You pressed Cancel!"); return true;}</script>

<description value="Select a button"/>

</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.

Open and Save DialogsA common type of dialog in one where the user can select a file to open or save.

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.

You can use the file picker in one of three modes:

Page 206: XUL Tutorial

• Open: the user is asked to select a file for opening. • Get Folder: the user is asked to select a folder (directory). • Save: the user is asked to select the name to save a file to.

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.

var nsIFilePicker = Components.interfaces.nsIFilePicker;var fp = Components.classes["@mozilla.org/filepicker;1"] .createInstance(nsIFilePicker);fp.init(window, "Select a File", nsIFilePicker.modeOpen);

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:

fp.appendFilter("Audio Files","*.wav; *.mp3");

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:

Page 207: XUL Tutorial

• 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.

var res = fp.show();if (res == nsIFilePicker.returnOK){ var thefile = fp.file; // --- do something with the file here ---}

Creating a WizardMany 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:

Example 12.4.1: Source

<?xml version="1.0"?>

<?xml-stylesheet href="chrome://global/skin/" type="text/css"?>

<wizard id="example-window" title="Select a Dog Wizard" xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">

<wizardpage> <description> This wizard will help you select the type of dog that is best for you."

Page 208: XUL Tutorial

</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>

<wizardpage description="Dog Details"> <label value="Provide additional details about the dog you would like:"/> <radiogroup> <caption label="Size"/> <radio value="small" label="Small"/> <radio value="large" label="Large"/> </radiogroup> <radiogroup> <caption label="Gender"/> <radio value="male" label="Male"/> <radio value="female" label="Female"/> </radiogroup> </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.

Handling Page Changes

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

Page 209: XUL Tutorial

information that the user entered during the wizard.

For example:

<wizard id="example-window" title="Select a Dog Wizard" onwizardfinish="return saveDogInfo();" xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">

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.

Attribute Place on Tag When it is Called

pagehide wizardpage Called on the page that the user is leaving.

pageadvanced wizardpage Called on the page the user is leaving.

wizardnext wizard Called on the wizard.

pageshow wizardpage Called on the page that the user is entering.

A similar process occurs for the Back button.

More WizardsThis section describes some additional features of wizards.

More Complex Navigation

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.

Page 210: XUL Tutorial

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):

<wizardpage pageid="type" next="font"><wizardpage pageid="font" next="done"><wizardpage pageid="color" next="done"><wizardpage pageid="done">

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.

Example 12.5.1: Source

<?xml version="1.0"?>

<?xml-stylesheet href="chrome://global/skin/" type="text/css"?>

<wizard id="theWizard" title="Secret Code Wizard" xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">

<script> function checkCode() {

Page 211: XUL Tutorial

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.

OverlaysThis 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

Page 212: XUL Tutorial

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.

Example 12.6.1: Source

<?xml version="1.0"?>

<!DOCTYPE overlay SYSTEM "chrome://findfile/locale/findfile.dtd">

<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.

<menu id="edit-menu" label="Edit" accesskey="e"> <menupopup id="edit-popup"> <menuitem label="&cutCmd.label;" accesskey="&cutCmd.accesskey;" key="cut_cmd"/> <menuitem label="&copyCmd.label;" accesskey="&copyCmd.accesskey;" key="copy_cmd"/>

Page 213: XUL Tutorial

<menuitem label="&pasteCmd.label;" accesskey="&pasteCmd.accesskey;" key="paste_cmd" disabled="true"/> </menupopup> </menu> <menu id="help-menu" label="&helpCmd.label;" accesskey="&helpCmd.accesskey;"/></menubar>

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:

<!ENTITY helpCmd.label "Help"> <!ENTITY helpCmd.accesskey "h"> <!ENTITY contentsCmd.label "Contents"> <!ENTITY indexCmd.label "Index"> <!ENTITY aboutCmd.label "About..."> <!ENTITY contentsCmd.accesskey "c"> <!ENTITY indexCmd.accesskey "i"> <!ENTITY aboutCmd.accesskey "a"> <!ENTITY findfilehelpCmd.label "Find files help"> <!ENTITY findfilehelpCmd.accesskey "f">

We will use the last two entities in a moment.

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.

Let's change the help menu in this manner.

findfile.xul:<menu id="help-menu"/>

helpoverlay.xul:<menu id="help-menu" label="&helpCmd.label;" accesskey="&helpCmd.accesskey;">

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"?>

<?xml-stylesheet href="chrome://global/skin/global.css" type="text/css"?>

<window title="Stop and Go" id="test-window" xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">

Page 214: XUL Tutorial

<?xul-overlay href="chrome://findfile/content/toverlay.xul"?>

<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.

We can use this technique in the find files dialog also:

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.

Placing Overlaid 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

Page 215: XUL Tutorial

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.

Cross Package OverlaysThis section describes how to apply overlays to files that don't import them.

Page 216: XUL Tutorial

Applying Overlays to other Packages

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.

Example 12.7.1: Source

<?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.

Page 217: XUL Tutorial

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 InstallerThis 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

Page 218: XUL Tutorial

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:

function doneFn ( name , result ){ alert("The package " + name + " was installed with a result of " + result);}

var xpi = new Object();xpi["Calendar"] = "calendar.xpi";InstallTrigger.install(xpi,doneFn);

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.

Let's try this with the find files dialog.

function doneFn ( name , result ){ if (result) alert("An error occured: " + result);}

var xpi = new Object();

Page 219: XUL Tutorial

xpi["Find Files"] = "findfile.xpi";InstallTrigger.install(xpi,doneFn);

The XPI Archive

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.jsfindfile 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 ScriptsThis section describes the install script.

Creating an 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

Page 220: XUL Tutorial

described below.

The install script should take the following steps:

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.

The Component Registry

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).

This key is structured as directory-like path of the following form:

/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

/Netscape/Personal Security Manager

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:

initInstall( packageName , regPackage , version );

Example:initInstall("Find Files","/Xulplanet/Find Files","0.5.0.0");

Page 221: XUL Tutorial

The first argument is the name of the package in user-readable form. The second argument is the registry key used to hold the package information as described earlier. The third argument is the version of the package being installed.

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.

Setting Install Files

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.

registerChrome(Install.CONTENT | Install.DELAYED_CHROME, getFolder(findDir, "content"));registerChrome(Install.SKIN | Install.DELAYED_CHROME, getFolder(findDir, "skin"));

Page 222: XUL Tutorial

registerChrome(Install.LOCALE | Install.DELAYED_CHROME, getFolder(findDir, "locale"));

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:

Example 13.2.1: Source

initInstall("Find Files","/Xulplanet/Find Files","0.5.0.0");

findDir = getFolder("Chrome","findfile");setPackageFolder(findDir);

addDirectory("findfile");

registerChrome(Install.CONTENT | Install.DELAYED_CHROME, getFolder(findDir, "content"));registerChrome(Install.SKIN | Install.DELAYED_CHROME, getFolder(findDir, "skin"));registerChrome(Install.LOCALE | Install.DELAYED_CHROME, getFolder(findDir, "locale"));

performInstall();

Additional Install FeaturesThis section describes some more specifics of installers.

Installer File Manipulation

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);

Page 223: XUL Tutorial

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.

The XUL Tutorial was created by Neil Deakin