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 // 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 }