001 // Copyright May 16, 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 net.sf.tacos.components.dojo; 015 016 import java.util.Collection; 017 import java.util.HashMap; 018 import java.util.Iterator; 019 import java.util.List; 020 import java.util.Map; 021 022 import net.sf.tacos.binding.ICallbackFunction; 023 import net.sf.tacos.util.JSONMarkupWriter; 024 025 import org.apache.tapestry.IActionListener; 026 import org.apache.tapestry.IBinding; 027 import org.apache.tapestry.IDirect; 028 import org.apache.tapestry.IMarkupWriter; 029 import org.apache.tapestry.IRequestCycle; 030 import org.apache.tapestry.IScript; 031 import org.apache.tapestry.Tapestry; 032 import org.apache.tapestry.TapestryUtils; 033 import org.apache.tapestry.dojo.IWidget; 034 import org.apache.tapestry.engine.DirectServiceParameter; 035 import org.apache.tapestry.engine.IEngineService; 036 import org.apache.tapestry.engine.ILink; 037 import org.apache.tapestry.event.BrowserEvent; 038 import org.apache.tapestry.json.JSONObject; 039 import org.apache.tapestry.link.DirectLink; 040 import org.apache.tapestry.listener.ListenerInvoker; 041 042 /** 043 * Implementation of any dojo widget as a tapestry component. 044 * The formal parameter <code>dojoType</code> is used to determine which dojo widget to create. 045 * Informal parameters are used as javascript properties to create widget with. 046 * Please have a look into the <a href="http://dojotoolkit.org/api">http://dojotoolkit.org/api</a> 047 * for further information about the widget properties. A simple example is the dojo Clock widget: 048 *<pre> 049 *<div jwcid="@dojo:Widget" dojoType="Clock" timeZoneOffset="5" labelColor="#fff"/> 050 *</pre> 051 * <p> 052 * The formal parameter <code>isContainer</code> allows a widget to have children. 053 * All the widgets within the body of a container are added as childs. For example dojo tabs: 054 * </p> 055 * <pre> 056 *<div jwcid="@dojo:Widget" dojoType="TabContainer" isContainer="true"> 057 * <div jwcid="@dojo:Widget" dojoType="ContentPane" label="Athens">Athens</div> 058 * <div jwcid="@dojo:Widget" dojoType="ContentPane" label="Moscow">Moscow</div> 059 *</div> 060 *</pre> 061 * <p> 062 * DojoWidget allows you to connect any client side event to a listener method. 063 * Please see the dojo api for further information about the events fired by the particular widget. 064 * To connect a client side even just use the informal parameter with the corresponding name. 065 * For example, specifying a component definition such as: 066 * </p> 067 * <pre> 068 * <div jwcid="@dojo:Widget" dojoType="..." onClick="listener:onClick" onMouseDown="listener:onMouseDown"/> 069 * </pre> 070 * <p> 071 * It's possible to specify a list of events for which to generate non async requests specifying the dojo events 072 * list in the noAsync parameter as a comma separated list. This is particularly useful 073 * with menu/button widget that most of the times need normal requests to be generated. 074 * <pre> 075 * <div jwcid="@dojo:Widget" dojoType="MenuItem2" onClick="listener:onMenuClick" noAsync="onClick"/> 076 * </pre> 077 * </p> 078 * Furthermore it is poossible to connect a client side event to a javascript function. 079 * For this purpose use the prefix <code>callback</code>. For example: 080 * </p> 081 * <pre> 082 * <div jwcid="@dojo:Widget" dojoType="SliderHorizontal" onValueChanged="callback:sliderCallback"/> 083 * </pre> 084 * <p>Whereby <code>sliderCallback</code> is a javascript function:</p> 085 * <pre> 086 * function sliderCallback(value) { 087 * alert(value); 088 * } 089 * </pre> 090 * @since 4.1.0 091 */ 092 public abstract class DojoWidget extends GenericWidget implements IDojoContainer, IDirect{ 093 094 public static final String CONTAINER_ATTRIBUTE = DojoWidget.class.getName(); 095 096 /** 097 * If the current component is a container.<br/> 098 * If the user has defined a value for 'isContainer', then that is used. 099 * 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 }