You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@tapestry.apache.org by hl...@apache.org on 2010/04/26 01:22:15 UTC

svn commit: r937906 - in /tapestry/tapestry5/trunk/tapestry-core/src: main/java/org/apache/tapestry5/corelib/components/ main/java/org/apache/tapestry5/corelib/internal/ main/java/org/apache/tapestry5/internal/services/ main/java/org/apache/tapestry5/i...

Author: hlship
Date: Sun Apr 25 23:22:14 2010
New Revision: 937906

URL: http://svn.apache.org/viewvc?rev=937906&view=rev
Log:
TAP5-1109: Updating multiple zones within a Form creates anomalous empty text fields

Added:
    tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/ajax/AjaxFormUpdateController.java   (with props)
    tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/ajax/AjaxFormUpdateControllerImpl.java   (with props)
    tapestry/tapestry5/trunk/tapestry-core/src/test/app1/MultiZoneUpdateInsideForm.tml
    tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry5/integration/app1/pages/MultiZoneUpdateInsideForm.java   (with props)
Modified:
    tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/corelib/components/Select.java
    tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/corelib/components/Zone.java
    tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/corelib/internal/ComponentActionSink.java
    tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/corelib/internal/HiddenFieldPositioner.java
    tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/InternalModule.java
    tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/RenderCommandComponentEventResultProcessor.java
    tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/ajax/AjaxFormUpdateFilter.java
    tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/structure/ComponentPageElementImpl.java
    tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/structure/StructureMessages.java
    tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/services/ajax/MultiZoneUpdateEventResultProcessor.java
    tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/services/ajax/SingleZonePartialRendererFilter.java
    tapestry/tapestry5/trunk/tapestry-core/src/main/resources/org/apache/tapestry5/internal/structure/StructureStrings.properties
    tapestry/tapestry5/trunk/tapestry-core/src/main/resources/org/apache/tapestry5/tapestry.js
    tapestry/tapestry5/trunk/tapestry-core/src/test/conf/testng-limited.xml
    tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry5/integration/app1/ZoneTests.java
    tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry5/integration/app1/pages/Index.java

Modified: tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/corelib/components/Select.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/corelib/components/Select.java?rev=937906&r1=937905&r2=937906&view=diff
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/corelib/components/Select.java (original)
+++ tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/corelib/components/Select.java Sun Apr 25 23:22:14 2010
@@ -15,27 +15,30 @@
 package org.apache.tapestry5.corelib.components;
 
 import org.apache.tapestry5.*;
-import org.apache.tapestry5.annotations.*;
+import org.apache.tapestry5.annotations.BeforeRenderTemplate;
+import org.apache.tapestry5.annotations.Environmental;
+import org.apache.tapestry5.annotations.Events;
+import org.apache.tapestry5.annotations.Mixin;
+import org.apache.tapestry5.annotations.Parameter;
+import org.apache.tapestry5.annotations.QueryParameter;
 import org.apache.tapestry5.corelib.base.AbstractField;
 import org.apache.tapestry5.corelib.data.BlankOption;
-import org.apache.tapestry5.corelib.internal.ComponentActionSink;
-import org.apache.tapestry5.corelib.internal.HiddenFieldPositioner;
 import org.apache.tapestry5.corelib.mixins.RenderDisabled;
 import org.apache.tapestry5.internal.TapestryInternalUtils;
-import org.apache.tapestry5.internal.services.PageRenderQueue;
-import org.apache.tapestry5.internal.util.Holder;
+import org.apache.tapestry5.internal.util.CaptureResultCallback;
 import org.apache.tapestry5.internal.util.SelectModelRenderer;
 import org.apache.tapestry5.ioc.Messages;
 import org.apache.tapestry5.ioc.annotations.Inject;
-import org.apache.tapestry5.ioc.internal.util.IdAllocator;
 import org.apache.tapestry5.ioc.internal.util.InternalUtils;
-import org.apache.tapestry5.json.JSONArray;
 import org.apache.tapestry5.json.JSONObject;
-import org.apache.tapestry5.services.*;
+import org.apache.tapestry5.services.ComponentDefaultProvider;
+import org.apache.tapestry5.services.FieldValidatorDefaultSource;
+import org.apache.tapestry5.services.FormSupport;
+import org.apache.tapestry5.services.Request;
+import org.apache.tapestry5.services.ValueEncoderFactory;
+import org.apache.tapestry5.services.ValueEncoderSource;
+import org.apache.tapestry5.services.javascript.JavascriptSupport;
 import org.apache.tapestry5.util.EnumSelectModel;
-import org.slf4j.Logger;
-
-import java.util.Locale;
 
 /**
  * Select an item from a list of values, using an [X]HTML <select> element on the client side. An validation
@@ -51,7 +54,6 @@ import java.util.Locale;
 { EventConstants.VALIDATE, EventConstants.VALUE_CHANGED + " when 'zone' parameter is bound" })
 public class Select extends AbstractField
 {
-    public static final String FORM_COMPONENTID_PARAMETER = "t:formcomponentid";
     public static final String CHANGE_EVENT = "change";
 
     private class Renderer extends SelectModelRenderer
@@ -81,9 +83,6 @@ public class Select extends AbstractFiel
     @Inject
     private ComponentDefaultProvider defaultProvider;
 
-    @Inject
-    private Locale locale;
-
     // Maybe this should default to property "<componentId>Model"?
     /**
      * The model used to identify the option groups and options to be presented to the user. This can be generated
@@ -120,7 +119,6 @@ public class Select extends AbstractFiel
      * Performs input validation on the value supplied by the user in the form submission.
      */
     @Parameter(defaultPrefix = BindingConstants.VALIDATE)
-    @SuppressWarnings("unchecked")
     private FieldValidator<Object> validate;
 
     /**
@@ -135,7 +133,7 @@ public class Select extends AbstractFiel
      * indicated zone. The component will trigger the event {@link EventConstants#VALUE_CHANGED} to inform its
      * container that Select's value has changed.
      * 
-     * @since 5.2.0.0
+     * @since 5.2.0
      */
     @Parameter(defaultPrefix = BindingConstants.LITERAL)
     private String zone;
@@ -147,25 +145,7 @@ public class Select extends AbstractFiel
     private FormSupport formSupport;
 
     @Inject
-    private Environment environment;
-
-    @Inject
-    private RenderSupport renderSupport;
-
-    @Inject
-    private HiddenFieldLocationRules rules;
-
-    @Inject
-    private Logger logger;
-
-    @Inject
-    private ClientDataEncoder clientDataEncoder;
-
-    @Inject
-    private ComponentSource componentSource;
-
-    @Inject
-    private PageRenderQueue pageRenderQueue;
+    private JavascriptSupport javascriptSupport;
 
     @SuppressWarnings("unused")
     @Mixin
@@ -226,83 +206,30 @@ public class Select extends AbstractFiel
 
         if (this.zone != null)
         {
-            final Link link = this.resources.createEventLink(CHANGE_EVENT);
+            Link link = resources.createEventLink(CHANGE_EVENT);
 
-            link.addParameter(FORM_COMPONENTID_PARAMETER, this.formSupport.getFormComponentId());
+            JSONObject spec = new JSONObject("selectId", getClientId(), "zoneId", zone, "url", link.toURI());
 
-            final JSONArray spec = new JSONArray();
-            spec.put("change");
-            spec.put(getClientId());
-            spec.put(this.zone);
-            spec.put(link.toAbsoluteURI());
-
-            this.renderSupport.addInit("updateZoneOnEvent", spec);
+            javascriptSupport.addInitializerCall("linkSelectToZone", spec);
         }
     }
 
