View Javadoc

1   // Copyright May 16, 2006 The Apache Software Foundation
2   //
3   // Licensed under the Apache License, Version 2.0 (the "License");
4   // you may not use this file except in compliance with the License.
5   // You may obtain a copy of the License at
6   //
7   //     http://www.apache.org/licenses/LICENSE-2.0
8   //
9   // Unless required by applicable law or agreed to in writing, software
10  // distributed under the License is distributed on an "AS IS" BASIS,
11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12  // See the License for the specific language governing permissions and
13  // limitations under the License.
14  package net.sf.tacos.components.dojo;
15  
16  import java.util.Collection;
17  import java.util.HashMap;
18  import java.util.Iterator;
19  import java.util.List;
20  import java.util.Map;
21  
22  import net.sf.tacos.binding.ICallbackFunction;
23  import net.sf.tacos.util.JSONMarkupWriter;
24  
25  import org.apache.tapestry.IActionListener;
26  import org.apache.tapestry.IBinding;
27  import org.apache.tapestry.IDirect;
28  import org.apache.tapestry.IMarkupWriter;
29  import org.apache.tapestry.IRequestCycle;
30  import org.apache.tapestry.IScript;
31  import org.apache.tapestry.Tapestry;
32  import org.apache.tapestry.TapestryUtils;
33  import org.apache.tapestry.dojo.IWidget;
34  import org.apache.tapestry.engine.DirectServiceParameter;
35  import org.apache.tapestry.engine.IEngineService;
36  import org.apache.tapestry.engine.ILink;
37  import org.apache.tapestry.event.BrowserEvent;
38  import org.apache.tapestry.json.JSONObject;
39  import org.apache.tapestry.link.DirectLink;
40  import org.apache.tapestry.listener.ListenerInvoker;
41  
42  /**
43   * Implementation of any dojo widget as a tapestry component.
44   * The formal parameter <code>dojoType</code> is used to determine which dojo widget to create. 
45   * Informal parameters are used as javascript properties to create widget with.
46   * Please have a look into the <a href="http://dojotoolkit.org/api">http://dojotoolkit.org/api</a>
47   * for further information about the widget properties. A simple example is the dojo Clock widget: 
48   *<pre>
49   *&lt;div jwcid="@dojo:Widget" dojoType="Clock" timeZoneOffset="5" labelColor="#fff"/&gt;
50   *</pre>
51   * <p>
52   * The formal parameter <code>isContainer</code> allows a widget to have children.
53   * All the widgets within the body of a container are added as childs. For example dojo tabs:
54   * </p>
55   * <pre>
56   *&lt;div jwcid="@dojo:Widget" dojoType="TabContainer" isContainer="true"&gt;
57   *	&lt;div jwcid="@dojo:Widget" dojoType="ContentPane" label="Athens"&gt;Athens&lt;/div&gt;
58   *	&lt;div jwcid="@dojo:Widget" dojoType="ContentPane" label="Moscow"&gt;Moscow&lt;/div&gt;
59   *&lt;/div&gt;
60   *</pre>
61   * <p>
62   * DojoWidget allows you to connect any client side event to a listener method.
63   * Please see the dojo api for further information about the events fired by the particular widget.
64   * To connect a client side even just use the informal parameter with the corresponding name.
65   * For example, specifying a component definition such as:
66   * </p>
67   * <pre>
68   * &lt;div jwcid="@dojo:Widget" dojoType="..." onClick="listener:onClick" onMouseDown="listener:onMouseDown"/&gt;
69   * </pre>
70   * <p>
71   * It's possible to specify a list of events for which to generate non async requests specifying the dojo events
72   * list in the noAsync parameter as a comma separated list. This is particularly useful
73   * with menu/button widget that most of the times need normal requests to be generated.
74   * <pre>
75   * &lt;div jwcid="@dojo:Widget" dojoType="MenuItem2" onClick="listener:onMenuClick" noAsync="onClick"/&gt;
76   * </pre>
77   * </p>
78   * Furthermore it is poossible to connect a client side event to a javascript function.
79   * For this purpose use the prefix <code>callback</code>. For example:
80   * </p>
81   * <pre>
82   * &lt;div jwcid="@dojo:Widget" dojoType="SliderHorizontal" onValueChanged="callback:sliderCallback"/&gt;
83   * </pre>
84   *  <p>Whereby <code>sliderCallback</code> is a javascript function:</p>
85   * <pre>
86   *  function sliderCallback(value) {
87   *  	alert(value);
88   *  }
89   * </pre>
90   * @since 4.1.0
91   */
92  public abstract class DojoWidget extends GenericWidget implements IDojoContainer, IDirect{
93  
94  	public static final String CONTAINER_ATTRIBUTE = DojoWidget.class.getName();
95          
96      /**
97       * If the current component is a container.<br/>
98       * If the user has defined a value for 'isContainer', then that is used.
99       *
100      * Otherwise, several rules are used to determine if the component is
101      * a valid container. The rules are:<ul>
102      * <li>the dojoType parameter ends with 'Container'.</li>
103      * </ul>
104      *
105      */
106     public boolean getContainerState() {
107         if (isParameterBound("isContainer")) {
108             return getIsContainer();
109         } else {
110             String type = getDojoType();
111             return (type.endsWith("Container") || type.equals("ContentPane"));
112         }
113     }
114     
115     /**
116      * {@inheritDoc}
117      */
118     public void renderWidget(IMarkupWriter writer, IRequestCycle cycle){
119         boolean isContainer = getContainerState();
120         IDojoContainer previousContainer = (IDojoContainer) cycle.getAttribute(CONTAINER_ATTRIBUTE);
121         boolean rewinding = cycle.isRewinding();
122         
123         if (!rewinding) {
124             writer.begin(getTemplateTagName());
125             renderIdAttribute(writer, cycle);
126             String style = getStyle();
127             if (style!=null)
128                 writer.attribute("style", style);
129             
130             if(isContainer){
131                 cycle.setAttribute(CONTAINER_ATTRIBUTE, this);
132             }
133         }
134         
135         renderBody(writer, cycle);
136         
137         if (!rewinding) {
138         	writer.end();
139 
140             if (previousContainer!=null)
141                 previousContainer.addChild(this);            
142 
143             JSONMarkupWriter jsonWriter = new JSONMarkupWriter();
144             renderInformalParameters(jsonWriter, cycle);
145             JSONObject json = jsonWriter.getAttributes();
146             
147             Map asynclisteners = getAsyncListenerBindings();
148             Map listeners = getListenerBindings();
149             Map callbacks = getCallbackBindings();
150             removeListenersFromInputSymbols(json, asynclisteners);
151             removeListenersFromInputSymbols(json, listeners);
152             removeListenersFromInputSymbols(json, callbacks);
153 
154             Map parms = new HashMap();
155             String url = getLink().getURL();
156             parms.put("component", this);
157             parms.put("type", getDojoType());
158             parms.put("url", url);
159             parms.put("props", json.toString());
160             parms.put("asynclisteners", asynclisteners);
161             parms.put("listeners", listeners);
162             parms.put("callbacks", callbacks);
163             getScript().execute(this, cycle, TapestryUtils.getPageRenderSupport(cycle, this), parms);
164 
165             cycle.setAttribute(CONTAINER_ATTRIBUTE, previousContainer);
166         }
167     }
168     
169     
170     /**
171      * {@inheritDoc}
172      */
173 	public void addChild(IWidget widget) {
174             // need to store the clientIds here instead of the widget instances,
175             // cause that's what's changing
176 		getChildren().add(widget.getClientId());
177 	}
178 	
179 	/**
180 	 * The informal parameters of the component are used as arguments for <code>dojo.widget.Widget</code>.
181 	 * The method {@link this{@link #renderInformalParameters(IMarkupWriter, IRequestCycle)}}
182 	 * is used to pass through these parameters to the dojo widget. Some of the parameters does not
183 	 * represent dojo arguments and should not be passed through.
184 	 * This method can be used to remove specified bindings from the Map of input symbols.
185 	 * @param json json object to remove bindings from 
186 	 * @param bindings bindings to remove
187 	 */
188 	private void removeListenersFromInputSymbols(JSONObject json, Map bindings){
189 		Iterator i = bindings.keySet().iterator();
190 		while(i.hasNext()){
191 			json.remove((String)i.next());
192 		}
193 	}
194 	
195 	/**
196 	 * Returns a {@link Map} of the {@link org.apache.tapestry.IBinding bindings} implementing
197 	 * the {@link org.apache.tapestry.IActionListener} interface.
198 	 */
199 	private Map getAllListenerBindings(){
200 		return getBindingsByClass(getBindingNames(), IActionListener.class);
201 	}
202 
203 	/**
204 	 * Returns a {@link Map} of the {@link org.apache.tapestry.IBinding bindings} implementing
205 	 * the {@link org.apache.tapestry.IActionListener} interface for which to
206 	 * generate non async requests.
207 	 */
208 	public Map getListenerBindings(){
209 		Map listeners = getBindingsByClass(getBindingNames(), IActionListener.class);
210 		Map result = new HashMap();
211 		String[] noAsync = constructNoAsync();
212 		if (noAsync != null) {
213 			for (int i = 0; i < noAsync.length; i++) {
214 				if (listeners.get(noAsync[i]) != null) {
215 					result.put(noAsync[i], listeners.get(noAsync[i]));
216 				}
217 			}		
218 		}
219 		return result;
220 	}
221 	/**
222 	 * Returns a {@link Map} of the {@link org.apache.tapestry.IBinding bindings} implementing
223 	 * the {@link org.apache.tapestry.IActionListener} interface for which
224 	 * to generate async requests.
225 	 */
226 	public Map getAsyncListenerBindings(){
227 		Map listeners = getBindingsByClass(getBindingNames(), IActionListener.class);
228 		String[] noAsync = constructNoAsync();
229 		if (noAsync != null) {
230 			for (int i = 0; i < noAsync.length; i++) {
231 				listeners.remove(noAsync[i]);
232 			}
233 		}
234 		return listeners;
235 	}
236 	
237 	/**
238 	 * Returns an array containing the names of the events for which not to generate
239 	 * async requests
240 	 */
241 	private String[] constructNoAsync() {
242 		String noAsync = getNoAsync();
243 		if (noAsync != null) {
244 			if (noAsync.indexOf(",") != -1) {
245 				return noAsync.split(",");
246 			} else {
247 				String[] result = {noAsync};
248 				return result;
249 			}
250 		}
251 		return null;
252 	}
253 	
254 	/**
255 	 * Returns a {@link Map} of the {@link org.apache.tapestry.IBinding bindings} implementing
256 	 * the {@link net.sf.tacos.binding.ICallbackFunction} interface.
257 	 */
258 	public Map getCallbackBindings(){
259 		return getBindingsByClass(getBindingNames(), ICallbackFunction.class);
260 	}
261 	
262 	/**
263 	 * Filters the {@link Map} of the {@link org.apache.tapestry.IBinding bindings} of the component 
264 	 * and returns a {@link Map} of {@link org.apache.tapestry.IBinding bindings} the specified Class parameter
265 	 * is assignable from.
266 	 * 
267 	 * @param names names of the bindings 
268 	 * @param clazz class to determine if it is assignable from the bindings
269 	 */
270 	protected Map getBindingsByClass(Collection names, Class clazz){
271 		Map result = new HashMap();
272 		Iterator i = names.iterator();
273 		while(i.hasNext()){
274 			String name = (String)i.next();
275 			IBinding binding = getBinding(name);
276 			if(clazz.isAssignableFrom(binding.getClass())){
277 				result.put(name, binding);
278 			}
279 		}
280 		return result;
281 	}
282 	
283 	/**Returns a link*/
284 	private ILink getLink(){
285 		Object[] parameters = DirectLink.constructServiceParameters(getParameters());
286 		DirectServiceParameter dsp = new DirectServiceParameter(this, parameters);
287 		return getEngine().getLink(isStateful(), dsp);
288 	}
289 	
290 	/** Returns the url to use for a non async event  */
291 	public String getNoAsyncEventUrl(String eventName){
292 		Object[] parameters = DirectLink.constructServiceParameters(getParameters());
293 		DirectServiceParameter dsp = new DirectServiceParameter(this, parameters);
294 		return getEngine().getLink(isStateful(), dsp).getURL() + "&" 
295 			+ BrowserEvent.NAME + "=" + eventName;
296 	}
297 		
298     /**
299      * Invoked by the direct service to trigger the application-specific action by notifying the
300      * {@link IActionListener listener}.
301      */
302 	public void trigger(IRequestCycle cycle) {
303         String event = cycle.getParameter(BrowserEvent.NAME);
304         IActionListener listener = (IActionListener)getAllListenerBindings().get(event);
305         if (listener == null)
306             throw Tapestry.createRequiredParameterException(this, event);
307         getListenerInvoker().invokeListener(listener, this, cycle);
308 		
309 	}
310 
311     /** Injected script. */
312     public abstract IScript getScript();
313     
314     /** Injected engine service . */
315     public abstract IEngineService getEngine();
316     
317     /** Injected listener invoker. */
318     public abstract ListenerInvoker getListenerInvoker();
319     
320     /** Returns the style. */
321     public abstract String getStyle();
322     
323     /** Returns the type of the dojo widget */
324     public abstract String getDojoType();
325     
326     /**Returns listener parameters*/
327     public abstract Object getParameters();
328     
329     /** Returns widget events for which to generate no async requests */
330     public abstract String getNoAsync();
331     
332     /**
333      * Returns a list of children widgetIds.
334      * The list is always empty if {@link #getIsContainer()} returns <code>false</code>.
335      */
336     public abstract List getChildren();
337 }