001 // Copyright 2007, 2008, 2009, 2011 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.tapestry5.corelib.components; 016 017 import org.apache.tapestry5.*; 018 import org.apache.tapestry5.annotations.Environmental; 019 import org.apache.tapestry5.annotations.Events; 020 import org.apache.tapestry5.annotations.Parameter; 021 import org.apache.tapestry5.internal.TapestryInternalUtils; 022 import org.apache.tapestry5.ioc.annotations.Inject; 023 import org.apache.tapestry5.services.ComponentDefaultProvider; 024 import org.apache.tapestry5.services.Environment; 025 import org.apache.tapestry5.services.FormSupport; 026 import org.apache.tapestry5.services.Request; 027 028 /** 029 * A wrapper component around some number of {@link Radio} components, used to organize the selection and define the 030 * property to be edited. Examples of its use are in the {@link Radio} documentation. 031 * 032 * @tapestrydoc 033 */ 034 @Events(EventConstants.VALIDATE) 035 public class RadioGroup implements Field 036 { 037 /** 038 * The property read and updated by the group as a whole. 039 */ 040 @Parameter(required = true, principal = true, autoconnect = true) 041 private Object value; 042 043 /** 044 * If true, then the field will render out with a disabled attribute (to turn off client-side behavior). Further, a 045 * disabled field ignores any value in the request when the form is submitted. 046 */ 047 @Parameter("false") 048 private boolean disabled; 049 050 /** 051 * The user presentable label for the field. If not provided, a reasonable label is generated from the component's 052 * id, first by looking for a message key named "id-label" (substituting the component's actual id), then by 053 * converting the actual id to a presentable string (for example, "userId" to "User Id"). 054 */ 055 @Parameter(defaultPrefix = BindingConstants.LITERAL) 056 private String label; 057 058 /** 059 * The id used to generate a page-unique client-side identifier for the component. If a component renders multiple 060 * times, a suffix will be appended to the to id to ensure uniqueness. The uniqued value may be accessed via the 061 * {@link #getClientId() clientId property}. 062 */ 063 @Parameter(value = "prop:componentResources.id", defaultPrefix = BindingConstants.LITERAL) 064 private String clientId; 065 066 /** 067 * A ValueEncoder used to convert server-side objects (provided by the 068 * selected Radio componnent's "value" parameter) into unique client-side 069 * strings (typically IDs) and back. Note: this parameter may be OMITTED if 070 * Tapestry is configured to provide a ValueEncoder automatically for the 071 * type of property bound to the "value" parameter. 072 */ 073 @Parameter(required = true, allowNull = false) 074 private ValueEncoder encoder; 075 076 /** 077 * The object that will perform input validation. The validate binding prefix is generally used to provide this 078 * object in a declarative fashion. 079 */ 080 @Parameter(defaultPrefix = BindingConstants.VALIDATE) 081 @SuppressWarnings("unchecked") 082 private FieldValidator<Object> validate; 083 084 @Inject 085 private ComponentDefaultProvider defaultProvider; 086 087 @Inject 088 private ComponentResources resources; 089 090 @Environmental 091 private FormSupport formSupport; 092 093 @Inject 094 private Environment environment; 095 096 @Inject 097 private Request request; 098 099 @Environmental 100 private ValidationTracker tracker; 101 102 @Inject 103 private FieldValidationSupport fieldValidationSupport; 104 105 private String controlName; 106 107 String defaultLabel() 108 { 109 return defaultProvider.defaultLabel(resources); 110 } 111 112 final ValueEncoder defaultEncoder() 113 { 114 return defaultProvider.defaultValueEncoder("value", resources); 115 } 116 117 private static class Setup implements ComponentAction<RadioGroup> 118 { 119 private static final long serialVersionUID = -7984673040135949374L; 120 121 private final String controlName; 122 123 Setup(String controlName) 124 { 125 this.controlName = controlName; 126 } 127 128 public void execute(RadioGroup component) 129 { 130 component.setup(controlName); 131 } 132 133 @Override 134 public String toString() 135 { 136 return String.format("RadioGroup.Setup[%s]", controlName); 137 } 138 } 139 140 private static final ComponentAction<RadioGroup> PROCESS_SUBMISSION = new ComponentAction<RadioGroup>() 141 { 142 private static final long serialVersionUID = -3857110108918776386L; 143 144 public void execute(RadioGroup component) 145 { 146 component.processSubmission(); 147 } 148 149 @Override 150 public String toString() 151 { 152 return "RadioGroup.ProcessSubmission"; 153 } 154 }; 155 156 private void setup(String elementName) 157 { 158 controlName = elementName; 159 } 160 161 private void processSubmission() 162 { 163 String rawValue = request.getParameter(controlName); 164 165 tracker.recordInput(this, rawValue); 166 try 167 { 168 if (validate != null) 169 fieldValidationSupport.validate(rawValue, resources, validate); 170 } 171 catch (ValidationException ex) 172 { 173 tracker.recordError(this, ex.getMessage()); 174 } 175 176 value = encoder.toValue(rawValue); 177 } 178 179 /** 180 * Obtains the element name for the group, and stores a {@link RadioContainer} into the {@link Environment} (so that 181 * the {@link Radio} components can find it). 182 */ 183 final void setupRender() 184 { 185 ComponentAction<RadioGroup> action = new Setup(formSupport.allocateControlName(clientId)); 186 187 formSupport.storeAndExecute(this, action); 188 189 String submittedValue = tracker.getInput(this); 190 191 final String selectedValue = submittedValue != null ? submittedValue : encoder.toClient(value); 192 193 environment.push(RadioContainer.class, new RadioContainer() 194 { 195 public String getControlName() 196 { 197 return controlName; 198 } 199 200 public boolean isDisabled() 201 { 202 return disabled; 203 } 204 205 @SuppressWarnings("unchecked") 206 public String toClient(Object value) 207 { 208 // TODO: Ensure that value is of the expected type? 209 210 return encoder.toClient(value); 211 } 212 213 public boolean isSelected(Object value) 214 { 215 return TapestryInternalUtils.isEqual(encoder.toClient(value), selectedValue); 216 } 217 }); 218 219 formSupport.store(this, PROCESS_SUBMISSION); 220 } 221 222 /** 223 * Pops the {@link RadioContainer} off the Environment. 224 */ 225 final void afterRender() 226 { 227 environment.pop(RadioContainer.class); 228 } 229 230 public String getControlName() 231 { 232 return controlName; 233 } 234 235 public String getLabel() 236 { 237 return label; 238 } 239 240 public boolean isDisabled() 241 { 242 return disabled; 243 } 244 245 /** 246 * Returns null; the radio group does not render as a tag and so doesn't have an id to share. RadioGroup implements 247 * {@link org.apache.tapestry5.Field} only so it can interact with the 248 * {@link org.apache.tapestry5.ValidationTracker}. 249 * 250 * @return null 251 */ 252 public String getClientId() 253 { 254 return null; 255 } 256 257 public boolean isRequired() 258 { 259 return validate.isRequired(); 260 } 261 }