-    Object onChange(
-
-    @QueryParameter(FORM_COMPONENTID_PARAMETER)
-    final String formId,
-
-    @QueryParameter(value = "t:selectvalue", allowBlank = true)
-    final String changedValue)
+    Object onChange(@QueryParameter(value = "t:selectvalue", allowBlank = true)
+    final String selectValue)
     {
-        final Object newValue = toValue(changedValue);
-
-        final Holder<Object> holder = Holder.create();
-
-        final ComponentEventCallback callback = new ComponentEventCallback()
-        {
-            public boolean handleResult(final Object result)
-            {
-
-                holder.put(result);
+        final Object newValue = toValue(selectValue);
 
-                Select.this.value = newValue;
-
-                return true;
-            }
-        };
+        CaptureResultCallback<Object> callback = new CaptureResultCallback<Object>();
 
         this.resources.triggerEvent(EventConstants.VALUE_CHANGED, new Object[]
         { newValue }, callback);
 
-        final PartialMarkupRendererFilter filter = new PartialMarkupRendererFilter()
-        {
-            public void renderMarkup(MarkupWriter writer, JSONObject reply, PartialMarkupRenderer renderer)
-            {
-
-                HiddenFieldPositioner hiddenFieldPositioner = new HiddenFieldPositioner(writer, Select.this.rules);
-
-                final ComponentActionSink actionSink = new ComponentActionSink(Select.this.logger,
-                        Select.this.clientDataEncoder);
-
-                final Form form = (Form) Select.this.componentSource.getComponent(formId);
-
-                FormSupport formSupport = form.createRenderTimeFormSupport(form.getClientId(), actionSink,
-                        new IdAllocator());
-
-                Select.this.environment.push(FormSupport.class, formSupport);
-                Select.this.environment.push(ValidationTracker.class, new ValidationTrackerImpl());
-
-                renderer.renderMarkup(writer, reply);
-
-                Select.this.environment.pop(ValidationTracker.class);
-                Select.this.environment.pop(FormSupport.class);
-
-                hiddenFieldPositioner.getElement().attributes("type", "hidden", "name", Form.FORM_DATA, "value",
-                        actionSink.getClientData());
-
-            }
-        };
-
-        this.pageRenderQueue.addPartialMarkupRendererFilter(filter);
+        this.value = newValue;
 
-        return holder.get();
+        return callback.getResult();
     }
 
-    protected Object toValue(final String submittedValue)
+    protected Object toValue(String submittedValue)
     {
         return InternalUtils.isBlank(submittedValue) ? null : this.encoder.toValue(submittedValue);
     }

Modified: tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/corelib/components/Zone.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/corelib/components/Zone.java?rev=937906&r1=937905&r2=937906&view=diff
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/corelib/components/Zone.java (original)
+++ tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/corelib/components/Zone.java Sun Apr 25 23:22:14 2010
@@ -18,6 +18,7 @@ import org.apache.tapestry5.BindingConst
 import org.apache.tapestry5.Block;
 import org.apache.tapestry5.CSSClassConstants;
 import org.apache.tapestry5.ClientElement;
+import org.apache.tapestry5.ComponentAction;
 import org.apache.tapestry5.ComponentResources;
 import org.apache.tapestry5.MarkupWriter;
 import org.apache.tapestry5.QueryParameterConstants;
@@ -25,11 +26,19 @@ import org.apache.tapestry5.annotations.
 import org.apache.tapestry5.annotations.Environmental;
 import org.apache.tapestry5.annotations.Parameter;
 import org.apache.tapestry5.annotations.SupportsInformalParameters;
+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.dom.Element;
 import org.apache.tapestry5.ioc.annotations.Inject;
 import org.apache.tapestry5.services.ClientBehaviorSupport;
+import org.apache.tapestry5.services.ClientDataEncoder;
+import org.apache.tapestry5.services.Environment;
+import org.apache.tapestry5.services.FormSupport;
 import org.apache.tapestry5.services.Heartbeat;
+import org.apache.tapestry5.services.HiddenFieldLocationRules;
 import org.apache.tapestry5.services.javascript.JavascriptSupport;
+import org.slf4j.Logger;
 
 /**
  * A Zone is portion of the output page designed for easy dynamic updating via Ajax or other client-side effects. A
@@ -108,6 +117,9 @@ public class Zone implements ClientEleme
     @Environmental
     private ClientBehaviorSupport clientBehaviorSupport;
 
+    @Inject
+    private Environment environment;
+
     /**
      * If true (the default) then the zone will render normally. If false, then the "t-invisible" CSS class is added,
      * which will make the zone initially invisible.
@@ -121,8 +133,23 @@ public class Zone implements ClientEleme
     @Inject
     private Heartbeat heartbeat;
 
+    @Inject
+    private Logger logger;
+
+    @Inject
+    private ClientDataEncoder clientDataEncoder;
+
+    @Inject
+    private HiddenFieldLocationRules rules;
+
     private String clientId;
 
+    private boolean insideForm;
+
+    private HiddenFieldPositioner hiddenFieldPositioner;
+
+    private ComponentActionSink actionSink;
+
     String defaultElementName()
     {
         return resources.getElementName("div");
@@ -143,6 +170,35 @@ public class Zone implements ClientEleme
 
         clientBehaviorSupport.addZone(clientId, show, update);
 
+        FormSupport existingFormSupport = environment.peek(FormSupport.class);
+
+        insideForm = existingFormSupport != null;
+
+        if (insideForm)
+        {
+            hiddenFieldPositioner = new HiddenFieldPositioner(writer, rules);
+
+            actionSink = new ComponentActionSink(logger, clientDataEncoder);
+
+            environment.push(FormSupport.class, new FormSupportAdapter(existingFormSupport)
+            {
+                @Override
+                public <T> void store(T component, ComponentAction<T> action)
+                {
+                    actionSink.store(component, action);
+                }
+
+                @Override
+                public <T> void storeAndExecute(T component, ComponentAction<T> action)
+                {
+                    store(component, action);
+
+                    action.execute(component);
+                }
+
+            });
+        }
+
         heartbeat.begin();
     }
 
@@ -150,6 +206,24 @@ public class Zone implements ClientEleme
     {
         heartbeat.end();
 
+        if (insideForm)
+        {
+            environment.pop(FormSupport.class);
+
+            if (actionSink.isEmpty())
+            {
+                hiddenFieldPositioner.discard();
+            }
+            else
+            {
+                hiddenFieldPositioner.getElement().attributes("type", "hidden",
+
+                "name", Form.FORM_DATA,
+
+                "value", actionSink.getClientData());
+            }
+        }
+
         writer.end(); // div
     }
 

Modified: tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/corelib/internal/ComponentActionSink.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/corelib/internal/ComponentActionSink.java?rev=937906&r1=937905&r2=937906&view=diff
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/corelib/internal/ComponentActionSink.java (original)
+++ tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/corelib/internal/ComponentActionSink.java Sun Apr 25 23:22:14 2010
@@ -4,7 +4,7 @@
 // you may not use this file except in compliance with the License.
 // You may obtain a copy of the License at
 //
-//     http://www.apache.org/licenses/LICENSE-2.0
+// http://www.apache.org/licenses/LICENSE-2.0
 //
 // Unless required by applicable law or agreed to in writing, software
 // distributed under the License is distributed on an "AS IS" BASIS,
@@ -36,6 +36,8 @@ public class ComponentActionSink
 
     private final ClientDataSink sink;
 
+    private boolean empty = true;
+
     public ComponentActionSink(Logger logger, ClientDataEncoder encoder)
     {
         this.logger = logger;
@@ -65,8 +67,15 @@ public class ComponentActionSink
         {
             throw new RuntimeException(InternalMessages.componentActionNotSerializable(completeId, ex), ex);
         }
+
+        empty = false;
     }
 
+    /** @since 5.2.0 */
+    public boolean isEmpty()
+    {
+        return empty;
+    }
 
     public String getClientData()
     {

Modified: tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/corelib/internal/HiddenFieldPositioner.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/corelib/internal/HiddenFieldPositioner.java?rev=937906&r1=937905&r2=937906&view=diff
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/corelib/internal/HiddenFieldPositioner.java (original)
+++ tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/corelib/internal/HiddenFieldPositioner.java Sun Apr 25 23:22:14 2010
@@ -76,7 +76,7 @@ public class HiddenFieldPositioner
      * 
      * @return the element
      * @throws IllegalStateException
-     *             if the element was not placed.
+     *             if the element was not positioned
      */
     public Element getElement()
     {
@@ -93,4 +93,21 @@ public class HiddenFieldPositioner
         return hiddenFieldElement;
     }
 
+    /**
+     * Discard this positioner (an alternative to invoking {@link #getElement()}).
+     * If an {@link Element} has been created for the hidden field, that
+     * element is removed.
+     * 
+     * @since 5.2.0
+     */
+    public void discard()
+    {
+        lock.lock();
+
+        if (hiddenFieldElement != null)
+            hiddenFieldElement.remove();
+
+        writer.removeListener(listener);
+    }
+
 }

