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.Collection; 020import java.util.List; 021import java.util.Map; 022 023import org.apache.wicket.Page; 024import org.apache.wicket.markup.ComponentTag; 025import org.apache.wicket.model.IModel; 026import org.apache.wicket.settings.DebugSettings; 027import org.apache.wicket.util.lang.Args; 028import org.apache.wicket.util.string.AppendingStringBuffer; 029import org.apache.wicket.util.string.Strings; 030import org.apache.wicket.util.value.IValueMap; 031 032 033/** 034 * A choice subclass that shows choices via checkboxes. 035 * <p> 036 * Java: 037 * 038 * <pre> 039 * List SITES = Arrays.asList(new String[] { "The Server Side", "Java Lobby", "Java.Net" }); 040 * // Add a set of checkboxes uses Input's 'site' property to designate the 041 * // current selections, and that uses the SITES list for the available options. 042 * form.add(new CheckBoxMultipleChoice("site", SITES)); 043 * </pre> 044 * 045 * HTML: 046 * 047 * <pre> 048 * <span valign="top" wicket:id="site"> 049 * <input type="checkbox">site 1</input> 050 * <input type="checkbox">site 2</input> 051 * </span> 052 * </pre> 053 * 054 * </p> 055 * 056 * @author Jonathan Locke 057 * @author Johan Compagner 058 * @author Martijn Dashorst 059 * @author Gwyn Evans 060 * @author Igor Vaynberg (ivaynberg) 061 * 062 * @param <T> 063 * The model object type 064 */ 065public class CheckBoxMultipleChoice<T> extends ListMultipleChoice<T> 066{ 067 private static final long serialVersionUID = 1L; 068 069 private String prefix = ""; 070 private String suffix = ""; 071 072 private LabelPosition labelPosition = LabelPosition.AFTER; 073 074 /** 075 * Constructor 076 * 077 * @param id 078 * See Component 079 * @see org.apache.wicket.Component#Component(String) 080 * @see AbstractChoice#AbstractChoice(String) 081 */ 082 public CheckBoxMultipleChoice(final String id) 083 { 084 super(id); 085 } 086 087 /** 088 * Constructor 089 * 090 * @param id 091 * See Component 092 * @param choices 093 * The collection of choices in the radio choice 094 * @see org.apache.wicket.Component#Component(String) 095 * @see AbstractChoice#AbstractChoice(String, java.util.List) 096 */ 097 public CheckBoxMultipleChoice(final String id, final List<? extends T> choices) 098 { 099 super(id, choices); 100 } 101 102 /** 103 * Constructor 104 * 105 * @param id 106 * See Component 107 * @param renderer 108 * The rendering engine 109 * @param choices 110 * The collection of choices in the radio choice 111 * @see org.apache.wicket.Component#Component(String) 112 * @see AbstractChoice#AbstractChoice(String, 113 * java.util.List,org.apache.wicket.markup.html.form.IChoiceRenderer) 114 */ 115 public CheckBoxMultipleChoice(final String id, final List<? extends T> choices, 116 final IChoiceRenderer<? super T> renderer) 117 { 118 super(id, choices, renderer); 119 } 120 121 /** 122 * Constructor 123 * 124 * @param id 125 * See Component 126 * @param model 127 * See Component 128 * @param choices 129 * The collection of choices in the radio choice 130 * @see org.apache.wicket.Component#Component(String, org.apache.wicket.model.IModel) 131 * @see AbstractChoice#AbstractChoice(String, org.apache.wicket.model.IModel, java.util.List) 132 */ 133 public CheckBoxMultipleChoice(final String id, IModel<? extends Collection<T>> model, 134 final List<? extends T> choices) 135 { 136 super(id, model, choices); 137 } 138 139 /** 140 * Constructor 141 * 142 * @param id 143 * See Component 144 * @param model 145 * See Component 146 * @param choices 147 * The collection of choices in the radio choice 148 * @param renderer 149 * The rendering engine 150 * @see org.apache.wicket.Component#Component(String, org.apache.wicket.model.IModel) 151 * @see AbstractChoice#AbstractChoice(String, org.apache.wicket.model.IModel, 152 * java.util.List,org.apache.wicket.markup.html.form.IChoiceRenderer) 153 */ 154 public CheckBoxMultipleChoice(final String id, IModel<? extends Collection<T>> model, 155 final List<? extends T> choices, final IChoiceRenderer<? super T> renderer) 156 { 157 super(id, model, choices, renderer); 158 } 159 160 /** 161 * Constructor 162 * 163 * @param id 164 * See Component 165 * @param choices 166 * The collection of choices in the radio choice 167 * @see org.apache.wicket.Component#Component(String) 168 * @see AbstractChoice#AbstractChoice(String, org.apache.wicket.model.IModel) 169 */ 170 public CheckBoxMultipleChoice(String id, IModel<? extends List<? extends T>> choices) 171 { 172 super(id, choices); 173 } 174 175 /** 176 * Constructor 177 * 178 * @param id 179 * See Component 180 * @param model 181 * The model that is updated with changes in this component. See Component 182 * @param choices 183 * The collection of choices in the radio choice 184 * @see AbstractChoice#AbstractChoice(String, 185 * org.apache.wicket.model.IModel,org.apache.wicket.model.IModel) 186 * @see org.apache.wicket.Component#Component(String, org.apache.wicket.model.IModel) 187 */ 188 public CheckBoxMultipleChoice(String id, IModel<? extends Collection<T>> model, 189 IModel<? extends List<? extends T>> choices) 190 { 191 super(id, model, choices); 192 } 193 194 /** 195 * Constructor 196 * 197 * @param id 198 * See Component 199 * @param choices 200 * The collection of choices in the radio choice 201 * @param renderer 202 * The rendering engine 203 * @see AbstractChoice#AbstractChoice(String, 204 * org.apache.wicket.model.IModel,org.apache.wicket.markup.html.form.IChoiceRenderer) 205 * @see org.apache.wicket.Component#Component(String) 206 */ 207 public CheckBoxMultipleChoice(String id, IModel<? extends List<? extends T>> choices, 208 IChoiceRenderer<? super T> renderer) 209 { 210 super(id, choices, renderer); 211 } 212 213 /** 214 * Constructor 215 * 216 * @param id 217 * See Component 218 * @param model 219 * The model that is updated with changes in this component. See Component 220 * @param choices 221 * The collection of choices in the radio choice 222 * @param renderer 223 * The rendering engine 224 * @see org.apache.wicket.Component#Component(String, org.apache.wicket.model.IModel) 225 * @see AbstractChoice#AbstractChoice(String, org.apache.wicket.model.IModel, 226 * org.apache.wicket.model.IModel,org.apache.wicket.markup.html.form.IChoiceRenderer) 227 */ 228 public CheckBoxMultipleChoice(String id, IModel<? extends Collection<T>> model, 229 IModel<? extends List<? extends T>> choices, IChoiceRenderer<? super T> renderer) 230 { 231 super(id, model, choices, renderer); 232 } 233 234 /** 235 * @return Prefix to use before choice 236 */ 237 public String getPrefix() 238 { 239 return prefix; 240 } 241 242 /** 243 * @param index 244 * index of the choice 245 * @param choice 246 * the choice itself 247 * @return Prefix to use before choice. The default implementation just returns 248 * {@link #getPrefix()}. Override to have a prefix dependent on the choice item. 249 */ 250 protected String getPrefix(int index, T choice) 251 { 252 return getPrefix(); 253 } 254 255 /** 256 * @param index 257 * index of the choice 258 * @param choice 259 * the choice itself 260 * @return Separator to use between radio options. The default implementation just returns 261 * {@link #getSuffix()}. Override to have a prefix dependent on the choice item. 262 */ 263 protected String getSuffix(int index, T choice) 264 { 265 return getSuffix(); 266 } 267 268 /** 269 * @param prefix 270 * Prefix to use before choice 271 * @return this 272 */ 273 public final CheckBoxMultipleChoice<T> setPrefix(final String prefix) 274 { 275 // Tell the page that this component's prefix was changed 276 final Page page = findPage(); 277 if (page != null) 278 { 279 addStateChange(); 280 } 281 282 this.prefix = prefix; 283 return this; 284 } 285 286 /** 287 * @return Separator to use between radio options 288 */ 289 public String getSuffix() 290 { 291 return suffix; 292 } 293 294 /** 295 * @param suffix 296 * Separator to use between radio options 297 * @return this 298 */ 299 public final CheckBoxMultipleChoice<T> setSuffix(final String suffix) 300 { 301 // Tell the page that this component's suffix was changed 302 final Page page = findPage(); 303 if (page != null) 304 { 305 addStateChange(); 306 } 307 308 this.suffix = suffix; 309 return this; 310 } 311 312 /** 313 * Sets the preferred position of the <label> for each choice 314 * 315 * @param labelPosition 316 * The preferred position for the label 317 * @return {@code this} instance, for chaining 318 */ 319 public CheckBoxMultipleChoice<T> setLabelPosition(LabelPosition labelPosition) 320 { 321 Args.notNull(labelPosition, "labelPosition"); 322 this.labelPosition = labelPosition; 323 return this; 324 } 325 326 /** 327 * @see org.apache.wicket.markup.html.form.ListMultipleChoice#onComponentTag(org.apache.wicket.markup.ComponentTag) 328 */ 329 @Override 330 protected void onComponentTag(ComponentTag tag) 331 { 332 super.onComponentTag(tag); 333 // No longer applicable, breaks XHTML validation. 334 tag.remove("multiple"); 335 tag.remove("size"); 336 tag.remove("disabled"); 337 tag.remove("name"); 338 } 339 340 /** 341 * Generates and appends html for a single choice into the provided buffer 342 * 343 * @param buffer 344 * Appending string buffer that will have the generated html appended 345 * @param choice 346 * Choice object 347 * @param index 348 * The index of this option 349 * @param selected 350 * The currently selected string value 351 */ 352 @Override 353 protected void appendOptionHtml(final AppendingStringBuffer buffer, final T choice, int index, 354 final String selected) 355 { 356 // Append option suffix 357 buffer.append(getPrefix(index, choice)); 358 359 String id = getChoiceRenderer().getIdValue(choice, index); 360 final String idAttr = getCheckBoxMarkupId(id); 361 362 CharSequence renderValue = renderValue(choice); 363 364 // Allows user to add attributes to the <label..> tag 365 IValueMap labelAttrs = getAdditionalAttributesForLabel(index, choice); 366 StringBuilder extraLabelAttributes = new StringBuilder(); 367 if (labelAttrs != null) 368 { 369 for (Map.Entry<String, Object> attr : labelAttrs.entrySet()) 370 { 371 extraLabelAttributes.append(' ') 372 .append(Strings.escapeMarkup(attr.getKey())) 373 .append("=\"") 374 .append(Strings.escapeMarkup(attr.getValue().toString())) 375 .append('"'); 376 } 377 } 378 379 labelPosition.before(buffer, idAttr, extraLabelAttributes, renderValue); 380 381 // Add checkbox element 382 buffer.append("<input name=\""); 383 buffer.append(getInputName()); 384 buffer.append('"'); 385 buffer.append(" type=\"checkbox\""); 386 if (isSelected(choice, index, selected)) 387 { 388 buffer.append(" checked=\"checked\""); 389 } 390 if (isDisabled(choice, index, selected) || !isEnabledInHierarchy()) 391 { 392 buffer.append(" disabled=\"disabled\""); 393 } 394 buffer.append(" value=\""); 395 buffer.append(Strings.escapeMarkup(id)); 396 buffer.append("\" id=\""); 397 buffer.append(Strings.escapeMarkup(idAttr)); 398 buffer.append('"'); 399 400 // Allows user to add attributes to the <input..> tag 401 { 402 IValueMap attrs = getAdditionalAttributes(index, choice); 403 if (attrs != null) 404 { 405 for (Map.Entry<String, Object> attr : attrs.entrySet()) 406 { 407 buffer.append(' ') 408 .append(Strings.escapeMarkup(attr.getKey())) 409 .append("=\"") 410 .append(Strings.escapeMarkup(attr.getValue().toString())) 411 .append('"'); 412 } 413 } 414 } 415 416 DebugSettings debugSettings = getApplication().getDebugSettings(); 417 String componentPathAttributeName = debugSettings.getComponentPathAttributeName(); 418 if (Strings.isEmpty(componentPathAttributeName) == false) 419 { 420 CharSequence path = getPageRelativePath(); 421 path = Strings.replaceAll(path, "_", "__"); 422 path = Strings.replaceAll(path, ":", "_"); 423 buffer.append(' ').append(componentPathAttributeName).append("=\"") 424 .append(path) 425 .append("_input_") 426 .append(index) 427 .append('"'); 428 } 429 430 buffer.append("/>"); 431 432 labelPosition.after(buffer, idAttr, extraLabelAttributes, renderValue); 433 434 // Append option suffix 435 buffer.append(getSuffix(index, choice)); 436 } 437 438 /** 439 * You may subclass this method to provide additional attributes to the <label ..> tag. 440 * 441 * @param index 442 * index of the choice 443 * @param choice 444 * the choice itself 445 * @return tag attribute name/value pairs. 446 */ 447 protected IValueMap getAdditionalAttributesForLabel(int index, T choice) 448 { 449 return null; 450 } 451 452 /** 453 * You may subclass this method to provide additional attributes to the <input ..> tag. 454 * 455 * @param index 456 * @param choice 457 * @return tag attribute name/value pairs. 458 */ 459 protected IValueMap getAdditionalAttributes(final int index, final T choice) 460 { 461 return null; 462 } 463 464 /** 465 * Creates markup id for the input tag used to generate the checkbox for the element with the 466 * specified {@code id}. 467 * <p> 468 * NOTE It is useful to override this method if the contract for the generated ids should be 469 * fixed, for example in cases when the id generation pattern in this method is used to predict 470 * ids by some external javascript. If the contract is fixed in the user's code then upgrading 471 * wicket versions will guarantee not to break it should the default contract be changed at a 472 * later time. 473 * </p> 474 * 475 * @param id 476 * @return markup id for the input tag 477 */ 478 protected String getCheckBoxMarkupId(String id) 479 { 480 return getMarkupId() + '-' + getInputName() + '_' + id; 481 } 482}