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    }