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