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 }