You are viewing a plain text version of this content. The canonical link for it is here.
Posted to cvs@cocoon.apache.org by sy...@apache.org on 2004/10/04 11:25:34 UTC
svn commit: rev 51873 - in cocoon/branches/BRANCH_2_1_X: . src/blocks/forms/java/org/apache/cocoon/forms/formmodel src/blocks/forms/java/org/apache/cocoon/forms/generation src/blocks/forms/samples src/blocks/forms/samples/flow src/blocks/forms/samples/forms
Author: sylvain
Date: Mon Oct 4 02:25:32 2004
New Revision: 51873
Added:
cocoon/branches/BRANCH_2_1_X/src/blocks/forms/java/org/apache/cocoon/forms/generation/JXMacrosHelper.java (contents, props changed)
cocoon/branches/BRANCH_2_1_X/src/blocks/forms/java/org/apache/cocoon/forms/generation/jx-macros.xml (contents, props changed)
Modified:
cocoon/branches/BRANCH_2_1_X/src/blocks/forms/java/org/apache/cocoon/forms/formmodel/AbstractWidget.java
cocoon/branches/BRANCH_2_1_X/src/blocks/forms/samples/flow/forms_flow_example.js
cocoon/branches/BRANCH_2_1_X/src/blocks/forms/samples/forms/form1_template.xml
cocoon/branches/BRANCH_2_1_X/src/blocks/forms/samples/sitemap.xmap
cocoon/branches/BRANCH_2_1_X/status.xml
Log:
Implementation of the CForms template language as JX macros
Modified: cocoon/branches/BRANCH_2_1_X/src/blocks/forms/java/org/apache/cocoon/forms/formmodel/AbstractWidget.java
==============================================================================
--- cocoon/branches/BRANCH_2_1_X/src/blocks/forms/java/org/apache/cocoon/forms/formmodel/AbstractWidget.java (original)
+++ cocoon/branches/BRANCH_2_1_X/src/blocks/forms/java/org/apache/cocoon/forms/formmodel/AbstractWidget.java Mon Oct 4 02:25:32 2004
@@ -395,4 +395,13 @@
this.attributes.remove(name);
}
}
+
+ public String toString() {
+ String className = this.getClass().getName();
+ int last = className.lastIndexOf('.');
+ if (last != -1) {
+ className = className.substring(last+1);
+ }
+ return className + "@" + getRequestParameterName();
+ }
}
Added: cocoon/branches/BRANCH_2_1_X/src/blocks/forms/java/org/apache/cocoon/forms/generation/JXMacrosHelper.java
==============================================================================
--- (empty file)
+++ cocoon/branches/BRANCH_2_1_X/src/blocks/forms/java/org/apache/cocoon/forms/generation/JXMacrosHelper.java Mon Oct 4 02:25:32 2004
@@ -0,0 +1,224 @@
+package org.apache.cocoon.forms.generation;
+
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Locale;
+import java.util.Map;
+
+import org.apache.cocoon.forms.Constants;
+import org.apache.cocoon.forms.formmodel.ContainerWidget;
+import org.apache.cocoon.forms.formmodel.Form;
+import org.apache.cocoon.forms.formmodel.Repeater;
+import org.apache.cocoon.forms.formmodel.Widget;
+import org.apache.cocoon.forms.validation.ValidationError;
+import org.apache.cocoon.xml.AbstractXMLPipe;
+import org.apache.cocoon.xml.AttributesImpl;
+import org.apache.cocoon.xml.XMLConsumer;
+import org.apache.commons.collections.ArrayStack;
+import org.xml.sax.Attributes;
+import org.xml.sax.SAXException;
+
+/**
+ * Helper class for the implementation of the CForms template language with JXTemplate macros.
+ *
+ * @version CVS $Id$
+ */
+public class JXMacrosHelper {
+
+ private XMLConsumer cocoonConsumer;
+ private ArrayStack stack = new ArrayStack();
+
+ private Map classes = null; // lazily created
+
+ /**
+ * Builds and helper object, given the generator's consumer.
+ *
+ * @param consumer the generator's consumer
+ * @return a helper object
+ */
+ public static JXMacrosHelper createHelper(XMLConsumer consumer) {
+ return new JXMacrosHelper(consumer);
+ }
+
+ public JXMacrosHelper(XMLConsumer consumer) {
+ this.cocoonConsumer = consumer;
+ }
+
+ public void startForm(Map attributes) throws SAXException {
+ // build attributes
+ AttributesImpl attrs = new AttributesImpl();
+ Iterator iter = attributes.entrySet().iterator();
+ while(iter.hasNext()) {
+ Map.Entry entry = (Map.Entry)iter.next();
+ attrs.addCDATAAttribute((String)entry.getKey(), (String)entry.getValue());
+ }
+
+ this.cocoonConsumer.startPrefixMapping(Constants.INSTANCE_PREFIX, Constants.INSTANCE_NS);
+ this.cocoonConsumer.startElement(
+ Constants.INSTANCE_NS,
+ "form-template",
+ Constants.INSTANCE_PREFIX_COLON + "form-template",
+ attrs);
+ }
+
+ public void endForm() throws SAXException {
+ this.cocoonConsumer.endElement(
+ Constants.INSTANCE_NS,
+ "form-template",
+ Constants.INSTANCE_PREFIX_COLON + "form-template");
+ this.cocoonConsumer.endPrefixMapping(Constants.INSTANCE_PREFIX);
+ }
+
+ /**
+ * Flush the root element name that has been stored in {@link #generateSaxFragment(Widget, Locale)}
+ *
+ * @param obj the object that is terminated (widget or validation error)
+ * @throws SAXException
+ */
+ public void flushRoot(Object obj) throws SAXException {
+ Object stackObj = stack.pop();
+ if (stackObj != obj) {
+ throw new IllegalStateException("Flushing on wrong widget (expected " + stackObj +
+ ", got " + obj + ")");
+ }
+ ((RootBufferingPipe)stack.pop()).flushRoot();
+ }
+
+ /**
+ * Get a child widget of a given widget, throwing an exception if no such child exists.
+ *
+ * @param currentWidget
+ * @param id
+ * @return
+ */
+ public Widget getWidget(Widget currentWidget, String id) {
+ Widget result = null;
+
+ if (currentWidget instanceof ContainerWidget) {
+ result = ((ContainerWidget)currentWidget).getChild(id);
+ }
+
+ if (result != null) {
+ return result;
+ } else {
+ throw new IllegalArgumentException("Widget '" + currentWidget +
+ "' has no child named '" + id + "'");
+ }
+ }
+
+ public Repeater getRepeater(Widget currentWidget, String id) {
+ Widget child = getWidget(currentWidget, id);
+ if (child instanceof Repeater) {
+ return (Repeater)child;
+ } else {
+ throw new IllegalArgumentException("Widget '" + child + "' is not a repeater");
+ }
+ }
+
+ /**
+ * Generate a widget's SAX fragment, buffering the root element's <code>endElement()</code>
+ * event so that the template can insert styling information in it.
+ *
+ * @param widget
+ * @param locale
+ * @throws SAXException
+ */
+ public void generateWidget(Widget widget, Locale locale) throws SAXException {
+ // Needs to be buffered
+ RootBufferingPipe pipe = new RootBufferingPipe(this.cocoonConsumer);
+ this.stack.push(pipe);
+ this.stack.push(widget);
+ widget.generateSaxFragment(pipe, locale);
+ }
+
+ public void generateWidgetLabel(Widget widget, String id) throws SAXException {
+ getWidget(widget, id).generateLabel(this.cocoonConsumer);
+ }
+
+ public void generateRepeaterWidgetLabel(Widget widget, String id, String widgetId) throws SAXException {
+ getRepeater(widget, id).generateWidgetLabel(widgetId, this.cocoonConsumer);
+ }
+
+ public void generateRepeaterSize(Widget widget, String id) throws SAXException {
+ getRepeater(widget, id).generateSize(this.cocoonConsumer);
+ }
+
+ public void generateValidationError(ValidationError error) throws SAXException {
+ // Needs to be buffered
+ RootBufferingPipe pipe = new RootBufferingPipe(this.cocoonConsumer);
+ this.stack.push(pipe);
+ this.stack.push(error);
+ error.generateSaxFragment(pipe);
+ }
+
+ public void defineClassBody(Form form, String id, Object body) {
+ // TODO: check that class actually exists in the form
+ if (this.classes == null) {
+ this.classes = new HashMap();
+ }
+
+ // TODO: check if class doesn't already exist?
+ this.classes.put(id, body);
+ }
+
+ public Object getClassBody(String id) {
+ Object result = this.classes == null ? null : this.classes.get(id);
+
+ if (result == null) {
+ throw new IllegalArgumentException("No class '" + id + "' has been defined.");
+ } else {
+ return result;
+ }
+ }
+
+ public boolean isSelectedCase(Widget unionWidget, String caseValue) {
+ String value = (String)unionWidget.getValue();
+ return caseValue.equals(value != null ? value : "");
+ }
+
+ /**
+ * A SAX pipe that buffers the <code>endElement()</code> event of the root element.
+ * This is needed by the generator version of the Woody transformer (see woody-jxmacros.xml).
+ *
+ * @version CVS $Id$
+ */
+ private static class RootBufferingPipe extends AbstractXMLPipe {
+ private int depth = 0;
+
+ private String rootUri;
+ private String rootLoc;
+ private String rootRaw;
+
+ public RootBufferingPipe(XMLConsumer next) {
+ this.setConsumer(next);
+ }
+
+ public void startElement(String uri, String loc, String raw, Attributes a) throws SAXException
+ {
+ if (depth == 0) {
+ // Root element: keep its description
+ this.rootUri = uri;
+ this.rootLoc = loc;
+ this.rootRaw = raw;
+ }
+ depth++;
+ super.startElement(uri, loc, raw, a);
+ }
+
+ public void endElement(String uri, String loc, String raw) throws SAXException
+ {
+ depth--;
+ if (depth > 0) {
+ // Propagate all but root element
+ super.endElement(uri, loc, raw);
+ }
+ }
+
+ public void flushRoot() throws SAXException {
+ if (depth != 0) {
+ throw new IllegalStateException("Depth is not zero");
+ }
+ super.endElement(this.rootUri, this.rootLoc, this.rootRaw);
+ }
+ }
+}
Added: cocoon/branches/BRANCH_2_1_X/src/blocks/forms/java/org/apache/cocoon/forms/generation/jx-macros.xml
==============================================================================
--- (empty file)
+++ cocoon/branches/BRANCH_2_1_X/src/blocks/forms/java/org/apache/cocoon/forms/generation/jx-macros.xml Mon Oct 4 02:25:32 2004
@@ -0,0 +1,152 @@
+<!-- An implementation of the CForms template engine as a JXTemplate tag library -->
+
+<jx:template xmlns:jx="http://apache.org/cocoon/templates/jx/1.0"
+ xmlns:fi="http://apache.org/cocoon/forms/1.0#instance">
+ <!--
+ ft:form-template
+ -->
+ <jx:macro name="form-template" targetNamespace="http://apache.org/cocoon/forms/1.0#template">
+ <jx:set var="cformsHelper" value="${Packages.org.apache.cocoon.forms.generation.JXMacrosHelper.createHelper(cocoon.consumer)}"/>
+ <jx:set var="cformsDummy" value="${cformsHelper.startForm(macro.arguments)}"/>
+ <jx:set var="form" value="${CocoonFormsInstance}"/>
+ <!-- the form is also the current widget -->
+ <jx:set var="widget" value="${form}"/>
+ <jx:evalBody/>
+ <jx:set var="cformsDummy" value="${cformsHelper.endForm()}"/>
+ </jx:macro>
+
+ <!--
+ ft:widget
+ -->
+ <jx:macro name="widget" targetNamespace="http://apache.org/cocoon/forms/1.0#template">
+ <jx:parameter name="id"/>
+
+ <jx:set var="widget" value="${cformsHelper.getWidget(widget, id)}"/>
+ <jx:set var="cformsDummy" value="${cformsHelper.generateWidget(widget, locale)}"/>
+ <jx:evalBody/>
+ <jx:set var="cformsDummy" value="${cformsHelper.flushRoot(widget)}"/>
+ </jx:macro>
+
+ <!--
+ ft:repeater-widget-label
+ -->
+ <jx:macro name="repeater-widget-label" targetNamespace="http://apache.org/cocoon/forms/1.0#template">
+ <jx:parameter name="id"/>
+ <jx:parameter name="widget-id"/>
+
+ <jx:set var="cformsDummy" value="${cformsHelper.generateRepeaterWidgetLabel(widget, id, this['widget-id'])}"/>
+ </jx:macro>
+
+ <!--
+ ft:widget-label
+ -->
+ <jx:macro name="widget-label" targetNamespace="http://apache.org/cocoon/forms/1.0#template">
+ <jx:parameter name="id"/>
+
+ <jx:set var="cformsDummy" value="${cformsHelper.generateWidgetLabel(widget, id)}"/>
+ </jx:macro>
+
+ <!--
+ ft:repeater-size
+ -->
+ <jx:macro name="repeater-size" targetNamespace="http://apache.org/cocoon/forms/1.0#template">
+ <jx:parameter name="id"/>
+
+ <jx:set var="cformsDummy" value="${cformsHelper.generateRepeaterSize(widget, id)}"/>
+ </jx:macro>
+
+ <!--
+ ft:repeater-widget
+ -->
+ <jx:macro name="repeater-widget" targetNamespace="http://apache.org/cocoon/forms/1.0#template">
+ <jx:parameter name="id"/>
+
+ <jx:set var="repeater" value="${cformsHelper.getRepeater(widget, id)}"/>
+ <jx:forEach varStatus="repeaterLoop" begin="0" end="${repeater.getSize() - 1}">
+ <jx:set var="widget" value="${repeater.getRow(repeaterLoop.index)}"/>
+ <jx:evalBody/>
+ </jx:forEach>
+ </jx:macro>
+
+ <!--
+ ft:continuation-id
+ -->
+ <jx:macro name="continuation-id" targetNamespace="http://apache.org/cocoon/forms/1.0#template">
+ <fi:continuation-id>${cocoon.continuation.id}</fi:continuation-id>
+ </jx:macro>
+
+ <!--
+ ft:class
+ -->
+ <jx:macro name="class" targetNamespace="http://apache.org/cocoon/forms/1.0#template">
+ <jx:parameter name="id"/>
+
+ <jx:set var="cformsDummy" value="${cformsHelper.defineClassBody(form, id, macro.body)}"/>
+ </jx:macro>
+
+ <!--
+ ft:new
+ -->
+ <jx:macro name="new" targetNamespace="http://apache.org/cocoon/forms/1.0#template">
+ <jx:parameter name="id"/>
+
+ <jx:eval select="${cformsHelper.getClassBody(id)}"/>
+ </jx:macro>
+
+ <!--
+ ft:struct : just increase the nesting level
+ -->
+ <jx:macro name="struct" targetNamespace="http://apache.org/cocoon/forms/1.0#template">
+ <jx:parameter name="id"/>
+
+ <jx:set var="widget" value="${cformsHelper.getWidget(widget, id)}"/>
+ <fi:struct id="${widget.getRequestParameterName()}">
+ <jx:evalBody/>
+ </fi:struct>
+ </jx:macro>
+
+ <!--
+ ft:union
+ -->
+ <jx:macro name="union" targetNamespace="http://apache.org/cocoon/forms/1.0#template">
+ <jx:parameter name="id"/>
+
+ <jx:set var="widget" value="${cformsHelper.getWidget(widget, id)}"/>
+ <fi:union id="${widget.getRequestParameterName()}">
+ <jx:evalBody/>
+ </fi:union>
+ </jx:macro>
+
+ <!--
+ ft:case
+ -->
+ <jx:macro name="case" targetNamespace="http://apache.org/cocoon/forms/1.0#template">
+ <jx:parameter name="id"/>
+
+ <jx:if test="${cformsHelper.isSelectedCase(widget, id)}">
+ <jx:set widget="${cformsHelper.getWidget(widget, id)}"/>
+ <jx:evalBody/>
+ </jx:if>
+ </jx:macro>
+
+ <!--
+ ft:validation-error
+ -->
+ <jx:macro name="validation-error" targetNamespace="http://apache.org/cocoon/forms/1.0#template">
+ <jx:parameter name="id"/>
+
+ <jx:set var="widget" value="${cformsHelper.getWidget(widget, id)}"/>
+ <jx:set var="validationError" value="${widget.getValidationError()}"/>
+ <jx:if test="${validationError != null}">
+ <jx:set var="cformsDummy" value="${cformsHelper.generateValidationError(validationError)}"/>
+ <jx:evalBody/>
+ <jx:set var="cformsDummy" value="${cformsHelper.flushRoot(validationError)}"/>
+ </jx:if>
+ </jx:macro>
+
+
+ <!--
+ ft:aggregate-widget : TODO
+ -->
+
+</jx:template>
Modified: cocoon/branches/BRANCH_2_1_X/src/blocks/forms/samples/flow/forms_flow_example.js
==============================================================================
--- cocoon/branches/BRANCH_2_1_X/src/blocks/forms/samples/flow/forms_flow_example.js (original)
+++ cocoon/branches/BRANCH_2_1_X/src/blocks/forms/samples/flow/forms_flow_example.js Mon Oct 4 02:25:32 2004
@@ -32,7 +32,7 @@
model.drinks = ["Jupiler", "Coca Cola"];
form.locale = locale;
- form.showForm("form1-display-pipeline");
+ form.showForm("form1-display-pipeline.jx");
print("submitId = " + form.submitId);
if (form.isValid) {
print("visa=" + model.visa);
Modified: cocoon/branches/BRANCH_2_1_X/src/blocks/forms/samples/forms/form1_template.xml
==============================================================================
--- cocoon/branches/BRANCH_2_1_X/src/blocks/forms/samples/forms/form1_template.xml (original)
+++ cocoon/branches/BRANCH_2_1_X/src/blocks/forms/samples/forms/form1_template.xml Mon Oct 4 02:25:32 2004
@@ -14,9 +14,16 @@
See the License for the specific language governing permissions and
limitations under the License.
-->
-<!-- The only difference between this file and the form1_template_action.xml
- is the value of the action attribute on the ft:form-template element -->
-<page xmlns:ft="http://apache.org/cocoon/forms/1.0#template" xmlns:fi="http://apache.org/cocoon/forms/1.0#instance">
+<!-- This file is similar to form1_template_action.xml. They differ in group layout,
+ form's action attribute (to use continuations) and, this one is a dynamic form
+ template that changes the 'contacts' repeater layout depending on its size -->
+<page 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">
+
+ <!-- Import the macros that define CForms template elements -->
+ <jx:import uri="resource://org/apache/cocoon/forms/generation/jx-macros.xml"/>
+
<title>Sample form</title>
<content>
<ft:form-template action="#{$continuation/id}.continue" method="POST">
@@ -86,6 +93,7 @@
<!-- manual layout of fields, without use of a fi:group -->
<ft:widget-label id="contacts"/><br/>
+
<ft:repeater-size id="contacts"/>
<table border="1">
<tr>
@@ -96,24 +104,34 @@
<th><ft:repeater-widget-label id="contacts" widget-id="birthdate"/></th>
<th><ft:repeater-widget-label id="contacts" widget-id="select"/></th>
</tr>
- <!-- The contents of the repeater-widget element is a template that will
- be applied to each row in the repeater. -->
- <ft:repeater-widget id="contacts">
- <tr>
- <td><ft:widget id="firstname"/></td>
- <td><ft:widget id="lastname"/></td>
- <td><ft:widget id="phone"/></td>
- <td><ft:widget id="email"/></td>
- <td>
- <ft:widget id="birthdate"/>
- </td>
- <td><ft:widget id="select"/></td>
- </tr>
- </ft:repeater-widget>
+ <jx:choose>
+ <jx:when test="${widget.getChild('contacts').getSize() == 0}">
+ <tr><td colspan="6" align="center"><em>There are no contacts to display</em></td></tr>
+ </jx:when>
+ <jx:otherwise>
+ <!-- The contents of the repeater-widget element is a template that will
+ be applied to each row in the repeater. -->
+ <ft:repeater-widget id="contacts">
+ <tr>
+ <td><ft:widget id="firstname"/></td>
+ <td><ft:widget id="lastname"/></td>
+ <td><ft:widget id="phone"/></td>
+ <td><ft:widget id="email"/></td>
+ <td><ft:widget id="birthdate"/></td>
+ <td><ft:widget id="select"/></td>
+ </tr>
+ </ft:repeater-widget>
+ </jx:otherwise>
+ </jx:choose>
<tr>
- <td colspan="4" align="right">
+ <td colspan="6">
<ft:widget id="addcontact"/>
- <ft:widget id="removecontacts"/>
+ <jx:if test="${widget.getChild('contacts').getSize() > 0}">
+ <ft:widget id="removecontacts"/>
+ <br/>
+ <small>Hint: remove all contacts to see how dynamic form templates can change
+ their layout depending on widget values</small>
+ </jx:if>
</td>
</tr>
</table>
Modified: cocoon/branches/BRANCH_2_1_X/src/blocks/forms/samples/sitemap.xmap
==============================================================================
--- cocoon/branches/BRANCH_2_1_X/src/blocks/forms/samples/sitemap.xmap (original)
+++ cocoon/branches/BRANCH_2_1_X/src/blocks/forms/samples/sitemap.xmap Mon Oct 4 02:25:32 2004
@@ -153,8 +153,10 @@
<map:call continuation="{1}"/>
</map:match>
+ <!--
+ | Show a form, using the forms transformer
+ -->
<map:match pattern="*-display-pipeline">
- <!-- pipeline to show the form -->
<map:generate src="forms/{1}_template.xml"/>
<map:transform type="forms"/>
<map:transform type="i18n">
@@ -167,6 +169,24 @@
<map:serialize/>
</map:match>
+ <!--
+ | Show a form, using the jx template macros
+ -->
+ <map:match pattern="*-display-pipeline.jx">
+ <map:generate type="jx" src="forms/{1}_template.xml"/>
+ <map:transform type="i18n">
+ <map:parameter name="locale" value="en-US"/>
+ </map:transform>
+ <map:call resource="simple-page2html">
+ <map:parameter name="file" value="forms/{1}_template.xml"/>
+ </map:call>
+ <map:transform src="resources/forms-samples-styling.xsl"/>
+ <map:serialize/>
+ </map:match>
+
+ <!--
+ | Display a success page using XSP
+ -->
<map:match pattern="*-success-pipeline.xsp">
<map:generate type="serverpages" src="forms/{1}_success.xsp"/>
<map:call resource="simple-page2html">
Modified: cocoon/branches/BRANCH_2_1_X/status.xml
==============================================================================
--- cocoon/branches/BRANCH_2_1_X/status.xml (original)
+++ cocoon/branches/BRANCH_2_1_X/status.xml Mon Oct 4 02:25:32 2004
@@ -205,6 +205,10 @@
<changes>
<release version="@version@" date="@date@">
+ <action dev="SW" type="add">
+ CForms block: Implementation of the CForm template language using JX macros,
+ allowing smart page layout that depends on the widgets state and value.
+ </action>
<action dev="AG" type="remove">
Remove jisp from distribution.
</action>