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; 020 021import org.springframework.util.Assert; 022import org.springframework.util.ObjectUtils; 023import org.springframework.util.StringUtils; 024import org.springframework.web.servlet.support.BindStatus; 025import org.springframework.web.util.TagUtils; 026 027/** 028 * Convenient tag that allows one to supply a collection of objects 029 * that are to be rendered as '{@code option}' tags within a 030 * '{@code select}' tag. 031 * 032 * <p><i>Must</i> be used within a {@link SelectTag 'select' tag}. 033 * 034 * @author Rob Harrop 035 * @author Juergen Hoeller 036 * @author Scott Andrews 037 * @since 2.0 038 */ 039@SuppressWarnings("serial") 040public class OptionsTag extends AbstractHtmlElementTag { 041 042 /** 043 * The {@link java.util.Collection}, {@link java.util.Map} or array of 044 * objects used to generate the inner '{@code option}' tags. 045 */ 046 private Object items; 047 048 /** 049 * The name of the property mapped to the '{@code value}' attribute 050 * of the '{@code option}' tag. 051 */ 052 private String itemValue; 053 054 /** 055 * The name of the property mapped to the inner text of the 056 * '{@code option}' tag. 057 */ 058 private String itemLabel; 059 060 private boolean disabled; 061 062 063 /** 064 * Set the {@link java.util.Collection}, {@link java.util.Map} or array 065 * of objects used to generate the inner '{@code option}' tags. 066 * <p>Required when wishing to render '{@code option}' tags from an 067 * array, {@link java.util.Collection} or {@link java.util.Map}. 068 * <p>Typically a runtime expression. 069 */ 070 public void setItems(Object items) { 071 this.items = items; 072 } 073 074 /** 075 * Get the {@link java.util.Collection}, {@link java.util.Map} or array 076 * of objects used to generate the inner '{@code option}' tags. 077 * <p>Typically a runtime expression. 078 */ 079 protected Object getItems() { 080 return this.items; 081 } 082 083 /** 084 * Set the name of the property mapped to the '{@code value}' 085 * attribute of the '{@code option}' tag. 086 * <p>Required when wishing to render '{@code option}' tags from 087 * an array or {@link java.util.Collection}. 088 */ 089 public void setItemValue(String itemValue) { 090 Assert.hasText(itemValue, "'itemValue' must not be empty"); 091 this.itemValue = itemValue; 092 } 093 094 /** 095 * Return the name of the property mapped to the '{@code value}' 096 * attribute of the '{@code option}' tag. 097 */ 098 protected String getItemValue() { 099 return this.itemValue; 100 } 101 102 /** 103 * Set the name of the property mapped to the label (inner text) of the 104 * '{@code option}' tag. 105 */ 106 public void setItemLabel(String itemLabel) { 107 Assert.hasText(itemLabel, "'itemLabel' must not be empty"); 108 this.itemLabel = itemLabel; 109 } 110 111 /** 112 * Get the name of the property mapped to the label (inner text) of the 113 * '{@code option}' tag. 114 */ 115 protected String getItemLabel() { 116 return this.itemLabel; 117 } 118 119 /** 120 * Set the value of the '{@code disabled}' attribute. 121 */ 122 public void setDisabled(boolean disabled) { 123 this.disabled = disabled; 124 } 125 126 /** 127 * Get the value of the '{@code disabled}' attribute. 128 */ 129 protected boolean isDisabled() { 130 return this.disabled; 131 } 132 133 134 @Override 135 protected int writeTagContent(TagWriter tagWriter) throws JspException { 136 SelectTag selectTag = getSelectTag(); 137 Object items = getItems(); 138 Object itemsObject = null; 139 if (items != null) { 140 itemsObject = (items instanceof String ? evaluate("items", items) : items); 141 } 142 else { 143 Class<?> selectTagBoundType = selectTag.getBindStatus().getValueType(); 144 if (selectTagBoundType != null && selectTagBoundType.isEnum()) { 145 itemsObject = selectTagBoundType.getEnumConstants(); 146 } 147 } 148 if (itemsObject != null) { 149 String selectName = selectTag.getName(); 150 String itemValue = getItemValue(); 151 String itemLabel = getItemLabel(); 152 String valueProperty = 153 (itemValue != null ? ObjectUtils.getDisplayString(evaluate("itemValue", itemValue)) : null); 154 String labelProperty = 155 (itemLabel != null ? ObjectUtils.getDisplayString(evaluate("itemLabel", itemLabel)) : null); 156 OptionsWriter optionWriter = new OptionsWriter(selectName, itemsObject, valueProperty, labelProperty); 157 optionWriter.writeOptions(tagWriter); 158 } 159 return SKIP_BODY; 160 } 161 162 /** 163 * Appends a counter to a specified id, 164 * since we're dealing with multiple HTML elements. 165 */ 166 @Override 167 protected String resolveId() throws JspException { 168 Object id = evaluate("id", getId()); 169 if (id != null) { 170 String idString = id.toString(); 171 return (StringUtils.hasText(idString) ? TagIdGenerator.nextId(idString, this.pageContext) : null); 172 } 173 return null; 174 } 175 176 private SelectTag getSelectTag() { 177 TagUtils.assertHasAncestorOfType(this, SelectTag.class, "options", "select"); 178 return (SelectTag) findAncestorWithClass(this, SelectTag.class); 179 } 180 181 @Override 182 protected BindStatus getBindStatus() { 183 return (BindStatus) this.pageContext.getAttribute(SelectTag.LIST_VALUE_PAGE_ATTRIBUTE); 184 } 185 186 187 /** 188 * Inner class that adapts OptionWriter for multiple options to be rendered. 189 */ 190 private class OptionsWriter extends OptionWriter { 191 192 private final String selectName; 193 194 public OptionsWriter(String selectName, Object optionSource, String valueProperty, String labelProperty) { 195 super(optionSource, getBindStatus(), valueProperty, labelProperty, isHtmlEscape()); 196 this.selectName = selectName; 197 } 198 199 @Override 200 protected boolean isOptionDisabled() throws JspException { 201 return isDisabled(); 202 } 203 204 @Override 205 protected void writeCommonAttributes(TagWriter tagWriter) throws JspException { 206 writeOptionalAttribute(tagWriter, "id", resolveId()); 207 writeOptionalAttributes(tagWriter); 208 } 209 210 @Override 211 protected String processOptionValue(String value) { 212 return processFieldValue(this.selectName, value, "option"); 213 } 214 } 215 216}