All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.springframework.web.servlet.tags.form.OptionWriter Maven / Gradle / Ivy

There is a newer version: 6.1.6
Show newest version
/*
 * Copyright 2002-2012 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.beans.PropertyEditor;
import java.util.Collection;
import java.util.Map;

import javax.servlet.jsp.JspException;

import org.springframework.beans.BeanWrapper;
import org.springframework.beans.PropertyAccessorFactory;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
import org.springframework.util.CollectionUtils;
import org.springframework.web.servlet.support.BindStatus;

/**
 * Provides supporting functionality to render a list of '{@code option}'
 * tags based on some source object. This object can be either an array, a
 * {@link Collection}, or a {@link Map}.
 * 

Using an array or a {@link Collection}:

*

* If you supply an array or {@link Collection} source object to render the * inner '{@code option}' tags, you may optionally specify the name of * the property on the objects which corresponds to the value of the * rendered '{@code option}' (i.e., the {@code valueProperty}) * and the name of the property that corresponds to the label (i.e., * the {@code labelProperty}). These properties are then used when * rendering each element of the array/{@link Collection} as an '{@code option}'. * If either property name is omitted, the value of {@link Object#toString()} of * the corresponding array/{@link Collection} element is used instead. However, * if the item is an enum, {@link Enum#name()} is used as the default value. *

*

Using a {@link Map}:

*

* You can alternatively choose to render '{@code option}' tags by * supplying a {@link Map} as the source object. *

*

* If you omit property names for the value and * label: *

*
    *
  • the {@code key} of each {@link Map} entry will correspond to the * value of the rendered '{@code option}', and
  • *
  • the {@code value} of each {@link Map} entry will correspond to * the label of the rendered '{@code option}'.
  • *
*

* If you supply property names for the value and * label: *

*
    *
  • the value of the rendered '{@code option}' will be * retrieved from the {@code valueProperty} on the object * corresponding to the {@code key} of each {@link Map} entry, and
  • *
  • the label of the rendered '{@code option}' will be * retrieved from the {@code labelProperty} on the object * corresponding to the {@code value} of each {@link Map} entry. *
*

When using either of these approaches:

*
    *
  • Property names for the value and label are * specified as arguments to the * {@link #OptionWriter(Object, BindStatus, String, String, boolean) constructor}.
  • *
  • An '{@code option}' is marked as 'selected' if its key * {@link #isOptionSelected matches} the value that is bound to the tag instance.
  • *
* * @author Rob Harrop * @author Juergen Hoeller * @author Sam Brannen * @author Scott Andrews * @since 2.0 */ class OptionWriter { private final Object optionSource; private final BindStatus bindStatus; @Nullable private final String valueProperty; @Nullable private final String labelProperty; private final boolean htmlEscape; /** * Create a new {@code OptionWriter} for the supplied {@code objectSource}. * @param optionSource the source of the {@code options} (never {@code null}) * @param bindStatus the {@link BindStatus} for the bound value (never {@code null}) * @param valueProperty the name of the property used to render {@code option} values * (optional) * @param labelProperty the name of the property used to render {@code option} labels * (optional) */ public OptionWriter(Object optionSource, BindStatus bindStatus, @Nullable String valueProperty, @Nullable String labelProperty, boolean htmlEscape) { Assert.notNull(optionSource, "'optionSource' must not be null"); Assert.notNull(bindStatus, "'bindStatus' must not be null"); this.optionSource = optionSource; this.bindStatus = bindStatus; this.valueProperty = valueProperty; this.labelProperty = labelProperty; this.htmlEscape = htmlEscape; } /** * Write the '{@code option}' tags for the configured {@link #optionSource} to * the supplied {@link TagWriter}. */ public void writeOptions(TagWriter tagWriter) throws JspException { if (this.optionSource.getClass().isArray()) { renderFromArray(tagWriter); } else if (this.optionSource instanceof Collection) { renderFromCollection(tagWriter); } else if (this.optionSource instanceof Map) { renderFromMap(tagWriter); } else if (this.optionSource instanceof Class && ((Class) this.optionSource).isEnum()) { renderFromEnum(tagWriter); } else { throw new JspException( "Type [" + this.optionSource.getClass().getName() + "] is not valid for option items"); } } /** * Render the inner '{@code option}' tags using the {@link #optionSource}. * @see #doRenderFromCollection(java.util.Collection, TagWriter) */ private void renderFromArray(TagWriter tagWriter) throws JspException { doRenderFromCollection(CollectionUtils.arrayToList(this.optionSource), tagWriter); } /** * Render the inner '{@code option}' tags using the supplied * {@link Map} as the source. * @see #renderOption(TagWriter, Object, Object, Object) */ private void renderFromMap(TagWriter tagWriter) throws JspException { Map optionMap = (Map) this.optionSource; for (Map.Entry entry : optionMap.entrySet()) { Object mapKey = entry.getKey(); Object mapValue = entry.getValue(); Object renderValue = (this.valueProperty != null ? PropertyAccessorFactory.forBeanPropertyAccess(mapKey).getPropertyValue(this.valueProperty) : mapKey); Object renderLabel = (this.labelProperty != null ? PropertyAccessorFactory.forBeanPropertyAccess(mapValue).getPropertyValue(this.labelProperty) : mapValue); renderOption(tagWriter, mapKey, renderValue, renderLabel); } } /** * Render the inner '{@code option}' tags using the {@link #optionSource}. * @see #doRenderFromCollection(java.util.Collection, TagWriter) */ private void renderFromCollection(TagWriter tagWriter) throws JspException { doRenderFromCollection((Collection) this.optionSource, tagWriter); } /** * Render the inner '{@code option}' tags using the {@link #optionSource}. * @see #doRenderFromCollection(java.util.Collection, TagWriter) */ private void renderFromEnum(TagWriter tagWriter) throws JspException { doRenderFromCollection(CollectionUtils.arrayToList(((Class) this.optionSource).getEnumConstants()), tagWriter); } /** * Render the inner '{@code option}' tags using the supplied {@link Collection} of * objects as the source. The value of the {@link #valueProperty} field is used * when rendering the '{@code value}' of the '{@code option}' and the value of the * {@link #labelProperty} property is used when rendering the label. */ private void doRenderFromCollection(Collection optionCollection, TagWriter tagWriter) throws JspException { for (Object item : optionCollection) { BeanWrapper wrapper = PropertyAccessorFactory.forBeanPropertyAccess(item); Object value; if (this.valueProperty != null) { value = wrapper.getPropertyValue(this.valueProperty); } else if (item instanceof Enum) { value = ((Enum) item).name(); } else { value = item; } Object label = (this.labelProperty != null ? wrapper.getPropertyValue(this.labelProperty) : item); renderOption(tagWriter, item, value, label); } } /** * Render an HTML '{@code option}' with the supplied value and label. Marks the * value as 'selected' if either the item itself or its value match the bound value. */ private void renderOption(TagWriter tagWriter, Object item, @Nullable Object value, @Nullable Object label) throws JspException { tagWriter.startTag("option"); writeCommonAttributes(tagWriter); String valueDisplayString = getDisplayString(value); String labelDisplayString = getDisplayString(label); valueDisplayString = processOptionValue(valueDisplayString); // allows render values to handle some strange browser compat issues. tagWriter.writeAttribute("value", valueDisplayString); if (isOptionSelected(value) || (value != item && isOptionSelected(item))) { tagWriter.writeAttribute("selected", "selected"); } if (isOptionDisabled()) { tagWriter.writeAttribute("disabled", "disabled"); } tagWriter.appendValue(labelDisplayString); tagWriter.endTag(); } /** * Determine the display value of the supplied {@code Object}, * HTML-escaped as required. */ private String getDisplayString(@Nullable Object value) { PropertyEditor editor = (value != null ? this.bindStatus.findEditor(value.getClass()) : null); return ValueFormatter.getDisplayString(value, editor, this.htmlEscape); } /** * Process the option value before it is written. *

The default implementation simply returns the same value unchanged. */ protected String processOptionValue(String resolvedValue) { return resolvedValue; } /** * Determine whether the supplied values matched the selected value. *

Delegates to {@link SelectedValueComparator#isSelected}. */ private boolean isOptionSelected(@Nullable Object resolvedValue) { return SelectedValueComparator.isSelected(this.bindStatus, resolvedValue); } /** * Determine whether the option fields should be disabled. */ protected boolean isOptionDisabled() throws JspException { return false; } /** * Write default attributes configured to the supplied {@link TagWriter}. */ protected void writeCommonAttributes(TagWriter tagWriter) throws JspException { } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy