Dynamically modifying XUL-based user interface

This article discusses manipulating XUL interfaces, using DOM and other APIs. It explains the concept of DOM documents, demonstrates a few simple examples of using DOM calls to perform basic manipulations on a document, and then demonstrates working with anonymous XBL content using Mozilla-specific methods.

It is written for beginner to intermediate XUL developers. We assume that the reader has basic knowledge of both XUL and JavaScript. You may also want to read some introductory documents about DOM, like the About the Document Object Model article or the Introduction page of the Gecko DOM Reference.

Introduction

As you know, XUL is an XML language used in various Mozilla-based applications, such as Firefox and Thunderbird, to describe the user interface. In XUL applications JavaScript defines the behavior, using DOM APIs to access the XUL document.

So what are the Document Object Model APIs?

They are the interfaces that are used in any interaction of a script and a document. If you have ever written a script that interacts with a XUL (or HTML) document, you have already used DOM calls. The most well known DOM method is probably document.getElementById(), which returns an element, given its id. You may also have used other calls, such as element.setAttribute(), or, if you wrote an extension, the addEventListener() method. All of these are defined in DOM.

There are also DOM methods that create, move, or delete elements from a document. They will be demonstrated in a later section. Right now, let's learn what a document is.

What is a document?

A document is a data structure which is manipulated through DOM APIs. A logical structure of every document is a tree, with nodes being elements, attributes, processing instructions, comments, etc. Use the DOM Inspector tool to see the tree representation of any document. Todo: simple example of a XUL document and a tree

You can think of a document as an in-memory representation of valid HTML or well-formed XML such as XHTML or XUL.

It is important to remember that different web pages (and even different instances of the same web page) correspond to different documents. Each XUL window has its own distinct document, and there may even be a few different documents in a single window, when there is an <iframe>, <browser>, or a <tabbrowser> element. You must be sure to always manipulate the correct document. (Read more about this in Working with windows in chrome code.) When your script is included using a <script> tag, the document property references the DOM document that includes the script.

Examples: Using DOM methods

This section demonstrates the use of appendChild(), createElement(), insertBefore(), and removeChild() DOM methods.

Removing all children of an element

This example removes all children of an element with id=someElement from the current document, by calling removeChild() method to remove the first child, until there are no children remaining.

Note, that hasChildNodes() and firstChild are also part of the DOM API.

var element = document.getElementById("someElement");
  while(element.hasChildNodes()){
    element.removeChild(element.firstChild);
  }

Inserting menu items to a menu

This example adds two new menu items to a <menupopup>: to the start and to the end of it. It uses document.createElementNS() method to create the items, and insertBefore() with appendChild() to insert the created xml elements within the document.

Note:

  • document.createElementNS() creates an element, but does not put it anywhere in the document. You need to use other DOM methods, such as appendChild() to insert the newly created element in the document.
  • appendChild() appends the node after all other nodes, while insertBefore() inserts the node before the node referenced by its second parameter.
function createMenuItem(aLabel) {
  const XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
  var item = document.createElementNS(XUL_NS, "menuitem"); // create a new XUL menuitem
  item.setAttribute("label", aLabel);
  return item;
}
var popup = document.getElementById("myPopup"); // a <menupopup> element
var first = createMenuItem("First item");
var last = createMenuItem("Last item");
popup.insertBefore(first, popup.firstChild);
popup.appendChild(last);

You can also use appendChild() and insertBefore() to move existing elements. For example you could move the item labeled "First item" to the end of popup by adding this statement as a last line to the snippet above:

popup.appendChild(first);

This statement would remove the node from its current position in the document and re-insert it at the end of the popup.

Anonymous content (XBL)

XBL is the language used in Mozilla to define new widgets. Widgets defined in XBL may choose to define some content which is inserted to the bound element, when the binding is attached. This content, called anonymous content, is not accessible through normal DOM methods.

You need to use the methods of nsIDOMDocumentXBL interface instead. For example:

// gets the first anonymous child of the given node
document.getAnonymousNodes(node)[0];

// returns a NodeList of anonymous elements with anonid attribute equals el1
document.getAnonymousElementByAttribute(node, "anonid", "el1");

See getAnonymousNodes and getAnonymousElementByAttribute in the XBL reference for more information.

Once you have an anonymous node, you can use regular DOM methods to navigate and manipulate the rest of the nodes of that binding.

See also