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}