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