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    }