001/* 002 * Licensed to the Apache Software Foundation (ASF) under one or more 003 * contributor license agreements. See the NOTICE file distributed with 004 * this work for additional information regarding copyright ownership. 005 * The ASF licenses this file to You under the Apache License, Version 2.0 006 * (the "License"); you may not use this file except in compliance with 007 * the License. You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 */ 017package org.apache.wicket.markup.html.form; 018 019import java.util.List; 020 021import org.apache.wicket.Localizer; 022import org.apache.wicket.model.IModel; 023import org.apache.wicket.util.string.AppendingStringBuffer; 024import org.apache.wicket.util.string.Strings; 025 026 027/** 028 * Abstract base class for single-select choices. 029 * 030 * @author Jonathan Locke 031 * @author Eelco Hillenius nm 032 * @author Johan Compagner 033 * 034 * @param <T> 035 * The model object type 036 */ 037public abstract class AbstractSingleSelectChoice<T> extends AbstractChoice<T, T> 038{ 039 private static final long serialVersionUID = 1L; 040 041 /** String to display when the selected value is null and nullValid is false. */ 042 private static final String CHOOSE_ONE = "Choose One"; 043 044 /** whether or not null will be offered as a choice once a nonnull value is saved */ 045 private boolean nullValid = false; 046 047 /** 048 * Constructor. 049 * 050 * @param id 051 * See Component 052 */ 053 public AbstractSingleSelectChoice(final String id) 054 { 055 super(id); 056 } 057 058 /** 059 * Constructor. 060 * 061 * @param id 062 * See Component 063 * @param choices 064 * The collection of choices in the dropdown 065 */ 066 public AbstractSingleSelectChoice(final String id, final List<? extends T> choices) 067 { 068 super(id, choices); 069 } 070 071 /** 072 * Constructor. 073 * 074 * @param id 075 * See Component 076 * @param renderer 077 * The rendering engine 078 * @param choices 079 * The collection of choices in the dropdown 080 */ 081 public AbstractSingleSelectChoice(final String id, final List<? extends T> choices, 082 final IChoiceRenderer<? super T> renderer) 083 { 084 super(id, choices, renderer); 085 } 086 087 /** 088 * Constructor. 089 * 090 * @param id 091 * See Component 092 * @param model 093 * See Component 094 * @param choices 095 * The collection of choices in the dropdown 096 */ 097 public AbstractSingleSelectChoice(final String id, IModel<T> model, 098 final List<? extends T> choices) 099 { 100 super(id, model, choices); 101 } 102 103 /** 104 * Constructor. 105 * 106 * @param id 107 * See Component 108 * @param model 109 * See Component 110 * @param choices 111 * The drop down choices 112 * @param renderer 113 * The rendering engine 114 */ 115 public AbstractSingleSelectChoice(final String id, IModel<T> model, 116 final List<? extends T> choices, final IChoiceRenderer<? super T> renderer) 117 { 118 super(id, model, choices, renderer); 119 } 120 121 /** 122 * Constructor. 123 * 124 * @param id 125 * See Component 126 * @param choices 127 * The collection of choices in the dropdown 128 */ 129 public AbstractSingleSelectChoice(String id, IModel<? extends List<? extends T>> choices) 130 { 131 super(id, choices); 132 } 133 134 /** 135 * Constructor. 136 * 137 * @param id 138 * See Component 139 * @param model 140 * See Component 141 * @param choices 142 * The drop down choices 143 */ 144 public AbstractSingleSelectChoice(String id, IModel<T> model, 145 IModel<? extends List<? extends T>> choices) 146 { 147 super(id, model, choices); 148 } 149 150 /** 151 * Constructor. 152 * 153 * @param id 154 * See Component 155 * @param choices 156 * The drop down choices 157 * @param renderer 158 * The rendering engine 159 */ 160 public AbstractSingleSelectChoice(String id, IModel<? extends List<? extends T>> choices, 161 IChoiceRenderer<? super T> renderer) 162 { 163 super(id, choices, renderer); 164 } 165 166 /** 167 * Constructor. 168 * 169 * @param id 170 * See Component 171 * @param model 172 * See Component 173 * @param choices 174 * The drop down choices 175 * @param renderer 176 * The rendering engine 177 */ 178 public AbstractSingleSelectChoice(String id, IModel<T> model, 179 IModel<? extends List<? extends T>> choices, IChoiceRenderer<? super T> renderer) 180 { 181 super(id, model, choices, renderer); 182 } 183 184 /** 185 * @see FormComponent#getModelValue() 186 */ 187 @Override 188 public String getModelValue() 189 { 190 final T object = getModelObject(); 191 if (object != null) 192 { 193 int index = getChoices().indexOf(object); 194 return getChoiceRenderer().getIdValue(object, index); 195 } 196 else 197 { 198 return ""; 199 } 200 } 201 202 /** 203 * Determines whether or not the null value should be included in the list of choices when the 204 * field's model value is nonnull, and whether or not the null_valid string property (e.g. 205 * "Choose One") should be displayed until a nonnull value is selected. 206 * 207 * If set to false, then "Choose One" will be displayed when the value is null. After a value is 208 * selected, and that change is propagated to the underlying model, the user will no longer see 209 * the "Choose One" option, and there will be no way to reselect null as the value. 210 * 211 * If set to true, the null string property (the empty string, by default) will always be 212 * displayed as an option, whether or not a nonnull value has ever been selected. 213 * 214 * Note that this setting has no effect on validation; in order to guarantee that a value will 215 * be specified on form validation, {@link #setRequired(boolean)}. This is because even if 216 * setNullValid() is called with false, the user can fail to provide a value simply by never 217 * activating (i.e. clicking on) the component. 218 * 219 * @return <code>true</code> when the <code>null</code> value is allowed. 220 */ 221 public boolean isNullValid() 222 { 223 return nullValid; 224 } 225 226 /** 227 * Determines whether or not the null value should be included in the list of choices when the 228 * field's model value is nonnull, and whether or not the null_valid string property (e.g. 229 * "Choose One") should be displayed until a nonnull value is selected. 230 * 231 * If set to false, then "Choose One" will be displayed when the value is null. After a value is 232 * selected, and that change is propagated to the underlying model, the user will no longer see 233 * the "Choose One" option, and there will be no way to reselect null as the value. 234 * 235 * If set to true, the null string property (the empty string, by default) will always be 236 * displayed as an option, whether or not a nonnull value has ever been selected. 237 * 238 * Note that this setting has no effect on validation; in order to guarantee that a value will 239 * be specified on form validation, {@link #setRequired(boolean)}. This is because even if 240 * setNullValid() is called with false, the user can fail to provide a value simply by never 241 * activating (i.e. clicking on) the component. 242 * 243 * @param nullValid 244 * whether null is a valid value 245 * @return this for chaining 246 */ 247 public AbstractSingleSelectChoice<T> setNullValid(boolean nullValid) 248 { 249 this.nullValid = nullValid; 250 return this; 251 } 252 253 /** 254 * @see org.apache.wicket.markup.html.form.FormComponent#convertValue(String[]) 255 */ 256 @Override 257 protected final T convertValue(final String[] value) 258 { 259 String tmp = ((value != null) && (value.length > 0)) ? value[0] : null; 260 return convertChoiceIdToChoice(tmp); 261 } 262 263 /** 264 * Converts submitted choice id string back to choice object. 265 * 266 * @param id 267 * string id of one of the choice objects in the choices list. can be null. 268 * @return choice object. null if none match the specified id. 269 */ 270 protected T convertChoiceIdToChoice(String id) 271 { 272 final IModel<? extends List<? extends T>> choices = getChoicesModel(); 273 final IChoiceRenderer<? super T> renderer = getChoiceRenderer(); 274 T object = (T) renderer.getObject(id, choices); 275 return object; 276 } 277 278 /** 279 * Asks the {@link Localizer} for the property to display for an additional default choice 280 * depending on {@link #isNullValid()}: 281 * 282 * <ul> 283 * <li> 284 * "nullValid" if {@code null} is valid, defaulting to an empty string.</li> 285 * <li> 286 * "null" if {@code null} is not valid but no choice is selected (i.e. {@code selectedValue} is 287 * empty), defaulting to "Choose one".</li> 288 * </ul> 289 * 290 * Otherwise no additional default choice will be returned. 291 * 292 * @see #getNullValidKey() 293 * @see #getNullKey() 294 * @see org.apache.wicket.markup.html.form.AbstractChoice#getDefaultChoice(String) 295 */ 296 @Override 297 protected CharSequence getDefaultChoice(final String selectedValue) 298 { 299 // Is null a valid selection value? 300 if (isNullValid()) 301 { 302 // Null is valid, so look up the value for it 303 String option = getNullValidDisplayValue(); 304 305 // The <option> tag buffer 306 final AppendingStringBuffer buffer = new AppendingStringBuffer(64 + option.length()); 307 308 // Add option tag 309 buffer.append("\n<option"); 310 311 // If null is selected, indicate that 312 if (selectedValue != null && selectedValue.isEmpty()) 313 { 314 buffer.append(" selected=\"selected\""); 315 } 316 317 // Add body of option tag 318 buffer.append(" value=\"\">").append(option).append("</option>"); 319 return buffer; 320 } 321 else 322 { 323 // Null is not valid. Is it selected anyway? 324 if (selectedValue != null && selectedValue.isEmpty()) 325 { 326 // Force the user to pick a non-null value 327 String option = getNullKeyDisplayValue(); 328 return "\n<option selected=\"selected\" value=\"\">" + option + "</option>"; 329 } 330 } 331 return ""; 332 } 333 334 /** 335 * Returns the display value for the null value. The default behavior is to look the value up by 336 * using the key from <code>getNullValidKey()</code>. 337 * 338 * @return The value to display for null 339 */ 340 protected String getNullValidDisplayValue() 341 { 342 String option = getLocalizer().getStringIgnoreSettings(getNullValidKey(), this, null, null); 343 if (Strings.isEmpty(option)) 344 { 345 option = getLocalizer().getString("nullValid", this, ""); 346 } 347 return option; 348 } 349 350 /** 351 * Return the localization key for nullValid value 352 * 353 * @return getId() + ".nullValid" 354 */ 355 protected String getNullValidKey() 356 { 357 return getId() + ".nullValid"; 358 } 359 360 /** 361 * Returns the display value if null is not valid but is selected. The default behavior is to 362 * look the value up by using the key from <code>getNullKey()</code>. 363 * 364 * @return The value to display if null is not value but selected, e.g. "Choose One" 365 */ 366 protected String getNullKeyDisplayValue() 367 { 368 String option = getLocalizer().getStringIgnoreSettings(getNullKey(), this, null, null); 369 370 if (Strings.isEmpty(option)) 371 { 372 option = getLocalizer().getString("null", this, CHOOSE_ONE); 373 } 374 return option; 375 } 376 377 /** 378 * Return the localization key for null value 379 * 380 * @return getId() + ".null" 381 */ 382 protected String getNullKey() 383 { 384 return getId() + ".null"; 385 } 386 387 /** 388 * Gets whether the given value represents the current selection. 389 * 390 * 391 * @param object 392 * The object to check 393 * @param index 394 * The index of the object in the collection 395 * @param selected 396 * The current selected id value 397 * @return Whether the given value represents the current selection 398 */ 399 @Override 400 protected boolean isSelected(final T object, int index, String selected) 401 { 402 return (selected != null) && selected.equals(getChoiceRenderer().getIdValue(object, index)); 403 } 404}