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 }