Author: sylvain
Date: Mon Oct  4 02:26:14 2004
New Revision: 51874

Added:
   
cocoon/trunk/src/blocks/forms/java/org/apache/cocoon/forms/generation/JXMacrosHelper.java
   (contents, props changed)
   
cocoon/trunk/src/blocks/forms/java/org/apache/cocoon/forms/generation/jx-macros.xml
   (contents, props changed)
Modified:
   
cocoon/trunk/src/blocks/forms/java/org/apache/cocoon/forms/formmodel/AbstractWidget.java
   cocoon/trunk/src/blocks/forms/samples/flow/forms_flow_example.js
   cocoon/trunk/src/blocks/forms/samples/forms/form1_template.xml
   cocoon/trunk/src/blocks/forms/samples/sitemap.xmap
Log:
Implementation of the CForms template language as JX macros

Modified: 
cocoon/trunk/src/blocks/forms/java/org/apache/cocoon/forms/formmodel/AbstractWidget.java
==============================================================================
--- 
cocoon/trunk/src/blocks/forms/java/org/apache/cocoon/forms/formmodel/AbstractWidget.java
    (original)
+++ 
cocoon/trunk/src/blocks/forms/java/org/apache/cocoon/forms/formmodel/AbstractWidget.java
    Mon Oct  4 02:26:14 2004
@@ -171,8 +171,8 @@
             relativeWidget = getForm();
             relativePath = path.substring(1);
         } else {
-            if (path.startsWith(".." + Widget.PATH_SEPARATOR))  {
-                relativeWidget = getParent();
+               if (path.startsWith(".." + Widget.PATH_SEPARATOR))  {
+                       relativeWidget = getParent();
                 relativePath = path.substring(3);
             } else {
                 String childId = path.substring(0, sepPosition );
@@ -452,5 +452,14 @@
         if (this.attributes != null) {
             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/trunk/src/blocks/forms/java/org/apache/cocoon/forms/generation/JXMacrosHelper.java
==============================================================================
--- (empty file)
+++ 
cocoon/trunk/src/blocks/forms/java/org/apache/cocoon/forms/generation/JXMacrosHelper.java
   Mon Oct  4 02:26:14 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 [EMAIL PROTECTED] 
#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/trunk/src/blocks/forms/java/org/apache/cocoon/forms/generation/jx-macros.xml
==============================================================================
--- (empty file)
+++ 
cocoon/trunk/src/blocks/forms/java/org/apache/cocoon/forms/generation/jx-macros.xml
 Mon Oct  4 02:26:14 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/trunk/src/blocks/forms/samples/flow/forms_flow_example.js
==============================================================================
--- cocoon/trunk/src/blocks/forms/samples/flow/forms_flow_example.js    
(original)
+++ cocoon/trunk/src/blocks/forms/samples/flow/forms_flow_example.js    Mon Oct 
 4 02:26:14 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/trunk/src/blocks/forms/samples/forms/form1_template.xml
==============================================================================
--- cocoon/trunk/src/blocks/forms/samples/forms/form1_template.xml      
(original)
+++ cocoon/trunk/src/blocks/forms/samples/forms/form1_template.xml      Mon Oct 
 4 02:26:14 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">
@@ -96,24 +103,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/trunk/src/blocks/forms/samples/sitemap.xmap
==============================================================================
--- cocoon/trunk/src/blocks/forms/samples/sitemap.xmap  (original)
+++ cocoon/trunk/src/blocks/forms/samples/sitemap.xmap  Mon Oct  4 02:26:14 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">

Reply via email to