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 }