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 2006/11/18 00:42:15 UTC

svn commit: r476369 [1/2] - in /tapestry/tapestry5/tapestry-core/trunk/src: main/java/org/apache/tapestry/ main/java/org/apache/tapestry/corelib/components/ main/java/org/apache/tapestry/corelib/mixins/ main/java/org/apache/tapestry/internal/bindings/ ...

Author: hlship
Date: Fri Nov 17 15:42:14 2006
New Revision: 476369

URL: http://svn.apache.org/viewvc?view=rev&rev=476369
Log:
Add component: binding prefix.
Add a Label component.
Add getLabel() method to the Field interface.
Add optional label parameter to AbstractField; a default value can be generated from the component's id.

Added:
    tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/corelib/components/Label.java
    tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/bindings/ComponentBinding.java
    tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/bindings/ComponentBindingFactory.java
Modified:
    tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/Field.java
    tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/corelib/components/AbstractField.java
    tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/corelib/mixins/RenderInformals.java
    tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/structure/ComponentPageElementImpl.java
    tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/util/InternalUtils.java
    tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/services/TapestryModule.java
    tapestry/tapestry5/tapestry-core/trunk/src/site/apt/guide/parameters.apt
    tapestry/tapestry5/tapestry-core/trunk/src/test/java/org/apache/tapestry/integration/IntegrationTests.java
    tapestry/tapestry5/tapestry-core/trunk/src/test/java/org/apache/tapestry/integration/app1/pages/Countdown.java
    tapestry/tapestry5/tapestry-core/trunk/src/test/java/org/apache/tapestry/internal/util/InternalUtilsTest.java
    tapestry/tapestry5/tapestry-core/trunk/src/test/resources/org/apache/tapestry/integration/app1/pages/Countdown.html
    tapestry/tapestry5/tapestry-core/trunk/src/test/resources/org/apache/tapestry/integration/app1/pages/SimpleForm.html

Modified: tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/Field.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/Field.java?view=diff&rev=476369&r1=476368&r2=476369
==============================================================================
--- tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/Field.java (original)
+++ tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/Field.java Fri Nov 17 15:42:14 2006
@@ -12,23 +12,32 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package org.apache.tapestry;
-
-/**
- * Defines a field within a form.
- */
-public interface Field
-{
-    /**
-     * Returns the value used as the name attribute of the rendered element. This value will be
-     * unique within an enclosing form, even if the same component renders multiple times.
-     */
-    String getElementName();
-
-    /**
-     * Returns a unique id for the element. This value will be unique for any given rendering of a
-     * page. This value is intended for use as the id attribute of the client-side element, and will
-     * be used with any DHTML/Ajax related JavaScript.
-     */
-    String getClientId();
-}
+package org.apache.tapestry;
+
+/**
+ * Defines a field within a form.
+ */
+public interface Field
+{
+    /**
+     * Returns the value used as the name attribute of the rendered element. This value will be
+     * unique within an enclosing form, even if the same component renders multiple times.
+     */
+    String getElementName();
+
+    /**
+     * Returns a unique id for the element. This value will be unique for any given rendering of a
+     * page. This value is intended for use as the id attribute of the client-side element, and will
+     * be used with any DHTML/Ajax related JavaScript.
+     */
+    String getClientId();
+
+    /**
+     * Returns a user presentable (localized) label for the field, which may be used inside
+     * <label> elements on the client, and inside client or server-side validation error
+     * messages.
+     * 
+     * @return the label
+     */
+    String getLabel();
+}

Modified: tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/corelib/components/AbstractField.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/corelib/components/AbstractField.java?view=diff&rev=476369&r1=476368&r2=476369
==============================================================================
--- tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/corelib/components/AbstractField.java (original)
+++ tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/corelib/components/AbstractField.java Fri Nov 17 15:42:14 2006
@@ -12,122 +12,140 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package org.apache.tapestry.corelib.components;
-
-import java.io.Serializable;
-
-import org.apache.tapestry.ComponentAction;
-import org.apache.tapestry.ComponentResources;
-import org.apache.tapestry.Field;
-import org.apache.tapestry.annotations.ComponentClass;
-import org.apache.tapestry.annotations.Environmental;
-import org.apache.tapestry.annotations.Inject;
-import org.apache.tapestry.annotations.Mixin;
-import org.apache.tapestry.annotations.SetupRender;
-import org.apache.tapestry.corelib.mixins.RenderInformals;
-import org.apache.tapestry.services.FormSupport;
-import org.apache.tapestry.services.PageRenderSupport;
-import org.apache.tapestry.services.WebRequest;
-
-/**
- * Provides initialization of the clientId and elementName properties. In addition, adds the
- * {@link RenderInformals} mixin.
- */
-@ComponentClass
-public abstract class AbstractField implements Field
-{
-    @SuppressWarnings("unused")
-    @Mixin
-    private RenderInformals _renderInformals;
-
-    static class SetupAction implements ComponentAction<AbstractField>, Serializable
-    {
-        private static final long serialVersionUID = 2690270808212097020L;
-
-        private final String _elementName;
-
-        public SetupAction(final String elementName)
-        {
-            _elementName = elementName;
-        }
-
-        public void execute(AbstractField component)
-        {
-            component.setupElementName(_elementName);
-        }
-    }
-
-    static class ProcessSubmissionAction implements ComponentAction<AbstractField>, Serializable
-    {
-        private static final long serialVersionUID = -4346426414137434418L;
-
-        public void execute(AbstractField component)
-        {
-            component.processSubmission();
-        }
-    }
-
-    /** Used a shared instance for all types of fields, for efficiency. */
-    private static final ProcessSubmissionAction PROCESS_SUBMISSION_ACTION = new ProcessSubmissionAction();
-
-    private String _clientId;
-
-    private String _elementName;
-
-    @Environmental
-    private FormSupport _formSupport;
-
-    @Environmental
-    private PageRenderSupport _pageRenderSupport;
-
-    @Inject
-    private ComponentResources _resources;
-
-    @Inject("infrastructure:request")
-    private WebRequest _request;
-
-    @SetupRender
-    final void setup()
-    {
-        String id = _resources.getId();
-
-        // Often, these will end up as the same value. There are many exceptions, including a form
-        // that renders inside a loop.
-
-        _clientId = _pageRenderSupport.allocateClientId(id);
-        String elementName = _formSupport.allocateElementName(id);
-
-        _formSupport.storeAndExecute(this, new SetupAction(elementName));
-        _formSupport.store(this, PROCESS_SUBMISSION_ACTION);
-    }
-
-    public final String getClientId()
-    {
-        return _clientId;
-    }
-
-    public final String getElementName()
-    {
-        return _elementName;
-    }
-
-    private void setupElementName(String elementName)
-    {
-        _elementName = elementName;
-    }
-
-    private final void processSubmission()
-    {
-        processSubmission(_request);
-    }
-
-    /**
-     * Method implemented by subclasses to actually do the work of processing the submission of the
-     * form. The element's elementName property will already have been set.
-     * 
-     * @param request
-     *            the current request, presumably so that the component may extract a quert
-     *            parameter value that matches the component's element name.
-     */
-    protected abstract void processSubmission(WebRequest request);
-}
+package org.apache.tapestry.corelib.components;
+
+import java.io.Serializable;
+
+import org.apache.tapestry.ComponentAction;
+import org.apache.tapestry.ComponentResources;
+import org.apache.tapestry.Field;
+import org.apache.tapestry.annotations.ComponentClass;
+import org.apache.tapestry.annotations.Environmental;
+import org.apache.tapestry.annotations.Inject;
+import org.apache.tapestry.annotations.Mixin;
+import org.apache.tapestry.annotations.Parameter;
+import org.apache.tapestry.annotations.SetupRender;
+import org.apache.tapestry.corelib.mixins.RenderInformals;
+import org.apache.tapestry.internal.util.InternalUtils;
+import org.apache.tapestry.services.FormSupport;
+import org.apache.tapestry.services.PageRenderSupport;
+import org.apache.tapestry.services.WebRequest;
+
+/**
+ * Provides initialization of the clientId and elementName properties. In addition, adds the
+ * {@link RenderInformals} mixin.
+ */
+@ComponentClass
+public abstract class AbstractField implements Field
+{
+    /**
+     * The user presentable label for the field. If not provided, a reasonable label is generated
+     * from the component's id.
+     */
+    @Parameter
+    private String _label;
+
+    @SuppressWarnings("unused")
+    @Mixin
+    private RenderInformals _renderInformals;
+
+    static class SetupAction implements ComponentAction<AbstractField>, Serializable
+    {
+        private static final long serialVersionUID = 2690270808212097020L;
+
+        private final String _elementName;
+
+        public SetupAction(final String elementName)
+        {
+            _elementName = elementName;
+        }
+
+        public void execute(AbstractField component)
+        {
+            component.setupElementName(_elementName);
+        }
+    }
+
+    static class ProcessSubmissionAction implements ComponentAction<AbstractField>, Serializable
+    {
+        private static final long serialVersionUID = -4346426414137434418L;
+
+        public void execute(AbstractField component)
+        {
+            component.processSubmission();
+        }
+    }
+
+    /** Used a shared instance for all types of fields, for efficiency. */
+    private static final ProcessSubmissionAction PROCESS_SUBMISSION_ACTION = new ProcessSubmissionAction();
+
+    private String _clientId;
+
+    private String _elementName;
+
+    @Environmental
+    private FormSupport _formSupport;
+
+    @Environmental
+    private PageRenderSupport _pageRenderSupport;
+
+    @Inject
+    private ComponentResources _resources;
+
+    @Inject("infrastructure:request")
+    private WebRequest _request;
+
+    @SetupRender
+    final void setup()
+    {
+        String id = _resources.getId();
+
+        // Often, these will end up as the same value. There are many exceptions, including a form
+        // that renders inside a loop.
+
+        _clientId = _pageRenderSupport.allocateClientId(id);
+        String elementName = _formSupport.allocateElementName(id);
+
+        _formSupport.storeAndExecute(this, new SetupAction(elementName));
+        _formSupport.store(this, PROCESS_SUBMISSION_ACTION);
+    }
+
+    public final String getClientId()
+    {
+        return _clientId;
+    }
+
+    public final String getElementName()
+    {
+        return _elementName;
+    }
+
+    private void setupElementName(String elementName)
+    {
+        _elementName = elementName;
+    }
+
+    private final void processSubmission()
+    {
+        processSubmission(_request);
+    }
+
+    /**
+     * Method implemented by subclasses to actually do the work of processing the submission of the
+     * form. The element's elementName property will already have been set.
+     * 
+     * @param request
+     *            the current request, presumably so that the component may extract a quert
+     *            parameter value that matches the component's element name.
+     */
+    protected abstract void processSubmission(WebRequest request);
+
+    public String getLabel()
+    {
+        if (_label != null)
+            return _label;
+
+        return InternalUtils.toUserPresentable(_resources.getId());
+
+    }
+}

