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 *<div jwcid="@dojo:Widget" dojoType="Clock" timeZoneOffset="5" labelColor="#fff"/>
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 *<div jwcid="@dojo:Widget" dojoType="TabContainer" isContainer="true">
57 * <div jwcid="@dojo:Widget" dojoType="ContentPane" label="Athens">Athens</div>
58 * <div jwcid="@dojo:Widget" dojoType="ContentPane" label="Moscow">Moscow</div>
59 *</div>
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 * <div jwcid="@dojo:Widget" dojoType="..." onClick="listener:onClick" onMouseDown="listener:onMouseDown"/>
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 * <div jwcid="@dojo:Widget" dojoType="MenuItem2" onClick="listener:onMenuClick" noAsync="onClick"/>
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 * <div jwcid="@dojo:Widget" dojoType="SliderHorizontal" onValueChanged="callback:sliderCallback"/>
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 }