Adding windows and dialogs

Opening windows and dialogs

To open a new window, use the Javascript window.open function just like with HTML windows.

window.open(
  "chrome://xulschoolhello/content/someWindow.xul",
  "xulschoolhello-some-window",
  "chrome,centerscreen");

The first argument is the URL to open, the second is an id to identify the window, and the last is an optional comma-separated list of features, which describe the behavior and appearance of the window. If this value is null or empty, the default toolbars of the main window will be added to the new one, which is rarely what you want. The window.open page has a detailed description of the features you can use and their values. The following features are very important and you should always keep them in mind:

  • chrome. This can only be used from the chrome, not from regular HTML JavaScript. It indicates that a chrome document is being opened, not a web page. This means that the document defines the whole window, and not only the inner content area. You should always include it when opening a XUL window or dialog.
  • modal. Modal windows block their parent window until action is taken on them. An alert window is an example of this. Modal windows should be avoided when possible because they interrupt the user's browsing and can become annoying very quickly. This feature should only be used when there's no way to continue without having the user do something. Never open modal windows at startup.
  • resizable. Indicates the user can resize the window or not. In general, windows should not be resizable unless they display dynamically generated content from a datasource, such as lists or trees that may need resizing to have a better view of the content.

To open a new dialog, use the function window.openDialog, an extension of the open function. It allows you to send a set of optional parameters that can be used to communicate with the dialog.

let someValue = 2;
let returnValue = { accepted : false , result : "" };

window.openDialog(
  "chrome://xulschoolhello/content/someDialog.xul",
  "xulschoolhello-some-dialog", "chrome,centerscreen",
  someValue, returnValue); // you can send as many extra parameters as you need.

// if (returnValue.accepted) { do stuff }

The optional parameters are available in the dialog code through the window.arguments property:

let someValue = window.arguments[0];
let returnValue = window.arguments[1];

// returnValue.accepted = true;
// returnValue.result = "something";

The parameter named returnValue is an object that the dialog will modify to reflect what the user did in it. This is the simplest way to have the dialog return something to its opener. Note that the opener will wait until the dialog is closed. This means the openDialog function call will not return until the dialog has been closed by the user.

Common Dialogs and the Prompt Service

There are several types of dialogs that are fairly common, so there are ways to create them easily without having to reinvent the wheel and write all their XUL and JS code all over again. Whenever you need a new dialog, you should ask yourself if it can be implemented using these common dialogs, and use them whenever it is possible. They have been thoroughly tested for OS integration, accessbility and localization, so you save yourself a lot of work and favor them.

Using the Prompt Service is the recommended way to create common dialogs in an extension. Read the article and its examples carefully, because there are many useful functions to use in the Prompt Service. There are some equivalent, simpler functions that are available in the window object, but those are meant for unprivileged HTML JavaScript code.

Alert

The alert is the simplest form of dialog. All it does is display a text message that the user can read and then click the OK button to dismiss it. We have been using the window.alert function to open alert messages in our examples so far, but that's not the right way to do it. It's OK to use this function if you're just debugging some problem and want to see if the program reaches a specific line of code, or to inspect the value of a variable, but your final extension should not have alert calls anywhere.

If you use window.alert, the alert window will have the title [JavaScript Application], indicating that the source of the message is not well defined. The Prompt Service allows better alerts to be displayed. Here's an example of displaying an alert using the Prompt Service:

let prompts =
  Cc["@mozilla.org/embedcomp/prompt-service;1"].
    getService(Ci.nsIPromptService);

prompts.alert(window, "Alert Title", "Hello!");

You should of course use localized strings instead of hard-coded ones.

The Prompt Service allows you to set the title of the dialog however you want it, and also lets you specify the window you want to use as a parent for the alert. This normally should be set to the current window. You can pass a null value and the service will pick the currently active window.

Confirm

Confirmation dialogs display a text with a Yes / No question, and prompts the user to choose an answer. In HTML you can use the window.confirm function for this. The Prompt Service has a confirm method with similar behavior:

let prompts =
  Cc["@mozilla.org/embedcomp/prompt-service;1"].
    getService(Ci.nsIPromptService);

if (prompts.confirm(window, "Confirm Title", "Would you like to continue?")) {
  // do something.
} else {
  // do something else
}

The method returns a boolean value indicating the user's response.

Others

Unprivileged Javascript can also use the window.prompt function to receive text input from the user. The Prompt Service has a very rich set of functions that allow different kinds of inputs, such as text, passwords, usernames and passwords, and checkboxes that can be used for "Never ask this again"-type dialogs. The confirmEx and prompt methods are the most customizable, allowing a great deal of options that cover most common dialog cases.