Added: tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/corelib/components/Label.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/corelib/components/Label.java?view=auto&rev=476369
==============================================================================
--- tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/corelib/components/Label.java (added)
+++ tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/corelib/components/Label.java Fri Nov 17 15:42:14 2006
@@ -0,0 +1,83 @@
+// Copyright 2006 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.tapestry.corelib.components;
+
+import org.apache.tapestry.Field;
+import org.apache.tapestry.MarkupWriter;
+import org.apache.tapestry.annotations.AfterRender;
+import org.apache.tapestry.annotations.BeforeRenderBody;
+import org.apache.tapestry.annotations.BeginRender;
+import org.apache.tapestry.annotations.ComponentClass;
+import org.apache.tapestry.annotations.Environmental;
+import org.apache.tapestry.annotations.Parameter;
+import org.apache.tapestry.corelib.mixins.RenderInformals;
+import org.apache.tapestry.dom.Element;
+import org.apache.tapestry.services.Heartbeat;
+
+/** Generates a &lt;label&gt; element for a particular field. */
+@ComponentClass
+public class Label extends RenderInformals
+{
+    @Parameter(required = true)
+    private Field _field;
+
+    @Environmental
+    private Heartbeat _heartbeat;
+
+    @BeginRender
+    void begin(MarkupWriter writer)
+    {
+        final Field field = _field;
+        final Element element = writer.element("label");
+
+        // Since we don't know if the field has rendered yet, we need to defer writing the for
+        // attribute until
+        // we know the field has rendered (and set its clientId property). That's exactly what
+        // Heartbeat
+        // is for.
+
+        Runnable command = new Runnable()
+        {
+            public void run()
+            {
+                String fieldId = field.getClientId();
+
+                // TODO: What if "for" was an informal parameter? We need to override that here,
+                // which involves a change
+                // to the Element API.
+
+                element.attribute("for", fieldId);
+            }
+        };
+
+        _heartbeat.defer(command);
+    }
+
+    @BeforeRenderBody
+    boolean renderBody()
+    {
+        // Don't render the body of the component even if there is one.
+
+        return false;
+    }
+
+    @AfterRender
+    void after(MarkupWriter writer)
+    {
+        writer.write(_field.getLabel());
+
+        writer.end(); // label
+    }
+}

Modified: tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/corelib/mixins/RenderInformals.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/corelib/mixins/RenderInformals.java?view=diff&rev=476369&r1=476368&r2=476369
==============================================================================
--- tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/corelib/mixins/RenderInformals.java (original)
+++ tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/corelib/mixins/RenderInformals.java Fri Nov 17 15:42:14 2006
@@ -34,6 +34,8 @@
  * If you want this behavior, but need to render more than a single tag, then implement render phase
  * methods for the {@link BeforeRenderTemplate} and {@link AfterRenderTemplate} phases. Use those
  * phases to write the additional elements and close them.
+ * <p>
+ * This is often used as a base class, for cases where a component doesn't have other mixins.
  */
 @ComponentClass
 @SupportsInformalParameters

Added: tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/bindings/ComponentBinding.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/bindings/ComponentBinding.java?view=auto&rev=476369
==============================================================================
--- tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/bindings/ComponentBinding.java (added)
+++ tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/bindings/ComponentBinding.java Fri Nov 17 15:42:14 2006
@@ -0,0 +1,53 @@
+// Copyright 2006 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.tapestry.internal.bindings;
+
+import org.apache.tapestry.ComponentResources;
+import org.apache.tapestry.Location;
+
+public class ComponentBinding extends AbstractBinding
+{
+    private final String _description;
+
+    private final ComponentResources _resources;
+
+    private final String _componentId;
+
+    public ComponentBinding(String description, ComponentResources resources, String componentId,
+            Location location)
+    {
+        super(location);
+
+        _description = description;
+        _resources = resources;
+        _componentId = componentId;
+    }
+
+    public Object get()
+    {
+        return _resources.getEmbeddedComponent(_componentId);
+    }
+
+    public Class getBindingType()
+    {
+        return get().getClass();
+    }
+
+    @Override
+    public String toString()
+    {
+        return String.format("ComponentResources[%s %s]", _description, _componentId);
+    }
+}

Added: tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/bindings/ComponentBindingFactory.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/bindings/ComponentBindingFactory.java?view=auto&rev=476369
==============================================================================
--- tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/bindings/ComponentBindingFactory.java (added)
+++ tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/bindings/ComponentBindingFactory.java Fri Nov 17 15:42:14 2006
@@ -0,0 +1,31 @@
+// Copyright 2006 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.tapestry.internal.bindings;
+
+import org.apache.tapestry.Binding;
+import org.apache.tapestry.ComponentResources;
+import org.apache.tapestry.Location;
+import org.apache.tapestry.services.BindingFactory;
+
+/** The "component:" binding prefix, which allows access to a child component via its id. */
+public class ComponentBindingFactory implements BindingFactory
+{
+    public Binding newBinding(String description, ComponentResources component, String expression,
+            Location location)
+    {
+        return new ComponentBinding(description, component, expression, location);
+    }
+
+}

