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