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 2008/06/03 04:36:34 UTC

svn commit: r662624 - in /tapestry/tapestry5/trunk: tapestry-core/src/main/java/org/apache/tapestry5/ tapestry-core/src/main/java/org/apache/tapestry5/corelib/base/ tapestry-core/src/main/java/org/apache/tapestry5/corelib/components/ tapestry-core/src/...

Author: hlship
Date: Mon Jun  2 19:36:33 2008
New Revision: 662624

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

Added:
    tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/corelib/components/AddRowLink.java
    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/RemoveRowLink.java
    tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/corelib/internal/AjaxFormLoopContext.java
    tapestry/tapestry5/trunk/tapestry-core/src/main/resources/org/apache/tapestry5/corelib/components/AjaxFormLoop.tml
    tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry5/integration/app1/data/DoubleItem.java
Modified:
    tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/BindingConstants.java
    tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/corelib/base/AbstractField.java
    tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/corelib/base/AbstractTextField.java
    tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/corelib/components/BeanEditor.java
    tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/corelib/components/DateField.java
    tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/corelib/components/Form.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/corelib/components/Grid.java
    tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/corelib/components/GridRows.java
    tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/corelib/components/Label.java
    tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/corelib/components/Loop.java
    tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/corelib/components/PageLink.java
    tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/corelib/components/PropertyEditor.java
    tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/corelib/components/RadioGroup.java
    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/SubmitNotifier.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/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/tapestry.js
    tapestry/tapestry5/trunk/tapestry-core/src/test/app1/FormInjectorDemo.tml
    tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry5/integration/IntegrationTests.java
    tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry5/integration/app1/components/ActionLinkIndirect.java
    tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry5/integration/app1/pages/FormInjectorDemo.java
    tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry5/internal/transform/components/ParameterComponent.java
    tapestry/tapestry5/trunk/tapestry-core/src/test/resources/log4j.properties
    tapestry/tapestry5/trunk/tapestry-upload/src/main/java/org/apache/tapestry5/upload/components/Upload.java

Modified: tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/BindingConstants.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/BindingConstants.java?rev=662624&r1=662623&r2=662624&view=diff
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/BindingConstants.java (original)
+++ tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/BindingConstants.java Mon Jun  2 19:36:33 2008
@@ -16,19 +16,55 @@
      */
     public static final String PROP = "prop";
 
+    /**
+     * A way of selecting a named {@link org.apache.tapestry5.NullFieldStrategy} contributed to {@link
+     * org.apache.tapestry5.services.NullFieldStrategySource}.
+     */
     public static final String NULLFIELDSTRATEGY = "nullfieldstrategy";
 
+    /**
+     * A reference to a component within the container's template, by local component id.
+     */
     public static final String COMPONENT = "component";
 
+    /**
+     * A reference to a localized message from the component's message catalog (including message keys inherited from
+     * the application global message catalog).
+     */
     public static final String MESSAGE = "message";
 
+    /**
+     * References (and configures) one ore more named {@link org.apache.tapestry5.Validator}s contributed to the {@link
+     * org.apache.tapestry5.services.FieldValidatorSource} service.
+     *
+     * @see org.apache.tapestry5.services.FieldValidatorSource
+     */
     public static final String VALIDATE = "validate";
 
+    /**
+     * References a named {@link org.apache.tapestry5.Translator} contributed to the {@link
+     * org.apache.tapestry5.services.TranslatorSource} service.
+     */
     public static final String TRANSLATE = "translate";
 
+    /**
+     * References a named block within the template.
+     */
     public static final String BLOCK = "block";
 
+    /**
+     * References a localized asset.  The asset will be relative to the component's class file, unless a prefix
+     * (typically, "context:") is used.
+     *
+     * @see org.apache.tapestry5.Asset
+     * @see org.apache.tapestry5.services.AssetSource
+     */
     public static final String ASSET = "asset";
 
+    /**
+     * Allows for temporary storage of information during the render only (may not currently be used during form
+     * submission processing).  This is often used to store the current object iterated over by a {@link
+     * org.apache.tapestry5.corelib.components.Loop} component.
+     */
     public static final String VAR = "var";
 }

Modified: tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/corelib/base/AbstractField.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/corelib/base/AbstractField.java?rev=662624&r1=662623&r2=662624&view=diff
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/corelib/base/AbstractField.java (original)
+++ tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/corelib/base/AbstractField.java Mon Jun  2 19:36:33 2008
@@ -71,13 +71,13 @@
         }
     };
 
-    static class SetupAction implements ComponentAction<AbstractField>, Serializable
+    static class Setup implements ComponentAction<AbstractField>, Serializable
     {
         private static final long serialVersionUID = 2690270808212097020L;
 
         private final String controlName;
 
-        public SetupAction(String controlName)
+        public Setup(String controlName)
         {
             this.controlName = controlName;
         }
@@ -86,9 +86,15 @@
         {
             component.setupControlName(controlName);
         }
+
+        @Override
+        public String toString()
+        {
+            return String.format("AbstractField.Setup[%s]", controlName);
+        }
     }
 
-    static class ProcessSubmissionAction implements ComponentAction<AbstractField>, Serializable
+    static class ProcessSubmission implements ComponentAction<AbstractField>, Serializable
     {
         private static final long serialVersionUID = -4346426414137434418L;
 
@@ -96,12 +102,18 @@
         {
             component.processSubmission();
         }
+
+        @Override
+        public String toString()
+        {
+            return "AbstractField.ProcessSubmission";
+        }
     }
 
     /**
      * Used a shared instance for all types of fields, for efficiency.
      */
-    private static final ProcessSubmissionAction PROCESS_SUBMISSION_ACTION = new ProcessSubmissionAction();
+    private static final ProcessSubmission PROCESS_SUBMISSION_ACTION = new ProcessSubmission();
 
     /**
      * The id used to generate a page-unique client-side identifier for the component. If a component renders multiple
@@ -152,7 +164,7 @@
         assignedClientId = renderSupport.allocateClientId(id);
         String controlName = formSupport.allocateControlName(id);
 
-        formSupport.storeAndExecute(this, new SetupAction(controlName));
+        formSupport.storeAndExecute(this, new Setup(controlName));
         formSupport.store(this, PROCESS_SUBMISSION_ACTION);
     }
 

Modified: tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/corelib/base/AbstractTextField.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/corelib/base/AbstractTextField.java?rev=662624&r1=662623&r2=662624&view=diff
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/corelib/base/AbstractTextField.java (original)
+++ tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/corelib/base/AbstractTextField.java Mon Jun  2 19:36:33 2008
@@ -62,7 +62,7 @@
     private Translator<Object> translate;
 
     /**
-     * The object that will perform input validation (which occurs after translation). The translate binding prefix is
+     * The object that will perform input validation (which occurs after translation). The validate binding prefix is
      * generally used to provide this object in a declarative fashion.
      */
     @Parameter(defaultPrefix = BindingConstants.VALIDATE)

Added: tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/corelib/components/AddRowLink.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/corelib/components/AddRowLink.java?rev=662624&view=auto
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/corelib/components/AddRowLink.java (added)
+++ tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/corelib/components/AddRowLink.java Mon Jun  2 19:36:33 2008
@@ -0,0 +1,57 @@
+// Copyright 2008 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.corelib.components;
+
+import org.apache.tapestry5.ComponentResources;
+import org.apache.tapestry5.MarkupWriter;
+import org.apache.tapestry5.RenderSupport;
+import org.apache.tapestry5.annotations.Environmental;
+import org.apache.tapestry5.annotations.SupportsInformalParameters;
+import org.apache.tapestry5.corelib.internal.AjaxFormLoopContext;
+import org.apache.tapestry5.ioc.annotations.Inject;
+
+/**
+ * Used inside an {@link org.apache.tapestry5.corelib.components.AjaxFormLoop} component to spur the addition of a new
+ * row.  Triggers a server-side "addRow" event which must return a Block (or component) to render the new row.
+ */
+@SupportsInformalParameters
+public class AddRowLink
+{
+    @Environmental
+    private AjaxFormLoopContext context;
+
+    @Inject
+    private RenderSupport renderSupport;
+
+    @Inject
+    private ComponentResources resources;
+
+    void beginRender(MarkupWriter writer)
+    {
+        String id = renderSupport.allocateClientId(resources);
+
+        writer.element("a",
+                       "id", id,
+                       "href", "#");
+        resources.renderInformalParameters(writer);
+
+        context.addAddRowTrigger(id);
+    }
+
+    void afterRender(MarkupWriter writer)
+    {
+        writer.end();
+    }
+}

Added: 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=662624&view=auto
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/corelib/components/AjaxFormLoop.java (added)
+++ tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/corelib/components/AjaxFormLoop.java Mon Jun  2 19:36:33 2008
@@ -0,0 +1,426 @@
+// Copyright 2008 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.corelib.components;
+
+import org.apache.tapestry5.*;
+import org.apache.tapestry5.annotations.*;
+import org.apache.tapestry5.corelib.internal.AjaxFormLoopContext;
+import org.apache.tapestry5.internal.services.ComponentClassCache;
+import org.apache.tapestry5.internal.services.PageRenderQueue;
+import org.apache.tapestry5.ioc.annotations.Inject;
+import org.apache.tapestry5.ioc.internal.util.Defense;
+import org.apache.tapestry5.ioc.services.TypeCoercer;
+import org.apache.tapestry5.json.JSONArray;
+import org.apache.tapestry5.json.JSONObject;
+import org.apache.tapestry5.services.*;
+
+import java.io.Serializable;
+import java.util.Collections;
+import java.util.Iterator;
+
+/**
+ * A special form of the {@link org.apache.tapestry5.corelib.components.Loop} component that adds a lot of Ajax support
+ * to handle adding new rows and removing existing rows dynamically.  Expects that the values being iterated over are
+ * entities that can be identified via a {@link org.apache.tapestry5.PrimaryKeyEncoder}.
+ */
+public class AjaxFormLoop
+{
+    /**
+     * The element to render for each iteration of the loop. The default comes from the template, or "div" if the
+     * template did not specify an element.
+     */
+    @Parameter(defaultPrefix = BindingConstants.LITERAL)
+    @Property(write = false)
+    private String element;
+
+    /**
+     * The objects to iterate over (passed to the internal Loop component).
+     */
+    @Parameter(required = true)
+    private Iterable source;
+
+    /**
+     * The current value from the source.
+     */
+    @Parameter(required = true)
+    private Object value;
+
+    /**
+     * A block to render after the loop as the body of the {@link org.apache.tapestry5.corelib.components.FormInjector}.
+     * This typically contains a {@link org.apache.tapestry5.corelib.components.AddRowLink}.
+     */
+    @Parameter("block:defaultAddRow")
+    @Property(write = false)
+    private Block addRow;
+
+    /**
+     * The block that contains the form injector (it is rendered last, as the "tail" of the AjaxFormLoop). This, in
+     * turn, references the addRow block (from a parameter, or a default).
+     */
+    @Inject
+    private Block tail;
+
+    /**
+     * Required parameter used to convert server-side objects (provided from the source) into client-side ids and back.
+     */
+    @Parameter(required = true)
+    private PrimaryKeyEncoder encoder;
+
+    @InjectComponent
+    private ClientElement rowInjector;
+
+    @InjectComponent
+    private FormFragment fragment;
+
+    @Inject
+    private Block ajaxResponse;
+
+    @Inject
+    private ComponentResources resources;
+
+    @Environmental
+    private FormSupport formSupport;
+
+    @Environmental
+    private Heartbeat heartbeat;
+
+    @Inject
+    private Environment environment;
+
+    @Inject
+    private RenderSupport renderSupport;
+
+    private JSONArray addRowTriggers;
+
+    private Iterator iterator;
+
+    @Inject
+    private TypeCoercer typeCoercer;
+
+    @Inject
+    private ComponentClassCache componentClassCache;
+
+    @Inject
+    private ComponentDefaultProvider componentDefaultProvider;
+
+    @Inject
+    private PageRenderQueue pageRenderQueue;
+
+    private boolean renderingInjector;
+
+    private final AjaxFormLoopContext context = new AjaxFormLoopContext()
+    {
+        public void addAddRowTrigger(String clientId)
+        {
+            Defense.notBlank(clientId, "clientId");
+
+            addRowTriggers.put(clientId);
+        }
+
+        private String currentFragmentId()
+        {
+            ClientElement element = renderingInjector ? rowInjector : fragment;
+
+            return element.getClientId();
+        }
+
+        public void addRemoveRowTrigger(String clientId)
+        {
+            Serializable id = idForCurrentValue();
+
+            String idType = id.getClass().getName();
+
+            Link link = resources.createActionLink("triggerRemoveRow", false, id, idType);
+
+            String asURI = link.toAbsoluteURI();
+
+            JSONObject spec = new JSONObject();
+            spec.put("link", clientId);
+            spec.put("fragment", currentFragmentId());
+            spec.put("url", asURI);
+
+            renderSupport.addInit("formLoopRemoveLink", spec);
+        }
+    };
+
+    Binding defaultSource()
+    {
+        return componentDefaultProvider.defaultBinding("source", resources);
+    }
+
+
+    String defaultElement()
+    {
+        return resources.getElementName("div");
+    }
+
+
+    /**
+     * Action for synchronizing the current element of the loop by recording its client value / primary key.
+     */
+    static class SyncValue implements ComponentAction<AjaxFormLoop>
+    {
+        private final Serializable id;
+
+        public SyncValue(Serializable id)
+        {
+            this.id = id;
+        }
+
+        public void execute(AjaxFormLoop component)
+        {
+            component.syncValue(id);
+        }
+
+        @Override
+        public String toString()
+        {
+            return String.format("AjaxFormLoop.SyncValue[%s]", id);
+        }
+    }
+
+    private static final ComponentAction<AjaxFormLoop> BEGIN_HEARTBEAT = new ComponentAction<AjaxFormLoop>()
+    {
+        public void execute(AjaxFormLoop component)
+        {
+            component.beginHeartbeat();
+        }
+
+        @Override
+        public String toString()
+        {
+            return "AjaxFormLoop.BeginHeartbeat";
+        }
+    };
+
+    @Property(write = false)
+    private final Renderable beginHeartbeat = new Renderable()
+    {
+        public void render(MarkupWriter writer)
+        {
+            formSupport.storeAndExecute(AjaxFormLoop.this, BEGIN_HEARTBEAT);
+        }
+    };
+
+    private static final ComponentAction<AjaxFormLoop> END_HEARTBEAT = new ComponentAction<AjaxFormLoop>()
+    {
+        public void execute(AjaxFormLoop component)
+        {
+            component.endHeartbeat();
+        }
+
+        @Override
+        public String toString()
+        {
+            return "AjaxFormLoop.EndHeartbeat";
+        }
+    };
+
+    @Property(write = false)
+    private final Renderable endHeartbeat = new Renderable()
+    {
+        public void render(MarkupWriter writer)
+        {
+            formSupport.storeAndExecute(AjaxFormLoop.this, END_HEARTBEAT);
+        }
+    };
+
+    @Property(write = false)
+    private final Renderable beforeBody = new Renderable()
+    {
+        public void render(MarkupWriter writer)
+        {
+            beginHeartbeat();
+            syncCurrentValue();
+        }
+    };
+
+    @Property(write = false)
+    private final Renderable afterBody = new Renderable()
+    {
+        public void render(MarkupWriter writer)
+        {
+            endHeartbeat();
+        }
+    };
+
+    @SuppressWarnings({ "unchecked" })
+    @Log
+    private void syncValue(Serializable id)
+    {
+        Object value = encoder.toValue(id);
+
+        if (value == null)
+            throw new RuntimeException(
+                    String.format("Unable to convert serialized id '%s' back into an object.", id));
+
+        this.value = value;
+    }
+
+    @Property(write = false)
+    private final Renderable syncValue = new Renderable()
+    {
+        public void render(MarkupWriter writer)
+        {
+            syncCurrentValue();
+        }
+    };
+
+    private void syncCurrentValue()
+    {
+        Serializable id = idForCurrentValue();
+
+        // Add the command that restores value from the value id,
+        // when the form is submitted.
+
+        formSupport.store(this, new SyncValue(id));
+    }
+
+    /**
+     * Uses the {@link org.apache.tapestry5.PrimaryKeyEncoder} to convert the current row value to an id.
+     */
+    @SuppressWarnings({ "unchecked" })
+    private Serializable idForCurrentValue()
+    {
+        return encoder.toKey(value);
+    }
+
+
+    void setupRender()
+    {
+        addRowTriggers = new JSONArray();
+
+        pushContext();
+
+        iterator = source == null
+                   ? Collections.EMPTY_LIST.iterator()
+                   : source.iterator();
+
+        renderingInjector = false;
+    }
+
+    private void pushContext()
+    {
+        environment.push(AjaxFormLoopContext.class, context);
+    }
+
+    boolean beginRender(MarkupWriter writer)
+    {
+        if (!iterator.hasNext()) return false;
+
+        value = iterator.next();
+
+        return true;  // Render body, etc.
+    }
+
+    Object afterRender(MarkupWriter writer)
+    {
+        // When out of source items to render, switch over to the addRow block (either the default,
+        // or from the addRow parameter) before proceeding to cleanup render.
+
+        if (!iterator.hasNext())
+        {
+            renderingInjector = true;
+            return tail;
+        }
+
+        // There's more to come, loop back to begin render.
+
+        return false;
+    }
+
+    void cleanupRender()
+    {
+        popContext();
+
+        JSONObject spec = new JSONObject();
+
+        spec.put("rowInjector", rowInjector.getClientId());
+        spec.put("addRowTriggers", addRowTriggers);
+
+        renderSupport.addInit("ajaxFormLoop", spec);
+    }
+
+    private void popContext()
+    {
+        environment.pop(AjaxFormLoopContext.class);
+    }
+
+    /**
+     * When the action event arrives from the FormInjector, we fire our own event, "addRow" to tell the container to add
+     * a new row, and to return that new entity for rendering.
+     */
+    @Log
+    Object onActionFromRowInjector()
+    {
+        ComponentEventCallback callback = new ComponentEventCallback()
+        {
+            public boolean handleResult(Object result)
+            {
+                value = result;
+
+                return true;
+            }
+        };
+
+        resources.triggerEvent("addRow", null, callback);
+
+        if (value == null)
+            throw new IllegalArgumentException(
+                    String.format("Event handler for event 'addRow' from %s should have returned a non-null value.",
+                                  resources.getCompleteId())
+            );
+
+
+        pageRenderQueue.addPartialFilter(new PartialMarkupRendererFilter()
+        {
+            public void renderMarkup(MarkupWriter writer, JSONObject reply, PartialMarkupRenderer renderer)
+            {
+                pushContext();
+
+                renderer.renderMarkup(writer, reply);
+
+                popContext();
+            }
+        });
+
+        return ajaxResponse;
+    }
+
+    @Log
+    Object onTriggerRemoveRow(String rowId, String idTypeName)
+    {
+        Class idType = componentClassCache.forName(idTypeName);
+
+        Serializable coerced = (Serializable) typeCoercer.coerce(rowId, idType);
+
+        Object value = encoder.toValue(coerced);
+
+        resources.triggerEvent("removeRow", new Object[] { value }, null);
+
+        return new JSONObject();
+    }
+
+    private void beginHeartbeat()
+    {
+        heartbeat.begin();
+    }
+
+    private void endHeartbeat()
+    {
+        heartbeat.end();
+    }
+
+}

Modified: tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/corelib/components/BeanEditor.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/corelib/components/BeanEditor.java?rev=662624&r1=662623&r2=662624&view=diff
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/corelib/components/BeanEditor.java (original)
+++ tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/corelib/components/BeanEditor.java Mon Jun  2 19:36:33 2008
@@ -46,6 +46,12 @@
         {
             component.doPrepare();
         }
+
+        @Override
+        public String toString()
+        {
+            return "BeanEditor.Prepare";
+        }
     }
 
     /**

Modified: tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/corelib/components/DateField.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/corelib/components/DateField.java?rev=662624&r1=662623&r2=662624&view=diff
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/corelib/components/DateField.java (original)
+++ tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/corelib/components/DateField.java Mon Jun  2 19:36:33 2008
@@ -52,7 +52,7 @@
      * The object that will perform input validation (which occurs after translation). The translate binding prefix is
      * generally used to provide this object in a declarative fashion.
      */
-    @Parameter(defaultPrefix = "validate")
+    @Parameter(defaultPrefix = BindingConstants.VALIDATE)
     @SuppressWarnings("unchecked")
     private FieldValidator<Object> validate = NOOP_VALIDATOR;
 

Modified: tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/corelib/components/Form.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/corelib/components/Form.java?rev=662624&r1=662623&r2=662624&view=diff
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/corelib/components/Form.java (original)
+++ tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/corelib/components/Form.java Mon Jun  2 19:36:33 2008
@@ -15,10 +15,7 @@
 package org.apache.tapestry5.corelib.components;
 
 import org.apache.tapestry5.*;
-import org.apache.tapestry5.annotations.Environmental;
-import org.apache.tapestry5.annotations.Mixin;
-import org.apache.tapestry5.annotations.Parameter;
-import org.apache.tapestry5.annotations.Persist;
+import org.apache.tapestry5.annotations.*;
 import org.apache.tapestry5.corelib.internal.ComponentActionSink;
 import org.apache.tapestry5.corelib.internal.FormSupportImpl;
 import org.apache.tapestry5.corelib.mixins.RenderInformals;
@@ -34,6 +31,7 @@
 import org.apache.tapestry5.ioc.internal.util.TapestryException;
 import org.apache.tapestry5.runtime.Component;
 import org.apache.tapestry5.services.*;
+import org.slf4j.Logger;
 
 import java.io.EOFException;
 import java.io.IOException;
