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.util.Assert; 024import org.springframework.web.servlet.support.BindStatus; 025import org.springframework.web.util.TagUtils; 026 027/** 028 * JSP tag for rendering an HTML '{@code option}' tag. 029 * 030 * <p><b>Must be used nested inside a {@link SelectTag}.</b> 031 * 032 * <p>Provides full support for databinding by marking an 033 * '{@code option}' as 'selected' if the {@link #setValue value} 034 * matches the value bound to the out {@link SelectTag}. 035 * 036 * <p>The {@link #setValue value} property is required and corresponds to 037 * the '{@code value}' attribute of the rendered '{@code option}'. 038 * 039 * <p>An optional {@link #setLabel label} property can be specified, the 040 * value of which corresponds to inner text of the rendered 041 * '{@code option}' tag. If no {@link #setLabel label} is specified 042 * then the {@link #setValue value} property will be used when rendering 043 * the inner text. 044 * 045 * @author Rob Harrop 046 * @author Juergen Hoeller 047 * @since 2.0 048 */ 049@SuppressWarnings("serial") 050public class OptionTag extends AbstractHtmlElementBodyTag implements BodyTag { 051 052 /** 053 * The name of the JSP variable used to expose the value for this tag. 054 */ 055 public static final String VALUE_VARIABLE_NAME = "value"; 056 057 /** 058 * The name of the JSP variable used to expose the display value for this tag. 059 */ 060 public static final String DISPLAY_VALUE_VARIABLE_NAME = "displayValue"; 061 062 /** 063 * The name of the '{@code selected}' attribute. 064 */ 065 private static final String SELECTED_ATTRIBUTE = "selected"; 066 067 /** 068 * The name of the '{@code value}' attribute. 069 */ 070 private static final String VALUE_ATTRIBUTE = VALUE_VARIABLE_NAME; 071 072 /** 073 * The name of the '{@code disabled}' attribute. 074 */ 075 private static final String DISABLED_ATTRIBUTE = "disabled"; 076 077 078 /** 079 * The 'value' attribute of the rendered HTML {@code <option>} tag. 080 */ 081 private Object value; 082 083 /** 084 * The text body of the rendered HTML {@code <option>} tag. 085 */ 086 private String label; 087 088 private Object oldValue; 089 090 private Object oldDisplayValue; 091 092 private boolean disabled; 093 094 095 /** 096 * Set the 'value' attribute of the rendered HTML {@code <option>} tag. 097 */ 098 public void setValue(Object value) { 099 this.value = value; 100 } 101 102 /** 103 * Get the 'value' attribute of the rendered HTML {@code <option>} tag. 104 */ 105 protected Object getValue() { 106 return this.value; 107 } 108 109 /** 110 * Set the value of the '{@code disabled}' attribute. 111 */ 112 public void setDisabled(boolean disabled) { 113 this.disabled = disabled; 114 } 115 116 /** 117 * Get the value of the '{@code disabled}' attribute. 118 */ 119 protected boolean isDisabled() { 120 return this.disabled; 121 } 122 123 /** 124 * Set the text body of the rendered HTML {@code <option>} tag. 125 * <p>May be a runtime expression. 126 */ 127 public void setLabel(String label) { 128 Assert.notNull(label, "'label' must not be null"); 129 this.label = label; 130 } 131 132 /** 133 * Get the text body of the rendered HTML {@code <option>} tag. 134 */ 135 protected String getLabel() { 136 return this.label; 137 } 138 139 140 @Override 141 protected void renderDefaultContent(TagWriter tagWriter) throws JspException { 142 Object value = this.pageContext.getAttribute(VALUE_VARIABLE_NAME); 143 String label = getLabelValue(value); 144 renderOption(value, label, tagWriter); 145 } 146 147 @Override 148 protected void renderFromBodyContent(BodyContent bodyContent, TagWriter tagWriter) throws JspException { 149 Object value = this.pageContext.getAttribute(VALUE_VARIABLE_NAME); 150 String label = bodyContent.getString(); 151 renderOption(value, label, tagWriter); 152 } 153 154 /** 155 * Make sure we are under a '{@code select}' tag before proceeding. 156 */ 157 @Override 158 protected void onWriteTagContent() { 159 assertUnderSelectTag(); 160 } 161 162 @Override 163 protected void exposeAttributes() throws JspException { 164 Object value = resolveValue(); 165 this.oldValue = this.pageContext.getAttribute(VALUE_VARIABLE_NAME); 166 this.pageContext.setAttribute(VALUE_VARIABLE_NAME, value); 167 this.oldDisplayValue = this.pageContext.getAttribute(DISPLAY_VALUE_VARIABLE_NAME); 168 this.pageContext.setAttribute(DISPLAY_VALUE_VARIABLE_NAME, getDisplayString(value, getBindStatus().getEditor())); 169 } 170 171 @Override 172 protected BindStatus getBindStatus() { 173 return (BindStatus) this.pageContext.getAttribute(SelectTag.LIST_VALUE_PAGE_ATTRIBUTE); 174 } 175 176 @Override 177 protected void removeAttributes() { 178 if (this.oldValue != null) { 179 this.pageContext.setAttribute(VALUE_ATTRIBUTE, this.oldValue); 180 this.oldValue = null; 181 } 182 else { 183 this.pageContext.removeAttribute(VALUE_VARIABLE_NAME); 184 } 185 186 if (this.oldDisplayValue != null) { 187 this.pageContext.setAttribute(DISPLAY_VALUE_VARIABLE_NAME, this.oldDisplayValue); 188 this.oldDisplayValue = null; 189 } 190 else { 191 this.pageContext.removeAttribute(DISPLAY_VALUE_VARIABLE_NAME); 192 } 193 } 194 195 private void renderOption(Object value, String label, TagWriter tagWriter) throws JspException { 196 tagWriter.startTag("option"); 197 writeOptionalAttribute(tagWriter, "id", resolveId()); 198 writeOptionalAttributes(tagWriter); 199 String renderedValue = getDisplayString(value, getBindStatus().getEditor()); 200 renderedValue = processFieldValue(getSelectTag().getName(), renderedValue, "option"); 201 tagWriter.writeAttribute(VALUE_ATTRIBUTE, renderedValue); 202 if (isSelected(value)) { 203 tagWriter.writeAttribute(SELECTED_ATTRIBUTE, SELECTED_ATTRIBUTE); 204 } 205 if (isDisabled()) { 206 tagWriter.writeAttribute(DISABLED_ATTRIBUTE, "disabled"); 207 } 208 tagWriter.appendValue(label); 209 tagWriter.endTag(); 210 } 211 212 @Override 213 protected String autogenerateId() throws JspException { 214 return null; 215 } 216 217 /** 218 * Return the value of the label for this '{@code option}' element. 219 * <p>If the {@link #setLabel label} property is set then the resolved value 220 * of that property is used, otherwise the value of the {@code resolvedValue} 221 * argument is used. 222 */ 223 private String getLabelValue(Object resolvedValue) throws JspException { 224 String label = getLabel(); 225 Object labelObj = (label == null ? resolvedValue : evaluate("label", label)); 226 return getDisplayString(labelObj, getBindStatus().getEditor()); 227 } 228 229 private void assertUnderSelectTag() { 230 TagUtils.assertHasAncestorOfType(this, SelectTag.class, "option", "select"); 231 } 232 233 private SelectTag getSelectTag() { 234 return (SelectTag) findAncestorWithClass(this, SelectTag.class); 235 } 236 237 private boolean isSelected(Object resolvedValue) { 238 return SelectedValueComparator.isSelected(getBindStatus(), resolvedValue); 239 } 240 241 private Object resolveValue() throws JspException { 242 return evaluate(VALUE_VARIABLE_NAME, getValue()); 243 } 244 245}