This section introduces the DOM interfaces in XBL, illustrates how they work, and explains the core concepts involved in XBL interaction with the DOM, such as scope characteristics and insertion points.
XBL has two core DOM interfaces, DocumentXBL and ElementXBL. These extensions to the Document and Element interfaces are not part of the formal DOM specifications. All methods can be accessed and used from JavaScript. Here is a list of these interface methods.
The DocumentXBL interface gains access to and interacts with an XBL document. The methods of this interface are as follows:
XBL documents are loaded only the first time a bound document uses a binding from it. You can get around this problem and load the binding documents synchronously by using this method. It returns an XBL document for use within a bound document. If your document is large and you need to optimize performance, this method may provide better performance.
document.loadBindingDocument('chrome://package/content/myBindings.xml');
For use only within a binding, this method returns the bound element -- i.e., the top-level node of the binding, when passed an element within a binding.
var listbox = document.getBindingParent(this); var cellValue = listbox.childNodes[3].firstChild.label;
Returns an array with the input binding's top-level content nodes. Refer to the section Section 7.4.3, later in this chapter, for more details.
Returns a single anonymous element when passed an element, an attribute from that element, and its value. Refer to the section Section 7.4.3 for more details.
The ElementXBL interface adds and removes bindings from a bound element. The methods of this interface are as follows:
Dynamically attaches a binding, given as a parameter, to an element. Refer to the following sections for more details.
Dynamically removes the given binding. Refer to the following sections for more details.
The section Section 7.2 covered the attachment of a binding to a bound element using CSS. This technique is the most common method, but you can also attach bindings with the addBinding method.
Using this method as an alternative to CSS attachment is useful when you do not want to attach a binding in all circumstances. In an application based on user input, you may not want to load a binding until certain values are entered. For example, in a membership database, the information that appears on screen may depend on the user's level of membership. The following snippets show how it is used.
<mybinding id="myNewWidget" class="attached" />
To load a binding, add these two lines in your script.
var binding = document.getElementById("myNewWidget"); document.addBinding(binding, "chrome://mypackage/content/myBindings.xml#super");
Notice that the URL used to access the binding takes the same format as in the CSS property -- i.e., the path to the file and the id of the binding qualified by #.
The best way to remove a binding attached via CSS is to change the style rule for that element. You can change the class to one that does not have a different or null binding reference, for example. Then a stylesheet can be set up to provide binding references for both an attached and unattached element.
This example shows how to remove a reference to a binding by resetting it to an empty reference:
mybinding.attached { -moz-binding : url("mybindings.xml#my-binding"); } mybinding.unattached { -moz-binding : url(""); }
When you want to detach the binding from an element, you can do this:
var mywidget = document.getElementById("binding1"); mywidget.setAttribute("class","unattached");
An element can have only one binding attached at a time, so this is a programmatic trick for knocking the "real" binding out of its place with an empty one, rather than actually removing it.
-moz-binding:url("") can be used at this time as a hack around the -moz-binding:none binding. The later binding does not currently work in Mozilla. |
The other method used to detach a binding, which is more intuitive from a DOM perspective, uses the removeBinding method:
var binding = document.getElementById("myNewWidget"); document.removeBinding(binding, "chrome://mypackage/content/myBindings.xml#super");
This method ensures that other style information is not lost if you have it attached to a particular class.
When a binding is removed, the anonymous content is destroyed and the methods, properties, and event handlers no longer apply.
In the case of an inheritance chain (see the Section 7.5 section later in this chapter for more details), the bindings are destroyed from the bottom upwards. This means that if there is tear-down code in the form of a destructor, it is executed last on the base binding.
Although a document cannot access the content of bindings attached to it, a binding can access the document it is attached to (bound document). This gives bindings the ability to provide more than just additional content to the document. It also means that you can find information about the context of bound element in a document and provide information about it from within the binding.
From the perspective of nodes inside the anonymous content, you can use DOM properties to find a higher-level node and then in turn use that to get to other nodes:
This property is the bound element for the top-most element in the anonymous content. This bound element resides in the document that the binding is attached to.
For all elements in the anonymous content, this is the document the bound element resides in.
While higher-level nodes can be accessed from anonymous content, parents do not have explicit access to their anonymous children using the DOM childNodes property. Using firstChild or nextSibling will also not work. |
Example 7-4 illustrates both properties in use.
Example 7-4 is a binding with two buttons, each of which brings up an alert when activated. The alert simply shows the name of an element that is accessed in the code attached to the button. In Button A, the parent node is the containing box. One level further is the bound element, <mybinding> -- the parent node of the box parent. The alert dialog raised by the alert shows "mybinding." Once a binding is applied, the binding's owner (ownerDocument) is the bound document. Assuming that Button B is a XUL window, the alert, when activated, shows "window." This property can be used to access properties of the document object.
Content bound to a document can introduce different levels of scope. Some of the scope is available at the document level, and some is at the binding level. With content in different scopes, there are limits to which standard DOM methods can be used to access other elements and objects in and above a binding. XBL contains some special methods to help work around some of the limitations caused by these barriers, such as not being able to change binding content dynamically or access certain property values.
The two XBL-specific interfaces that exist on the document to get a handle on this content are getAnonymousNodes and getAnonymousElementByAttribute. The advantage of using these interfaces is that they provide a bridge between behavior and content. Use them when you want to dynamically manipulate content or get a value -- for example, when accessing a particular textbox contained in binding, reminiscent of the one used earlier in the chapter when you were introduced to the <inputfield /> binding.
The method getAnonymousNodes(element) takes a node as a parameter and returns a list of nodes that are in the anonymous content. The following code uses script in the <getter> to access the anonymous node and return a value contained in it.
<getter> <![CDATA[ var list = document.getAnonymousNodes(this)[0]; return list.selectedItem.getAttribute('label'); ]]> </getter>
If we assume that this binding's content is a XUL menu list, then this code gets the label attribute of the menu item that is currently selected in that list (list.selectedItem). The list variable contains the value returned by the getAnonymousNodes function, which is passed the binding node (this). The method returns an array, so the item is accessed via the first index of 0.
The method getAnonymousElementByAttribute(element, attr, value) returns a single anonymous node rather than a list. This node is qualified by an element name, a particular attribute, and the value of that attribute, and returns this specific node. This retrieval provides faster access to anonymous content elements when you know an attribute's value.
<property name="emailID" onget="return document.getAnonymousElementByAttribute(this, 'id', 'emailAddressNode');" readonly="true"/>
This example uses an id attribute to retrieve a specific node. You can use this method when multiple elements of the same type exist and you need to get access to a particular one -- for example, a particular field on a database entry submission form like an email address or a telephone number.
Although these two functions (getAnonymousNodes and getAnonymousElementsByAttribute) were probably designed to be used within a binding scope, they can be used both inside and outside a binding to access anonymous content. Bindings are meant to be self-contained, and getting anonymous nodes outside a binding breaks this philosophy. However, these functions can act as a bridge between scopes if you are careful.
All examples in the chapter have so far dealt with standard binding content rendering within a bound document. The processes outlined in this section can, in one sense, be seen as abnormal because they allow ordering of the content to change based on insertion points defined in the binding. This process is done with the XBL <children> element
Zero or more children can be contained in anonymous content. These children are marked up with the XBL-specific <children> tag. They can be either the content of the element using the binding or anonymous content generated by the base binding. If the <children> tag contains its own elements, then it will be used as the default content. If the element the binding is attached to contains children, the default content will be ignored.
The location of the <children> tags determines the content's insertion point. Insertion points play an important role in the generation of content within a template because they affect how the content is displayed and accessed by the DOM.
<binding id="my-binding">
<content>
<xul:vbox>
<children />
</xul:vbox>
</content>
</binding>
This stripped-down binding has only a vertical box as its own content and looks to the children of the bound element for more content by using the <children> element. Here is the XUL content that uses the binding:
<mybinding id="myNewWidget" flex="1" class="attached"> <label value="this is child 1" /> <label value="this is child 2" /> </mybinding>
When the binding is attached and the content is drawn, the insertion point for the two labels is inside the container vertical box inside the binding. This scenario could be used when a binding is used multiple times. Each time, it needs to be rendered differently with extra content that can be provided this way.
Sometimes multiple siblings are located within a box in the XUL, but you want to use only some of them in the binding, such as when a user logs into a system and content is displayed depending on its level of membership. In these cases, you can be selective about which children should be included in the binding. Example 7-5 shows how to use the includes attribute on the <children> element.
The children element in Example 7-5 essentially tells, "Of all the content contained in the bound element, insert only the image element at this particular insertion point." Here is the XUL code that goes with this example:
<mybinding id="myNewWidget" flex="1"> <image src="http://www.mozdev.org/sharedimages/header.gif" /> <label value="a non includes element" /> </mybinding>
The image is the only child taken from the XUL content and the label is ignored.
If you have children that are not defined in the includes attribute, then the binding is discarded and not used. If the bound element uses another element in addition to an image element, the binding is discarded and only the explicit content is used. If the image element isn't used at all, the binding is discarded.
<mybinding id="myNewWidget" flex="1"> <image src="http://www.mozdev.org/sharedimages/header.gif" /> <label value="an element" /> </mybinding>
This example renders the image and the label and discards the binding. The anonymous content does not appear because the binding is discarded and only the explicit content is used.