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
015package org.apache.tapestry.form;
016
017import org.apache.tapestry.AbstractComponent;
018import org.apache.tapestry.IForm;
019import org.apache.tapestry.IMarkupWriter;
020import org.apache.tapestry.IRequestCycle;
021import org.apache.tapestry.TapestryUtils;
022import org.apache.tapestry.valid.IValidationDelegate;
023import 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 */
033public 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}