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