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