Sooner or later when forms start to grow more complex it is time to think about how to avoid redundancies and keep the documents maintainable. If you push loads of data into a 5000 lines XForms document with potentially hundreds of controls you shouldn’t complain about bad performance. Instead it’s time to modularize your forms as you would do with any other code. As a sideeffect this will also have a very positive influence on runtime performance.
As XForms follows a MVC architecture the XForms model is the first logical candidate when decomposing larger forms into smaller pieces. Aggregating more complex forms from little snippets of UI (or snippets of a model) is a limited approach as the interesting parts are located on the bind Elements. This is where controls learn about their constraints, states, calculations and data types. Instead of just glueing pieces of markup together the inclusion of complete models allow the reuse of all the semantics defined within them.
This section describes two approaches of form (and instance) aggregation but will be focussed on aggregating complete XForms models with their appropriate UI or complete forms into larger documents. Though possible aggregation of separate little Model or UI snippets is not covered here.
Dynamic embedding (subforms)
The feature described here is not part of the XForms standard and is therefore not portable to other implementations. The matter is discussed by the WG and it’s likely that there will be a similar solution in XForms 1.2. Future releases may change the syntax or processing model as further specifications mature.
In complex applications that work with a multitude of different data structures the form that needs to be loaded for a given structure is sometimes not known before runtime. Especially if larger data structures have to be aggregated from a set of substructures (like XML Schema complex types) a mechanism is needed to dynamically load a subform for a given data structure, embed it into the running page and let the loaded part interact with the already loaded model(s).
Building ‘super-forms’ that incorporate all the logic and handle all the substructures only works to a certain extend. When forms grow into thousands of lines of markup a critical phase with regard to maintainability and extensibility is reached.
Another aspect is reuse. Subforms can be complete XHTML/XForms documents that run independently of the ‘masterform’.
betterFORM provides a solution for these use cases through a tiny extension of the XForms ‘load’ action syntax. The XForms load action traverses an URI referenced by the value of the resource child Element or Attribute. It further provides an attribute ‘show’ which allows ‘replace’ and ‘new’ as possible values. ‘new’ loads the referenced URI into a new (browser) window while ‘replace’ replaces the current document with the content of the specified resource (thereby ending the running XForms session).
<xf:load show=“replace“> <xf:resource value=“someURI“/> </xf:load>
XForms borrows the show Attribute and the two possible values ‘new’ and ‘replace’ from the W3C XLink standard. XLink itself defines more possible values for the show Attribute. One of these is ’embed’ which is adopted by betterFORM. Last thing needed is a way to say where to place the loaded subform into the document. BetterForm borrows the Attribute targetId Attribute from XForms for that purpose. The targetId is an idref to an Element which must exist in the document.
BetterFORM provides two different ways to reference a subform for embedding:
The difference is that for 2. the part after the fragment identifier (‘#’) is interpreted as an idref pointing to a node in the target document ‘FileName.xml’.
Embedding complete documents
The following sample shows the load markup within a masterform to embed a complete subform into the specified target node ‘inputMountPoint’
... <xf:load show="embed" targetId="inputMountPoint"> <xf:resource value=“'Input.xml'“/> </xf:load> ... <div id=“inputMountPoint“/>
Executing the load action above loads and parses the resource denoted by the resource value (here Input.xml) and initializes it as XForms. The initialized markup then replaces the node specified by the targetid (<div id=“inputMountPoint“>).
To allow further reference to that node within your document the newly imported node will get the id of the Element referenced by targetId. The sample shows a complete XForms subform with its model and UI controls.
The host document in this example uses a HTML snippet instead of a full HTML file. This is because you can’t load an HTML document into the body of another without using iframes.
<div xmlns="http://www.w3.org/1999/xhtml" xmlns:xf="http://www.w3.org/2002/xforms"> <xf:model id="m-child-1"> <xf:instance id="i-child-1" xmlns=""> <data> <item>false</item> <item>2009-10-01</item> <item>2009-10-01</item> ... </data> </xf:instance> <xf:bind nodeset="item" type="boolean" readonly="true()"/> <xf:bind nodeset="item" type="date"/> ... </xf:model> <xf:group> <xf:input ref="item" model="m-child-1"> <xf:label>item 1</xf:label> </xf:input> <xf:input ref="item" model="m-child-1"> <xf:label>item 2</xf:label> </xf:input> ... </xf:group> </div>
The document must have a single root node. Here the model and UI are wrapped into a HTML div Element giving a single handle to both parts.
Embedding complete documents is a start but degrades the subform to a piece of markup that only makes sense in the context of a ‘master’ or ‘parent’ form. It also complicates testing of the subforms as you are forced to run in ’embedded mode’.
Having forms that can act standalone and/or be embedded into others certainly offers new perspectives for modularizing an application.
This example shows how to load a piece of markup (which happens to contain a complete XForms model and UI) into a running XForms:
<xf:load show="embed" targetid="controlMount"> <xf:resource value="'../reference/Output.xhtml#xforms'"/> </xf:load>
Note the fragment identifier (‘xforms’) on the resource URI. This will be used as an idref pointing to an Element in the target document. Only this Element will be grabbed for embedding.
The approach to load a subform with a single idref enforces a slightly different authoring style than might be found in XForms examples on the net. Many people put the XForms model into the head of a XHTML document and the UI into the body. This is fine but a mere convention. The XForms model is perfectly allowed to live in the body. That makes no difference for the processing and is a key premise of the betterFORM implementation for the efficient embedding of forms.
The example below uses the Element with id ‘xforms’ to wrap model and UI in the body. This gives us the handle to fetch the model along with its UI in one go. The model itself is additionally wrapped with a div with style=“display:none;“. That prevents the browser from becoming puzzled through the foreign markup.
The document for this example would look like this (shortened):
<?xml version="1.0" encoding="ISO-8859-1"?> <html xmlns="http://www.w3.org/1999/xhtml" xmlns:xf="http://www.w3.org/2002/xforms"> <head> <title>Output Control</title> </head> <body> <!-- other content --> <div id=“xforms“> <div style=“display:none;“> <xf:model id=“foo“> ... </xf:model> </div> <xf:group> <!-- XForms UI controls here --> </xf:group> </div> <!-- other content --> </body> </html>
If there’s already a model loaded which is a descendent of the target Element that model will be unloaded by dispatching a XForms model-destruct event to each descendent model. After the model(s) have shut down the new form will be imported and initialized as specified by the XForms Specification by dispatching xforms-model-construct, xforms-model-construct-done and xforms-ready events.
To unload a previously embedded form dispatch a load action with the value ‘none’ instead of ’embed’ for the show Attribute:
<xf:load show=“none“ targetid=“controlMount“/>
Embedding works very fast and stable though the functionality also raises some questions:
a. how can models interact with each other?
b. how can models exchange data without breaking the rules?
c. how can id collisions be avoided?
We have found a few first answers for a) and b) leaving c) for later versions of betterFORM. For the moment the form author has to take care that id collisions do not happen.
Accessing nodes in a foreign model
Just loading models and putting them side by side into a processor at runtime is fine but also of limited value if you can’t exchange data between these models.
Each model is handled within the XForms standard as a separate entity with its own data, dependencies, constraints, types, states and lifecycle. You may think of a XForms model as an object as you find it in object-oriented languages like Java. Thus the developers of betterFORM decided to take the conservative road by not ‘breaking the capsule’ of the XForms model. The approach will evolve the more experiences with embedding XForms have been made.
To access a single node or a nodeset in a foreign model you can use the instanceOfModel XPath function. It works similar to the XForms instance function but takes the model id as first parameter.
The example shows how to copy the ‘origin’ node value ‘foo’ in model ‘model-a’ to the ‘clone’ node in model ‘model-b’:
This example shows how to copy a value from ‘model-a’ to ‘model-b’ with a setvalue action:
<xf:model id=“model-a“> <xf:instance id=“a-default“> <data xmlns=““> <origin>foo</origin> </data> </xf:instance> </xf:model> <xf:model id=“model-b“> <xf:instance> <data xmlns=““> <clone/> </data> </xf:instance> </xf:model> ... <setvalue model=“model-b“ ref=“clone“ value=“instanceOfModel('model-a','default')/origin“ />
This is a good and simple way of passing a few parameters between forms like modes, language settings and the like.
Special attention should be paid to the fact that using instanceOfModel() ties your forms together. They will not work any more when a model with a matching id cannot be found at runtime. Conditional execution of the function can prevent this.
Exchanging instances between models
The instanceOfModel function is a convenient way if you only want to share small portions of data. The ModelSubmissionHandler allows to exchange larger quantities or whole trees of instance data between models. As the name suggests it enables submissions between models that work just the same as every submission in XForms with full support of all submission Attributes.
The ModelSubmissionHandler supports the GET and POST submission methods to:
- GET – fetch data from a foreign model and copy them into a local instance.
- POST – send local instance data and update the foreign model.
<xf:submission id="s-load-foreign-instance" resource="model:foreign#instance('contact')" replace="instance" method="get" model=“local“/>
This submission will fetch the instance ‘contact’ from model ‘foreign’ and replace its own default instance.
Of course you can use all other features of submission and select only a part of an instance and insert it at a target node in the replaced one.
<xf:submission id="s-update-foreign" resource="model:foreign#instance('contact')/data/postaladdress[@id='2']" replace="none" method="post" model=“local“/>
This submission sends the default instance of the local model to model ‘foreign’ for updating but only the data will be inserted at a node ‘postalAddress’ in the foreign instance.
Often it is sufficient to just aggregate larger documents from different source files instead of using dynamic embedding as just shown. This so called static inclusion takes place before the XForms processor receives the form document to process it.
Dynamic embedding gives you ultimate control over when to load and embed at runtime while static inclusion is just an aggregation (but nevertheless sometimes useful) mechanism like server-side includes or XInclude.
Why not use XInclude then? It turned out that with XInclude it’s harder to control when the inclusion is wanted and to suppress it when it is not wanted. Further the Xinclude implementation support in Xerces did not make it easy to get to the desired results (e.g. limited XPointer support).
The inclusion feature is not enabled by default in betterFORM. You have to activate it by editing the betterform-config.xml found in the WEB-INF directory of your installation. Change the value of property ‘webprocessor.doIncludes’ to ‘true’ and save the file.
betterFORM uses a XSLT transformation for the inclusion which is called before a new form session starts. At the moment betterFORM only supports fetching of complete documents and fetching of document fragments by id though extensions are made easy (e.g. selecting an Element with an XPath location path) by simply adapting the stylesheet.
Static Inclusion Example:
When the document which contains this tag is requested the transform will be triggered, fetch the file ‘foo.xhml’ and extract the Element denoted by id from it. This Element will then become part of the source document and replace the <bf:include /> Element.