Modified: tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/InternalModule.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/InternalModule.java?rev=937906&r1=937905&r2=937906&view=diff
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/InternalModule.java (original)
+++ tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/InternalModule.java Sun Apr 25 23:22:14 2010
@@ -18,6 +18,7 @@ import javax.servlet.http.Cookie;
 
 import org.apache.tapestry5.SymbolConstants;
 import org.apache.tapestry5.internal.pageload.PageLoaderImpl;
+import org.apache.tapestry5.internal.services.ajax.AjaxFormUpdateController;
 import org.apache.tapestry5.internal.services.javascript.JavascriptStackPathConstructor;
 import org.apache.tapestry5.internal.structure.ComponentPageElementResourcesSource;
 import org.apache.tapestry5.internal.structure.ComponentPageElementResourcesSourceImpl;
@@ -86,6 +87,7 @@ public class InternalModule
         binder.bind(ComponentModelSource.class);
         binder.bind(AssetResourceLocator.class);
         binder.bind(JavascriptStackPathConstructor.class);
+        binder.bind(AjaxFormUpdateController.class);
     }
 
     /**

Modified: tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/RenderCommandComponentEventResultProcessor.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/RenderCommandComponentEventResultProcessor.java?rev=937906&r1=937905&r2=937906&view=diff
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/RenderCommandComponentEventResultProcessor.java (original)
+++ tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/RenderCommandComponentEventResultProcessor.java Sun Apr 25 23:22:14 2010
@@ -1,10 +1,10 @@
-// Copyright 2007, 2008 The Apache Software Foundation
+// Copyright 2007, 2008, 2010 The Apache Software Foundation
 //
 // Licensed under the Apache License, Version 2.0 (the "License");
 // you may not use this file except in compliance with the License.
 // You may obtain a copy of the License at
 //
-//     http://www.apache.org/licenses/LICENSE-2.0
+// http://www.apache.org/licenses/LICENSE-2.0
 //
 // Unless required by applicable law or agreed to in writing, software
 // distributed under the License is distributed on an "AS IS" BASIS,
@@ -14,27 +14,61 @@
 
 package org.apache.tapestry5.internal.services;
 
+import org.apache.tapestry5.MarkupWriter;
+import org.apache.tapestry5.internal.services.ajax.AjaxFormUpdateController;
 import org.apache.tapestry5.runtime.RenderCommand;
+import org.apache.tapestry5.runtime.RenderQueue;
 import org.apache.tapestry5.services.ComponentEventResultProcessor;
 
 import java.io.IOException;
 
 /**
- * Processor for objects that implement {@link RenderCommand} (such as {@link org.apache.tapestry5.internal.structure.BlockImpl}).
- *
+ * Processor for objects that implement {@link RenderCommand} (such as
+ * {@link org.apache.tapestry5.internal.structure.BlockImpl}).
+ * 
  * @see AjaxPartialResponseRenderer#renderPartialPageMarkup()
  */
 public class RenderCommandComponentEventResultProcessor implements ComponentEventResultProcessor<RenderCommand>
 {
-    private PageRenderQueue pageRenderQueue;
+    private final PageRenderQueue pageRenderQueue;
 
-    public RenderCommandComponentEventResultProcessor(PageRenderQueue pageRenderQueue)
+    private final AjaxFormUpdateController ajaxFormUpdateController;
+
+    private final RenderCommand setup = new RenderCommand()
+    {
+        public void render(MarkupWriter writer, RenderQueue queue)
+        {
+            ajaxFormUpdateController.setupBeforePartialZoneRender(writer);
+        }
+    };
+
+    private final RenderCommand cleanup = new RenderCommand()
+    {
+        public void render(MarkupWriter writer, RenderQueue queue)
+        {
+            ajaxFormUpdateController.cleanupAfterPartialZoneRender();
+        }
+    };
+
+    public RenderCommandComponentEventResultProcessor(PageRenderQueue pageRenderQueue,
+            AjaxFormUpdateController ajaxFormUpdateController)
     {
         this.pageRenderQueue = pageRenderQueue;
+        this.ajaxFormUpdateController = ajaxFormUpdateController;
     }
 
-    public void processResultValue(RenderCommand value) throws IOException
+    public void processResultValue(final RenderCommand value) throws IOException
     {
-        pageRenderQueue.initializeForPartialPageRender(value);
+        RenderCommand wrapper = new RenderCommand()
+        {
+            public void render(MarkupWriter writer, RenderQueue queue)
+            {
+                queue.push(cleanup);
+                queue.push(value);
+                queue.push(setup);
+            }
+        };
+
+        pageRenderQueue.initializeForPartialPageRender(wrapper);
     }
 }