@@ -137,6 +135,9 @@
     private String zone;
 
     @Inject
+    private Logger logger;
+
+    @Inject
     private Environment environment;
 
     @Inject
@@ -198,7 +199,7 @@
     void beginRender(MarkupWriter writer)
     {
 
-        actionSink = new ComponentActionSink();
+        actionSink = new ComponentActionSink(logger);
 
         name = renderSupport.allocateClientId(resources);
 
@@ -281,6 +282,7 @@
     }
 
     @SuppressWarnings({ "unchecked", "InfiniteLoopStatement" })
+    @Log
     Object onAction(EventContext context) throws IOException
     {
         tracker.clear();
@@ -369,6 +371,9 @@
 
         for (String actionsBase64 : values)
         {
+            if (logger.isDebugEnabled())
+                logger.debug(String.format("Processing actions: %s", actionsBase64));
+
             ObjectInputStream ois = null;
 
             Component component = null;
@@ -384,6 +389,10 @@
 
                     component = source.getComponent(componentId);
 
+                    if (logger.isDebugEnabled())
+                        logger.debug(String.format("Processing: %s  %s", componentId, action));
+
+
                     action.execute(component);
 
                     component = null;

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=662624&r1=662623&r2=662624&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 Mon Jun  2 19:36:33 2008
@@ -26,6 +26,7 @@
 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;
 
@@ -35,7 +36,6 @@
  * 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.
  * <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).
@@ -53,7 +53,6 @@
     @Parameter
     private boolean visible;
 
-
     /**
      * Name of a function on the client-side Tapestry.ElementEffect object that is invoked to make the fragment visible.
      * If not specified, then the default "slidedown" function is used.
@@ -68,6 +67,14 @@
     @Parameter(defaultPrefix = BindingConstants.LITERAL)
     private String hide;
 
+    /**
+     * The element to render for each iteration of the loop. The default comes from the template, or "div" if the
+     * template did not specific an element.
+     */
+    @Parameter(defaultPrefix = BindingConstants.LITERAL)
+    private String element;
+
+
     @Inject
     private Environment environment;
 
@@ -91,10 +98,18 @@
     private Request request;
 
     @Inject
+    private Logger logger;
+
+    @Inject
     private HiddenFieldLocationRules rules;
 
     private HiddenFieldPositioner hiddenFieldPositioner;
 
+    String defaultElement()
+    {
+        return resources.getElementName("div");
+    }
+
     private void handleSubmission(String elementName, List<WrappedComponentAction> actions)
     {
         String value = request.getParameter(elementName);
@@ -125,7 +140,7 @@
 
         hiddenFieldPositioner = new HiddenFieldPositioner(writer, rules);
 
-        Element element = writer.element("div", "id", clientId);
+        Element element = writer.element(this.element, "id", clientId);
 
         resources.renderInformalParameters(writer);
 
@@ -135,7 +150,7 @@
 
         clientBehaviorSupport.addFormFragment(clientId, show, hide);
 
-        componentActions = new ComponentActionSink();
+        componentActions = new ComponentActionSink(logger);
 
         // Here's the magic of environmentals ... we can create a wrapper around
         // the normal FormSupport environmental that intercepts some of the behavior.

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=662624&r1=662623&r2=662624&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 Mon Jun  2 19:36:33 2008
@@ -22,6 +22,7 @@
 import org.apache.tapestry5.corelib.internal.ComponentActionSink;
 import org.apache.tapestry5.corelib.internal.FormSupportImpl;
 import org.apache.tapestry5.corelib.internal.HiddenFieldPositioner;
+import org.apache.tapestry5.dom.Element;
 import org.apache.tapestry5.internal.services.ClientBehaviorSupport;
 import org.apache.tapestry5.internal.services.ComponentResultProcessorWrapper;
 import org.apache.tapestry5.internal.services.PageRenderQueue;
@@ -30,6 +31,7 @@
 import org.apache.tapestry5.runtime.RenderCommand;
 import org.apache.tapestry5.runtime.RenderQueue;
 import org.apache.tapestry5.services.*;
+import org.slf4j.Logger;
 
 import java.io.IOException;
 import java.util.List;
@@ -100,6 +102,11 @@
     @Inject
     private Environment environment;
 
+    @Inject
+    private Logger logger;
+
+    private Element clientElement;
+
     String defaultElement()
     {
         return resources.getElementName("div");
@@ -109,9 +116,9 @@
     {
         clientId = renderSupport.allocateClientId(resources);
 
-        writer.element(element,
+        clientElement = writer.element(element,
 
-                       "id", clientId);
+                                       "id", clientId);
 
         resources.renderInformalParameters(writer);
 
@@ -128,6 +135,11 @@
     void afterRender(MarkupWriter writer)
     {
         writer.end();
+
+        // Add the class name to the rendered client element. This allows nested elements to locate
+        // the containing FormInjector element.
+
+        clientElement.addClassName("t-forminjector");
     }
 
 
@@ -168,7 +180,7 @@
 
         final String formId = request.getParameter(FORMID_PARAMETER);
 
-        final ComponentActionSink actionSink = new ComponentActionSink();
+        final ComponentActionSink actionSink = new ComponentActionSink(logger);
 
         final RenderCommand cleanup = new RenderCommand()
         {

Modified: tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/corelib/components/Grid.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/corelib/components/Grid.java?rev=662624&r1=662623&r2=662624&view=diff
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/corelib/components/Grid.java (original)
+++ tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/corelib/components/Grid.java Mon Jun  2 19:36:33 2008
@@ -219,7 +219,8 @@
 
     @SuppressWarnings("unused")
     @Component(
-            parameters = { "rowClass=rowClass", "rowsPerPage=rowsPerPage", "currentPage=currentPage", "row=row", "volatile=inherit:volatile", "lean=inherit:lean" })
+            parameters = { "rowClass=rowClass", "rowsPerPage=rowsPerPage", "currentPage=currentPage", "row=row",
+                    "volatile=inherit:volatile", "lean=inherit:lean" })
     private GridRows rows;
 
     @Component(parameters = { "source=dataSource", "rowsPerPage=rowsPerPage", "currentPage=currentPage", "zone=zone" })
@@ -388,6 +389,12 @@
         {
             component.setupDataSource();
         }
+
+        @Override
+        public String toString()
+        {
+            return "Grid.SetupDataSource";
+        }
     };
 
     Object setupRender()

Modified: tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/corelib/components/GridRows.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/corelib/components/GridRows.java?rev=662624&r1=662623&r2=662624&view=diff
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/corelib/components/GridRows.java (original)
+++ tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/corelib/components/GridRows.java Mon Jun  2 19:36:33 2008
@@ -55,6 +55,12 @@
         {
             component.setupForRow(rowIndex);
         }
+
+        @Override
+        public String toString()
+        {
+            return String.format("GridRows.SetupForRow[%d]", rowIndex);
+        }
     }
 
     /**

Modified: tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/corelib/components/Label.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/corelib/components/Label.java?rev=662624&r1=662623&r2=662624&view=diff
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/corelib/components/Label.java (original)
+++ tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/corelib/components/Label.java Mon Jun  2 19:36:33 2008
@@ -14,10 +14,7 @@
 
 package org.apache.tapestry5.corelib.components;
 
-import org.apache.tapestry5.ComponentResources;
-import org.apache.tapestry5.Field;
-import org.apache.tapestry5.MarkupWriter;
-import org.apache.tapestry5.ValidationDecorator;
+import org.apache.tapestry5.*;
 import org.apache.tapestry5.annotations.*;
 import org.apache.tapestry5.dom.Element;
 import org.apache.tapestry5.ioc.annotations.Inject;
@@ -39,7 +36,7 @@
      * The for parameter is used to identify the {@link Field} linked to this label (it is named this way because it
      * results in the for attribute of the label element).
      */
-    @Parameter(name = "for", required = true, defaultPrefix = "component")
+    @Parameter(name = "for", required = true, defaultPrefix = BindingConstants.COMPONENT)
     private Field field;
 
     @Environmental

Modified: tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/corelib/components/Loop.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/corelib/components/Loop.java?rev=662624&r1=662623&r2=662624&view=diff
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/corelib/components/Loop.java (original)
+++ tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/corelib/components/Loop.java Mon Jun  2 19:36:33 2008
@@ -48,6 +48,12 @@
         {
             component.resetIndex();
         }
+
+        @Override
+        public String toString()
+        {
+            return "Loop.ResetIndex";
+        }
     };
 
     /**
@@ -63,6 +69,11 @@
             component.setupForVolatile();
         }
 
+        @Override
+        public String toString()
+        {
+            return "Loop.SetupForVolatile";
+        }
     };
 
     /**
@@ -77,6 +88,12 @@
         {
             component.advanceVolatile();
         }
+
+        @Override
+        public String toString()
+        {
+            return "Loop.AdvanceVolatile";
+        }
     };
 
     /**
@@ -92,6 +109,11 @@
             component.endHeartbeat();
         }
 
+        @Override
+        public String toString()
+        {
+            return "Loop.EndHeartbeat";
+        }
     };
 
     /**
@@ -112,6 +134,12 @@
         {
             component.restoreState(storedValue);
         }
+
+        @Override
+        public String toString()
+        {
+            return String.format("Loop.RestoreState[%s]", storedValue);
+        }
     }
 
     /**
@@ -132,6 +160,12 @@
         {
             component.restoreStateViaEncodedPrimaryKey(primaryKey);
         }
+
+        @Override
+        public String toString()
+        {
+            return String.format("Loop.RestoreStateViaEncodedPrimaryKey[%s]", primaryKey);
+        }
     }
 
     /**
@@ -155,6 +189,12 @@
         {
             component.prepareForKeys(keys);
         }
+
+        @Override
+        public String toString()
+        {
+            return "Loop.PrepareForKeys" + keys;
+        }
     }
 
     /**
@@ -185,7 +225,7 @@
      * The element to render. If not null, then the loop will render the indicated element around its body (on each pass
      * through the loop). The default is derived from the component template.
      */
-    @Parameter(value = "prop:componentResources.elementName", defaultPrefix = BindingConstants.LITERAL)
+    @Parameter(defaultPrefix = BindingConstants.LITERAL)
     private String element;
 
     /**
@@ -218,6 +258,11 @@
         return componentDefaultProvider.defaultBinding("source", resources);
     }
 
+    String defaultElement()
+    {
+        return resources.getElementName();
+    }
+
     @SetupRender
     boolean setup()
     {

Modified: tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/corelib/components/PageLink.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/corelib/components/PageLink.java?rev=662624&r1=662623&r2=662624&view=diff
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/corelib/components/PageLink.java (original)
+++ tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/corelib/components/PageLink.java Mon Jun  2 19:36:33 2008
@@ -14,6 +14,7 @@
 
 package org.apache.tapestry5.corelib.components;
 
+import org.apache.tapestry5.BindingConstants;
 import org.apache.tapestry5.ComponentResources;
 import org.apache.tapestry5.Link;
 import org.apache.tapestry5.MarkupWriter;
@@ -36,7 +37,7 @@
     /**
      * The logical name of the page to link to.
      */
-    @Parameter(required = true, defaultPrefix = "literal")
+    @Parameter(required = true, defaultPrefix = BindingConstants.LITERAL)
     private String page;
 
     @Inject

Modified: tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/corelib/components/PropertyEditor.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/corelib/components/PropertyEditor.java?rev=662624&r1=662623&r2=662624&view=diff
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/corelib/components/PropertyEditor.java (original)
+++ tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/corelib/components/PropertyEditor.java Mon Jun  2 19:36:33 2008
@@ -51,6 +51,12 @@
         {
             component.setupEnvironment(property);
         }
+
+        @Override
+        public String toString()
+        {
+            return String.format("PropertyEditor.SetupEnvironment[%s]", property);
+        }
     }
 
     static class CleanupEnvironment implements ComponentAction<PropertyEditor>

Modified: tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/corelib/components/RadioGroup.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/corelib/components/RadioGroup.java?rev=662624&r1=662623&r2=662624&view=diff
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/corelib/components/RadioGroup.java (original)
+++ tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/corelib/components/RadioGroup.java Mon Jun  2 19:36:33 2008
@@ -103,6 +103,12 @@
         {
             component.setup(controlName);
         }
+
+        @Override
+        public String toString()
+        {
+            return String.format("RadioGroup.Setup[%s]", controlName);
+        }
     }
 
     private static final ComponentAction<RadioGroup> PROCESS_SUBMISSION = new ComponentAction<RadioGroup>()
@@ -113,6 +119,12 @@
         {
             component.processSubmission();
         }
+
+        @Override
+        public String toString()
+        {
+            return "RadioGroup.ProcessSubmission";
+        }
     };
 
     private void setup(String elementName)

Added: tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/corelib/components/RemoveRowLink.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/corelib/components/RemoveRowLink.java?rev=662624&view=auto
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/corelib/components/RemoveRowLink.java (added)
+++ tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/corelib/components/RemoveRowLink.java Mon Jun  2 19:36:33 2008
@@ -0,0 +1,62 @@
+// Copyright 2008 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.corelib.components;
+
+import org.apache.tapestry5.ComponentResources;
+import org.apache.tapestry5.MarkupWriter;
+import org.apache.tapestry5.RenderSupport;
+import org.apache.tapestry5.annotations.Environmental;
+import org.apache.tapestry5.annotations.SupportsInformalParameters;
+import org.apache.tapestry5.corelib.internal.AjaxFormLoopContext;
+import org.apache.tapestry5.ioc.annotations.Inject;
+
+/**
+ * Used inside a {@link org.apache.tapestry5.corelib.components.AjaxFormLoop} to remove the current row from the loop.
+ * This fires a server-side "removeRow" event (from the AjaxFormLoop component). On the client-side, the element for the
+ * row is hidden, then removed altogether.
+ */
+@SupportsInformalParameters
+public class RemoveRowLink
+{
+    @Inject
+    private ComponentResources resources;
+
+    @Environmental
+    private AjaxFormLoopContext context;
+
+    @Environmental
+    private RenderSupport renderSupport;
+
+    void beginRender(MarkupWriter writer)
+    {
+        String clientId = renderSupport.allocateClientId(resources);
+
+        writer.element("a",
+
+                       "href", "#",
+
+                       "id", clientId);
+
+        resources.renderInformalParameters(writer);
+
+        context.addRemoveRowTrigger(clientId);
+    }
+
+
+    void afterRender(MarkupWriter writer)
+    {
+        writer.end();
+    }
+}

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=662624&r1=662623&r2=662624&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 Mon Jun  2 19:36:33 2008
@@ -112,7 +112,7 @@
     /**
      * Performs input validation on the value supplied by the user in the form submission.
      */
