Author: sabob
Date: Sat Apr 18 16:28:19 2009
New Revision: 766349
URL: http://svn.apache.org/viewvc?rev=766349&view=rev
Log:
improved ControlRegistry to support custom callbacks. CLK-524
Modified:
incubator/click/trunk/click/framework/src/org/apache/click/ClickServlet.java
incubator/click/trunk/click/framework/src/org/apache/click/ControlRegistry.java
Modified: incubator/click/trunk/click/framework/src/org/apache/click/ClickServlet.java
URL: http://svn.apache.org/viewvc/incubator/click/trunk/click/framework/src/org/apache/click/ClickServlet.java?rev=766349&r1=766348&r2=766349&view=diff
==============================================================================
--- incubator/click/trunk/click/framework/src/org/apache/click/ClickServlet.java (original)
+++ incubator/click/trunk/click/framework/src/org/apache/click/ClickServlet.java Sat Apr 18
16:28:19 2009
@@ -488,17 +488,19 @@
ErrorPage errorPage = (ErrorPage) page;
errorPage.setMode(configService.getApplicationMode());
- // Clear the control registry
- ControlRegistry.getThreadLocalRegistry().clearRegistry();
+ // Clear the POST_ON_PROCESS_EVENT control listeners from the registry
+ // Registered listeners from other phases must still be invoked
+ ControlRegistry.getThreadLocalRegistry().getEventHolder(
+ ControlRegistry.POST_ON_PROCESS_EVENT).clear();
}
boolean continueProcessing = performOnSecurityCheck(page, context);
+ ControlRegistry controlRegistry = ControlRegistry.getThreadLocalRegistry();
+
if (continueProcessing) {
performOnInit(page, context);
- ControlRegistry controlRegistry = ControlRegistry.getThreadLocalRegistry();
-
continueProcessing = performOnProcess(page, context, controlRegistry);
if (continueProcessing) {
@@ -508,6 +510,8 @@
}
}
+ controlRegistry.fireActionEvents(context, ControlRegistry.POST_ON_RENDER_EVENT);
+
performRender(page, context);
}
@@ -1477,9 +1481,10 @@
}
}
- // Fire all the registered action events
if (continueProcessing) {
- continueProcessing = controlRegistry.fireActionEvents(context);
+ // Fire registered action events for the POST_PROCSESS phase
+ continueProcessing = controlRegistry.fireActionEvents(context,
+ ControlRegistry.POST_ON_PROCESS_EVENT);
if (logger.isTraceEnabled()) {
String msg = " invoked: Control listeners : "
Modified: incubator/click/trunk/click/framework/src/org/apache/click/ControlRegistry.java
URL: http://svn.apache.org/viewvc/incubator/click/trunk/click/framework/src/org/apache/click/ControlRegistry.java?rev=766349&r1=766348&r2=766349&view=diff
==============================================================================
--- incubator/click/trunk/click/framework/src/org/apache/click/ControlRegistry.java (original)
+++ incubator/click/trunk/click/framework/src/org/apache/click/ControlRegistry.java Sat Apr
18 16:28:19 2009
@@ -24,13 +24,29 @@
import org.apache.commons.lang.Validate;
/**
- * Provides a thread local register for ActionListener events. The ClickServlet
- * will fire any ActionListeners registered after processing all the page's
- * controls.
+ * Provides a thread local register for ActionListener callbacks.
+ * <p/>
+ * <b>Please note:</b> this class is meant for component development and can
be
+ * ignored otherwise.
+ * <p/>
+ * When registering an ActionListener you can specify the callback to occur for
+ * a specific event. For example ActionListeners can be registered to fire
+ * <tt>after</tt> the <tt>onProcess</tt> or <tt>onRender</tt>
events. By default
+ * ActionListeners will be fired <tt>after</tt> the <tt>onProcess</tt>
event.
+ * <p/>
+ * The ClickServlet will notify the ControlRegistry which ActionListeners
+ * to fire. For example, after the <tt>onProcess</tt> event, the ClickServlet
+ * will notify the registry to fire ActionListeners registered for the
+ * {@link #POST_ON_PROCESS_EVENT} (this is the default event when listeners are fired).
+ * Similarly, after the <tt>onRender</tt> event, ClickServlet will notify the
+ * registry to fire ActionListeners registered for the {@link #POST_ON_RENDER_EVENT}.
+ * <p/>
+ * Out of the box ControlRegistry supports the events {@link #POST_ON_PROCESS_EVENT}
+ * (the default) and {@link #POST_ON_RENDER_EVENT}.
*
* <h4>Example Usage</h4>
- * Developers who implement their own controls, should look at the following
- * example control <tt>onProcess</tt> implementation.
+ * The following example shows how to register an ActionListener with a custom
+ * Control:
*
* <pre class="prettyprint">
* public class MyLink extends AbstractControl {
@@ -40,7 +56,7 @@
* bindRequestValue();
*
* if (isClicked()) {
- * // Register this controls listener for invocation after
+ * // Register the control listener for invocation after
* // control processing has finished
* registerActionEvent();
* }
@@ -53,8 +69,48 @@
* {@link org.apache.click.control.AbstractControl#registerActionEvent()}.
* This method registers the Control's action listener with ControlRegistry.
* The ClickServlet will subsequently invoke the registered
- * {@link ActionListener#onAction(Control)} methods after all the Page's control
- * <tt>onProcess()</tt> methods have been invoked.
+ * {@link ActionListener#onAction(Control)} method after all the Page controls
+ * <tt>onProcess()</tt> method have been invoked.
+ * <p/>
+ * On rare occasions one need to manipulate a Control's state right before it
+ * is rendered. The {@link #POST_ON_RENDER_EVENT} callback can be used for this
+ * situation. For example:
+ *
+ * <pre class="prettyprint">
+ * public class MyForm extends Form {
+ *
+ * public MyForm() {
+ * init();
+ * }
+ *
+ * public MyForm(String name) {
+ * super(name);
+ * init();
+ * }
+ *
+ * private void init() {
+ * ActionListener listener = new ActionListener() {
+ * public boolean onAction(Control source {
+ * // Add a hidden field to hold state for MyForm
+ * add(new HiddenField("my-form-name", getName() + '_' + "myform"));
+ * return true;
+ * }
+ * };
+ *
+ * ControlRegistry.registerActionEvent(this, listener, ControlRegistry.POST_ON_RENDER_EVENT);
+ * }
+ *
+ * ...
+ *
+ * } </pre>
+ *
+ * The above example fires the ActionListener <tt>after</tt> the <tt>onRender</tt>
+ * event. This ensures a HiddenField is added right before the MyForm is
+ * streamed to the browser.
+ * <p/>
+ * Registering the listener in MyForm constructor guarantees that the
+ * listener will be registered even if MyForm is subclassed because the compiler
+ * forces subclasses to invoke their super constructor.
*
* @author Bob Schellink
* @author Malcolm Edgar
@@ -66,103 +122,164 @@
/** The thread local registry holder. */
private static final ThreadLocal THREAD_LOCAL_REGISTRY = new ThreadLocal();
+ /**
+ * Indicates the listener should fire <tt>AFTER</tt> the onProcess event.
+ * The <tt>POST_ON_PROCESS_EVENT</tt> is the event during which control
+ * listeners will fire.
+ */
+ public static final int POST_ON_PROCESS_EVENT = 1;
+
+ /**
+ * Indicates the listener should fire <tt>AFTER</tt> the onRender event.
+ * Listeners fired in the <tt>POST_ON_RENDER_EVENT</tT> are
+ * <tt>guaranteed</tt> to trigger, even when redirecting, forwarding or if
+ * page processing is cancelled.
+ */
+ public static final int POST_ON_RENDER_EVENT = 2;
+
// -------------------------------------------------------------- Variables
- /** The list of registered event sources. */
- private List eventSourceList;
+ /** The POST_PROCESS events holder. */
+ private EventHolder postProcessEventHolder;
- /** The list of registered event listeners. */
- private List eventListenerList;
+ /** The POST_RENDER events holder. */
+ private EventHolder postRenderEventHolder;
// --------------------------------------------------------- Public Methods
/**
* Register the event source and event ActionListener to be fired by the
* ClickServlet once all the controls have been processed.
+ * <p/>
+ * Listeners registered by this method will be fired in the
+ * {@link #POST_ON_PROCESS_EVENT}.
+ *
+ * @see #registerActionEvent(org.apache.click.Control, org.apache.click.ActionListener,
int)
*
* @param source the action event source
* @param listener the event action listener
*/
public static void registerActionEvent(Control source, ActionListener listener) {
+ registerActionEvent(source, listener, POST_ON_PROCESS_EVENT);
+ }
+
+ /**
+ * Register the event source and event ActionListener to be fired by the
+ * ClickServlet in the specified event.
+ *
+ * @param source the action event source
+ * @param listener the event action listener
+ * @param event the specific event to trigger the action event
+ */
+ public static void registerActionEvent(Control source,
+ ActionListener listener, int event) {
+
Validate.notNull(source, "Null source parameter");
Validate.notNull(listener, "Null listener parameter");
ControlRegistry instance = getThreadLocalRegistry();
- List eventSourceList = (List) instance.getEventSourceList();
- List eventListenerList = (List) instance.getEventListenerList();
- eventSourceList.add(source);
- eventListenerList.add(listener);
+ EventHolder eventHolder = instance.getEventHolder(event);
+ eventHolder.registerActionEvent(source, listener);
}
// ------------------------------------------------ Package Private Methods
/**
- * Checks if any Action Events have been registered.
+ * Fire the actions for the given listener list and event source list which
+ * return true if the page should continue processing.
+ * <p/>
+ * This method will be passed the listener list and event source list
+ * of a specific event e.g. {@link #POST_ON_PROCESS_EVENT} or
+ * {@link #POST_ON_RENDER_EVENT}.
+ * event.
+ * <p/>
+ * This method can be overridden if you need to customize the way events
+ * are fired.
+ *
+ * @param context the request context
+ * @param eventSourceList the list of source controls
+ * @param eventListenerList the list of listeners to fire
+ *
+ * @return true if the page should continue processing or false otherwise
*/
- boolean hasActionEvents() {
- if (eventListenerList == null || eventListenerList.isEmpty()) {
- return false;
+ boolean fireActionEvents(Context context, List eventSourceList,
+ List eventListenerList) {
+
+ boolean continueProcessing = true;
+
+ for (int i = 0, size = eventSourceList.size(); i < size; i++) {
+ Control source = (Control) eventSourceList.get(i);
+ ActionListener listener = (ActionListener) eventListenerList.get(i);
+
+ if (!listener.onAction(source)) {
+ continueProcessing = false;
+ }
}
- return true;
+
+ return continueProcessing;
}
/**
- * Return the list of event listeners.
+ * Fire all the registered action events for the specified event and return
+ * true if the page should continue processing.
+ *
+ * @param context the request context
+ * @param event the specific event which events to fire
*
- * @return list of event listeners
+ * @return true if the page should continue processing or false otherwise
*/
- List getEventListenerList() {
- if (eventListenerList == null) {
- eventListenerList = new ArrayList();
- }
- return eventListenerList;
+ boolean fireActionEvents(Context context, int event) {
+ EventHolder eventHolder = getEventHolder(event);
+ return eventHolder.fireActionEvents(context);
}
/**
- * Return the list of event sources.
+ * Return the EventHolder for the specified event.
+ *
+ * @param event the event which EventHolder to retrieve
*
- * @return list of event sources
+ * @return the EventHolder for the specified event
*/
- List getEventSourceList() {
- if (eventSourceList == null) {
- eventSourceList = new ArrayList();
+ EventHolder getEventHolder(int event) {
+ if (event == POST_ON_RENDER_EVENT) {
+ return getPostRenderEventHolder();
+ } else if (event == POST_ON_PROCESS_EVENT) {
+ return getPostProcessEventHolder();
+ } else {
+ return null;
}
- return eventSourceList;
}
/**
- * Fire all the registered action events and return true if the page should
- * continue processing.
+ * Return the {@link #POST_ON_PROCESS_EVENT} {@link EventHolder}.
*
- * @return true if the page should continue processing or false otherwise
+ * @return the {@link #POST_ON_PROCESS_EVENT} {@link EventHolder}
*/
- boolean fireActionEvents(Context context) {
- boolean continueProcessing = true;
-
- if (!hasActionEvents()) {
- return true;
+ EventHolder getPostProcessEventHolder() {
+ if (postProcessEventHolder == null) {
+ postProcessEventHolder = new EventHolder();
}
+ return postProcessEventHolder;
+ }
- for (int i = 0, size = eventSourceList.size(); i < size; i++) {
- Control source = (Control) eventSourceList.get(i);
- ActionListener listener = (ActionListener) eventListenerList.get(i);
-
- if (!listener.onAction(source)) {
- continueProcessing = false;
- }
+ /**
+ * Return the {@link #POST_ON_RENDER_EVENT} {@link EventHolder}.
+ *
+ * @return the {@link #POST_ON_RENDER_EVENT} {@link EventHolder}
+ */
+ EventHolder getPostRenderEventHolder() {
+ if (postRenderEventHolder == null) {
+ postRenderEventHolder = new EventHolder();
}
-
- return continueProcessing;
+ return postRenderEventHolder;
}
/**
* Clear the registry.
*/
void clearRegistry() {
- if (hasActionEvents()) {
- eventListenerList.clear();
- eventSourceList.clear();
- }
+ getPostProcessEventHolder().clear();
+ getPostRenderEventHolder().clear();
}
/**
@@ -279,4 +396,92 @@
return (ControlRegistry) get(length - 1);
}
}
+
+ /**
+ * Holds the list of listeners and event sources.
+ */
+ class EventHolder {
+
+ /** The list of registered event sources. */
+ private List eventSourceList;
+
+ /** The list of registered event listeners. */
+ private List eventListenerList;
+
+ /**
+ * Register the event source and event ActionListener to be fired in the
+ * specified event.
+ *
+ * @param source the action event source
+ * @param listener the event action listener
+ * @param event the specific event to trigger the action event
+ */
+ void registerActionEvent(Control source, ActionListener listener) {
+ Validate.notNull(source, "Null source parameter");
+ Validate.notNull(listener, "Null listener parameter");
+
+ getEventSourceList().add(source);
+ getEventListenerList().add(listener);
+ }
+
+ /**
+ * Checks if any Action Events have been registered.
+ */
+ boolean hasActionEvents() {
+ if (eventListenerList == null || eventListenerList.isEmpty()) {
+ return false;
+ }
+ return true;
+ }
+
+ /**
+ * Return the list of event listeners.
+ *
+ * @return list of event listeners
+ */
+ List getEventListenerList() {
+ if (eventListenerList == null) {
+ eventListenerList = new ArrayList();
+ }
+ return eventListenerList;
+ }
+
+ /**
+ * Return the list of event sources.
+ *
+ * @return list of event sources
+ */
+ List getEventSourceList() {
+ if (eventSourceList == null) {
+ eventSourceList = new ArrayList();
+ }
+ return eventSourceList;
+ }
+
+ /**
+ * Clear the events.
+ */
+ void clear() {
+ if (hasActionEvents()) {
+ getEventSourceList().clear();
+ getEventListenerList().clear();
+ }
+ }
+
+ /**
+ * Fire all the registered action events and return true if the page should
+ * continue processing.
+ *
+ * @return true if the page should continue processing or false otherwise
+ */
+ boolean fireActionEvents(Context context) {
+
+ if (!hasActionEvents()) {
+ return true;
+ }
+
+ return ControlRegistry.this.fireActionEvents(context,
+ getEventSourceList(), getEventListenerList());
+ }
+ }
}
|