5.2. JavaScript and the DOM

In the application layer of Mozilla, there is little distinction between a web page and the graphical user interface. Mozilla's implementation of the DOM is fundamentally the same for both XUL and HTML. In both cases, state changes and events are propagated through various DOM calls, meaning that the UI itself is content -- not unlike that of a web page. In application development, where the difference between application "chrome" and rendered content is typically big, this uniformity is a significant step forward.

5.2.1. What Is the DOM?

The DOM is an API used to access HTML and XML documents. It does two things for web developers: provides a structural representation of the document and defines the way the structure should be accessed from script. In the Mozilla XPFE framework, this functionality allows you to manipulate the user interface as a structured group of nodes, create new UI and content, and remove elements as needed.

Because it is designed to access arbitrary HTML and XML, the DOM applies not only to XUL, but also to MathML, SVG, and other XML markup. By connecting web pages and XML documents to scripts or programming languages, the DOM is not a particular application, product, or proprietary ordering of web pages. Rather, it is an API -- an interface that vendors must implement if their products are to conform to the W3C DOM standard. Mozilla's commitment to standards ensures that its applications and tools do just that.

When you use JavaScript to create new elements in an HTML file or change the attributes of a XUL button, you access an object model in which these structures are organized. This model is the DOM for that document or data. The DOM provides a context for the scripting language to operate in. The specific context for web and XML documents -- the top-level window object, the elements that make up a web document, and the data stored in those elements as children -- is standardized in several different specifications, the most recent of which is the upcoming DOM Level 3 standard.

5.2.2. The DOM Standards and Mozilla

The DOM specifications are split into different levels overseen by the W3C. Each level provides its own features and Mozilla has varying, but nearly complete, levels of support for each. Currently, Mozilla's support for the DOM can be summarized as follows:

Mozilla strives to be standards-compliant, but typically reaches full support only when those standards have become recommendations rather than working drafts. Currently, Level 1 and Level 2 are recommendations and Level 3 is a working draft.

Standards like the DOM make Mozilla an especially attractive software development kit (SDK) for web developers. The same layout engine that renders web content also draws the GUI and pushes web development out of the web page into the application chrome. The DOM provides a consistent, unified interface for accessing all the documents you develop, making the content and chrome accessible for easy cross-platform development and deployment.

5.2.3. DOM Methods and Properties

Methods in the DOM allow you to access and manipulate any element in the user interface or in the content of a web page. Getting and setting attributes, creating elements, hiding elements, and appending children all involve direct manipulation of the DOM. The DOM mediates all interaction between scripts and the interface itself, so even when you do something as simple as changing an image when the user clicks a button, you use the DOM to register an event handler with the button and DOM attributes on the image element to change its source.

The DOM Level 1 and Level 2 Core specifications contain multiple interfaces, including Node, NodeList, Element, and Document. The following sections describe some interface methods used to manipulate the object model of application chrome, documents, or metadata in Mozilla. The Document and Element interfaces, in particular, contain useful methods for XUL developers.

5.2.3.3. getAttribute

Attributes are properties that are defined directly on an element. XUL elements have attributes such as disabled, height, style, orient, and label.

<box id="my-id" foo="hello 1" bar="hello 2" />

In the snippet above, the strings "my-id," "hello 1," and "hello 2" are values of the box element attributes. Note that Gecko does not enforce a set of attributes for XUL elements. XUL documents must be well-formed, but they are not validated against any particular XUL DTD or schema. This lack of enforcement means that attributes can be placed on elements ad hoc. Although this placement can be confusing, particularly when you look at the source code for the Mozilla browser itself, it can be very helpful when you create your own applications and want to track the data that interests you.

Once you have an object assigned to a variable, you can use the DOM method getAttribute to get a reference to any attribute in that object. The getAttribute method takes the name of the desired attribute as a string. For example, if you add an attribute called foo to a box element, you can access that attribute's value and assign it to a variable:

<box id="my-id" foo="this is the foo attribute" /> 
<script>
  var boxEl = document.getElementById('my-id');
  var foo   = boxEl.getAttribute('foo');
  dump(foo+'\n');
</script>

The dump method outputs the string "this is the foo attribute," which is the value of the attribute foo. You can also add or change existing attributes with the setAttribute DOM method.