Added: tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/ajax/AjaxFormUpdateController.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/ajax/AjaxFormUpdateController.java?rev=937906&view=auto
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/ajax/AjaxFormUpdateController.java (added)
+++ tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/ajax/AjaxFormUpdateController.java Sun Apr 25 23:22:14 2010
@@ -0,0 +1,46 @@
+// Copyright 2010 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry5.internal.services.ajax;
+
+import org.apache.tapestry5.MarkupWriter;
+import org.apache.tapestry5.ValidationTracker;
+import org.apache.tapestry5.ajax.MultiZoneUpdate;
+import org.apache.tapestry5.services.FormSupport;
+
+/**
+ * Coordinates the rendering of page partials in the context of an
+ * Ajax update to an existing Form.
+ * 
+ * @see AjaxFormUpdateFilter
+ * @see MultiZoneUpdate
+ * @since 5.2.0
+ */
+public interface AjaxFormUpdateController
+{
+    void initializeForForm(String formComponentId, String formClientId);
+
+    /**
+     * Called before starting to render a zone's content; initializes
+     * the {@link FormSupport} and {@link ValidationTracker} environmentals
+     * and starts a heartbeat.
+     */
+    void setupBeforePartialZoneRender(MarkupWriter writer);
+
+    /**
+     * Ends the heartbeat, executes deferred Form actions,
+     * and cleans up the environmentals.
+     */
+    void cleanupAfterPartialZoneRender();
+}

Propchange: tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/ajax/AjaxFormUpdateController.java
------------------------------------------------------------------------------
    svn:eol-style = native

Added: tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/ajax/AjaxFormUpdateControllerImpl.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/ajax/AjaxFormUpdateControllerImpl.java?rev=937906&view=auto
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/ajax/AjaxFormUpdateControllerImpl.java (added)
+++ tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/ajax/AjaxFormUpdateControllerImpl.java Sun Apr 25 23:22:14 2010
@@ -0,0 +1,152 @@
+// Copyright 2010 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry5.internal.services.ajax;
+
+import org.apache.tapestry5.MarkupWriter;
+import org.apache.tapestry5.ValidationTracker;
+import org.apache.tapestry5.ValidationTrackerImpl;
+import org.apache.tapestry5.corelib.components.Form;
+import org.apache.tapestry5.corelib.internal.ComponentActionSink;
+import org.apache.tapestry5.corelib.internal.HiddenFieldPositioner;
+import org.apache.tapestry5.corelib.internal.InternalFormSupport;
+import org.apache.tapestry5.internal.util.CaptureResultCallback;
+import org.apache.tapestry5.ioc.ScopeConstants;
+import org.apache.tapestry5.ioc.annotations.Scope;
+import org.apache.tapestry5.ioc.internal.util.IdAllocator;
+import org.apache.tapestry5.runtime.Component;
+import org.apache.tapestry5.services.ClientDataEncoder;
+import org.apache.tapestry5.services.ComponentSource;
+import org.apache.tapestry5.services.Environment;
+import org.apache.tapestry5.services.FormSupport;
+import org.apache.tapestry5.services.Heartbeat;
+import org.apache.tapestry5.services.HiddenFieldLocationRules;
+import org.slf4j.Logger;
+
+@Scope(ScopeConstants.PERTHREAD)
+public class AjaxFormUpdateControllerImpl implements AjaxFormUpdateController
+{
+    private final ComponentSource componentSource;
+
+    private final HiddenFieldLocationRules rules;
+
+    private final Environment environment;
+
+    private final Heartbeat heartbeat;
+
+    private final ClientDataEncoder clientDataEncoder;
+
+    private final Logger logger;
+
+    private String formComponentId;
+
+    private String formClientId;
+
+    private HiddenFieldPositioner hiddenFieldPositioner;
+
+    private ComponentActionSink actionSink;
+
+    private InternalFormSupport formSupport;
+
+    public AjaxFormUpdateControllerImpl(ComponentSource componentSource, HiddenFieldLocationRules rules,
+            Environment environment, Heartbeat heartbeat, ClientDataEncoder clientDataEncoder, Logger logger)
+    {
+        this.componentSource = componentSource;
+        this.rules = rules;
+        this.environment = environment;
+        this.heartbeat = heartbeat;
+        this.clientDataEncoder = clientDataEncoder;
+        this.logger = logger;
+    }
+
+    public void initializeForForm(String formComponentId, String formClientId)
+    {
+        this.formComponentId = formComponentId;
+        this.formClientId = formClientId;
+    }
+
+    public void setupBeforePartialZoneRender(MarkupWriter writer)
+    {
+        if (formComponentId == null)
+            return;
+
+        hiddenFieldPositioner = new HiddenFieldPositioner(writer, rules);
+
+        actionSink = new ComponentActionSink(logger, clientDataEncoder);
+
+        formSupport = createInternalFormSupport(formClientId, formComponentId, actionSink);
+
+        environment.push(FormSupport.class, formSupport);
+        environment.push(ValidationTracker.class, new ValidationTrackerImpl());
+
+        heartbeat.begin();
+    }
+
+    public void cleanupAfterPartialZoneRender()
+    {
+        if (formComponentId == null)
+            return;
+
+        heartbeat.end();
+
+        formSupport.executeDeferred();
+
+        environment.pop(ValidationTracker.class);
+        environment.pop(FormSupport.class);
+
+        // If the Zone didn't actually contain any form control elements, then
+        // nothing will have been written to the action sink. In that case,
+        // get rid of the hidden field, if one was even added.
+
+        if (actionSink.isEmpty())
+        {
+            hiddenFieldPositioner.discard();
+
+            return;
+        }
+
+        // We've collected some hidden data that needs to be placed inside the Zone.
+        // This will raise an exception if the content of the zone didn't provide such a position.
+
+        hiddenFieldPositioner.getElement().attributes("type", "hidden",
+
+        "name", Form.FORM_DATA,
+
+        "value", actionSink.getClientData());
+    }
+
+    private InternalFormSupport createInternalFormSupport(String formClientId, String formComponentId,
+            ComponentActionSink actionSink)
+    {
+        // Kind of ugly, but the only way to ensure we don't have name collisions on the
+        // client side is to force a unique id into each name (as well as each id, but that's
+        // JavascriptSupport's job). It would be nice if we could agree on the uid, but
+        // not essential.
+
+        String uid = Long.toHexString(System.currentTimeMillis());
+
+        IdAllocator idAllocator = new IdAllocator("_" + uid);
+
+        Component formComponent = componentSource.getComponent(formComponentId);
+
+        CaptureResultCallback<InternalFormSupport> callback = CaptureResultCallback.create();
+
+        // This is a bit of a back-door to access a non-public method!
+
+        formComponent.getComponentResources().triggerEvent("internalCreateRenderTimeFormSupport", new Object[]
+        { formClientId, actionSink, idAllocator }, callback);
+
+        return callback.getResult();
+    }
+}

Propchange: tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/ajax/AjaxFormUpdateControllerImpl.java
------------------------------------------------------------------------------
    svn:eol-style = native

Modified: tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/ajax/AjaxFormUpdateFilter.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/ajax/AjaxFormUpdateFilter.java?rev=937906&r1=937905&r2=937906&view=diff
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/ajax/AjaxFormUpdateFilter.java (original)
+++ tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/ajax/AjaxFormUpdateFilter.java Sun Apr 25 23:22:14 2010
@@ -16,30 +16,19 @@ package org.apache.tapestry5.internal.se
 
 import java.io.IOException;
 
