001 // Copyright 2004, 2005 The Apache Software Foundation
002 //
003 // Licensed under the Apache License, Version 2.0 (the "License");
004 // you may not use this file except in compliance with the License.
005 // You may obtain a copy of the License at
006 //
007 // http://www.apache.org/licenses/LICENSE-2.0
008 //
009 // Unless required by applicable law or agreed to in writing, software
010 // distributed under the License is distributed on an "AS IS" BASIS,
011 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
012 // See the License for the specific language governing permissions and
013 // limitations under the License.
014 package org.apache.tapestry.internal.event.impl;
015
016 import edu.emory.mathcs.backport.java.util.concurrent.ConcurrentHashMap;
017 import org.apache.hivemind.util.Defense;
018 import org.apache.tapestry.*;
019 import org.apache.tapestry.event.BrowserEvent;
020 import org.apache.tapestry.event.ResetEventListener;
021 import org.apache.tapestry.form.FormSupport;
022 import org.apache.tapestry.internal.event.ComponentEventProperty;
023 import org.apache.tapestry.internal.event.EventBoundListener;
024 import org.apache.tapestry.internal.event.IComponentEventInvoker;
025 import org.apache.tapestry.listener.ListenerInvoker;
026 import org.apache.tapestry.spec.IComponentSpecification;
027 import org.apache.tapestry.spec.IEventListener;
028
029 import java.util.ArrayList;
030 import java.util.List;
031 import java.util.Map;
032
033
034 /**
035 * Implementation of {@link IComponentEventInvoker}.
036 */
037 public class ComponentEventInvoker implements IComponentEventInvoker, ResetEventListener
038 {
039 static final ComponentEventProperty[] EMPTY_PROPERTIES = new ComponentEventProperty[0];
040
041 // Mapped component id path -> List of IEventListeners
042 private Map _components = new ConcurrentHashMap();
043 // Mapped form id path -> List of IEventListeners
044 private Map _formComponents = new ConcurrentHashMap();
045 // Used to invoke actual listener methods
046 private ListenerInvoker _invoker;
047
048 // Cached set of ComponentEventProperty[] arrays mapped to specific components
049 private Map _propertyCache = new ConcurrentHashMap();
050
051 /**
052 * {@inheritDoc}
053 */
054 public void invokeListeners(IComponent component, IRequestCycle cycle, BrowserEvent event)
055 {
056 Defense.notNull(component, "component");
057 Defense.notNull(cycle, "cycle");
058 Defense.notNull(event, "event");
059
060 invokeComponentListeners(component, cycle, event);
061
062 invokeElementListeners(component, cycle, event);
063 }
064
065 /**
066 * {@inheritDoc}
067 */
068 public void invokeFormListeners(FormSupport formSupport, final IRequestCycle cycle, final BrowserEvent event)
069 {
070 Defense.notNull(formSupport, "formSupport");
071 Defense.notNull(cycle, "cycle");
072 Defense.notNull(event, "event");
073
074 IForm form = formSupport.getForm();
075 String formIdPath = form.getExtendedId();
076
077 String targetId = (String)event.getTarget().get("id");
078 String componentIdPath = event.getComponentIdPath();
079
080 if (targetId == null || componentIdPath == null)
081 return;
082
083 List comps = getFormEventListeners(formIdPath);
084 if (comps == null)
085 return;
086
087 boolean disableFocus = false;
088
089 for (int i=0; i < comps.size(); i++)
090 {
091 IComponentSpecification spec = (IComponentSpecification)comps.get(i);
092 EventBoundListener[] listeners = spec.getFormEvents(formIdPath, event);
093
094 IPage page = form.getPage();
095
096 for (int e=0; e < listeners.length; e++)
097 {
098 // ensure ~only~ the method that targeted this event gets called!
099
100 if (!listeners[e].getComponentId().equals(componentIdPath))
101 continue;
102
103 // clear validation errors but not input if async validation is
104 // disabled
105
106 if (!listeners[e].isValidateForm())
107 {
108 form.getDelegate().clearErrors();
109 }
110
111 // handle disabling focus
112 if (!disableFocus && !listeners[e].shouldFocusForm())
113 disableFocus = true;
114
115 IComponent target = page.getNestedComponent(listeners[e].getComponentIdPath());
116
117 // defer execution until after form is done rewinding
118
119 form.addDeferredRunnable(
120 new FormRunnable(target.getListeners().getListener(listeners[e].getMethodName()),
121 target,
122 cycle));
123 }
124 }
125
126 // Form uses cycle attributes to test whether or not to focus .
127 // The attribute existing at all is enough to bypass focusing.
128
129 if (disableFocus)
130 {
131 cycle.disableFocus();
132 }
133 }
134
135 void invokeComponentListeners(IComponent component, IRequestCycle cycle, BrowserEvent event)
136 {
137 String idPath = component.getExtendedId();
138 List listeners = getEventListeners(idPath);
139
140 if (listeners == null)
141 return;
142
143 IPage page = component.getPage();
144
145 for (int i = 0; i < listeners.size(); i++)
146 {
147 IComponentSpecification listener = (IComponentSpecification)listeners.get(i);
148
149 ComponentEventProperty props = listener.getComponentEvents(idPath);
150
151 if (props == null)
152 continue;
153
154 List clisteners = props.getEventListeners(event.getName());
155 for (int e=0; e < clisteners.size(); e++)
156 {
157 EventBoundListener eventListener = (EventBoundListener)clisteners.get(e);
158
159 IComponent target = page.getNestedComponent(eventListener.getComponentIdPath());
160
161 _invoker.invokeListener(target.getListeners().getListener(eventListener.getMethodName()), target, cycle);
162 }
163
164 }
165 }
166
167 void invokeElementListeners(IComponent component, IRequestCycle cycle, BrowserEvent event)
168 {
169 String targetId = (String)event.getTarget().get("id");
170 if (targetId == null)
171 return;
172
173 ComponentEventProperty prop = component.getSpecification().getElementEvents(targetId);
174 if (prop == null)
175 return;
176
177 List listeners = prop.getEventListeners(event.getName());
178
179 for (int i=0; i < listeners.size(); i++)
180 {
181 EventBoundListener listener = (EventBoundListener)listeners.get(i);
182
183 _invoker.invokeListener(component.getListeners().getListener(listener.getMethodName()), component, cycle);
184 }
185 }
186
187 /** Local runnable for deferred form connections. */
188 class FormRunnable implements Runnable {
189
190 private IActionListener _listener;
191 private IComponent _component;
192 private IRequestCycle _cycle;
193
194 public FormRunnable(IActionListener listener, IComponent comp, IRequestCycle cycle)
195 {
196 _listener = listener;
197 _component = comp;
198 _cycle = cycle;
199 }
200
201 public void run()
202 {
203 _invoker.invokeListener(_listener, _component, _cycle);
204 }
205 }
206
207 /**
208 * {@inheritDoc}
209 */
210 public void addEventListener(String componentId, IComponentSpecification listener)
211 {
212 List listeners = (List)_components.get(componentId);
213
214 if (listeners == null)
215 {
216 listeners = new ArrayList();
217 _components.put(componentId, listeners);
218 }
219
220 if (!listeners.contains(listener))
221 {
222 listeners.add(listener);
223 }
224
225 _propertyCache.remove(componentId);
226 }
227
228 /**
229 * {@inheritDoc}
230 */
231 public List getEventListeners(String componentId)
232 {
233 if (componentId == null)
234 return null;
235
236 return (List)_components.get(componentId);
237 }
238
239 public ComponentEventProperty[] getEventPropertyListeners(String componentIdPath)
240 {
241 if (componentIdPath == null)
242 return EMPTY_PROPERTIES;
243
244 ComponentEventProperty[] ret = (ComponentEventProperty[])_propertyCache.get(componentIdPath);
245 if (ret != null)
246 return ret;
247
248 List listeners = getEventListeners(componentIdPath);
249 if (listeners == null || listeners.size() < 1)
250 return EMPTY_PROPERTIES;
251
252 List props = new ArrayList();
253 for (int i=0; i < listeners.size(); i++)
254 {
255 IEventListener listener = (IEventListener)listeners.get(i);
256
257 props.add(listener.getComponentEvents(componentIdPath));
258 }
259
260 ret = (ComponentEventProperty[])props.toArray(new ComponentEventProperty[props.size()]);
261
262 _propertyCache.put(componentIdPath, ret);
263
264 return ret;
265 }
266
267 /**
268 * {@inheritDoc}
269 */
270 public void addFormEventListener(String formId, IComponentSpecification listener)
271 {
272 List listeners = (List)_formComponents.get(formId);
273
274 if (listeners == null)
275 {
276 listeners = new ArrayList();
277 _formComponents.put(formId, listeners);
278 }
279
280 if (!listeners.contains(listener))
281 {
282 listeners.add(listener);
283 }
284 }
285
286 /**
287 * {@inheritDoc}
288 */
289 public List getFormEventListeners(String formId)
290 {
291 if (formId == null)
292 return null;
293
294 return (List)_formComponents.get(formId);
295 }
296
297 /**
298 * {@inheritDoc}
299 */
300 public void resetEventDidOccur()
301 {
302 _components.clear();
303 _formComponents.clear();
304 _propertyCache.clear();
305 }
306
307 /** Injected. */
308 public void setInvoker(ListenerInvoker invoker)
309 {
310 _invoker = invoker;
311 }
312 }