Using the Prompt Service will save you a lot of XUL coding, and you'll be at ease knowing that you're using Mozilla's tried and tested code.

The Dialog Element

When the Prompt Service is not enough, you'll have to create you own XUL dialogs. Luckily, you still get a great deal of help from the platform if you use the dialog element as the document root instead of the more generic window element.

You may be asking yourself what's the big deal about defining a simple XUL window with an OK and maybe a Cancel button. The dialogs we have covered in this section are very simple and shouldn't be too hard to implement manually using XUL. Well, it's more complicated than that. Different operating systems order and position their buttons differently in their dialogs. There are also subtle aspects about window size, margins and paddings that are not the same for all systems, so you should avoid making dialogs from scratch or overriding the default dialog CSS styles.

The dialog element handles all of this transparently. All you need to do is define which buttons you'll need and the actions associated with them.

<dialog id="xulschoolhello-hello-dialog"
  title="&xulschoolhello.helloDialog.title;"
  buttons="accept,cancel"
  ondialogaccept="return XULSchoolChrome.HelloDialog.accept();"
  ondialogcancel="return XULSchoolChrome.HelloDialog.cancel();"
  xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">

Carefully read the specification of the dialog element. You'll see that you can choose from a wide variety of buttons, associate any action you need to them, override their labels, and even add completely customized extra buttons. All of this without having to worry about your dialog looking odd in some operating systems. The only constant rule is that clicking on OK and Cancel will close the dialog unless your associated function returns false. You should avoid returning false, though, specially with the Cancel button. Dialogs in general should be easy to dismiss.

Your dialogs shouldn't have fixed dimensions because of potential localization problems. Dialogs are sized to their contents and will generally work fine. However, there are cases where the dialog contents are generated or expanded dynamically by your code, and you'll need to resize them appropriately. The window.sizeToContent function is what you need in this case.

Input Controls

Most additional XUL windows and dialogs in extensions are some kind of input form. Let's look into the most commonly used form elements and how to use them in your XUL windows. There isn't much we need to add from what the XUL Tutorial explains, so go ahead and read the following sections:

There are some other aspects to take into account when handling input controls, which we cover in the following sections.

Groupboxes

The groupbox element should be easy to understand: it groups a series of XUL controls together. It's a box container with styling that is usually a visible border around its contents, so that it's clear what is being grouped together. It is frequently used with the caption element to associate the grouped elements with a title.

The groupbox shouldn't be seen as an aesthetic device, but a logical one. If all you need is a border, use CSS. The groupbox element should be used when enclosed elements share some function which is separate from other elements or groups in the same window. It's also a useful accessibility feature, because screen readers will read the caption right before reading any text in its contents. You can change its style using CSS in case you don't want the borders to appear. See the Firefox Preferences window for an example of this: sections are defined using groupbox elements, but their style is quite different from the default.

Attribute Persistence

User actions can change the state of your windows, such as selecting an option in a listbox, or entering text in a textbox. If the user closes and then reopens your window, all the controls are reset to their defaults, which may not be what you want. You need some way of remembering the user-manipulated attribute values so that the window reloads it last state when opened.

Most XUL elements support the persist attribute, which has this exact function. You set the persist attribute with a space-separated list of attribute names, indicating which attribute values must be persisted across window "sessions".

<checkbox id="xulschoolhello-some-checkbox"
  label="&xulschoolhello.someText.label;"
  checked="false" persist="checked" />

Setting the id attribute of the element is mandatory if you want the persist attribute to work. You can also set persistence programatically using the document.persist function:

document.persist("xulschoolhello-some-checkbox", "checked");

Persistent data is stored in the user profile, in the file localstore.rdf. You may need to modify or delete this file often when testing persistent data in your extension.

Focus and Tabbing

Moving through all input controls in a window using only the keyboard is an accessibility requirement. You can do this in most Firefox windows by pressing the Tab key. Each Tab key press moves you to the next control in the window, giving it focus.

In general, there's nothing you need to do in order to have good keyboard focus management. Firefox will automatically focus the first input control in your window, and tab focus advances in the order the items are found in the XUL document. If you have a very complex layout, or need customized tabbing behavior, you can set the tabindex attribute in the controls. You can also use the focus function to focus an element depending on events such as window load. You should do this carefully, to avoid having inaccessible controls.

You can also use the -moz-user-focus CSS property to enable focusing of elements that typically wouldn't receive focus. Again, this should be used sparingly.

This tutorial was kindly donated to Mozilla by Appcoast.