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 }