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 }