The Precept Stuff
-----------------
This is all still far from being ready for prime time but in order to share efforts I think
it makes sense to have it in scratchpad. I'll give a short indroduction about the idea, the
terms I am using, what is still missing, what should change and how I see its future.
Installation
------------
Add the roles and xconf snippet to you installation, set your precptor path (it's not yet
resolved) mount the sitemap and access the url "app/demo.html" relative to the sitemap mount.
Introduction
------------
Form processing is always about collecting data of a specific structure. When this data
selection process is finished the data will be used for some kind of purpose. Seeing this
from a more concrete XMLish point of view one could say we are building some kind of XML
instance which needs to conform to a schema description. That's exactly the major goal of
this approach.
The model
---------
At the start of the flow we create some kind of instance that can hold our values. This
instance will be held inside the users session until the end of form processing. In order to
specify the structure and validation of the form data we need some kind of schema. Since
"schema" is widely used as the short form for the w3.org XML Schema language I have chosen to
use a different term that means schemas in general (XML Schema, relax-ng, etc.): "preceptors".
In the first place the preceptor defines the structure of the instance but also holds
information about the value constraints. That's our model.
The view
--------
Especially for multipage forms (sometimes called "wizards") it is quite obvious that only parts
of an instance are supposed to be displayed.
[-----------------instance---------------]
[---view1---][----view2----][----view3---]
So we have different views of the instance. There are 2 different ways of achieving this.
(see webapp example1 and example2 for more details)
The controller
--------------
The controller needs to populate request parameters into the instance and depending on the
validation result select the correct following view. At the beginning I was quite optimistic
this could all happen automagically - now I know better. I experienced two problems that are
not to solve automagically:
1. checkboxes - Arrggh! This is the most stupid behavior I have ever seen... Anyway we have
to deal with it. The only really working solution is that the controller "knows" when a
checkbox value is supposed to be in the request. Otherwise you cannot turn off your
checkboxes because a turned off checkbox will not appear in the request.
2. partial validation - It's not true that you always want to validate a complete view.
Never thought this would be necessary until some people showed me a real world example.
The current approach is to bind methods to the form submit buttons. So you can define
explicitly what should happen (populated, validated, which view is supposed to be shown) on a
specific button. So you are free to do whatever you want on a button click...
This usually will happen inside an multiaction (maybe also within Ovidiu's schecoon?)
...
public Map introspection(Redirector redirector, Sou...
getLogger().debug("start of flow");
// start of flow create session
Session session = createSession(objectModel);
// create instance
Instance instance = createInstance("feedback");
// save instance into session
session.setAttribute("form-feedback",instance);
// select first view
return(page(VIEW1));
}
...
public Map doNext(Redirector redirector, Sou...
getLogger().debug("populating");
// populate a set of data into the instace
populate(objectModel, "form-feedback", SET_INSTALLATION );
// check if there are errors in those fields
List errors = validate(objectModel, "form-feedback", SET_INSTALLATION );
if(errors != null) {
// errors - go back to last view
getLogger().debug("some constraints FAILED");
return (page(VIEW1));
}
else {
// errors - go to next view
getLogger().debug("all constraints are ok");
return (page(VIEW2));
}
}
...
The Instance
------------
You can drop in any instance implementation that conforms to the following interface:
public interface Instance extends Component {
public void setValue(String xpath, Object value);
public void setValue(String xpath, Object value, Context context);
public Object getValue(String xpath);
public List validate(String xpath, Context context);
public List validate(Context context);
public void setPreceptor( Preceptor preceptor );
public Preceptor getPreceptor();
public void toSAX( ContentHandler handler, boolean withConstraints);
public long getLastModified();
}
Note: I removed the Exceptions for this README. Please use the Instance.java as reference.
Currently I have written a (very) simple dom implementation. (without namespace support or
any other features. More a simple tree. But should be quite fast)
And the first step towards a BeanInstance. (Validation is missing and needs some more
discussion)
...
somwhere.my.bean
...
The Preceptor
-------------
A preceptor is usually some kind of wrapper around or interface to one of the well known
validators. If you write a preceptor e.g. for XML Schema you can easily drop it in and
design your form within an XSD.
public interface Preceptor extends Component {
public List getConstraitsFor( String xpath );
public boolean isValidNode( String xpath );
public void buildInstance( Instance instance );
}
Note: I removed the Exceptions for this README. Please use the Instance.java as reference.
The mapping between the instance and the preceptor looks like this:
The Constraint
--------------
A constraint restricts valid values of nodes inside the instance for a given context. If we
don't want to write our own constraints (and this should be the goal) we need to wrap
existing ones into the Constraint Interface.
public interface Constraint {
public boolean isSatisfiedBy( Object value, Context context );
public String getId();
public String getType();
public void toSAX(ContentHandler handler) throws SAXException;
}
TODOs
-----
* make the easyrelax PreceptorBuilder use the parser component
* put the easyrelax ConstraintFactory under CM control
* use the real configuration values in the easyrelax constraints
* implement the getLastModified in the simple dom instance
* use the resolver to lookup the mapping for the bean instance
* implement the validation for the bean instance
* implement the getLastModified in the bean instance
* maybe pass the validation result as request attribute to the
instance transformer
* discuss the selectMany instance representation
* discuss schematron integration
* implement the following tags in the instance transformer