On Thu, 2004-11-04 at 23:42 +0100, Daniel Fagerstrom wrote:
> >>I prefer the request processor idea to the current form population where
> >>each widget reads it data from the request object. The current scheme
> >>makes CForms unecesarily bound to the request parameter model of input
> >>data. With a request processor that is reponsible to write input data
> >>into the form model, it would be easy to plug in a different request
> >>processor if one gets xml input from a browser that implements XForms, e.g.
> >
> > I think it would be quite easy to make this change to CForm.
>
> So do I, although I havn't studied the details in CForms for a while. I
> guess it can be done in a completely back compatible way. A request
> processor, a convertor/renderer and a view adapter needs to be written.
> Then one just don't use the readFromRequest and generateSaxfragment any
> more. There might be subtilities in the state sequence within widgets
> that complicates thins though.
I tried to implement a RequestProcessor for CForms just to see if it was
doable. It doesn't yet work quite the way you envision with converters
and renderers as I wanted the change to be as minimal as possible.
Instead it uses some of the existing widget methods for population. I've
attached two patches if you want to try it out (I'm a bit of a novice
with subversion and creating patches so they might not work). As far as
I can tell the changes are backwards compatible (the other samples seems
to work anyway).
Instead of the original RequestProcessor there is instead a
WidgetPopulator. It works similar in that it is added as an attribute to
the request in Form.js. Then during rendering, each widget that is
rendered will try to register itself on this WidgetPopulator (this is
done in jx-macros.xml). Currently only widgets of type Action,
BooleanField, Field, MultiValueField, RowAction, Submit and Upload will
be registered. I don't really know how to handle AggregateField and
haven't tested if it suffices to register it's child widgets only.
After the form has been submitted the list of registered widgets is sent
to Form.process(). Then instead of recursively populating all widgets
the form widget will iterate through the list of registered widgets and
call readFromRequest() on each.
Currently the WidgetPopulator is simply a holder of widgets that should
be populated and it is the Form that does the population. I did it this
way because the CForm code was a bit overwhelming and it was hard to get
a grip on what side effects would occur if population was lifted out of
the Form. Preferably population should be done in the WidgetPopulator.
// Jonas
Index: src/blocks/forms/java/org/apache/cocoon/forms/formmodel/Form.java
===================================================================
--- src/blocks/forms/java/org/apache/cocoon/forms/formmodel/Form.java (revision 56658)
+++ src/blocks/forms/java/org/apache/cocoon/forms/formmodel/Form.java (working copy)
@@ -15,6 +15,8 @@
*/
package org.apache.cocoon.forms.formmodel;
+import java.util.Iterator;
+import java.util.List;
import java.util.Locale;
import java.util.StringTokenizer;
@@ -168,6 +170,10 @@
this.listener = WidgetEventMulticaster.remove(this.listener, listener);
}
+ public boolean process(FormContext formContext) {
+ return process(formContext, null);
+ }
+
/**
* Processes a form submit. If the form is finished, i.e. the form should not be redisplayed to the user,
* then this method returns true, otherwise it returns false. To know if the form was sucessfully
@@ -184,7 +190,7 @@
* This processing can be interrupted by the widgets (or their event listeners) by calling
* [EMAIL PROTECTED] #endProcessing(boolean)}.
*/
- public boolean process(FormContext formContext) {
+ public boolean process(FormContext formContext, List registeredWidgets) {
// Fire the binding phase events
fireEvents();
@@ -221,7 +227,7 @@
// Start buffering events
this.bufferEvents = true;
- doReadFromRequest(formContext);
+ doReadFromRequest(formContext, registeredWidgets);
// Fire events, still buffering them: this ensures they will be handled in the same
// order as they were added.
@@ -264,9 +270,17 @@
throw new UnsupportedOperationException("Please use Form.process()");
}
- private void doReadFromRequest(FormContext formContext) {
- // let all individual widgets read their value from the request object
- super.readFromRequest(formContext);
+ private void doReadFromRequest(FormContext formContext, List registeredWidgets) {
+ if (registeredWidgets == null) {
+ // let all individual widgets read their value from the request object
+ super.readFromRequest(formContext);
+ } else {
+ // Provided with a list of widgets that should be populated
+ // we instead choose to populate them individually.
+ Iterator it = registeredWidgets.iterator();
+ while (it.hasNext())
+ ((Widget) it.next()).readFromRequest(formContext);
+ }
}
/**
Index: src/blocks/forms/java/org/apache/cocoon/forms/generation/jx-macros.xml
===================================================================
--- src/blocks/forms/java/org/apache/cocoon/forms/generation/jx-macros.xml (revision 56658)
+++ src/blocks/forms/java/org/apache/cocoon/forms/generation/jx-macros.xml (working copy)
@@ -25,6 +25,7 @@
<jx:set var="widget" value="${cformsHelper.getWidget(widget, id)}"/>
<jx:if test="${cformsHelper.isVisible(widget)}">
+ <jx:set var="wpdummy" value="${Packages.org.apache.cocoon.forms.flow.WidgetPopulator.registerWidget(request, widget)}"/>
<jx:set var="cformsDummy" value="${cformsHelper.generateWidget(widget, locale)}"/>
<jx:evalBody/>
<jx:set var="cformsDummy" value="${cformsHelper.flushRoot(widget)}"/>
Index: src/blocks/forms/java/org/apache/cocoon/forms/flow/javascript/Form.js
===================================================================
--- src/blocks/forms/java/org/apache/cocoon/forms/flow/javascript/Form.js (revision 56658)
+++ src/blocks/forms/java/org/apache/cocoon/forms/flow/javascript/Form.js (working copy)
@@ -24,6 +24,8 @@
// Revisit this class, so it gives access to more than the value.
defineClass("org.apache.cocoon.forms.flow.javascript.ScriptableWidget");
+importClass(Packages.org.apache.cocoon.forms.flow.WidgetPopulator);
+
/**
* Create a form, given the URI of its definition file
*/
@@ -95,7 +97,7 @@
* @parameter bizdata some business data for the view (like in cocoon.sendPageAndWait()).
* The "{FormsPipelineConfig.CFORMSKEY}" and "locale" properties are added to this object.
*/
-Form.prototype.showForm = function(uri, bizData) {
+Form.prototype.showForm = function(uri, bizData, useWidgetPopulator) {
if (bizData == undefined) bizData = new Object();
bizData[Packages.org.apache.cocoon.forms.transformation.FormsPipelineConfig.CFORMSKEY] = this.form;
@@ -119,6 +121,9 @@
this.form.fireEvents();
do {
+ if (useWidgetPopulator) {
+ var widgetPopulator = WidgetPopulator.createInstance(cocoon.request);
+ }
var k = cocoon.sendPageAndWait(uri, bizData);
if (result == null) result = k;
@@ -130,7 +135,12 @@
var objectModel = org.apache.cocoon.components.ContextHelper.getObjectModel(this.avalonContext);
org.apache.cocoon.components.flow.FlowHelper.setContextObject(objectModel, bizData);
- finished = this.form.process(formContext);
+ if (useWidgetPopulator) {
+ finished = this.form.process(formContext, widgetPopulator.getRegisteredWidgets());
+ } else {
+ finished = this.form.process(formContext);
+ }
+
if (finished) {
this.isValid = this.form.isValid();
}
Index: src/blocks/forms/java/org/apache/cocoon/forms/flow/WidgetPopulator.java
===================================================================
--- src/blocks/forms/java/org/apache/cocoon/forms/flow/WidgetPopulator.java (revision 0)
+++ src/blocks/forms/java/org/apache/cocoon/forms/flow/WidgetPopulator.java (revision 0)
@@ -0,0 +1,61 @@
+package org.apache.cocoon.forms.flow;
+
+import java.util.ArrayList;
+import org.apache.cocoon.environment.Request;
+import org.apache.cocoon.forms.formmodel.Action;
+import org.apache.cocoon.forms.formmodel.BooleanField;
+import org.apache.cocoon.forms.formmodel.Field;
+import org.apache.cocoon.forms.formmodel.MultiValueField;
+import org.apache.cocoon.forms.formmodel.RowAction;
+import org.apache.cocoon.forms.formmodel.Submit;
+import org.apache.cocoon.forms.formmodel.Upload;
+import org.apache.cocoon.forms.formmodel.Widget;
+
+public class WidgetPopulator {
+
+ ArrayList registeredWidgets = new ArrayList();
+
+
+ /**
+ * Creates a WidgetPopulator and adds it to the reqeust attribute named
+ * "org.apache.cocoon.forms.flow.WidgetPopulator"
+ */
+ public static WidgetPopulator createInstance(Request request) {
+ WidgetPopulator wp = new WidgetPopulator();
+ request.setAttribute(WidgetPopulator.class.getName(), wp);
+ return wp;
+ }
+
+ /**
+ * Convienience function. It will register the widget if a WidgetPopulator
+ * is present. No action taken otherwise.
+ */
+ public static void registerWidget(Request request, Widget widget) {
+ WidgetPopulator wp = (WidgetPopulator) request.getAttribute(WidgetPopulator.class.getName());
+
+ if (wp != null)
+ wp.registerWidget(widget);
+ }
+
+ /**
+ * Adds the widget if it is capable of populating itself from
+ * the request
+ */
+ public void registerWidget(Widget widget) {
+ // TODO: investigate if AggregateField should be included here
+ if (widget instanceof Action ||
+ widget instanceof BooleanField ||
+ widget instanceof Field ||
+ widget instanceof MultiValueField ||
+ widget instanceof RowAction ||
+ widget instanceof Submit ||
+ widget instanceof Upload) {
+
+ registeredWidgets.add(widget);
+ }
+ }
+
+ public ArrayList getRegisteredWidgets() {
+ return registeredWidgets;
+ }
+}
Index: src/blocks/forms/samples/widgetpopulator/calculator-view.jx
===================================================================
--- src/blocks/forms/samples/widgetpopulator/calculator-view.jx (revision 0)
+++ src/blocks/forms/samples/widgetpopulator/calculator-view.jx (revision 0)
@@ -0,0 +1,42 @@
+<?xml version="1.0"?>
+
+<html xmlns:ft="http://apache.org/cocoon/forms/1.0#template"
+ xmlns:fi="http://apache.org/cocoon/forms/1.0#instance"
+ xmlns:jx="http://apache.org/cocoon/templates/jx/1.0">
+
+ <jx:import uri="resource://org/apache/cocoon/forms/generation/jx-macros.xml"/>
+
+ <body>
+
+ <h1>Calculator wizard</h1>
+
+ <ft:form-template action="#{$continuation/id}.kont" method="POST">
+
+ <jx:if test="${page == 'page1'}">
+
+ <p>Term1: <ft:widget id="page1/term1"/></p>
+ <ft:widget id="page1/next"/>
+
+ </jx:if>
+
+ <jx:if test="${page == 'page2'}">
+
+ <p>Term2: <ft:widget id="page2/term2"/></p>
+ <ft:widget id="page2/previous"/>
+ <ft:widget id="page2/next"/>
+
+ </jx:if>
+
+ <jx:if test="${page == 'page3'}">
+
+ <p>Sum: <ft:widget id="page3/sum"/></p>
+ <ft:widget id="page3/previous"/>
+
+ </jx:if>
+
+ <p>
+ <ft:widget id="back"/>
+ </p>
+ </ft:form-template>
+ </body>
+</html>
Index: src/blocks/forms/samples/widgetpopulator/calculator-viewmodel.xml
===================================================================
--- src/blocks/forms/samples/widgetpopulator/calculator-viewmodel.xml (revision 0)
+++ src/blocks/forms/samples/widgetpopulator/calculator-viewmodel.xml (revision 0)
@@ -0,0 +1,89 @@
+<?xml version="1.0"?>
+
+<fd:form xmlns:fd="http://apache.org/cocoon/forms/1.0#definition">
+
+ <fd:widgets>
+
+ <fd:struct id="page1">
+ <fd:widgets>
+
+ <fd:field id="term1" required="true">
+ <fd:datatype base="integer"/>
+ </fd:field>
+
+
+ <fd:action id="next" action-command="asdf">
+ <fd:label>Next</fd:label>
+ <fd:on-action>
+ <javascript>
+ var calculatorPage1 = event.getSourceWidget().lookupWidget("/page1");
+
+ cocoon.log.error("!!! nextAction");
+
+ if (calculatorPage1.validate()) {
+ viewData["page"] = "page2";
+ }
+ </javascript>
+ </fd:on-action>
+ </fd:action>
+ </fd:widgets>
+ </fd:struct>
+
+ <fd:struct id="page2">
+ <fd:widgets>
+
+ <fd:field id="term2" required="true">
+ <fd:datatype base="integer"/>
+ </fd:field>
+
+ <fd:action id="previous" action-command="asdf">
+ <fd:label>Previous</fd:label>
+ <fd:on-action>
+ <javascript>
+ viewData["page"] = "page1";
+ </javascript>
+ </fd:on-action>
+ </fd:action>
+
+ <fd:action id="next" action-command="asdf">
+ <fd:label>Next</fd:label>
+ <fd:on-action>
+ <javascript>
+ var calculatorPage2 = event.getSourceWidget().lookupWidget("/page2");
+
+ if (calculatorPage2.validate()) {
+ var term1 = event.getSourceWidget().lookupWidget("/page1/term1").getValue();
+ var term2 = event.getSourceWidget().lookupWidget("/page2/term2").getValue();
+ var sumWidget = event.getSourceWidget().lookupWidget("/page3/sum");
+ sumWidget.setValue(new java.lang.Integer(term1.intValue() + term2.intValue()));
+ viewData["page"] = "page3";
+ }
+ </javascript>
+ </fd:on-action>
+ </fd:action>
+ </fd:widgets>
+ </fd:struct>
+
+ <fd:struct id="page3">
+ <fd:widgets>
+
+ <fd:output id="sum">
+ <fd:datatype base="integer"/>
+ </fd:output>
+
+ <fd:action id="previous" action-command="asdf">
+ <fd:label>Previous</fd:label>
+ <fd:on-action>
+ <javascript>
+ viewData["page"] = "page2";
+ </javascript>
+ </fd:on-action>
+ </fd:action>
+ </fd:widgets>
+ </fd:struct>
+
+ <fd:submit id="back" validate="false" action-command="asdf">
+ <fd:label>Back to main menu</fd:label>
+ </fd:submit>
+ </fd:widgets>
+</fd:form>
Index: src/blocks/forms/samples/widgetpopulator/flow.js
===================================================================
--- src/blocks/forms/samples/widgetpopulator/flow.js (revision 0)
+++ src/blocks/forms/samples/widgetpopulator/flow.js (revision 0)
@@ -0,0 +1,9 @@
+cocoon.load("resource://org/apache/cocoon/forms/flow/javascript/Form.js");
+
+function public_calculator() {
+
+ var form = new Form("calculator-viewmodel.xml");
+ form.showForm("calculator-view.jx", {page: "page1"}, true);
+ cocoon.redirectTo("main.html");
+
+}
Index: src/blocks/forms/samples/widgetpopulator/sitemap.xmap
===================================================================
--- src/blocks/forms/samples/widgetpopulator/sitemap.xmap (revision 0)
+++ src/blocks/forms/samples/widgetpopulator/sitemap.xmap (revision 0)
@@ -0,0 +1,40 @@
+<?xml version="1.0"?>
+
+<map:sitemap xmlns:map="http://apache.org/cocoon/sitemap/1.0">
+
+ <map:flow language="javascript">
+ <map:script src="flow.js"/>
+ </map:flow>
+
+ <map:pipelines>
+ <map:pipeline>
+ <map:match pattern="">
+ <map:redirect-to uri="main.html"/>
+ </map:match>
+
+ <map:match pattern="*.func">
+ <map:call function="public_{1}"/>
+ </map:match>
+
+ <map:match pattern="*.kont">
+ <map:call continuation="{1}"/>
+ </map:match>
+
+ <map:match pattern="*.jx">
+ <map:generate type="jx" src="{1}.jx"/>
+
+ <map:transform src="../resources/forms-samples-styling.xsl">
+ <map:parameter name="resources-uri" value="../resources"/>
+ </map:transform>
+
+ <map:serialize type="html"/>
+ </map:match>
+
+ <map:match pattern="*.html">
+ <map:generate src="{1}.html"/>
+ <map:serialize type="html"/>
+ </map:match>
+ </map:pipeline>
+ </map:pipelines>
+
+</map:sitemap>
Index: src/blocks/forms/samples/widgetpopulator/main.html
===================================================================
--- src/blocks/forms/samples/widgetpopulator/main.html (revision 0)
+++ src/blocks/forms/samples/widgetpopulator/main.html (revision 0)
@@ -0,0 +1,11 @@
+<?xml version="1.0"?>
+
+<html>
+
+ <body>
+ <h1>Widget populator samples</h1>
+ <p>
+ <a href="calculator.func">Calculator wizard sample</a><br />
+ </p>
+ </body>
+</html>