001/* 002 * Copyright 2002-2018 the original author or authors. 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * https://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016 017package org.springframework.web.servlet.tags.form; 018 019import javax.servlet.jsp.JspException; 020 021import org.springframework.lang.Nullable; 022import org.springframework.util.Assert; 023import org.springframework.util.ObjectUtils; 024import org.springframework.util.StringUtils; 025import org.springframework.web.servlet.support.BindStatus; 026import org.springframework.web.util.TagUtils; 027 028/** 029 * The {@code <options>} tag renders a list of HTML 'option' tags. 030 * Sets 'selected' as appropriate based on bound value. 031 * 032 * <p><i>Must</i> be used within a {@link SelectTag 'select' tag}. 033 * 034 * <p> 035 * <table> 036 * <caption>Attribute Summary</caption> 037 * <thead> 038 * <tr> 039 * <th class="colFirst">Attribute</th> 040 * <th class="colOne">Required?</th> 041 * <th class="colOne">Runtime Expression?</th> 042 * <th class="colLast">Description</th> 043 * </tr> 044 * </thead> 045 * <tbody> 046 * <tr class="altColor"> 047 * <td><p>cssClass</p></td> 048 * <td><p>false</p></td> 049 * <td><p>true</p></td> 050 * <td><p>HTML Optional Attribute</p></td> 051 * </tr> 052 * <tr class="rowColor"> 053 * <td><p>cssErrorClass</p></td> 054 * <td><p>false</p></td> 055 * <td><p>true</p></td> 056 * <td><p>HTML Optional Attribute. Used when the bound field has errors.</p></td> 057 * </tr> 058 * <tr class="altColor"> 059 * <td><p>cssStyle</p></td> 060 * <td><p>false</p></td> 061 * <td><p>true</p></td> 062 * <td><p>HTML Optional Attribute</p></td> 063 * </tr> 064 * <tr class="rowColor"> 065 * <td><p>dir</p></td> 066 * <td><p>false</p></td> 067 * <td><p>true</p></td> 068 * <td><p>HTML Standard Attribute</p></td> 069 * </tr> 070 * <tr class="altColor"> 071 * <td><p>disabled</p></td> 072 * <td><p>false</p></td> 073 * <td><p>true</p></td> 074 * <td><p>HTML Optional Attribute. Setting the value of this attribute 075 * to 'true' will disable the HTML element.</p></td> 076 * </tr> 077 * <tr class="rowColor"> 078 * <td><p>htmlEscape</p></td> 079 * <td><p>false</p></td> 080 * <td><p>true</p></td> 081 * <td><p>Enable/disable HTML escaping of rendered values.</p></td> 082 * </tr> 083 * <tr class="altColor"> 084 * <td><p>id</p></td> 085 * <td><p>false</p></td> 086 * <td><p>true</p></td> 087 * <td><p>HTML Standard Attribute</p></td> 088 * </tr> 089 * <tr class="rowColor"> 090 * <td><p>itemLabel</p></td> 091 * <td><p>false</p></td> 092 * <td><p>true</p></td> 093 * <td><p>Name of the property mapped to the inner text of the 'option' tag</p></td> 094 * </tr> 095 * <tr class="altColor"> 096 * <td><p>items</p></td> 097 * <td><p>true</p></td> 098 * <td><p>true</p></td> 099 * <td><p>The Collection, Map or array of objects used to generate the inner 'option' tags</p></td> 100 * </tr> 101 * <tr class="rowColor"> 102 * <td><p>itemValue</p></td> 103 * <td><p>false</p></td> 104 * <td><p>true</p></td> 105 * <td><p>Name of the property mapped to 'value' attribute of the 'option' tag</p></td> 106 * </tr> 107 * <tr class="altColor"> 108 * <td><p>lang</p></td> 109 * <td><p>false</p></td> 110 * <td><p>true</p></td> 111 * <td><p>HTML Standard Attribute</p></td> 112 * </tr> 113 * <tr class="rowColor"> 114 * <td><p>onclick</p></td> 115 * <td><p>false</p></td> 116 * <td><p>true</p></td> 117 * <td><p>HTML Event Attribute</p></td> 118 * </tr> 119 * <tr class="altColor"> 120 * <td><p>ondblclick</p></td> 121 * <td><p>false</p></td> 122 * <td><p>true</p></td> 123 * <td><p>HTML Event Attribute</p></td> 124 * </tr> 125 * <tr class="rowColor"> 126 * <td><p>onkeydown</p></td> 127 * <td><p>false</p></td> 128 * <td><p>true</p></td> 129 * <td><p>HTML Event Attribute</p></td> 130 * </tr> 131 * <tr class="altColor"> 132 * <td><p>onkeypress</p></td> 133 * <td><p>false</p></td> 134 * <td><p>true</p></td> 135 * <td><p>HTML Event Attribute</p></td> 136 * </tr> 137 * <tr class="rowColor"> 138 * <td><p>onkeyup</p></td> 139 * <td><p>false</p></td> 140 * <td><p>true</p></td> 141 * <td><p>HTML Event Attribute</p></td> 142 * </tr> 143 * <tr class="altColor"> 144 * <td><p>onmousedown</p></td> 145 * <td><p>false</p></td> 146 * <td><p>true</p></td> 147 * <td><p>HTML Event Attribute</p></td> 148 * </tr> 149 * <tr class="rowColor"> 150 * <td><p>onmousemove</p></td> 151 * <td><p>false</p></td> 152 * <td><p>true</p></td> 153 * <td><p>HTML Event Attribute</p></td> 154 * </tr> 155 * <tr class="altColor"> 156 * <td><p>onmouseout</p></td> 157 * <td><p>false</p></td> 158 * <td><p>true</p></td> 159 * <td><p>HTML Event Attribute</p></td> 160 * </tr> 161 * <tr class="rowColor"> 162 * <td><p>onmouseover</p></td> 163 * <td><p>false</p></td> 164 * <td><p>true</p></td> 165 * <td><p>HTML Event Attribute</p></td> 166 * </tr> 167 * <tr class="altColor"> 168 * <td><p>onmouseup</p></td> 169 * <td><p>false</p></td> 170 * <td><p>true</p></td> 171 * <td><p>HTML Event Attribute</p></td> 172 * </tr> 173 * <tr class="rowColor"> 174 * <td><p>tabindex</p></td> 175 * <td><p>false</p></td> 176 * <td><p>true</p></td> 177 * <td><p>HTML Standard Attribute</p></td> 178 * </tr> 179 * <tr class="altColor"> 180 * <td><p>title</p></td> 181 * <td><p>false</p></td> 182 * <td><p>true</p></td> 183 * <td><p>HTML Standard Attribute</p></td> 184 * </tr> 185 * </tbody> 186 * </table> 187 * 188 * @author Rob Harrop 189 * @author Juergen Hoeller 190 * @author Scott Andrews 191 * @since 2.0 192 */ 193@SuppressWarnings("serial") 194public class OptionsTag extends AbstractHtmlElementTag { 195 196 /** 197 * The {@link java.util.Collection}, {@link java.util.Map} or array of 198 * objects used to generate the inner '{@code option}' tags. 199 */ 200 @Nullable 201 private Object items; 202 203 /** 204 * The name of the property mapped to the '{@code value}' attribute 205 * of the '{@code option}' tag. 206 */ 207 @Nullable 208 private String itemValue; 209 210 /** 211 * The name of the property mapped to the inner text of the 212 * '{@code option}' tag. 213 */ 214 @Nullable 215 private String itemLabel; 216 217 private boolean disabled; 218 219 220 /** 221 * Set the {@link java.util.Collection}, {@link java.util.Map} or array 222 * of objects used to generate the inner '{@code option}' tags. 223 * <p>Required when wishing to render '{@code option}' tags from an 224 * array, {@link java.util.Collection} or {@link java.util.Map}. 225 * <p>Typically a runtime expression. 226 */ 227 public void setItems(Object items) { 228 this.items = items; 229 } 230 231 /** 232 * Get the {@link java.util.Collection}, {@link java.util.Map} or array 233 * of objects used to generate the inner '{@code option}' tags. 234 * <p>Typically a runtime expression. 235 */ 236 @Nullable 237 protected Object getItems() { 238 return this.items; 239 } 240 241 /** 242 * Set the name of the property mapped to the '{@code value}' 243 * attribute of the '{@code option}' tag. 244 * <p>Required when wishing to render '{@code option}' tags from 245 * an array or {@link java.util.Collection}. 246 */ 247 public void setItemValue(String itemValue) { 248 Assert.hasText(itemValue, "'itemValue' must not be empty"); 249 this.itemValue = itemValue; 250 } 251 252 /** 253 * Return the name of the property mapped to the '{@code value}' 254 * attribute of the '{@code option}' tag. 255 */ 256 @Nullable 257 protected String getItemValue() { 258 return this.itemValue; 259 } 260 261 /** 262 * Set the name of the property mapped to the label (inner text) of the 263 * '{@code option}' tag. 264 */ 265 public void setItemLabel(String itemLabel) { 266 Assert.hasText(itemLabel, "'itemLabel' must not be empty"); 267 this.itemLabel = itemLabel; 268 } 269 270 /** 271 * Get the name of the property mapped to the label (inner text) of the 272 * '{@code option}' tag. 273 */ 274 @Nullable 275 protected String getItemLabel() { 276 return this.itemLabel; 277 } 278 279 /** 280 * Set the value of the '{@code disabled}' attribute. 281 */ 282 public void setDisabled(boolean disabled) { 283 this.disabled = disabled; 284 } 285 286 /** 287 * Get the value of the '{@code disabled}' attribute. 288 */ 289 protected boolean isDisabled() { 290 return this.disabled; 291 } 292 293 294 @Override 295 protected int writeTagContent(TagWriter tagWriter) throws JspException { 296 SelectTag selectTag = getSelectTag(); 297 Object items = getItems(); 298 Object itemsObject = null; 299 if (items != null) { 300 itemsObject = (items instanceof String ? evaluate("items", items) : items); 301 } 302 else { 303 Class<?> selectTagBoundType = selectTag.getBindStatus().getValueType(); 304 if (selectTagBoundType != null && selectTagBoundType.isEnum()) { 305 itemsObject = selectTagBoundType.getEnumConstants(); 306 } 307 } 308 if (itemsObject != null) { 309 String selectName = selectTag.getName(); 310 String itemValue = getItemValue(); 311 String itemLabel = getItemLabel(); 312 String valueProperty = 313 (itemValue != null ? ObjectUtils.getDisplayString(evaluate("itemValue", itemValue)) : null); 314 String labelProperty = 315 (itemLabel != null ? ObjectUtils.getDisplayString(evaluate("itemLabel", itemLabel)) : null); 316 OptionsWriter optionWriter = new OptionsWriter(selectName, itemsObject, valueProperty, labelProperty); 317 optionWriter.writeOptions(tagWriter); 318 } 319 return SKIP_BODY; 320 } 321 322 /** 323 * Appends a counter to a specified id, 324 * since we're dealing with multiple HTML elements. 325 */ 326 @Override 327 protected String resolveId() throws JspException { 328 Object id = evaluate("id", getId()); 329 if (id != null) { 330 String idString = id.toString(); 331 return (StringUtils.hasText(idString) ? TagIdGenerator.nextId(idString, this.pageContext) : null); 332 } 333 return null; 334 } 335 336 private SelectTag getSelectTag() { 337 TagUtils.assertHasAncestorOfType(this, SelectTag.class, "options", "select"); 338 return (SelectTag) findAncestorWithClass(this, SelectTag.class); 339 } 340 341 @Override 342 protected BindStatus getBindStatus() { 343 return (BindStatus) this.pageContext.getAttribute(SelectTag.LIST_VALUE_PAGE_ATTRIBUTE); 344 } 345 346 347 /** 348 * Inner class that adapts OptionWriter for multiple options to be rendered. 349 */ 350 private class OptionsWriter extends OptionWriter { 351 352 @Nullable 353 private final String selectName; 354 355 public OptionsWriter(@Nullable String selectName, Object optionSource, 356 @Nullable String valueProperty, @Nullable String labelProperty) { 357 358 super(optionSource, getBindStatus(), valueProperty, labelProperty, isHtmlEscape()); 359 this.selectName = selectName; 360 } 361 362 @Override 363 protected boolean isOptionDisabled() throws JspException { 364 return isDisabled(); 365 } 366 367 @Override 368 protected void writeCommonAttributes(TagWriter tagWriter) throws JspException { 369 writeOptionalAttribute(tagWriter, "id", resolveId()); 370 writeOptionalAttributes(tagWriter); 371 } 372 373 @Override 374 protected String processOptionValue(String value) { 375 return processFieldValue(this.selectName, value, "option"); 376 } 377 } 378 379}