Author: hlship
Date: Tue Jun  3 12:25:36 2008
New Revision: 662863

URL: http://svn.apache.org/viewvc?rev=662863&view=rev
Log:
TAPESTRY-2380: Add AjaxFormLoop component

Modified:
    
tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/corelib/components/AjaxFormLoop.java
    
tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/corelib/components/FormFragment.java
    
tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/corelib/components/FormInjector.java
    
tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/PageRenderQueue.java
    
tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/PageRenderQueueImpl.java
    
tapestry/tapestry5/trunk/tapestry-core/src/main/resources/org/apache/tapestry5/corelib/components/AjaxFormLoop.tml
    
tapestry/tapestry5/trunk/tapestry-core/src/main/resources/org/apache/tapestry5/tapestry.js

Modified: 
tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/corelib/components/AjaxFormLoop.java
URL: 
http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/corelib/components/AjaxFormLoop.java?rev=662863&r1=662862&r2=662863&view=diff
==============================================================================
--- 
tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/corelib/components/AjaxFormLoop.java
 (original)
+++ 
tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/corelib/components/AjaxFormLoop.java
 Tue Jun  3 12:25:36 2008
@@ -384,7 +384,9 @@
             );
 
 
-        pageRenderQueue.addPartialFilter(new PartialMarkupRendererFilter()
+        renderingInjector = true;
+
+        pageRenderQueue.addPartialMarkupRendererFilter(new 
PartialMarkupRendererFilter()
         {
             public void renderMarkup(MarkupWriter writer, JSONObject reply, 
PartialMarkupRenderer renderer)
             {

Modified: 
tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/corelib/components/FormFragment.java
URL: 
http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/corelib/components/FormFragment.java?rev=662863&r1=662862&r2=662863&view=diff
==============================================================================
--- 
tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/corelib/components/FormFragment.java
 (original)
+++ 
tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/corelib/components/FormFragment.java
 Tue Jun  3 12:25:36 2008
@@ -21,24 +21,27 @@
 import org.apache.tapestry5.corelib.internal.ComponentActionSink;
 import org.apache.tapestry5.corelib.internal.FormSupportAdapter;
 import org.apache.tapestry5.corelib.internal.HiddenFieldPositioner;
-import org.apache.tapestry5.corelib.internal.WrappedComponentAction;
 import org.apache.tapestry5.dom.Element;
 import org.apache.tapestry5.internal.services.ClientBehaviorSupport;
 import org.apache.tapestry5.ioc.annotations.Inject;
 import org.apache.tapestry5.services.*;
 import org.slf4j.Logger;
 
-import java.util.List;
-
 /**
- * A SubForm is a portion of a Form that may be selectively displayed.  Form 
elements inside a FormFragment will
+ * A FormFragment is a portion of a Form that may be selectively displayed.  
Form elements inside a FormFragment will
  * automatically bypass validation when the fragment is invisible.  The trick 
is to also bypass server-side form
- * processing for such fields when the form is submitted; the fragment uses a 
hidden field to track its client-side
- * visibility and will bypass field component submission logic for the 
components it encloses.
+ * processing for such fields when the form is submitted; client-side logic 
"removes" the [EMAIL PROTECTED]
+ * org.apache.tapestry5.corelib.components.Form#FORM_DATA form data} for the 
fragment if it is invisible when the form
+ * is submitted; alternately, client-side logic can simply remove the form 
fragment element (including its visible and
+ * hidden fields) to prevent server-side processing.
+ * <p/>
  * <p/>
- * In addition, should the client-side element for a Form fragment be removed 
before the enclosing form is submitted,
- * then none of the fields inside the fragment will be processed (this can be 
considered an extension of the "if not
- * visible, don't process" option above).
+ * The client-side element has a new property, formFragment, added to it.  The 
formFragment object has new methods to
+ * control the client-side behavior of the fragment: <dl> <dt>hide()</dt> 
<dd>Hides the element, using the configured
+ * client-side animation effect.</dd> <dt>hideAndRemove()</dt> <dd>As with 
hide(), but the element is removed from the
+ * DOM after being hidden.</dd> <dt>show()</dt> <dd>Makes the element visible, 
using the configured client-side
+ * animation effect.</dd> <dt>toggle()</dt> <dd>Invokes hide() or show() as 
necessary.</dd> <dt>setVisible()</dt>
+ * <dd>Passed a boolean parameter, invokes hide() or show() as necessary.</dd> 
</dl>
  *
  * @see org.apache.tapestry5.corelib.mixins.TriggerFragment
  */
@@ -74,7 +77,6 @@
     @Parameter(defaultPrefix = BindingConstants.LITERAL)
     private String element;
 
-
     @Inject
     private Environment environment;
 
@@ -110,22 +112,6 @@
         return resources.getElementName("div");
     }
 
-    private void handleSubmission(String elementName, 
List<WrappedComponentAction> actions)
-    {
-        String value = request.getParameter(elementName);
-
-        boolean visible = Boolean.parseBoolean(value);
-
-        if (!visible) return;
-
-        // Note that we DON'T update the visible parameter, it is read only.
-
-        for (WrappedComponentAction action : actions)
-        {
-            action.execute(componentSource);
-        }
-    }
-
     /**
      * Renders a &lt;div&gt; tag and provides an override of the [EMAIL 
PROTECTED] org.apache.tapestry5.services.FormSupport}
      * environmental.
@@ -147,7 +133,6 @@
         if (!visible)
             element.addClassName(CSSClassConstants.INVISIBLE);
 
-
         clientBehaviorSupport.addFormFragment(clientId, show, hide);
 
         componentActions = new ComponentActionSink(logger);
@@ -189,7 +174,6 @@
      */
     void afterRender(MarkupWriter writer)
     {
-
         hiddenFieldPositioner.getElement().attributes(
                 "type", "hidden",
 

Modified: 
tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/corelib/components/FormInjector.java
URL: 
http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/corelib/components/FormInjector.java?rev=662863&r1=662862&r2=662863&view=diff
==============================================================================
--- 
tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/corelib/components/FormInjector.java
 (original)
+++ 
tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/corelib/components/FormInjector.java
 Tue Jun  3 12:25:36 2008
@@ -28,8 +28,8 @@
 import org.apache.tapestry5.internal.services.PageRenderQueue;
 import org.apache.tapestry5.ioc.annotations.Inject;
 import org.apache.tapestry5.ioc.internal.util.IdAllocator;
+import org.apache.tapestry5.json.JSONObject;
 import org.apache.tapestry5.runtime.RenderCommand;
-import org.apache.tapestry5.runtime.RenderQueue;
 import org.apache.tapestry5.services.*;
 import org.slf4j.Logger;
 
@@ -40,6 +40,10 @@
  * A way to add new content to an existing Form. The FormInjector emulates its 
tag from the template (or uses a
  * &lt;div&gt;). When triggered, new content is obtained from the application 
and is injected before or after the
  * element.
+ * <p/>
+ * On the client side, a new function, trigger(), is added to the element. 
Invoking this client-side function will
+ * trigger the FormInjector; a request is sent to the server, new content is 
generated, and the new content is placed
+ * before or after (per configuration) the existing FormInjector element.
  */
 @SupportsInformalParameters
 public class FormInjector implements ClientElement
@@ -165,14 +169,14 @@
      * event notification is what will ultimately render (typically, its a 
Block).  However, we do a <em>lot</em> of
      * tricks to provide the desired FormSupport around the what renders.
      */
-    Object onInject(EventContext context) throws IOException
+    void onInject(EventContext context) throws IOException
     {
         ComponentResultProcessorWrapper callback = new 
ComponentResultProcessorWrapper(
                 componentEventResultProcessor);
 
         resources.triggerContextEvent(EventConstants.ACTION, context, 
callback);
 
-        if (!callback.isAborted()) return null;
+        if (!callback.isAborted()) return;
 
         // Here's where it gets very, very tricky.
 
@@ -182,28 +186,9 @@
 
         final ComponentActionSink actionSink = new ComponentActionSink(logger);
 
-        final RenderCommand cleanup = new RenderCommand()
+        PartialMarkupRendererFilter filter = new PartialMarkupRendererFilter()
         {
-            public void render(MarkupWriter writer, RenderQueue queue)
-            {
-                environment.pop(ValidationTracker.class);
-
-                FormSupportImpl formSupport = (FormSupportImpl) 
environment.pop(FormSupport.class);
-
-                formSupport.executeDeferred();
-
-                hiddenFieldPositioner.getElement().attributes(
-                        "type", "hidden",
-
-                        "name", Form.FORM_DATA,
-
-                        "value", actionSink.toBase64());
-            }
-        };
-
-        final RenderCommand setup = new RenderCommand()
-        {
-            public void render(final MarkupWriter writer, RenderQueue queue)
+            public void renderMarkup(MarkupWriter writer, JSONObject reply, 
PartialMarkupRenderer renderer)
             {
                 hiddenFieldPositioner = new HiddenFieldPositioner(writer, 
rules);
 
@@ -216,20 +201,32 @@
 
                 IdAllocator idAllocator = new IdAllocator(":" + uid);
 
+                clientId = renderSupport.allocateClientId(resources);
+
+                reply.put("elementId", clientId);
+
                 FormSupportImpl formSupport = new FormSupportImpl(formId, 
actionSink, clientBehaviorSupport, true,
                                                                   idAllocator);
 
                 environment.push(FormSupport.class, formSupport);
                 environment.push(ValidationTracker.class, new 
ValidationTrackerImpl());
 
-                // Queue up the root render command to execute first, and the 
cleanup
-                // to execute after it is done.
+                renderer.renderMarkup(writer, reply);
+
+                formSupport.executeDeferred();
+
+                environment.pop(ValidationTracker.class);
+                environment.pop(FormSupport.class);
+
+                hiddenFieldPositioner.getElement().attributes(
+                        "type", "hidden",
 
-                queue.push(cleanup);
-                queue.push(rootRenderCommand);
+                        "name", Form.FORM_DATA,
+
+                        "value", actionSink.toBase64());
             }
         };
 
-        return setup;
+        pageRenderQueue.addPartialMarkupRendererFilter(filter);
     }
 }

Modified: 
tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/PageRenderQueue.java
URL: 
http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/PageRenderQueue.java?rev=662863&r1=662862&r2=662863&view=diff
==============================================================================
--- 
tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/PageRenderQueue.java
 (original)
+++ 
tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/PageRenderQueue.java
 Tue Jun  3 12:25:36 2008
@@ -86,5 +86,5 @@
      *
      * @param filter to add to the pipeline
      */
-    void addPartialFilter(PartialMarkupRendererFilter filter);
+    void addPartialMarkupRendererFilter(PartialMarkupRendererFilter filter);
 }

Modified: 
tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/PageRenderQueueImpl.java
URL: 
http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/PageRenderQueueImpl.java?rev=662863&r1=662862&r2=662863&view=diff
==============================================================================
--- 
tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/PageRenderQueueImpl.java
 (original)
+++ 
tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/PageRenderQueueImpl.java
 Tue Jun  3 12:25:36 2008
@@ -108,7 +108,7 @@
         queue.run(writer);
     }
 
-    public void addPartialFilter(PartialMarkupRendererFilter filter)
+    public void addPartialMarkupRendererFilter(PartialMarkupRendererFilter 
filter)
     {
         Defense.notNull(filter, "filter");
 

Modified: 
tapestry/tapestry5/trunk/tapestry-core/src/main/resources/org/apache/tapestry5/corelib/components/AjaxFormLoop.tml
URL: 
http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/main/resources/org/apache/tapestry5/corelib/components/AjaxFormLoop.tml?rev=662863&r1=662862&r2=662863&view=diff
==============================================================================
--- 
tapestry/tapestry5/trunk/tapestry-core/src/main/resources/org/apache/tapestry5/corelib/components/AjaxFormLoop.tml
 (original)
+++ 
tapestry/tapestry5/trunk/tapestry-core/src/main/resources/org/apache/tapestry5/corelib/components/AjaxFormLoop.tml
 Tue Jun  3 12:25:36 2008
@@ -1,9 +1,7 @@
 <t:container xmlns:t="http://tapestry.apache.org/schema/tapestry_5_0_0.xsd";>
 
     <t:formfragment t:id="fragment" element="prop:element" visible="true">
-        <t:delegate to="beforeBody"/>
-        <t:body/>
-        <t:delegate to="afterBody"/>
+        <t:delegate to="block:ajaxResponse"/>
     </t:formfragment>
 
     <t:block id="tail">

Modified: 
tapestry/tapestry5/trunk/tapestry-core/src/main/resources/org/apache/tapestry5/tapestry.js
URL: 
http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/main/resources/org/apache/tapestry5/tapestry.js?rev=662863&r1=662862&r2=662863&view=diff
==============================================================================
--- 
tapestry/tapestry5/trunk/tapestry-core/src/main/resources/org/apache/tapestry5/tapestry.js
 (original)
+++ 
tapestry/tapestry5/trunk/tapestry-core/src/main/resources/org/apache/tapestry5/tapestry.js
 Tue Jun  3 12:25:36 2008
@@ -14,9 +14,16 @@
 
 var Tapestry = {
 
-    FORM_VALIDATE_EVENT : "form:validate",
+    /** Event, triggered on a Form element, to spur fields within the form to 
validate thier input. */
+    FORM_VALIDATE_EVENT : "tapestry:formvalidate",
 
-    FORM_PREPARE_FOR_SUBMIT_EVENT : "form:prepareforsubmit",
+    /** Event, triggered on a Form element, to allow callbacks to perpare for 
a form submission (this
+     * occurs after validation).
+     */
+    FORM_PREPARE_FOR_SUBMIT_EVENT : "tapestry:formprepareforsubmit",
+
+    /** Event, triggered on the document object, which identifies the current 
focus element. */
+    FOCUS_CHANGE_EVENT : "tapestry:focuschange",
 
     DEBUG_ENABLED : false,
 
@@ -37,10 +44,6 @@
 
     ErrorPopup : Class.create(),
 
-    // An array of Tapestry.ErrorPopup instances that have been created for 
fields within the page.
-
-    errorPopups : [],
-
     // Adds a callback function that will be invoked when the DOM is loaded 
(which
     // occurs *before* window.onload, which has to wait for images and such to 
load
     // first.  This simply observes the dom:loaded event on the document 
object (support for
@@ -76,12 +79,7 @@
             {
                 element.observe("focus", function()
                 {
-                    Tapestry.focusedElement = element;
-
-                    $(Tapestry.errorPopups).each(function(popup)
-                    {
-                        popup.handleFocusChange(element);
-                    });
+                    document.fire(Tapestry.FOCUS_CHANGE_EVENT, element);
                 });
 
                 element.isObservingFocusChange = true;
@@ -242,6 +240,27 @@
         }
 
         return true;
+    },
+
+    /**
+     * Default function for handling Ajax-related failures.
+     */
+    ajaxFailureHandler : function()
+    {
+        Tapestry.error("Communication with the server failed.");
+    },
+
+    /**
+     * Processes a typical Ajax request for a URL invoking the provided 
handler on success.
+     * On failure, error() is invoked to inform the user.
+     *
+     * @param url of Ajax request
+     * @param successHandler to invoke on success
+     * @return the Ajax.Request object
+     */
+    ajaxRequest : function(url, successHandler)
+    {
+        return new Ajax.Request(url, { onSuccess: successHandler, onFailure: 
Tapestry.ajaxFailureHandler })
     }
 };
 
@@ -283,17 +302,18 @@
                 {
                     var effect = Tapestry.ElementEffect.fade(container);
 
-                    effect.afterFinish = function()
+                    effect.options.afterFinish = function()
                     {
                         container.remove();
                     };
                 }
             }
 
-            new Ajax.Request(spec.url, { onSuccess : successHandler });
+            Tapestry.ajaxRequest(spec.url, successHandler);
         });
     },
 
