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; 020import javax.servlet.jsp.tagext.BodyContent; 021import javax.servlet.jsp.tagext.BodyTag; 022 023import org.springframework.lang.Nullable; 024import org.springframework.web.servlet.support.BindStatus; 025import org.springframework.web.util.TagUtils; 026 027/** 028 * The {@code <option>} tag renders a single HTML 'option'. Sets 'selected' as 029 * appropriate based on bound value. 030 * 031 * <p><b>Must be used nested inside a {@link SelectTag}.</b> 032 * 033 * <p>Provides full support for databinding by marking an 034 * '{@code option}' as 'selected' if the {@link #setValue value} 035 * matches the value bound to the out {@link SelectTag}. 036 * 037 * <p>The {@link #setValue value} property is required and corresponds to 038 * the '{@code value}' attribute of the rendered '{@code option}'. 039 * 040 * <p>An optional {@link #setLabel label} property can be specified, the 041 * value of which corresponds to inner text of the rendered 042 * '{@code option}' tag. If no {@link #setLabel label} is specified 043 * then the {@link #setValue value} property will be used when rendering 044 * the inner text. 045 * 046 * <p> 047 * <table> 048 * <caption>Attribute Summary</caption> 049 * <thead> 050 * <tr> 051 * <th class="colFirst">Attribute</th> 052 * <th class="colOne">Required?</th> 053 * <th class="colOne">Runtime Expression?</th> 054 * <th class="colLast">Description</th> 055 * </tr> 056 * </thead> 057 * <tbody> 058 * <tr class="altColor"> 059 * <td><p>cssClass</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>cssErrorClass</p></td> 066 * <td><p>false</p></td> 067 * <td><p>true</p></td> 068 * <td><p>HTML Optional Attribute. Used when the bound field has 069 * errors.</p></td> 070 * </tr> 071 * <tr class="altColor"> 072 * <td><p>cssStyle</p></td> 073 * <td><p>false</p></td> 074 * <td><p>true</p></td> 075 * <td><p>HTML Optional Attribute</p></td> 076 * </tr> 077 * <tr class="rowColor"> 078 * <td><p>dir</p></td> 079 * <td><p>false</p></td> 080 * <td><p>true</p></td> 081 * <td><p>HTML Standard Attribute</p></td> 082 * </tr> 083 * <tr class="altColor"> 084 * <td><p>disabled</p></td> 085 * <td><p>false</p></td> 086 * <td><p>true</p></td> 087 * <td><p>HTML Optional Attribute. Setting the value of this attribute to 'true' 088 * will disable the HTML element.</p></td> 089 * </tr> 090 * <tr class="rowColor"> 091 * <td><p>htmlEscape</p></td> 092 * <td><p>false</p></td> 093 * <td><p>true</p></td> 094 * <td><p>Enable/disable HTML escaping of rendered values.</p></td> 095 * </tr> 096 * <tr class="altColor"> 097 * <td><p>id</p></td> 098 * <td><p>false</p></td> 099 * <td><p>true</p></td> 100 * <td><p>HTML Standard Attribute</p></td> 101 * </tr> 102 * <tr class="rowColor"> 103 * <td><p>label</p></td> 104 * <td><p>false</p></td> 105 * <td><p>true</p></td> 106 * <td><p>HTML Optional Attribute</p></td> 107 * </tr> 108 * <tr class="altColor"> 109 * <td><p>lang</p></td> 110 * <td><p>false</p></td> 111 * <td><p>true</p></td> 112 * <td><p>HTML Standard Attribute</p></td> 113 * </tr> 114 * <tr class="rowColor"> 115 * <td><p>onclick</p></td> 116 * <td><p>false</p></td> 117 * <td><p>true</p></td> 118 * <td><p>HTML Event Attribute</p></td> 119 * </tr> 120 * <tr class="altColor"> 121 * <td><p>ondblclick</p></td> 122 * <td><p>false</p></td> 123 * <td><p>true</p></td> 124 * <td><p>HTML Event Attribute</p></td> 125 * </tr> 126 * <tr class="rowColor"> 127 * <td><p>onkeydown</p></td> 128 * <td><p>false</p></td> 129 * <td><p>true</p></td> 130 * <td><p>HTML Event Attribute</p></td> 131 * </tr> 132 * <tr class="altColor"> 133 * <td><p>onkeypress</p></td> 134 * <td><p>false</p></td> 135 * <td><p>true</p></td> 136 * <td><p>HTML Event Attribute</p></td> 137 * </tr> 138 * <tr class="rowColor"> 139 * <td><p>onkeyup</p></td> 140 * <td><p>false</p></td> 141 * <td><p>true</p></td> 142 * <td><p>HTML Event Attribute</p></td> 143 * </tr> 144 * <tr class="altColor"> 145 * <td><p>onmousedown</p></td> 146 * <td><p>false</p></td> 147 * <td><p>true</p></td> 148 * <td><p>HTML Event Attribute</p></td> 149 * </tr> 150 * <tr class="rowColor"> 151 * <td><p>onmousemove</p></td> 152 * <td><p>false</p></td> 153 * <td><p>true</p></td> 154 * <td><p>HTML Event Attribute</p></td> 155 * </tr> 156 * <tr class="altColor"> 157 * <td><p>onmouseout</p></td> 158 * <td><p>false</p></td> 159 * <td><p>true</p></td> 160 * <td><p>HTML Event Attribute</p></td> 161 * </tr> 162 * <tr class="rowColor"> 163 * <td><p>onmouseover</p></td> 164 * <td><p>false</p></td> 165 * <td><p>true</p></td> 166 * <td><p>HTML Event Attribute</p></td> 167 * </tr> 168 * <tr class="altColor"> 169 * <td><p>onmouseup</p></td> 170 * <td><p>false</p></td> 171 * <td><p>true</p></td> 172 * <td><p>HTML Event Attribute</p></td> 173 * </tr> 174 * <tr class="rowColor"> 175 * <td><p>tabindex</p></td> 176 * <td><p>false</p></td> 177 * <td><p>true</p></td> 178 * <td><p>HTML Standard Attribute</p></td> 179 * </tr> 180 * <tr class="altColor"> 181 * <td><p>title</p></td> 182 * <td><p>false</p></td> 183 * <td><p>true</p></td> 184 * <td><p>HTML Standard Attribute</p></td> 185 * </tr> 186 * <tr class="rowColor"> 187 * <td><p>value</p></td> 188 * <td><p>true</p></td> 189 * <td><p>true</p></td> 190 * <td><p>HTML Optional Attribute</p></td> 191 * </tr> 192 * </tbody> 193 * </table> 194 * 195 * @author Rob Harrop 196 * @author Juergen Hoeller 197 * @since 2.0 198 */ 199@SuppressWarnings("serial") 200public class OptionTag extends AbstractHtmlElementBodyTag implements BodyTag { 201 202 /** 203 * The name of the JSP variable used to expose the value for this tag. 204 */ 205 public static final String VALUE_VARIABLE_NAME = "value"; 206 207 /** 208 * The name of the JSP variable used to expose the display value for this tag. 209 */ 210 public static final String DISPLAY_VALUE_VARIABLE_NAME = "displayValue"; 211 212 /** 213 * The name of the '{@code selected}' attribute. 214 */ 215 private static final String SELECTED_ATTRIBUTE = "selected"; 216 217 /** 218 * The name of the '{@code value}' attribute. 219 */ 220 private static final String VALUE_ATTRIBUTE = VALUE_VARIABLE_NAME; 221 222 /** 223 * The name of the '{@code disabled}' attribute. 224 */ 225 private static final String DISABLED_ATTRIBUTE = "disabled"; 226 227 228 /** 229 * The 'value' attribute of the rendered HTML {@code <option>} tag. 230 */ 231 @Nullable 232 private Object value; 233 234 /** 235 * The text body of the rendered HTML {@code <option>} tag. 236 */ 237 @Nullable 238 private String label; 239 240 @Nullable 241 private Object oldValue; 242 243 @Nullable 244 private Object oldDisplayValue; 245 246 private boolean disabled; 247 248 249 /** 250 * Set the 'value' attribute of the rendered HTML {@code <option>} tag. 251 */ 252 public void setValue(Object value) { 253 this.value = value; 254 } 255 256 /** 257 * Get the 'value' attribute of the rendered HTML {@code <option>} tag. 258 */ 259 @Nullable 260 protected Object getValue() { 261 return this.value; 262 } 263 264 /** 265 * Set the value of the '{@code disabled}' attribute. 266 */ 267 public void setDisabled(boolean disabled) { 268 this.disabled = disabled; 269 } 270 271 /** 272 * Get the value of the '{@code disabled}' attribute. 273 */ 274 protected boolean isDisabled() { 275 return this.disabled; 276 } 277 278 /** 279 * Set the text body of the rendered HTML {@code <option>} tag. 280 * <p>May be a runtime expression. 281 */ 282 public void setLabel(String label) { 283 this.label = label; 284 } 285 286 /** 287 * Get the text body of the rendered HTML {@code <option>} tag. 288 */ 289 @Nullable 290 protected String getLabel() { 291 return this.label; 292 } 293 294 295 @Override 296 protected void renderDefaultContent(TagWriter tagWriter) throws JspException { 297 Object value = this.pageContext.getAttribute(VALUE_VARIABLE_NAME); 298 String label = getLabelValue(value); 299 renderOption(value, label, tagWriter); 300 } 301 302 @Override 303 protected void renderFromBodyContent(BodyContent bodyContent, TagWriter tagWriter) throws JspException { 304 Object value = this.pageContext.getAttribute(VALUE_VARIABLE_NAME); 305 String label = bodyContent.getString(); 306 renderOption(value, label, tagWriter); 307 } 308 309 /** 310 * Make sure we are under a '{@code select}' tag before proceeding. 311 */ 312 @Override 313 protected void onWriteTagContent() { 314 assertUnderSelectTag(); 315 } 316 317 @Override 318 protected void exposeAttributes() throws JspException { 319 Object value = resolveValue(); 320 this.oldValue = this.pageContext.getAttribute(VALUE_VARIABLE_NAME); 321 this.pageContext.setAttribute(VALUE_VARIABLE_NAME, value); 322 this.oldDisplayValue = this.pageContext.getAttribute(DISPLAY_VALUE_VARIABLE_NAME); 323 this.pageContext.setAttribute(DISPLAY_VALUE_VARIABLE_NAME, getDisplayString(value, getBindStatus().getEditor())); 324 } 325 326 @Override 327 protected BindStatus getBindStatus() { 328 return (BindStatus) this.pageContext.getAttribute(SelectTag.LIST_VALUE_PAGE_ATTRIBUTE); 329 } 330 331 @Override 332 protected void removeAttributes() { 333 if (this.oldValue != null) { 334 this.pageContext.setAttribute(VALUE_ATTRIBUTE, this.oldValue); 335 this.oldValue = null; 336 } 337 else { 338 this.pageContext.removeAttribute(VALUE_VARIABLE_NAME); 339 } 340 341 if (this.oldDisplayValue != null) { 342 this.pageContext.setAttribute(DISPLAY_VALUE_VARIABLE_NAME, this.oldDisplayValue); 343 this.oldDisplayValue = null; 344 } 345 else { 346 this.pageContext.removeAttribute(DISPLAY_VALUE_VARIABLE_NAME); 347 } 348 } 349 350 private void renderOption(Object value, String label, TagWriter tagWriter) throws JspException { 351 tagWriter.startTag("option"); 352 writeOptionalAttribute(tagWriter, "id", resolveId()); 353 writeOptionalAttributes(tagWriter); 354 String renderedValue = getDisplayString(value, getBindStatus().getEditor()); 355 renderedValue = processFieldValue(getSelectTag().getName(), renderedValue, "option"); 356 tagWriter.writeAttribute(VALUE_ATTRIBUTE, renderedValue); 357 if (isSelected(value)) { 358 tagWriter.writeAttribute(SELECTED_ATTRIBUTE, SELECTED_ATTRIBUTE); 359 } 360 if (isDisabled()) { 361 tagWriter.writeAttribute(DISABLED_ATTRIBUTE, "disabled"); 362 } 363 tagWriter.appendValue(label); 364 tagWriter.endTag(); 365 } 366 367 @Override 368 protected String autogenerateId() throws JspException { 369 return null; 370 } 371 372 /** 373 * Return the value of the label for this '{@code option}' element. 374 * <p>If the {@link #setLabel label} property is set then the resolved value 375 * of that property is used, otherwise the value of the {@code resolvedValue} 376 * argument is used. 377 */ 378 private String getLabelValue(Object resolvedValue) throws JspException { 379 String label = getLabel(); 380 Object labelObj = (label == null ? resolvedValue : evaluate("label", label)); 381 return getDisplayString(labelObj, getBindStatus().getEditor()); 382 } 383 384 private void assertUnderSelectTag() { 385 TagUtils.assertHasAncestorOfType(this, SelectTag.class, "option", "select"); 386 } 387 388 private SelectTag getSelectTag() { 389 return (SelectTag) findAncestorWithClass(this, SelectTag.class); 390 } 391 392 private boolean isSelected(Object resolvedValue) { 393 return SelectedValueComparator.isSelected(getBindStatus(), resolvedValue); 394 } 395 396 @Nullable 397 private Object resolveValue() throws JspException { 398 return evaluate(VALUE_VARIABLE_NAME, getValue()); 399 } 400 401}