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.tapestry.AbstractComponent;
018    import org.apache.tapestry.IForm;
019    import org.apache.tapestry.IMarkupWriter;
020    import org.apache.tapestry.IRequestCycle;
021    import org.apache.tapestry.TapestryUtils;
022    import org.apache.tapestry.engine.NullWriter;
023    import org.apache.tapestry.valid.IValidationDelegate;
024    import org.apache.tapestry.valid.ValidationConstants;
025    
026    /**
027     * A base class for building components that correspond to HTML form elements. All such components
028     * must be wrapped (directly or indirectly) by a {@link Form} component.
029     * 
030     * @author Howard Lewis Ship
031     * @author Paul Ferraro
032     * @since 1.0.3
033     */
034    public abstract class AbstractFormComponent extends AbstractComponent implements IFormComponent
035    {
036        
037        public abstract IForm getForm();
038    
039        public abstract void setForm(IForm form);
040    
041        public abstract String getName();
042    
043        public abstract void setName(String name);
044    
045        /**
046         * Returns true if the corresponding field, on the client side, can accept user focus (i.e.,
047         * implements the focus() method). Most components can take focus (if not disabled), but a few ({@link Hidden})
048         * override this method to always return false.
049         */
050    
051        protected boolean getCanTakeFocus()
052        {
053            return !isDisabled();
054        }
055    
056        /**
057         * Should be connected to a parameter named "id" (annotations would be helpful here!). For
058         * components w/o such a parameter, this will simply return null.
059         */
060    
061        public abstract String getIdParameter();
062        
063        /**
064         * Invoked by {@link AbstractComponent#render(IMarkupWriter, IRequestCycle)} to actually 
065         * render the component (with any parameter values already set). 
066         * This implementation checks the rewinding state of the {@link IForm} that contains the
067         * component and forwards processing to either 
068         * {@link #renderFormComponent(IMarkupWriter, IRequestCycle)} or 
069         * {@link #rewindFormComponent(IMarkupWriter, IRequestCycle)}. 
070         * Those two are the methods that subclasses should implement. 
071         *  
072         * @see org.apache.tapestry.AbstractComponent#renderComponent(org.apache.tapestry.IMarkupWriter,
073         *      org.apache.tapestry.IRequestCycle)
074         */
075        protected void renderComponent(IMarkupWriter writer, IRequestCycle cycle)
076        {
077            IForm form = TapestryUtils.getForm(cycle, this);
078    
079            setForm(form);
080            
081            if (form.wasPrerendered(writer, this))
082                return;
083            
084            IValidationDelegate delegate = form.getDelegate();
085            
086            delegate.setFormComponent(this);
087            
088            setName(form);
089            
090            if (form.isRewinding())
091            {
092                if (!isDisabled())
093                {
094                    rewindFormComponent(writer, cycle);
095                }
096                
097                // This is for the benefit of the couple of components (LinkSubmit) that allow a body.
098                // The body should render when the component rewinds.
099                
100                if (getRenderBodyOnRewind())
101                    renderBody(writer, cycle);
102            }
103            else if (!cycle.isRewinding())
104            {
105                if (!NullWriter.class.isInstance(writer))
106                    form.setFormFieldUpdating(true);
107                
108                renderFormComponent(writer, cycle);
109                
110                if (getCanTakeFocus() && !isDisabled())
111                {
112                    delegate.registerForFocus(
113                            this,
114                            delegate.isInError() ? ValidationConstants.ERROR_FIELD
115                                    : ValidationConstants.NORMAL_FIELD);
116                }
117    
118            }
119        }
120    
121        /**
122         * A small number of components should always render their body on rewind (even if the component
123         * is itself disabled) and should override this method to return true. Components that
124         * explicitly render their body inside
125         * {@link #rewindFormComponent(IMarkupWriter, IRequestCycle)} should leave this method returning
126         * false. Remember that if the component is {@link IFormComponent#isDisabled() disabled} then
127         * {@link #rewindFormComponent(IMarkupWriter, IRequestCycle)} won't be invoked.
128         * 
129         * @return false; override this method to change.
130         */
131        protected boolean getRenderBodyOnRewind()
132        {
133            return false;
134        }
135    
136        protected void renderDelegatePrefix(IMarkupWriter writer, IRequestCycle cycle)
137        {
138            getForm().getDelegate().writePrefix(writer, cycle, this, null);
139        }
140    
141        protected void renderDelegateAttributes(IMarkupWriter writer, IRequestCycle cycle)
142        {
143            getForm().getDelegate().writeAttributes(writer, cycle, this, null);
144        }
145    
146        protected void renderDelegateSuffix(IMarkupWriter writer, IRequestCycle cycle)
147        {
148            getForm().getDelegate().writeSuffix(writer, cycle, this, null);
149        }
150        
151        protected void setName(IForm form)
152        {
153            setName(form.getElementId(this));
154        }
155        
156        /**
157         * {@inheritDoc}
158         */
159        protected void generateClientId()
160        {
161        }
162        
163        /**
164         * {@inheritDoc}
165         */
166        public String peekClientId()
167        {
168            if (getPage() == null)
169                return null;
170            
171            IForm form = (IForm) getPage().getRequestCycle().getAttribute(TapestryUtils.FORM_ATTRIBUTE);
172            if (form == null)
173                return null;
174            
175            return form.peekClientId(this);
176        }
177        
178        /**
179         * Returns false. Subclasses that might be required must override this method. Typically, this
180         * involves checking against the component's validators.
181         * 
182         * @since 4.0
183         */
184        public boolean isRequired()
185        {
186            return false;
187        }
188    
189        /**
190         * Invoked from {@link #renderComponent(IMarkupWriter, IRequestCycle)} 
191         * to render the component. 
192         *  
193         * @param writer
194         * @param cycle
195         */
196        protected abstract void renderFormComponent(IMarkupWriter writer, IRequestCycle cycle);
197    
198        /**
199         * Invoked from {@link #renderComponent(IMarkupWriter, IRequestCycle)} to rewind the 
200         * component. If the component is {@link IFormComponent#isDisabled() disabled} 
201         * this will not be invoked. 
202         * 
203         * @param writer
204         * @param cycle
205         */
206        protected abstract void rewindFormComponent(IMarkupWriter writer, IRequestCycle cycle);
207    }