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 <label> 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);
+ }
+
+}