You are reading O'Reilly XForms Essentials by Micah Dubinko. (What is this?) - Buy XForms Essentials Online

A Real-World Example

Despite the differences, comparisons between XForms and InfoPath are inevitable. Back in Chapter 2, XForms Building Blocks, we examined a UBL purchase order application. It is possible to recreate that application in InfoPath, and thus compare the results. Doing so is largely a hand-to-mouse experience with the InfoPath application. The result is shown in Figure A.1, “InfoPath design-time”.

Limitations in InfoPath made a few changes necessary—for example, there is no match for the range control—but overall the solution ended up quite similar to that of XForms.

One notable difference, however, is that tables, which can be seen in Figure A.1, “InfoPath design-time” as dotted lines, are required for any kind of layout, which might make things more challenging for non-visual users.

The other major difference was the lack of declarative elements. In XForms, the bind element establishes a relationship that the XForms Processor sticks to at all times. In InfoPath, script attached to a number of different entry points is required. The purchase order application had four assertions to maintain:

In XForms, these four assertions are accomplished through four bind elements:

<xforms:bind nodeset="cat:OrderLine/cat:LineExtensionAmount/@currencyID"
     calculate="../../cat:LineExtensionTotalAmount/@currencyID"/>
<xforms:bind nodeset="cat:OrderLine/cat:Item/cat:BasePrice/cat:PriceAmount/@currencyID"
     calculate="../../../../cat:LineExtensionTotalAmount/@currencyID"/>
<xforms:bind nodeset="cat:OrderLine/cat:LineExtensionAmount" 
     type="xs:decimal"
     calculate="../cat:Quantity * ../cat:Item/cat:BasePrice/cat:PriceAmount"/>
<xforms:bind nodeset="cat:LineExtensionTotalAmount" type="xs:decimal"
     calculate="sum(../cat:OrderLine/cat:LineExtensionAmount)"/>

In InfoPath, however, the needed script is somewhat more verbose:

XDocument.DOM.setProperty("SelectionNamespaces",
    'xmlns:cat="urn:oasis:names:tc:ubl:CommonAggregateTypes:1.0:0.70"
    xmlns:ns1="urn:oasis:names:tc:ubl:Order:1.0:0.70"
    xmlns:my="http://schemas.microsoft.com/office/infopath/2003/myXSD/2003-04-19T20:40:35"');

function XDocument::OnLoad(eventObj)
{
updateCurrency(  );
}

// This function is associated with: /ns1:Order/cat:OrderLine/cat:Quantity
function msoxd_cat_Quantity::OnAfterChange(eventObj)
{
recalcLineItem(eventObj.Site.parentNode);
recalcTotal(  );
}

// This function is associated with: /ns1:Order/cat:OrderLine/cat:Item/cat:BasePrice/cat:PriceAmount
function msoxd_cat_PriceAmount::OnAfterChange(eventObj)
{
recalcLineItem(eventObj.Site.parentNode.parentNode.parentNode);
recalcTotal(  );
}

// This function is associated with: /ns1:Order/cat:LineExtensionTotalAmount/@currencyID
function msoxd_ _LineExtensionTotalAmount_currencyID_attr::OnAfterChange(eventObj)
{
updateCurrency(  );
}

function recalcLineItem( lineNode ) {
var quantity = lineNode.selectSingleNode("cat:Quantity");
var price = lineNode.selectSingleNode("cat:Item/cat:BasePrice/cat:PriceAmount");
var extended = lineNode.selectSingleNode("cat:LineExtensionAmount");
var extPrice = parseFloat(getElementValue(quantity)) * parseFloat(getElementValue(price));

setElementValue(extended , floatToString(extPrice, 2));
}

function recalcTotal(  ) {
var dom = XDocument.DOM;
var extended = dom.selectSingleNode("/ns1:Order/cat:LineExtensionTotalAmount");
var newTotal = sum("/ns1:Order/cat:OrderLine/cat:LineExtensionAmount");
setElementValue( extended, newTotal );
}

function updateCurrency(  ) {
var dom = XDocument.DOM;
var copyFrom = dom.selectSingleNode("/ns1:Order/cat:LineExtensionTotalAmount/@currencyID");
var lines = dom.selectNodes("/ns1:Order/cat:OrderLine");

// loop through each line item, copying in the currencyID
for (var idx=0; idx<lines.length; idx++) {
  var copyTo = lines[idx].selectSingleNode("cat:LineExtensionAmount/@currencyID");
  copyTo.nodeValue = copyFrom.nodeValue;
  copyTo = lines[idx].selectSingleNode("cat:Item/cat:BasePrice/cat:PriceAmount/@currencyID");
  copyTo.nodeValue = copyFrom.nodeValue;
  }
}

// Utility functions

function getElementValue( node ) {
if (node.firstChild)
  return node.firstChild.nodeValue;
else
  return "";
}

function setElementValue( node, newval ) {
if (node.firstChild) {
  node.firstChild.nodeValue = newval;
} else {
  var textnode = node.ownerDocument.createTextNode( newval );
  node.appendChild( textnode );
  }
}

function sum(xpath) {
var nodes = XDocument.DOM.selectNodes(xpath);
var total = 0;
for (var idx=0; idx<nodes.length; idx++) {
  total = total + parseFloat(getElementValue(nodes[idx]));
  }
return total;
}

function floatToString(value)
{
return "" + value;
}

This script approach requires a bit of care in getting and setting values from XML elements. In accordance with the DOM worldview, actual data values are stored in a text node child of the element node, except that an empty value is signified by the lack of any text child node.

Though not used here, the extensive sample forms that come with InfoPath include a large library of script that can be reused via copy-and-paste. The resulting InfoPath document can be filled out in the same application that designed the form, as shown in Figure A.2, “Completing an InfoPath form”.