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}