JdbcMapper/beehive-netui-tags/src/main/java/org/apache/beehive/netui/tags/html/Select.java

1357 lines
49 KiB
Java

/*
* 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.
*
* <p>The &lt;netui:select> tag can generate a set of
* selectable options in two ways:
*
* <blockquote>
* <ol>
* <li>they can be dynamically generated by pointing the
* &lt;netui:select> tag at a String[] object or
* {@link java.util.HashMap java.util.HashMap}</li>
* <li>they can be statically generated by providing a set of children
* {@link SelectOption}
* tags</li>
* </ol>
* </blockquote>
*
* <p><b>Dynamically Generated Options</b>
*
* <p>You can dynamically generate a set of selectable options by
* pointing the &lt;netui:select> tag at a String[].
*
* <pre> public String[] colors = {"red", "green", "blue", "orange", "pink", "aqua", "black", "brown", "tan"};
*
* public String[] getColors()
* {
* return colors;
* }</pre>
*
* <p>To point the &lt;netui:select> tag at the String[] object use the
* <code>optionsDataSource</code> attribute.</p>
*
* <pre> &lt;netui:select dataSource="actionForm.selection"
* optionsDataSource="${pageFlow.colors}"/></pre>
*
* Note that you can make the display value and the submitted value differ by pointing the
* optionsDataSource attribute of the &lt;netui:select> tag at a HashMap object.
* (Any object that implements the {@link java.util.Map java.util.Map} interface will work.)
*
* <pre> 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");
* }</pre>
*
* <p>However, you cannot use a Map object if you choose to use the Select as a repeater
* (setting the attribute repeater="true").</p>
*
* <p>Point the &lt;netui:select> at the Map object using the <code>optionsDataSource</code> attribute.</p>
*
* <pre> &lt;netui:select dataSource="actionForm.selection"
* optionsDataSource="${pageFlow.optionsMap}"/></pre>
*
* The following HTML will be generated.
*
* <pre> &lt;select name="wlw-select_key:{actionForm.selection}">
* &lt;option value="#3333ff">blue&lt;/option>
* &lt;option value="#33ff33">green&lt;/option>
* &lt;option value="#ff3333">red&lt;/option>
* &lt;/select></pre>
*
* <p><b>Statically Generated Options</b></p>
*
* <p>To statically generate selecable options, place a set of &lt;netui:selectOption> tags inside
* the &lt;netui:select> tag.
*
* <pre> &lt;netui:select dataSource="actionForm.selection" size="5">
* &lt;netui:selectOption value="red" />
* &lt;netui:selectOption value="blue" />
* &lt;netui:selectOption value="green" />
* &lt;netui:selectOption value="yellow" />
* &lt;netui:selectOption value="orange" />
* &lt;/netui:select></pre>
*
* <p><b>Submitting Selections</b></p>
*
* <p>A &lt;netui:select> is submitted as a String or String[] object, depending on whether the
* <code>multiple</code> attribute is set to true. In the following example, the <code>dataSource</code>
* attribute points at a String[] object.</p>
*
* <pre> &lt;/netui:select dataSource="actionForm.selections"...</pre>
*
* <p>In this case, the &lt;netui:select> tag submits to a String[] field of a Form Bean.</p>
*
* <pre> public static class SubmitForm extends FormData
* {
* private String[] selections;
*
* public void setSelections(String[] selections)
* {
* this.selections = selections;
* }
*
* public String[] getSelections()
* {
* return this.selections;
* }
* }</pre>
*
* <p><b>Use Select as a Repeater with Multiple Repeating Types</b></p>
*
* <p>Optionally, use the &lt;netui:select> tag as a repeater to render multiple options
* from the <code>dataSource</code> and <code>defaultValue</code> attributes as well as
* the <code>optionsDataSource</code>. The &lt;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 <code>repeatingOrder</code> attribute sets the order that repeating types
* are generated. The <code>repeatingType</code> attribute on the &lt;netui:selectOption>
* tag identifies each of the types to be rendered.</p>
*
* <p>Use JSTL boolean conditional tags with the &lt;netui:selectOption> elements
* to help manage repeaters of different data types.
* For example, the <code>dataSource</code> could point to a String[] while
* the <code>optionsDataSource</code> points to an Object[] where each object has
* name and value fields...</p>
*
* <pre> &lt;netui:select dataSource="actionForm.selections"
* optionsDataSource="${pageFlow.options}"
* repeatingOrder="dataSource,option"
* repeater="true" multiple="true">
* &lt;c:if test="${container.metadata.dataSourceStage}">
* &lt;netui:selectOption repeatingType="dataSource" value="${container.item}">
* &lt;netui:span value="${container.item}" />
* &lt;/netui:selectOption>
* &lt;/c:if>
* &lt;c:if test="${container.metadata.optionStage}">
* &lt;netui:selectOption repeatingType="option" value="${container.item.name}">
* &lt;netui:span value="${container.item.value}" />
* &lt;/netui:selectOption>
* &lt;/c:if>
* &lt;/netui:select>
* </pre>
* @example The following sample uses the <code>optionsDataSource</code> attribute to reference a
* dynamically generated dropdown list.
*
* <pre>
* &lt;netui:select dataSource="actionForm.selectedOption"
* optionsDataSource="${actionForm.itemOptions}" />
* </pre>
*
* <p>Assume that the <code>optionsDataSource</code> attribute refers to
* a <code>java.util.Map</code> object.
* The Map object will be rendered as a series
* of &lt;option> tags. HTML that is similar to the following will be
* rendered in the browser:</p>
*
* <pre> &lt;select name="wlw-select_key:{actionForm.itemOptions}">
* &lt;option value="633">Aurora Bridge&lt;/option>
* &lt;option value="631">FA-18 fighter jet&lt;/option>
* &lt;option value="635">Space Needle&lt;/option>
* &lt;option value="642">Thin Mints&lt;/option>
* ...
* &lt;/select></pre>
* @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, <code>REPEATING_OPTION, REPEATING_DEFAULT,
* REPEATING_DATASOURCE and REPEATING_NULL</code>.
*/
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 <code>value</code> attribute.
*/
public static final String NULL_VALUE = "netui_null";
/**
* Constant value of the <code>repeatingType</code> attribute for options handling the <code>null</code> 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 <code>AbstractHtmlState</code> class.
*/
protected AbstractHtmlState getState()
{
return _state;
}
/**
* Return an <code>ArrayList</code> which represents a chain of <code>INameInterceptor</code>
* objects. This method by default returns <code>null</code> and should be overridden
* by objects that support naming.
* @return an <code>ArrayList</code> that will contain <code>INameInterceptor</code> 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 <code>nullable</code> is set to true.
* @jsptagref.databindable false
* @jsptagref.attributesyntaxvalue <i>boolean_multipleSelectEnabled</i>
* @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 <i>boolean_repeater</i>
* @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,
* <pre> repeatingOrder="dataSource,option"</pre>
*
* Then a &lt;netui:selectOption> element could set the repeatingType attribute to "dataSource"
* while another is defined for "option".
* @jsptagref.databindable false
* @jsptagref.attributesyntaxvalue <i>string_order</i>
* @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 &lt;select> has the multiple <code>attribute</code> set to true, the null option won't be shown.
* @jsptagref.databindable false
* @jsptagref.attributesyntaxvalue <i>boolean_nullable</i>
* @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 <select> has the multiple attribute set to true, the null option won't be shown."
*/
public void setNullable(boolean nullable)
{
_nullable = nullable;
}
/**
* Gets the options datasource value (an expression).
* @return the options datasource
*/
public Object getOptionsDataSource()
{
return _optionsDataSource;
}
/**
* Set the text of the nullable option.
* If the <code>nullable<code> option is true, this is
* the text of that option. The default is "";
* @jsptagref.attributedescription Boolean.
* If the <code>nullable</code> attribute is set to true, then the <code>nullableOptionText</code>
* attribute determines the display text of the null option.
* The default is to use the empty string, "", as the display text.
* @jsptagref.databindable false
* @jsptagref.attributesyntaxvalue <i>boolean_nullableOptionText</i>
* @netui:attribute required="false" rtexprvalue="true" type="boolean"
* description="If the nullable attribute is set to true, then the nullableOptionText
* attribute determines the display text of the null option.
*/
public void setNullableOptionText(String nullableOptionText)
{
_nullableOptionText = nullableOptionText;
}
/**
* This method will return the object representing the <code>optionsDataSource</code>. This
* is overridden from the base class, because there are only two types which will be
* retunred from the method. The <code>optionsDataSource</code> will either be a instance of a <code>Map</code>
* or and instanceof a <code>Iterator</code>.
* @return the object instance object representing the objectsDataSource. This may be null.
* @throws JspException on an error
*/
protected Object evaluateOptionsDataSource()
throws JspException
{
Object val = _optionsDataSource;
if (val == null) {
// optionsDataSource is option so this is a warning
if (_optionsDataSource != null)
logger.warn(Bundle.getString("Tags_IteratorError",
new Object[]{getTagName(), "optionsDataSource", _optionsDataSource}));
return null;
}
if (val instanceof Map)
return val;
Iterator options = null;
options = IteratorFactory.createIterator(val);
if (options == null)
options = IteratorFactory.EMPTY_ITERATOR;
return options;
}
/**
* Sets how many options are displayed.
* @param size the size (a number)
* @jsptagref.attributedescription The number of visible options
* @jsptagref.databindable false
* @jsptagref.attributesyntaxvalue <i>integer_size</i>
* @netui:attribute required="false" rtexprvalue="true" type="int"
* description="The number of visible options"
*/
public void setSize(int size)
{
_state.size = size;
}
/**
* Does the specified value match one of those we are looking for?
* @param value Value to be compared
*/
public boolean isMatched(String value)
{
if (value == null)
return false;
if ((_match != null)) {
for (int i = 0; i < _match.length; i++) {
if (value.equals(_match[i]))
return true;
}
}
else {
if (_defaultSelections != null) {
return (_defaultSelections.contains(value));
}
}
return false;
}
//********************************** IDataAccessProvider Interface ******************************
// setDataSource is implemented by the HtmlDataSourceTag class
// getDataSource is implemented by the HtmlDataSourceTag class
/**
* Get the current index in this iteration. This should be a
* zero based integer that increments after each iteration.
* @return the current index of iteration or 0
*/
public int getCurrentIndex()
{
return _repIdx;
}
/**
* Get the current data item in this IDataAccessProvider.
* @return the current data item or <code>null</code>
*/
public Object getCurrentItem()
{
return _repCurItem;
}
/**
* Get a metadata object for the current item. This interface
* is optional, and implementations of this interface are
* provided by the IDataAccessProvider interface. See these
* implementations for information about their support for
* current item metadata.
* @return the current metadata or <code>null</code> if no metadata can be
* found or metadata is not supported by a IDataAccessProvider implementation
*/
public Object getCurrentMetadata()
{
return this;
}
/**
* Get the parent IDataAccessProvider of a IDataAccessProvider. A IDataAccessProvider
* implementation may be able to nest IDataAccessProviders. In this case,
* it can be useful to be able to also nest access to data from parent
* providers. Implementations of this interface are left with having
* to discover and export parents. The return value from this call
* on an implementing Object can be <code>null</code>.
* @return the parent IDataAccessProvider or <code>null</code> if this method
* is not supported or the parent can not be found.
*/
public IDataAccessProvider getProviderParent()
{
return (IDataAccessProvider) findAncestorWithClass(this, IDataAccessProvider.class);
}
/**
* Return the enum value of the currently repeating stage.
* @return The currently repeating stage.
*/
public RepeatingStages getRepeatingStage()
{
return _repCurStage;
}
/**
* Boolean indicating that we are processing the optionsDataSource.
* @return <code>true</code> if we are processing the optionsDataSource.
*/
public boolean isOptionStage()
{
return _repCurStage == RepeatingStages.OPTION;
}
/**
* Boolean indicating that we are processing the defaultValue.
* @return <code>true</code> if we are processing the defaultValue.
*/
public boolean isDefaultStage()
{
return _repCurStage == RepeatingStages.DEFAULT;
}
/**
* Boolean indicating that we are processing the dataSource.
* @return <code>true</code> if we are processing the dataSource.
*/
public boolean isDataSourceStage()
{
return _repCurStage == RepeatingStages.DATASOURCE;
}
/**
* Boolean indicating that we are processing the defined null value.
* @return <code>true</code> if we are processing the defined null value.
*/
public boolean isNullStage()
{
return _repCurStage == RepeatingStages.NULL;
}
/**
* Render the beginning of this select.
* @throws JspException if a JSP exception has occurred
*/
public int doStartTag() throws JspException
{
Object val = evaluateDataSource();
_defaultSelections = (List) evaluateDefaultValue();
// if there were expression errors report them
if (hasErrors())
return SKIP_BODY;
buildMatch(val);
if (hasErrors())
return SKIP_BODY;
_formatters = new ArrayList();
_optionList = new ArrayList();
// Walk the options data source
_dynamicOptions = evaluateOptionsDataSource();
if (_repeater) {
_repCurStage = _order[0];
boolean valid = doRepeaterAfterBody();
if (!valid)
return SKIP_BODY;
DataAccessProviderStack.addDataAccessProvider(this, pageContext);
}
// Continue processing this page
return EVAL_BODY_BUFFERED;
}
/**
* Save any body content of this tag, which will generally be the
* option(s) representing the values displayed to the user.
* @throws JspException if a JSP exception has occurred
*/
public int doAfterBody() throws JspException
{
if (hasErrors()) {
return SKIP_BODY;
}
// if this is a repeater we need to repeater over the body...
if (_repeater) {
if (doRepeaterAfterBody())
return EVAL_BODY_AGAIN;
}
if (bodyContent != null) {
String value = bodyContent.getString();
bodyContent.clearBody();
if (value == null)
value = "";
_saveBody = value.trim();
}
return SKIP_BODY;
}
/**
* Render the end of this select.
* @throws JspException if a JSP exception has occurred
*/
public int doEndTag() throws JspException
{
ServletRequest req = pageContext.getRequest();
String fmtErrors = null;
if (_formatterError) {
fmtErrors = getErrorsFromBody();
}
if (hasErrors())
return reportAndExit(EVAL_PAGE);
_state.disabled = isDisabled();
//Create hidden field for state tracking
ByRef ref = new ByRef();
nameHtmlControl(_state, ref);
if (hasErrors())
return reportAndExit(EVAL_PAGE);
// Only write out the hidden field if the select is not
// disabled. If it is disabled, then nothing will be posted
// back from this.
WriteRenderAppender writer = new WriteRenderAppender(pageContext);
if (!_state.disabled) {
_hiddenState.clear();
String hiddenParamName = null;
hiddenParamName = _state.name + OLDVALUE_SUFFIX;
_hiddenState.name = hiddenParamName;
_hiddenState.value = "true";
TagRenderingBase hiddenTag = TagRenderingBase.Factory.getRendering(TagRenderingBase.INPUT_HIDDEN_TAG, req);
hiddenTag.doStartTag(writer, _hiddenState);
hiddenTag.doEndTag(writer);
write("\n");
}
// Render any formatting errors that may have occurred.
if (fmtErrors != null)
write(fmtErrors);
TagRenderingBase br = TagRenderingBase.Factory.getRendering(TagRenderingBase.SELECT_TAG, req);
br.doStartTag(writer, _state);
// Render the content of the body, these would be the options
if (_saveBody != null) {
write(_saveBody);
}
// if we are repeating then the body contained the options so we can exit here
if (_repeater) {
if (hasErrors())
return reportAndExit(EVAL_PAGE);
br.doEndTag(writer);
if (!ref.isNull())
write((String) ref.getRef());
// Continue processing this page
localRelease();
return EVAL_PAGE;
}
// All of the code below will pass through the optionsDataSource, the dataSource and defaultValue and
// create a full Select.
if (_dynamicOptions != null) {
if (_dynamicOptions instanceof Map) {
Map dynamicOptionsMap = (Map) _dynamicOptions;
Iterator keyIterator = dynamicOptionsMap.keySet().iterator();
while (keyIterator.hasNext()) {
Object optionValue = keyIterator.next();
String optionDisplay = null;
if (dynamicOptionsMap.get(optionValue) != null) {
optionDisplay = dynamicOptionsMap.get(optionValue).toString();
}
if (optionValue != null) {
addOption(req, optionValue.toString(), optionDisplay);
}
}
}
else if (_dynamicOptions instanceof Iterator) {
Iterator dynamicOptionsIterator = (Iterator) evaluateOptionsDataSource();
while (dynamicOptionsIterator.hasNext()) {
Object o = dynamicOptionsIterator.next();
if (o != null) {
String optionValue = o.toString();
addOption(req, optionValue, optionValue);
}
}
}
}
// add the value from the DataSource and Default value
addDatasourceIfNeeded(req);
addDefaultsIfNeeded(req);
if (_nullable && !isMultiple()) {
String txt = (_nullableOptionText != null) ? _nullableOptionText : "";
addOption(req, NULL_VALUE, txt);
}
br.doEndTag(writer);
if (!ref.isNull())
write((String) ref.getRef());
// Continue processing this page
localRelease();
return EVAL_PAGE;
}
/**
* Release any acquired resources.
*/
protected void localRelease()
{
if (_repeater)
DataAccessProviderStack.removeDataAccessProvider(pageContext);
super.localRelease();
_state.clear();
_defaultSelections = null;
_formatters = null;
_match = null;
_saveBody = null;
_nullable = false;
_nullableOptionText = null;
_optionList = null;
_repIdx = 0;
_repeater = false;
_repCurItem = null;
_repCurStage = RepeatingStages.BEFORE;
_dynamicOptions = null;
_formatterError = false;
_optRb = null;
_order = DEFAULT_ORDER;
}
private String getErrorsFromBody()
{
final String END_TOKEN = "</span>";
assert(_saveBody != null);
InternalStringBuilder body = new InternalStringBuilder(_saveBody.length());
InternalStringBuilder error = new InternalStringBuilder(_saveBody.length());
// pull out all of the spans These should be legally constructed, otherwise we will ignore them.
int len = _saveBody.length();
int pos = 0;
while (pos < len) {
// find the start of a span, if we dont' find one then it's over....
int start = _saveBody.indexOf("<span", pos);
if (start == -1)
break;
// if we don't find the end of the <span> then we don't have a legal span so ignore it
int end = _saveBody.indexOf(END_TOKEN);
if (end == -1)
break;
// copy the pos to start into the body
int realEnd = end + END_TOKEN.length() + 1;
body.append(_saveBody.substring(pos, start));
error.append(_saveBody.substring(start, realEnd));
pos = realEnd;
}
// recreate the remainder of the body, everything not left
body.append(_saveBody.substring(pos, len));
_saveBody = body.toString();
// return the error
return error.toString();
}
/**
* This method will side affects the <code>_repCurItem</code> to insure that it
* is set to the next item in the iteration set. It will return <code>true</code>
* if there is a next item, and <code>false</code> when we are done with the iteration
* @return returns <code>true</code> when <code>_repCurItem</code> contains the next item and
* <code>false</code> when we are done.
* @throws JspException
*/
private boolean doRepeaterAfterBody()
throws JspException
{
switch (_repCurStage.getValue()) {
case RepeatingStages.INT_BEFORE:
if (!moveNext())
return false;
return doRepeaterAfterBody();
case RepeatingStages.INT_OPTION:
assert (_repeaterIterator instanceof Iterator);
while (_repeaterIterator.hasNext()) {
_repCurItem = _repeaterIterator.next();
if (_repCurItem != null) {
_optionList.add(_repCurItem);
return true;
}
}
if (!moveNext())
return false;
return doRepeaterAfterBody();
case RepeatingStages.INT_DEFAULT:
case RepeatingStages.INT_DATASOURCE:
case RepeatingStages.INT_NULL:
assert (_repeaterIterator instanceof Iterator);
while (_repeaterIterator.hasNext()) {
_repCurItem = _repeaterIterator.next();
if (!_optionList.contains(_repCurItem)) {
_optionList.add(_repCurItem);
return true;
}
}
if (!moveNext())
return false;
return doRepeaterAfterBody();
}
return false;
}
/**
* This method will move to the next iteration type. The order of the
* iteration is defined by the <code>_order</code> array. The result
* is side affecting the _repeaterIterator by initializing it. If there
* is nothing further, then we will return false, otherwise we return true.
* @return
* @throws JspException
*/
private boolean moveNext()
throws JspException
{
// increment the current position, if we are beyond the end of the array return
_repIdx++;
if (_repIdx == _order.length)
return false;
// Get the next stage and clear the _repeaterIterator
_repCurStage = _order[_repIdx];
_repeaterIterator = null;
// process each type of iteration...
// Each will recursively call moveNext, if that stage doesn't support iteration
switch (_repCurStage.getValue()) {
case RepeatingStages.INT_BEFORE:
break;
case RepeatingStages.INT_OPTION:
// This produces an error if the optionsDataSource is an instance of an iterator
if (!(_dynamicOptions instanceof Iterator)) {
String s = Bundle.getString("Tags_OptionsDSIteratorError");
registerTagError(s, null);
return false;
}
assert(_dynamicOptions instanceof Iterator);
_repeaterIterator = (Iterator) _dynamicOptions;
break;
case RepeatingStages.INT_DEFAULT:
if (_defaultSelections != null)
_repeaterIterator = _defaultSelections.iterator();
break;
case RepeatingStages.INT_DATASOURCE:
if (_match != null)
_repeaterIterator = Arrays.asList(_match).iterator();
break;
case RepeatingStages.INT_NULL:
if (_nullable)
_repeaterIterator = new ArrayIterator(NULL_INSTANCE);
break;
}
// return true when we set the iterator, otherwise move to the next stage.
return (_repeaterIterator != null) ? true : moveNext();
}
/**
* This method builds the list of selected items so that they can be marked as selected.
* @param val The <code>dataSource</code>
*/
private void buildMatch(Object val)
{
// create the match data
if (val != null) {
if (val instanceof String) {
_match = new String[]{(String) val};
}
else if (val instanceof String[]) {
String[] s = (String[]) val;
int cnt = 0;
for (int i = 0; i < s.length; i++) {
if (s[i] != null)
cnt++;
}
if (cnt == s.length)
_match = s;
else {
if (cnt > 0) {
_match = new String[cnt];
cnt = 0;
for (int i = 0; i < s.length; i++) {
if (s[i] != null) {
_match[cnt++] = s[i];
}
}
}
}
}
else {
Iterator matchIterator = null;
// val is never null so this would be an error
matchIterator = IteratorFactory.createIterator(val);
if (matchIterator == null) {
matchIterator = IteratorFactory.EMPTY_ITERATOR;
}
ArrayList matchList = new ArrayList();
while (matchIterator.hasNext()) {
Object o = matchIterator.next();
if (o == null)
continue;
matchList.add(o);
}
int size = matchList.size();
_match = new String[size];
for (int i = 0; i < size; i++) {
assert (matchList.get(i) != null);
assert (matchList.get(i).toString() != null);
_match[i] = matchList.get(i).toString();
}
}
if (logger.isDebugEnabled()) {
logger.debug("****** Select Matches ******");
if (_match != null) {
for (int i = 0; i < _match.length; i++) {
logger.debug(i + ": " + _match[i]);
}
}
}
}
else {
if (_nullable && !isMultiple() && (_defaultSelections == null || _defaultSelections.size() == 0)) {
_match = new String[]{NULL_VALUE};
}
}
}
// add the default values specified in the tag if they are needed.
private void addDefaultsIfNeeded(ServletRequest req)
throws JspException
{
if (_defaultSelections != null) {
Iterator iterator = _defaultSelections.iterator();
while (iterator.hasNext()) {
Object selection = iterator.next();
if (!_optionList.contains(selection)) {
addOption(req, selection.toString(), selection.toString());
}
}
}
}
private boolean isMultiple()
{
return _state.multiple;
}
// add dthe datasource values if needed.
private void addDatasourceIfNeeded(ServletRequest req)
throws JspException
{
if (_match == null)
return;
for (int i = 0; i < _match.length; i++) {
if (!_optionList.contains(_match[i])) {
if (!_match[i].equals(NULL_VALUE))
addOption(req, _match[i], _match[i]);
}
}
}
private void addOption(ServletRequest req, String optionValue, String optionDisplay)
throws JspException
{
assert(optionValue != null);
assert(optionDisplay != null);
write("\n");
_optionState.clear();
_optionState.value = optionValue;
_optionState.style = _state.style;
_optionState.styleClass = _state.styleClass;
if (isMatched(optionValue)) {
_optionState.selected = true;
}
WriteRenderAppender writer = new WriteRenderAppender(pageContext);
if (_optRb == null)
_optRb = TagRenderingBase.Factory.getRendering(TagRenderingBase.OPTION_TAG, req);
_optRb.doStartTag(writer, _optionState);
if (optionDisplay != null) {
write(formatText(optionDisplay));
}
else {
write("&lt;");
write(optionValue);
write(">");
}
_optRb.doEndTag(writer);
addOptionToList(optionValue);
}
/**
* Adds a FormatTag.Formatter to the Select's set of formatters
* @param formatter a FormatTag.Formatter added by a child FormatTag.
*/
public void addFormatter(FormatTag.Formatter formatter)
{
_formatters.add(formatter);
}
/**
* Indicate that a formatter has reported an error so the formatter should output it's
* body text.
*/
public void formatterHasError()
{
_formatterError = true;
}
/**
*/
public void addOptionToList(String value)
{
_optionList.add(value);
}
/**
* Apply the Select's set of formatters to the given text
* @param text the text to format.
* @return the formatted text
*/
public String formatText(Object text)
throws JspException
{
int cnt = _formatters.size();
for (int i = 0; i < cnt; i++) {
FormatTag.Formatter currentFormatter = (FormatTag.Formatter) _formatters.get(i);
try {
text = currentFormatter.format(text);
}
catch (JspException e) {
registerTagError(e.getMessage(), e);
}
}
return text.toString();
}
/* ==================================================================
*
* This tag's publically exposed HTML, CSS, and JavaScript attributes
*
* ==================================================================
*/
/**
* Sets the accessKey attribute value. This should key value of the
* keyboard navigation key. It is recommended not to use the following
* values because there are often used by browsers <code>A, C, E, F, G,
* H, V, left arrow, and right arrow</code>.
* @param accessKey the accessKey value.
* @jsptagref.attributedescription The keyboard navigation key for the element.
* The following values are not recommended because they
* are often used by browsers: <code>A, C, E, F, G,
* H, V, left arrow, and right arrow</code>
* @jsptagref.databindable false
* @jsptagref.attributesyntaxvalue <i>string_accessKey</i>
* @netui:attribute required="false" rtexprvalue="true" type="char"
* description="The keyboard navigation key for the element.
* The following values are not recommended because they
* are often used by browsers: A, C, E, F, G,
* H, V, left arrow, and right arrow"
*/
public void setAccessKey(char accessKey)
{
_state.registerAttribute(AbstractHtmlState.ATTR_GENERAL, ACCESSKEY, Character.toString(accessKey));
}
/**
* Sets the tabIndex of the rendered html tag.
* @param tabindex the tab index.
* @jsptagref.attributedescription The tabIndex of the rendered HTML tag. This attribute determines the position of the
* tag in the sequence of page elements that the user may advance through by pressing the TAB key.
* @jsptagref.databindable false
* @jsptagref.attributesyntaxvalue <i>string_tabIndex</i>
* @netui:attribute required="false" rtexprvalue="true" type="int"
* description="The tabIndex of the rendered HTML tag. This attribute determines the position of the
* tag in the sequence of page elements that the user may advance through by pressing the TAB key."
*/
public void setTabindex(int tabindex)
{
_state.registerAttribute(AbstractHtmlState.ATTR_GENERAL, TABINDEX, Integer.toString(tabindex));
}
}