-    @Parameter(defaultPrefix = "validate")
+    @Parameter(defaultPrefix = BindingConstants.VALIDATE)
     @SuppressWarnings("unchecked")
     private FieldValidator<Object> validate = NOOP_VALIDATOR;
 

Modified: tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/corelib/components/SubmitNotifier.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/corelib/components/SubmitNotifier.java?rev=662624&r1=662623&r2=662624&view=diff
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/corelib/components/SubmitNotifier.java (original)
+++ tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/corelib/components/SubmitNotifier.java Mon Jun  2 19:36:33 2008
@@ -41,6 +41,12 @@
         {
             component.trigger(eventType);
         }
+
+        @Override
+        public String toString()
+        {
+            return String.format("SubmitNotifier.TriggerEvent[%s]", eventType);
+        }
     }
 
 

Added: tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/corelib/internal/AjaxFormLoopContext.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/corelib/internal/AjaxFormLoopContext.java?rev=662624&view=auto
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/corelib/internal/AjaxFormLoopContext.java (added)
+++ tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/corelib/internal/AjaxFormLoopContext.java Mon Jun  2 19:36:33 2008
@@ -0,0 +1,37 @@
+// Copyright 2008 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.corelib.internal;
+
+/**
+ * Interface that allows an enclosing {@link org.apache.tapestry5.corelib.components.AjaxFormLoop} to work with enclosed
+ * components such as {@link org.apache.tapestry5.corelib.components.AddRowLink} or {@link
+ * org.apache.tapestry5.corelib.components.RemoveRowLink}.
+ */
+public interface AjaxFormLoopContext
+{
+    /**
+     * Adds a clientId to the list of client-side elements that trigger the addition of a new row.
+     *
+     * @param clientId unique id (typically via {@link org.apache.tapestry5.RenderSupport#allocateClientId(org.apache.tapestry5.ComponentResources)})
+     */
+    void addAddRowTrigger(String clientId);
+
+    /**
+     * Used by {@link org.apache.tapestry5.corelib.components.RemoveRowLink} to
+     *
+     * @param clientId
+     */
+    void addRemoveRowTrigger(String clientId);
+}

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=662624&r1=662623&r2=662624&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 Mon Jun  2 19:36:33 2008
@@ -18,6 +18,7 @@
 import org.apache.tapestry5.internal.util.Base64ObjectOutputStream;
 import org.apache.tapestry5.ioc.internal.util.Defense;
 import org.apache.tapestry5.runtime.Component;
+import org.slf4j.Logger;
 
 import java.io.IOException;
 