-import org.apache.tapestry5.MarkupWriter;
-import org.apache.tapestry5.ValidationTracker;
-import org.apache.tapestry5.ValidationTrackerImpl;
-import org.apache.tapestry5.corelib.components.Form;
-import org.apache.tapestry5.corelib.components.Zone;
-import org.apache.tapestry5.corelib.internal.ComponentActionSink;
-import org.apache.tapestry5.corelib.internal.HiddenFieldPositioner;
-import org.apache.tapestry5.corelib.internal.InternalFormSupport;
-import org.apache.tapestry5.internal.services.PageRenderQueue;
 import org.apache.tapestry5.internal.services.RequestConstants;
-import org.apache.tapestry5.internal.util.CaptureResultCallback;
-import org.apache.tapestry5.ioc.internal.util.IdAllocator;
 import org.apache.tapestry5.ioc.internal.util.InternalUtils;
-import org.apache.tapestry5.json.JSONObject;
-import org.apache.tapestry5.runtime.Component;
-import org.apache.tapestry5.services.*;
-import org.slf4j.Logger;
+import org.apache.tapestry5.services.Ajax;
+import org.apache.tapestry5.services.ComponentEventRequestFilter;
+import org.apache.tapestry5.services.ComponentEventRequestHandler;
+import org.apache.tapestry5.services.ComponentEventRequestParameters;
+import org.apache.tapestry5.services.Request;
 
 /**
- * Filter for the {@link Ajax} {@link ComponentEventRequestHandler} pipeline that may
- * contribute a {@link PartialMarkupRendererFilter} into the {@link PageRenderQueue}.
- * The contributed filter detects the case of a a client-side Tapestry.ZoneManager (i.e., a {@link Zone} component) and,
- * if it indicates the containing {@link Form} component, sets up a {@link PartialMarkupRendererFilter} to re-establish
- * the Form so that it will all mesh together on the client side.
+ * Filter for the {@link Ajax} {@link ComponentEventRequestHandler} that informs the {@link AjaxFormUpdateController}
+ * about the form's client id and component id. Partial renders work with the
+ * AjaxFormUpdateController to ensure that the Form data, if any, is collected and rendered
+ * as part of the response.
  * 
  * @since 5.2.0
  */
@@ -47,32 +36,12 @@ public class AjaxFormUpdateFilter implem
 {
     private final Request request;
 
-    private final ComponentSource componentSource;
+    private final AjaxFormUpdateController ajaxFormUpdateController;
 
-    private final HiddenFieldLocationRules rules;
-
-    private final Environment environment;
-
-    private final Heartbeat heartbeat;
-
-    private final ClientDataEncoder clientDataEncoder;
-
-    private final PageRenderQueue queue;
-
-    private final Logger logger;
-
-    public AjaxFormUpdateFilter(Request request, ComponentSource componentSource, HiddenFieldLocationRules rules,
-            Environment environment, Heartbeat heartbeat, ClientDataEncoder clientDataEncoder, PageRenderQueue queue,
-            Logger logger)
+    public AjaxFormUpdateFilter(Request request, AjaxFormUpdateController ajaxFormUpdateController)
     {
         this.request = request;
-        this.componentSource = componentSource;
-        this.rules = rules;
-        this.environment = environment;
-        this.heartbeat = heartbeat;
-        this.clientDataEncoder = clientDataEncoder;
-        this.queue = queue;
-        this.logger = logger;
+        this.ajaxFormUpdateController = ajaxFormUpdateController;
     }
 
     public void handle(ComponentEventRequestParameters parameters, ComponentEventRequestHandler handler)
@@ -82,89 +51,8 @@ public class AjaxFormUpdateFilter implem
         String formComponentId = request.getParameter(RequestConstants.FORM_COMPONENTID_PARAMETER);
 
         if (InternalUtils.isNonBlank(formClientId) && InternalUtils.isNonBlank(formComponentId))
-            addFilterToPartialRenderQueue(formClientId, formComponentId);
+            ajaxFormUpdateController.initializeForForm(formComponentId, formClientId);
 
         handler.handle(parameters);
     }
-
-    private void addFilterToPartialRenderQueue(String formClientId, String formComponentId)
-    {
-        queue.addPartialMarkupRendererFilter(createFilter(formClientId, formComponentId));
-    }
-
-    private PartialMarkupRendererFilter createFilter(final String formClientId, final String formComponentId)
-    {
-        return new PartialMarkupRendererFilter()
-        {
-            public void renderMarkup(MarkupWriter writer, JSONObject reply, PartialMarkupRenderer renderer)
-            {
-                HiddenFieldPositioner hiddenFieldPositioner = new HiddenFieldPositioner(writer, rules);
-
-                ComponentActionSink actionSink = new ComponentActionSink(logger, clientDataEncoder);
-
-                InternalFormSupport formSupport = createInternalFormSupport(formClientId, formComponentId, actionSink);
-
-                setupBeforeRender(formSupport);
-
-                renderer.renderMarkup(writer, reply);
-
-                cleanupAfterRender(formSupport);
-
-                injectHiddenFieldIntoDOM(hiddenFieldPositioner, actionSink);
-            }
-
-            private void setupBeforeRender(InternalFormSupport formSupport)
-            {
-                environment.push(FormSupport.class, formSupport);
-                environment.push(ValidationTracker.class, new ValidationTrackerImpl());
-
-                heartbeat.begin();
-            }
-
-            private void cleanupAfterRender(InternalFormSupport formSupport)
-            {
-                formSupport.executeDeferred();
-
-                heartbeat.end();
-
-                environment.pop(ValidationTracker.class);
-                environment.pop(FormSupport.class);
-            }
-
-            private void injectHiddenFieldIntoDOM(HiddenFieldPositioner hiddenFieldPositioner,
-                    ComponentActionSink actionSink)
-            {
-                hiddenFieldPositioner.getElement().attributes("type", "hidden",
-
-                "name", Form.FORM_DATA,
-
-                "value", actionSink.getClientData());
-            }
-
-            private InternalFormSupport createInternalFormSupport(String formClientId, String formComponentId,
-                    ComponentActionSink actionSink)
-            {
-                // Kind of ugly, but the only way to ensure we don't have name collisions on the
-                // client side is to force a unique id into each name (as well as each id, but that's
-                // JavascriptSupport's job). It would be nice if we could agree on the uid, but
-                // not essential.
-
-                String uid = Long.toHexString(System.currentTimeMillis());
-
-                IdAllocator idAllocator = new IdAllocator("_" + uid);
-
-                Component formComponent = componentSource.getComponent(formComponentId);
-
-                CaptureResultCallback<InternalFormSupport> callback = CaptureResultCallback.create();
-
-                // This is a bit of a back-door to access a non-public method!
-
-                formComponent.getComponentResources().triggerEvent("internalCreateRenderTimeFormSupport", new Object[]
-                { formClientId, actionSink, idAllocator }, callback);
-
-                return callback.getResult();
-            }
-
-        };
-    }
 }

Modified: tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/structure/ComponentPageElementImpl.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/structure/ComponentPageElementImpl.java?rev=937906&r1=937905&r2=937906&view=diff
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/structure/ComponentPageElementImpl.java (original)
+++ tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/structure/ComponentPageElementImpl.java Sun Apr 25 23:22:14 2010
@@ -54,6 +54,8 @@ import org.apache.tapestry5.ioc.internal
 import org.apache.tapestry5.ioc.internal.util.InternalUtils;
 import org.apache.tapestry5.ioc.internal.util.Orderer;
 import org.apache.tapestry5.ioc.internal.util.TapestryException;
