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 /**
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 private static final String ATTRIBUTE_NAME = "org.apache.tapestry.active.RadioGroup";
043
044 // Cached copy of the value from the selectedBinding
045 private Object _selection;
046
047 // The value from the HTTP request indicating which
048 // Radio was selected by the user.
049 private int _selectedOption;
050
051 private boolean _rewinding;
052
053 private boolean _rendering;
054
055 private int _nextOptionId;
056
057 public static RadioGroup get(IRequestCycle cycle)
058 {
059 return (RadioGroup) cycle.getAttribute(ATTRIBUTE_NAME);
060 }
061
062 public int getNextOptionId()
063 {
064 if (!_rendering)
065 throw Tapestry.createRenderOnlyPropertyException(this, "nextOptionId");
066
067 return _nextOptionId++;
068 }
069
070 public boolean isRewinding()
071 {
072 if (!_rendering)
073 throw Tapestry.createRenderOnlyPropertyException(this, "rewinding");
074
075 return _rewinding;
076 }
077
078 /**
079 * Returns true if the value is equal to the current selection for the group. This is invoked by
080 * a {@link Radio}during rendering to determine if it should be marked 'checked'.
081 */
082
083 public boolean isSelection(Object value)
084 {
085 if (!_rendering)
086 throw Tapestry.createRenderOnlyPropertyException(this, "selection");
087
088 if (_selection == value)
089 return true;
090
091 if (_selection == null || value == null)
092 return false;
093
094 return _selection.equals(value);
095 }
096
097 /**
098 * Invoked by the {@link Radio}which is selected to update the property bound to the selected
099 * parameter.
100 */
101
102 public void updateSelection(Object value)
103 {
104 getBinding("selected").setObject(value);
105
106 _selection = value;
107 }
108
109 /**
110 * Used by {@link Radio}components when rewinding to see if their value was submitted.
111 */
112
113 public boolean isSelected(int option)
114 {
115 return _selectedOption == option;
116 }
117
118 /**
119 * @see org.apache.tapestry.AbstractComponent#prepareForRender(org.apache.tapestry.IRequestCycle)
120 */
121 protected void prepareForRender(IRequestCycle cycle)
122 {
123 if (cycle.getAttribute(ATTRIBUTE_NAME) != null)
124 throw new ApplicationRuntimeException(Tapestry.getMessage("RadioGroup.may-not-nest"),
125 this, null, null);
126
127 cycle.setAttribute(ATTRIBUTE_NAME, this);
128
129 _rendering = true;
130 _nextOptionId = 0;
131 }
132
133 /**
134 * @see org.apache.tapestry.AbstractComponent#cleanupAfterRender(org.apache.tapestry.IRequestCycle)
135 */
136 protected void cleanupAfterRender(IRequestCycle cycle)
137 {
138 _rendering = false;
139 _selection = null;
140
141 cycle.removeAttribute(ATTRIBUTE_NAME);
142 }
143
144 /**
145 * @see org.apache.tapestry.form.AbstractRequirableField#renderFormComponent(org.apache.tapestry.IMarkupWriter,
146 * org.apache.tapestry.IRequestCycle)
147 */
148 protected void renderFormComponent(IMarkupWriter writer, IRequestCycle cycle)
149 {
150 _rewinding = false;
151
152 // For rendering, the Radio components need to know what the current
153 // selection is, so that the correct one can mark itself 'checked'.
154 _selection = getBinding("selected").getObject();
155
156 renderBody(writer, cycle);
157
158 getValidatableFieldSupport().renderContributions(this, writer, cycle);
159 }
160
161 /**
162 * @see org.apache.tapestry.form.AbstractFormComponent#rewindFormComponent(org.apache.tapestry.IMarkupWriter,
163 * org.apache.tapestry.IRequestCycle)
164 */
165 protected void rewindFormComponent(IMarkupWriter writer, IRequestCycle cycle)
166 {
167 String value = cycle.getParameter(getName());
168
169 if (value == null)
170 _selectedOption = -1;
171 else
172 _selectedOption = Integer.parseInt(value);
173
174 _rewinding = true;
175
176 renderBody(writer, cycle);
177
178 try
179 {
180 getValidatableFieldSupport().validate(this, writer, cycle, _selection);
181 }
182 catch (ValidatorException e)
183 {
184 getForm().getDelegate().record(e);
185 }
186 }
187
188 /**
189 * Injected.
190 */
191 public abstract ValidatableFieldSupport getValidatableFieldSupport();
192
193 /**
194 * @see org.apache.tapestry.form.AbstractFormComponent#isRequired()
195 */
196 public boolean isRequired()
197 {
198 return getValidatableFieldSupport().isRequired(this);
199 }
200
201 /**
202 * This component can not take focus.
203 */
204 protected boolean getCanTakeFocus()
205 {
206 return false;
207 }
208 }