A clean separation of presentation, user interface, and business processing model
John is a senior product architect and research scientist for PureEdge Solutions. He can be contacted at firstname.lastname@example.org.
Forms provide a fundamental way in which users interact with web applications. However, the now familiar HTML-based forms have shortcomings ranging from poor integration with XML to device dependence. To address these problems, the W3C has turned to XForms, an application that combines XML and forms. XForms combines its own vocabulary with that of XPath and XML Schema to allow the expression of the core business processing model of a web-based application. This includes not just a structured XML-based data model, but also many constructs that simplify the application-design experience, including a declarative business rules engine for automatic calculation of data values and other properties, input validation by XML schema and dynamic constraints, event-driven action sequences, intent-based user-interface definition, and language features for specifying the properties of data submissions. In essence, the XForms processing model provides a cause-and-effect programming paradigm that backs user-initiated and event-driven modifications of data with a declarative business rules engine.
The XForms Processing Model
With XForms, you define a business processing model that includes:
- Instances of XML data to be processed.
- XML schema for the data instances.
- Declarative business rules describing the calculated relationships among the data.
- Declarative business rules defining properties of and dynamic constraints on the data.
- Event-driven invocation of imperative action sequences.
- An intent-based user interface definition that provides the core behaviors of the subsuming host language's presentation layer.
- Definitions for submission of XML data.
Schema data-type validation occurs in real time as data entry occurs, and full schema validation is performed before submission. XForms is at its best when XML data is submitted directly to the server, rather than boiling everything down to tag-value pairs. The response of a submission is typically replacement XML data or a whole new document.
The backbone of XForms, though, is the processing model that is defined for XML instance data. XForms provides a cause-and-effect programming paradigm by backing user input and event-based action sequences with schema validation and a declarative business rules engine. As anyone who has experienced the increased power of event-driven programming or seen the rise of the spreadsheet can attest, each of these methods alone significantly reduces complexity. XForms provides a powerful blend of these successful methodologies to eliminate the tedium of imperative scripting over ad hoc data models.
The declarative computation engine performs updates of instance data values using depth-first search and topological sorting, the same methodology as a spreadsheet. With an <xforms:bind> element, the form author simply declares that the content of an XML node or a property of that node is the calculated result of an XPath expression, and the XForms processor takes care of updating the XML node or property whenever any other XML node referenced by the XPath expression changes. With the <xforms:bind> at the origin, XForms derives power from the declarative computation system along four axes:
- Automatic ordering. XForms models support an arbitrary number of bind elements, so the XForms processor determines the order of recalculation using the spreadsheet algorithm.
- Iteration. XPath is used to the fullest by allowing the form author to apply a single XPath expression to an arbitrary number of XML instance data nodes. For example, the calculations for all line subtotals of a purchase order can be declared with a single bind element, regardless of how many lines there are.
- Model item properties. Bind elements can declare formulae for the intrinsic value of an XML node as well as several XForms defined, such as whether the XML node is relevant, required, or read only, or whether it satisfies a dynamic constraint (which cannot be expressed with XML Schema).
- Cause-and-effect programming. The declarative computation engine is invoked to effect the business rules expressed by the model binds, regardless of the cause of the change to the XML instance data. The resultant values of all model item properties are then automatically applied throughout the XForms system.
Despite the intent of XForms to express more complex web-based applications, it is useful to begin with a straightforward example to illustrate the basics; say, a form to calculate the monthly payment and total payback of a compound interest loan. Listing One is an XForms model containing a simple data instance for the details of a loan.
In many XForms applications, the data will have an associated schema to define data types and structural constraints on the XML. This could be done by placing the XML schema directly into the xforms:model, or more commonly (as in Listing One), by URI reference in the schema attribute. For data-type assignment, which is the most important to client-side processing, it is also possible to avoid using XML Schema altogether by instead making the assignments with the XForms type model item property in an <xforms:bind>. This is an important technique for forms that must operate on small, resource-constrained devices. Listing Two shows an assignment of types and a few other properties to a few of the instance nodes.
In Listing Two, each <xforms:bind> selects one or more nodes using the nodeset attribute, then an attribute such as type assigns a model item property. Note that the second bind uses both type and the dynamic constraint model item properties to ensure that each of the two nodes is both a double number and positive. The third bind uses the required model item property to ensure that the name and address of the loan applicant are filled out. The final bind uses the relevant model item property to indicate that the payment and total payout nodes are not relevant unless the inputs that will be used to calculate them are given. Although relevance can affect submission, it is used here to make the UI controls that will be bound to these nodes invisible until they become relevant.
The most important model item property of an instance node is its intrinsic value. In the case of the loan payment and total payout, these are derived by calculation over other instance nodes. Listing Three shows how to declare value formulae using the calculate attribute.
The first formula is the easiest since the total amount paid back is simply the product of the individual payment amount and the number of payments. However, it is also clear that this formula should be run last, even though it appears to be first. The XForms compute system automatically works out the correct order for the calculations. The second <xforms:bind> calculates the result of a temporary variable that was created to simplify the main loan calculation. Any number of additional XForms instances can be added to the XForms model, and each can contain as much data as needed by the form author. In this case, you need to translate from the human version of interest rate percentage to the mathematical value needed by the loan formula.
The third <xforms:bind> in Listing Three has several interesting features. First, it shows the use of conditional logic. Using an if() function, the calculation determines whether it is appropriate to apply the compound interest formula. The compound interest formula itself requires exponentiation, which is not available in XPath. It is also not available in XForms 1.0 (though it is currently planned to appear in XForms 1.1). However, XForms does allow extension function to be added by implementations, and forms, which use extension functions, must declare the extra required functions in the functions attribute of the <xforms:model> element (see Listing One).
The XForms Intent-Based UI
The values and model item properties of an XForms model are propagated to XForms UI controls by binding them to instance nodes. For example, Listing Four shows the ref attribute being used with <xforms:input> controls to obtain data, such as the principal or interest rate for the loan application of the prior section, with the <xforms:output> control to provide the results.
The <xforms:submit> in Listing Four lets users perform the data submission. The <xforms:trigger> allows the ability to perform sequences of actions other than just submission. Although the example in the listing could more efficiently be written with an <xforms:reset> action, the example uses a few <xforms:setvalue> actions instead to demonstrate cause-and-effect programming. Clearly, the payment and total payout are recalculated in response to user input of a principal, duration, or interest rate, but the model effects the declared binds regardless of the cause of the change. Hence, pressing the clear button causes the <xforms:setvalue> actions to occur, after which the payment and total payout are recalculated (back to zeroes, in this case).
XForms has several other user-interface controls to collect input by other methods. For example, the <xforms:textarea> collects multiline input, and the <xforms:select> control provides a multiselection control. Listing Five presents an example of a single selection control that would be used to let users choose a type of currency in the aforementioned loan application.
The exact type of single selection control is not specified by XForms. It could be a popup list, list box, or set of radio buttons. Regardless, one selection within the control will be created for each node of the nodeset given in the <xforms:itemset>. The <xforms:label> indicates what to show to users for the choice, and the <xforms:value> indicates what to put for a given choice into the referenced node of the <xforms:select1>. In this example, the third choice is USD, and if the user selects the choice labeled USD, then the value "USD" is placed into the currency attribute of the Principal element.
The <xforms:itemset> is not the only XForms element capable of iteration. We've already seen the use of the nodeset attribute in the <xforms:bind> to iterate through a set of nodes and assign model item properties to each node. Perhaps the most powerful UI construct in XForms is the <xforms:repeat>, which iterates a UI template for each node of a given nodeset. Often we think of the XForms repeat as generating a "table," with each "row" being a copy of the template for one of the nodes in the node set.
While the iteration capabilities of the XForms repeat are powerful, sometimes it is useful in the design process to have a simpler construct that lets multiple controls be grouped together and act as a unit. For this, XForms offers the <xforms:group>. It also offers a sort of multifaceted grouping construct called the <xforms:switch>, which allows easy switching to a particular group of controls from among many cases. For example, an address block consists of multiple controls, but its countenance depends on the type of address (for instance, the locale choice of users).
XForms represents a clean architecture for separating presentation, user interface, and business processing model. The separation, though, is first and foremost a logical one with the purpose of deriving classic software engineering benefits such as encapsulation and loose coupling. These are particularly important to the design experience for intelligent documents.
Yet, the user experience can often be simplified and enhanced through the creation of single documents that combine these separate logical components. Users are least encumbered when they have a single file containing their data, its presentation template, and the functional rules that govern how the document is updated in response to their further input. It is particularly helpful if users can attach other supporting files to the intelligent document. This lets users exploit the intelligence of the document when offline, and to efficiently e-mail or otherwise transport the entire context of a transaction.
For these and many other reasons, the XFDL language (see my article "XFDL: The Extensible Forms Description Language," DDJ, December 1999, and http://www.PureEdge.com/xforms/) has been upgraded to be a new "skin" for XForms. Perhaps the most compelling augmentation that XFDL provides to a core XForms document is the ability to create secure, auditable web transactions via the application of digital signatures over not just the data and processing model of the form, but the XFDL presentation layer itself.
<xforms:model xmlns:xforms="http://www.w3.org/2002/xforms" xmlns:xsd="http://www.w3.org/2001/XMLSchema" schema="loan.xsd" functions="power"> <xforms:instance xmlns="" id='loan'> <Loan> <Borrower> <Name>John Q. Public</Name> <Addr>123 Main St. Tinyville</Addr> </Borrower> <Principal currency="CDN">10000</Principal> <Duration>12</Duration> <InterestRate>5</InterestRate> <Payment>856.07</Payment> <TotalPayout>10272.84</TotalPayout> </Loan> </xforms:instance> <!-- More XForms instances for extra data --> <!-- XForms binds for model item properties and calculations --> <xforms:submission id='loanSub' method='post' action='http://example.org'/> </xforms:model>Back to article
<xforms:bind nodeset="Duration" type="xsd:nonNegativeInteger"/> <xforms:bind nodeset="Principal | InterestRate" type="xsd:double" constraint=". > 0"/> <xforms:bind nodeset="Borrower/*" required="true()"/> <xforms:bind nodeset="Payment | TotalPayout" relevant="../Principal > 0 and ../Duration > 0 and ../InterestRate > 0"/>Back to article
<xforms:bind nodeset="TotalPayout" calculate="../Payment * ../Duration"/> <xforms:instance id="rate" xmlns=""> <rate></rate> </xforms:instance> <xforms:bind nodeset="instance('rate')" calculate="instance('loan')/InterestRate div 100.0 div 12.0"/> <xforms:bind nodeset="Payment" calculate=" if(instance('rate') > 0, ../Principal * instance('rate') div (1.0 - power(1.0 + instance('rate'), -../Duration))), ../Principal * ../Duration"/>Back to article
<xforms:input ref="Principal"> <xforms:label>Enter principal:</xforms:label> </xforms:input> <xforms:input ref="Duration"> <xforms:label>Enter duration:</xforms:label> </xforms:input> <xforms:input ref="InterestRate"> <xforms:label>Enter interest rate:</xforms:label> </xforms:input> <xforms:output ref="Payment"> <xforms:label>Monthly payment:</xforms:label> </xforms:output> <xforms:output ref="TotalPayout"> <xforms:label>Total to be repaid:</xforms:label> </xforms:output> <xforms:trigger> <xforms:label>Clear</xforms:label> <xforms:action ev:event="DOMActivate"> <xforms:setvalue ref="Principal" value="'0'"/> <xforms:setvalue ref="Duration" value="'0'"/> <xforms:setvalue ref="InterestRate" value="'0'"/> <xforms:action ev:event="DOMActivate"> </xforms:trigger> <xforms:submit submission=`loanSub'> <xforms:label>Submit</xforms:label> </xforms:submit>Back to article
<!-- Another instance in the XForms model --> <xforms:instance id="curr" xmlns=""> <currencies> <choice type="CDN"/> <choice type="Euro"/> <choice type="USD"/> <choice type="Yen"/> </currencies> </xforms:instance> <!-- In the user interface layer --> <xforms:select1 ref="Principal/@currency"> <xforms:label>Choose currency</xforms:label> <xforms:itemset nodeset="instance('curr')/choice"> <xforms:label ref="@type"/> <xforms:value ref="@type"/> </xforms:itemset> </xforms:select1>Back to article