After (nearly) finishing a project where we implemented our own little forms frameworks (... we had already started when Woody became part of Cocoon) I want to share some ideas with you.
To be able to make a comparisons I dived into the current implementation of Woody. Now I can say that I'm really impressed by the architecture and I also like it very much *how* you did it.
So here a few (random) thoughts what could be useful for Woody:
Global widget definition repository -----------------------------------
Currently we have a strict binding between widget definition and the template where the widgets are instantiated:
1 : 1 +------------+ +------------+ | | | | | widget- | | widget- | | defintion | -----> | instances | | | | | | | | | +------------+ +------------+
This goes so far that all defined widgets must be instantiated in order to get no (hidden) errors during validation. This also means for applications that consist of more than one form you sooner or later have to define your widgets (e.g. an email address) as often as you instantiate them.
I think you misunderstand the exact role of the WoodyTransformer and the moment where instances are created.
If we make an analogy between forms and objects, we can consider a form definition as a class and a form instance as an object, instance of the forementioned class.
This instance is created in the controller part of the request processing (flowscript or action) and is made available to the view pipeline through a request attribute.
What the WoodyTransforme does is just asking to the widget instances referenced by the <wt:widget> elements to output their representation. It *does not* instanciate the widgets.
I think we should decouple this behaviour into following:
+------------+ | | | global |
| widget |\
| repository | \
| | \
+------------+ \use
| \
| ext \
V \
+------------+ \ +------------+
| | | |
| widget- | use | widget- |
| defintion | ------- | instances |
| | | |
| | | |
+------------+ +------------+
The global widget repository contains widgets that can be reused in all forms within your Cocoon installation. I also think that it could be possible to extend global widget defintions:
<wd:field id="glEmail" required="true"> <wd:datatype base="string"> <wd:validation> <wd:email/> </wd:validation> </wd:datatype> </wd:field>
<wd:field id="supplierEmail" extends="glEmail"> <wd:label>Supplier's email address:</wd:label> </wd:field>
<wd:field id="customerEmail" extends="glEmail"> <wd:label>Customer's email address:</wd:label> </wd:field>
I proposed to introduce a datatype dictionary to factorize common type definition (along with their validators and selection-list). This would turn your example into something like:
<wd:datatype id="email" base="string"> <wd:validation> <wd:email/> </wd:validation> </wd:datatype>
And then: <wd:widget id="supplierEmail" required="true"> <wd:datatype base="email"/> </wd:widget>
Multi page forms ----------------
We have already discussed this several times on the dev-list and at the GetTogether in Gent. IIUC Thorsten, Bruno and I agreed that we don't like some kind of a "phases" concept because everything you need can be found at the template and why should you declare things twice. If you think of dynamic forms (e.g. wheter a widget is show depends on the rights of the user) you also come into troubles because your phases will have to become dynamic too!
As mentioned the list of the required widgets is already known because the template transformer instantiates all widgets. But there is a problem (or at least I'm not sure how to solve it) with this approach: The template transformer is the last step before a new continuation is created.
Nope. The continuation is created in the controller, before the view processing starts.
So it would be possible to save a list of all required widgets. That's fine. The problem is that this list has to be connected to a certain continuation because otherwise you would get serious troubles if the user clones the window. OTOH if we save the list of the required widgets with the continuation it can become expensive from a memory POV, can't it?
Are there other thougts how to solve the multi page forms problem? Sylvain, you mentioned at the end of your presentation something at the
GT Hackaton but I can't remember. Could you help us (me)?
What I mentioned was the need for a generic flowscript wizard library, that could allow to define "backtrack points" in the list of ancestors of the current continuation.
The idea is to have a Wizard class that provides two methods:
- markStep(): mark a step in the wizard. A step is a location in the flowscript where we can go back. We may eventually want to name step to allow going back to directly to a step that's not the immediate previous one.
- handleBack(): if the "wizard-back" button was clicked, restore the previous continuation in the step stack.
Example usage (notice that the second page depends on a value input on the first one):
function myWizard() {
var wizard = new Wizard();
// register the current execution location as a backtrack point
wizard.markStep();
cocoon.sendPageAndWait("first-page.html");
var value = cocoon.request.getParameter("value");
wizard.markStep();
if (value == "foo") {
cocoon.sendPageAndWait("foo-page.html");
wizard.handleBack();
} else {
cocoon.sendPageAndWait("bar-page.html");
wizard.handleBack();
}
wizard.markStep();
cocoon.sendPageAndWait("third-page.html");
wizard.handleBack();
cocoon.sendPage("finished.html");
}A real application would of course extract form data afer each page and use it before sending "finished.html".
Lookup values -------------
We already have some helpers to make it easy to enter correct data into your fields. One thing missing is the possibility to lookup values. Suppose you have a field "country". One possibility would be to a drop down field the other possibility a usual input field with an icon on its right side. If you click on this icon another window opens (inline like in Eclipse or a popup) and you get a list of all countries. You can select one and the value is copied into the main form. So what's the advantage compared to a selection list:
* You don't need to load such a long list like all countries in the world at form load time but then when the user wants to fill in the value (this makes no real difference for one field but if you have e.g. 10 fields containing so many possible values there is one, believe me!) * You can provide additional information: e.g. you have to select a customer - you could show the address, the birthdate, ... to make the choice for the user easier * You can structure your date e.g. you can create a tree where all countries can be found in a tree * You can provide help when a selection list is not good enough. Think of a search form e.g. you can search in your customers database for the right one and select it. The selected customer is filled in the form. * You can fill in more than one field with one selection (think of a row in a table --> with one selection the whole row could be completed)
I already did this on a previous project to select people in a corporate directory containing about 6000 entries. The user can type in a few letters, and then the popup automatically displays the names starting with these letters.
Mmmh... can this be a particular implementation and rendering of a selection list where the possible values are available through an externally visible pipeline?
Sylvain
-- Sylvain Wallez Anyware Technologies http://www.apache.org/~sylvain http://www.anyware-tech.com { XML, Java, Cocoon, OpenSource }*{ Training, Consulting, Projects } Orixo, the opensource XML business alliance - http://www.orixo.com
