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