Intent
"Write once, use many" paradigm applied to forms. Create form parts once, that you can aggregate and reuse across many forms, in many applications without any neither change nor redundancy.
Motivation
Forms usually contains some identical information (System information, Author, Date and time concepts, ...) and differ only a little from mode to mode (create, update, search, detail). If we use forms framework in a quick approach, we usually define form (and configuration files) for each mode, and redundancy and complexity increase a lot, while maintenability decreases a lot too... The infernal spiral (direct french translation :-).
We can have a more elaborate approach to avoid all these drawbacks. I will try to explain you this in the following lines.
Architecture
- Form definition : describes the fields contained in the form
- Form binding : describes the xml object you want to obtain. According the framework you use, you may convert it in Java object too, for example
- Form template : describes how you want your fields to be displayed
Filtering (see DesignPattern/Filtering) : filters and adapts files above thanks to additional tags (the filters)
Advanced concepts
To avoid redundancy, increase maintenance and reduce complexity, it may be a good idea :
- to implement class concept (at the field level), that will be aggregated and will define form repositories
to define a metabind file for the binding and template file, associated with filter tags (see DesignPattern/Filtering)
- to define meta widgets in this metabind file, that will aggregate binding and templating tags in logical units you can reuse in different forms (like class for field and form definition)
Just before starting, here is the directory structure. Under the crud/feature/cud/docs directory, you have :
Class concept implementation
First, we have a form repository that lists the form classes we need. Form repositories are stored in cud/doctypes directory. Above, we create a calendar doctype, composed of a few classes :
<fd:form
xmlns:fd="http://apache.org/cocoon/forms/1.0#definition"
xmlns:fb="http://apache.org/cocoon/forms/1.0#binding"
xmlns:i18n="http://apache.org/cocoon/i18n/2.1"
xmlns:filter="http://bluexml.org/filter/1.0">
<fd:widgets id="calendar">
<fd:new id="Base"/>
<fd:new id="Authoring"/>
<fd:new id="Classification"/>
<fd:new id="Calendar"/>
<fd:new id="Date"/>
<fd:new id="Time"/>
<fd:new id="Alarm"/>
<fd:new id="Security"/>
<fd:new id="NewVersion"/>
<fd:new id="Versions"/>
</fd:widgets>
</fd:form>Classes are in reality form definition. You will find above the form definition for the Calendar class. You may notice dynamic list.
<fd:form xmlns:fd="http://apache.org/cocoon/forms/1.0#definition"
xmlns:fb="http://apache.org/cocoon/forms/1.0#binding"
xmlns:i18n="http://apache.org/cocoon/i18n/2.1"
xmlns:filter="http://bluexml.org/filter/1.0">
<fd:widgets>
<fd:class id="Calendar">
<fd:widgets>
<fd:field id="title" required="true">
<fd:datatype base="string"/>
</fd:field>
<fd:field id="description">
<fd:datatype base="string"/>
</fd:field>
<fd:field id="type">
<fd:datatype base="string"/>
<fd:selection-list src="cocoon:/process-selection-list" dynamic="true"/>
</fd:field>
<fd:field id="location">
<fd:datatype base="string"/>
<fd:selection-list src="cocoon:/process-selection-list-specific?xpath=/document[(meta/doctype='ae_entity')and(meta/type='lieu')]&order=meta/name&value=$doc/meta/id&label=fn:string(fn:concat($doc/meta/name,%20'%20-%20',%20$doc/meta/city))" dynamic="true"/>
</fd:field>
<fd:field id="link">
<fd:datatype base="string"/>
</fd:field>
<fd:field id="visibility">
<fd:datatype base="string"/>
<fd:selection-list src="cocoon:/process-selection-list" dynamic="true"/>
</fd:field>
<fd:field id="calendar">
<fd:datatype base="string"/>
<fd:selection-list src="cocoon:/process-selection-list" dynamic="true"/>
</fd:field>
</fd:widgets>
</fd:class>
</fd:widgets>
</fd:form>With this first file, we'll have this final form definition.
Form binding and template
Data structure and structure of the presentation are often (but not always) related, when editing or searching data. So I decided to use only one file. It reduces complexity and increases maintenability. The use of meta widgets and filter tags help a lot to achieve this goal too. This way, one file only is necessary.
Metawidgets
They are only a group of binding, template and filter tags.
For example, you maybe use often Date-Time widgets, that displays date and time. To avoid redundancy, you can define the following metawidget :
<?xml version="1.0" encoding="ISO-8859-1"?>
<!-- BX CPT -->
<fd:form
xmlns:fd="http://apache.org/cocoon/forms/1.0#definition"
xmlns:fb="http://apache.org/cocoon/forms/1.0#binding"
xmlns:i18n="http://apache.org/cocoon/i18n/2.1"
xmlns:filter="http://bluexml.org/filter/1.0"
xmlns:meta="http://bluexml.org/meta/1.0">
<meta:widget id="Time">
<meta:group label="Début" styling-style="width: 300px;" styling-layout="rowWithLabel">
<fb:value id="dtstart" path="dtstart">
<fd:convertor datatype="date" type="formatting"/>
</fb:value>
<meta:group label="à" styling-layout="row">
<fb:value id="tstart_hh" path="tstart_hh"/>h
<fb:value id="tstart_mm" path="tstart_mm"/>m
<fb:value id="tstart_ss" path="tstart_ss"/>s
</meta:group>
</meta:group>
<meta:group label="Fin" styling-style="width: 300px;" styling-layout="rowWithLabel">
<fb:value id="dtend" path="dtend">
<fd:convertor datatype="date" type="formatting"/>
</fb:value>
<meta:group label="à" styling-layout="row">
<fb:value id="tend_hh" path="tend_hh"/>h
<fb:value id="tend_mm" path="tend_mm"/>m
<fb:value id="tend_ss" path="tend_ss"/>s
</meta:group>
</meta:group>
</meta:widget>
</fd:form>To include this set of definition (31 lines), you just have to put the following line in your metabind file :
<meta:new id="DateTime"/>
Metabind file
In the example below, a calendar doctype is defined. Usual bindings for Date-Time (among others) are reused through the Date-Time metawidget.
<fb:context
xmlns:fd="http://apache.org/cocoon/forms/1.0#definition"
xmlns:fb="http://apache.org/cocoon/forms/1.0#binding"
xmlns:fi="http://apache.org/cocoon/forms/1.0#instance"
xmlns:ft="http://apache.org/cocoon/forms/1.0#template"
xmlns:filter="http://bluexml.org/filter/1.0"
xmlns:meta="http://bluexml.org/meta/1.0"
path="document">
<fb:context path="meta">
<meta:group
styling-type="categories"
label="General"
state="internal-tab-state">
<meta:group styling-layout="columns" label="General">
<meta:new id="General"/>
<meta:new id="DateTime"/>
</meta:group>
<meta:group styling-type="fieldset" styling-layout="columns" label="Compl.">
<fb:value id="visibility" path="visibility"/>
<fb:value id="location" path="location"/>
<fb:value id="link" path="link"/>
</meta:group>
<meta:new id="Alarm"/>
<meta:new id="Classification"/>
<meta:new id="NewVersion"/>
<meta:new id="System"/>
</meta:group>
</fb:context>
<meta:new id="Versions"/>
<ft:widget id="action.ok"/>
</fb:context>
Form binding
From this metabind file, we create the following form binding (by just changing the last parameter) :
From this same metabind file, we may have different form binding for user's role (but it is not configured here).
Form template
From this metabind file, we create this form template (create, update, search).
create form template - "real app's screenshot" (notice the rich text editor FCKEditor, very good project)
update - "real app's screenshot" (the form is initialized with data from the database)
search - "real app's screenshot" (the description field became a plain input field instead of a textarea, useless in search mode)
Code
Here is the global idea. All the code is part of the BlueXML project, located in http://www.bluexml.org. Just download the code in the download section.
You may have a look through the cvs browser at :
"cud directory feature" - for sitemap,
"cud directory configuration" - for common configuration files (other directories are for specific data for each customer)
Conclusion
Here is the global idea. Maybe we could implement this at the Forms framework core ?