/*
* Copyright 2002-2018 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.web.servlet.tags.form;
import java.util.Collection;
import java.util.Map;
import javax.servlet.jsp.JspException;
import org.springframework.lang.Nullable;
import org.springframework.util.ObjectUtils;
import org.springframework.web.bind.WebDataBinder;
import org.springframework.web.servlet.support.BindStatus;
The <select>
tag renders an HTML 'select' element. Supports data binding to the selected option. Inner 'option
' tags can be rendered using one of the approaches supported by the OptionWriter class.
Also supports the use of nested OptionTags
or (typically one) nested OptionsTag
.
Attribute Summary
Attribute
Required?
Runtime Expression?
Description
accesskey
false
true
HTML Standard Attribute
cssClass
false
true
HTML Optional Attribute
cssErrorClass
false
true
HTML Optional Attribute. Used when the bound field has errors.
cssStyle
false
true
HTML Optional Attribute
dir
false
true
HTML Standard Attribute
disabled
false
true
HTML Optional Attribute. Setting the value of this attribute to 'true'
will disable the HTML element.
htmlEscape
false
true
Enable/disable HTML escaping of rendered values.
id
false
true
HTML Standard Attribute
itemLabel
false
true
Name of the property mapped to the inner text of the 'option' tag
items
false
true
The Collection, Map or array of objects used to generate the inner
'option' tags
itemValue
false
true
Name of the property mapped to 'value' attribute of the 'option'
tag
lang
false
true
HTML Standard Attribute
multiple
false
true
HTML Optional Attribute
onblur
false
true
HTML Event Attribute
onchange
false
true
HTML Event Attribute
onclick
false
true
HTML Event Attribute
ondblclick
false
true
HTML Event Attribute
onfocus
false
true
HTML Event Attribute
onkeydown
false
true
HTML Event Attribute
onkeypress
false
true
HTML Event Attribute
onkeyup
false
true
HTML Event Attribute
onmousedown
false
true
HTML Event Attribute
onmousemove
false
true
HTML Event Attribute
onmouseout
false
true
HTML Event Attribute
onmouseover
false
true
HTML Event Attribute
onmouseup
false
true
HTML Event Attribute
path
true
true
Path to property for data binding
size
false
true
HTML Optional Attribute
tabindex
false
true
HTML Standard Attribute
title
false
true
HTML Standard Attribute
Author: Rob Harrop, Juergen Hoeller See Also: Since: 2.0
/**
* The {@code <select>} tag renders an HTML 'select' element.
* Supports data binding to the selected option.
*
* <p>Inner '{@code option}' tags can be rendered using one of the
* approaches supported by the OptionWriter class.
*
* <p>Also supports the use of nested {@link OptionTag OptionTags} or
* (typically one) nested {@link OptionsTag}.
*
* <p>
* <table>
* <caption>Attribute Summary</caption>
* <thead>
* <tr>
* <th class="colFirst">Attribute</th>
* <th class="colOne">Required?</th>
* <th class="colOne">Runtime Expression?</th>
* <th class="colLast">Description</th>
* </tr>
* </thead>
* <tbody>
* <tr class="altColor">
* <td><p>accesskey</p></td>
* <td><p>false</p></td>
* <td><p>true</p></td>
* <td><p>HTML Standard Attribute</p></td>
* </tr>
* <tr class="rowColor">
* <td><p>cssClass</p></td>
* <td><p>false</p></td>
* <td><p>true</p></td>
* <td><p>HTML Optional Attribute</p></td>
* </tr>
* <tr class="altColor">
* <td><p>cssErrorClass</p></td>
* <td><p>false</p></td>
* <td><p>true</p></td>
* <td><p>HTML Optional Attribute. Used when the bound field has errors.</p></td>
* </tr>
* <tr class="rowColor">
* <td><p>cssStyle</p></td>
* <td><p>false</p></td>
* <td><p>true</p></td>
* <td><p>HTML Optional Attribute</p></td>
* </tr>
* <tr class="altColor">
* <td><p>dir</p></td>
* <td><p>false</p></td>
* <td><p>true</p></td>
* <td><p>HTML Standard Attribute</p></td>
* </tr>
* <tr class="rowColor">
* <td><p>disabled</p></td>
* <td><p>false</p></td>
* <td><p>true</p></td>
* <td><p>HTML Optional Attribute. Setting the value of this attribute to 'true'
* will disable the HTML element.</p></td>
* </tr>
* <tr class="altColor">
* <td><p>htmlEscape</p></td>
* <td><p>false</p></td>
* <td><p>true</p></td>
* <td><p>Enable/disable HTML escaping of rendered values.</p></td>
* </tr>
* <tr class="rowColor">
* <td><p>id</p></td>
* <td><p>false</p></td>
* <td><p>true</p></td>
* <td><p>HTML Standard Attribute</p></td>
* </tr>
* <tr class="altColor">
* <td><p>itemLabel</p></td>
* <td><p>false</p></td>
* <td><p>true</p></td>
* <td><p>Name of the property mapped to the inner text of the 'option' tag</p></td>
* </tr>
* <tr class="rowColor">
* <td><p>items</p></td>
* <td><p>false</p></td>
* <td><p>true</p></td>
* <td><p>The Collection, Map or array of objects used to generate the inner
* 'option' tags</p></td>
* </tr>
* <tr class="altColor">
* <td><p>itemValue</p></td>
* <td><p>false</p></td>
* <td><p>true</p></td>
* <td><p>Name of the property mapped to 'value' attribute of the 'option'
* tag</p></td>
* </tr>
* <tr class="rowColor">
* <td><p>lang</p></td>
* <td><p>false</p></td>
* <td><p>true</p></td>
* <td><p>HTML Standard Attribute</p></td>
* </tr>
* <tr class="altColor">
* <td><p>multiple</p></td>
* <td><p>false</p></td>
* <td><p>true</p></td>
* <td><p>HTML Optional Attribute</p></td>
* </tr>
* <tr class="rowColor">
* <td><p>onblur</p></td>
* <td><p>false</p></td>
* <td><p>true</p></td>
* <td><p>HTML Event Attribute</p></td>
* </tr>
* <tr class="altColor">
* <td><p>onchange</p></td>
* <td><p>false</p></td>
* <td><p>true</p></td>
* <td><p>HTML Event Attribute</p></td>
* </tr>
* <tr class="rowColor">
* <td><p>onclick</p></td>
* <td><p>false</p></td>
* <td><p>true</p></td>
* <td><p>HTML Event Attribute</p></td>
* </tr>
* <tr class="altColor">
* <td><p>ondblclick</p></td>
* <td><p>false</p></td>
* <td><p>true</p></td>
* <td><p>HTML Event Attribute</p></td>
* </tr>
* <tr class="rowColor">
* <td><p>onfocus</p></td>
* <td><p>false</p></td>
* <td><p>true</p></td>
* <td><p>HTML Event Attribute</p></td>
* </tr>
* <tr class="altColor">
* <td><p>onkeydown</p></td>
* <td><p>false</p></td>
* <td><p>true</p></td>
* <td><p>HTML Event Attribute</p></td>
* </tr>
* <tr class="rowColor">
* <td><p>onkeypress</p></td>
* <td><p>false</p></td>
* <td><p>true</p></td>
* <td><p>HTML Event Attribute</p></td>
* </tr>
* <tr class="altColor">
* <td><p>onkeyup</p></td>
* <td><p>false</p></td>
* <td><p>true</p></td>
* <td><p>HTML Event Attribute</p></td>
* </tr>
* <tr class="rowColor">
* <td><p>onmousedown</p></td>
* <td><p>false</p></td>
* <td><p>true</p></td>
* <td><p>HTML Event Attribute</p></td>
* </tr>
* <tr class="altColor">
* <td><p>onmousemove</p></td>
* <td><p>false</p></td>
* <td><p>true</p></td>
* <td><p>HTML Event Attribute</p></td>
* </tr>
* <tr class="rowColor">
* <td><p>onmouseout</p></td>
* <td><p>false</p></td>
* <td><p>true</p></td>
* <td><p>HTML Event Attribute</p></td>
* </tr>
* <tr class="altColor">
* <td><p>onmouseover</p></td>
* <td><p>false</p></td>
* <td><p>true</p></td>
* <td><p>HTML Event Attribute</p></td>
* </tr>
* <tr class="rowColor">
* <td><p>onmouseup</p></td>
* <td><p>false</p></td>
* <td><p>true</p></td>
* <td><p>HTML Event Attribute</p></td>
* </tr>
* <tr class="altColor">
* <td><p>path</p></td>
* <td><p>true</p></td>
* <td><p>true</p></td>
* <td><p>Path to property for data binding</p></td>
* </tr>
* <tr class="rowColor">
* <td><p>size</p></td>
* <td><p>false</p></td>
* <td><p>true</p></td>
* <td><p>HTML Optional Attribute</p></td>
* </tr>
* <tr class="altColor">
* <td><p>tabindex</p></td>
* <td><p>false</p></td>
* <td><p>true</p></td>
* <td><p>HTML Standard Attribute</p></td>
* </tr>
* <tr class="rowColor">
* <td><p>title</p></td>
* <td><p>false</p></td>
* <td><p>true</p></td>
* <td><p>HTML Standard Attribute</p></td>
* </tr>
* </tbody>
* </table>
*
* @author Rob Harrop
* @author Juergen Hoeller
* @since 2.0
* @see OptionTag
*/
@SuppressWarnings("serial")
public class SelectTag extends AbstractHtmlInputElementTag {
The PageContext
attribute under which the bound value is exposed to inner OptionTags
. /**
* The {@link javax.servlet.jsp.PageContext} attribute under
* which the bound value is exposed to inner {@link OptionTag OptionTags}.
*/
public static final String LIST_VALUE_PAGE_ATTRIBUTE =
"org.springframework.web.servlet.tags.form.SelectTag.listValue";
Marker object for items that have been specified but resolve to null.
Allows to differentiate between 'set but null' and 'not set at all'.
/**
* Marker object for items that have been specified but resolve to null.
* Allows to differentiate between 'set but null' and 'not set at all'.
*/
private static final Object EMPTY = new Object();
/**
* The {@link Collection}, {@link Map} or array of objects used to generate
* the inner '{@code option}' tags.
*/
@Nullable
private Object items;
The name of the property mapped to the 'value
' attribute of the 'option
' tag. /**
* The name of the property mapped to the '{@code value}' attribute
* of the '{@code option}' tag.
*/
@Nullable
private String itemValue;
The name of the property mapped to the inner text of the 'option
' tag. /**
* The name of the property mapped to the inner text of the
* '{@code option}' tag.
*/
@Nullable
private String itemLabel;
The value of the HTML 'size
' attribute rendered on the final 'select
' element. /**
* The value of the HTML '{@code size}' attribute rendered
* on the final '{@code select}' element.
*/
@Nullable
private String size;
Indicates whether or not the 'select
' tag allows multiple-selections. /**
* Indicates whether or not the '{@code select}' tag allows
* multiple-selections.
*/
@Nullable
private Object multiple;
The TagWriter
instance that the output is being written. Only used in conjunction with nested OptionTags
.
/**
* The {@link TagWriter} instance that the output is being written.
* <p>Only used in conjunction with nested {@link OptionTag OptionTags}.
*/
@Nullable
private TagWriter tagWriter;
Set the Collection
, Map
or array of objects used to generate the inner 'option
' tags. Required when wishing to render 'option
' tags from an array, Collection
or Map
.
Typically a runtime expression.
Params: - items – the items that comprise the options of this selection
/**
* Set the {@link Collection}, {@link Map} or array of objects used to
* generate the inner '{@code option}' tags.
* <p>Required when wishing to render '{@code option}' tags from
* an array, {@link Collection} or {@link Map}.
* <p>Typically a runtime expression.
* @param items the items that comprise the options of this selection
*/
public void setItems(@Nullable Object items) {
this.items = (items != null ? items : EMPTY);
}
Get the value of the 'items
' attribute. May be a runtime expression.
/**
* Get the value of the '{@code items}' attribute.
* <p>May be a runtime expression.
*/
@Nullable
protected Object getItems() {
return this.items;
}
Set the name of the property mapped to the 'value
' attribute of the 'option
' tag. Required when wishing to render 'option
' tags from an array or Collection
.
May be a runtime expression.
/**
* Set the name of the property mapped to the '{@code value}'
* attribute of the '{@code option}' tag.
* <p>Required when wishing to render '{@code option}' tags from
* an array or {@link Collection}.
* <p>May be a runtime expression.
*/
public void setItemValue(String itemValue) {
this.itemValue = itemValue;
}
Get the value of the 'itemValue
' attribute. May be a runtime expression.
/**
* Get the value of the '{@code itemValue}' attribute.
* <p>May be a runtime expression.
*/
@Nullable
protected String getItemValue() {
return this.itemValue;
}
Set the name of the property mapped to the label (inner text) of the 'option
' tag. May be a runtime expression.
/**
* Set the name of the property mapped to the label (inner text) of the
* '{@code option}' tag.
* <p>May be a runtime expression.
*/
public void setItemLabel(String itemLabel) {
this.itemLabel = itemLabel;
}
Get the value of the 'itemLabel
' attribute. May be a runtime expression.
/**
* Get the value of the '{@code itemLabel}' attribute.
* <p>May be a runtime expression.
*/
@Nullable
protected String getItemLabel() {
return this.itemLabel;
}
Set the value of the HTML 'size
' attribute rendered on the final 'select
' element. /**
* Set the value of the HTML '{@code size}' attribute rendered
* on the final '{@code select}' element.
*/
public void setSize(String size) {
this.size = size;
}
Get the value of the 'size
' attribute. /**
* Get the value of the '{@code size}' attribute.
*/
@Nullable
protected String getSize() {
return this.size;
}
Set the value of the HTML 'multiple
' attribute rendered on the final 'select
' element. /**
* Set the value of the HTML '{@code multiple}' attribute rendered
* on the final '{@code select}' element.
*/
public void setMultiple(Object multiple) {
this.multiple = multiple;
}
Get the value of the HTML 'multiple
' attribute rendered on the final 'select
' element. /**
* Get the value of the HTML '{@code multiple}' attribute rendered
* on the final '{@code select}' element.
*/
@Nullable
protected Object getMultiple() {
return this.multiple;
}
Renders the HTML 'select
' tag to the supplied TagWriter
. Renders nested 'option
' tags if the items
property is set, otherwise exposes the bound value for the nested OptionTags
.
/**
* Renders the HTML '{@code select}' tag to the supplied
* {@link TagWriter}.
* <p>Renders nested '{@code option}' tags if the
* {@link #setItems items} property is set, otherwise exposes the
* bound value for the nested {@link OptionTag OptionTags}.
*/
@Override
protected int writeTagContent(TagWriter tagWriter) throws JspException {
tagWriter.startTag("select");
writeDefaultAttributes(tagWriter);
if (isMultiple()) {
tagWriter.writeAttribute("multiple", "multiple");
}
tagWriter.writeOptionalAttributeValue("size", getDisplayString(evaluate("size", getSize())));
Object items = getItems();
if (items != null) {
// Items specified, but might still be empty...
if (items != EMPTY) {
Object itemsObject = evaluate("items", items);
if (itemsObject != null) {
final String selectName = getName();
String valueProperty = (getItemValue() != null ?
ObjectUtils.getDisplayString(evaluate("itemValue", getItemValue())) : null);
String labelProperty = (getItemLabel() != null ?
ObjectUtils.getDisplayString(evaluate("itemLabel", getItemLabel())) : null);
OptionWriter optionWriter =
new OptionWriter(itemsObject, getBindStatus(), valueProperty, labelProperty, isHtmlEscape()) {
@Override
protected String processOptionValue(String resolvedValue) {
return processFieldValue(selectName, resolvedValue, "option");
}
};
optionWriter.writeOptions(tagWriter);
}
}
tagWriter.endTag(true);
writeHiddenTagIfNecessary(tagWriter);
return SKIP_BODY;
}
else {
// Using nested <form:option/> tags, so just expose the value in the PageContext...
tagWriter.forceBlock();
this.tagWriter = tagWriter;
this.pageContext.setAttribute(LIST_VALUE_PAGE_ATTRIBUTE, getBindStatus());
return EVAL_BODY_INCLUDE;
}
}
If using a multi-select, a hidden element is needed to make sure all items are correctly unselected on the server-side in response to a null
post. /**
* If using a multi-select, a hidden element is needed to make sure all
* items are correctly unselected on the server-side in response to a
* {@code null} post.
*/
private void writeHiddenTagIfNecessary(TagWriter tagWriter) throws JspException {
if (isMultiple()) {
tagWriter.startTag("input");
tagWriter.writeAttribute("type", "hidden");
String name = WebDataBinder.DEFAULT_FIELD_MARKER_PREFIX + getName();
tagWriter.writeAttribute("name", name);
tagWriter.writeAttribute("value", processFieldValue(name, "1", "hidden"));
tagWriter.endTag();
}
}
private boolean isMultiple() throws JspException {
Object multiple = getMultiple();
if (multiple != null) {
String stringValue = multiple.toString();
return ("multiple".equalsIgnoreCase(stringValue) || Boolean.parseBoolean(stringValue));
}
return forceMultiple();
}
Returns 'true
' if the bound value requires the resultant 'select
' tag to be multi-select. /**
* Returns '{@code true}' if the bound value requires the
* resultant '{@code select}' tag to be multi-select.
*/
private boolean forceMultiple() throws JspException {
BindStatus bindStatus = getBindStatus();
Class<?> valueType = bindStatus.getValueType();
if (valueType != null && typeRequiresMultiple(valueType)) {
return true;
}
else if (bindStatus.getEditor() != null) {
Object editorValue = bindStatus.getEditor().getValue();
if (editorValue != null && typeRequiresMultiple(editorValue.getClass())) {
return true;
}
}
return false;
}
/**
* Returns '{@code true}' for arrays, {@link Collection Collections}
* and {@link Map Maps}.
*/
private static boolean typeRequiresMultiple(Class<?> type) {
return (type.isArray() || Collection.class.isAssignableFrom(type) || Map.class.isAssignableFrom(type));
}
Closes any block tag that might have been opened when using nested options
. /**
* Closes any block tag that might have been opened when using
* nested {@link OptionTag options}.
*/
@Override
public int doEndTag() throws JspException {
if (this.tagWriter != null) {
this.tagWriter.endTag();
writeHiddenTagIfNecessary(this.tagWriter);
}
return EVAL_PAGE;
}
/**
* Clears the {@link TagWriter} that might have been left over when using
* nested {@link OptionTag options}.
*/
@Override
public void doFinally() {
super.doFinally();
this.tagWriter = null;
this.pageContext.removeAttribute(LIST_VALUE_PAGE_ATTRIBUTE);
}
}