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