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                {
115                    rewindFormComponent(writer, cycle);
116                }
117    
118                // This is for the benefit of the couple of components (LinkSubmit) that allow a body.
119                // The body should render when the component rewinds.
120    
121                if (getRenderBodyOnRewind())
122                    renderBody(writer, cycle);
123            }
124            else if (!cycle.isRewinding())
125            {
126                renderFormComponent(writer, cycle);
127    
128                if (getCanTakeFocus() && !isDisabled())
129                {
130                    delegate.registerForFocus(
131                            this,
132                            delegate.isInError() ? ValidationConstants.ERROR_FIELD
133                                    : ValidationConstants.NORMAL_FIELD);
134                }
135    
136            }
137        }
138    
139        /**
140         * A small number of components should always render their body on rewind (even if the component
141         * is itself disabled) and should override this method to return true. Components that
142         * explicitly render their body inside
143         * {@link #rewindFormComponent(IMarkupWriter, IRequestCycle)} should leave this method returning
144         * false. Remember that if the component is {@link IFormComponent#isDisabled() disabled} then
145         * {@link #rewindFormComponent(IMarkupWriter, IRequestCycle)} won't be invoked.
146         * 
147         * @return false; override this method to change.
148         */
149        protected boolean getRenderBodyOnRewind()
150        {
151            return false;
152        }
153    
154        protected void renderDelegatePrefix(IMarkupWriter writer, IRequestCycle cycle)
155        {
156            getForm().getDelegate().writePrefix(writer, cycle, this, null);
157        }
158    
159        protected void renderDelegateAttributes(IMarkupWriter writer, IRequestCycle cycle)
160        {
161            getForm().getDelegate().writeAttributes(writer, cycle, this, null);
162        }
163    
164        protected void renderDelegateSuffix(IMarkupWriter writer, IRequestCycle cycle)
165        {
166            getForm().getDelegate().writeSuffix(writer, cycle, this, null);
167        }
168    
169        protected void setName(IForm form)
170        {
171            form.getElementId(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        protected abstract void renderFormComponent(IMarkupWriter writer, IRequestCycle cycle);
186    
187        protected abstract void rewindFormComponent(IMarkupWriter writer, IRequestCycle cycle);
188    }