001    // Copyright May 20, 2006 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;
015    
016    import org.apache.tapestry.event.BrowserEvent;
017    
018    import java.util.*;
019    
020    
021    /**
022     * Represents a configured listener/event(s) binding for a 
023     * a component and the events that may be optionally listened
024     * for on the client browser.
025     */
026    public class ComponentEventProperty implements Cloneable
027    {
028        private Map _eventMap = new HashMap();
029        private Map _formEventMap = new HashMap();
030        
031        private String _componentId;
032    
033        /**
034         * Creates a new component event property mapped to the specified component id.
035         *
036         * @param componentId
037         *          The component which is the target of all mappings in this property.
038         */
039        public ComponentEventProperty(String componentId)
040        {
041            _componentId = componentId;
042        }
043    
044        /**
045         * Used in cloning only currently.
046         *
047         * @param componentId
048         *          The component this property is bound to.
049         * @param events
050         *          The list of event mappings.
051         * @param formEvents
052         *          The list of form event mappings.
053         */
054        public ComponentEventProperty(String componentId, Map events, Map formEvents)
055        {
056            _componentId = componentId;
057            _eventMap = events;
058            _formEventMap = formEvents;
059        }
060    
061        /**
062         * Adds a listener bound to the specified client side
063         * events.
064         * 
065         * @param events
066         * @param methodName
067         * @param async
068         */
069        public void addListener(String[] events, String methodName,
070                String formId, boolean validateForm, boolean async, boolean focus)
071        {
072            addListener(events, methodName, formId, validateForm, async, focus, true);
073        }
074    
075        /**
076         * Adds a listener bound to the specified client side
077         * events.
078         * 
079         * @param events The javascript events to bind to.
080         * @param methodName The method to invoke when triggered.
081         * @param formId Optional form to bind event to.
082         * @param validateForm Whether or not form client side validation should be performed.
083         * @param async  Whether or not the request should be asynchronous.
084         * @param focus Whether or not the form should recieve focus events. (if any forms are involved)
085         * @param autoSubmit Whether or not {@link org.apache.tapestry.form.IFormComponent}s should have their forms autowired for submission.
086         */
087        public void addListener(String[] events, String methodName, 
088                String formId, boolean validateForm, boolean async, boolean focus, boolean autoSubmit)
089        {
090            for (int i=0; i < events.length; i++)
091            {
092                if (formId != null && formId.length() > 0)
093                {
094                    addFormEventListener(events[i], methodName, formId, validateForm, async, focus, autoSubmit);
095                } else
096                {
097                    EventBoundListener listener = new EventBoundListener(methodName, formId, validateForm,
098                                                                         _componentId, async, focus, autoSubmit);
099                    List listeners = getEventListeners(events[i]);
100                    if (!listeners.contains(listener))
101                    {
102                        listeners.add(listener);
103                    }
104                }
105            }
106        }
107        
108        /**
109         * Adds a form listener to the specified client side event.
110         * @param event
111         * @param methodName
112         * @param formId 
113         * @param validateForm
114         */
115        public void addFormEventListener(String event, String methodName,
116                String formId, boolean validateForm, boolean async, boolean focus, boolean autoSubmit)
117        {
118            EventBoundListener listener = new EventBoundListener(methodName, formId, validateForm, _componentId,
119                                                                 async, focus, autoSubmit);
120            
121            List listeners = getFormEventListeners(event);
122            if (!listeners.contains(listener))
123                listeners.add(listener);
124        }
125        
126        /**
127         * Adds a listener to the specified client side event.
128         * @param event
129         * @param methodName
130         */
131        public void addEventListener(String event, String methodName, boolean autoSubmit)
132        {
133            EventBoundListener listener = new EventBoundListener(methodName, _componentId);
134            
135            List listeners = getEventListeners(event);
136            if (!listeners.contains(listener))
137                listeners.add(listener);
138        }
139    
140        /**
141         * Moves all of the non-form-submitting events with autoSubmit=true in {@link #_eventMap} over
142         * to the list of form-submitting events {@link #_formEventMap}.
143         * This is called when the targeted component is an {@link org.apache.tapestry.form.IFormComponent}
144         * by the {@link org.apache.tapestry.pageload.EventConnectionVisitor}.
145         * */
146        public void connectAutoSubmitEvents(String formIdPath)
147        {
148            Iterator it = getEvents().iterator();
149            List removeKeys = new ArrayList();
150            
151            while (it.hasNext())
152            {
153                String key = (String)it.next();
154                List listeners = (List) _eventMap.get(key);
155    
156                Iterator lit = listeners.iterator();
157                while (lit.hasNext())
158                {    
159                    EventBoundListener listener = (EventBoundListener) lit.next();
160                   if ( !listener.isAutoSubmit() )
161                        continue;
162                    
163                    listener.setFormId(formIdPath);
164                    lit.remove();
165                    
166                    List formListeners = getFormEventListeners(key);
167                    if (!formListeners.contains(listener))
168                        formListeners.add(listener);
169                }
170                
171                // remove mapping if empty
172                
173                if (listeners.size() == 0)
174                {
175                    removeKeys.add(key);
176                }
177            }
178    
179            for (int i=0; i < removeKeys.size(); i++)
180            {    
181                _eventMap.remove(removeKeys.get(i));
182            }
183    
184            it = getFormEvents().iterator();
185            
186            while (it.hasNext())
187            {
188                String key = (String) it.next();
189                List listeners = (List) _formEventMap.get(key);
190                Iterator lit = listeners.iterator();
191    
192                while(lit.hasNext())
193                {
194                    EventBoundListener listener = (EventBoundListener) lit.next();
195                    listener.setFormId(formIdPath);
196                }
197            }
198        }
199    
200        /**
201         * Replaces all instances of the existing component id mapped for this property with the new
202         * {@link org.apache.tapestry.IComponent#getIdPath()} version.
203         *
204         * @param extendedId The component extended id path.
205         * @param idPath The component idPath from the page.
206         */
207        public void rewireComponentId(String extendedId, String idPath)
208        {
209            _componentId = extendedId;
210    
211            Iterator it = getEvents().iterator();
212            while (it.hasNext())
213            {
214                String key = (String) it.next();
215                List listeners = (List)_eventMap.get(key);
216    
217                for (int i=0; i < listeners.size(); i++)
218                {
219                    EventBoundListener listener = (EventBoundListener) listeners.get(i);
220    
221                    listener.setComponentId(extendedId);
222                    listener.setComponentIdPath(idPath);
223                }
224            }
225    
226            it = getFormEvents().iterator();
227            while (it.hasNext())
228            {
229                String key = (String) it.next();
230                List listeners = (List)_formEventMap.get(key);
231    
232                for (int i=0; i < listeners.size(); i++)
233                {
234                    EventBoundListener listener = (EventBoundListener) listeners.get(i);
235    
236                    listener.setComponentId(extendedId);
237                    listener.setComponentIdPath(idPath);
238                }
239            }
240        }
241    
242        /**
243         * @return the componentId
244         */
245        public String getComponentId()
246        {
247            return _componentId;
248        }
249    
250        /**
251         * Gets the current list of listeners for a specific event,
252         * creates a new instance if one doesn't exist already.
253         * 
254         * @param event
255         * 
256         * @return The current set of listeners bound to the specified event.
257         */
258        public List getEventListeners(String event)
259        {
260            List listeners = (List)_eventMap.get(event);
261            if (listeners == null)
262            {
263                listeners = new ArrayList();
264                _eventMap.put(event, listeners);
265            }
266            
267            return listeners;
268        }
269        
270        /**
271         * Gets the current list of listeners for a specific event,
272         * creates a new instance if one doesn't exist already.
273         * 
274         * @param event
275         * 
276         * @return The current set of listeners that will submit a form bound to the
277         *          specified event.
278         */
279        public List getFormEventListeners(String event)
280        {
281            List listeners = (List)_formEventMap.get(event);
282            if (listeners == null)
283            {
284                listeners = new ArrayList();
285                _formEventMap.put(event, listeners);
286            }
287            
288            return listeners;
289        }
290        
291        /**
292         * The set of all non form based events.
293         * @return The unique set of events.
294         */
295        public Set getEvents()
296        {
297            return _eventMap.keySet();
298        }
299        
300        /**
301         * The set of all form based listener events.
302         * 
303         * @return All mapped form event keys.
304         */
305        public Set getFormEvents()
306        {
307            return _formEventMap.keySet();
308        }
309        
310        /**
311         * Creates a list of listeners bound to a particular form
312         * and client side browser event. 
313         * 
314         * @param formId
315         *          The form to find listeners for.
316         * @param event
317         *          The browser event that generated the request.
318         * @param append 
319         *          The optional list to add the listeners to.
320         * @return The list of listeners to invoke for the form and event passed in,
321         *          will be empty if none found.
322         */
323        public List getFormEventListeners(String formId, BrowserEvent event, List append)
324        {   
325            List ret = (append == null) ? new ArrayList() : append;
326            
327            List listeners = (List)_formEventMap.get(event.getName());
328            if (listeners == null) 
329                return ret;
330            
331            for (int i=0; i < listeners.size(); i++)
332            {
333                EventBoundListener listener = (EventBoundListener)listeners.get(i);
334    
335                if (listener.getFormId().equals(formId))
336                    ret.add(listener);
337            }
338            
339            return ret;
340        }
341    
342        void cloneEvents(Map source, Map target)
343                throws CloneNotSupportedException
344        {
345            Iterator it = source.keySet().iterator();
346            while (it.hasNext())
347            {
348                String event = (String) it.next();
349                List listeners = (List)source.get(event);
350    
351                List newListeners = new ArrayList();
352                for (int i=0; i < listeners.size(); i++)
353                {
354                    EventBoundListener listener = (EventBoundListener) listeners.get(i);
355                    newListeners.add(listener.clone());
356                }
357    
358                target.put(event, newListeners);
359            }
360        }
361    
362        public Object clone()
363        throws CloneNotSupportedException
364        {
365            Map events = new HashMap();
366            Map formEvents = new HashMap();
367    
368            cloneEvents(_eventMap, events);
369            cloneEvents(_formEventMap, formEvents);
370    
371            return new ComponentEventProperty(_componentId, events, formEvents);
372        }
373    }