This article is based solely on XForms and JavaScript. It was tested against the Mozilla XForms plugin installed on Mozilla Firefox 2.0. Everything used is standard XForms and JavaScript, so it should run on other implementations of these two standard technologies. No server-side technologies are used.
Let's take a look at a classic example of XForms. It shows how to create a table representing repeated nodes in an XML document. It shows, among other things, how to use XForms to perform aggregate calculations and how to use XForms to add or remove nodes from the model's data with the view being automatically kept in sync. Take a look at the full source code in Listing 1.
Listing 1. Classic XForms table example
<?xml version="1.0" encoding="UTF-8"?>
<xhtml:html xmlns:ev="http://www.w3.org/2001/xml-events"
xmlns:xforms="http://www.w3.org/2002/xforms"
xmlns:xhtml="http://www.w3.org/1999/xhtml"
xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<xhtml:head>
<xhtml:title>Demonstration of table with
column total</xhtml:title>
<xf:model id="my-model" xmlns="http://www.w3.org/1999/xhtml"
xmlns:xf="http://www.w3.org/2002/xforms">
<xf:instance id="my-data" src="my-data.xml" xmlns=""/>
<xf:bind calculate="sum(../Item/Amount)" nodeset="/Data/Total"/>
<xf:submission action="my-data.xml" id="update-from-local-file"
instance="my-data" method="get" replace="instance"/>
<xf:submission action="my-data.xml" id="view-xml-instance"
method="get"/>
<xf:submission action="my-data.xml" id="save-to-local-file"
method="put"/>
</xf:model>
</xhtml:head>
<xhtml:body>
<xf:group ref="/Data" xmlns="http://www.w3.org/1999/xhtml"
xmlns:xf="http://www.w3.org/2002/xforms">
<xf:label/>
<xf:repeat id="repeatItem" nodeset="Item"
xmlns="http://www.w3.org/1999/xhtml">
<xf:input class="item-description" id="description-input"
ref="Description" xmlns="http://www.w3.org/1999/xhtml">
<xf:label/>
</xf:input>
<xf:input class="item-amount" ref="Amount"
xmlns="http://www.w3.org/1999/xhtml">
<xf:label/>
</xf:input>
</xf:repeat>
<xhtml:div id="sum">
<xf:output ref="/Data/Total" xmlns="http://www.w3.org/1999/xhtml">
<xf:label/>
</xf:output>
</xhtml:div>
<xf:trigger id="insertbutton" xmlns="http://www.w3.org/1999/xhtml">
<xf:label>Add Item</xf:label>
<xf:action ev:event="DOMActivate">
<xf:insert at="last()" nodeset="Item[last()]"
position="after"/>
<xf:setvalue ref="Item[last()]/Description" value="''"/>
<xf:setvalue ref="Item[last()]/Amount" value="0"/>
<xf:setfocus control="description-input"/>
</xf:action>
</xf:trigger>
<xf:trigger id="delete" xmlns="http://www.w3.org/1999/xhtml">
<xf:label>Delete Item</xf:label>
<xf:delete at="index('repeatItem')" ev:event="DOMActivate"
nodeset="Item[index('repeatItem')]"/>
</xf:trigger>
<xf:submit submission="update-from-local-file"
xmlns="http://www.w3.org/1999/xhtml">
<xf:label>Reload</xf:label>
</xf:submit>
<xf:submit submission="save-to-local-file"
xmlns="http://www.w3.org/1999/xhtml">
<xf:label>Save</xf:label>
</xf:submit>
<xf:submit submission="view-xml-instance"
xmlns="http://www.w3.org/1999/xhtml">
<xf:label>View XML Instance</xf:label>
</xf:submit>
</xf:group>
</xhtml:body>
</xhtml:html>
|
You may notice from the source code that the data in our model is coming from an external XML file. That file is shown in Listing 2.
Listing 2. XML data for example
<?xml version="1.0" encoding="UTF-8"?>
<Data>
<Item>
<Description>Furniture</Description>
<Amount>1000</Amount>
</Item>
<Item>
<Description>Dock</Description>
<Amount>2000</Amount>
</Item>
<Item>
<Description>Boat</Description>
<Amount>3000</Amount>
</Item>
<Item>
<Description>Lawn equipment</Description>
<Amount>4000</Amount>
</Item>
<Item>
<Description>Hot tub</Description>
<Amount>5000</Amount>
</Item>
<Total>15000</Total>
</Data>
|
You can run the example by simply opening it in a Web browser. You should see something that looks like Figure 1.
Figure 1. Classic XForms example
Try it out and notice you can add or remove rows using the Add Item and Delete Item buttons shown. For example, if you click the Delete Item button once, you should see something similar to Figure 2.
Figure 2. One item removed
Notice how not only did the top item get deleted, but the total was recalculated. This is a great example of the power of XForms. Click the Delete Item button four more times and you should get something that looks like Figure 3.
Figure 3. All items removed
That's kind of unsightly, but you can just click the Add Item button and start re-entering data, right? It turns out that clicking the Add Item button will do nothing in this situation.
So why does the Add Item not work in this situation? If you look at the source code, the Add Item button causes an insert to our model, adding a record using the XForms insert command. That record happens to be an "Item" node, so the Add Item action then sets some default values for the Item's Description and Amount nodes. The Add Item defines what kind of node to insert by essentially cloning the last node in the structure. That's what the nodeset="Item[last()]" does. That's the source of your "bug." If you eliminate all the Items, then there is nothing to clone and thus nothing to insert. So the Add Item button fails. Of course the question now is, how do you fix this?
Like most problems in software engineering, there are many ways to solve the problem described here. The solution demonstrated here uses JavaScript. Your strategy will be to change the way you delete. You can see in Listing 1 that the Delete Item button uses the XForms delete command. You'll replace the call to this command with a call to a JavaScript function. That function will have to interact with the XForm model. You will only modify the call to the Delete Item button, so you don't have to change the Add Item button. Let's take a look at the JavaScript solution.
As mentioned, the idea will be to only change the Delete Item button, not the Add Item button. So for the Add Item button to work, you can never delete all of your data from the model. So you'll keep track of how many items are around, and when you get down to the last one, you won't delete it. You'll just replace its contents with the default contents, like if you did the Add Item button. Let's take a look at the JavaScript code in Listing 3.
Listing 3. JavaScript
deleteItem() function
<xhtml:script type="text/javascript">
//<![CDATA[
function deleteItem(){
var model = document.getElementById("my-model");
var instance = model.getInstanceDocument("my-data");
var dataElement = instance.getElementsByTagName("Data")[0];
var itemElements = dataElement.getElementsByTagName("Item");
var cnt = itemElements.length;
if (cnt > 1){
dataElement.removeChild(itemElements[cnt-1]);
} else {
// last element so just set its data to default vals
var descripElement =
itemElements[0].getElementsByTagName("Description")[0];
descripElement.childNodes[0].nodeValue = "";
var amtElement =
itemElements[0].getElementsByTagName("Amount")[0];
amtElement.childNodes[0].nodeValue = "0";
}
model.rebuild();
model.recalculate();
model.refresh();
}
//]]>
</xhtml:script>
|
Here's how this works. You need to access the XForms model from your JavaScript. Luckily this can be done easily using JavaScript's DOM APIs. The XForms model is part of the page's DOM, so you just use the document.getElementById() method, just like you would do to access an HTML div or an HTML input field.
When you access your XForms model using document.getElementById(), as shown in Listing 3, what you get is a nsIXFormsModelElement. This object has several very useful methods on it, including the getInstanceDocument() method used above. This gives you access to your XForms instance defined in Listing 1. This is a DOM object representing the XML document seen in Listing 2. So in your JavaScript code, you simply walk the DOM to get the Item elements. You determine how many Items there are in your model and store it in the cnt variable. There are obviously two use cases to deal with here. The first is if you have more than one Item, and the second is if you have only one. In the first use case, you need to delete the item.
Deleting the item with JavaScript
So how do you delete one of your Item records using JavaScript instead of the XForms delete control? The solution is surprisingly easy. The getInstanceDocument() method called earlier gave you a true DOM object. So you can do anything on this object that you would do with any other DOM object. It supports the full DOM API. So you simply use the removeChild() method on your DOM element. You remove the Item at index (cnt - 1), since your array of elements (obtained by calling getElementsByTagName("Item")) is 0-indexed. No magical XForms APIs here, just straightforward DOM programming.
You've reproduced the logic that you would normally accomplish using the XForms delete command. You did this so you could more gracefully deal with the case where you were deleting the last Item. Now take a look at that corner case, such as, cnt == 1.
As mentioned earlier, the key here is that you do not want to actually delete the last Item. If you do that, then the XForms insert command used in the Add Item action will no longer work. Of course, you can also re-code that as well, but it's best to try to avoid that.
So instead of deleting the last Item, you'll just replace its contents with a blank Item. This is the same kind of Item you get when you click the Add Item button. For instance, it has an empty string for its Description and 0 for its Amount. To do this, you once again just use the DOM API. You simply access the last Item element, and then access that element's Description and Amount elements. We set the values of their text nodes to the empty string and 0, respectively.
Synchronizing the view and the model
At this point you've handled both of your use cases for Delete Item. There's still a little more work you need to do. Normally when you use XForms commands to modify the XForms model, all recalculations and view refreshing is done automatically. This is not the case when you use JavaScript to access these same things. You need to do these same things manually.
Luckily, the nsIXFormsModelElement object you got a reference to at the beginning of the deleteItem() function has some more useful methods that will help out. The first thing you use is its rebuild() method. This method causes it to rebuild its internal representing of the data in the model. It essentially syncs up the model object with the DOM. This doesn't affect the view at all, only the model.
Next you need to use the model's recalculate() method. You need to do this because you were keeping track of the sum of the amounts in your table using an XForms bind-calculate command in your model. That calculation gets refreshed when you call the recalculate() method. Again, this is only syncing up your model with the DOM. It does not affect the view at all.
Now that the model is in sync with the data, you can re-paint your view. You do this by calling the refresh() method on the model. This method causes all controls bound to your model to be refreshed. For the case where you are deleting a row, this will cause that row to disappear. In the case where you are deleting the last row, it will cause the data in that last row to be changed to the blank data specified. In both cases, this will cause the total being displayed to be updated based on the current data.
Now that you've written a clever JavaScript function to handle your deletes, you just need to modify your XForm so that it calls this JavaScript when the Delete Item button is invoked. To do that, you simply modify the XForms declaration for Delete Item, as shown in Listing 4.
Listing 4. New delete item XForm control
<xf:trigger id="delete" xmlns="http://www.w3.org/1999/xhtml">
<xf:label>Delete Item</xf:label>
<xf:load ev:event="DOMActivate"
resource="javascript:deleteItem()"/>
</xf:trigger>
|
Notice that you simply replaced the XForms delete command with a load command that references the deleteItem() JavaScript function. That's the last change you need to make, so let's run the modified example.
Simply load the example into your browser. It should look just like the original shown in Figure 1. However, when you get down to the last row and click Delete Item, you should see the result shown in Figure 4.
Figure 4. Deleting the last row
Now the last row won't disappear. Instead it will simply go to default values. If you click the Add Item button, you'll see something similar to Figure 5.
Figure 5. Add Item after deleting Last Item
The Add Item button still works, with no modification.
You've seen how you can create a smarter delete item action using JavaScript. More importantly, you've seen how JavaScript can be used to access and modify XForms model data, recalculate XForms-bound calculations, and refresh XForms views. You can imagine several other ways you could implement the Add and Delete Item actions to provide more and more sophisticated functionality. Hopefully, you can also see how to use some of these techniques to improve your own XForms-based applications.
| Description | Name | Size | Download method |
|---|---|---|---|
| Article sample code | xformsjavascript_source.zip | 2KB | HTTP |
Information about download methods
Learn
-
Get a basic introduction to XForms in Introduction to XForms, Part 1: The new Web standard for forms (Chris Herborth, developerWorks, September 2006), Introduction to XForms, Part 2: Forms, models, controls, and submission actions (Chris Herborth, developerWorks, September 2006), and Introduction to XForms, Part 3: Using actions and events (Chris Herborth, developerWorks, September 2005).
-
Learn more about using JavaScript and XForms together in the tip Call JavaScript from an XForms form (Nicholas Chase, developerWorks, January 2007).
-
Explore the full power of the repeat command used in this article, by reading the developerWorks article Make the most of XForms repeats (Jan J. Kratky and Steve K. Speicher, developerWorks, November 2006).
-
Want a faster way to create XForms? Read about Visual XForms Designer in Develop forms using the Visual XForms Designer (Jan J. Kratky, Keith Wells, and Kevin E. Kelly, developerWorks, June 2006).
-
Learn more on DOM programming on the client side in the article JavaScript and the Document Object Model (Nicholas Chase, developerWorks, July 2002).
-
Get a thorough introduction to DOM programming in the developerWorks tutorial Understanding DOM (Nicholas Chase, developerWorks, March 2007).
-
Learn some advanced DOM techniques in the article XML Matters: Beyond the DOM (Dethe Elza, developerWorks, May 2005).
-
Learn more about XForms in the IBM developerWorks XML zone.
-
See where XForms is going.
-
Learn the essentials for creating the next generation of forms in XForms basics (Nicholas Chase, developerWorks, October 2006).
-
Visit the IBM XForms community.
-
IBM XML certification: Find out how you can become an IBM-Certified Developer in XML and related technologies.
-
XML technical library: See the developerWorks XML Zone for a wide range of technical articles and tips, tutorials, standards, and IBM Redbooks.
-
developerWorks technical events and webcasts: Stay current with technology in these sessions.
Get products and technologies
-
The XForms Recommendation is maintained by the W3C.
-
Download the XForms extension for Mozilla.
Discuss
Comments (Undergoing maintenance)





