Chapter 3. XUL Elements and Features
The XML-based User-interface Language (XUL) includes all of the basic widgets you need to build application user interfaces. These interfaces include tabs, text areas, buttons, and menus, as well as handy interfaces you may not have thought you needed, such as the <stack> widget or <colorpicker>.
Chapter 2 introduced some of the XUL elements that make up a window and basic applications. This chapter examines XUL elements and features in more detail, describing the rationale behind them, their look and behavior, and common usage. Though not comprehensive, the chapter provides more than enough information about XUL to get you started on building your own Mozilla applications, particularly when used in conjunction with the XUL reference in Appendix C.
The elements described here, such as menus, buttons, trees, and boxes, are needed in almost any type of application, and most of the examples are generic, so you can plug them into any application or customize them to your needs. We've packed a lot of information in this chapter and it will be a useful reference as you begin to develop your applications.
3.1. The XUL Document Object
At the core of a XUL file is the document object. As in HTML, document is an object that represents the XUL document itself -- the content as opposed to the window that surrounds it. The document provides methods for getting individual elements, manipulating the structure of the document, or updating style rules.
A document object provides methods such as getElementById, getElementsByTagName, createElement, and createTextNode for DOM querying and manipulation of the actual document. Further details about the DOM are available in Chapter 5.
Other types of document objects include the width and height of the window, a popupNode property that accesses the elements currently displaying a pop up (a XUL widget that attaches to another widget and appears above it holding some content), a tooltipNode property that accesses the element currently displaying a tooltip, and a documentElement property that accesses the body of the document:
var docEl = document.documentElement; var secondLevelNodes = new Array( ); for (var I=0; I<docEl.childNodes.length;I++) { secondLevelNodes[I] = docEl.childNodes[I]; }
This example creates an array of all the second-level nodes in relation to the document, and could be extended to walk to the whole tree. Using nodes in the structural representation of the document to get to other nodes in this way allows you to quickly access and change any part of a document with script.
The document object is global only for the particular scope that you are working in, so every window, dialog, and page has its own document object. To access it, just use the document. prefix followed by the name of the property you want to access:
var title = document.getElementById("bookTitle");
It is possible to access document outside the current scope -- for example, the window that opened another one using window.opener:
var title = window.opener.document.getElementById("bookTitle");
3.1.1. XUL Parsing and the Document Object Model
Mozilla runs XUL documents through the Expat XML parser to check that they are well-formed. Expat is an XML parser library, written in C, that was integrated into Mozilla at the early stages of the code rewrite when the source was made open.
During parsing, a content model based on the Document Object Model (DOM) is built, allowing access to the content in a way that facilitates dynamic manipulation. Once the XML tags are in the correct namespaces, Mozilla parses the document a second time to ensure that XUL tags themselves are valid. If this fails, or if the document does not conform to the syntax rules, an error appears in your window so you can address the problem.
The parsing process builds an internal tree structure that can be used as a handle for querying, modifying, and copying documents the structure represents. Chapter 5 describes in more detail the relationship between JavaScript (the main scripting engine used in Mozilla) and the DOM, and it goes further with examples of commonly used methods for querying and modifying a XUL document. To view that internal tree, you can use a tool called the DOM Inspector, which is a Mozilla application that lets you view and manipulate the document object model of any XUL file or web page. For more information about the DOM Inspector, see Appendix B.
3.2. Application Windows
<window> is just one of the possible root elements of a XUL document, the others being <overlay>, <dialog>, <page>, and <wizard>. Overlays play an especially important role in managing and modularizing the code in your XUL application, so the section Section 3.11, later in this chapter, is dedicated to them.
The remaining root elements all have the XUL namespace and XUL window attributes and properties. All have a XUL document object. Yet, added features exist for each. Which element you choose for the XUL document depends on the purpose of the window. A <window> is typically top-level, a <dialog> is secondary, appearing above another window, a <page> is a document that appears in a frame, and a <wizard> stores a set of subsections for loading one at a time through a step-by-step process.
3.2.1. Dialogs
Dialogs usually carry out specific functions like displaying a message or getting information from the user. The <dialog> element was created relatively late in the XUL toolkit development cycle to cater for some special needs of dialog windows, including their position relative to other windows (particularly the main application window) and the existence of buttons for accepting or rejecting a particular operation. A dialog in XUL appears in Example 3-1.
Example 3-1. XUL dialog
<dialog id="turboDialog" buttons="accept" buttonpack="center" xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" title="Empty the Cache"
onunload="SetTurboPref( );">
As you can see, the dialog includes the XUL namespace and the id and title attributes. However, some attributes, such as buttons and buttonpack, don't appear in a regular window context.
Dialogs commonly require the user to take some action or make a choice, so the button attributes are provided for this purpose. In addition to buttons and buttonpack, there are special event handlers on the dialog element -- ondialogaccept, ondialogcancel, and ondialoghelp -- that correspond to the buttons typically displayed and can execute code in response to user input. As with onunload, you can place a function in the ondialogaccept event handler that executes when the user clicks the OK button:
<dialog id="flush" buttons="accept" buttonpack="center" xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" title="&exitWarningTitle.label;" ondialogaccept="doCacheFlush( );">
3.2.2. Pages
The <page> element is designed specifically for documents that are loaded in a frame of a higher-level document. They are not top-level windows themselves. In Mozilla, the page element is used often in the preferences dialog to represent the various preference panels.
As with the dialog in Example 3-1, the <page> element in Example 3-2 includes the familiar namespace attribute (xmlns) and load handler (onload). The headertitle attribute is also used for the top of the page, which itself gets loaded into another window that has its own title.
Example 3-2. XUL page
<page xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" onload="parent.initPanel('chrome://communicator/content/pref/pref-fonts.xul');" headertitle="&lHeader;">
An application of the page element in Mozilla is in the global preferences for the whole suite of Mozilla applications. Figure 3-1 shows the layout of this preferences panel. In Example 3-2, the entity in the header title, &lHeader;, resolves to “Languages” and be displayed above the individual preference panel page.
The main preferences window is a XUL dialog, and the content is split in two. On the left is a tree from an overlay that contains the preference topics, and on the right is a XUL page loaded into an <iframe>.
<iframe id="panelFrame" name="panelFrame" style="width:0px" flex="1"/>
As shown in Figure 3-1, selecting one of the topics in the left panel changes the page that is loaded into the frame. Although the changeover requires quite a bit of scripting in practice, at a basic level, it is just a case of changing the src attribute on the frame:
document.getElementById("panelFrame").setAttribute("src", "chrome://communicator/content/pref/pref-navigator.xul" );
3.2.3. Wizards
This type of window is designed for a very specific type of functionality -- to walk the user through a step-by-step process, with each step represented by a different screen. Using one window after another can create inconsistencies, including different sizes and performance issues. These can be especially bad when you try to create an interface that guides the user through a new process, such as setting up an account of some kind. Thus, the wizard element was adapted from the wizard paradigm now common in some native toolkits. Example 3-3 shows how Mozilla handles wizard dialogs.
Example 3-3. A XUL wizard
<wizard id="NewAccount" title="Account Set-up" onwizardcancel="return Cancel( );" onwizardfinish="return Finish( );" onload="onLoad( );" width="44em" height="30em" xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" xmlns:nc="http://home.netscape.com/NC-rdf#"> <wizardpage id="wPage1" pageid="page-1" label="New Account" onpageshow="return acctNamePageInit( );" onpageadvanced="nextPage(this)"> <vbox flex="1"> <description>Welcome and enjoy the wizardry</description> <image src="page1.png"/> </vbox> </wizardpage> <wizardpage id="wPage2"/> <wizardpage id="wPage3"/> </wizard>
A wizardpage is similar to a page because it has a surrounding window into which it is loaded. The difference, as shown in Example 3-3, is that in the wizard, the pages exist as a set within the window-level <wizard> element. Order wizardpages in the sequence you want them to appear on the screen. When the user accepts one page, the next one is loaded. In Example 3-3, the content of the first page is text and an image, and the other pages define only id attributes (though this is exactly how you might set them up if their actual content were overlaid into this wizard at runtime). You can use the wizard code in Example 3-3 by including the <?xml version="1.0"?> preamble at the top, adding label attributes to pages two and three, and seeing the pages advance as you click the buttons that guide the wizard process when you load the XUL file into the browser.
3.3. Application Widgets
Like most applications, yours may rely on menus and toolbars as part of the basic user interface. Menus and toolbars are common, multipurpose widgets that are familiar to most users. Menus often appear as part of a menu bar that organizes all of the capabilities of the program, or they can be single menus for presenting a simple list of choices. Buttons provide quick access to the most commonly used tasks and help get information back from the user. Beyond these basics, however, XUL provides widgets for creating almost any kind of interface (and the flexibility of Mozilla's presentation layer means you can make even the most prosaic menus look any way you want).
3.3.1. The Toolbox
As your applications grow in complexity and provide more services to the user, the toolbox can be a good way to organize menus, toolbars, and other widgets. A <toolbox> is a special container for holding one or more toolbars and/or menu bars. A Mozilla toolbar implements a toolbargrippy and a box that contains children. The toolbargrippy is a bar on the lefthand side used for collapsing and expanding the bar. This useful method allows users to control the space that is available to them onscreen.
3.3.1.1. Toolbars
The <toolbar> element shown in Example 3-4 contains buttons used to carry out various application functions. Buttons are the most common children of a toolbar, but they are by no means the only widgets or content you can put in there.
Example 3-4. Toolbar with buttons and spacing
<toolbox>
<toolbar id="fixed-toolbar" class="toolbar-primary"
tbautostretch="always" persist="collapsed">
<toolbarbutton id="newfileBtn" label="New" oncommand="doNew( );" />
<toolbarseparator />
<toolbarbutton id="openfileBtn" label="Open" oncommand="doOpen( );" />
<spacer flex="1" />
</toolbar>
</toolbox>
To apply spacing between elements, the <spacer> element can be used. In Example 3-4, all space that remains after the buttons are drawn goes after the buttons because the spacer there is flexible and the buttons are not. Space added elsewhere with other <spacer> elements is determined by ratio of the flex values on the elements competing for layout space. Extending the toolbar in Example 3-4, you can add a print button on the far right:
<toolbarbutton id="newfileBtn" label="New" oncommand="doNew( );" />
<toolbarseparator />
<toolbarbutton id="openfileBtn" label="Open" oncommand="doOpen( );" />
<spacer flex="1" />
<toolbarbutton id="printBtn" label="Print" oncommand="doPrint( );" />
The <toolbarseparator> element does not create additional spacing between the first two toolbarbuttons, but there is space between them and the print button, which is pushed to the far right because the flex attribute of the spacer in between is set to 1.
3.3.1.2. Menu bar
Among the other most common nested elements within a toolbox is a XUL <menubar>. <menubar> is a container much like the toolbar that has one or more menus as its children.
<menubar id="fixed-menubar"> <menu label="Quantity” /> <menu label="Color” /> </menubar>
//FIXME did we loose content here?
There is one caveat in menubar behavior that you should be aware of. On the Mac OS, application menu bars appear at the top of the screen. If you have any nonmenu elements contained in your menu bar widget, they are ignored because the OS does not know how to display them there.
As Example 3-5 illustrates, it's easy to build up a simple application menu and get the intrinsic look and collapsibility of a menu bar with a few simple lines of code:
Example 3-5. Application menu bar
<?xml version="1.0"?> <window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> <menubar id="appbar"> <menu label="File"> <menupopup> <menuitem label="New"/> <menuitem label="Open"/> </menupopup> </menu> <menu label="Edit" /> </menubar> </window>
The complete XUL file in Example 3-5 produces the menu bar in Figure 3-2.
3.3.2. Selection Lists
There are a number of ways to create lists in Mozilla. This section provides three alternative ways of presenting the same choices to the user. The options are illustrated in Figure 3-3. The one thing these three selection list widgets -- menus, pop ups, and menu lists -- have in common is they all use menu items to display individual choices:
<menuitem label="Tachinidae" oncommand="changeF(1)"/> <menuitem label="Tanyderidae" oncommand="changeF(2)"/> <menuitem label="Tipulidae" oncommand="changeF(3)"/> <menuitem label="Syrphidae" oncommand="changeF(4)"/> <menuitem label="Tephritidae" oncommand="changeF(5)"/>
When you wrap the menuitem elements above in a menu, a menu list, and a pop-up window, you see the variations in Figure 3-3.
3.3.2.1. Menus
Menus are much more flexible than they first appear to be. They can appear anywhere in the UI, for one thing, and needn't be stuck at the top of the window. They can be in buttons, trees, or just out on their own. Example 3-6 shows the basic structure of a menu.
Example 3-6. A sample menu
<menu label="Quantity"> <menupopup> <!-- menuitems here --> </menupopup> </menu>
There is a rigid ordering of nesting in a menu. A menu contains a <menupopup>, which in turn contains one or more menu items. Optionally, you can segregate groups of menu items by using a <menuseparator> in the pop up, which draws a thin line between items.
3.3.2.2. Pop ups
The pop up manifests as either a <menupopup> or a <popup> element. The latter can be used in a number of different ways, but Example 3-7 focuses on its common use in context menus.
Example 3-7. Context menu using pop up
<popup id="FlyContext"
onpopupshowing="return doThis( );"
onpopuphiding=" return doThat( );">
<!-- menuitems here -->
</popup>
A couple of extra steps are needed to prepare a context pop up for activation. First, you must attach the popup element to a widget in the UI by using the id of the pop up that must correspond to the context of the widget:
<toolbar id="main-toolbar" context="FlyContext" />
When the toolbar is clicked, the pop up that corresponds to that value appears. You can have some script execute when you show and/or hide the pop up by using the onpopupshowing and onpopuphiding methods, as when you show and hide items in a dynamic menu.
The second step includes the pop up in a set of pop ups, enclosed in a <popupset> element. Though not strictly necessary as a container for pop ups, the pop-up set helps organize the free-floating[1] pop-up windows in your application and makes it easy to overlay them or overlay into them as the situation requires.
3.3.2.3. Menu lists
Another manifestation of the pop up is in the use of menu lists. A menu list is a choice of options presented to solicit a single choice, usually in the form of a drop-down menu, for which XUL provides the <menulist> element. Example 3-8 presents a straightforward menu list with a selection of items to choose from. As in the other pop-up examples, selecting an item executes the code defined in the oncommand event handler for that item (e.g., changeF(1) for the menu item “Tachinidae”).
Example 3-8. XUL menu list
<menulist id="FlyInput"> <menupopup> <!-- menuitems here --> </menupopup> </menulist>
The menulist widget provides functionality beyond that of a regular menu. The menu list can be made editable when the user should be allowed to enter a value not represented in the menu items. In this case, the menulist element definition in Example 3-8 would change to something such as:
<menulist id="FlyInput" editable="true" oninput="onInputFly( );" onchange="onChangeFly( );">
A true value on the editable attribute allows input in the list. Input can be validated immediately by using the oninput attribute. The addition of the onchange attribute can be used to carry out an extra script when a new selection is made.
Notes
3.4. Tabular and Hierarchical Information
Many options exist to display hierarchical information in your user interface. The most common are tree-like and table-like structures, both of which are represented by elements in Mozilla's XPFE toolkit. In this section, we look at list boxes, trees, and grids. With the exception of the tree, these elements are not limited in regard to the content they can contain. Currently, the tree only holds text and image content and grids are designed for holding the more diverse content as shown in upcoming examples.
3.4.1. List Boxes
<listbox> is used to display tabular data. Example 3-9 shows a listbox widget with all the basic features, including the definition of the number of columns (listcol), the listbox header (listhead), and a list item (listitem).
Example 3-9. Listbox widget
<listbox rows="5” class="list" id="FlyTree" onselect="SelectFly( )"> <listcols> <listcol flex="1"/> <splitter class="tree-splitter"/> <listcol flex="1"/> </listcols> <listhead> <listheader label="Name" /> <listheader label="Type" /> </listhead> <listitem id="type-d"> <listcell label="Syrphidae" /> <listcell label="flower" /> </listitem> <!-- More Items --> </listbox>
The first thing of note in the markup in Example 3-9 is the rules for the nesting of elements within a listbox structure. The number of columns needs to be set, each with a <listcol> element, and all have to be wrapped in a <listcols> set. Example 3-9 has two columns. They are separated by a draggable grippy item, which also acts as a column separator in the header row. The cells for those columns are contained in a <listitem> grouping. The header is optional and has the same structure as a list item. Once you've put a hierarchy like this in place, you can put the content you want into the tabular structure.
The listbox does not support multilevel/nested rows. Also note that the class attribute example above is what gives the tree much of its particular appearance. Listboxes and trees often use class-based style rules for their appearance and positioning (e.g., the column splitter in Example 3-9).
Example 3-9 creates the listbox in Figure 3-4.
3.4.2. High Performance Trees
The <listbox> widget is suitable only for certain kinds of content. For better scalability and multilevel capabilities, the <tree> was created. <tree> is an advanced tree widget that was originally designed for the Mail/News component in Mozilla. In its first incarnation, it was called the outliner widget.
The tree is designed for high performance in large lists, such as newsgroups, message folders, and other applications where the volume of data is expected to be high. The tree widget has a simpler, more lightweight layout, but it is more difficult to use, requiring the addition of special “views” in order to display data.
3.4.2.1. Tree features
The implementation of the tree widget is unique in the XUL universe in that it displays its content only when it comes into view, which makes it very efficient for long lists of data. Table 3-1 lists some of the main features of the tree.
Even with this rich set of features, however, a tree can display only text and image content. The listbox is more flexible in the type of content that appears in its cells.
3.4.2.2. Tree views
In the tree widget, a view is a model for the population and display of data. The view is a flexible feature of the tree that can handle everything from simple data in a content view to more dynamic data from a custom view or an RDF datasource (builder view). Table 3-2 shows the main features of each, using general categories of datasource, speed, and type of usage.
As already mentioned, the tree is used in the Mail/News thread pane, but there are plenty of other places to look for it in Mozilla. Custom views and tree widgets are implemented for the Address Book results, JS Debugger, DOM Inspector, Bookmarks, and for autocomplete. You can see builder views in History and a content view implementation in Preferences.
3.4.2.3. The tree content model
The content in a tree is defined with <tree>, <treecols>, <treecol>, and <treechildren> tags. Example 3-10 shows a basic column number definition (two in this instance) and a treechildren placeholder that defines the tree body.
Example 3-10. Tree base model
<tree id="tree" flex="1"> <treecols> <treecol id="Col1" label="Col1" flex="1"/> <treecol id="Col2" label="Col1" flex="1"/> </treecols> <treechildren/> </tree>
As in the listbox, a well-defined hierarchy of elements has to be observed. This hierarchy is part of the content model for a tree. The organization of content within a tree enforced by the specific tree elements is listed below.
Unlike listbox, nested children are possible for multilevel trees. An example of nested children appears later in this chapter in Example 3-11.
<treeitem>
This element contains a single top-level row and all its descendants. The container attribute is used to mark this row as a container and is optional. The open attribute is used for expanded containers.
<treerow>
The row is contained in the <treeitem> element. You may optionally set the properties attribute on the <treerow> to a whitespace-separated list of properties.
<treeseparator>
A special element used to draw a horizontal separating line. The properties attribute is used to compute the properties that apply to the separator.
<treecell>
The <treecell> element must appear within the <treerow> element. It specifies the text and properties that apply for a cell. The label attribute is used to set the text for the cell. The optional properties attribute is used to compute the properties that apply to the cell. The ref attribute correlates a cell within an <treerow> to the column in the tree and is optional.
Tying the concepts presented in this section together allows us to present Example 3-11, which shows a multilevel tree with two columns and two top-level rows.
Example 3-11. Multilevel tree content view
<tree id="tree" hidecolumnpicker="true" flex="1"> <treecols> <treecol id="type" label="Type" flex="1" primary="true"/> <treecol id="method" label="Method" flex="1"/> </treecols> <treechildren> <treeitem> <treerow> <treecell label="Bike"/> <treecell label="Bicycle"/> </treerow> </treeitem> <treeitem container="true" open="true"> <treerow> <treecell label="Fly"/> <treecell label="Wings"/> </treerow> <treechildren> <! -- Second level row -- > <treeitem> <treerow> <treecell label="Glide"/> <treecell label="Hand-Glider"/> </treerow> </treeitem> </treechildren> </treeitem> </treechildren> </tree>
To create a new sublevel, create another <treechildren> element; inside of it, place a <treeitem>, which, in turn, contains one or more rows and cells. Figure 3-5 illustrates the result of this hierarchy.
3.4.2.4. Using trees in XUL templates
XUL templates are special built-in structures that allow dynamic updating of XUL elements and that are often used with trees and list boxes. Templates harness the power of the Resource Description Framework (RDF) to pull data from external datasources and dynamically create or update content in the UI. The following code extract shows the basic structure of a XUL template for displaying the browser history in Mozilla:
<template> <rule> <treechildren> <treeitem uri="rdf:*" rdf:type="rdf:http://www.w3.org/1999/02/22-rdf-syntax- ns#type"> <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"/> <!-- further cells --> </treerow> </treeitem> </treechildren> </rule> </template>
For each entry or row in the browser history, the template extracts information from the datasource and renders it in a treecell. It then updates it each time a page is visited. For a more detailed discussion, refer to Chapter 9.
3.4.2.5. Custom tree views
Custom views extend upon the static presentation of data in a tree with more flexibility, different ways to present the same data, and interfaces for defining behavior related to content. The functions include intercepting a treeitem selection and carrying out some functionality, populating or getting values from the tree, and returning the number of rows currently in the tree.
The first thing you have to do to build a custom view is instantiate your tree and then associate a view object with it, commonly known as a view.
document.getElementById('main-tree').treeBoxObject.view=mainView;
In this example, the view that is exposed in the nsITreeView XPCOM object is essentially the lifeline for the tree, supplying the data that populates the view. The view is assigned to the code object that contains all the functions available to it and your implementation of what you need to do when they are activated.
Here is a large subset of the functions available to the view object:
setTree(tree)
Called during initialization and used to connect the tree view to the front end. This connection ensures that the correct tree is associated with the view.
getCellText (row,column)
Returns the text of a particular cell, or an empty string if there's just an image in it.
rowCount
Set up the number of rows that you anticipate for your tree.
cycleHeader(index)
Called when you click on the header of a particular column.
toggleOpenState
Put code in here to be carried out when the view is expanded and collapsed.
setCellText (row, colID, value)
Called when the contents of the cell have been edited.
performAction (action)
An event from a set of commands can be invoked when you carry out a certain action on the outliner. The tree invokes this method when certain keys are pressed. For example, when the ENTER key is pressed, performAction calls with the “enter” string.
There are more local conveniences in the form of PerformActionOnRow and performActionOnCell.
selectionChanged
Should be hooked up to the onselect handler of the <tree> element in the XUL content.
3.4.3. Grid
A <grid> is another XUL table structure, designed to be more flexible with the content it can hold than the other tabular widgets. Example 3-12 shows a two-column grid that holds text input boxes and labels for them.
Example 3-12. XUL grid
<grid> <columns><column flex="1"/><column flex="2"/></columns> <rows> <row align="center"> <label value="Title"/> <textbox id="title-text" oninput="TextboxInput(this.id)"/> </row> <row align="center"> <label value="Author"/> <textbox id="author-text" oninput=" TextboxInput(this.id)"/> </row> <row align="center"> <label value="About"/> <textbox id="about-text" oninput=" TextboxInput(this.id)"/> </row> </rows> </grid>
In a grid, the number of columns needs to be defined and placed in a <columns> set. In Example 3-12, the first column holds the labels and the second contains the text boxes. These two columns are horizontal to each other and in rows for easy association. The flex is greater on the second column, allowing more space for the text input boxes. As with all examples in this chapter, you can see Example 3-12 in action by adding the XML processing instruction at the top and surrounding the grid in a basic window root element.
3.5. Words and Pictures
The text widgets described here are used to label other widgets, or simply to display messages or instructions to the user in the interface and include a text input widget. Images can be displayed with the main image element or in various ways on other elements, such as buttons or menus.
3.5.1. Text Input
The <textbox> element is a text input box not unlike the HTML <textarea> element. The default <textbox> element has a single line.
<textbox id="singleFlyInput" />
However, setting the multiline attribute makes it into a larger text area.
<textbox id="multiFlyInput" value="Fly Name" multiline="true" rows="4" />
A multiline textbox defaults to three lines unless constricted by a fixed size on a container or stretched out with flex. To force the number of lines, use the rows attribute. If you want to restrict the number of characters inputted, set the size attribute to a numeric value.
<textbox id="holdtheFlyInput" cols="3" rows="2" />
The initial value of an input widget is blank if no value is specified. Setting the readonly attribute to true or false can control editing access.
3.5.1.1. Autocomplete
Autocompletion is the process of automatically finishing a user's input by offering possible choices, or completions, when something is typed in. In Mozilla, this mechanism is simply known as autocomplete, and the textbox widget is used for this process in such places as the browser URL bar and in the address area of the mail compose window. Example 3-13 shows the code from the Open Web Location dialog, which provides autocompletion.
Example 3-13. Text autocomplete
<textbox id="dialog.input" flex="1" type="autocomplete" searchSessions="history" timeout="50" maxrows="6" disablehistory="false" oninput="doEnabling( );"> <menupopup id="ubhist-popup" class="autocomplete-history-popup" popupalign="topleft" popupanchor="bottomleft" onpopupshowing="createUBHistoryMenu(event.target);" oncommand="useUBHistoryItem(event.target)"/> </textbox>
The first thing to note is the nested <menupopup>. This pop up holds the choices in a drop-down format. The relevant attribute in this example is type on the <textbox>, which has a value of autocomplete.
Figure 3-6 shows the autocomplete widget. As the user types the URL into the textbox, auto completion kicks in and the values are retrieved to show in the pop-up list, from which the user can then choose. When similar values are input regularly, autocomplete can be a great time-saving feature.
3.5.2. Text Display
Three tags available in XUL handle basic text display in the UI, and each has its own context for use. They include a <caption>, a <label>, and a <description> element.
The caption is designed specifically for the text that appears inline in the border of a group box. You can control where the caption appears by putting the caption element above or below the other content in the group box:
<groupbox id="textWidgetsBox"> <caption id="textTitle" label="Text Widgets"/> <!-- content here --> </groupbox>
label is more flexible than caption because it isn't tied to a particular widget and can even be used as a standalone.
For longer text, the <description> element is best. You can embed text in the description element and have it wrap to the maximum size of the containing element:
<description> The mozdev.org site provides free project hosting for the Mozilla community. You are welcome to take a look at the more than 60 projects hosted on the site or to start your own development project. </description>
Or you can use the value attribute when you're sure the text will not overflow. In this case, <description> is interchangeable with the <label> element for use in identifying other items in the UI:
<description value="Start a project today." />
3.5.3. Images
XUL supports the display of images in the native web formats of JPEG, PNG, and GIF. Most images you will find in the Mozilla UI are GIF files, which retain the best quality when compressed. Chapter 4 discusses theme issues and considerations in more detail. The basic syntax for displaying an image is:
<image src="myImage.png" />
The <image> element is analogous to the HTML <img> element. The image to be displayed is directly associated with the element using the src attribute. You can also use list-style-image, which is a CSS2 property used to associate an image with an element. To do this, you need a style selector -- in this case, the id.
<image id="foo" />
The style property takes a value of src, which has one parameter, the image, or a chrome or resource URL pointing to the image.
#foo { list-style-image: url("myImage.png"); }
src is good for single images and for convenience, but in general, using the CSS property is recommended because it follows the principal of separating functionality from presentation and it better fits into a theme-swapping architecture, as used in the Mozilla suite.
Many in the open source community feel that PNG would have been a more natural choice for the project because it is a free format. Efforts to make this switch have been held up by a bug in gamma-corrected CSS color values and specified in both CSS1 and CSS2.
3.5.3.1. Images in other XUL elements
Image display is not the sole province of the image element. Using the list-style-image property, you can attach images to almost any element. For example, the tree widget has a couple of its own special associated CSS properties that allow you to define list-style-image values. -moz-tree-image defines images contained in a cell, and it takes input parameters that let you specify the id of the specific column and row to which the image should be applied:
treechildren:-moz-tree-image(col-id,row-id) { list-style-image: url("chrome://xfly/skin/images/outliner.gif"); }
Also, -moz-tree-twisty allows you define an image for the twisty that is used to open and close a level in a tree.
treechildren:-moz-tree-twisty { list-style-image: url("chrome://xfly/skin/images/twisty.gif"); }
The example above uses a parameter of open, but if no parameter is specified, the default is closed, so you can have a different image for both states.
The <tab> widget can also take a list-style-image property in CSS.
<tab id="TabOne" class="tabbies" selected="1" label="Click Me!" oncommand="SelectTab(1);" />
In this case, the class attribute is used as a selector for associating the element with the style rule in which the image is referenced:
.tabbies { list-style-image: url("chrome://xfly/skin/images/tab.gif"); }
3.6. Form Controls
In the HTML world, the textbox is one of the most commonly used elements in a form control. While the XPFE toolkit has no concept of a form, it was originally designed to allow HTML in the UI when needed, but only on a limited scale. Although it's still possible to incorporate HTML when you use the correct namespace, the existence of XUL widgets such as the textbox, checkbox and radio group selector obviates the need for HTML elements.
3.6.1. Radio
Radio groups are useful UI controls that present the user with a choice of options in XUL. In HTML, radio choices are represented by the <INPUT> element with the type attribute set to the value of radio, all wrapped in a form element. Example 3-14 shows how to make radio group choices in XUL.
Example 3-14. A radio group choice of options
<radiogroup id="flyTypes" orient="vertical"> <radio id="tachina" group="flyTypes" label="Tachinidae" oncommand="chooseType(this);"/> <radio id="primitive-crane" group="flyTypes" label="Tanyderidae" oncommand="chooseType(this);"/> <radio id="crane" group="flyTypes" label="Tipulidae" oncommand="chooseType(this);"/> <radio id="flower" group="flyTypes" label="Syrphidae" oncommand="chooseType(this);"/> <radio id="fruit" group="flyTypes" label="Tephritidae" oncommand="chooseType(this);"/> </radiogroup>
The options must be enclosed in the <radiogroup> element, and each one is represented by a <radio> element. The important attributes are the id on the <radiogroup> and the group attribute on the <radio> elements. These attributes have to be identical to ensure that only one option at a time can be chosen. The this keyword in JavaScript lets you access the selected item in a straightforward way. In this case, it sends the node to the script every time an option is selected.
3.6.2. Checkbox
A checkbox is a simpler widget that needn't be part of a selection group. It is often used to indicate if some functionality should be turned on or off, as shown in Figure 3-7.
<checkbox id="closeWindow" label="Close this window when download is complete" checked="true" />
Clicking on the box sets the checked attribute, for which the check indicates a positive value. You can set this attribute in script to give the checkbox an initial value.
3.6.3. Buttons
A button is a multipurpose widget that commonly lives in toolbars and dialog boxes. The two button elements, <button> and <toolbarbutton>, are essentially the same. Often only the class attribute values distinguish the two. You can use a <toolbarbutton> outside a toolbar or use a <button> inside a toolbar, though in practice, the two usually stay in their respective domains. This flexibility has the nice effect of letting you get the buttons in a particular area by using the getElementsByTagName method with, for example, the tag name “button.”
A common form of the button contains text and an image, with image on the left and the text to the right by default. However, you may want to take advantage of some of the classes available in Mozilla to define a different orientation, or you can simply write your own style rules for your buttons.[1] The text that appears on the button is contained in the label attribute and shown in this example:
<button id="newfileBtn" tooltiptext="New File" oncommand="doNew( )" label="New"/>
You can associate the image with the button using the src attribute, but the more common way is to use the list-style-image style rule in CSS, as in the following snippet of code that uses the id style selector:
#newfileBtn { list-style-image: url("chrome://editor/skin/images/newfile.gif"); }
3.6.3.1. Button types
Mozilla provides more than the standard “click” and “go” buttons in its toolkit. Table 3-3 describes the various button types in Mozilla.
Taking one of the button types in Table 3-3 as a mini-case study, you could use a button with the type menu-button to display more than one option at a time. The default orientation for this type of button is for the menu to be to the right of the button. Mozilla uses buttons of type menu-button for its back and forward buttons, in which the menu items hold previously visited pages. Figure 3-8 shows the appearance of the browser's back button displaying the last several pages viewed.
Other possible uses include options for different variations of the same feature, such as a New button that displays New File, New Project, or New Template options. The button action is the default option and the menuitems contain the rest of the choices.
3.6.3.2. Dialog buttons
The last four items in Table 3-3 are button types that make most sense in, and were designed especially for, dialog windows. The easiest way to include them in dialogs is to use the buttons attribute on the <dialog> element, which displays them automatically, as shown here:
<dialog xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" buttons="accept,cancel,help" buttonpack="center" ondialogaccept="return onAccept( );" ondialogcancel="return onCancel( );" ondialoghelp="return doHelpButton( );">
The functions activated when these buttons are clicked on are defined in the ondialogaccept, ondialogcancel, and ondialoghelp event handler attributes. These event handler shortcuts are best if you simply want to inherit the default button text (Ok, Cancel, and Help). In cases when you want your own text, or want some extra control over the scripting, you can define your own button with the dlgtype attribute:
<button dlgtype="accept" label="Go For It!" oncommand="doExtraFunction( )"/>
The buttonpack attribute determines whether the buttons appear on the right, left, or center of the window. If no value is given, the default platform orientation takes effect. On Windows, the default is the right, and on Unix, it's the center.
Notes
3.7. Widget Interaction
At a level above the use of widgets for different, singular functions in the application interface, Mozilla provides tools for hooking things together and creating application logic that can make your interfaces work more consistently and handle more complex tasks. If you have different elements in your application that execute the same function, for example, the command and observer system is the ideal way to facilitate reuse. Or you can use command sets to define command sets and key sets that can be overlaid and made available in different parts of your application, similar to how the cut and paste commands and others are spread over the Mozilla user interface but defined in a centralized file.
3.7.1. Broadcaster and Observers
Broadcasters and observers are a mechanism for making any number of elements aware of state and event information from a single, “broadcasting” element. That broadcasting element can be an actual <broadcaster> or a regular element that broadcasts its state with special attributes. A common example of broadcasting is the disabling of a group of elements -- a menu item and a separate button for viewing source, for example -- when the source for a web page is not available.
The state of a broadcaster has to be changed explicitly for its observers to be updated:
<broadcasterset> <broadcaster id="save_command” disabled="false"/> </broadcasterset>
Once a broadcaster is defined, a XUL file may define elements that observe the broadcast command:
<button id="new" label="Save File" observes="save_command"/> <key id="key_new" xulkey="true" key="s" observes="save_command" /> <menuitem id="new_menuitem" label="New" observes="save_command"/>
Observing elements can also be more specific about the attribute they want to mimic. This is done by using the <observes> element:
<menuitem id="new_menuitem" value="New" observes="open_new"/> <observes element="open_new" attribute="disabled"/> </menu>
The element attribute associates the broadcaster and attribute tells the <menuitem> element to mimic the behavior of the broadcaster's “disabled” attribute.
3.7.2. Commands
Any number of commands can be contained in a <commandset>, and multiple sets can exist for different events in your application. It is also possible for sets to contain other command sets, mixed with commands or on their own. The idea is that there will be one base set that all other sets must inherit from; this base set can be defined in the top-level XUL file for your application. The following code has a command set that has its own commands and that pulls in a second set defined elsewhere (moreEditItems).
<commandset id="EditItems" oncommandupdate="updateCommandsetItems(this)" commandupdater="true"; events="select"> <commandset id="moreEditItems" /> <command id="cmd_cut" oncommand="goDoCommand('cmd_cut');"/> <command id="cmd_copy" oncommand="goDoCommand('cmd_copy');"/> <command id="cmd_delete" oncommand="goDoCommand('cmd_delete');"/> </commandset>
The command updater is the mechanism used to pass command events between widgets in the UI. When an event is carried out, the message filters through to the command sets. Thus in the example above, if the select event is activated, all UI elements in this commandset become active. For example, setting the disabled attribute on a command set for saving disables all functional elements depending on it -- such as a menu item, a toolbar button, or a pop-up menu.
There are a number of ways to trigger the command updater. First, associate a widget with a particular command by using the command attribute:
<button id="cut-item" label="Cut" command="cmd_cut" enabled="true"/>
When this button is clicked, the command (cmd_cut) is located and carried out, firing the goDoCommand routine for that particular command.
Alternatively, your application might have a select event for a text element or an image. When the select event is fired, the message filters through to the command set, which, in turn, updates (by using oncommandupdate) the widgets-associated button with the commands.
The <keyset> element is a container for key elements. Key elements are used to execute commands from a keystroke combination. The keys Ctrl-Shift-s can be defined to execute a Save As command in your application (and that command can actually be defined in a command element):
<key id="key_saveas" key="s" modifiers="control,shift" command="cmd_saveas"/>
The key element has various special attributes like key, which is used to set an identifier shortcut key, or the modifiers attribute to set the trigger key. For example, modifiers="accel" would be the Ctrl key on Windows and GTK Unix platforms and the command button on Macintosh.
Example 3-15 shows a simple window that you can load up that has all element sets: commands, broadcasters, and keys.
Example 3-15. Shortcut keys with command observers
<?xml version="1.0"?> <window id="hello-goodbye" title="Hello Goodbye" xmlns:html="http://www.w3.org/1999/xhtml" xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" style="min-width:100px;min-height:100px;background-color:white;"> <broadcasterset id="broadcasterset"> <broadcaster id="cmd_hello" oncommand="alert('Hello There!');" /> </broadcasterset> <keyset id="keyset"> <key id="key_h" key="H" observes="cmd_hello" modifiers="accel,shift" /> <key id="key_g" key="G" command="cmd_goodbye" modifiers="accel,shift" /> </keyset> <commandset id="commandset"> <command id="cmd_goodbye" oncommand="alert('Goodbye!');" /> </commandset> <spacer flex="1"/> <label value="hello/goodbye"/> <textbox value="type ctl+shft+h"/> <textbox value="type ctl+shft+g"/> <spacer flex="1"/> </window>
3.8. Content Panels
Content widgets allow you to load content into the UI for display. These widgets -- browser and editor -- provide a window into which you can load. In the standard browser, these documents can be written in HTML, XML, text, or other supported content types.
3.8.1. Browser and IFrame
The <browser> element displays online content and provides full browsing capabilities to your application, such as navigation features or maintaining a history.
<browser id="content" type="content-primary" src="ch3.html"/>
The behind-the-scenes implementation for browser gives you access to certain interfaces that can be used in your scripts. These interfaces include:
- nsIDocShell
- nsIWebNavigation
- nsIMarkupDocumentViewer
- nsIContentViewerEdit
- nsIContentViewerFile
- nsIWebBrowserFind
- nsIDocumentCharsetInfo
Without going into detail, these interfaces all provide sophisticated functionality for web browsing and other browser-like services, and are made available to JavaScript in the application interface. You can explore them further by looking at the interfaces themselves -- at the IDL files of the same name in the Mozilla source tree.
If you would like to learn more about these available interfaces, the best place to look is the source code. The two recommended files to start with are browser.xml, which shows how the interfaces are exposed, and navigator.js, which shows how they are used. Both files can be browsed on the online Mozilla Cross Reference, at http://lxr.mozilla.org.
An alternative to <browser> is the <iframe>. It's similar to the browser widget in appearance, but better suited for simple or ephemeral content. It's often used as a preview window in HTML/XML editors and other WYSIWYG applications. iframes can also be good for dynamic document editing, as in the following example, in which the frame provides access to the document loaded as content. This can then be written to:
<iframe id="simple-content" />
The document's open( ), write( ), and close( ) methods, which are standard in the JavaScript engine, are used to write to the document:
var doc = window.frames[1].document; doc.open( ); doc.write("<html><body>Come fly with me ...</body></html>"); doc.close( );
In this code snippet, you get a handle to the particular frame that you want by using window.frames, which returns an array of all frames contained in a document. There can be multiple frames in a document, which are indexed. Here it is assumed that we get the second (1 in a zero-based array) frame. The doc variable has a reference to the content area and uses the methods available on the document object to write content -- in this case, HTML.
Ideas for using content panels include:[1]
- Create HTML or XML help pages for your application and upload them in a ready-made help browser.
- Create a previewer: test your XML, HTML, or CSS layout and styling in Gecko -- one of the most standards-compliant layout engines around.
- A slight variation of the previous use, you could use mini-versions inline in dialogs to load up examples that change depending on the selection of the user from a number of choices (a font previewer, for example).
- Pop ups contained in a window for display of web content.
3.8.2. Editor
The <editor> element loads editable content and can handle text or HTML editing. A good example of its usage is in Mozilla Composer, the HTML editor that comes bundled with Mozilla.
The <editor> tag creates an instance of the nsEditorBoxObject interface when it's initialized. From that point, you can use JavaScript (via the element.editorShell property) to get to the editorShell methods for carrying out editing on the loaded document.
The editor is also used in the various XUL and HTML text widgets in Mozilla, such as textbox and HTML forms, and for composing HTML messages in Mail and News. The text widgets differ from the full-blown editor because they act on a subtree of the document. Also, text widgets have limited text-editing services.
Uses for the editor, both practical and speculative, include:
- Plain text editor
- Web forms editor
- An HTML-enabled bulletin board, a guestbook entry form, or a Wiki that is a web interface collaboration area for posting comments
- Instant Messaging
Keeping Track in Multiframe Documents
The content contained in a <browser>, <iframe>, and <editor> is treated as a separate document. At times, you may want to access that specific content, so how do you keep track of different content?
To return all the content frames in a document, you can use:
var contentAreas = content.frames;
There are two ways to access specific content in a script: through the index of the frame within the containing document or by using the id attribute.
By index, starting at 0:
var content = window.frames[ 1 ];
By id:
var content = window.frames[ contentId ];
This code returns the second frame.
To flag one as default, use the type attribute and give it a value.
<iframe id="print-panel" type="content-primary" src="about:blank" flex="1""/>
This code allows quick access to the default via the window.content property:
window.content.print( );
Notes
3.9. The Box Model
The box model is the basic layout mechanism in XUL. Although it's possible to position your widgets in a window by using layout attributes of the window (a box-based container), using boxes allows you to arrange, nest, and position your widgets the way you want. The box model defines:
- How much space elements take up in relation to their siblings and containing elements
- The orientation of elements
- The relationship of elements to one another
Space can be determined in a number of ways. You can constrain size by putting fixed sizes on windows or the widgets contained therein. Or you can let the natural sizes take effect and let the elements size themselves according to their content. Applying boxes to your layout uses space efficiently and optimizes the layout of your XUL windows and dialogs.
3.9.1. Box Attributes
The XUL element <box> defines a number of attributes and some implicit behavior for layout. Boxes can be oriented within other boxes to define the general layout of the UI. Some boxes stretch to fit the space available within the top-level window or their containing element; others take only as much space as needed to display their own children.
Attributes on the box and its child elements determine the flexibility of the content contained within the box, the way that windows are resized, and the alignment of elements, as Table 3-4 describes.
The attribute names in Table 3-4 (with the exception of style) are defined directly on the box. But there are also CSS versions of these properties that use the prefix box-. pack becomes box-pack when it's defined in CSS, for example. These properties are not part of the CSS specification, so you may need to go one step further and use the format -moz-box-pack. These special extensions to CSS are described in the section Section 4.2.3 in Chapter 4.
The most commonly used attributes are orient, align, and pack. The orientation of the children of a box can be either vertical or horizontal. The default is horizontal for a plain <box>, but not for all box containers (<groupbox> is vertical). The <vbox> and <hbox> conveniences were created to bypass the use of this attribute and increase box layout efficiency in the rendering phase.
Here is a look at how the pack and align properties can effect the layout of widgets. First, here is a bit of code with no constraints:
<vbox style="width: 90px; height: 90px"> <button label="Pack Me!" /> <label value="This text is naturally aligned to the left" /> </vbox>
This XUL does not tell the button and text inside where to go, so they occupy the default positions shown in Figure 3-9.
Here is a changed box definition with the align and pack attributes set:
<vbox style="width: 90px; height: 90px" align="right" pack="center">
A noticeable visual difference can be seen in Figure 3-10.
The align value moves the items to the right of the box, while simultaneously constraining the button to fit only the text label, making better use of space. pack centers both the items horizontally.
3.9.2. Box-Like Containers
The basic XUL box is represented by the <box>, <vbox>, and <hbox> elements, but several more XUL elements are designed as box-like containers. They include:
- <radiogroup>
- <scrollbox>
- <tabbox>
- <groupbox>
- <toolbox>
- <stack>
- <deck>
- <listbox>
- <popup>
- <statusbar>
Descriptions of the tabbed box and the group box follow. Additional information on other box widgets can be found in the XUL element reference in Appendix C.
3.9.2.1. Tab box
Tab boxes may contain only <tabs> and <tabpanels> elements, as shown in Example 3-16. Beyond this, there is no restriction on the content that can go into the panels themselves. For the panels to display content properly, there have to be the same number of children and tabs in the tab panels.
Example 3-16. Tabbed panels
<tabbox orient="vertical" flex="1"> <tabs> <tab label="Fish" /> <tab label="Birds" /> <tab label="Coders" /> </tabs> <tabpanels flex="1"> <button label="Swim"/> <button label="Fly"/> <button label="Hack"/> </tabpanels> </tabbox>
Example 3-16 shows the main controls used to create a simple three-tab control with content elements on each panel. The tabs are associated with the appropriate panels by their order within the containing element.
3.9.2.2. Status bar
A status bar is a horizontal box that appears on the bottom of the screen in many Mozilla applications, including the Mozilla browser itself. It can be used for the same purpose in your application if you need it. The <statusbar> element typically contains icon images and text within one or more <statusbarpanel> elements:
<statusbar id="ch3-bar" persist="collapsed"> <statusbarpanel class="statusbarpanel-iconic" id="book-icon"/> <statusbarpanel id="status-text" label="Thanks for reading Chapter 3" flex="1" crop="right"/> <statusbarpanel class="statusbarpanel-iconic" id="book-icon-2"/> </statusbar>
As a box, the statusbar behaves like any other box widget. The panels constrain to their natural sizing and layout attributes such as flex situate all elements within. In this example, the icons appear to the left and right of the bar, while the flexed text panel takes up the remaining space.
3.9.3. Additional Box Features
Boxes work in concert with a few other special elements, including the <separator> and <spacer>. These two elements create space between widgets in a box and can be horizontal or vertical depending on the orientation of the box. The separator is a visible divider and the spacer is invisible space. The default size for both of them is small, but they can be given flex, width, and height values like other elements. Used correctly, they can make all the difference in how your UI looks.
3.9.3.1. Visibility
You can control the visibility of a box by showing and hiding it in different circumstances -- toggling the appearance of an advanced panel in a dialog, for example, or text that appears after a user selects something. One way to control visibility is to use the collapsed attribute to cede the space taken by the box to surrounding elements:
<box flex="1" collapsed="true" />
You can also set the CSS display property to none:
<box flex="1" style="display: none;" />
The document is rendered as though the element did not exist in the document tree. If you want the space to be maintained but the element to remain invisible, you can use the CSS visibility property:
<box flex="1" style="visibility: hidden;" />
Rendering the space but not the content avoids flicker and UI wobbles when content is being shown and hidden intermittently.
3.9.3.2. Overflow
A value of scroll or auto for the CSS overflow property ensures that a scrollbar appears and that the content can be accessed even when it can't be displayed. A value of hidden hides the content outside of the box. The content is not clipped if this value is set to visible, but it will be visible outside the box.
<vbox flex="1" style="height:39px;overflow: auto;">
This snippet constrains the height of the box but displays a scrollbar when the content exceeds the available space.
3.9.4. Stacks and Decks
A variant and special model of the box is available in the stack and deck elements. Both are boxes, but they lay their children out one on top of the other like a stack of crates or a deck of cards, rather than sequentially.
A stack shows all its levels at once. If you have transparency or extra space on one level, you can see underneath. Stacks are useful when you want to add shadows in your content or if you want to create transparent layering effects. If you have a bird's eye view of XUL content, you can see the elements on each layer flowing into each other, like the text on top of an image in Figure 3-11.
<stack> <image src="logo5.gif"/> <label value="BUZZ ..." style="font-weight:bold; font-size: large" top="70px" left="140px"/> </stack>
Decks show only one level at a time. In Example 3-17, logo3.gif is foremost because the selectedIndex attribute on the deck is set to 2 in a zero-based index.
Example 3-17. A deck with three image layers
<deck id="fly-deck" selectedIndex="2"> <image src="logo1.gif" /> <image src="logo2.gif" /> <image src="logo3.gif" /> </deck>
As Example 3-18 shows, it is possible to switch pages using the DOM by changing the index on the deck. The setAttribute method changes the selectedIndex attribute of the deck element in script and can be executed, for example, when a user clicks a button or in other places in the interface.
Example 3-18. Deck layer switching
var deck = document.getElementById("fly-deck"); var selected = deck.getAttribute("selectedIndex"); if (!selected) selected = 0; if (selected < 2) { selected = parseInt(selected) + 1; deck.setAttribute("selectedIndex", selected); } else { selected = 0; deck.setAttribute("selectedIndex", selected); }
When applied to the deck in Example 3-17, the code in Example 3-18 continuously flips to the next image in the sequence and returns to the top of the deck when the last image is reached. In this case, there are only three images, so a maximum of 2 is put on the index check.
3.9.4.1. Moveable content
At one point in XUL toolkit development, an element called the bulletinboard allowed you to layer child elements one on top of another like its real-world namesake. You could change the coordinates of the child elements on the screen by using the top and left attributes. The order of the children in the XUL content determines the z-ordering on screen. As stack developed and took on much of this functionality, bulletinboard was officially deprecated and some of its properties were merged back into the stack element. As Example 3-19 demonstrates, this was a boon for the stack element, which can use coordinates to position children like its ancestor. The two boxes in the example are positioned at varying distances away from the stack's top left corner.
Example 3-19. Content positioning in a stack
<stack> <box id="box1" top="20px" left="40px"> <image src="logo1.gif" /> </box> <box id="box2" top="40px" left="50px"> <image src="logo2.gif" /> </box> </stack>
You can position the two boxes, each containing an image, in any of the following ways:
- By placing the top and left attributes directly on the tags
- By setting them via stylesheets using the CSS properties top and left
- By using DOM calls in your script
Here is some script used to switch the position of the boxes from one location to another by changing the top and left attributes:
Box1=document.getElementById("box1") Box1.setAttribute("top","40px") Box1.setAttribute("left","50px") Box2=document.getElementById("box2") Box2.setAttribute("top","20px") Box2.setAttribute("left","40px")
3.10. XUL Attributes
Each XUL element has an attributes property that contains an array of all its attributes. This section summarizes some of the general XUL attributes that developers find useful, including debug.
3.10.1. Stretchiness
An object becomes flexible when the flex attribute is placed on the element. Flexible objects can shrink or grow as the box shrinks and grows. Whenever extra space is left over in a box, the flexible objects are expanded to fill that space. Flex is specified as a numerical value, and all flex is relative. For example, a child with a flex of 2 is twice as flexible as a child with a flex of 1, as Example 3-20 shows. The flex attribute is invaluable for positioning elements in the box model.
Example 3-20. Flexible buttons
<?xml version="1.0"?> <?xml-stylesheet href="chrome://global/skin" type="text/css"?>
<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> <box id="parent" style="margin: 50px;"> <button flex="2" label="flex2" /> <button flex="1" label="flex1" /> </box> </window>
3.10.2. Style
The style attribute allows you to apply CSS style properties to your XUL element directly within the content. The attribute lets you access CSS properties (including width, height, min-width, min-height, max-width, and max-height), which give you more control over the size of your widget.
<button style="height: 20px; background-color: blue;" />
Don't use the style attribute too often, though, especially if you want to have more than one theme for your application. See the section Section 4.2.1.3 in Chapter 4 for information about how this attribute can make your application less modular and, for some, a better way to apply style to your XUL documents.
3.10.3. Persistence
The persist attribute preserves the state and appearance of a widget on an attribute-by-attribute basis. This feature is useful for properties that change or are set by users during a session, such as window size and positioning, splitter state, and the visibility of certain elements.
<splitter id="sidebar-splitter" collapse="before"
persist="state hidden" align="center" orient="vertical">
When the state of the splitter changes -- when a user handles the <grippy> and collapses the splitter, for example, and then quits -- the persist attribute preserves the splitter state and its visibility for the next session.
3.10.4. The debug Attribute
Many of XUL elements, particularly container elements like box and toolbar, support the debug attribute as a useful tool for developers. If debug is set to true, extra borders are drawn around the element and all its children, with different color coding for each level. This setting can be used to check the orientation and the flexibility of elements. debug displays horizontal boxes with a blue border and vertical boxes with a red border, for example, making it easy to see subtle differences or layout bugs. The border above the element will be straight for nonflexible elements and grooved for flexible elements.
3.11. Overlays
An overlay is a separate file in which additional XUL content can be defined and loaded at runtime. Overlays are often used to define things like menus that appear in different components or parts of the application.
If you are creating a large application or a UI with many elements as a part of your design, the files can easily become large. The size in itself does not render it ineffective, but it does make the job of the developer a little difficult when tracking down and changing features. The best way to overcome this size problem is to use overlays. Another reason to use overlays is to extract information from a certain logical portion of the UI and contain it in a file of its own. This extraction and containment promotes modularization and reusability.
3.11.1. How to Use Overlays
The following declaration is the principal method for including reusable content in a XUL window.
<?xul-overlay href="chrome://global/content/globalOverlay.xul"?>
This declaration follows the same syntax as CSS processing instructions. Like other XML processing instructions, it uses a ? at the beginning and end, just inside the braces. The href attribute points to the overlay and uses Mozilla's chrome:// type URL.
To insert content from an overlay, use the same id of an element in the “base file” for a similar element in your overlay content, and the overlay will replace the base file at runtime (or be merged with it, as described later in this chapter in the Section 3.11.2 section).
When the base element is empty, it is replaced with the corresponding overlay element and any child subcontent. The following toolbar snippet shows a reference placed in a base file:
<toolbar id="main-toolbar" />
When an overlay is read with the content below, the previous line is replaced with that content:
<toolbar id="main-menubar" persist="collapsed"> <toolbarbutton id="new-button” label="New" observes="cmd_new"/> <toolbarbutton id="open-button” label="Open" observes="cmd_open"/> <toolbarbutton id="save-button” label="Save" observes="cmd_save"/> </toolbar>
Overlay files are XUL files with a .xul extension. The content within that file has to be contained in an <overlay> element, which is the root of the document. For example, the toolbar is a first level child of the root.
<overlay id="xflyOverlay"> <toolbar id="main-toolbar" /> <!-- more overlay content --> </overlay>
//FIXME did we loose content here?
Styles from overlays override styles from base XUL files, so be careful not to load master styles in an overlay.
3.11.1.1. Dynamic loading
The usual method for loading overlays, as outlined previously, is to include the overlay processing instruction in your XUL file. The dynamic loading of content is more subtle, but just as effective. Mozilla has a registry of overlays, in the form of an RDF datasource that lives in the chrome directory. These overlays live in the tree in a directory called overlayinfo under the chrome root.[1] When a new package or component is registered, the overlays that come with it are loaded automatically.
Dynamic overlays are commonly used to extend certain parts of the Mozilla application itself when new packages are installed that need access points, as do new language packages and themes, for instance. Certain menus in the UI, for example, are open for third-party authors to add items. Adding the name of your package to Mozilla's Tasks menu, for example, provides a convenient launching point and is handled with dynamic overlays. Chapter 6 provides more information on this topic, in the section Section 6.2.3.3.
3.11.2. Content Positioning
Content positioning is the order in which widgets appear in the UI. Usually content is laid out in the order elements are defined in the XUL file. However, there are a couple of ways to override this ordering in XUL.
Continuing with the example of the overlaid toolbar in the previous section, it is possible for both the base definition and the overlaid definition to have children. In this instance, the content is merged, with the original content appearing before the overlaid content by default:
<toolbar id="main-toolbar"> <toolbarbutton id="print-button" label="Print" observes="cmd_print"/> </toolbar>
If the toolbarbutton above is in the base XUL, then the ordering of the buttons would be Print, New, Open, and Save. It is possible to change this ordering by using insertbefore, however, as shown in Example 3-21.
Example 3-21. Positioning attributes
<toolbar id="main-toolbar" persist="collapsed"> <toolbarbutton id="new-button" label="New" observes="cmd_new" insertbefore="print-button"/> <toolbarbutton id="open-button" label="Open" observes="cmd_open"/> <toolbarbutton id="save-button” label="Save” observes="cmd_save" position="2"/> </toolbar>
The insertbefore attribute is placed on one of the child items to signify that it should go before a sibling in the base file. insertbefore takes an element id as a value and says, in this case, that the New button should go before Print. Conversely, you can move an item after it by using the insertafter attribute. For more precision, you can use position to position an item absolutely in the sequence of siblings. In Example 3-21, the position attribute puts the Save button in the second position, so the final order is New, Save, Print, and Open.
Notes
3.12. The Extras
Certain lesser-known elements and features are indispensable to the savvy XUL developer and can add that something extra to Mozilla applications, as shown here.
3.12.1. Tooltips
Tooltips are visual pop ups that appear when you place the cursor over a piece of the UI. The hovering behavior of a tooltip is useful for many things, including abbreviated help and the display of values that are otherwise obscured in the UI. In the Mozilla application, the most common places where they are used are on toolbar buttons and splitter grippies that divide panels in the window.
To invoke a tooltip, add a tooltiptext attribute to the widget that needs it:
<button id="printButton” label="Print” tooltiptext="Print this page” />
Defining this attribute is enough to ensure that the generic Mozilla tip box appears with the specified text when you place the cursor over the element.
Tooltips are actually implemented as an XBL binding. Underneath, a tooltip is essentially a pop up with a description element within that holds text. You can also create your own tooltips.
To create your own content and customized appearance for a tooltip:
- Create the content.
- Attach it to the pop-up element you will be using.
- Give the pop up a unique ID.
The following snippet shows the kind of tooltip you can create and then reuse in your application code:
<popupset id="aTooltipSet"> <popup id="myTooltip" class="tooltip" onpopupshowing="return FillInTooltip(document.tooltipNode);" > <description id="TOOLTIP-tooltipText" class="my-tooltip-label" flex="1"/> </popup> </popupset>
Use your newly created widget by adding its id value to the tooltip attribute to the UI element that wants it:
<treeitem id="FlyDescription" tooltip="myTooltip" tooltiptext="" />
Note that this example assumes that the actual text will be applied dynamically to the tooltiptext attribute, which is initially empty. This is useful in many situations -- for example, in tree cells that contain transient values.
The advantage of creating your own tooltip is that you can apply your own styles to it, giving the text and background whatever font and colors you want. A variation of the tooltip attribute named contenttooltip is used for content panels.
3.12.2. Progress Meter
Sometimes in your application you need to give the user feedback during a long operation. The classic example in the browser is the status bar that shows a visual representation of the time remaining when you load a big web page or download a file.
Of these two activities, loading pages and downloading files, downloading uses the determined mode, meaning that the time to complete the operation is calculable. In this case, an algorithm is written based on the file size and the bandwidth values to formulate the time remaining. The second of three modes of a progress meter is the undetermined mode, in which the time for the operation to complete is unknown. Commonly called the “barber pole,” the progress meter shows a spinning pole when in undetermined mode. The third mode is normal, which shows an empty bar. You can get/set the mode by using the mode attribute.
Here is the XUL for a sample progress meter:
<progressmeter id="progressTask" mode="normal" value="0" onclick="alert('Task is in progress')"/>
Here is the accompanying script for activating the progress meter:
var meter = document.getElementById('progressTask'); meter.setAttribute('mode', 'undetermined'); sometask( ); meter.setAttribute('mode', 'determined'); meter.setAttribute('value', '100%');
The mode is changed to undetermined just before carrying out the task, and is represented by the function sometask( ). The JavaScript code is synchronous, so it will not hand back control until the operation is complete.
3.12.3. Links
Mozilla is a web application, and many programs and operating systems (e.g., Windows XP) are moving toward full web integration. Linking is fundamental in application programming, so Mozilla provides a couple of ways to do it in your XUL document.
3.12.3.1. Use of the <html:a> element
To use HTML in your XUL file, you must define the HTML namespace at the top of your document:
<window id="MyOverlay"
xmlns:html="http://www.w3.org/1999/xhtml"
xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
Then you can use the HTML elements just as you would in a regular web page, but with the addition of the namespace you declared:
<vbox> <html:a href="myOverlay.html">Go to Help page</html:a> </vbox>
When you use a page with code in your application, the user can click the link and open a Mozilla browser to the requested page or item.
3.12.3.2. Simple XLinks
You can also tap into the more sophisticated XML capabilities in Mozilla by trying a simple XLink. Again, the correct namespace is required:
<window xmlns:xlink=http://www.w3.org/1999/xlink ...>
Then you define your link as follows:
<xlink:link xlink:type="simple” xlink:href="c.xml">c.xml</xlink:link>
The element here is link, the type is simple, and the locator is href.
3.13. Building the Application Shell
Now that the main XUL widgets and some crucial concepts like the box model have been described, you can bring things together and create an application shell, a user interface that isn't (yet) hooked up to application code, but which can be re-used for different applications.
The XUL in Example 3-22 extends the xFly application work you've already done in Chapter 2. It defines the interface for a viewer that will let you browse the examples in this book, giving xFly a measure of introspection. Examine the code closely in Example 3-22 to give yourself a feel for how the elements in the UI interact with each other to form something that is greater than the sum of its parts. Look particularly at how box elements are used such as vbox, hbox, tabbox, and statusbar.
Example 3-22. xFly application main workspace
<?xml version="1.0"?> <?xml-stylesheet href="chrome://global/skin" type="text/css"?> <?xml-stylesheet href="chrome://xfly/skin" type="text/css"?> <?xul-overlay href="chrome://xfly/content/xflyoverlay.xul"?> <!DOCTYPE window SYSTEM "chrome://xfly/locale/xfly.dtd"> <window title="&window.title;" xmlns:html="http://www.w3.org/1999/xhtml" xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
type="xfly:main" width="800" height="600" onload="onLoad( )"> <script type="application/x-javascript" src="chrome://xfly/content/xfly.js" /> <stringbundle id="bundle_xfly" src="chrome://xfly/locale/xfly.properties"/> <toolbox> <menubar id="appbar"> <menu label="xFly"> <menupopup> <menuitem label="Close" oncommand="exitxFly( )"/> </menupopup> </menu> <menu label="Examples"> <menupopup> <!-- items to go here --> </menupopup> </menu> <menu label="Help"> <menupopup> <menuitem label="About" oncommand="doAbout( )"/> </menupopup> </menu> </menubar> </toolbox> <hbox flex="1"> <vbox id="left-frame"> <tree id="example-tree" /> <hbox align="start"> <image src="chrome://xfly/skin/images/logo5.gif" /> </hbox> </vbox> <splitter collapse="before" resizeafter="grow" persist="state"> <grippy /> </splitter> <tabbox id="raven-main-tabcontent" flex="1" orient="vertical"> <tabs orient="horizontal"> <tab id="tab-view" label="View Example"/> <tab id="tab-source" label="View Example Source"/> </tabs> <tabpanels flex="1"> <iframe id="right-frame" name="right-frame" flex="3” src="chrome://xfly/content/examples/2-1.xul"/> <iframe id="right-frame-source" name="right-frame-source" flex="3" src="view-source:chrome://xfly/content/examples/2-1.xul"/> </tabpanels> </tabbox> </hbox> <statusbar id="ch3-bar" persist="collapsed"> <statusbarpanel class="statusbarpanel-iconic" id="book-icon"/> <statusbarpanel id="status-text" label="Thanks for reading the book!" flex="4" crop="right"/> <statusbarpanel class="statusbarpanel-iconic" id="example-status" flex="1"/> </statusbar> </window>
The main application windows consists of a menu bar, two frames, and a status bar. The menus provide access to application-level functions like closing the window, or launching an “About” window. At the bottom of the window, the status bar displays the book icon and some status messages for the application. Between the menu bar and the status bar are the two main panels: a vertical box (<vbox>) on the left that contains a tree for choosing examples with the xFly logo beneath it, and an <iframe> into which the examples are loaded on the right. There are two tabs in the example pane, one for showing the example rendered and one for looking at the source.
The code in Example 3-22 is not the final code for xFly, but it does show some important widgets used for the main layout of the application. But the layout in Example 3-22 (in which a <toolbox> holds the menus, a <statusbar> displays messages from the application, and the box model is used to layout the application display) is a very useful template for XUL applications.
What remains to define is the tree structure that actually holds the various examples. In Example 3-22, the <tree> has an ID attribute that is meant to pick up content defined in an overlay. Example 3-23 shows what such an overlay would look like, but if you'd rather, you can take the content of the <tree id="example-tree"> element in this example, define it as direct content of the <tree> in Example 3-22, and end up with the application shell shown in Figure 3-12. See the section Section 3.11 earlier in this chapter for more information about how to add content to your XUL using overlay files.
Example 3-23. Example tree in the xFly application
<?xml version="1.0"?> <overlay id="xflyOverlay" xmlns:html="http://www.w3.org/1999/xhtml" xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> <tree id="example-tree" onselect="onExampleSelect( );" seltype="single" hidecolumnpicker="false" enableColumnDrag="true" flex="1"> <treecols> <treecol id="type" label="Title" flex="1" primary="true" persist="width ordinal hidden"/> <splitter class="tree-splitter"/> <treecol id="method" label="Example" flex="1" persist="width ordinal hidden"/> </treecols> <treechildren> <treeitem container="true" open="true"> <treerow> <treecell label="Chapter 2"/> </treerow>
<treechildren> <!-- Second level row --> <treeitem> <treerow> <treecell label="Hello xFly" url="chrome://xfly/content/examples/2-1.xul"/> <treecell label="2-1"/> </treerow> </treeitem> </treechildren> </treeitem> <treeitem container="true" open="true"> <treerow> <treecell label="Chapter 3"/> </treerow> <treechildren> <!-- Second level row --> <treeitem> <treerow> <treecell label="Menu Bar" url="chrome://xfly/content/examples/3-5.xul"/> <treecell label="3-5"/> </treerow> </treeitem> <treeitem> <treerow> <treecell label="Listbox" url="chrome://xfly/content/examples/3-9.xul"/> <treecell label="3-9"/> </treerow> </treeitem> <treeitem> <treerow> <treecell label="Grid" url="chrome://xfly/content/examples/3-12.xul"/> <treecell label="3-12"/> </treerow> </treeitem> </treechildren> </treeitem> </treechildren> </tree> </overlay>
[1] Free-floating because their location in the interface is not determined by their position in the XUL markup, as it usually is for items like menus and buttons.
[1] Unfortunately, button skins and the class attributes that associate them with button widgets change too often to list here. Some classes like “toolbar-primary” tend to be reused often for buttons in Mozilla, but the best way to find and use classes is to consult the source code itself or to create your own.
[1] Note that these examples are distinct from embedding the Gecko layout engine in your generic application. A separate toolkit and a set of APIs is available for doing this.
[1] Chapter 9 has more information on RDF datasources. To delve deeper into the chrome layout and install issues, see Chapter 6.
Get Creating Applications with Mozilla now with the O’Reilly learning platform.
O’Reilly members experience books, live events, courses curated by job role, and more from O’Reilly and nearly 200 top publishers.