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