+import org.apache.tapestry5.ioc.util.AvailableValues;
+import org.apache.tapestry5.ioc.util.UnknownValueException;
 import org.apache.tapestry5.model.ComponentModel;
 import org.apache.tapestry5.model.ParameterModel;
 import org.apache.tapestry5.runtime.Component;
@@ -870,7 +872,8 @@ public class ComponentPageElementImpl ex
         {
             Set<String> ids = InternalUtils.keys(children);
 
-            throw new TapestryException(StructureMessages.noSuchComponent(this, embeddedId, ids), this, null);
+            throw new UnknownValueException(String.format("Component %s does not contain embedded component '%s'.",
+                    getCompleteId(), embeddedId), new AvailableValues("embedded components", ids));
         }
 
         return embeddedElement;

Modified: tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/structure/StructureMessages.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/structure/StructureMessages.java?rev=937906&r1=937905&r2=937906&view=diff
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/structure/StructureMessages.java (original)
+++ tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/structure/StructureMessages.java Sun Apr 25 23:22:14 2010
@@ -38,13 +38,7 @@ final class StructureMessages
         return MESSAGES.format("missing-parameters", InternalUtils.joinSorted(parameters), element
                 .getComponentResources().getComponentModel().getComponentClassName());
     }
-
-    static String noSuchComponent(ComponentPageElement parent, String embeddedId, Set<String> components)
-    {
-        return MESSAGES.format("no-such-component", parent.getCompleteId(), embeddedId,
-                               InternalUtils.joinSorted(components));
-    }
-
+    
     static String unknownMixin(String componentId, String mixinClassName)
     {
         return MESSAGES.format("unknown-mixin", componentId, mixinClassName);

Modified: tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/services/ajax/MultiZoneUpdateEventResultProcessor.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/services/ajax/MultiZoneUpdateEventResultProcessor.java?rev=937906&r1=937905&r2=937906&view=diff
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/services/ajax/MultiZoneUpdateEventResultProcessor.java (original)
+++ tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/services/ajax/MultiZoneUpdateEventResultProcessor.java Sun Apr 25 23:22:14 2010
@@ -1,4 +1,4 @@
-// Copyright 2009 The Apache Software Foundation
+// Copyright 2009, 2010 The Apache Software Foundation
 //
 // Licensed under the Apache License, Version 2.0 (the "License");
 // you may not use this file except in compliance with the License.
@@ -17,6 +17,7 @@ package org.apache.tapestry5.services.aj
 import org.apache.tapestry5.MarkupWriter;
 import org.apache.tapestry5.ajax.MultiZoneUpdate;
 import org.apache.tapestry5.internal.services.PageRenderQueue;
+import org.apache.tapestry5.internal.services.ajax.AjaxFormUpdateController;
 import org.apache.tapestry5.ioc.internal.util.InternalUtils;
 import org.apache.tapestry5.ioc.services.TypeCoercer;
 import org.apache.tapestry5.runtime.RenderCommand;
@@ -43,10 +44,14 @@ public class MultiZoneUpdateEventResultP
 
     private final TypeCoercer typeCoercer;
 
-    public MultiZoneUpdateEventResultProcessor(PageRenderQueue queue, TypeCoercer typeCoercer)
+    private final AjaxFormUpdateController ajaxFormUpdateController;
+
+    public MultiZoneUpdateEventResultProcessor(PageRenderQueue queue, TypeCoercer typeCoercer,
+            AjaxFormUpdateController ajaxFormUpdateController)
     {
         this.queue = queue;
         this.typeCoercer = typeCoercer;
+        this.ajaxFormUpdateController = ajaxFormUpdateController;
     }
 
     public void processResultValue(final MultiZoneUpdate value) throws IOException
@@ -69,7 +74,8 @@ public class MultiZoneUpdateEventResultP
 
             RenderCommand zoneRenderCommand = toRenderer(zoneId, provided);
 
-            queue.addPartialMarkupRendererFilter(new SingleZonePartialRendererFilter(zoneId, zoneRenderCommand, queue));
+            queue.addPartialMarkupRendererFilter(new SingleZonePartialRendererFilter(zoneId, zoneRenderCommand, queue,
+                    ajaxFormUpdateController));
         }
     }
 

Modified: tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/services/ajax/SingleZonePartialRendererFilter.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/services/ajax/SingleZonePartialRendererFilter.java?rev=937906&r1=937905&r2=937906&view=diff
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/services/ajax/SingleZonePartialRendererFilter.java (original)
+++ tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/services/ajax/SingleZonePartialRendererFilter.java Sun Apr 25 23:22:14 2010
@@ -1,4 +1,4 @@
-// Copyright 2009 The Apache Software Foundation
+// Copyright 2009, 2010 The Apache Software Foundation
 //
 // Licensed under the Apache License, Version 2.0 (the "License");
 // you may not use this file except in compliance with the License.
@@ -17,6 +17,7 @@ package org.apache.tapestry5.services.aj
 import org.apache.tapestry5.MarkupWriter;
 import org.apache.tapestry5.dom.Element;
 import org.apache.tapestry5.internal.services.PageRenderQueue;
+import org.apache.tapestry5.internal.services.ajax.AjaxFormUpdateController;
 import org.apache.tapestry5.json.JSONObject;
 import org.apache.tapestry5.runtime.RenderCommand;
 import org.apache.tapestry5.runtime.RenderQueue;
@@ -37,11 +38,15 @@ public class SingleZonePartialRendererFi
 
     private final PageRenderQueue queue;
 
-    public SingleZonePartialRendererFilter(String zoneId, RenderCommand zoneRenderCommand, PageRenderQueue queue)
+    private final AjaxFormUpdateController ajaxFormUpdateController;
+
+    public SingleZonePartialRendererFilter(String zoneId, RenderCommand zoneRenderCommand, PageRenderQueue queue,
+            AjaxFormUpdateController ajaxFormUpdateController)
     {
         this.zoneId = zoneId;
         this.zoneRenderCommand = zoneRenderCommand;
         this.queue = queue;
+        this.ajaxFormUpdateController = ajaxFormUpdateController;
     }
 
     public void renderMarkup(MarkupWriter writer, final JSONObject reply, PartialMarkupRenderer renderer)
@@ -55,12 +60,18 @@ public class SingleZonePartialRendererFi
 
                 final Element zoneContainer = writer.element("zone-update", "zoneId", zoneId);
 
