Create a data binding solution for Firefox

Mozilla's Firefox browser is becoming a favorite among Web application developers, including myself. One fact that sets Firefox apart from Internet Explorer is that it provides support for XBL (eXtensible Binding Language). XBL offers the facilities to bind behaviors to DOM/HTML elements. Let's examine how XBL is important for binding data fields to XML data islands.

Mozilla's Firefox browser is becoming a favorite among Web application developers, including myself. One fact that sets Firefox apart from Internet Explorer is that it provides support for XBL (eXtensible Binding Language). XBL offers the facilities to bind behaviors to DOM/HTML elements. Let's examine how XBL is important for binding data fields to XML data islands.

One of the privileges available to developers who create IE solutions is data binding to data source objects including XML data islands. In IE, XML data islands are created as separate DOMDocuments within the existing page, which itself is a DOMDocument. This gives all the functionality of the DOMDocument. For instance, you can load new data by using the load() or loadXML() methods or select individual nodes using XPath queries.

In Mozilla, XML data islands are viewed as any other element within the DOM of the page. This means that in order to get any functionality from it, you'll need to add the behaviors to the data fields for updating any of the underlying data. You can also get the bound values and populate the data fields upon loading the page.

Let's say that you have an XML data island that contains basic user information, e.g., the user's name, address, and telephone number. Here's the XML that covers that data:

<xml id="xmlData" style="visibility:hidden;">
    <userInfo>
        <first_name>John</first_name>
        <last_name>Doe</last_name>
        <street>123 Some Street</street>
        <city>Smalltown</city>
        <state>US</state>
        <postal>99999</postal>
        <telephone>8005551212</telephone>
    </userInfo>
</xml>

The only thing that's special about this data island is the style attribute. Without hiding the XML data island, Mozilla browsers will display the content of the individual nodes.

Next, you should add the data fields to your page:

<body onload="document_onload()">
<form id="theForm">
    <input type="text" id="txtFirstName" value="" dataFld="first_name"
 class="linked_data"/><br>
    <input type="text" id="txtLastName" value="" dataFld="last_name"
 class="linked_data"/><br>
    <input type="text" id="txtStreet" value="" dataFld="street"
 class="linked_data"/><br>
    <input type="text" id="txtCity" value="" dataFld="city"
 class="linked_data"/><br>
    <input type="text" id="txtState" value="" dataFld="state"
class="linked_data"/><br>
    <input type="text" id="txtPostal" value="" dataFld="postal"
 class="linked_data"/><br>
    <input type="text" id="txtPhone" value="" dataFld="telephone"
class="linked_data"/><br>
<textarea cols="80" rows="10" id="txtXmlData"></textarea>
</form>
</body>

Notice the class and dataFld attributes of the INPUT elements. The class attribute adds the behavior you specify with XBL, and the dataFld attribute specifies the node to which you're binding.

IE developers will recognize the dataFld attribute. However, in Mozilla browsers, this doesn't automatically bind the data to the XML data island, so you have to add the XBL behavior.

In order to bind the elements, specify the location of your binding in your CSS:

<style>
.linked_data {
    -moz-binding: url(linked_data.xml#link);
}
</style>

Here's the XBL code that will accomplish updating the data when the data changes:

<?xml version="1.0"?>  
<xbl:bindings xmlns:xbl="http://www.mozilla.org/xbl">  
    <xbl:binding id="link">
        <xbl:implementation>
            <xbl:property name="linkedNode"/>
            <xbl:method name="update">
                <xbl:parameter name="newValue"/>
                <xbl:body>
                    alert("udpate method called with param " + newValue + ".");
                    this.linkedNode.nodeValue = newValue;
                    alert("success");
                </xbl:body>
            </xbl:method>
            <xbl:method name="bind">
                <xbl:body>
                    this.linkedNode =
document.getElementById("xmlData").getElementsByTagName(this.getAttribute
("dataFld"))[0].childNodes[0];
                    this.value = this.linkedNode.nodeValue;
                    document.getElementById("txtXmlData").value =
 getInnerXml(document.getElementById("xmlData"));
                </xbl:body>
            </xbl:method>
        </xbl:implementation>
        <xbl:handlers>
            <xbl:handler event="change">
                this.update(this.value);
            </xbl:handler>
        </xbl:handlers>
    </xbl:binding>
</xbl:bindings>

Within the binding, you created a property called linkedNode. This property will contain the actual text node object that houses the data for the bound element. You also added two methods, update and bind. The update method takes one parameter: the new value of the bound data. The bind method binds the bound control to the xml node through the linkedNode property. This is done during the page's onload event.

Finally, an event handler is added for the change event, which fires when the control loses focus after the value changes. When the change event occurs, the binding uses the update method and passes the bound element's value property.

Now you need to complete the data binding by setting the bound elements' values during the page's onload event:

<script language="JavaScript">

function document_onload() {
    document.getElementById("txtFirstName").bind();
    document.getElementById("txtLastName").bind();
    document.getElementById("txtStreet").bind();
    document.getElementById("txtCity").bind();
    document.getElementById("txtState").bind();
    document.getElementById("txtPostal").bind();
    document.getElementById("txtPhone").bind();
    document.getElementById("txtXmlData").value =
 getInnerXml(document.getElementById("xmlData"));
}

</script>

You simply use the bind method to complete this task. To check your work, I suggest using a simple function to write out my XML to the txtXmlData TEXTAREA during the load and change events (I had to do this since Firefox doesn't recognize the innerHTML property on the XML element):

var ELEMENT_NODE                   = 1;
var ATTRIBUTE_NODE                 = 2;
var TEXT_NODE                      = 3;
var CDATA_SECTION_NODE             = 4;
function getInnerXml(node) {
    var s = "<" + node.nodeName;
    if (node.hasChildNodes()) {
    for (var i = 0; i < node.childNodes.length; i++) {
            if (node.childNodes[i].nodeType == TEXT_NODE)
                s += ">" + node.childNodes[i].nodeValue;
            if (node.childNodes[i].nodeType == ELEMENT_NODE)
                s += getInnerXml(node.childNodes[i]);
        }
        s += "</" + node.nodeName + ">";
    } else {
        s += "/>";
    }
    return s;
}

If you would like the source code to this article as tested in Firefox Version 1.0.1, you can check it out here or here.

Phillip Perkins is a contractor with Ajilon Consulting. His experience ranges from machine control and client server to corporate intranet applications.