Modified: tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/structure/ComponentPageElementImpl.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/structure/ComponentPageElementImpl.java?view=diff&rev=476369&r1=476368&r2=476369
==============================================================================
--- tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/structure/ComponentPageElementImpl.java (original)
+++ tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/structure/ComponentPageElementImpl.java Fri Nov 17 15:42:14 2006
@@ -12,850 +12,910 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package org.apache.tapestry.internal.structure;
-
-import static org.apache.tapestry.util.CollectionFactory.newList;
-import static org.apache.tapestry.util.CollectionFactory.newMap;
-
-import java.util.Iterator;
-import java.util.List;
-import java.util.Map;
-
-import org.apache.commons.logging.Log;
-import org.apache.tapestry.BaseLocatable;
-import org.apache.tapestry.Binding;
-import org.apache.tapestry.ComponentEventHandler;
-import org.apache.tapestry.Link;
-import org.apache.tapestry.Location;
-import org.apache.tapestry.MarkupWriter;
-import org.apache.tapestry.internal.InternalComponentResources;
-import org.apache.tapestry.internal.TapestryException;
-import org.apache.tapestry.internal.services.ComponentEventImpl;
-import org.apache.tapestry.internal.services.Instantiator;
-import org.apache.tapestry.internal.util.InternalUtils;
-import org.apache.tapestry.ioc.IOCUtilities;
-import org.apache.tapestry.ioc.services.TypeCoercer;
-import org.apache.tapestry.model.ComponentModel;
-import org.apache.tapestry.model.ParameterModel;
-import org.apache.tapestry.runtime.Component;
-import org.apache.tapestry.runtime.ComponentEvent;
-import org.apache.tapestry.runtime.LifecycleEvent;
-import org.apache.tapestry.runtime.PageLifecycleListener;
-import org.apache.tapestry.runtime.RenderCommand;
-import org.apache.tapestry.runtime.RenderQueue;
-
-/**
- * Implements {@link org.apache.tapestry.internal.structure.PageElement} and
- * {@link org.apache.tapestry.internal.InternalComponentResources}, and represents a component
- * within an overall page. Much of a component page element's behavior is delegated to user code,
- * via a {@link org.apache.tapestry.runtime.Component} instance.
- * <p>
- * Once instantiated, a ComponentPageElementImpl should be registered as a
- * {@link org.apache.tapestry.internal.structure.Page}. This could be done inside the constructors,
- * but that tends to complicate unit tests, so its done by
- * {@link org.apache.tapestry.internal.services.PageElementFactoryImpl}.
- * <p>
- */
-public class ComponentPageElementImpl extends BaseLocatable implements ComponentPageElement,
-        PageLifecycleListener
-{
-    private static final ComponentCallback CONTAINING_PAGE_DID_ATTACH = new ComponentCallback()
-    {
-        public void run(Component component)
-        {
-            component.containingPageDidAttach();
-        }
-    };
-
-    private static final ComponentCallback CONTAINING_PAGE_DID_DETACH = new ComponentCallback()
-    {
-        public void run(Component component)
-        {
-            component.containingPageDidDetach();
-        }
-    };
-
-    private static final ComponentCallback CONTAINING_PAGE_DID_LOAD = new ComponentCallback()
-    {
-        public void run(Component component)
-        {
-            component.containingPageDidLoad();
-        }
-    };
-
-    private static final ComponentCallback POST_RENDER_CLEANUP = new ComponentCallback()
-    {
-        public void run(Component component)
-        {
-            component.postRenderCleanup();
-        }
-    };
-
-    // For the momement, every component will have a template, even if it consists of
-    // just a page element to queue up a BeforeRenderBody phase.
-
-    private final RenderCommand _afterRender = new RenderCommand()
-    {
-        public void render(final MarkupWriter writer, RenderQueue queue)
-        {
-            final LifecycleEvent<Boolean> event = newEvent(false);
-
-            ComponentCallback callback = new ComponentCallback()
-            {
-                public void run(Component component)
-                {
-                    component.afterRender(writer, event);
-                }
-            };
-
-            invoke(true, callback);
-
-            if (event.getResult())
-                queue.push(_preBeginRender);
-        }
-    };
-
-    private final RenderCommand _afterRenderBody = new RenderCommand()
-    {
-        public void render(final MarkupWriter writer, RenderQueue queue)
-        {
-            final LifecycleEvent<Boolean> event = newEvent(false);
-
-            ComponentCallback callback = new ComponentCallback()
-            {
-                public void run(Component component)
-                {
-                    component.afterRenderBody(writer, event);
-                }
-            };
-
-            invoke(true, callback);
-
-            if (event.getResult())
-                queue.push(_beforeRenderBody);
-        }
-    };
-
-    private final RenderCommand _afterRenderTemplate = new RenderCommand()
-    {
-        public void render(final MarkupWriter writer, final RenderQueue queue)
-        {
-            final LifecycleEvent<Boolean> event = newEvent(false);
-
-            ComponentCallback callback = new ComponentCallback()
-            {
-                public void run(Component component)
-                {
-                    component.afterRenderTemplate(writer, event);
-                }
-            };
-
-            invoke(true, callback);
-
-            if (event.getResult())
-                queue.push(_beforeRenderTemplate);
-        }
-    };
-
-    private final RenderCommand _beforeRenderBody = new RenderCommand()
-    {
-        public void render(final MarkupWriter writer, RenderQueue queue)
-        {
-            final LifecycleEvent<Boolean> event = newEvent(true);
-
-            ComponentCallback callback = new ComponentCallback()
-            {
-                public void run(Component component)
-                {
-                    component.beforeRenderBody(writer, event);
-                }
-            };
-
-            invoke(false, callback);
-
-            queue.push(_afterRenderBody);
-
-            if (event.getResult())
-                pushElements(queue, _body);
-        }
-    };
-
-    private final RenderCommand _beforeRenderTemplate = new RenderCommand()
-    {
-        public void render(final MarkupWriter writer, final RenderQueue queue)
-        {
-            final LifecycleEvent<Boolean> event = newEvent(true);
-
-            ComponentCallback callback = new ComponentCallback()
-            {
-                public void run(Component component)
-                {
-                    component.beforeRenderTemplate(writer, event);
-                }
-            };
-
-            invoke(false, callback);
-
-            queue.push(_afterRenderTemplate);
-
-            if (event.getResult())
-                pushElements(queue, _template);
-        }
-    };
-
-    private final RenderCommand _preBeginRender = new RenderCommand()
-    {
-        public void render(final MarkupWriter writer, final RenderQueue queue)
-        {
-            final LifecycleEvent<Boolean> event = newEvent(true);
-
-            ComponentCallback callback = new ComponentCallback()
-            {
-                public void run(Component component)
-                {
-                    component.preBeginRender(writer, event);
-                }
-            };
-
-            invoke(false, callback);
-
-            if (event.getResult())
-                queue.push(_beginRender);
-        }
-    };
-
-    private final RenderCommand _beginRender = new RenderCommand()
-    {
-        public void render(final MarkupWriter writer, final RenderQueue queue)
-        {
-            final LifecycleEvent<Boolean> event = newEvent(true);
-
-            ComponentCallback callback = new ComponentCallback()
-            {
-                public void run(Component component)
-                {
-                    component.beginRender(writer, event);
-                }
-            };
-
-            invoke(false, callback);
-
-            if (event.getResult())
-                queue.push(_postBeginRender);
-        }
-    };
-
-    private final RenderCommand _postBeginRender = new RenderCommand()
-    {
-        public void render(final MarkupWriter writer, final RenderQueue queue)
-        {
-            final LifecycleEvent<Boolean> event = newEvent(true);
-
-            ComponentCallback callback = new ComponentCallback()
-            {
-                public void run(Component component)
-                {
-                    component.postBeginRender(writer, event);
-                }
-            };
-
-            invoke(false, callback);
-
-            // If the component has no template whatsoever, then a
-            // renderBody element is added as the lone element of the component's template.
-            // So every component will have a non-empty template.
-
-            if (event.getResult())
-            {
-                queue.push(_afterRender);
-                queue.push(_beforeRenderTemplate);
-            }
-        }
-    };
-
-    private List<PageElement> _body;
-
-    private Map<String, ComponentPageElement> _children;
-
-    private final RenderCommand _cleanupRender = new RenderCommand()
-    {
-        public void render(final MarkupWriter writer, RenderQueue queue)
-        {
-            final LifecycleEvent<Boolean> event = newEvent(false);
-
-            ComponentCallback callback = new ComponentCallback()
-            {
-                public void run(Component component)
-                {
-                    component.cleanupRender(writer, event);
-                }
-            };
-
-            invoke(true, callback);
-
-            if (event.getResult())
-            {
-                queue.push(_setupRender);
-            }
-            else
-            {
-                _rendering = false;
-
-                invoke(false, POST_RENDER_CLEANUP);
-
-                // NOW and only now the component is done rendering and fully cleaned up. Decrement
-                // the page's dirty count. If the entire render goes well, then the page will be
-                // clean and can be stored into the pool for later reuse.
-
-                _page.decrementDirtyCount();
-            }
-
-        }
-    };
-
-    private final String _completeId;
-
-    // The user-provided class, with runtime code enhancements.
-    private final Component _component;
-
-    /**
-     * Component lifecycle instances for all mixins; the core component is added to this list during
-     * page load. This is only used in the case that a component has mixins (in which case, the core
-     * component is listed last).
-     */
-    private List<Component> _components = null;
-
-    private final ComponentPageElement _container;
-
-    private final InternalComponentResources _coreResources;
-
-    private final String _id;
-
-    private boolean _loaded;
-
-    private final Log _log;
-
-    /** Map from mixin name to resources for the mixin. Created when first mixin is added. */
-    private Map<String, InternalComponentResources> _mixinsByShortName;
-
-    private final String _nestedId;
-
-    private final Page _page;
-
-    private boolean _rendering;
-
-    private final RenderCommand _setupRender = new RenderCommand()
-    {
-        public void render(final MarkupWriter writer, RenderQueue queue)
-        {
-            _rendering = true;
-
-            final LifecycleEvent<Boolean> event = newEvent(true);
-
-            ComponentCallback callback = new ComponentCallback()
-            {
-                public void run(Component component)
-                {
-                    component.setupRender(writer, event);
-                }
-            };
-
-            invoke(false, callback);
-
-            queue.push(_cleanupRender);
-
-            if (event.getResult())
-                queue.push(_preBeginRender);
-        }
-
-        @Override
-        public String toString()
-        {
-            return String.format("SetupRender[%s]", _completeId);
-        }
-    };
-
-    private final List<PageElement> _template = newList();
-
-    private final TypeCoercer _typeCoercer;
-
-    /**
-     * Constructor for other components embedded within the root component or at deeper levels of
-     * the hierarchy.
-     * 
-     * @param page
-     *            ultimately containing this component
-     * @param container
-     *            component immediately containing this component (may be null for a root component)
-     * @param id
-     *            unique (within the container) id for this component (may be null for a root
-     *            component)
-     * @param instantiator
-     *            used to create the new component instance and access the component's model
-     * @param typeCoercer
-     *            used when coercing parameter values
-     * @param location
-     *            location of the element (within a template), used as part of exception reporting
-     */
-
-    public ComponentPageElementImpl(Page page, ComponentPageElement container, String id,
-            Instantiator instantiator, TypeCoercer typeCoercer, Location location)
-    {
-        super(location);
-
-        ComponentModel model = instantiator.getModel();
-
-        _container = container;
-        _id = id;
-        _page = page;
-        _typeCoercer = typeCoercer;
-        _log = model.getLog();
-
-        _coreResources = new InternalComponentResourcesImpl(this, instantiator, _typeCoercer);
-
-        _component = _coreResources.getComponent();
-
-        String componentClassName = _component.getClass().getName();
-
-        // A page (really, the root component of a page) does not have a container.
-
-        if (container == null)
-        {
-            _completeId = componentClassName;
-            _nestedId = null;
-        }
-        else
-        {
-            String parentNestedId = container.getNestedId();
-
-            // The root element has no nested id.
-            // The children of the root element have an id.
-
-            if (parentNestedId == null)
-            {
-                _nestedId = id;
-                _completeId = _page.getName() + ":" + id;
-            }
-            else
-            {
-                _nestedId = parentNestedId + "." + id;
-                _completeId = container.getCompleteId() + "." + id;
-            }
-        }
-    }
-
-    /**
-     * Constructor for the root component of a page.
-     */
-    public ComponentPageElementImpl(Page page, Instantiator instantiator, TypeCoercer typeCoercer)
-    {
-        this(page, null, null, instantiator, typeCoercer, null);
-    }
-
-    public void addEmbeddedElement(ComponentPageElement child)
-    {
-        if (_children == null)
-            _children = newMap();
-
-        String childId = child.getId();
-
-        // TODO: Check for conflicts!
-
-        _children.put(childId, child);
-    }
-
-    public void addMixin(Instantiator instantiator)
-    {
-        if (_mixinsByShortName == null)
-        {
-            _mixinsByShortName = newMap();
-            _components = newList();
-        }
-
-        String mixinClassName = instantiator.getModel().getComponentClassName();
-        String mixinName = IOCUtilities.toSimpleId(mixinClassName);
-
-        InternalComponentResourcesImpl resources = new InternalComponentResourcesImpl(this,
-                instantiator, _typeCoercer);
-
-        // TODO: Check for name collision?
-
-        _mixinsByShortName.put(mixinName, resources);
-
-        _components.add(resources.getComponent());
-    }
-
-    public void addParameter(String parameterName, Binding binding)
-    {
-        // Maybe should use colon here? Depends on what works best in the template,
-        // don't want to lock this out as just
-        int dotx = parameterName.lastIndexOf('.');
-
-        if (dotx > 0)
-        {
-            String mixinName = parameterName.substring(0, dotx);
-            InternalComponentResources mixinResources = InternalUtils.get(
-                    _mixinsByShortName,
-                    mixinName);
-
-            if (mixinResources == null)
-                throw new TapestryException(StructureMessages.missingMixinForParameter(
-                        _completeId,
-                        mixinName,
-                        parameterName), binding, null);
-
-            String simpleName = parameterName.substring(dotx + 1);
-
-            // TODO: If its informal? Ignore it unless the mixin supports informals? Warn otherwise?
-            // We'll have to see.
-
-            mixinResources.addParameter(simpleName, binding);
-        }
-
-        InternalComponentResources informalParameterResources = null;
-
-        // Does it match a formal parameter name of the core component? That takes precedence
-
-        if (_coreResources.getComponentModel().getParameterModel(parameterName) != null)
-        {
-            _coreResources.addParameter(parameterName, binding);
-            return;
-        }
-
-        for (String mixinName : InternalUtils.sortedKeys(_mixinsByShortName))
-        {
-            InternalComponentResources resources = _mixinsByShortName.get(mixinName);
-            if (resources.getComponentModel().getParameterModel(parameterName) != null)
-            {
-                resources.addParameter(parameterName, binding);
-                return;
-            }
-
-            if (informalParameterResources == null
-                    && resources.getComponentModel().getSupportsInformalParameters())
-                informalParameterResources = resources;
-        }
-
-        // An informal parameter
-
-        if (informalParameterResources == null
-                && _coreResources.getComponentModel().getSupportsInformalParameters())
-            informalParameterResources = _coreResources;
-
-        // For the moment, informal parameters accumulate in the core component's resources, but
-        // that will likely change.
-
-        if (informalParameterResources != null)
-            informalParameterResources.addParameter(parameterName, binding);
-    }
-
-    public void addToBody(PageElement element)
-    {
-        if (_body == null)
-            _body = newList();
-
-        _body.add(element);
-    }
-
-    public void addToTemplate(PageElement element)
-    {
-        _template.add(element);
-    }
-
-    public void containingPageDidAttach()
-    {
-        invoke(false, CONTAINING_PAGE_DID_ATTACH);
-    }
-
-    public void containingPageDidDetach()
-    {
-        invoke(false, CONTAINING_PAGE_DID_DETACH);
-    }
-
-    public void containingPageDidLoad()
-    {
-        verifyRequiredParametersAreBound();
-
-        _loaded = true;
-
-        // If this component has mixins, add the core component to the end of the list, after the
-        // mixins.
-
-        if (_components != null)
-            _components.add(_component);
-
-        invoke(false, CONTAINING_PAGE_DID_LOAD);
-    }
-
-    /**
-     * Delegates to the
-     * {@link Page#createActionLink(ComponentPageElement, String, boolean, Object[]) the containing page}.
-     * Why the extra layer? Because
-     */
-    public Link createActionLink(String action, boolean forForm, Object... context)
-    {
-        return _page.createActionLink(this, action, forForm, context);
-    }
-
-    public void enqueueBeforeRenderBody(RenderQueue queue)
-    {
-        // If no body, then no beforeRenderBody or afterRenderBody
-
-        if (_body != null)
-            queue.push(_beforeRenderBody);
-    }
-
-    public String getCompleteId()
-    {
-        return _completeId;
-    }
-
-    public Component getComponent()
-    {
-        return _component;
-    }
-
-    public InternalComponentResources getComponentResources()
-    {
-        return _coreResources;
-    }
-
-    public ComponentPageElement getContainer()
-    {
-        return _container;
-    }
-
-    public Page getContainingPage()
-    {
-        return _page;
-    }
-
-    public Component getEmbeddedComponent(String embeddedId)
-    {
-        return getEmbeddedElement(embeddedId).getComponent();
-    }
-
-    public ComponentPageElement getEmbeddedElement(String embeddedId)
-    {
-        ComponentPageElement embeddedElement = InternalUtils.get(_children, embeddedId);
-
-        if (embeddedElement == null)
-            throw new IllegalArgumentException(StructureMessages.noSuchComponent(this, embeddedId));
-
-        return embeddedElement;
-    }
-
-    public Object getFieldChange(String fieldName)
-    {
-        return _page.getFieldChange(this, fieldName);
-    }
-
-    public String getId()
-    {
-        return _id;
-    }
-
-    public Log getLog()
-    {
-        return _coreResources.getLog();
-    }
-
-    public Component getMixinByClassName(String mixinClassName)
-    {
-        Component result = null;
-
-        if (_mixinsByShortName != null)
-        {
-            for (InternalComponentResources resources : _mixinsByShortName.values())
-            {
-                if (resources.getComponentModel().getComponentClassName().equals(mixinClassName))
-                {
-                    result = resources.getComponent();
-                    break;
-                }
-            }
-        }
-
-        if (result == null)
-            throw new IllegalArgumentException(StructureMessages.unknownMixin(
-                    _completeId,
-                    mixinClassName));
-
-        return result;
-    }
-
-    public String getNestedId()
-    {
-        return _nestedId;
-    }
-
-    public Component getPage()
-    {
-        return _page.getRootElement().getComponent();
-    }
-
-    public boolean handleEvent(ComponentEvent event)
-    {
-        // Simple case: no mixins
-
-        if (_components == null)
-            return _component.handleComponentEvent(event);
-
-        // Otherwise, iterate over mixins + core component
-
-        boolean result = false;
-
-        for (Component component : _components)
-        {
-            result |= component.handleComponentEvent(event);
-
-            if (event.isAborted())
-                break;
-        }
-
-        return result;
-    }
-
-    public boolean hasFieldChange(String fieldName)
-    {
-        return getFieldChange(fieldName) != null;
-    }
-
-    public boolean isLoaded()
-    {
-        return _loaded;
-    }
-
-    public boolean isRendering()
-    {
-        return _rendering;
-    }
-
-    public void persistFieldChange(String fieldName, Object newValue)
-    {
-        // While loading the page (i.e., when setting field defaults), ignore these
-        // changes.
-
-        if (_loaded)
-            _page.persistFieldChange(this, fieldName, newValue);
-    }
-
-    /** Pushes the SetupRender phase state onto the queue. */
-    public final void render(MarkupWriter writer, RenderQueue queue)
-    {
-        // Once we start rendering, the page is considered dirty, until we cleanup post render.
-
-        _page.incrementDirtyCount();
-
-        queue.push(_setupRender);
-    }
-
-    @Override
-    public String toString()
-    {
-        return String.format("ComponentPageElement[%s]", _completeId);
-    }
-
-    public boolean triggerEvent(String eventType, Object[] context, ComponentEventHandler handler)
-    {
-        boolean result = false;
-
-        ComponentEvent event = new ComponentEventImpl(eventType, _id, context, handler,
-                _typeCoercer);
-
-        ComponentPageElement component = this;
-
-        while (component != null)
-        {
-            result |= component.handleEvent(event);
-
-            if (event.isAborted())
-                return result;
-
-            component = component.getContainer();
-        }
-
-        return result;
-    }
-
-    private void addUnboundParameterNames(String prefix, List<String> unbound,
-            InternalComponentResources resource)
-    {
-        ComponentModel model = resource.getComponentModel();
-
-        for (String name : model.getParameterNames())
-        {
-            if (resource.isBound(name))
-                continue;
-
-            ParameterModel parameterModel = model.getParameterModel(name);
-
-            if (parameterModel.isRequired())
-            {
-                String fullName = prefix == null ? name : prefix + "." + name;
-
-                unbound.add(fullName);
-            }
-        }
-    }
-
-    /**
-     * Invokes a callback on the component instances (the core component plus any mixins).
-     * 
-     * @param reverse
-     *            if true, the callbacks are in the reverse of the normal order (this is associated
-     *            with AfterXXX phases)
-     * @param callback
-     *            the object to receive each component instance
-     */
-    private void invoke(boolean reverse, ComponentCallback callback)
-    {
-        // Optimization: In the most general case (just the one component, no mixins)
-        // invoke the callback on the component and be done ... no iterators, no nothing.
-
-        if (_components == null)
-        {
-            callback.run(_component);
-            return;
-        }
-
-        Iterator<Component> i = reverse ? InternalUtils.reverseIterator(_components) : _components
-                .iterator();
-
-        while (i.hasNext())
-            callback.run(i.next());
-    }
-
-    private LifecycleEvent<Boolean> newEvent(boolean defaultValue)
-    {
-        return new LifecycleEvent<Boolean>(_log, Boolean.class, defaultValue);
-    }
-
-    private static void pushElements(RenderQueue queue, List<PageElement> list)
-    {
-        int count = size(list);
-        for (int i = count - 1; i >= 0; i--)
-            queue.push(list.get(i));
-    }
-
-    private static int size(List<?> list)
-    {
-        return list == null ? 0 : list.size();
-    }
-
-    private void verifyRequiredParametersAreBound()
-    {
-        List<String> unbound = newList();
-
-        addUnboundParameterNames(null, unbound, _coreResources);
-
-        for (String name : InternalUtils.sortedKeys(_mixinsByShortName))
-            addUnboundParameterNames(name, unbound, _mixinsByShortName.get(name));
-
-        if (unbound.isEmpty())
-            return;
-
-        throw new TapestryException(StructureMessages.missingParameters(unbound, this), this, null);
-    }
-
-}
+package org.apache.tapestry.internal.structure;
+
+import static org.apache.tapestry.util.CollectionFactory.newList;
+import static org.apache.tapestry.util.CollectionFactory.newMap;
+
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.commons.logging.Log;
+import org.apache.tapestry.BaseLocatable;
+import org.apache.tapestry.Binding;
+import org.apache.tapestry.ComponentEventHandler;
+import org.apache.tapestry.Link;
+import org.apache.tapestry.Location;
+import org.apache.tapestry.MarkupWriter;
+import org.apache.tapestry.internal.InternalComponentResources;
+import org.apache.tapestry.internal.TapestryException;
+import org.apache.tapestry.internal.services.ComponentEventImpl;
+import org.apache.tapestry.internal.services.Instantiator;
+import org.apache.tapestry.internal.util.InternalUtils;
+import org.apache.tapestry.ioc.IOCUtilities;
+import org.apache.tapestry.ioc.services.TypeCoercer;
+import org.apache.tapestry.model.ComponentModel;
+import org.apache.tapestry.model.ParameterModel;
+import org.apache.tapestry.runtime.Component;
+import org.apache.tapestry.runtime.ComponentEvent;
+import org.apache.tapestry.runtime.LifecycleEvent;
+import org.apache.tapestry.runtime.PageLifecycleListener;
+import org.apache.tapestry.runtime.RenderCommand;
+import org.apache.tapestry.runtime.RenderQueue;
+
+/**
+ * Implements {@link org.apache.tapestry.internal.structure.PageElement} and
+ * {@link org.apache.tapestry.internal.InternalComponentResources}, and represents a component
+ * within an overall page. Much of a component page element's behavior is delegated to user code,
+ * via a {@link org.apache.tapestry.runtime.Component} instance.
+ * <p>
+ * Once instantiated, a ComponentPageElementImpl should be registered as a
+ * {@link org.apache.tapestry.internal.structure.Page}. This could be done inside the constructors,
+ * but that tends to complicate unit tests, so its done by
+ * {@link org.apache.tapestry.internal.services.PageElementFactoryImpl}.
+ * <p>
+ */
+public class ComponentPageElementImpl extends BaseLocatable implements ComponentPageElement,
+        PageLifecycleListener
+{
+    private static final ComponentCallback CONTAINING_PAGE_DID_ATTACH = new ComponentCallback()
+    {
+        public void run(Component component)
+        {
+            component.containingPageDidAttach();
+        }
+    };
+
+    private static final ComponentCallback CONTAINING_PAGE_DID_DETACH = new ComponentCallback()
+    {
+        public void run(Component component)
+        {
+            component.containingPageDidDetach();
+        }
+    };
+
+    private static final ComponentCallback CONTAINING_PAGE_DID_LOAD = new ComponentCallback()
+    {
+        public void run(Component component)
+        {
+            component.containingPageDidLoad();
+        }
+    };
+
+    private static final ComponentCallback POST_RENDER_CLEANUP = new ComponentCallback()
+    {
+        public void run(Component component)
+        {
+            component.postRenderCleanup();
+        }
+    };
+
+    // For the momement, every component will have a template, even if it consists of
+    // just a page element to queue up a BeforeRenderBody phase.
+
+    private static void pushElements(RenderQueue queue, List<PageElement> list)
+    {
+        int count = size(list);
+        for (int i = count - 1; i >= 0; i--)
+            queue.push(list.get(i));
+    }
+
+    private static int size(List<?> list)
+    {
+        return list == null ? 0 : list.size();
+    }
+
+    private final RenderCommand _afterRender = new RenderCommand()
+    {
+        public void render(final MarkupWriter writer, RenderQueue queue)
+        
+        {
+            final LifecycleEvent<Boolean> event = newEvent(false);
+
+            ComponentCallback callback = new ComponentCallback()
+            {
+                public void run(Component component)
+                {
+                    component.afterRender(writer, event);
+                }
+            };
+
+            invoke(true, callback);
+
+            if (event.getResult())
+                queue.push(_preBeginRender);
+        }
+
+        @Override
+        public String toString()
+        {
+            return phaseToString("AfterRender");
+        }
+    };
+
+    private final RenderCommand _afterRenderBody = new RenderCommand()
+    {
+        public void render(final MarkupWriter writer, RenderQueue queue)
+        {
+            final LifecycleEvent<Boolean> event = newEvent(false);
+
+            ComponentCallback callback = new ComponentCallback()
+            {
+                public void run(Component component)
+                {
+                    component.afterRenderBody(writer, event);
+                }
+            };
+
+            invoke(true, callback);
+
+            if (event.getResult())
+                queue.push(_beforeRenderBody);
+        }
+
+        @Override
+        public String toString()
+        {
+            return phaseToString("AfterRenderBody");
+        }
+    };
+
+    private final RenderCommand _afterRenderTemplate = new RenderCommand()
+    {
+        public void render(final MarkupWriter writer, final RenderQueue queue)
+        {
+            final LifecycleEvent<Boolean> event = newEvent(false);
+
+            ComponentCallback callback = new ComponentCallback()
+            {
+                public void run(Component component)
+                {
+                    component.afterRenderTemplate(writer, event);
+                }
+            };
+
+            invoke(true, callback);
+
+            if (event.getResult())
+                queue.push(_beforeRenderTemplate);
+        }
+
+        @Override
+        public String toString()
+        {
+            return phaseToString("AfterRenderTemplate");
+        }
+    };
+
+    private final RenderCommand _beforeRenderBody = new RenderCommand()
+    {
+        public void render(final MarkupWriter writer, RenderQueue queue)
+        {
+            final LifecycleEvent<Boolean> event = newEvent(true);
+
+            ComponentCallback callback = new ComponentCallback()
+            {
+                public void run(Component component)
+                {
+                    component.beforeRenderBody(writer, event);
+                }
+            };
+
+            invoke(false, callback);
+
+            queue.push(_afterRenderBody);
+
+            if (event.getResult())
+                pushElements(queue, _body);
+        }
+
+        @Override
+        public String toString()
+        {
+            return phaseToString("BeforeRenderBody");
+        }
+    };
+
+    private final RenderCommand _beforeRenderTemplate = new RenderCommand()
+    {
+        public void render(final MarkupWriter writer, final RenderQueue queue)
+        {
+            final LifecycleEvent<Boolean> event = newEvent(true);
+
+            ComponentCallback callback = new ComponentCallback()
+            {
+                public void run(Component component)
+                {
+                    component.beforeRenderTemplate(writer, event);
+                }
+            };
+
+            invoke(false, callback);
+
+            queue.push(_afterRenderTemplate);
+
+            if (event.getResult())
+                pushElements(queue, _template);
+        }
+
+        @Override
+        public String toString()
+        {
+            return phaseToString("BeforeRenderTemplate");
+        }
+    };
+
+    private final RenderCommand _beginRender = new RenderCommand()
+    {
+        public void render(final MarkupWriter writer, final RenderQueue queue)
+        {
+            final LifecycleEvent<Boolean> event = newEvent(true);
+
+            ComponentCallback callback = new ComponentCallback()
+            {
+                public void run(Component component)
+                {
+                    component.beginRender(writer, event);
+                }
+            };
+
+            invoke(false, callback);
+
+            if (event.getResult())
+                queue.push(_postBeginRender);
+        }
+
+        @Override
+        public String toString()
+        {
+            return phaseToString("BeginRender");
+        }
+    };
+
+    private List<PageElement> _body;
+
+    private Map<String, ComponentPageElement> _children;
+
+    private final RenderCommand _cleanupRender = new RenderCommand()
+    {
+        public void render(final MarkupWriter writer, RenderQueue queue)
+        {
+            final LifecycleEvent<Boolean> event = newEvent(false);
+
+            ComponentCallback callback = new ComponentCallback()
+            {
+                public void run(Component component)
+                {
+                    component.cleanupRender(writer, event);
+                }
+            };
+
+            invoke(true, callback);
+
+            if (event.getResult())
+            {
+                queue.push(_setupRender);
+            }
+            else
+            {
+                _rendering = false;
+
+                invoke(false, POST_RENDER_CLEANUP);
+
+                // NOW and only now the component is done rendering and fully cleaned up. Decrement
+                // the page's dirty count. If the entire render goes well, then the page will be
+                // clean and can be stored into the pool for later reuse.
+
+                _page.decrementDirtyCount();
+            }
+
+        }
+
+        @Override
+        public String toString()
+        {
+            return phaseToString("CleanupRender");
+        }
+    };
+
+    private final String _completeId;
+
+    // The user-provided class, with runtime code enhancements.
+    private final Component _component;
+
+    /**
+     * Component lifecycle instances for all mixins; the core component is added to this list during
+     * page load. This is only used in the case that a component has mixins (in which case, the core
+     * component is listed last).
+     */
+    private List<Component> _components = null;
+
+    private final ComponentPageElement _container;
+
+    private final InternalComponentResources _coreResources;
+
+    private final String _id;
+
+    private boolean _loaded;
+
+    private final Log _log;
+
+    /** Map from mixin name to resources for the mixin. Created when first mixin is added. */
+    private Map<String, InternalComponentResources> _mixinsByShortName;
+
+    private final String _nestedId;
+
+    private final Page _page;
+
+    private final RenderCommand _postBeginRender = new RenderCommand()
+    {
+        public void render(final MarkupWriter writer, final RenderQueue queue)
+        {
+            final LifecycleEvent<Boolean> event = newEvent(true);
+
+            ComponentCallback callback = new ComponentCallback()
+            {
+                public void run(Component component)
+                {
+                    component.postBeginRender(writer, event);
+                }
+            };
+
+            invoke(false, callback);
+
+            // If the component has no template whatsoever, then a
+            // renderBody element is added as the lone element of the component's template.
+            // So every component will have a non-empty template.
+
+            if (event.getResult())
+            {
+                queue.push(_afterRender);
+                queue.push(_beforeRenderTemplate);
+            }
+        }
+
+        @Override
+        public String toString()
+        {
+            return phaseToString("PostBeginRender");
+        }
+    };
+
+    private final RenderCommand _preBeginRender = new RenderCommand()
+    {
+        public void render(final MarkupWriter writer, final RenderQueue queue)
+        {
+            final LifecycleEvent<Boolean> event = newEvent(true);
+
+            ComponentCallback callback = new ComponentCallback()
+            {
+                public void run(Component component)
+                {
+                    component.preBeginRender(writer, event);
+                }
+            };
+
+            invoke(false, callback);
+
+            if (event.getResult())
+                queue.push(_beginRender);
+        }
+
+        @Override
+        public String toString()
+        {
+            return phaseToString("PreBeginRender");
+        }
+    };
+
+    private boolean _rendering;
+
+    private final RenderCommand _setupRender = new RenderCommand()
+    {
+        public void render(final MarkupWriter writer, RenderQueue queue)
+        {
+            _rendering = true;
+
+            final LifecycleEvent<Boolean> event = newEvent(true);
+
+            ComponentCallback callback = new ComponentCallback()
+            {
+                public void run(Component component)
+                {
+                    component.setupRender(writer, event);
+                }
+            };
+
+            invoke(false, callback);
+
+            queue.push(_cleanupRender);
+
+            if (event.getResult())
+                queue.push(_preBeginRender);
+        }
+
+        @Override
+        public String toString()
+        {
+            return phaseToString("SetupRender");
+        }
+    };
+
+    private final List<PageElement> _template = newList();
+
+    private final TypeCoercer _typeCoercer;
+
+    /**
+     * Constructor for other components embedded within the root component or at deeper levels of
+     * the hierarchy.
+     * 
+     * @param page
+     *            ultimately containing this component
+     * @param container
+     *            component immediately containing this component (may be null for a root component)
+     * @param id
+     *            unique (within the container) id for this component (may be null for a root
+     *            component)
+     * @param instantiator
+     *            used to create the new component instance and access the component's model
+     * @param typeCoercer
+     *            used when coercing parameter values
+     * @param location
+     *            location of the element (within a template), used as part of exception reporting
+     */
+
+    public ComponentPageElementImpl(Page page, ComponentPageElement container, String id,
+            Instantiator instantiator, TypeCoercer typeCoercer, Location location)
+    {
+        super(location);
+
+        ComponentModel model = instantiator.getModel();
+
+        _container = container;
+        _id = id;
+        _page = page;
+        _typeCoercer = typeCoercer;
+        _log = model.getLog();
+
+        _coreResources = new InternalComponentResourcesImpl(this, instantiator, _typeCoercer);
+
+        _component = _coreResources.getComponent();
+
+        String componentClassName = _component.getClass().getName();
+
+        // A page (really, the root component of a page) does not have a container.
+
+        if (container == null)
+        {
+            _completeId = componentClassName;
+            _nestedId = null;
+        }
+        else
+        {
+            String parentNestedId = container.getNestedId();
+
+            // The root element has no nested id.
+            // The children of the root element have an id.
+
+            if (parentNestedId == null)
+            {
+                _nestedId = id;
+                _completeId = _page.getName() + ":" + id;
+            }
+            else
+            {
+                _nestedId = parentNestedId + "." + id;
+                _completeId = container.getCompleteId() + "." + id;
+            }
+        }
+    }
+
+    /**
+     * Constructor for the root component of a page.
+     */
+    public ComponentPageElementImpl(Page page, Instantiator instantiator, TypeCoercer typeCoercer)
+    {
+        this(page, null, null, instantiator, typeCoercer, null);
+    }
+
+    public void addEmbeddedElement(ComponentPageElement child)
+    {
+        if (_children == null)
+            _children = newMap();
+
+        String childId = child.getId();
+
+        // TODO: Check for conflicts!
+
+        _children.put(childId, child);
+    }
+
+    public void addMixin(Instantiator instantiator)
+    {
+        if (_mixinsByShortName == null)
+        {
+            _mixinsByShortName = newMap();
+            _components = newList();
+        }
+
+        String mixinClassName = instantiator.getModel().getComponentClassName();
+        String mixinName = IOCUtilities.toSimpleId(mixinClassName);
+
+        InternalComponentResourcesImpl resources = new InternalComponentResourcesImpl(this,
+                instantiator, _typeCoercer);
+
+        // TODO: Check for name collision?
+
+        _mixinsByShortName.put(mixinName, resources);
+
+        _components.add(resources.getComponent());
+    }
+
+    public void addParameter(String parameterName, Binding binding)
+    {
+        // Maybe should use colon here? Depends on what works best in the template,
+        // don't want to lock this out as just
+        int dotx = parameterName.lastIndexOf('.');
+
+        if (dotx > 0)
+        {
+            String mixinName = parameterName.substring(0, dotx);
+            InternalComponentResources mixinResources = InternalUtils.get(
+                    _mixinsByShortName,
+                    mixinName);
+
+            if (mixinResources == null)
+                throw new TapestryException(StructureMessages.missingMixinForParameter(
+                        _completeId,
+                        mixinName,
+                        parameterName), binding, null);
+
+            String simpleName = parameterName.substring(dotx + 1);
+
+            // TODO: If its informal? Ignore it unless the mixin supports informals? Warn otherwise?
+            // We'll have to see.
+
+            mixinResources.addParameter(simpleName, binding);
+        }
+
+        InternalComponentResources informalParameterResources = null;
+
+        // Does it match a formal parameter name of the core component? That takes precedence
+
+        if (_coreResources.getComponentModel().getParameterModel(parameterName) != null)
+        {
+            _coreResources.addParameter(parameterName, binding);
+            return;
+        }
+
+        for (String mixinName : InternalUtils.sortedKeys(_mixinsByShortName))
+        {
+            InternalComponentResources resources = _mixinsByShortName.get(mixinName);
+            if (resources.getComponentModel().getParameterModel(parameterName) != null)
+            {
+                resources.addParameter(parameterName, binding);
+                return;
+            }
+
+            if (informalParameterResources == null
+                    && resources.getComponentModel().getSupportsInformalParameters())
+                informalParameterResources = resources;
+        }
+
+        // An informal parameter
+
+        if (informalParameterResources == null
+                && _coreResources.getComponentModel().getSupportsInformalParameters())
+            informalParameterResources = _coreResources;
+
+        // For the moment, informal parameters accumulate in the core component's resources, but
+        // that will likely change.
+
+        if (informalParameterResources != null)
+            informalParameterResources.addParameter(parameterName, binding);
+    }
+
+    public void addToBody(PageElement element)
+    {
+        if (_body == null)
+            _body = newList();
+
+        _body.add(element);
+    }
+
+    public void addToTemplate(PageElement element)
+    {
+        _template.add(element);
+    }
+
+    private void addUnboundParameterNames(String prefix, List<String> unbound,
+            InternalComponentResources resource)
+    {
+        ComponentModel model = resource.getComponentModel();
+
+        for (String name : model.getParameterNames())
+        {
+            if (resource.isBound(name))
+                continue;
+
+            ParameterModel parameterModel = model.getParameterModel(name);
+
+            if (parameterModel.isRequired())
+            {
+                String fullName = prefix == null ? name : prefix + "." + name;
+
+                unbound.add(fullName);
+            }
+        }
+    }
+
+    public void containingPageDidAttach()
+    {
+        invoke(false, CONTAINING_PAGE_DID_ATTACH);
+    }
+
+    public void containingPageDidDetach()
+    {
+        invoke(false, CONTAINING_PAGE_DID_DETACH);
+    }
+
+    public void containingPageDidLoad()
+    {
+        verifyRequiredParametersAreBound();
+
+        _loaded = true;
+
+        // If this component has mixins, add the core component to the end of the list, after the
+        // mixins.
+
+        if (_components != null)
+            _components.add(_component);
+
+        invoke(false, CONTAINING_PAGE_DID_LOAD);
+    }
+
+    /**
+     * Delegates to the
+     * {@link Page#createActionLink(ComponentPageElement, String, boolean, Object[]) the containing page}.
+     * Why the extra layer? Because
+     */
+    public Link createActionLink(String action, boolean forForm, Object... context)
+    {
+        return _page.createActionLink(this, action, forForm, context);
+    }
+
+    public void enqueueBeforeRenderBody(RenderQueue queue)
+    {
+        // If no body, then no beforeRenderBody or afterRenderBody
+
+        if (_body != null)
+            queue.push(_beforeRenderBody);
+    }
+
+    public String getCompleteId()
+    {
+        return _completeId;
+    }
+
+    public Component getComponent()
+    {
+        return _component;
+    }
+
+    public InternalComponentResources getComponentResources()
+    {
+        return _coreResources;
+    }
+
+    public ComponentPageElement getContainer()
+    {
+        return _container;
+    }
+
+    public Page getContainingPage()
+    {
+        return _page;
+    }
+
+    public Component getEmbeddedComponent(String embeddedId)
+    {
+        return getEmbeddedElement(embeddedId).getComponent();
+    }
+
+    public ComponentPageElement getEmbeddedElement(String embeddedId)
+    {
+        ComponentPageElement embeddedElement = InternalUtils.get(_children, embeddedId);
+
+        if (embeddedElement == null)
+            throw new IllegalArgumentException(StructureMessages.noSuchComponent(this, embeddedId));
+
+        return embeddedElement;
+    }
+
+    public Object getFieldChange(String fieldName)
+    {
+        return _page.getFieldChange(this, fieldName);
+    }
+
+    public String getId()
+    {
+        return _id;
+    }
+
+    public Log getLog()
+    {
+        return _coreResources.getLog();
+    }
+
+    public Component getMixinByClassName(String mixinClassName)
+    {
+        Component result = null;
+
+        if (_mixinsByShortName != null)
+        {
+            for (InternalComponentResources resources : _mixinsByShortName.values())
+            {
+                if (resources.getComponentModel().getComponentClassName().equals(mixinClassName))
+                {
+                    result = resources.getComponent();
+                    break;
+                }
+            }
+        }
+
+        if (result == null)
+            throw new IllegalArgumentException(StructureMessages.unknownMixin(
+                    _completeId,
+                    mixinClassName));
+
+        return result;
+    }
+
+    public String getNestedId()
+    {
+        return _nestedId;
+    }
+
+    public Component getPage()
+    {
+        return _page.getRootElement().getComponent();
+    }
+
+    public boolean handleEvent(ComponentEvent event)
+    {
+        // Simple case: no mixins
+
+        if (_components == null)
+            return _component.handleComponentEvent(event);
+
+        // Otherwise, iterate over mixins + core component
+
+        boolean result = false;
+
+        for (Component component : _components)
+        {
+            result |= component.handleComponentEvent(event);
+
+            if (event.isAborted())
+                break;
+        }
+
+        return result;
+    }
+
+    public boolean hasFieldChange(String fieldName)
+    {
+        return getFieldChange(fieldName) != null;
+    }
+
+    /**
+     * Invokes a callback on the component instances (the core component plus any mixins).
+     * 
+     * @param reverse
+     *            if true, the callbacks are in the reverse of the normal order (this is associated
+     *            with AfterXXX phases)
+     * @param callback
+     *            the object to receive each component instance
+     */
+    private void invoke(boolean reverse, ComponentCallback callback)
+    {
+        // Optimization: In the most general case (just the one component, no mixins)
+        // invoke the callback on the component and be done ... no iterators, no nothing.
+
+        if (_components == null)
+        {
+            callback.run(_component);
+            return;
+        }
+
+        Iterator<Component> i = reverse ? InternalUtils.reverseIterator(_components) : _components
+                .iterator();
+
+        while (i.hasNext())
+            callback.run(i.next());
+    }
+
+    public boolean isLoaded()
+    {
+        return _loaded;
+    }
+
+    public boolean isRendering()
+    {
+        return _rendering;
+    }
+
+    private LifecycleEvent<Boolean> newEvent(boolean defaultValue)
+    {
+        return new LifecycleEvent<Boolean>(_log, Boolean.class, defaultValue);
+    }
+
+    public void persistFieldChange(String fieldName, Object newValue)
+    {
+        // While loading the page (i.e., when setting field defaults), ignore these
+        // changes.
+
+        if (_loaded)
+            _page.persistFieldChange(this, fieldName, newValue);
+    }
+
+    private String phaseToString(String phaseName)
+    {
+        return String.format("%s[%s]", phaseName, _completeId);
+    }
+
+    /** Pushes the SetupRender phase state onto the queue. */
+    public final void render(MarkupWriter writer, RenderQueue queue)
+    {
+        // Once we start rendering, the page is considered dirty, until we cleanup post render.
+
+        _page.incrementDirtyCount();
+
+        queue.push(_setupRender);
+    }
+
+    @Override
+    public String toString()
+    {
+        return String.format("ComponentPageElement[%s]", _completeId);
+    }
+
+    public boolean triggerEvent(String eventType, Object[] context, ComponentEventHandler handler)
+    {
+        boolean result = false;
+
+        ComponentEvent event = new ComponentEventImpl(eventType, _id, context, handler,
+                _typeCoercer);
+
+        ComponentPageElement component = this;
+
+        while (component != null)
+        {
+            result |= component.handleEvent(event);
+
+            if (event.isAborted())
+                return result;
+
+            component = component.getContainer();
+        }
+
+        return result;
+    }
+
+    private void verifyRequiredParametersAreBound()
+    {
+        List<String> unbound = newList();
+
+        addUnboundParameterNames(null, unbound, _coreResources);
+
+        for (String name : InternalUtils.sortedKeys(_mixinsByShortName))
+            addUnboundParameterNames(name, unbound, _mixinsByShortName.get(name));
+
+        if (unbound.isEmpty())
+            return;
+
+        throw new TapestryException(StructureMessages.missingParameters(unbound, this), this, null);
+    }
+
+}