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.ApplicationRuntimeException;
018 import org.apache.hivemind.Location;
019 import org.apache.tapestry.AbstractComponent;
020 import org.apache.tapestry.IActionListener;
021 import org.apache.tapestry.IComponent;
022 import org.apache.tapestry.IDirect;
023 import org.apache.tapestry.IForm;
024 import org.apache.tapestry.IMarkupWriter;
025 import org.apache.tapestry.IRender;
026 import org.apache.tapestry.IRequestCycle;
027 import org.apache.tapestry.RenderRewoundException;
028 import org.apache.tapestry.Tapestry;
029 import org.apache.tapestry.TapestryUtils;
030 import org.apache.tapestry.engine.ActionServiceParameter;
031 import org.apache.tapestry.engine.DirectServiceParameter;
032 import org.apache.tapestry.engine.IEngineService;
033 import org.apache.tapestry.engine.ILink;
034 import org.apache.tapestry.listener.ListenerInvoker;
035 import org.apache.tapestry.valid.IValidationDelegate;
036 import 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
063 public 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 }