Forms
At this point, our application allows us to navigate in
our TaskBean objects, and we're ready to start editing them.
To take advantage of the Cocoon Forms editing features, we need the
following:
-
A pipeline to call the Flowscript handleForm() function
(provided by the Cocoon Forms Flowscript library, Form.js)
with the appropriate parameters.
-
A Form Model file, to define the data model
of our form.
-
A Form Template file, to define the layout
of our form while letting Cocoon Forms create the appropriate
HTML elements to edit our form widgets.
-
A Form Binding file, to define a
mapping between the Form data model and our Java beans.
This seems a lot of work when described in this way, but notice
once again how modular things are: each "concern" of our bean editor
is clearly defined in its own place.
Note also that, while our example uses static XML files for these
definitions, nothing prevents you from generating them dynamically
using pipelines and the cocoon:/ protocol. If your application
provides a central data dictionary, for example, it would be possible
to generate at least simple versions of the required definition files
automatically.
Sitemap
Let's look first at the pipeline definition for the
edit/singleTask request:
<map:match id="edit" pattern="edit/*">
<map:call function="handleForm">
<map:parameter name="function" value="{1}Editor"/>
<map:parameter name="form-definition" value="cocoon-app/forms/{1}FormModel.xml"/>
<map:parameter name="bindingURI" value="cocoon-app/forms/{1}FormBinding.xml"/>
</map:call>
</map:match>
What this does is to call the handleForm() function of the
Form.js Flowscript library, telling it which of
our own Flowscript function to call to edit the form, and indicating
the locations of the Form Model (form-definition) and
Form Binding documents.
Later, the Flowscript function shown below will call this pipeline:
<map:match id="showForm" pattern="internal/show-form/*">
<map:generate src="cocoon-app/forms/{1}FormTemplate.xml"/>
<map:transform type="forms"/>
<map:transform src="context://samples/blocks/forms/resources/forms-samples-styling.xsl"/>
<map:call resource="html"/>
</map:match>
Using the FormsTransformer to generate the appropriate HTML
elements for our form's widgets.
Flowscript
Here's our Flowscript form editing function, called by
the handleForm library function to edit our form:
0027 // Edit a single TaskBean object using Cocoon Forms
0028 function singleTaskEditor(form) {
0029 id = cocoon.request.getParameter("taskId");
0030 bean = db.getTaskBeanById(id);
0032 form.load(bean);
0033 form.showForm("internal/show-form/singleTask");
0034 form.save(bean);
0035 displayTaskBean(id,bean);
0036 }
Notice how simple and readable the code is - the magic happens
behind the Cocoon Forms scenes, based on the Forms definitions
shown below.
Form Model
Here's the definition of our Form Model.
<fd:form id="form">
<fd:widgets>
<fd:output id="taskId" readonly="true" required="true">
<fd:label>
Task ID
</fd:label>
<fd:datatype base="integer"/>
</fd:output>
<fd:field id="taskName" required="true">
<fd:label>
Task name
</fd:label>
<fd:datatype base="string"/>
</fd:field>
<fd:field id="assignedTo" required="true">
<fd:label>
Assigned to
</fd:label>
<fd:datatype base="string"/>
</fd:field>
<fd:repeater id="comments">
<fd:label>
Comments
</fd:label>
<fd:widgets>
<fd:output id="id">
<fd:datatype base="integer"/>
</fd:output>
<fd:booleanfield id="select">
<fd:label>
Select
</fd:label>
</fd:booleanfield>
<fd:field id="date" required="true">
<fd:label>
Date
</fd:label>
<fd:datatype base="date">
<fd:convertor>
<fd:patterns>
<fd:pattern>
dd/MM/yyyy
</fd:pattern>
</fd:patterns>
</fd:convertor>
</fd:datatype>
</fd:field>
<fd:field id="comment" required="true">
<fd:label>
Comment
</fd:label>
<fd:datatype base="string">
<fd:validation>
<fd:length min="5" max="150">
<fd:failmessage>
The comment length must be between 5 and 150 characters
</fd:failmessage>
</fd:length>
</fd:validation>
</fd:datatype>
</fd:field>
</fd:widgets>
</fd:repeater>
<fd:repeater-action id="addcomment" action-command="add-row" repeater="comments">
<fd:label>
Add comment
</fd:label>
</fd:repeater-action>
<fd:repeater-action id="removecomment" action-command="delete-rows" repeater="comments"
select="select">
<fd:label>
Remove selected comments
</fd:label>
</fd:repeater-action>
</fd:widgets>
</fd:form>
As you can see, the Forms subsystem uses widgets
with labels and datatypes to define a Model.
If you omit the repeater element above, this is a fairly simple
data model which represents our form's data. The repeater is used to model
the 1-N relationship between our TaskBean and TaskCommentBean objects.
Strongly typed fields are an important features of Cocoon Forms,
and provide many standard validation features which make our
life easier.
This model is independent from the way the form
is going to look in HTML (or WML, or XUL, or whatever), and
also independent of the internal structure of our Java beans.
Here we use constants for the widget labels (field names),
but the i18n transformer could be added to our pipeline to
easily generate views and forms in multiple languages.
Form Binding
<fb:context id="form" path="/">
<fb:value id="taskId" path="id" direction="load"/>
<fb:value id="taskName" path="taskName"/>
<fb:value id="assignedTo" path="assignedTo"/>
<fb:repeater id="comments" parent-path="." row-path="comments">
<fb:identity>
<fb:value id="id" path="@id" direction="load"/>
</fb:identity>
<fb:on-bind>
<fb:value id="date" path="date"/>
<fb:value id="comment" path="comment"/>
</fb:on-bind>
<fb:on-delete-row>
<fb:delete-node/>
</fb:on-delete-row>
<fb:on-insert-row>
<fb:insert-bean classname="org.apache.cocoon.samples.tour.beans.TaskCommentBean" addmethod="addComment"/>
</fb:on-insert-row>
</fb:repeater>
</fb:context>
Here's the binding definition, which allows the Forms
subsystem to automatically move data from our Form's internal model
to our Java beans.
This looks simple enough: for example, we tell the Forms
subsystem that the form's taskId field is mapped
in readonly mode to the bean's id field.
The data mapping is triggered by the Flowscript form.load and
form.save calls shown above.
For the taskId form field, the readonly flag
makes the binding unidirectional.
The value of the form field will not be copied to the bean
when form.save is called.
Form Template
Here's the template used by the FormsTransformer, which is activated
by the above sitemap excerpt.
<page id="page">
<title>
TaskBean editing
</title>
<content>
<ft:form-template action="#{$continuation/id}.continue" method="POST">
<h2>
Task info
</h2>
<table>
<tr>
<tr>
<td class="legend">
<ft:widget-label id="taskId"/>
</td>
<td>
<ft:widget id="taskId"/>
</td>
</tr>
<td class="legend">
<ft:widget-label id="taskName"/>
</td>
<td>
<ft:widget id="taskName">
<fi:styling size="60"/>
</ft:widget>
</td>
</tr>
<tr>
<td class="legend">
<ft:widget-label id="assignedTo"/>
</td>
<td>
<ft:widget id="assignedTo">
<fi:styling size="60"/>
</ft:widget>
</td>
</tr>
</table>
<input type="submit" value="Save page"/>
<h2>
<ft:widget-label id="comments"/>
</h2>
<ft:repeater-size id="comments"/>
<table>
<tr>
<th>
</th>
<th>
<ft:repeater-widget-label id="comments" widget-id="date"/>
</th>
<th>
<ft:repeater-widget-label id="comments" widget-id="comment"/>
</th>
</tr>
<ft:repeater-widget id="comments">
<tr>
<td>
<ft:widget id="select"/>
</td>
<td>
<ft:widget id="date">
<fi:styling type="text" size="10"/>
</ft:widget>
</td>
<td>
<ft:widget id="comment">
<fi:styling size="60"/>
</ft:widget>
</td>
</tr>
</ft:repeater-widget>
<tr>
<td colspan="3" align="right">
<ft:widget id="addcomment"/>
<ft:widget id="removecomment"/>
</td>
</tr>
</table>
</ft:form-template>
</content>
</page>
Based on the Form Model definition, The FormsTransformer replaces
the <ft:widget> elements by the appropriate HTML input elements.
That's it!
Well, this time it certainly gets more complicated. But stand back and look
at the code in the bean-editor/cocoon-app directory
of this tour's files.
You will hopefully notice that we've written little "code", actually
almost only definitions but very little actual code. Yet, the editing application
is
robust, easy to use, and very adaptable to different presentations or output
devices.
Another important feature of the Cocoon Forms subsystem is its extensibility:
a clean design makes it fairly easy to add custom Java classes for custom formatting,
validation and bindings.