Limitations in InfoPath made a few changes necessary—for example, there is no match for the range control for a volume-control slider—but overall the solution ended up quite similar to that of XForms.
One notable difference, however, is that tables, which can be seen here 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; for example, that the currency type entered once gets copied to all of the line items. In InfoPath, script attached to a number of different entry points is required. To conform to the rules of UBL with this simple interface, the purchase order application had four assertions to maintain:
- A single currency code is copied into each repeating line item.
- Since the currency code appears in two places in each line item, it is also copied to the second location.
- For each line item, the extended price is calculated as price times quantity.
- The total of the extended price across all line items is summed up.
In XForms, these four assertions are accomplished through four XML elements, each of which contains a desired condition to be met:
<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 approximately 70 lines:
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, n