+                ajaxFormUpdateController.setupBeforePartialZoneRender(writer);
+
                 queue.push(new RenderCommand()
                 {
                     public void render(MarkupWriter writer, RenderQueue queue)
                     {
                         writer.end(); // the zoneContainer element
 
+                        // Need to do this Ajax Form-related cleanup here, before we extract the zone content.
+
+                        ajaxFormUpdateController.cleanupAfterPartialZoneRender();
+
                         String zoneUpdateContent = zoneContainer.getChildMarkup();
 
                         zoneContainer.remove();

Modified: tapestry/tapestry5/trunk/tapestry-core/src/main/resources/org/apache/tapestry5/internal/structure/StructureStrings.properties
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/main/resources/org/apache/tapestry5/internal/structure/StructureStrings.properties?rev=937906&r1=937905&r2=937906&view=diff
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-core/src/main/resources/org/apache/tapestry5/internal/structure/StructureStrings.properties (original)
+++ tapestry/tapestry5/trunk/tapestry-core/src/main/resources/org/apache/tapestry5/internal/structure/StructureStrings.properties Sun Apr 25 23:22:14 2010
@@ -13,7 +13,6 @@
 # limitations under the License.
 
 missing-parameters=Parameter(s) '%s' are required for %s, but have not been bound.
-no-such-component=Component %s does not contain an embedded component with id '%s'. Available components: %s.
 unknown-mixin=Component %s does not contain a mixin of type %s.
 detach-failure=Listener %s failed during page detach: %s
 wrong-phase-result-type=The return value from a render phase event method was not compatible with the expected return type. Expected is a component, a block or an instance of %s. \

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=937906&r1=937905&r2=937906&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 Sun Apr 25 23:22:14 2010
@@ -378,8 +378,9 @@ var Tapestry = {
 				if (Tapestry.windowUnloaded)
 					return;
 
-				/* Prototype treats status == 0 as success, even though it seems to mean
-				 * the server didn't respond.
+				/*
+				 * Prototype treats status == 0 as success, even though it seems
+				 * to mean the server didn't respond.
 				 */
 				if (!response.getStatus() || !response.request.success()) {
 					Tapestry.error(Tapestry.Messages.ajaxRequestUnsuccessful);
@@ -832,6 +833,22 @@ Tapestry.Initializer = {
 				spec.zoneId, spec.url);
 	},
 
+	/**
+	 * Converts a link into an Ajax update of a Zone. The url includes the
+	 * information to reconnect with the server-side Form.
+	 * 
+	 * @param spec.selectId
+	 *            id or instance of <select>
+	 * @param spec.zoneId
+	 *            id of element to update when select is changed
+	 * @param spec.url
+	 *            component event request URL
+	 */
+	linkSelectToZone : function(spec) {
+		Tapestry.Initializer.updateZoneOnEvent("change", spec.selectId,
+				spec.zoneId, spec.url);
+	},
+
 	updateZoneOnEvent : function(eventName, element, zoneId, url) {
 		element = $(element);
 
@@ -888,20 +905,21 @@ Tapestry.Initializer = {
 			if (!zoneObject)
 				return;
 
-			var newUrl = url;
-
 			/*
 			 * A hack related to allowing a Select to perform an Ajax update of
 			 * the page.
 			 */
 
+			var parameters = {};
+
 			if (element.tagName == "SELECT" && element.value) {
-				newUrl += '&t:selectvalue=' + element.value;
+				parameters["t:selectvalue"] = element.value;
 			}
 
-			zoneObject.updateFromURL(newUrl);
+			zoneObject.updateFromURL(url, parameters);
 		});
 	},
+
 	/**
 	 * Keys in the masterSpec are ids of field control elements. Value is a list
 	 * of validation specs. Each validation spec is a 2 or 3 element array.

Added: tapestry/tapestry5/trunk/tapestry-core/src/test/app1/MultiZoneUpdateInsideForm.tml
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/test/app1/MultiZoneUpdateInsideForm.tml?rev=937906&view=auto
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-core/src/test/app1/MultiZoneUpdateInsideForm.tml (added)
+++ tapestry/tapestry5/trunk/tapestry-core/src/test/app1/MultiZoneUpdateInsideForm.tml Sun Apr 25 23:22:14 2010
@@ -0,0 +1,16 @@
+<t:border xmlns:t="http://tapestry.apache.org/schema/tapestry_5_1_0.xsd" xmlns:p="tapestry:parameter">
+
+  <h1>Multi Zone Update Inside Form Demo</h1>
+
+  <form t:type="Form" t:id="form" t:clientValidation="false" action="#">
+    <t:label for="selectValue1"/>
+    <select t:type="Select" t:id="selectValue1" t:validate="required" t:zone="select1ValueZone"/>
+    <t:zone t:id="select1ValueZone" visible="false">Show</t:zone>
+    <t:zone t:id="select2ValueZone">
+      <t:label for="selectValue2"/>
+      <select t:type="Select" t:id="selectValue2" t:validate="required"/>
+    </t:zone>
+    <br/>
+    <input type="submit" value="Upate Form"/>
+  </form>
+</t:border>
\ No newline at end of file

Modified: tapestry/tapestry5/trunk/tapestry-core/src/test/conf/testng-limited.xml
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/test/conf/testng-limited.xml?rev=937906&r1=937905&r2=937906&view=diff
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-core/src/test/conf/testng-limited.xml (original)
+++ tapestry/tapestry5/trunk/tapestry-core/src/test/conf/testng-limited.xml Sun Apr 25 23:22:14 2010
@@ -11,9 +11,7 @@
       <class name="org.apache.tapestry5.test.SeleniumLauncher"/>
 
       <!--  Modify classes below as needed. -->
-      <class name="org.apache.tapestry5.integration.app1.AjaxTests"/>
-      <class name="org.apache.tapestry5.integration.app1.FormTests"/>
-      <class name="org.apache.tapestry5.integration.app1.GridTests"/>
+      <class name="org.apache.tapestry5.integration.app1.ZoneTests"/>
 
     </classes>
   </test>

Modified: tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry5/integration/app1/ZoneTests.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry5/integration/app1/ZoneTests.java?rev=937906&r1=937905&r2=937906&view=diff
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry5/integration/app1/ZoneTests.java (original)
+++ tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry5/integration/app1/ZoneTests.java Sun Apr 25 23:22:14 2010
@@ -32,7 +32,7 @@ public class ZoneTests extends TapestryC
     {
         clickThru("Select Zone Demo");
 
-        type("carMaker", "BMW");
+        select("carMaker", "Bmw");
 
         waitForElementToAppear("carModelContainer");
 
@@ -44,7 +44,9 @@ public class ZoneTests extends TapestryC
 
         assertText(String.format("//div[@class='%s']/span", "t-error-popup"), "You must provide a value for Car Model.");
 
-        type("carModel", "7 Series");
+        String selectLocator = "//div[@id='modelZone']//select";
+
+        select(selectLocator, "7 Series");
 
         clickAndWait(SUBMIT);
 
@@ -52,13 +54,11 @@ public class ZoneTests extends TapestryC
 
         assertTextPresent("Car Model: 7 Series");
 
-        waitForElementToDisappear("carModelContainer");
-
-        type("carMaker", "MERCEDES");
+        select("carMaker", "Mercedes");
 
         waitForElementToAppear("carModelContainer");
 
-        type("carModel", "E-Class");
+        select(selectLocator, "E-Class");
 
         clickAndWait(SUBMIT);
 
@@ -206,7 +206,6 @@ public class ZoneTests extends TapestryC
     /**
      * TAP5-707
      */
-
     @Test
     public void zone_fade_back_backgroundcolor()
     {
@@ -238,10 +237,23 @@ public class ZoneTests extends TapestryC
         waitForElementToAppear("updated");
 
         type("//INPUT[@type='text']", "Tapestry 5.2");
-        
+
         clickAndWait(SUBMIT);
 
         assertText("output", "Tapestry 5.2");
     }
 
+    /** TAP5-1109 */
+    @Test
+    public void update_to_zone_inside_form()
+    {
+        clickThru("MultiZone Update inside a Form");
+
+        select("selectValue1", "3 pre ajax");
+
+        waitForElementToAppear("select1ValueZone");
+
+        select("//div[@id='select2ValueZone']//select", "4 post ajax");
+    }
+
 }

Modified: tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry5/integration/app1/pages/Index.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry5/integration/app1/pages/Index.java?rev=937906&r1=937905&r2=937906&view=diff
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry5/integration/app1/pages/Index.java (original)
+++ tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry5/integration/app1/pages/Index.java Sun Apr 25 23:22:14 2010
@@ -67,6 +67,9 @@ public class Index
     private static final List<Item> ITEMS = CollectionFactory
             .newList(
 
+                    new Item("MultiZoneUpdateInsideForm", "MultiZone Update inside a Form",
+                            "Update multiple zones within a single Form."),
+
                     new Item("ZoneFormUpdateDemo", "Zone/Form Update Demo", "Updating a Zone inside a Form"),
 
                     new Item("RenderNotificationDemo", "RenderNotification Demo", "Use of RenderNotification mixin"),
@@ -425,8 +428,8 @@ public class Index
 
                     new Item("BeanDisplayEnumDemo", "BeanDisplay Enum Demo",
                             "User represenation of enum values is correctly read from messages"),
-                            
-                    new Item("unavailablecomponentdemo", "Report Location of Unavailable Component", 
+
+                    new Item("unavailablecomponentdemo", "Report Location of Unavailable Component",
                             "Report Location of Unavailable Component")
 
             );

Added: tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry5/integration/app1/pages/MultiZoneUpdateInsideForm.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry5/integration/app1/pages/MultiZoneUpdateInsideForm.java?rev=937906&view=auto
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry5/integration/app1/pages/MultiZoneUpdateInsideForm.java (added)
+++ tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry5/integration/app1/pages/MultiZoneUpdateInsideForm.java Sun Apr 25 23:22:14 2010
@@ -0,0 +1,185 @@
+// Copyright 2010 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry5.integration.app1.pages;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.apache.tapestry5.AbstractOptionModel;
+import org.apache.tapestry5.EventContext;
+import org.apache.tapestry5.OptionGroupModel;
+import org.apache.tapestry5.OptionModel;
+import org.apache.tapestry5.SelectModel;
+import org.apache.tapestry5.ValueEncoder;
+import org.apache.tapestry5.ajax.MultiZoneUpdate;
+import org.apache.tapestry5.annotations.Component;
+import org.apache.tapestry5.annotations.Log;
+import org.apache.tapestry5.annotations.Property;
+import org.apache.tapestry5.corelib.components.Select;
+import org.apache.tapestry5.corelib.components.Zone;
+import org.apache.tapestry5.ioc.annotations.Inject;
+import org.apache.tapestry5.ioc.internal.util.Func;
+import org.apache.tapestry5.ioc.services.Coercion;
+import org.apache.tapestry5.services.Request;
+import org.apache.tapestry5.util.AbstractSelectModel;
+
+public class MultiZoneUpdateInsideForm
+{
+    @Inject
+    private Request request;
+
+    @Component(id = "selectValue1", parameters =
+    { "model=select1Model", "encoder=select1Model" })
+    private Select select1;
+
+    @Property
+    private SelectModel select1Model;
+
+    @Property
+    private SelectObj selectValue1;
+
+    @Component(id = "selectValue2", parameters =
+    { "model=select2Model", "encoder=select2Model" })
+    private Select select2;
+
+    @Property
+    private SelectModel select2Model;
+
+    @Property
+    private SelectObj selectValue2;
+
+    @Component(id = "select1ValueZone")
+    private Zone select1ValueZone;
+
+    @Component(id = "select2ValueZone")
+    private Zone select2ValueZone;
+
+    public class SelectObj
+    {
+        final int id;
+        final String label;
+
+        public SelectObj(int id, String label)
+        {
+            this.id = id;
+            this.label = label;
+        }
+
+        public int getId()
+        {
+            return id;
+        }
+
+        public String getLabel()
+        {
+            return label;
+        }
+    }
+
+    public class SelectObjModel extends AbstractSelectModel implements ValueEncoder<SelectObj>
+    {
+        private final List<SelectObj> options;
+
+        public SelectObjModel(List<SelectObj> options)
+        {
+            this.options = options;
+        }
+
+        public List<OptionGroupModel> getOptionGroups()
+        {
+            return null;
+        }
+
+        public List<OptionModel> getOptions()
+        {
+            return Func.map(options, new Coercion<SelectObj, OptionModel>()
+            {
+                public OptionModel coerce(final SelectObj input)
+                {
+                    return new AbstractOptionModel()
+                    {
+                        public Object getValue()
+                        {
+                            return input;
+                        }
+
+                        public String getLabel()
+                        {
+                            return input.getLabel();
+                        }
+                    };
+                }
+            });
+        }
+
+        public String toClient(SelectObj value)
+        {
+            return String.valueOf(value.getId());
+        }
+
+        public SelectObj toValue(String clientValue)
+        {
+            int id = Integer.parseInt(clientValue);
+
+            for (SelectObj so : options)
+            {
+                if (so.id == id)
+                    return so;
+            }
+
+            return null;
+        }
+    }
+
+    void onActivate(EventContext ctx)
+    {
+        List<SelectObj> select1List = new ArrayList();
+        select1List.add(new SelectObj(0, "0 pre ajax"));
+        select1List.add(new SelectObj(1, "1 pre ajax"));
+        select1List.add(new SelectObj(2, "2 pre ajax"));
+        select1List.add(new SelectObj(3, "3 pre ajax"));
+        select1List.add(new SelectObj(4, "4 pre ajax"));
+        select1Model = new SelectObjModel(select1List);
+
+        List<SelectObj> select2List = new ArrayList();
+        select2List.add(new SelectObj(0, "0 pre ajax"));
+        select2List.add(new SelectObj(1, "1 pre ajax"));
+        select2List.add(new SelectObj(2, "2 pre ajax"));
+        select2List.add(new SelectObj(3, "3 pre ajax"));
+        select2Model = new SelectObjModel(select2List);
+    }
+
+    @Log
+    public Object onValueChangedFromSelectValue1(SelectObj selectObj)
+    {
+        List<SelectObj> select2List = new ArrayList();
+        select2List.add(new SelectObj(4, "4 post ajax"));
+        select2List.add(new SelectObj(5, "5 post ajax"));
+        select2List.add(new SelectObj(6, "6 post ajax"));
+        select2List.add(new SelectObj(7, "7 post ajax"));
+        select2Model = new SelectObjModel(select2List);
+
+        if (request.isXHR())
+        {
+            return new MultiZoneUpdate("select1ValueZone", select1ValueZone.getBody()).add("select2ValueZone",
+                    select2ValueZone.getBody());
+        }
+        else
+        {
+            return this;
+        }
+    }
+
+}

Propchange: tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry5/integration/app1/pages/MultiZoneUpdateInsideForm.java
------------------------------------------------------------------------------
    svn:eol-style = native