@@ -27,10 +28,14 @@
  */
 public class ComponentActionSink
 {
+    private final Logger logger;
+
     private final Base64ObjectOutputStream stream;
 
-    public ComponentActionSink()
+    public ComponentActionSink(Logger logger)
     {
+        this.logger = logger;
+
         try
         {
             stream = new Base64ObjectOutputStream();
@@ -48,6 +53,9 @@
 
         String completeId = castComponent.getComponentResources().getCompleteId();
 
+        if (logger.isDebugEnabled())
+            logger.debug(String.format("Storing action: %s %s", completeId, action));
+
         try
         {
             // Writing the complete id is not very efficient, but the GZip 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=662624&r1=662623&r2=662624&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 Mon Jun  2 19:36:33 2008
@@ -18,6 +18,7 @@
 import org.apache.tapestry5.internal.structure.Page;
 import org.apache.tapestry5.json.JSONObject;
 import org.apache.tapestry5.runtime.RenderCommand;
+import org.apache.tapestry5.services.PartialMarkupRendererFilter;
 
 
 /**
@@ -75,4 +76,15 @@
      * @param reply  JSONObject which will contain the partial response
      */
     void renderPartial(MarkupWriter writer, JSONObject reply);
+
+    /**
+     * Adds an optional filter to the rendering.  Optional filters are <em>temporary</em>, used just during the current
+     * partial render (as opposed to filters contributed to the {@link org.apache.tapestry5.services.PartialMarkupRenderer}
+     * service which are permanent, shared and stateless.
+     * <p/>
+     * Filters are added to the <em>end</em> of the pipeline (after all permanent contributions).
+     *
+     * @param filter to add to the pipeline
+     */
+    void addPartialFilter(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=662624&r1=662623&r2=662624&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 Mon Jun  2 19:36:33 2008
@@ -19,9 +19,13 @@
 import org.apache.tapestry5.internal.structure.Page;
 import static org.apache.tapestry5.ioc.IOCConstants.PERTHREAD_SCOPE;
 import org.apache.tapestry5.ioc.annotations.Scope;
+import org.apache.tapestry5.ioc.internal.util.CollectionFactory;
 import org.apache.tapestry5.ioc.internal.util.Defense;
+import org.apache.tapestry5.ioc.util.Stack;
 import org.apache.tapestry5.json.JSONObject;
 import org.apache.tapestry5.runtime.RenderCommand;
+import org.apache.tapestry5.services.PartialMarkupRenderer;
+import org.apache.tapestry5.services.PartialMarkupRendererFilter;
 
 /**
  * This services keeps track of the page being rendered and the root command for the partial render, it is therefore
@@ -35,6 +39,26 @@
 
     private RenderCommand rootCommand;
 
+    private final Stack<PartialMarkupRendererFilter> filters = CollectionFactory.newStack();
+
+    private static class Bridge implements PartialMarkupRenderer
+    {
+        private final PartialMarkupRendererFilter filter;
+
+        private final PartialMarkupRenderer delegate;
+
+        private Bridge(PartialMarkupRendererFilter filter, PartialMarkupRenderer delegate)
+        {
+            this.filter = filter;
+            this.delegate = delegate;
+        }
+
+        public void renderMarkup(MarkupWriter writer, JSONObject reply)
+        {
+            filter.renderMarkup(writer, reply, delegate);
+        }
+    }
+
     public void initializeForCompletePage(Page page)
     {
         this.page = page;
@@ -84,8 +108,34 @@
         queue.run(writer);
     }
 
+    public void addPartialFilter(PartialMarkupRendererFilter filter)
+    {
+        Defense.notNull(filter, "filter");
+
+        filters.push(filter);
+    }
+
     public void renderPartial(MarkupWriter writer, JSONObject reply)
     {
+        PartialMarkupRenderer terminator = new PartialMarkupRenderer()
+        {
+            public void renderMarkup(MarkupWriter writer, JSONObject reply)
+            {
+                render(writer);
+            }
+        };
+
+        PartialMarkupRenderer delegate = terminator;
+
+        while (!filters.isEmpty())
+        {
+            PartialMarkupRendererFilter filter = filters.pop();
+
+            PartialMarkupRenderer bridge = new Bridge(filter, delegate);
+
+            delegate = bridge;
+        }
+
         // The partial will quite often contain multiple elements (or just a block of plain text),
         // so those must be enclosed in a root element.
 
@@ -93,7 +143,7 @@
 
         // The initialize methods will already have been invoked.
 
-        render(writer);
+        delegate.renderMarkup(writer, reply);
 
         writer.end();
 

Added: 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=662624&view=auto
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-core/src/main/resources/org/apache/tapestry5/corelib/components/AjaxFormLoop.tml (added)
+++ tapestry/tapestry5/trunk/tapestry-core/src/main/resources/org/apache/tapestry5/corelib/components/AjaxFormLoop.tml Mon Jun  2 19:36:33 2008
@@ -0,0 +1,23 @@
+<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:formfragment>
+
+    <t:block id="tail">
+        <t:forminjector element="prop:element" t:id="rowInjector">
+            <t:delegate to="prop:addRow"/>
+            <t:block id="defaultAddRow">
+                <t:addrowlink>Add row</t:addrowlink>
+            </t:block>
+        </t:forminjector>
+
+        <t:block id="ajaxResponse">
+            <t:delegate to="beforeBody"/>
+            <t:body/>
+            <t:delegate to="afterBody"/>
+        </t:block>
+    </t:block>
+</t:container>

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=662624&r1=662623&r2=662624&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 Mon Jun  2 19:36:33 2008
@@ -91,7 +91,7 @@
 
     // Generalized initialize function for Tapestry, used to help minimize the amount of JavaScript
     // for the page by removing redundancies such as repeated Object and method names. The spec
-    // is a hash whose keys are the names of methods of the Tapestry object.
+    // is a hash whose keys are the names of methods of the Tapestry.Initializer object.
     // The value is an array of arrays.  The outer arrays represent invocations
     // of the method.  The inner array are the parameters for each invocation.
     // As an optimization, the inner value may not be an array but instead
@@ -248,6 +248,52 @@
 /** Container of functions that may be invoked by the Tapestry.init() function. */
 Tapestry.Initializer = {
 
+    ajaxFormLoop : function(spec)
+    {
+        var rowInjector = $(spec.rowInjector);
+
+        $(spec.addRowTriggers).each(function(triggerId)
+        {
+            $(triggerId).observe("click", function(event)
+            {
+                $(rowInjector).trigger();
+
+                Event.stop(event);
+            })
+        });
+    },
+
+    formLoopRemoveLink : function(spec)
+    {
+        var link = $(spec.link);
+
+        link.observe("click", function(event)
+        {
+            Event.stop(event);
+
+            var successHandler = function(transport)
+            {
+                var container = $(spec.fragment);
+
+                if (container.formFragment != undefined)
+                {
+                    container.formFragment.hideAndRemove();
+                }
+                else
+                {
+                    var effect = Tapestry.ElementEffect.fade(container);
+
+                    effect.afterFinish = function()
+                    {
+                        container.remove();
+                    };
+                }
+            }
+
+            new Ajax.Request(spec.url, { onSuccess : successHandler });
+        });
+    },
+
     /**
      * Convert a form or link into a trigger of an Ajax update that
      * updates the indicated Zone.
@@ -790,34 +836,35 @@
     }
 };
 
-// Wrappers around Prototype and Scriptaculous effects, invoked from Tapestry.Zone.show().
-// All the functions of this object should have all-lowercase names. 
+// Wrappers around Prototype and Scriptaculous effects.
+// All the functions of this object should have all-lowercase names.
+// The methods all return the Effect object they create.
 
 Tapestry.ElementEffect = {
 
     show : function(element)
     {
-        element.show();
+        return new Effect.Appear(element);
     },
 
     highlight : function(element)
     {
-        new Effect.Highlight(element);
+        return new Effect.Highlight(element);
     },
 
     slidedown : function (element)
     {
-        new Effect.SlideDown(element);
+        return new Effect.SlideDown(element);
     },
 
     slideup : function(element)
     {
-        new Effect.SlideUp(element);
+        return new Effect.SlideUp(element);
     },
 
     fade : function(element)
     {
-        new Effect.Fade(element);
+        return new Effect.Fade(element);
     }
 };
 
@@ -900,6 +947,16 @@
             this.hideFunc(this.element);
     },
 
+    hideAndRemove : function()
+    {
+        var effect = this.hideFunc(this.element);
+
+        effect.afterFinish = function()
+        {
+            this.element.remove();
+        };
+    },
+
     show : function()
     {
         if (! this.element.visible())
@@ -945,7 +1002,7 @@
                 // before or after the FormInjector's element.
 
                 var newElement = new Element(this.element.tagName, { 'class' : this.element.className });
-
+                
                 // Insert the new element before or after the existing element.
 
                 var param = { };

Modified: tapestry/tapestry5/trunk/tapestry-core/src/test/app1/FormInjectorDemo.tml
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/test/app1/FormInjectorDemo.tml?rev=662624&r1=662623&r2=662624&view=diff
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-core/src/test/app1/FormInjectorDemo.tml (original)
+++ tapestry/tapestry5/trunk/tapestry-core/src/test/app1/FormInjectorDemo.tml Mon Jun  2 19:36:33 2008
@@ -1,21 +1,18 @@
 <html t:type="Border" xmlns:t="http://tapestry.apache.org/schema/tapestry_5_0_0.xsd">
 
+    <h1>FormInjector Demo (now including AjaxFormLoop)</h1>
 
     <t:form>
 
-        <t:block id="newRow">
-
-            <div class="t-beaneditor-row">
+        <ul>
+            <li t:id="loop" t:type="AjaxFormLoop" source="doubleItems" value="item" encoder="encoder">
                 <t:submitnotifier>
-                    <t:textfield t:id="value"/>
+                    <t:textfield t:id="value" value="item.value"/>
+                    <t:removerowlink>remove</t:removerowlink>
                 </t:submitnotifier>
-            </div>
-
-        </t:block>
-
-        <ul>
-            <li t:id="forminjector" class="t-beaneditor-row">
-                <a href="#" id="addnewrow">Add a row</a>
+                <t:parameter name="addRow">
+                    <t:addrowlink>Add a row</t:addrowlink>
+                </t:parameter>
             </li>
         </ul>
 
@@ -30,5 +27,9 @@
         <span id="sum">${sum}</span>
     </p>
 
+    <h2>Data</h2>
+
+    <t:grid source="doubleItems"/>
+
 
 </html>
\ No newline at end of file

Modified: tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry5/integration/IntegrationTests.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry5/integration/IntegrationTests.java?rev=662624&r1=662623&r2=662624&view=diff
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry5/integration/IntegrationTests.java (original)
+++ tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry5/integration/IntegrationTests.java Mon Jun  2 19:36:33 2008
@@ -1873,6 +1873,14 @@
         clickAndWait(SUBMIT);
 
         assertText("sum", "5.1");
+
+        click("link=remove");
+
+        sleep(2000);
+
+        clickAndWait(SUBMIT);
+
+        assertText("sum", "0.0");
     }
 
     /**

Modified: tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry5/integration/app1/components/ActionLinkIndirect.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry5/integration/app1/components/ActionLinkIndirect.java?rev=662624&r1=662623&r2=662624&view=diff
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry5/integration/app1/components/ActionLinkIndirect.java (original)
+++ tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry5/integration/app1/components/ActionLinkIndirect.java Mon Jun  2 19:36:33 2008
@@ -14,6 +14,7 @@
 
 package org.apache.tapestry5.integration.app1.components;
 
+import org.apache.tapestry5.BindingConstants;
 import org.apache.tapestry5.MarkupWriter;
 import org.apache.tapestry5.annotations.Parameter;
 import org.apache.tapestry5.corelib.components.ActionLink;
@@ -23,7 +24,7 @@
     /**
      * The component to be rendered.
      */
-    @Parameter(required = true, defaultPrefix = "component")
+    @Parameter(required = true, defaultPrefix = BindingConstants.COMPONENT)
     private ActionLink component;
 
     Object beginRender(MarkupWriter writer)

Added: tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry5/integration/app1/data/DoubleItem.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry5/integration/app1/data/DoubleItem.java?rev=662624&view=auto
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry5/integration/app1/data/DoubleItem.java (added)
+++ tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry5/integration/app1/data/DoubleItem.java Mon Jun  2 19:36:33 2008
@@ -0,0 +1,46 @@
+// Copyright 2008 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.data;
+
+/**
+ * Used for tests involving {@link org.apache.tapestry5.corelib.components.AjaxFormLoop} (and indirectly, {@link
+ * org.apache.tapestry5.corelib.components.FormInjector}).
+ */
+public class DoubleItem
+{
+    private long id;
+
+    private double value;
+
+    public long getId()
+    {
+        return id;
+    }
+
+    public void setId(long id)
+    {
+        this.id = id;
+    }
+
+    public double getValue()
+    {
+        return value;
+    }
+
+    public void setValue(double value)
+    {
+        this.value = value;
+    }
+}

Modified: tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry5/integration/app1/pages/FormInjectorDemo.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry5/integration/app1/pages/FormInjectorDemo.java?rev=662624&r1=662623&r2=662624&view=diff
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry5/integration/app1/pages/FormInjectorDemo.java (original)
+++ tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry5/integration/app1/pages/FormInjectorDemo.java Mon Jun  2 19:36:33 2008
@@ -14,42 +14,83 @@
 
 package org.apache.tapestry5.integration.app1.pages;
 
-import org.apache.tapestry5.Block;
-import org.apache.tapestry5.RenderSupport;
-import org.apache.tapestry5.annotations.Component;
+import org.apache.tapestry5.PrimaryKeyEncoder;
+import org.apache.tapestry5.annotations.Log;
 import org.apache.tapestry5.annotations.Persist;
-import org.apache.tapestry5.corelib.components.FormInjector;
-import org.apache.tapestry5.ioc.annotations.Inject;
+import org.apache.tapestry5.annotations.Property;
+import org.apache.tapestry5.integration.app1.data.DoubleItem;
+import org.apache.tapestry5.ioc.internal.util.CollectionFactory;
+
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.atomic.AtomicLong;
 
 public class FormInjectorDemo
 {
+    @Property
+    private DoubleItem item;
+
     @Persist
+    @Property(write = false)
     private double sum;
 
-    private double value;
+    private static final Map<Long, DoubleItem> DB = CollectionFactory.newConcurrentMap();
 
-    @Inject
-    private Block newRow;
+    private static final AtomicLong ID_ALLOCATOR = new AtomicLong(System.currentTimeMillis());
 
-    @Inject
-    private RenderSupport renderSupport;
+    private static class DoubleItemComparator implements Comparator<DoubleItem>
+    {
+        public int compare(DoubleItem o1, DoubleItem o2)
+        {
+            return (int) (o1.getId() - o2.getId());
+        }
+    }
 
-    @Component
-    private FormInjector formInjector;
+    public PrimaryKeyEncoder getEncoder()
+    {
+        return new PrimaryKeyEncoder<Long, DoubleItem>()
+        {
+            public Long toKey(DoubleItem value)
+            {
+                return value.getId();
+            }
+
+            public void prepareForKeys(List<Long> keys)
+            {
+            }
+
+            public DoubleItem toValue(Long key)
+            {
+                return DB.get(key);
+            }
+        };
+    }
 
-    public double getSum()
+    @Log
+    public List<DoubleItem> getDoubleItems()
     {
-        return sum;
+        List<DoubleItem> items = CollectionFactory.newList(DB.values());
+
+        Collections.sort(items, new DoubleItemComparator());
+
+        return items;
     }
 
-    public double getValue()
+    Object onAddRow()
     {
-        return value;
+        DoubleItem item = new DoubleItem();
+        item.setId(ID_ALLOCATOR.incrementAndGet());
+
+        DB.put(item.getId(), item);
+
+        return item;
     }
 
-    public void setValue(double value)
+    void onRemoveRow(DoubleItem item)
     {
-        this.value = value;
+        DB.remove(item.getId());
     }
 
     void onPrepareForSubmit()
@@ -59,19 +100,12 @@
 
     void onAfterSubmit()
     {
-        sum += value;
+        sum += item.getValue();
     }
 
 
-    void afterRender()
-    {
-        renderSupport.addScript(
-                "$('addnewrow').observe('click', function() { $('%s').trigger(); return false; });",
-                formInjector.getClientId());
-    }
-
-    Object onActionFromFormInjector()
+    void onActionFromReset()
     {
-        return newRow;
+        DB.clear();
     }
 }

Modified: tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry5/internal/transform/components/ParameterComponent.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry5/internal/transform/components/ParameterComponent.java?rev=662624&r1=662623&r2=662624&view=diff
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry5/internal/transform/components/ParameterComponent.java (original)
+++ tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry5/internal/transform/components/ParameterComponent.java Mon Jun  2 19:36:33 2008
@@ -14,6 +14,7 @@
 
 package org.apache.tapestry5.internal.transform.components;
 
+import org.apache.tapestry5.BindingConstants;
 import org.apache.tapestry5.annotations.Parameter;
 
 /**
@@ -24,7 +25,7 @@
     @Parameter
     private String object;
 
-    @Parameter(cache = false, name = "uncached", defaultPrefix = "literal")
+    @Parameter(cache = false, name = "uncached", defaultPrefix = BindingConstants.LITERAL)
     private String uncachedObject;
 
     @Parameter(required = true)

Modified: tapestry/tapestry5/trunk/tapestry-core/src/test/resources/log4j.properties
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/test/resources/log4j.properties?rev=662624&r1=662623&r2=662624&view=diff
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-core/src/test/resources/log4j.properties (original)
+++ tapestry/tapestry5/trunk/tapestry-core/src/test/resources/log4j.properties Mon Jun  2 19:36:33 2008
@@ -23,5 +23,8 @@
 
 log4j.category.org.apache.tapestry5.integration.app1=debug
 
-
+log4j.category.org.apache.tapestry5.corelib.components.AjaxFormLoop=debug
+log4j.category.org.apache.tapestry5.corelib.components.Form=debug
+log4j.category.org.apache.tapestry5.corelib.components.FormFragment=debug
+log4j.category.org.apache.tapestry5.corelib.components.FormInjector=debug
 

Modified: tapestry/tapestry5/trunk/tapestry-upload/src/main/java/org/apache/tapestry5/upload/components/Upload.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-upload/src/main/java/org/apache/tapestry5/upload/components/Upload.java?rev=662624&r1=662623&r2=662624&view=diff
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-upload/src/main/java/org/apache/tapestry5/upload/components/Upload.java (original)
+++ tapestry/tapestry5/trunk/tapestry-upload/src/main/java/org/apache/tapestry5/upload/components/Upload.java Mon Jun  2 19:36:33 2008
@@ -47,7 +47,7 @@
      * The object that will perform input validation. The "validate:" binding prefix is generally used to provide this
      * object in a declarative fashion.
      */
-    @Parameter(defaultPrefix = "validate")
+    @Parameter(defaultPrefix = BindingConstants.VALIDATE)
     @SuppressWarnings("unchecked")
     private FieldValidator<Object> validate = NOOP_VALIDATOR;