/* * 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: * *
** **
*- they can be dynamically generated by pointing the * <netui:select> tag at a String[] object or * {@link java.util.HashMap java.util.HashMap}
*- they can be statically generated by providing a set of children * {@link SelectOption} * tags
*
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