XForms model to model communication
XForms has a MVC architecture and this it what makes it so powerful when it comes to build complex applications that reach far beyond a few fields with validation. But as applications grow there naturally comes the requirement for modularization. Models are the building blocks of an XForms application.
XForms already supports multiple models on one page, each with its own, encapsulated instances and constraints. But models must be able to exchange some data in order to act as a whole in a larger architecture. Sometimes this just means to pass a single or only a few values and sometimes this means to exchange whole instances. At the same time a model should enforce some kind of encapsulation as known from object-oriented languages to ensure consistency and robustness.
Before discussing possible ways to enable model-to-model communication it should be mentioned that there is already a pattern to exchange instances between models. As two models existing in a page can perfectly use the same instance URI to load and submit data it’s already possible to ‘share’ those. But first this comes to the price of serializing the data as XML and re-parsing them when loading into the other model. Second – there will be two (detached) copies of the same instance – one in each model that need to be synchronized with each other.
Alternatives for data-exchange between models
Though XForms is not a new technology there are no established patterns for data-exchange between models. The following paragraphs are based upon our experiences with this topic. Though the need has existed before the upcoming of subforms has made the need for some kind of data-exchange more urgent as for maximum re-use of subforms (which can be full-fledged standalone forms in other contexts) you’ll likely want your models to be as independent and loosely coupled as possible.
The following paragraphs present some approaches we found during our project work over time. It should be noted that these techniques are custom betterFORM extensions to the standard but equivalents might be found in other XForms implementations.
If complete instances or larger parts of them need to be shared between models a cross-model submission is the right answer. An example might an address model that shall be re-used within a larger context that also needs the address instance data structure for incorporation into one of its instances.
<xf:submission ref="instance()" resource="context:aKey" method="put" replace="none" ...
A bit of background will help to understand what the ContextSubmissionHandler is doing. In betterFORM each instance of a XForms processor maintains a hashmap called ‘context’ with params. E.g. this map holds URL params and http headers that were used to call the XForms page and serves as a way to pass in some values for use within the form. The above submission stores the default instance into this map under the key ‘aKey’. To fetch the instance from that map the following submission can be used:
<xf:submission ref="instance('another')" resource="context:aKey" method="get" replace="instance" ...
Here the ‘get’ method is used to read the previously stored instance from the map and replace instance ‘another’ with it. For safety the submissionhandler is consuming which means that the instance will be deleted automatically from the ‘context’ hashmap once it has been read.
This kind of submission is especially useful in conjunction with subforms – if you are using multiple subforms e.g. in a wizard and you want to pass instance data from one subform to another the context hashmap is the right way to do it. Form A might already have been destroyed when loading form B but the instance data can survive within the context hashmap and be used in the next step of the wizard.
Another advantage of cross-model submissions is that one model can re-use the validation constraints and logic of another without breaking the encapsulation between the models as data exit and enter a model through its interface – the submission module. As the data are exchanged directly between models within a single instance of an XForms processor its reasonably safe to rely on validations done by a foreign model without duplicating binding constraints in both models.
The instanceOfModel() function
If there are only a few values to be passed the instanceOfModel(modelId, instanceId) function is a way to do this. It takes two params – the id of a model and the id of an instance within that model.
<xf:setvalue ref="instanceOfModel('modelA', 'default')/baz" value="instanceOfModel('modelB','default')/foo/bar"/>
In this case the value of the XPath ‘foo/bar’ in the ‘default’ instance of ‘modelB’ will be set to the node ‘baz’ in the instance ‘default’ of ‘modelA’. Easy enough but this function also is somewhat dangerous as users might expect this function to work in any context. E.g. they might expect that instanceOfModel can also be used on bind elements. But this would create inter-model dependencies which is not a healthy thing to do. The XForms dependency engine was designed to efficiently handle dependencies within one model but not to create dependencies between models. Ignoring this could cause serious consequences for the consistency of data.
For this reason this function should be used with caution. As long as its used in conjunction with the setvalue action everything should be fine but you shouldn’t use it within a model. Therefore if in doubt the method proposed in the next section should be preferred.
Dispatching custom events
Dispatching a custom event with contextinfo is a less intrusive way of passing values between models than the instanceOfModel() function (and avoids its dangers). It uses the ability of XML Events to carry context information along with the actual event. A small addition to the standard XForms dispatch action allows you to specify custom parameters for a event and pass a set of values to a target in another model. As always an example illustrates this best.
Somewhere within the scope of model ‘A’ a dispatch action is fired. It passes two params as contextinfo to the event. The first one is a static string while the second (someXPath) pulls a value from an instance in model ‘A’.
<xf:group model="A"> --- <xf:dispatch name="customEvent" targetid="foo"> <xf:contextinfo name="param1" value="'hello'"/> <xf:contextinfo name="param2" value="someXPath"/> </xf:dispatch> ... </xf:group>
Somewhere in the scope of Model ‘B’ a handler for ‘customEvent’ is defined and can access the passed params with the standard XForms event() function. – That easy.
<group model="B" id="foo"> <xf:action ev:event="customEvent"> <xf:setvalue ref="staticResult" value="event('param1')"/> <xf:setvalue ref="xpathResult" value="event('param2')"/> </xf:action> ... </xf:group>
Though this method uses a bit more markup than the instanceOfModel approach it avoids its dangers. As it passes data by value and not by reference it honors the encapsulation of models.
This article was intended to show some alternatives for the model-to-model communication in XForms. However it is assumed that this is not the end of the discussion and other ways will be invented and hopefully be standardized in future version of XForms.
As always comments and suggestions are more than welcome.