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