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 015package org.apache.tapestry.form; 016 017import org.apache.hivemind.ApplicationRuntimeException; 018import org.apache.tapestry.IMarkupWriter; 019import org.apache.tapestry.IRequestCycle; 020import org.apache.tapestry.Tapestry; 021import 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 */ 034public abstract class RadioGroup extends AbstractFormComponent implements ValidatableField 035{ 036 // Cached copy of the value from the selectedBinding 037 private Object _selection; 038 039 // The value from the HTTP request indicating which 040 // Radio was selected by the user. 041 private int _selectedOption; 042 043 private boolean _rewinding; 044 045 private boolean _rendering; 046 047 private int _nextOptionId; 048 049 /** 050 * A <code>RadioGroup</code> places itself into the {@link IRequestCycle}as an attribute, so 051 * that its wrapped {@link Radio}components can identify thier state. 052 */ 053 054 private static final String ATTRIBUTE_NAME = "org.apache.tapestry.active.RadioGroup"; 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 if (cycle.getAttribute(ATTRIBUTE_NAME) != null) 123 throw new ApplicationRuntimeException(Tapestry.getMessage("RadioGroup.may-not-nest"), 124 this, null, null); 125 126 cycle.setAttribute(ATTRIBUTE_NAME, this); 127 128 _rendering = true; 129 _nextOptionId = 0; 130 } 131 132 /** 133 * @see org.apache.tapestry.AbstractComponent#cleanupAfterRender(org.apache.tapestry.IRequestCycle) 134 */ 135 protected void cleanupAfterRender(IRequestCycle cycle) 136 { 137 _rendering = false; 138 _selection = null; 139 140 cycle.removeAttribute(ATTRIBUTE_NAME); 141 } 142 143 /** 144 * @see org.apache.tapestry.form.AbstractRequirableField#renderFormComponent(org.apache.tapestry.IMarkupWriter, 145 * org.apache.tapestry.IRequestCycle) 146 */ 147 protected void renderFormComponent(IMarkupWriter writer, IRequestCycle cycle) 148 { 149 _rewinding = false; 150 151 // For rendering, the Radio components need to know what the current 152 // selection is, so that the correct one can mark itself 'checked'. 153 _selection = getBinding("selected").getObject(); 154 155 renderBody(writer, cycle); 156 157 getValidatableFieldSupport().renderContributions(this, writer, cycle); 158 } 159 160 /** 161 * @see org.apache.tapestry.form.AbstractFormComponent#rewindFormComponent(org.apache.tapestry.IMarkupWriter, 162 * org.apache.tapestry.IRequestCycle) 163 */ 164 protected void rewindFormComponent(IMarkupWriter writer, IRequestCycle cycle) 165 { 166 String value = cycle.getParameter(getName()); 167 168 if (value == null) 169 _selectedOption = -1; 170 else 171 _selectedOption = Integer.parseInt(value); 172 173 _rewinding = true; 174 175 renderBody(writer, cycle); 176 177 try 178 { 179 getValidatableFieldSupport().validate(this, writer, cycle, _selection); 180 } 181 catch (ValidatorException e) 182 { 183 getForm().getDelegate().record(e); 184 } 185 } 186 187 /** 188 * Injected. 189 */ 190 public abstract ValidatableFieldSupport getValidatableFieldSupport(); 191 192 /** 193 * @see org.apache.tapestry.form.AbstractFormComponent#isRequired() 194 */ 195 public boolean isRequired() 196 { 197 return getValidatableFieldSupport().isRequired(this); 198 } 199 200 /** 201 * This component can not take focus. 202 */ 203 protected boolean getCanTakeFocus() 204 { 205 return false; 206 } 207 208 /** 209 * @see org.apache.tapestry.form.AbstractFormComponent#getRenderBodyOnRewind() 210 */ 211 protected boolean getAlwaysRenderBodyOnRewind() 212 { 213 return true; 214 } 215}