/* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You 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 * * http://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. * * $Header:$ */ package org.apache.beehive.netui.tags.html; import org.apache.beehive.netui.util.internal.InternalStringBuilder; import org.apache.beehive.netui.pageflow.ProcessPopulate; import org.apache.beehive.netui.pageflow.RequestParameterHandler; import org.apache.beehive.netui.script.common.DataAccessProviderStack; import org.apache.beehive.netui.script.common.IDataAccessProvider; import org.apache.beehive.netui.tags.ByRef; import org.apache.beehive.netui.tags.naming.FormDataNameInterceptor; import org.apache.beehive.netui.tags.naming.IndexedNameInterceptor; import org.apache.beehive.netui.tags.naming.PrefixNameInterceptor; import org.apache.beehive.netui.tags.rendering.*; import org.apache.beehive.netui.util.Bundle; import org.apache.beehive.netui.util.iterator.ArrayIterator; import org.apache.beehive.netui.util.iterator.IteratorFactory; import org.apache.beehive.netui.util.logging.Logger; import javax.servlet.ServletRequest; import javax.servlet.jsp.JspException; import java.util.*; /** * Renders a select containing a set of SelectOptions. * * Select binds to an Iterator of Strings. * * If Select uses any Format tags, it must have those tags come before any nested * SelectOption tags. * @jsptagref.tagdescription Renders an HTML <select> tag containing a set of selectable options. * *

The <netui:select> tag can generate a set of * selectable options in two ways: * *

*
    *
  1. they can be dynamically generated by pointing the * <netui:select> tag at a String[] object or * {@link java.util.HashMap java.util.HashMap}
  2. *
  3. they can be statically generated by providing a set of children * {@link SelectOption} * tags
  4. *
*
* *

Dynamically Generated Options * *

You can dynamically generate a set of selectable options by * pointing the <netui:select> tag at a String[]. * *

    public String[] colors = {"red", "green", "blue", "orange", "pink", "aqua", "black", "brown", "tan"};
 *
 *    public String[] getColors()
 *    {
 *        return colors;
 *    }
* *

To point the <netui:select> tag at the String[] object use the * optionsDataSource attribute.

* *
    <netui:select dataSource="actionForm.selection"
 *                  optionsDataSource="${pageFlow.colors}"/>
* * Note that you can make the display value and the submitted value differ by pointing the * optionsDataSource attribute of the <netui:select> tag at a HashMap object. * (Any object that implements the {@link java.util.Map java.util.Map} interface will work.) * *
    public HashMap optionsMap = new HashMap();
 *
 *    protected HashMap getOptionsMap()
 *    {
 *        return optionsMap;
 *    }
 *
 *    protected void onCreate()
 *    {
 *        optionsMap.put("#ff3333", "red");
 *        optionsMap.put("#3333ff", "blue");
 *        optionsMap.put("#33ff33", "green");
 *    }
* *

However, you cannot use a Map object if you choose to use the Select as a repeater * (setting the attribute repeater="true").

* *

Point the <netui:select> at the Map object using the optionsDataSource attribute.

* *
    <netui:select dataSource="actionForm.selection"
 *                  optionsDataSource="${pageFlow.optionsMap}"/>
* * The following HTML will be generated. * *
    <select name="wlw-select_key:{actionForm.selection}">
 *        <option value="#3333ff">blue</option>
 *        <option value="#33ff33">green</option>
 *        <option value="#ff3333">red</option>
 *    </select>
* *

Statically Generated Options

* *

To statically generate selecable options, place a set of <netui:selectOption> tags inside * the <netui:select> tag. * *

    <netui:select dataSource="actionForm.selection" size="5">
 *        <netui:selectOption value="red" />
 *        <netui:selectOption value="blue" />
 *        <netui:selectOption value="green" />
 *        <netui:selectOption value="yellow" />
 *        <netui:selectOption value="orange" />
 *    </netui:select>
* *

Submitting Selections

* *

A <netui:select> is submitted as a String or String[] object, depending on whether the * multiple attribute is set to true. In the following example, the dataSource * attribute points at a String[] object.

* *
    </netui:select dataSource="actionForm.selections"...
* *

In this case, the <netui:select> tag submits to a String[] field of a Form Bean.

* *
    public static class SubmitForm extends FormData
 *    {
 *        private String[] selections;
 *
 *        public void setSelections(String[] selections)
 *        {
 *            this.selections = selections;
 *        }
 *
 *        public String[] getSelections()
 *        {
 *            return this.selections;
 *        }
 *    }
* *

Use Select as a Repeater with Multiple Repeating Types

* *

Optionally, use the <netui:select> tag as a repeater to render multiple options * from the dataSource and defaultValue attributes as well as * the optionsDataSource. The <netui:select> element can dynamically generate * option elements for different repeating types of "option", "dataSource", "default", * (optionsDataSource, dataSource, and defaultValue attributes respectively) and "null". * The Select repeatingOrder attribute sets the order that repeating types * are generated. The repeatingType attribute on the <netui:selectOption> * tag identifies each of the types to be rendered.

* *

Use JSTL boolean conditional tags with the <netui:selectOption> elements * to help manage repeaters of different data types. * For example, the dataSource could point to a String[] while * the optionsDataSource points to an Object[] where each object has * name and value fields...

* *
    <netui:select dataSource="actionForm.selections"
 *                  optionsDataSource="${pageFlow.options}"
 *                  repeatingOrder="dataSource,option"
 *                  repeater="true" multiple="true">
 *        <c:if test="${container.metadata.dataSourceStage}">
 *            <netui:selectOption  repeatingType="dataSource" value="${container.item}">
 *                <netui:span value="${container.item}" />
 *            </netui:selectOption>
 *        </c:if>
 *        <c:if test="${container.metadata.optionStage}">
 *            <netui:selectOption  repeatingType="option" value="${container.item.name}">
 *                <netui:span value="${container.item.value}" />
 *            </netui:selectOption>
 *        </c:if>
 *    </netui:select>
 * 
* @example The following sample uses the optionsDataSource attribute to reference a * dynamically generated dropdown list. * *
 *    <netui:select dataSource="actionForm.selectedOption"
 *                  optionsDataSource="${actionForm.itemOptions}" />
 * 
* *

Assume that the optionsDataSource attribute refers to * a java.util.Map object. * The Map object will be rendered as a series * of <option> tags. HTML that is similar to the following will be * rendered in the browser:

* *
    <select name="wlw-select_key:{actionForm.itemOptions}">
 *        <option value="633">Aurora Bridge</option>
 *        <option value="631">FA-18 fighter jet</option>
 *        <option value="635">Space Needle</option>
 *        <option value="642">Thin Mints</option>
 * 	      ...
 *    </select>
* @netui:tag name="select" description="Defines a multiple-choice menu or drop-down list within a netui:form element." * @netui:attribute name="onSelect" hide="true" description="" */ public class Select extends HtmlOptionsDataSourceTag implements IDataAccessProvider, IFormattable { // @todo: needs to create DRT tests for: verification of errors, verirication of data sources matching, // @todo: verification of formating inside a repeater. // @todo: on the null tag, we need to default the null value to NULL_VALUE // @todo: need to handle null, in the options // @todo: should handle no optionDataSource when repeating... private static final Logger logger = Logger.getInstance(Select.class); private SelectTag.State _state = new SelectTag.State(); private OptionTag.State _optionState = new OptionTag.State(); private InputHiddenTag.State _hiddenState = new InputHiddenTag.State(); private boolean _formatterError = false; private static Object[] NULL_INSTANCE = {null}; /** * This enum defines stages through the possible option values. */ public static class RepeatingStages { private static final int INT_BEFORE = 0; private static final int INT_OPTION = 1; private static final int INT_DEFAULT = 2; private static final int INT_DATASOURCE = 3; private static final int INT_NULL = 4; private static final int INT_DONE = 5; static final RepeatingStages BEFORE = new RepeatingStages(INT_BEFORE); static final RepeatingStages OPTION = new RepeatingStages(INT_OPTION); static final RepeatingStages DEFAULT = new RepeatingStages(INT_DEFAULT); static final RepeatingStages DATASOURCE = new RepeatingStages(INT_DATASOURCE); static final RepeatingStages NULL = new RepeatingStages(INT_NULL); static final RepeatingStages DONE = new RepeatingStages(INT_DONE); /** * These are the publically exposed stages, REPEATING_OPTION, REPEATING_DEFAULT, * REPEATING_DATASOURCE and REPEATING_NULL. */ public static final String REPEATING_OPTION = "option"; public static final String REPEATING_DEFAULT = "default"; public static final String REPEATING_DATASOURCE = "dataSource"; public static final String REPEATING_NULL = "null"; public int value; // prevent construction... private RepeatingStages(int val) { value = val; } int getValue() { return value; } /** * Returns the String value that can be used to order the selection. * @return The String Value. */ public String toString() { switch (value) { case INT_OPTION: return REPEATING_OPTION; case INT_DEFAULT: return REPEATING_DEFAULT; case INT_DATASOURCE: return REPEATING_DATASOURCE; case INT_NULL: return REPEATING_NULL; default: return "Unknown Stage"; } } /** * Given a String value defined above, return the enum value for it. * @param value a String value matching one of the public Strings defined for the class. * @return the matching RepeatingStages or null. */ public static RepeatingStages parseString(String value) { if (REPEATING_OPTION.equals(value)) return OPTION; if (REPEATING_DEFAULT.equals(value)) return DEFAULT; if (REPEATING_DATASOURCE.equals(value)) return DATASOURCE; if (REPEATING_NULL.equals(value)) return NULL; return null; } } /** * This defines the default order of processing the options when repeating. */ private static final RepeatingStages[] DEFAULT_ORDER = {RepeatingStages.BEFORE, RepeatingStages.OPTION, RepeatingStages.DATASOURCE, RepeatingStages.DEFAULT, RepeatingStages.NULL}; /** * Default value of the options value attribute. */ public static final String NULL_VALUE = "netui_null"; /** * Constant value of the repeatingType attribute for options handling the null option. */ //public static final String REPEATING_NULL = "Null"; private static final String SELECT_KEY = "select_key"; private static final String OLDVALUE_SUFFIX = "OldValue"; // IDataAccessProvider support private int _repIdx = 0; // The current index for repeating over the optionsDataSource private RepeatingStages _repCurStage = RepeatingStages.BEFORE; // The current stage defined by the stage constants above private boolean _repeater; // Boolean flag indicating if this is a repeater or not private Object _repCurItem; // The current item access by the IDataAccessProvider private Iterator _repeaterIterator; // The iterator being used to output the options. private RepeatingStages[] _order = DEFAULT_ORDER; private Object _dynamicOptions; // The interator (or map) for the options data source, repeating this is current var private String _saveBody; private String _nullableOptionText; private List _defaultSelections; private ArrayList _formatters; private ArrayList _optionList; private String[] _match; // The actual values we will match against private boolean _nullable; private TagRenderingBase _optRb; private static final List _internalNamingChain; static { List l = new ArrayList(3); l.add(new FormDataNameInterceptor()); l.add(new IndexedNameInterceptor()); l.add(new PrefixNameInterceptor(SELECT_KEY)); _internalNamingChain = Collections.unmodifiableList(l); org.apache.beehive.netui.pageflow.ProcessPopulate.registerPrefixHandler(SELECT_KEY, new SelectPrefixHandler()); } /** */ public static class SelectPrefixHandler implements RequestParameterHandler { public void process(javax.servlet.http.HttpServletRequest request, String key, String expr, ProcessPopulate.ExpressionUpdateNode node) { String[] returnArray = null; if (!key.endsWith(OLDVALUE_SUFFIX)) { //This select has values and should stay that way returnArray = request.getParameterValues(key); } else { //Check the request to see if select also exists String newKey = key.substring(0, key.indexOf(OLDVALUE_SUFFIX)); String[] select = request.getParameterValues(newKey); if (select != null) { returnArray = select; } else { returnArray = new String[0]; //null; } } if (node.expression.endsWith(OLDVALUE_SUFFIX)) { node.expression = node.expression.substring(0, node.expression.indexOf(OLDVALUE_SUFFIX)); } //Check for the NULL_VALUE, replace it with null for (int i = 0; i < returnArray.length; i++) { if (returnArray[i].equals(NULL_VALUE)) { returnArray[i] = null; } } node.values = returnArray; if (logger.isDebugEnabled()) { logger.debug("\n*********************************************\n" + "process with key \"" + key + "\" and expression \"" + node.expression + "\"" + "and result size: " + (returnArray != null ? "" + returnArray.length : null) + "\n" + "*********************************************\n"); } } } public Select() { super(); } /** * Return the name of the Tag. */ public String getTagName() { return "Select"; } public String getDataSource() { return "{" + _dataSource.toString() + "}"; } /** * This method will return the state associated with the tag. This is used by this * base class to access the individual state objects created by the tags. * @return a subclass of the AbstractHtmlState class. */ protected AbstractHtmlState getState() { return _state; } /** * Return an ArrayList which represents a chain of INameInterceptor * objects. This method by default returns null and should be overridden * by objects that support naming. * @return an ArrayList that will contain INameInterceptor objects. */ protected List getNamingChain() { return _internalNamingChain; } /** * Evaluate the defaultValues */ protected Object evaluateDefaultValue() throws JspException { Object val = _defaultValue; List defaults = null; if (val instanceof String) { defaults = new ArrayList(); defaults.add(val); } else { Iterator optionsIterator = null; optionsIterator = IteratorFactory.createIterator(val); // default value is optional so only warn if (optionsIterator == null && _defaultValue != null) logger.warn(Bundle.getString("Tags_IteratorError", new Object[]{getTagName(), "defaultValue", _defaultValue})); if (optionsIterator == null) optionsIterator = IteratorFactory.EMPTY_ITERATOR; defaults = new ArrayList(); while (optionsIterator.hasNext()) { Object o = optionsIterator.next(); defaults.add(o.toString()); } } return defaults; } /** * Set whether multiple selections are allowed. * @param multiple the multiple value ("true" or "false") * @jsptagref.attributedescription Boolean. Whether or not multi-selection is enabled. * If multiple selection is enabled, a null option will not be displayed, even if * the nullable is set to true. * @jsptagref.databindable false * @jsptagref.attributesyntaxvalue boolean_multipleSelectEnabled * @netui:attribute required="false" rtexprvalue="true" type="boolean" * description="Whether or not multi-selection is enabled. * If multiple selection is enabled, a null option will not be displayed, even if * the nullable is set to true." */ public void setMultiple(boolean multiple) { _state.multiple = multiple; } /** * Set whether repeating of contained options is on. * @param repeater the repeater value ("true" or "false") * @jsptagref.attributedescription Set whether repeating of contained options is on. * @jsptagref.databindable false * @jsptagref.attributesyntaxvalue boolean_repeater * @netui:attribute required="false" rtexprvalue="true" type="boolean" * description="Set whether repeating of contained options is on." */ public void setRepeater(boolean repeater) { _repeater = repeater; } /** * Gets whether a repeating contained options is on. * @return the repeater value */ public boolean isRepeater() { return _repeater; } /** * This method will set the order of the options generated in the select. It must contain a * comma separated string listing the order or the stages that the repeating types are processed. * These values are "option", "dataSource", "default", and "null". * @param order comma separated ordering of items when there is a repeating select. * @jsptagref.attributedescription Define the order of options generated for a repeating Select. * It must contain a comma separated string listing the order or the stages that the repeating types * are processed. These values are "option", "dataSource", "default", and "null". For example, *
    repeatingOrder="dataSource,option"
* * Then a <netui:selectOption> element could set the repeatingType attribute to "dataSource" * while another is defined for "option". * @jsptagref.databindable false * @jsptagref.attributesyntaxvalue string_order * @netui:attribute required="false" rtexprvalue="true" * description="Define the order of options for a repeating Select" */ public void setRepeatingOrder(String order) throws JspException { String[] options = order.split(","); RepeatingStages[] stageOrder = new RepeatingStages[options.length + 1]; stageOrder[0] = RepeatingStages.BEFORE; for (int i = 0; i < options.length; i++) { String opt = options[i].trim(); stageOrder[i + 1] = RepeatingStages.parseString(opt); if (stageOrder[i + 1] == null) { String s = Bundle.getString("Tags_SelectBadRepeatingStage", new Object[]{opt}); registerTagError(s, null); } } _order = stageOrder; } /** * Set whether a null option is desired. * @param nullable the nullable value * @jsptagref.attributedescription Boolean. * Whether a option with the value null should be added to the bottom of the list. * If <select> has the multiple attribute set to true, the null option won't be shown. * @jsptagref.databindable false * @jsptagref.attributesyntaxvalue boolean_nullable * @netui:attribute required="false" rtexprvalue="true" type="boolean" * description="Whether a option with the value null should be added to the bottom of the list. * If