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.hivemind.ApplicationRuntimeException;
018import org.apache.tapestry.IMarkupWriter;
019import org.apache.tapestry.IRequestCycle;
020import org.apache.tapestry.Tapestry;
021import org.apache.tapestry.valid.ValidatorException;
022
023/**
024 * A special type of form component that is used to contain {@link Radio}components. The Radio and
025 * {@link Radio}group components work together to update a property of some other object, much like
026 * a more flexible version of a {@link PropertySelection}. [ <a
027 * href="../../../../../ComponentReference/RadioGroup.html">Component Reference </a>]
028 * <p>
029 * As of 4.0, this component can be validated.
030 * 
031 * @author Howard Lewis Ship
032 * @author Paul Ferraro
033 */
034public abstract class RadioGroup extends AbstractFormComponent implements ValidatableField
035{
036    // Cached copy of the value from the selectedBinding
037    private Object _selection;
038
039    // The value from the HTTP request indicating which
040    // Radio was selected by the user.
041    private int _selectedOption;
042
043    private boolean _rewinding;
044
045    private boolean _rendering;
046
047    private int _nextOptionId;
048
049    /**
050     * A <code>RadioGroup</code> places itself into the {@link IRequestCycle}as an attribute, so
051     * that its wrapped {@link Radio}components can identify thier state.
052     */
053
054    private static final String ATTRIBUTE_NAME = "org.apache.tapestry.active.RadioGroup";
055
056    public static RadioGroup get(IRequestCycle cycle)
057    {
058        return (RadioGroup) cycle.getAttribute(ATTRIBUTE_NAME);
059    }
060
061    public int getNextOptionId()
062    {
063        if (!_rendering)
064            throw Tapestry.createRenderOnlyPropertyException(this, "nextOptionId");
065
066        return _nextOptionId++;
067    }
068
069    public boolean isRewinding()
070    {
071        if (!_rendering)
072            throw Tapestry.createRenderOnlyPropertyException(this, "rewinding");
073
074        return _rewinding;
075    }
076
077    /**
078     * Returns true if the value is equal to the current selection for the group. This is invoked by
079     * a {@link Radio}during rendering to determine if it should be marked 'checked'.
080     */
081
082    public boolean isSelection(Object value)
083    {
084        if (!_rendering)
085            throw Tapestry.createRenderOnlyPropertyException(this, "selection");
086
087        if (_selection == value)
088            return true;
089
090        if (_selection == null || value == null)
091            return false;
092
093        return _selection.equals(value);
094    }
095
096    /**
097     * Invoked by the {@link Radio}which is selected to update the property bound to the selected
098     * parameter.
099     */
100
101    public void updateSelection(Object value)
102    {
103        getBinding("selected").setObject(value);
104
105        _selection = value;
106    }
107
108    /**
109     * Used by {@link Radio}components when rewinding to see if their value was submitted.
110     */
111
112    public boolean isSelected(int option)
113    {
114        return _selectedOption == option;
115    }
116
117    /**
118     * @see org.apache.tapestry.AbstractComponent#prepareForRender(org.apache.tapestry.IRequestCycle)
119     */
120    protected void prepareForRender(IRequestCycle cycle)
121    {
122        if (cycle.getAttribute(ATTRIBUTE_NAME) != null)
123            throw new ApplicationRuntimeException(Tapestry.getMessage("RadioGroup.may-not-nest"),
124                    this, null, null);
125
126        cycle.setAttribute(ATTRIBUTE_NAME, this);
127
128        _rendering = true;
129        _nextOptionId = 0;
130    }
131
132    /**
133     * @see org.apache.tapestry.AbstractComponent#cleanupAfterRender(org.apache.tapestry.IRequestCycle)
134     */
135    protected void cleanupAfterRender(IRequestCycle cycle)
136    {
137        _rendering = false;
138        _selection = null;
139
140        cycle.removeAttribute(ATTRIBUTE_NAME);
141    }
142
143    /**
144     * @see org.apache.tapestry.form.AbstractRequirableField#renderFormComponent(org.apache.tapestry.IMarkupWriter,
145     *      org.apache.tapestry.IRequestCycle)
146     */
147    protected void renderFormComponent(IMarkupWriter writer, IRequestCycle cycle)
148    {
149        _rewinding = false;
150
151        // For rendering, the Radio components need to know what the current
152        // selection is, so that the correct one can mark itself 'checked'.
153        _selection = getBinding("selected").getObject();
154
155        renderBody(writer, cycle);
156
157        getValidatableFieldSupport().renderContributions(this, writer, cycle);
158    }
159
160    /**
161     * @see org.apache.tapestry.form.AbstractFormComponent#rewindFormComponent(org.apache.tapestry.IMarkupWriter,
162     *      org.apache.tapestry.IRequestCycle)
163     */
164    protected void rewindFormComponent(IMarkupWriter writer, IRequestCycle cycle)
165    {
166        String value = cycle.getParameter(getName());
167
168        if (value == null)
169            _selectedOption = -1;
170        else
171            _selectedOption = Integer.parseInt(value);
172
173        _rewinding = true;
174
175        renderBody(writer, cycle);
176
177        try
178        {
179            getValidatableFieldSupport().validate(this, writer, cycle, _selection);
180        }
181        catch (ValidatorException e)
182        {
183            getForm().getDelegate().record(e);
184        }
185    }
186
187    /**
188     * Injected.
189     */
190    public abstract ValidatableFieldSupport getValidatableFieldSupport();
191
192    /**
193     * @see org.apache.tapestry.form.AbstractFormComponent#isRequired()
194     */
195    public boolean isRequired()
196    {
197        return getValidatableFieldSupport().isRequired(this);
198    }
199
200    /**
201     * This component can not take focus.
202     */
203    protected boolean getCanTakeFocus()
204    {
205        return false;
206    }
207
208    /**
209     * @see org.apache.tapestry.form.AbstractFormComponent#getRenderBodyOnRewind()
210     */
211    protected boolean getAlwaysRenderBodyOnRewind()
212    {
213        return true;
214    }
215}