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        private static final String ATTRIBUTE_NAME = "org.apache.tapestry.active.RadioGroup";
042        
043        // Cached copy of the value from the selectedBinding
044        private Object _selection;
045    
046        // The value from the HTTP request indicating which
047        // Radio was selected by the user.
048        private int _selectedOption;
049    
050        private boolean _rewinding;
051    
052        private 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            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            renderDelegatePrefix(writer, cycle);
156            
157            writer.begin(getTemplateTagName());
158            
159            renderInformalParameters(writer, cycle);
160            
161            renderDelegateAttributes(writer, cycle);
162            
163            renderBody(writer, cycle);
164            
165            writer.end();
166            
167            renderDelegateSuffix(writer, cycle);
168            
169            getValidatableFieldSupport().renderContributions(this, writer, cycle);
170        }
171        
172        /**
173         * @see org.apache.tapestry.form.AbstractFormComponent#rewindFormComponent(org.apache.tapestry.IMarkupWriter,
174         *      org.apache.tapestry.IRequestCycle)
175         */
176        protected void rewindFormComponent(IMarkupWriter writer, IRequestCycle cycle)
177        {
178            String value = cycle.getParameter(getName());
179    
180            if (value == null)
181                _selectedOption = -1;
182            else
183                _selectedOption = Integer.parseInt(value);
184    
185            _rewinding = true;
186            
187            renderBody(writer, cycle);
188            
189            try
190            {
191                getValidatableFieldSupport().validate(this, writer, cycle, _selection);
192            }
193            catch (ValidatorException e)
194            {
195                getForm().getDelegate().record(e);
196            }
197        }
198    
199        /**
200         * Injected.
201         */
202        public abstract ValidatableFieldSupport getValidatableFieldSupport();
203    
204        /**
205         * @see org.apache.tapestry.form.AbstractFormComponent#isRequired()
206         */
207        public boolean isRequired()
208        {
209            return getValidatableFieldSupport().isRequired(this);
210        }
211    
212        /**
213         * This component can not take focus.
214         */
215        protected boolean getCanTakeFocus()
216        {
217            return false;
218        }
219    }