5.2.3.9. getElementsByTagName

Another very useful method is getElementsByTagName. This method returns an array of elements of the specified type. The argument used is the string element type. "box," for example, could be used to obtain an array of all boxes in a document. The array is zero-based, so the elements start at 0 and end with the last occurrence of the element in the document. If you have three boxes in a document and want to reference each box, you can do it as follows:

<box id="box-one" />  
<box id="box-two" />  
<box id="box-three" />  
<script>
  document.getElementsByTagName('box')[0];
  document.getElementsByTagName('box')[1];
  document.getElementsByTagName('box')[2];
</script>

Or you can get the array and index into it like this:

var box = document.getElementsByTagName('box');

box[0], the first object in the returned array, is a XUL box.

To see the number of boxes on a page, you can use the length property of an array:

var len = document.getElementsByTagName('box').length;
dump(len+'\n');
console output: 3

To output the id of the box:

<box id="box-one" />  
<box id="box-two" />  
<box id="box-three" />  
<script>
  var el      = document.getElementsByTagName('box');
  var tagId   = el[0].id;
  dump(tagId+"\n");
</script>
console output: box-one

To get to an attribute of the second box:

<box id="box-one" />  
<box id="box-two" foo="some attribute for the second box" />  
<box id="box-three" />  
<script>
  var el        = document.getElementsByTagName('box');
  var att       = el[1].getAttribute('foo');
  dump(att      +"\n");
</script>
console output: some attribute for the second box

getElementsByTagName is a handy way to obtain DOM elements without using getElementById. Not all elements have id attributes, so other means of getting at the elements must be used occasionally.[1]

5.2.3.10. Getting an element object and its properties

In addition to a basic set of attributes, an element may have many properties. These properties don't typically appear in the markup for the element, so they can be harder to learn and remember. To see the properties of an element object node, however, you can use a JavaScript for in loop to iterate through the list, as shown in Example 5-1.

Note the implicit functionality in the el object itself: when you iterate over the object reference, you ask for all members of the class of which that object is an instance. This simple example "spells" the object out to the console. Since the DOM recognizes the window as another element (albeit the root element) in the Document Object Model, you can use a similar script in Example 5-2 to get the properties of the window itself.

The output in Example 5-2 is a small subset of all the DOM properties associated with a XUL window and the other XUL elements, but you can see all of them if you run the example. Analyzing output like this can familiarize you with the interfaces available from window and other DOM objects.

5.2.3.11. Retrieving elements by property

You can also use a DOM method to access elements with specific properties by using getElementsByAttribute. This method takes the name and value of the attribute as arguments and returns an array of nodes that contain these attribute values:

<checkbox id="box-one" />
<checkbox id="box-two" checked="true"/>
<checkbox id="box-three" checked="true"/>
<script>
  var chcks = document.getElementsByAttribute("checked", "true");
  var count = chcks.length;
  dump(count + " items checked \n");
</script>

One interesting use of this method is to toggle the state of elements in an interface, as when you get all menu items whose disabled attribute is set to true and set them to false. In the xFly sample, you can add this functionality with a few simple updates. In the xfly.js file in the xFly package, add the function defined in Example 5-3.

Although this example doesn't update elements whose disabled attribute is not specified, you can call this function from a new menu item and have it update all menus whose checked state you do monitor, as shown in Example 5-4.

When you add this to the xFly application window (from Example 2-10, for example, above the basic vbox structure), you get an application menu bar with a menu item, Toggle, that reverses the checked state of the three items in the "Fly Types" menu, as seen in Figure 5-2.

The following section explains more about hooking scripts up to the interface. Needless to say, when you use a method like getElementsByAttribute that operates on all elements with a particular attribute value, you must be careful not to grab elements you didn't intend (like a button elsewhere in the application that gets disabled for other purpose).

Notes

[1]

You can use other DOM methods, but these methods are most commonly used in the XPFE. Mozilla's support for the DOM is so thorough that you can use the W3C specifications as a list of methods and properties available to you in the chrome and in the web content the browser displays. The full W3C activity pages, including links to the specifications implemented by Mozilla, can be found at http://www.w3.org/DOM/.