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.hivemind.ApplicationRuntimeException;
018    import org.apache.tapestry.IMarkupWriter;
019    import org.apache.tapestry.IRequestCycle;
020    import org.apache.tapestry.Tapestry;
021    import 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     */
034    public abstract class RadioGroup extends AbstractFormComponent implements ValidatableField
035    {
036        /**
037         * A <code>RadioGroup</code> places itself into the {@link IRequestCycle}as an attribute, so
038         * that its wrapped {@link Radio}components can identify thier state.
039         */
040    
041        static final String ATTRIBUTE_NAME = "org.apache.tapestry.active.RadioGroup";
042    
043        // Cached copy of the value from the selectedBinding
044        Object _selection;
045    
046        // The value from the HTTP request indicating which
047        // Radio was selected by the user.
048        int _selectedOption;
049    
050        boolean _rewinding;
051    
052        boolean _rendering;
053    
054        private int _nextOptionId;
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            super.prepareForRender(cycle);
123    
124            if (cycle.getAttribute(ATTRIBUTE_NAME) != null)
125                throw new ApplicationRuntimeException(Tapestry.getMessage("RadioGroup.may-not-nest"),
126                                                      this, null, null);
127    
128            cycle.setAttribute(ATTRIBUTE_NAME, this);
129    
130            _rendering = true;
131            _nextOptionId = 0;
132        }
133    
134        /**
135         * @see org.apache.tapestry.AbstractComponent#cleanupAfterRender(org.apache.tapestry.IRequestCycle)
136         */
137        protected void cleanupAfterRender(IRequestCycle cycle)
138        {
139            super.cleanupAfterRender(cycle);
140    
141            _rendering = false;
142            _selection = null;
143    
144            cycle.removeAttribute(ATTRIBUTE_NAME);
145        }
146    
147        /**
148         * @see org.apache.tapestry.form.AbstractRequirableField#renderFormComponent(org.apache.tapestry.IMarkupWriter,
149         *      org.apache.tapestry.IRequestCycle)
150         */
151        protected void renderFormComponent(IMarkupWriter writer, IRequestCycle cycle)
152        {
153            _rewinding = false;
154    
155            // For rendering, the Radio components need to know what the current
156            // selection is, so that the correct one can mark itself 'checked'.
157            _selection = getBinding("selected").getObject();
158    
159            renderDelegatePrefix(writer, cycle);
160    
161            writer.begin(getTemplateTagName());
162    
163            renderInformalParameters(writer, cycle);
164    
165            renderDelegateAttributes(writer, cycle);
166    
167            renderBody(writer, cycle);
168    
169            writer.end();
170    
171            renderDelegateSuffix(writer, cycle);
172    
173            getValidatableFieldSupport().renderContributions(this, writer, cycle);
174        }
175    
176        /**
177         * @see org.apache.tapestry.form.AbstractFormComponent#rewindFormComponent(org.apache.tapestry.IMarkupWriter,
178         *      org.apache.tapestry.IRequestCycle)
179         */
180        protected void rewindFormComponent(IMarkupWriter writer, IRequestCycle cycle)
181        {
182            String value = cycle.getParameter(getName());
183    
184            if (value == null)
185                _selectedOption = -1;
186            else
187                _selectedOption = Integer.parseInt(value);
188    
189            _rewinding = true;
190    
191            renderBody(writer, cycle);
192    
193            try
194            {
195                getValidatableFieldSupport().validate(this, writer, cycle, _selection);
196            }
197            catch (ValidatorException e)
198            {
199                getForm().getDelegate().record(e);
200            }
201        }
202    
203        /**
204         * Injected.
205         */
206        public abstract ValidatableFieldSupport getValidatableFieldSupport();
207    
208        /**
209         * @see org.apache.tapestry.form.AbstractFormComponent#isRequired()
210         */
211        public boolean isRequired()
212        {
213            return getValidatableFieldSupport().isRequired(this);
214        }
215    
216        /**
217         * This component can not take focus.
218         */
219        protected boolean getCanTakeFocus()
220        {
221            return false;
222        }
223    }