+
     /**
      * Convert a form or link into a trigger of an Ajax update that
      * updates the indicated Zone.
@@ -343,7 +363,7 @@
 
         var handler = function(event)
         {
-            new Ajax.Request(element.href, { onSuccess : successHandler });
+            Tapestry.ajaxRequest(element.href, successHandler);
 
             return false;
         };
@@ -516,7 +536,6 @@
     {
         this.form = $(form);
         this.result = true;
-        this.firstError = true;
     },
 
     // Invoked by a validator function (which is passed the event) to record 
an error
@@ -527,11 +546,8 @@
 
     recordError : function(message)
     {
-        if (this.firstError)
-        {
-            this.field.activate();
-            this.firstError = false;
-        }
+        if (this.focusField == undefined)
+            this.focusField = this.field;
 
         this.field.decorateForValidationError(message);
 
@@ -544,7 +560,7 @@
 
     BUBBLE_VERT_OFFSET : -34,
 
-    BUBBLE_HORIZONTAL_OFFSET : -5,
+    BUBBLE_HORIZONTAL_OFFSET : -20,
 
     BUBBLE_WIDTH: "auto",
 
@@ -574,17 +590,31 @@
             Event.stop(event);  // Should be domevent.stop(), but that fails 
under IE
         }.bindAsEventListener(this));
 
-        Tapestry.errorPopups.push(this);
-
-        this.state = "hidden";
-
         this.queue = { position: 'end', scope: this.field.id };
 
         Event.observe(window, "resize", this.repositionBubble.bind(this));
+
+        document.observe(Tapestry.FOCUS_CHANGE_EVENT, function(event)
+        {
+            // Tapestry.debug("Focus change: #{memo} for #{field}", { memo: 
event.memo.id, field: this.field.id });
+
+            var focused = event.memo;
+
+            if (focused == this.field)
+            {
+                this.fadeIn();
+            }
+            else
+            {
+                this.fadeOut();
+            }
+        }.bind(this));
     },
 
     showMessage : function(message)
     {
+        // Tapestry.debug("Show message: #{message} for #{field}", { message: 
message, field: this.field.id });
+
         this.stopAnimation();
 
         this.innerSpan.update(message);
@@ -607,14 +637,19 @@
 
     fadeIn : function()
     {
+        // Tapestry.debug("fadeIn: " + this.field.id);
+
+        if (! this.hasMessage) return;
+
         this.repositionBubble();
 
-        if (this.state == "hidden")
-        {
-            this.state = "visible";
+        if (this.status == "fadeIn") return;
 
-            this.animation = new Effect.Appear(this.outerDiv, { afterFinish : 
this.afterFadeIn.bind(this), queue: this.queue });
-        }
+        if (this.outerDiv.visible()) return;
+
+        this.animation = new Effect.Appear(this.outerDiv, { queue: this.queue 
});
+
+        this.status = "fadeIn";
     },
 
     stopAnimation : function()
@@ -622,18 +657,18 @@
         if (this.animation) this.animation.cancel();
 
         this.animation = null;
+        this.status = null;
     },
 
     fadeOut : function ()
     {
-        this.stopAnimation();
+        // Tapestry.debug("fadeOut: " + this.field.id);
 
-        if (this.state == "visible")
-        {
-            this.state = "hidden";
+        if (this.status == "fadeOut") return;
 
-            this.animation = new Effect.Fade(this.outerDiv, { queue : 
this.queue });
-        }
+        this.animation = new Effect.Fade(this.outerDiv, { queue : this.queue 
});
+
+        this.status = "fadeOut";
     },
 
     hide : function()
@@ -643,34 +678,6 @@
         this.stopAnimation();
 
         this.outerDiv.hide();
-
-        this.state = "hidden";
-    },
-
-    afterFadeIn : function()
-    {
-        this.animation = null;
-
-        if (this.field != Tapestry.focusedElement) this.fadeOut();
-    },
-
-    handleFocusChange : function(element)
-    {
-        if (element == this.field)
-        {
-            if (this.hasMessage) this.fadeIn();
-            return;
-        }
-
-        if (this.animation == null)
-        {
-            this.fadeOut();
-            return;
-        }
-
-        // Must be fading in, let it finish, then fade it back out.
-
-        this.animation = new Effect.Fade(this.outerDiv, { queue : this.queue 
});
     }
 };
 
@@ -707,9 +714,14 @@
 
         this.form.fire(Tapestry.FORM_VALIDATE_EVENT, event);
 
-
         if (! event.result)
         {
+            // Calling focus() does not trigger this event, so we do it 
manually.
+            // Defer it long enough for the animations to start.
+
+            event.focusField.activate();
+            // document.fire(Tapestry.FOCUS_CHANGE_EVENT, event.focusField);
+
             Event.stop(domevent); // Should be domevent.stop(), but that fails 
under IE
         }
         else
@@ -739,8 +751,8 @@
         {
             var event = new Tapestry.FormEvent(this.field.form);
 
-     // This prevents the field from taking focus if there is an error.
-            event.firstError = false;
+            // This prevents the field from taking focus if there is an error.
+            event.focusField = this.field;
 
             event.field = this.field;
 
@@ -951,10 +963,10 @@
     {
         var effect = this.hideFunc(this.element);
 
-        effect.afterFinish = function()
+        effect.options.afterFinish = function()
         {
             this.element.remove();
-        };
+        }.bind(this);
     },
 
     show : function()
@@ -1017,6 +1029,8 @@
 
                 newElement.update(reply.content);
 
+                newElement.id = reply.elementId;
+
                 // Handle any scripting issues.
 
                 Tapestry.processScriptInReply(reply);
@@ -1027,7 +1041,7 @@
 
             }.bind(this);
 
-            new Ajax.Request(this.url, { onSuccess : successHandler });
+            Tapestry.ajaxRequest(this.url, successHandler);
 
             return false;
         }.bind(this);


Reply via email to