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