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 015package org.apache.tapestry.form; 016 017import org.apache.hivemind.ApplicationRuntimeException; 018import org.apache.hivemind.Location; 019import org.apache.tapestry.AbstractComponent; 020import org.apache.tapestry.IActionListener; 021import org.apache.tapestry.IComponent; 022import org.apache.tapestry.IDirect; 023import org.apache.tapestry.IForm; 024import org.apache.tapestry.IMarkupWriter; 025import org.apache.tapestry.IRender; 026import org.apache.tapestry.IRequestCycle; 027import org.apache.tapestry.RenderRewoundException; 028import org.apache.tapestry.Tapestry; 029import org.apache.tapestry.TapestryUtils; 030import org.apache.tapestry.engine.ActionServiceParameter; 031import org.apache.tapestry.engine.DirectServiceParameter; 032import org.apache.tapestry.engine.IEngineService; 033import org.apache.tapestry.engine.ILink; 034import org.apache.tapestry.listener.ListenerInvoker; 035import org.apache.tapestry.valid.IValidationDelegate; 036import org.apache.tapestry.web.WebResponse; 037 038/** 039 * Component which contains form element components. Forms use the action or direct services to 040 * handle the form submission. A Form will wrap other components and static HTML, including form 041 * components such as {@link TextArea}, {@link TextField}, {@link Checkbox}, etc. [ <a 042 * href="../../../../../ComponentReference/Form.html">Component Reference </a>] 043 * <p> 044 * When a form is submitted, it continues through the rewind cycle until <em>after</em> all of its 045 * wrapped elements have renderred. As the form component render (in the rewind cycle), they will be 046 * updating properties of the containing page and notifying thier listeners. Again: each form 047 * component is responsible not only for rendering HTML (to present the form), but for handling it's 048 * share of the form submission. 049 * <p> 050 * Only after all that is done will the Form notify its listener. 051 * <p> 052 * Starting in release 1.0.2, a Form can use either the direct service or the action service. The 053 * default is the direct service, even though in earlier releases, only the action service was 054 * available. 055 * <p> 056 * Release 4.0 adds two new listeners, {@link #getCancel()} and {@link #getRefresh()} and 057 * corresponding client-side behavior to force a form to refresh (update, bypassing input field 058 * validation) or cancel (update immediately). 059 * 060 * @author Howard Lewis Ship, David Solis 061 */ 062 063public abstract class Form extends AbstractComponent implements IForm, IDirect 064{ 065 private String _name; 066 067 private FormSupport _formSupport; 068 069 private class RenderInformalParameters implements IRender 070 { 071 public void render(IMarkupWriter writer, IRequestCycle cycle) 072 { 073 renderInformalParameters(writer, cycle); 074 } 075 } 076 077 private IRender _renderInformalParameters; 078 079 /** 080 * Returns the currently active {@link IForm}, or null if no form is active. This is a 081 * convienience method, the result will be null, or an instance of {@link IForm}, but not 082 * necessarily a <code>Form</code>. 083 * 084 * @deprecated Use {@link TapestryUtils#getForm(IRequestCycle, IComponent)} instead. 085 */ 086 087 public static IForm get(IRequestCycle cycle) 088 { 089 return (IForm) cycle.getAttribute(ATTRIBUTE_NAME); 090 } 091 092 /** 093 * Indicates to any wrapped form components that they should respond to the form submission. 094 * 095 * @throws ApplicationRuntimeException 096 * if not rendering. 097 */ 098 099 public boolean isRewinding() 100 { 101 if (!isRendering()) 102 throw Tapestry.createRenderOnlyPropertyException(this, "rewinding"); 103 104 return _formSupport.isRewinding(); 105 } 106 107 /** 108 * Injected. 109 * 110 * @since 4.0 111 */ 112 113 public abstract IEngineService getDirectService(); 114 115 /** 116 * Injected. 117 * 118 * @since 4.0 119 */ 120 121 public abstract IEngineService getActionService(); 122 123 /** 124 * Returns true if this Form is configured to use the direct service. 125 * <p> 126 * This is derived from the direct parameter, and defaults to true if not bound. 127 * 128 * @since 1.0.2 129 */ 130 131 public abstract boolean isDirect(); 132 133 /** 134 * Returns true if the stateful parameter is bound to a true value. If stateful is not bound, 135 * also returns the default, true. 136 * 137 * @since 1.0.1 138 */ 139 140 public boolean getRequiresSession() 141 { 142 return isStateful(); 143 } 144 145 /** 146 * Constructs a unique identifier (within the Form). The identifier consists of the component's 147 * id, with an index number added to ensure uniqueness. 148 * <p> 149 * Simply invokes 150 * {@link #getElementId(org.apache.tapestry.form.IFormComponent, java.lang.String)}with the 151 * component's id. 152 * 153 * @since 1.0.2 154 */ 155 156 public String getElementId(IFormComponent component) 157 { 158 return _formSupport.getElementId(component, component.getId()); 159 } 160 161 /** 162 * Constructs a unique identifier from the base id. If possible, the id is used as-is. 163 * Otherwise, a unique identifier is appended to the id. 164 * <p> 165 * This method is provided simply so that some components ({@link ImageSubmit}) have more 166 * specific control over their names. 167 * 168 * @since 1.0.3 169 */ 170 171 public String getElementId(IFormComponent component, String baseId) 172 { 173 return _formSupport.getElementId(component, baseId); 174 } 175 176 /** 177 * Returns the name generated for the form. This is used to faciliate components that write 178 * JavaScript and need to access the form or its contents. 179 * <p> 180 * This value is generated when the form renders, and is not cleared. If the Form is inside a 181 * {@link org.apache.tapestry.components.Foreach}, this will be the most recently generated 182 * name for the Form. 183 * <p> 184 * This property is exposed so that sophisticated applications can write JavaScript handlers for 185 * the form and components within the form. 186 * 187 * @see AbstractFormComponent#getName() 188 */ 189 190 public String getName() 191 { 192 return _name; 193 } 194 195 /** @since 3.0 * */ 196 197 protected void prepareForRender(IRequestCycle cycle) 198 { 199 super.prepareForRender(cycle); 200 201 TapestryUtils.storeForm(cycle, this); 202 } 203 204 protected void cleanupAfterRender(IRequestCycle cycle) 205 { 206 _formSupport = null; 207 208 TapestryUtils.removeForm(cycle); 209 210 IValidationDelegate delegate = getDelegate(); 211 212 if (delegate != null) 213 delegate.setFormComponent(null); 214 215 super.cleanupAfterRender(cycle); 216 } 217 218 protected void renderComponent(IMarkupWriter writer, IRequestCycle cycle) 219 { 220 String actionId = cycle.getNextActionId(); 221 222 _formSupport = newFormSupport(writer, cycle); 223 224 if (isRewinding()) 225 { 226 String submitType = _formSupport.rewind(); 227 228 IActionListener listener = findListener(submitType); 229 230 getListenerInvoker().invokeListener(listener, this, cycle); 231 232 // Abort the rewind render. 233 234 throw new RenderRewoundException(this); 235 } 236 237 // Note: not safe to invoke getNamespace() in Portlet world 238 // except during a RenderRequest. 239 240 String baseName = isDirect() ? constructFormNameForDirectService(cycle) 241 : constructFormNameForActionService(actionId); 242 243 _name = baseName + getResponse().getNamespace(); 244 245 if (_renderInformalParameters == null) 246 _renderInformalParameters = new RenderInformalParameters(); 247 248 ILink link = getLink(cycle, actionId); 249 250 _formSupport.render(getMethod(), _renderInformalParameters, link, getScheme(), getPort()); 251 } 252 253 IActionListener findListener(String mode) 254 { 255 IActionListener result = null; 256 257 if (mode.equals(FormConstants.SUBMIT_CANCEL)) 258 result = getCancel(); 259 else if (mode.equals(FormConstants.SUBMIT_REFRESH)) 260 result = getRefresh(); 261 else if (!getDelegate().getHasErrors()) 262 result = getSuccess(); 263 264 // If not success, cancel or refresh, or the corresponding listener 265 // is itself null, then use the default listener 266 // (which may be null as well!). 267 268 if (result == null) 269 result = getListener(); 270 271 return result; 272 } 273 274 /** 275 * Construct a form name for use with the action service. This implementation returns "Form" 276 * appended with the actionId. 277 * 278 * @since 4.0 279 */ 280 281 protected String constructFormNameForActionService(String actionId) 282 { 283 return "Form" + actionId; 284 } 285 286 /** 287 * Constructs a form name for use with the direct service. This implementation bases the form 288 * name on the form component's id (but ensures it is unique). Remember that Tapestry assigns an 289 * "ugly" id if an explicit component id is not provided. 290 * 291 * @since 4.0 292 */ 293 294 private String constructFormNameForDirectService(IRequestCycle cycle) 295 { 296 return cycle.getUniqueId(TapestryUtils.convertTapestryIdToNMToken(getId())); 297 } 298 299 /** 300 * Returns a new instance of {@link FormSupportImpl}. 301 */ 302 303 protected FormSupport newFormSupport(IMarkupWriter writer, IRequestCycle cycle) 304 { 305 return new FormSupportImpl(writer, cycle, this); 306 } 307 308 /** 309 * Adds an additional event handler. 310 * 311 * @since 1.0.2 312 */ 313 314 public void addEventHandler(FormEventType type, String functionName) 315 { 316 _formSupport.addEventHandler(type, functionName); 317 } 318 319 /** 320 * Simply invokes {@link #render(IMarkupWriter, IRequestCycle)}. 321 * 322 * @since 1.0.2 323 */ 324 325 public void rewind(IMarkupWriter writer, IRequestCycle cycle) 326 { 327 render(writer, cycle); 328 } 329 330 /** 331 * Method invoked by the direct service. 332 * 333 * @since 1.0.2 334 */ 335 336 public void trigger(IRequestCycle cycle) 337 { 338 cycle.rewindForm(this); 339 } 340 341 /** 342 * Builds the EngineServiceLink for the form, using either the direct or action service. 343 * 344 * @since 1.0.3 345 */ 346 347 protected ILink getLink(IRequestCycle cycle, String actionId) 348 { 349 if (isDirect()) 350 { 351 Object parameter = new DirectServiceParameter(this); 352 return getDirectService().getLink(true, parameter); 353 } 354 355 // I'd love to pull out support for the action service entirely! 356 357 Object parameter = new ActionServiceParameter(this, actionId); 358 359 return getActionService().getLink(true, parameter); 360 } 361 362 /** Injected */ 363 364 public abstract WebResponse getResponse(); 365 366 /** 367 * delegate parameter, which has a default (starting in release 4.0). 368 */ 369 370 public abstract IValidationDelegate getDelegate(); 371 372 /** listener parameter, may be null */ 373 public abstract IActionListener getListener(); 374 375 /** success parameter, may be null */ 376 public abstract IActionListener getSuccess(); 377 378 /** cancel parameter, may be null */ 379 public abstract IActionListener getCancel(); 380 381 /** refresh parameter, may be null */ 382 public abstract IActionListener getRefresh(); 383 384 /** method parameter */ 385 public abstract String getMethod(); 386 387 /** stateful parameter */ 388 public abstract boolean isStateful(); 389 390 /** scheme parameter, may be null */ 391 public abstract String getScheme(); 392 393 /** port , may be null */ 394 public abstract Integer getPort(); 395 396 public void setEncodingType(String encodingType) 397 { 398 _formSupport.setEncodingType(encodingType); 399 } 400 401 /** @since 3.0 */ 402 403 public void addHiddenValue(String name, String value) 404 { 405 _formSupport.addHiddenValue(name, value); 406 } 407 408 /** @since 3.0 */ 409 410 public void addHiddenValue(String name, String id, String value) 411 { 412 _formSupport.addHiddenValue(name, id, value); 413 } 414 415 public void prerenderField(IMarkupWriter writer, IComponent field, Location location) 416 { 417 _formSupport.prerenderField(writer, field, location); 418 } 419 420 public boolean wasPrerendered(IMarkupWriter writer, IComponent field) 421 { 422 return _formSupport.wasPrerendered(writer, field); 423 } 424 425 /** @since 4.0 */ 426 427 public void addDeferredRunnable(Runnable runnable) 428 { 429 _formSupport.addDeferredRunnable(runnable); 430 } 431 432 /** 433 * Injected 434 * 435 * @since 4.0 436 */ 437 438 public abstract ListenerInvoker getListenerInvoker(); 439 440 public void registerForFocus(IFormComponent field, int priority) 441 { 442 _formSupport.registerForFocus(field, priority); 443 } 444 445}