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.IDirect;
022    import org.apache.tapestry.IForm;
023    import org.apache.tapestry.IMarkupWriter;
024    import org.apache.tapestry.IRender;
025    import org.apache.tapestry.IRequestCycle;
026    import org.apache.tapestry.RenderRewoundException;
027    import org.apache.tapestry.Tapestry;
028    import org.apache.tapestry.TapestryUtils;
029    import org.apache.tapestry.engine.ActionServiceParameter;
030    import org.apache.tapestry.engine.DirectServiceParameter;
031    import org.apache.tapestry.engine.IEngineService;
032    import org.apache.tapestry.engine.ILink;
033    import org.apache.tapestry.listener.ListenerInvoker;
034    import org.apache.tapestry.valid.IValidationDelegate;
035    import org.apache.tapestry.web.WebResponse;
036    
037    /**
038     * Component which contains form element components. Forms use the action or direct services to
039     * handle the form submission. A Form will wrap other components and static HTML, including form
040     * components such as {@link TextArea}, {@link TextField}, {@link Checkbox}, etc. [ <a
041     * href="../../../../../ComponentReference/Form.html">Component Reference </a>]
042     * <p>
043     * When a form is submitted, it continues through the rewind cycle until <em>after</em> all of its
044     * wrapped elements have renderred. As the form component render (in the rewind cycle), they will be
045     * updating properties of the containing page and notifying thier listeners. Again: each form
046     * component is responsible not only for rendering HTML (to present the form), but for handling it's
047     * share of the form submission.
048     * <p>
049     * Only after all that is done will the Form notify its listener.
050     * <p>
051     * Starting in release 1.0.2, a Form can use either the direct service or the action service. The
052     * default is the direct service, even though in earlier releases, only the action service was
053     * available.
054     * <p>
055     * Release 4.0 adds two new listeners, {@link #getCancel()} and {@link #getRefresh()} and
056     * corresponding client-side behavior to force a form to refresh (update, bypassing input field
057     * validation) or cancel (update immediately).
058     * 
059     * @author Howard Lewis Ship, David Solis
060     */
061    
062    public abstract class Form extends AbstractComponent implements IForm, IDirect
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         * Returns the currently active {@link IForm}, or null if no form is active. This is a
084         * convienience method, the result will be null, or an instance of {@link IForm}, but not
085         * necessarily a <code>Form</code>.
086         * 
087         * @deprecated Use {@link TapestryUtils#getForm(IRequestCycle, IComponent)} instead.
088         */
089    
090        public static IForm get(IRequestCycle cycle)
091        {
092            return (IForm) cycle.getAttribute(ATTRIBUTE_NAME);
093        }
094    
095        /**
096         * Indicates to any wrapped form components that they should respond to the form submission.
097         * 
098         * @throws ApplicationRuntimeException
099         *             if not rendering.
100         */
101    
102        public boolean isRewinding()
103        {
104            if (!isRendering())
105                throw Tapestry.createRenderOnlyPropertyException(this, "rewinding");
106    
107            return _formSupport.isRewinding();
108        }
109    
110        /**
111         * Injected.
112         * 
113         * @since 4.0
114         */
115    
116        public abstract IEngineService getDirectService();
117    
118        /**
119         * Injected.
120         * 
121         * @since 4.0
122         */
123    
124        public abstract IEngineService getActionService();
125    
126        /**
127         * Returns true if this Form is configured to use the direct service.
128         * <p>
129         * This is derived from the direct parameter, and defaults to true if not bound.
130         * 
131         * @since 1.0.2
132         */
133    
134        public abstract boolean isDirect();
135    
136        /**
137         * Returns true if the stateful parameter is bound to a true value. If stateful is not bound,
138         * also returns the default, true.
139         * 
140         * @since 1.0.1
141         */
142    
143        public boolean getRequiresSession()
144        {
145            return isStateful();
146        }
147    
148        /**
149         * Constructs a unique identifier (within the Form). The identifier consists of the component's
150         * id, with an index number added to ensure uniqueness.
151         * <p>
152         * Simply invokes
153         * {@link #getElementId(org.apache.tapestry.form.IFormComponent, java.lang.String)}with the
154         * component's id.
155         * 
156         * @since 1.0.2
157         */
158    
159        public String getElementId(IFormComponent component)
160        {
161            return _formSupport.getElementId(component, component.getId());
162        }
163    
164        /**
165         * Constructs a unique identifier from the base id. If possible, the id is used as-is.
166         * Otherwise, a unique identifier is appended to the id.
167         * <p>
168         * This method is provided simply so that some components ({@link ImageSubmit}) have more
169         * specific control over their names.
170         * 
171         * @since 1.0.3
172         */
173    
174        public String getElementId(IFormComponent component, String baseId)
175        {
176            return _formSupport.getElementId(component, baseId);
177        }
178    
179        /**
180         * Returns the name generated for the form. This is used to faciliate components that write
181         * JavaScript and need to access the form or its contents.
182         * <p>
183         * This value is generated when the form renders, and is not cleared. If the Form is inside a
184         * {@link org.apache.tapestry.components.Foreach}, this will be the most recently generated
185         * name for the Form.
186         * <p>
187         * This property is exposed so that sophisticated applications can write JavaScript handlers for
188         * the form and components within the form.
189         * 
190         * @see AbstractFormComponent#getName()
191         */
192    
193        public String getName()
194        {
195            return _name;
196        }
197    
198        /** @since 3.0 * */
199    
200        protected void prepareForRender(IRequestCycle cycle)
201        {
202            super.prepareForRender(cycle);
203    
204            TapestryUtils.storeForm(cycle, this);
205        }
206    
207        protected void cleanupAfterRender(IRequestCycle cycle)
208        {
209            _formSupport = null;
210    
211            TapestryUtils.removeForm(cycle);
212    
213            IValidationDelegate delegate = getDelegate();
214    
215            if (delegate != null)
216                delegate.setFormComponent(null);
217    
218            super.cleanupAfterRender(cycle);
219        }
220    
221        protected void renderComponent(IMarkupWriter writer, IRequestCycle cycle)
222        {
223            String actionId = cycle.getNextActionId();
224    
225            _formSupport = newFormSupport(writer, cycle);
226    
227            if (isRewinding())
228            {
229                String submitType = _formSupport.rewind();
230    
231                IActionListener listener = findListener(submitType);
232    
233                getListenerInvoker().invokeListener(listener, this, cycle);
234    
235                // Abort the rewind render.
236    
237                throw new RenderRewoundException(this);
238            }
239    
240            // Note: not safe to invoke getNamespace() in Portlet world
241            // except during a RenderRequest.
242    
243            String baseName = isDirect() ? constructFormNameForDirectService(cycle)
244                    : constructFormNameForActionService(actionId);
245    
246            _name = baseName + getResponse().getNamespace();
247    
248            if (_renderInformalParameters == null)
249                _renderInformalParameters = new RenderInformalParameters();
250    
251            ILink link = getLink(cycle, actionId);
252            
253            _formSupport.render(getMethod(), _renderInformalParameters, link, getScheme(), getPort());
254        }
255    
256        IActionListener findListener(String mode)
257        {
258            IActionListener result = null;
259    
260            if (mode.equals(FormConstants.SUBMIT_CANCEL))
261                result = getCancel();
262            else if (mode.equals(FormConstants.SUBMIT_REFRESH))
263                result = getRefresh();
264            else if (!getDelegate().getHasErrors())
265                result = getSuccess();
266    
267            // If not success, cancel or refresh, or the corresponding listener
268            // is itself null, then use the default listener
269            // (which may be null as well!).
270    
271            if (result == null)
272                result = getListener();
273    
274            return result;
275        }
276    
277        /**
278         * Construct a form name for use with the action service. This implementation returns "Form"
279         * appended with the actionId.
280         * 
281         * @since 4.0
282         */
283    
284        protected String constructFormNameForActionService(String actionId)
285        {
286            return "Form" + actionId;
287        }
288    
289        /**
290         * Constructs a form name for use with the direct service. This implementation bases the form
291         * name on the form component's id (but ensures it is unique). Remember that Tapestry assigns an
292         * "ugly" id if an explicit component id is not provided.
293         * 
294         * @since 4.0
295         */
296    
297        private String constructFormNameForDirectService(IRequestCycle cycle)
298        {
299            return cycle.getUniqueId(TapestryUtils.convertTapestryIdToNMToken(getId()));
300        }
301    
302        /**
303         * Returns a new instance of {@link FormSupportImpl}.
304         */
305    
306        protected FormSupport newFormSupport(IMarkupWriter writer, IRequestCycle cycle)
307        {
308            return new FormSupportImpl(writer, cycle, this);
309        }
310    
311        /**
312         * Adds an additional event handler.
313         * 
314         * @since 1.0.2
315         */
316    
317        public void addEventHandler(FormEventType type, String functionName)
318        {
319            _formSupport.addEventHandler(type, functionName);
320        }
321    
322        /**
323         * Simply invokes {@link #render(IMarkupWriter, IRequestCycle)}.
324         * 
325         * @since 1.0.2
326         */
327    
328        public void rewind(IMarkupWriter writer, IRequestCycle cycle)
329        {
330            cycle.getResponseBuilder().render(writer, this, cycle);
331        }
332        
333        /**
334         * Method invoked by the direct service.
335         * 
336         * @since 1.0.2
337         */
338    
339        public void trigger(IRequestCycle cycle)
340        {
341            cycle.rewindForm(this);
342        }
343        
344        /**
345         * Builds the EngineServiceLink for the form, using either the direct or action service.
346         * 
347         * @since 1.0.3
348         */
349    
350        private ILink getLink(IRequestCycle cycle, String actionId)
351        {
352            if (isDirect())
353            {
354                Object parameter = new DirectServiceParameter(this);
355                return getDirectService().getLink(true, parameter);
356            }
357    
358            // I'd love to pull out support for the action service entirely!
359    
360            Object parameter = new ActionServiceParameter(this, actionId);
361    
362            return getActionService().getLink(true, parameter);
363        }
364    
365        /** Injected. */
366        public abstract WebResponse getResponse();
367    
368        /**
369         * delegate parameter, which has a default (starting in release 4.0).
370         */
371    
372        public abstract IValidationDelegate getDelegate();
373    
374        /** listener parameter, may be null. */
375        public abstract IActionListener getListener();
376    
377        /** success parameter, may be null. */
378        public abstract IActionListener getSuccess();
379    
380        /** cancel parameter, may be null. */
381        public abstract IActionListener getCancel();
382    
383        /** refresh parameter, may be null. */
384        public abstract IActionListener getRefresh();
385    
386        /** method parameter. */
387        public abstract String getMethod();
388    
389        /** stateful parameter. */
390        public abstract boolean isStateful();
391    
392        /** scheme parameter, may be null. */
393        public abstract String getScheme();
394        
395        /** port , may be null. */
396        public abstract Integer getPort();
397    
398        public void setEncodingType(String encodingType)
399        {
400            _formSupport.setEncodingType(encodingType);
401        }
402    
403        /** @since 3.0 */
404    
405        public void addHiddenValue(String name, String value)
406        {
407            _formSupport.addHiddenValue(name, value);
408        }
409    
410        /** @since 3.0 */
411    
412        public void addHiddenValue(String name, String id, String value)
413        {
414            _formSupport.addHiddenValue(name, id, value);
415        }
416    
417        public void prerenderField(IMarkupWriter writer, IComponent field, Location location)
418        {
419            _formSupport.prerenderField(writer, field, location);
420        }
421    
422        public boolean wasPrerendered(IMarkupWriter writer, IComponent field)
423        {
424            return _formSupport.wasPrerendered(writer, field);
425        }
426    
427        /** @since 4.0 */
428    
429        public void addDeferredRunnable(Runnable runnable)
430        {
431            _formSupport.addDeferredRunnable(runnable);
432        }
433    
434        /**
435         * Injected.
436         * 
437         * @since 4.0
438         */
439    
440        public abstract ListenerInvoker getListenerInvoker();
441    
442        public void registerForFocus(IFormComponent field, int priority)
443        {
444            _formSupport.registerForFocus(field, priority);
445        }
446    
447    }