Binding Framework
Intro
Likely you will want to use CForms to "edit stuff", such as the properties of a bean or data from an XML document (we'll simply use the term object to refer to either of these). This supposes that before you show the form, you copy the data from the object to the form, and after the form has been validated, you copy the data in the form back to the object. To avoid having to write actual code for this, a binding framework is available.
The same illustration as in the introduction, but now extended with the binding, can be viewed here.
The basic definition of a binding is as follows (if you don't know Java, just ignore this):
public interface Binding { public void loadFormFromModel(Widget frmModel, Object objModel); public void saveFormToModel(Widget frmModel, Object objModel); }
A binding can work with any object and can perform the binding in any possible way. Currently one implementation is available, based on JXPath. JXPath allows to address data in both beans and XML documents using XPath expressions, so this binding implementation can be used both with beans and XML documents. The rest of this document will focus on this implementation.
The binding is configured using an XML file. This XML file contains elements in the fb namespace (Forms Binding):
http://apache.org/cocoon/forms/1.0#binding
What does a binding file look like?
To give you an idea of what a binding file looks like, below a very simple example is shown.
<fb:context xmlns:fb="http://apache.org/cocoon/forms/1.0#binding" path="/" > <fb:value id="firstname" path="firstName"/> <fb:value id="lastname" path="lastName"/> <fb:value id="email" path="email"/> </fb:context>
The id attribute identifies the widget. The path attribute is the address of the items in the target object (a Javabean or an XML document). The paths can be arbitrary JXPath expressions.
[Convention] Let's call all elements in the fb namespace "binding elements". They all cause a binding-related action to be performed.
The fb:context element changes the JXPath context to the specified path. The path expressions on the binding elements occuring inside the context element will then be evaluated in this context, thus relative to the path specified on the fb:context element.
The fb:value element is used to bind the value of a widget.
The binding framework can do much more than what is shown in the simple example above, so read on for more meat.
Quick reference of supported binding elements
Element |
Description |
Attributes |
child elements |
---|---|---|---|
fb:* |
common settings for all bindings |
direction |
not applicable, see specific elements |
fb:context |
changes the JXPath context |
path |
any |
fb:value |
binds the value of widgets |
id, path |
fb:on-update, fd:convertor |
fb:aggregate |
binds aggregatefield widgets |
id, path |
fb:value |
fb:repeater |
binds repeater widgets |
id, parent-path, row-path |
fb:identity, fb:on-bind, fb:on-delete-row, fb:on-insert-row |
fb:identity |
specifies unique fields for a repeater row |
none |
fb:unique-field |
fb:set-attribute |
sets an attribute to a fixed value |
name, value |
none |
fb:delete-node |
deletes the current context node |
none |
none |
fb:insert-node |
insert a node in an XML document |
src, xpath |
piece of XML that should be inserted |
fb:insert-bean |
inserts an object in a list-type bean property |
addmethod, classname (optional) |
none |
fb:simple-repeater |
binds repeater widgets |
id, parent-path,row-path, clear-before-load, delete-parent-if-empty |
any |
fb:javascript |
write binding logic in Javascript |
id, path |
fb:load-form, fb:save-form |
fb:custom |
write binding logic in Java |
id, path, class, builderclass,factorymethod |
fb:config |
Detailed reference of binding elements
fb:*/@direction
All Bindings share the ability to have the two distinct actions they provide (i.e. load and save) been enabled or disabled by setting the attribute direction to one of the following values:
value |
load active? |
save active? |
---|---|---|
both(default) |
yes |
yes |
load |
yes |
no |
save |
no |
yes |
The default value 'both' for this attribute makes its use optional.
NOTE: this setting replaces the @readonly attribute that was available only on selected bindings.
fb:context
Attributes:
- path
- direction (optional)
The fb:context element is usually used in two occasions. First of all, it is used as the root element of the binding file; because an XML file must always have one root element, and you will usually want to perform more than one binding action.
Secondly, you use fb:context if you need to address multiple items having a common base path. On the one hand, this saves you on typing and helps readability, and on the other hand, this improves the performance of the binding. To illustrate this with an example, instead of doing this:
... <fb:value id="firstname" path="info/person/firstName"/> <fb:value id="lastname" path="info/person/lastName"/> ...
it is better to do this:
... <fb:context path="info/person"> <fb:value id="firstname" path="firstName"/> <fb:value id="lastname" path="lastName"/> </fb:context> ...
fb:value
Attributes:
- id
- path
- direction (optional)
- fb:on-update (optional)
- fd:convertor (note the fd: namespace!) (optional)
<fb:value id="birthday" path="person/birthday"> <fd:convertor datatype="date" type="formatting"> <fd:patterns> <fd:pattern>yyyy-MM-dd</fd:pattern> </fd:patterns> </fd:convertor> </fb:value>The datatype attribute on the fd:convertor element, which you don't have to specify in the form definition, identifies the datatype to which the convertor belongs.
fb:aggregate
Attributes:
- id
- path
- direction
- fb:value elements
fb:repeater
Attributes:- id
- parent-path
- row-path
- row-path-insert (optional)
- direction (optional)
- fb:identity
- fb:on-bind
- fb:on-delete-row
- fb:on-insert-row
<things> <thing ... /> <thing ... /> </things>then the parent-path attribute contains the path to the containing element ("things") and the row-path attribute contains the path to the repeating element ("thing").For beans: if your bean has a property "things" which is a Collection [or whathever JXPath supports as lists], then the parent-path should simply contain "." and the row-path "things".For both beans and XML documents there is an optional attribute row-path-insert which functions just like the row-path but is used for the nested on-insert-row binding (see below). By default the row-path-insert just takes the value of the row-path. By explicitely setting them different one can exploit one of the following use cases:
- (1) use xpath-predicates in the row-path (note that you can not do that on the row-path-insert)
- (2) save the inserted rows in a different target-node of the backend model.
The children of the fb:on-insert-row are executed in case a new row has been added to the repeater. Typically this will contain a fb:insert-node or a fb:insert-bean binding (see the descriptions of these binding elements for more details).
fb:identity
Child elements:
- fb:value widget bindings constituting the repeater row identity
The nested elements just describe regular value bindings (these can declare their own convertors if needed). Newly added rows in the repeater can (but should not) have a null value for the identity widget(s). Identity widgets typically are uneditable and are specified as <fd:output> widgets. If you don't need the identity widget(s) at the client, you don't need to add them to the template at all! This prevents, e.g., database IDs from getting to the client.
NOTE: This 'identity' binding is only active in the 'load' operation, so specifying the direction="save" is meaningless.
fb:set-attribute
Attributes:
- name
- value
- direction (optional)
fb:delete-node
Attributes:
- direction (optional)
Deletes the current context node.
NOTE: This binding is never active in the 'load' operation, so there is no need to specify the direction="save" to protect you model from being changed during load.
fb:insert-node
Attributes:
- src (optional)
- xpath (optional, only in combination with src)
- direction (optional)
NOTE: This binding is never active in the 'load' operation, so there is no need to specify the direction="save" to protect you model from being changed during load.
fb:insert-bean
Attributes:
- addmethod
- classname (optional)
- direction (optional)
fb:simple-repeater
Attributes:
- id
- parent-path (same as in fb:repeater)
- row-path (same as in fb:repeater)
- clear-before-load (default true)
- delete-parent-if-empty (default false)
- direction (optional)
fb:javascript
Attributes:- id
- path
- direction (optional)
- fb:load-form
- fb:save-form
<fb:javascript id="foo" path="@foo"> <fb:load-form> var appValue = jxpathPointer.getValue(); var formValue = doLoadConversion(appValue); widget.setValue(formValue); </fb:load-form> <fb:save-form> var formValue = widget.getValue(); var appValue = doSaveConversion(formValue); jxpathPointer.setValue(appValue); </fb:save-form> </fb:javascript>This example is rather trivial and could be replaced by a simple <fb:value>, but it shows the available variables in the script:
- widget: the widget identified by the id attribute,
- jxpathPointer: the JXPath pointer corresponding to the path attribute,
- jxpathContext (not shown): the JXPath context corresponding to the path attribute
<fb:javascript id="selectionListWidget" path="objectCollection" direction="load"> <fb:load-form> var collection = jxpathPointer.getNode(); widget.setSelectionList(collection, "id", "name") </fb:load-form> </fb:javascript>NOTE:
- The <fb:save-form> snippet should be ommitted if the direction attribute is set to load.
- The <fb:load-form> snippet should be ommitted if the direction attribute is set to save.
- The @readonly attribute supported in early versions of this binding has been replaced by the @direction attribute as supported now on all binding elements.
fb:custom
Attributes:- id (optional, if not provided the containing widget-context will be passed)
- path (optional, if not provided "." is assumed)
- direction (optional)
- class (optional, if not present @builderclass and @factorymethod should be)
- builderclass (optional)
- factorymethod (optional)
- fb:config
<fb:custom id="custom" path="custom-value" class="org.apache.cocoon.forms.samples.bindings.CustomValueWrapBinding"/>This describes the classname of your user defined binding class.Above imposes the following requirements:
- there is a class CustomValueWrapBinding available in the specified package
- it has a default (i.e. no arguments) constructor
- it is a subclass of org.apache.cocoon.forms.binding.AbstractCustomBinding
- void doLoad(Widget widget, JXPathContext context) throws BindingException;
- void doSave(Widget widget, JXPathContext context) throws BindingException;
- widget: the widget identified by the id attribute,
- context: the JXPath context corresponding to the path attribute
<fb:custom id="config" path="config-value" builderclass="org.apache.cocoon.forms.samples.bindings.CustomValueWrapBinding" factorymethod="createBinding" > <fb:config prefixchar="[" suffixchar="]" /> </fb:custom>The additional requirements to your user defined classes are now:
- there is a builderclass CustomValueWrapBinding class having a static factorymethod
- that can (optionally) take an org.w3c.dom.Element holding it's configuration
- and return an instance of your own user-defined binding which must be a non abstract subclass of org.apache.cocoon.forms.binding.AbstractCustomBinding