0001: /*
0002: * Licensed to the Apache Software Foundation (ASF) under one or more
0003: * contributor license agreements. See the NOTICE file distributed with
0004: * this work for additional information regarding copyright ownership.
0005: * The ASF licenses this file to You under the Apache License, Version 2.0
0006: * (the "License"); you may not use this file except in compliance with
0007: * the License. You may obtain a copy of the License at
0008: *
0009: * http://www.apache.org/licenses/LICENSE-2.0
0010: *
0011: * Unless required by applicable law or agreed to in writing, software
0012: * distributed under the License is distributed on an "AS IS" BASIS,
0013: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
0014: * See the License for the specific language governing permissions and
0015: * limitations under the License.
0016: *
0017: * $Header:$
0018: */
0019: package org.apache.beehive.netui.tags.html;
0020:
0021: import org.apache.beehive.netui.util.internal.InternalStringBuilder;
0022:
0023: import org.apache.beehive.netui.pageflow.ProcessPopulate;
0024: import org.apache.beehive.netui.pageflow.RequestParameterHandler;
0025: import org.apache.beehive.netui.script.common.DataAccessProviderStack;
0026: import org.apache.beehive.netui.script.common.IDataAccessProvider;
0027: import org.apache.beehive.netui.tags.ByRef;
0028: import org.apache.beehive.netui.tags.naming.FormDataNameInterceptor;
0029: import org.apache.beehive.netui.tags.naming.IndexedNameInterceptor;
0030: import org.apache.beehive.netui.tags.naming.PrefixNameInterceptor;
0031: import org.apache.beehive.netui.tags.rendering.*;
0032: import org.apache.beehive.netui.util.Bundle;
0033: import org.apache.beehive.netui.util.iterator.ArrayIterator;
0034: import org.apache.beehive.netui.util.iterator.IteratorFactory;
0035: import org.apache.beehive.netui.util.logging.Logger;
0036:
0037: import javax.servlet.ServletRequest;
0038: import javax.servlet.jsp.JspException;
0039: import java.util.*;
0040:
0041: /**
0042: * Renders a select containing a set of SelectOptions.
0043: *
0044: * Select binds to an Iterator of Strings.
0045: *
0046: * If Select uses any Format tags, it must have those tags come before any nested
0047: * SelectOption tags.
0048: * @jsptagref.tagdescription Renders an HTML <select> tag containing a set of selectable options.
0049: *
0050: * <p>The <netui:select> tag can generate a set of
0051: * selectable options in two ways:
0052: *
0053: * <blockquote>
0054: * <ol>
0055: * <li>they can be dynamically generated by pointing the
0056: * <netui:select> tag at a String[] object or
0057: * {@link java.util.HashMap java.util.HashMap}</li>
0058: * <li>they can be statically generated by providing a set of children
0059: * {@link SelectOption}
0060: * tags</li>
0061: * </ol>
0062: * </blockquote>
0063: *
0064: * <p><b>Dynamically Generated Options</b>
0065: *
0066: * <p>You can dynamically generate a set of selectable options by
0067: * pointing the <netui:select> tag at a String[].
0068: *
0069: * <pre> public String[] colors = {"red", "green", "blue", "orange", "pink", "aqua", "black", "brown", "tan"};
0070: *
0071: * public String[] getColors()
0072: * {
0073: * return colors;
0074: * }</pre>
0075: *
0076: * <p>To point the <netui:select> tag at the String[] object use the
0077: * <code>optionsDataSource</code> attribute.</p>
0078: *
0079: * <pre> <netui:select dataSource="actionForm.selection"
0080: * optionsDataSource="${pageFlow.colors}"/></pre>
0081: *
0082: * Note that you can make the display value and the submitted value differ by pointing the
0083: * optionsDataSource attribute of the <netui:select> tag at a HashMap object.
0084: * (Any object that implements the {@link java.util.Map java.util.Map} interface will work.)
0085: *
0086: * <pre> public HashMap optionsMap = new HashMap();
0087: *
0088: * protected HashMap getOptionsMap()
0089: * {
0090: * return optionsMap;
0091: * }
0092: *
0093: * protected void onCreate()
0094: * {
0095: * optionsMap.put("#ff3333", "red");
0096: * optionsMap.put("#3333ff", "blue");
0097: * optionsMap.put("#33ff33", "green");
0098: * }</pre>
0099: *
0100: * <p>However, you cannot use a Map object if you choose to use the Select as a repeater
0101: * (setting the attribute repeater="true").</p>
0102: *
0103: * <p>Point the <netui:select> at the Map object using the <code>optionsDataSource</code> attribute.</p>
0104: *
0105: * <pre> <netui:select dataSource="actionForm.selection"
0106: * optionsDataSource="${pageFlow.optionsMap}"/></pre>
0107: *
0108: * The following HTML will be generated.
0109: *
0110: * <pre> <select name="wlw-select_key:{actionForm.selection}">
0111: * <option value="#3333ff">blue</option>
0112: * <option value="#33ff33">green</option>
0113: * <option value="#ff3333">red</option>
0114: * </select></pre>
0115: *
0116: * <p><b>Statically Generated Options</b></p>
0117: *
0118: * <p>To statically generate selecable options, place a set of <netui:selectOption> tags inside
0119: * the <netui:select> tag.
0120: *
0121: * <pre> <netui:select dataSource="actionForm.selection" size="5">
0122: * <netui:selectOption value="red" />
0123: * <netui:selectOption value="blue" />
0124: * <netui:selectOption value="green" />
0125: * <netui:selectOption value="yellow" />
0126: * <netui:selectOption value="orange" />
0127: * </netui:select></pre>
0128: *
0129: * <p><b>Submitting Selections</b></p>
0130: *
0131: * <p>A <netui:select> is submitted as a String or String[] object, depending on whether the
0132: * <code>multiple</code> attribute is set to true. In the following example, the <code>dataSource</code>
0133: * attribute points at a String[] object.</p>
0134: *
0135: * <pre> </netui:select dataSource="actionForm.selections"...</pre>
0136: *
0137: * <p>In this case, the <netui:select> tag submits to a String[] field of a Form Bean.</p>
0138: *
0139: * <pre> public static class SubmitForm extends FormData
0140: * {
0141: * private String[] selections;
0142: *
0143: * public void setSelections(String[] selections)
0144: * {
0145: * this.selections = selections;
0146: * }
0147: *
0148: * public String[] getSelections()
0149: * {
0150: * return this.selections;
0151: * }
0152: * }</pre>
0153: *
0154: * <p><b>Use Select as a Repeater with Multiple Repeating Types</b></p>
0155: *
0156: * <p>Optionally, use the <netui:select> tag as a repeater to render multiple options
0157: * from the <code>dataSource</code> and <code>defaultValue</code> attributes as well as
0158: * the <code>optionsDataSource</code>. The <netui:select> element can dynamically generate
0159: * option elements for different repeating types of "option", "dataSource", "default",
0160: * (optionsDataSource, dataSource, and defaultValue attributes respectively) and "null".
0161: * The Select <code>repeatingOrder</code> attribute sets the order that repeating types
0162: * are generated. The <code>repeatingType</code> attribute on the <netui:selectOption>
0163: * tag identifies each of the types to be rendered.</p>
0164: *
0165: * <p>Use JSTL boolean conditional tags with the <netui:selectOption> elements
0166: * to help manage repeaters of different data types.
0167: * For example, the <code>dataSource</code> could point to a String[] while
0168: * the <code>optionsDataSource</code> points to an Object[] where each object has
0169: * name and value fields...</p>
0170: *
0171: * <pre> <netui:select dataSource="actionForm.selections"
0172: * optionsDataSource="${pageFlow.options}"
0173: * repeatingOrder="dataSource,option"
0174: * repeater="true" multiple="true">
0175: * <c:if test="${container.metadata.dataSourceStage}">
0176: * <netui:selectOption repeatingType="dataSource" value="${container.item}">
0177: * <netui:span value="${container.item}" />
0178: * </netui:selectOption>
0179: * </c:if>
0180: * <c:if test="${container.metadata.optionStage}">
0181: * <netui:selectOption repeatingType="option" value="${container.item.name}">
0182: * <netui:span value="${container.item.value}" />
0183: * </netui:selectOption>
0184: * </c:if>
0185: * </netui:select>
0186: * </pre>
0187: * @example The following sample uses the <code>optionsDataSource</code> attribute to reference a
0188: * dynamically generated dropdown list.
0189: *
0190: * <pre>
0191: * <netui:select dataSource="actionForm.selectedOption"
0192: * optionsDataSource="${actionForm.itemOptions}" />
0193: * </pre>
0194: *
0195: * <p>Assume that the <code>optionsDataSource</code> attribute refers to
0196: * a <code>java.util.Map</code> object.
0197: * The Map object will be rendered as a series
0198: * of <option> tags. HTML that is similar to the following will be
0199: * rendered in the browser:</p>
0200: *
0201: * <pre> <select name="wlw-select_key:{actionForm.itemOptions}">
0202: * <option value="633">Aurora Bridge</option>
0203: * <option value="631">FA-18 fighter jet</option>
0204: * <option value="635">Space Needle</option>
0205: * <option value="642">Thin Mints</option>
0206: * ...
0207: * </select></pre>
0208: * @netui:tag name="select" description="Defines a multiple-choice menu or drop-down list within a netui:form element."
0209: * @netui:attribute name="onSelect" hide="true" description=""
0210: */
0211: public class Select extends HtmlOptionsDataSourceTag implements
0212: IDataAccessProvider, IFormattable {
0213: // @todo: needs to create DRT tests for: verification of errors, verirication of data sources matching,
0214: // @todo: verification of formating inside a repeater.
0215: // @todo: on the null tag, we need to default the null value to NULL_VALUE
0216: // @todo: need to handle null, in the options
0217: // @todo: should handle no optionDataSource when repeating...
0218:
0219: private static final Logger logger = Logger
0220: .getInstance(Select.class);
0221:
0222: private SelectTag.State _state = new SelectTag.State();
0223: private OptionTag.State _optionState = new OptionTag.State();
0224: private InputHiddenTag.State _hiddenState = new InputHiddenTag.State();
0225: private boolean _formatterError = false;
0226:
0227: private static Object[] NULL_INSTANCE = { null };
0228:
0229: /**
0230: * This enum defines stages through the possible option values.
0231: */
0232: public static class RepeatingStages {
0233: private static final int INT_BEFORE = 0;
0234: private static final int INT_OPTION = 1;
0235: private static final int INT_DEFAULT = 2;
0236: private static final int INT_DATASOURCE = 3;
0237: private static final int INT_NULL = 4;
0238: private static final int INT_DONE = 5;
0239:
0240: static final RepeatingStages BEFORE = new RepeatingStages(
0241: INT_BEFORE);
0242: static final RepeatingStages OPTION = new RepeatingStages(
0243: INT_OPTION);
0244: static final RepeatingStages DEFAULT = new RepeatingStages(
0245: INT_DEFAULT);
0246: static final RepeatingStages DATASOURCE = new RepeatingStages(
0247: INT_DATASOURCE);
0248: static final RepeatingStages NULL = new RepeatingStages(
0249: INT_NULL);
0250: static final RepeatingStages DONE = new RepeatingStages(
0251: INT_DONE);
0252:
0253: /**
0254: * These are the publically exposed stages, <code>REPEATING_OPTION, REPEATING_DEFAULT,
0255: * REPEATING_DATASOURCE and REPEATING_NULL</code>.
0256: */
0257: public static final String REPEATING_OPTION = "option";
0258: public static final String REPEATING_DEFAULT = "default";
0259: public static final String REPEATING_DATASOURCE = "dataSource";
0260: public static final String REPEATING_NULL = "null";
0261:
0262: public int value;
0263:
0264: // prevent construction...
0265: private RepeatingStages(int val) {
0266: value = val;
0267: }
0268:
0269: int getValue() {
0270: return value;
0271: }
0272:
0273: /**
0274: * Returns the String value that can be used to order the selection.
0275: * @return The String Value.
0276: */
0277: public String toString() {
0278: switch (value) {
0279: case INT_OPTION:
0280: return REPEATING_OPTION;
0281: case INT_DEFAULT:
0282: return REPEATING_DEFAULT;
0283: case INT_DATASOURCE:
0284: return REPEATING_DATASOURCE;
0285: case INT_NULL:
0286: return REPEATING_NULL;
0287: default:
0288: return "Unknown Stage";
0289: }
0290: }
0291:
0292: /**
0293: * Given a String value defined above, return the enum value for it.
0294: * @param value a String value matching one of the public Strings defined for the class.
0295: * @return the matching RepeatingStages or null.
0296: */
0297: public static RepeatingStages parseString(String value) {
0298: if (REPEATING_OPTION.equals(value))
0299: return OPTION;
0300: if (REPEATING_DEFAULT.equals(value))
0301: return DEFAULT;
0302: if (REPEATING_DATASOURCE.equals(value))
0303: return DATASOURCE;
0304: if (REPEATING_NULL.equals(value))
0305: return NULL;
0306: return null;
0307: }
0308: }
0309:
0310: /**
0311: * This defines the default order of processing the options when repeating.
0312: */
0313: private static final RepeatingStages[] DEFAULT_ORDER = {
0314: RepeatingStages.BEFORE, RepeatingStages.OPTION,
0315: RepeatingStages.DATASOURCE, RepeatingStages.DEFAULT,
0316: RepeatingStages.NULL };
0317:
0318: /**
0319: * Default value of the options <code>value</code> attribute.
0320: */
0321: public static final String NULL_VALUE = "netui_null";
0322:
0323: /**
0324: * Constant value of the <code>repeatingType</code> attribute for options handling the <code>null</code> option.
0325: */
0326: //public static final String REPEATING_NULL = "Null";
0327: private static final String SELECT_KEY = "select_key";
0328: private static final String OLDVALUE_SUFFIX = "OldValue";
0329:
0330: // IDataAccessProvider support
0331: private int _repIdx = 0; // The current index for repeating over the optionsDataSource
0332: private RepeatingStages _repCurStage = RepeatingStages.BEFORE; // The current stage defined by the stage constants above
0333: private boolean _repeater; // Boolean flag indicating if this is a repeater or not
0334: private Object _repCurItem; // The current item access by the IDataAccessProvider
0335: private Iterator _repeaterIterator; // The iterator being used to output the options.
0336: private RepeatingStages[] _order = DEFAULT_ORDER;
0337:
0338: private Object _dynamicOptions; // The interator (or map) for the options data source, repeating this is current var
0339:
0340: private String _saveBody;
0341: private String _nullableOptionText;
0342:
0343: private List _defaultSelections;
0344: private ArrayList _formatters;
0345: private ArrayList _optionList;
0346: private String[] _match; // The actual values we will match against
0347: private boolean _nullable;
0348: private TagRenderingBase _optRb;
0349:
0350: private static final List _internalNamingChain;
0351:
0352: static {
0353: List l = new ArrayList(3);
0354: l.add(new FormDataNameInterceptor());
0355: l.add(new IndexedNameInterceptor());
0356: l.add(new PrefixNameInterceptor(SELECT_KEY));
0357: _internalNamingChain = Collections.unmodifiableList(l);
0358:
0359: org.apache.beehive.netui.pageflow.ProcessPopulate
0360: .registerPrefixHandler(SELECT_KEY,
0361: new SelectPrefixHandler());
0362: }
0363:
0364: /**
0365: */
0366: public static class SelectPrefixHandler implements
0367: RequestParameterHandler {
0368: public void process(
0369: javax.servlet.http.HttpServletRequest request,
0370: String key, String expr,
0371: ProcessPopulate.ExpressionUpdateNode node) {
0372: String[] returnArray = null;
0373:
0374: if (!key.endsWith(OLDVALUE_SUFFIX)) {
0375: //This select has values and should stay that way
0376: returnArray = request.getParameterValues(key);
0377: } else {
0378: //Check the request to see if select also exists
0379: String newKey = key.substring(0, key
0380: .indexOf(OLDVALUE_SUFFIX));
0381: String[] select = request.getParameterValues(newKey);
0382: if (select != null) {
0383: returnArray = select;
0384: } else {
0385: returnArray = new String[0]; //null;
0386: }
0387: }
0388:
0389: if (node.expression.endsWith(OLDVALUE_SUFFIX)) {
0390: node.expression = node.expression.substring(0,
0391: node.expression.indexOf(OLDVALUE_SUFFIX));
0392: }
0393:
0394: //Check for the NULL_VALUE, replace it with null
0395: for (int i = 0; i < returnArray.length; i++) {
0396: if (returnArray[i].equals(NULL_VALUE)) {
0397: returnArray[i] = null;
0398: }
0399: }
0400:
0401: node.values = returnArray;
0402:
0403: if (logger.isDebugEnabled()) {
0404: logger
0405: .debug("\n*********************************************\n"
0406: + "process with key \""
0407: + key
0408: + "\" and expression \""
0409: + node.expression
0410: + "\""
0411: + "and result size: "
0412: + (returnArray != null ? ""
0413: + returnArray.length : null)
0414: + "\n"
0415: + "*********************************************\n");
0416: }
0417: }
0418: }
0419:
0420: public Select() {
0421: super ();
0422: }
0423:
0424: /**
0425: * Return the name of the Tag.
0426: */
0427: public String getTagName() {
0428: return "Select";
0429: }
0430:
0431: public String getDataSource() {
0432: return "{" + _dataSource.toString() + "}";
0433: }
0434:
0435: /**
0436: * This method will return the state associated with the tag. This is used by this
0437: * base class to access the individual state objects created by the tags.
0438: * @return a subclass of the <code>AbstractHtmlState</code> class.
0439: */
0440: protected AbstractHtmlState getState() {
0441: return _state;
0442: }
0443:
0444: /**
0445: * Return an <code>ArrayList</code> which represents a chain of <code>INameInterceptor</code>
0446: * objects. This method by default returns <code>null</code> and should be overridden
0447: * by objects that support naming.
0448: * @return an <code>ArrayList</code> that will contain <code>INameInterceptor</code> objects.
0449: */
0450: protected List getNamingChain() {
0451: return _internalNamingChain;
0452: }
0453:
0454: /**
0455: * Evaluate the defaultValues
0456: */
0457: protected Object evaluateDefaultValue() throws JspException {
0458: Object val = _defaultValue;
0459:
0460: List defaults = null;
0461: if (val instanceof String) {
0462: defaults = new ArrayList();
0463: defaults.add(val);
0464: } else {
0465: Iterator optionsIterator = null;
0466: optionsIterator = IteratorFactory.createIterator(val);
0467:
0468: // default value is optional so only warn
0469: if (optionsIterator == null && _defaultValue != null)
0470: logger.warn(Bundle.getString("Tags_IteratorError",
0471: new Object[] { getTagName(), "defaultValue",
0472: _defaultValue }));
0473:
0474: if (optionsIterator == null)
0475: optionsIterator = IteratorFactory.EMPTY_ITERATOR;
0476:
0477: defaults = new ArrayList();
0478: while (optionsIterator.hasNext()) {
0479: Object o = optionsIterator.next();
0480: defaults.add(o.toString());
0481: }
0482: }
0483:
0484: return defaults;
0485: }
0486:
0487: /**
0488: * Set whether multiple selections are allowed.
0489: * @param multiple the multiple value ("true" or "false")
0490: * @jsptagref.attributedescription Boolean. Whether or not multi-selection is enabled.
0491: * If multiple selection is enabled, a null option will not be displayed, even if
0492: * the <code>nullable</code> is set to true.
0493: * @jsptagref.databindable false
0494: * @jsptagref.attributesyntaxvalue <i>boolean_multipleSelectEnabled</i>
0495: * @netui:attribute required="false" rtexprvalue="true" type="boolean"
0496: * description="Whether or not multi-selection is enabled.
0497: * If multiple selection is enabled, a null option will not be displayed, even if
0498: * the nullable is set to true."
0499: */
0500: public void setMultiple(boolean multiple) {
0501: _state.multiple = multiple;
0502: }
0503:
0504: /**
0505: * Set whether repeating of contained options is on.
0506: * @param repeater the repeater value ("true" or "false")
0507: * @jsptagref.attributedescription Set whether repeating of contained options is on.
0508: * @jsptagref.databindable false
0509: * @jsptagref.attributesyntaxvalue <i>boolean_repeater</i>
0510: * @netui:attribute required="false" rtexprvalue="true" type="boolean"
0511: * description="Set whether repeating of contained options is on."
0512: */
0513: public void setRepeater(boolean repeater) {
0514: _repeater = repeater;
0515: }
0516:
0517: /**
0518: * Gets whether a repeating contained options is on.
0519: * @return the repeater value
0520: */
0521: public boolean isRepeater() {
0522: return _repeater;
0523: }
0524:
0525: /**
0526: * This method will set the order of the options generated in the select. It must contain a
0527: * comma separated string listing the order or the stages that the repeating types are processed.
0528: * These values are "option", "dataSource", "default", and "null".
0529: * @param order comma separated ordering of items when there is a repeating select.
0530: * @jsptagref.attributedescription Define the order of options generated for a repeating Select.
0531: * It must contain a comma separated string listing the order or the stages that the repeating types
0532: * are processed. These values are "option", "dataSource", "default", and "null". For example,
0533: * <pre> repeatingOrder="dataSource,option"</pre>
0534: *
0535: * Then a <netui:selectOption> element could set the repeatingType attribute to "dataSource"
0536: * while another is defined for "option".
0537: * @jsptagref.databindable false
0538: * @jsptagref.attributesyntaxvalue <i>string_order</i>
0539: * @netui:attribute required="false" rtexprvalue="true"
0540: * description="Define the order of options for a repeating Select"
0541: */
0542: public void setRepeatingOrder(String order) throws JspException {
0543: String[] options = order.split(",");
0544: RepeatingStages[] stageOrder = new RepeatingStages[options.length + 1];
0545: stageOrder[0] = RepeatingStages.BEFORE;
0546: for (int i = 0; i < options.length; i++) {
0547: String opt = options[i].trim();
0548: stageOrder[i + 1] = RepeatingStages.parseString(opt);
0549: if (stageOrder[i + 1] == null) {
0550: String s = Bundle.getString(
0551: "Tags_SelectBadRepeatingStage",
0552: new Object[] { opt });
0553: registerTagError(s, null);
0554: }
0555: }
0556: _order = stageOrder;
0557: }
0558:
0559: /**
0560: * Set whether a null option is desired.
0561: * @param nullable the nullable value
0562: * @jsptagref.attributedescription Boolean.
0563: * Whether a option with the value null should be added to the bottom of the list.
0564: * If <select> has the multiple <code>attribute</code> set to true, the null option won't be shown.
0565: * @jsptagref.databindable false
0566: * @jsptagref.attributesyntaxvalue <i>boolean_nullable</i>
0567: * @netui:attribute required="false" rtexprvalue="true" type="boolean"
0568: * description="Whether a option with the value null should be added to the bottom of the list.
0569: * If <select> has the multiple attribute set to true, the null option won't be shown."
0570: */
0571: public void setNullable(boolean nullable) {
0572: _nullable = nullable;
0573: }
0574:
0575: /**
0576: * Gets the options datasource value (an expression).
0577: * @return the options datasource
0578: */
0579: public Object getOptionsDataSource() {
0580: return _optionsDataSource;
0581: }
0582:
0583: /**
0584: * Set the text of the nullable option.
0585: * If the <code>nullable<code> option is true, this is
0586: * the text of that option. The default is "";
0587: * @jsptagref.attributedescription Boolean.
0588: * If the <code>nullable</code> attribute is set to true, then the <code>nullableOptionText</code>
0589: * attribute determines the display text of the null option.
0590: * The default is to use the empty string, "", as the display text.
0591: * @jsptagref.databindable false
0592: * @jsptagref.attributesyntaxvalue <i>boolean_nullableOptionText</i>
0593: * @netui:attribute required="false" rtexprvalue="true" type="boolean"
0594: * description="If the nullable attribute is set to true, then the nullableOptionText
0595: * attribute determines the display text of the null option.
0596: */
0597: public void setNullableOptionText(String nullableOptionText) {
0598: _nullableOptionText = nullableOptionText;
0599: }
0600:
0601: /**
0602: * This method will return the object representing the <code>optionsDataSource</code>. This
0603: * is overridden from the base class, because there are only two types which will be
0604: * retunred from the method. The <code>optionsDataSource</code> will either be a instance of a <code>Map</code>
0605: * or and instanceof a <code>Iterator</code>.
0606: * @return the object instance object representing the objectsDataSource. This may be null.
0607: * @throws JspException on an error
0608: */
0609: protected Object evaluateOptionsDataSource() throws JspException {
0610: Object val = _optionsDataSource;
0611: if (val == null) {
0612: // optionsDataSource is option so this is a warning
0613: if (_optionsDataSource != null)
0614: logger.warn(Bundle
0615: .getString("Tags_IteratorError", new Object[] {
0616: getTagName(), "optionsDataSource",
0617: _optionsDataSource }));
0618: return null;
0619: }
0620:
0621: if (val instanceof Map)
0622: return val;
0623:
0624: Iterator options = null;
0625: options = IteratorFactory.createIterator(val);
0626: if (options == null)
0627: options = IteratorFactory.EMPTY_ITERATOR;
0628:
0629: return options;
0630: }
0631:
0632: /**
0633: * Sets how many options are displayed.
0634: * @param size the size (a number)
0635: * @jsptagref.attributedescription The number of visible options
0636: * @jsptagref.databindable false
0637: * @jsptagref.attributesyntaxvalue <i>integer_size</i>
0638: * @netui:attribute required="false" rtexprvalue="true" type="int"
0639: * description="The number of visible options"
0640: */
0641: public void setSize(int size) {
0642: _state.size = size;
0643: }
0644:
0645: /**
0646: * Does the specified value match one of those we are looking for?
0647: * @param value Value to be compared
0648: */
0649: public boolean isMatched(String value) {
0650: if (value == null)
0651: return false;
0652: if ((_match != null)) {
0653: for (int i = 0; i < _match.length; i++) {
0654: if (value.equals(_match[i]))
0655: return true;
0656: }
0657: } else {
0658: if (_defaultSelections != null) {
0659: return (_defaultSelections.contains(value));
0660: }
0661: }
0662:
0663: return false;
0664:
0665: }
0666:
0667: //********************************** IDataAccessProvider Interface ******************************
0668: // setDataSource is implemented by the HtmlDataSourceTag class
0669: // getDataSource is implemented by the HtmlDataSourceTag class
0670:
0671: /**
0672: * Get the current index in this iteration. This should be a
0673: * zero based integer that increments after each iteration.
0674: * @return the current index of iteration or 0
0675: */
0676: public int getCurrentIndex() {
0677: return _repIdx;
0678: }
0679:
0680: /**
0681: * Get the current data item in this IDataAccessProvider.
0682: * @return the current data item or <code>null</code>
0683: */
0684: public Object getCurrentItem() {
0685: return _repCurItem;
0686: }
0687:
0688: /**
0689: * Get a metadata object for the current item. This interface
0690: * is optional, and implementations of this interface are
0691: * provided by the IDataAccessProvider interface. See these
0692: * implementations for information about their support for
0693: * current item metadata.
0694: * @return the current metadata or <code>null</code> if no metadata can be
0695: * found or metadata is not supported by a IDataAccessProvider implementation
0696: */
0697: public Object getCurrentMetadata() {
0698: return this ;
0699: }
0700:
0701: /**
0702: * Get the parent IDataAccessProvider of a IDataAccessProvider. A IDataAccessProvider
0703: * implementation may be able to nest IDataAccessProviders. In this case,
0704: * it can be useful to be able to also nest access to data from parent
0705: * providers. Implementations of this interface are left with having
0706: * to discover and export parents. The return value from this call
0707: * on an implementing Object can be <code>null</code>.
0708: * @return the parent IDataAccessProvider or <code>null</code> if this method
0709: * is not supported or the parent can not be found.
0710: */
0711: public IDataAccessProvider getProviderParent() {
0712: return (IDataAccessProvider) findAncestorWithClass(this ,
0713: IDataAccessProvider.class);
0714: }
0715:
0716: /**
0717: * Return the enum value of the currently repeating stage.
0718: * @return The currently repeating stage.
0719: */
0720: public RepeatingStages getRepeatingStage() {
0721: return _repCurStage;
0722: }
0723:
0724: /**
0725: * Boolean indicating that we are processing the optionsDataSource.
0726: * @return <code>true</code> if we are processing the optionsDataSource.
0727: */
0728: public boolean isOptionStage() {
0729: return _repCurStage == RepeatingStages.OPTION;
0730: }
0731:
0732: /**
0733: * Boolean indicating that we are processing the defaultValue.
0734: * @return <code>true</code> if we are processing the defaultValue.
0735: */
0736: public boolean isDefaultStage() {
0737: return _repCurStage == RepeatingStages.DEFAULT;
0738: }
0739:
0740: /**
0741: * Boolean indicating that we are processing the dataSource.
0742: * @return <code>true</code> if we are processing the dataSource.
0743: */
0744: public boolean isDataSourceStage() {
0745: return _repCurStage == RepeatingStages.DATASOURCE;
0746: }
0747:
0748: /**
0749: * Boolean indicating that we are processing the defined null value.
0750: * @return <code>true</code> if we are processing the defined null value.
0751: */
0752: public boolean isNullStage() {
0753: return _repCurStage == RepeatingStages.NULL;
0754: }
0755:
0756: /**
0757: * Render the beginning of this select.
0758: * @throws JspException if a JSP exception has occurred
0759: */
0760: public int doStartTag() throws JspException {
0761: Object val = evaluateDataSource();
0762: _defaultSelections = (List) evaluateDefaultValue();
0763:
0764: // if there were expression errors report them
0765: if (hasErrors())
0766: return SKIP_BODY;
0767:
0768: buildMatch(val);
0769: if (hasErrors())
0770: return SKIP_BODY;
0771:
0772: _formatters = new ArrayList();
0773: _optionList = new ArrayList();
0774:
0775: // Walk the options data source
0776: _dynamicOptions = evaluateOptionsDataSource();
0777: if (_repeater) {
0778: _repCurStage = _order[0];
0779: boolean valid = doRepeaterAfterBody();
0780: if (!valid)
0781: return SKIP_BODY;
0782: DataAccessProviderStack.addDataAccessProvider(this ,
0783: pageContext);
0784: }
0785:
0786: // Continue processing this page
0787: return EVAL_BODY_BUFFERED;
0788: }
0789:
0790: /**
0791: * Save any body content of this tag, which will generally be the
0792: * option(s) representing the values displayed to the user.
0793: * @throws JspException if a JSP exception has occurred
0794: */
0795: public int doAfterBody() throws JspException {
0796: if (hasErrors()) {
0797: return SKIP_BODY;
0798: }
0799:
0800: // if this is a repeater we need to repeater over the body...
0801: if (_repeater) {
0802: if (doRepeaterAfterBody())
0803: return EVAL_BODY_AGAIN;
0804: }
0805:
0806: if (bodyContent != null) {
0807: String value = bodyContent.getString();
0808: bodyContent.clearBody();
0809: if (value == null)
0810: value = "";
0811: _saveBody = value.trim();
0812: }
0813: return SKIP_BODY;
0814: }
0815:
0816: /**
0817: * Render the end of this select.
0818: * @throws JspException if a JSP exception has occurred
0819: */
0820: public int doEndTag() throws JspException {
0821: ServletRequest req = pageContext.getRequest();
0822:
0823: String fmtErrors = null;
0824: if (_formatterError) {
0825: fmtErrors = getErrorsFromBody();
0826: }
0827: if (hasErrors())
0828: return reportAndExit(EVAL_PAGE);
0829:
0830: _state.disabled = isDisabled();
0831:
0832: //Create hidden field for state tracking
0833: ByRef ref = new ByRef();
0834: nameHtmlControl(_state, ref);
0835:
0836: if (hasErrors())
0837: return reportAndExit(EVAL_PAGE);
0838:
0839: // Only write out the hidden field if the select is not
0840: // disabled. If it is disabled, then nothing will be posted
0841: // back from this.
0842: WriteRenderAppender writer = new WriteRenderAppender(
0843: pageContext);
0844: if (!_state.disabled) {
0845: _hiddenState.clear();
0846: String hiddenParamName = null;
0847: hiddenParamName = _state.name + OLDVALUE_SUFFIX;
0848: _hiddenState.name = hiddenParamName;
0849: _hiddenState.value = "true";
0850:
0851: TagRenderingBase hiddenTag = TagRenderingBase.Factory
0852: .getRendering(TagRenderingBase.INPUT_HIDDEN_TAG,
0853: req);
0854: hiddenTag.doStartTag(writer, _hiddenState);
0855: hiddenTag.doEndTag(writer);
0856: write("\n");
0857: }
0858:
0859: // Render any formatting errors that may have occurred.
0860: if (fmtErrors != null)
0861: write(fmtErrors);
0862:
0863: TagRenderingBase br = TagRenderingBase.Factory.getRendering(
0864: TagRenderingBase.SELECT_TAG, req);
0865: br.doStartTag(writer, _state);
0866:
0867: // Render the content of the body, these would be the options
0868: if (_saveBody != null) {
0869: write(_saveBody);
0870: }
0871:
0872: // if we are repeating then the body contained the options so we can exit here
0873: if (_repeater) {
0874:
0875: if (hasErrors())
0876: return reportAndExit(EVAL_PAGE);
0877:
0878: br.doEndTag(writer);
0879: if (!ref.isNull())
0880: write((String) ref.getRef());
0881:
0882: // Continue processing this page
0883: localRelease();
0884: return EVAL_PAGE;
0885: }
0886:
0887: // All of the code below will pass through the optionsDataSource, the dataSource and defaultValue and
0888: // create a full Select.
0889: if (_dynamicOptions != null) {
0890: if (_dynamicOptions instanceof Map) {
0891: Map dynamicOptionsMap = (Map) _dynamicOptions;
0892: Iterator keyIterator = dynamicOptionsMap.keySet()
0893: .iterator();
0894: while (keyIterator.hasNext()) {
0895: Object optionValue = keyIterator.next();
0896: String optionDisplay = null;
0897: if (dynamicOptionsMap.get(optionValue) != null) {
0898: optionDisplay = dynamicOptionsMap.get(
0899: optionValue).toString();
0900: }
0901:
0902: if (optionValue != null) {
0903: addOption(req, optionValue.toString(),
0904: optionDisplay);
0905: }
0906: }
0907: } else if (_dynamicOptions instanceof Iterator) {
0908: Iterator dynamicOptionsIterator = (Iterator) evaluateOptionsDataSource();
0909: while (dynamicOptionsIterator.hasNext()) {
0910: Object o = dynamicOptionsIterator.next();
0911: if (o != null) {
0912: String optionValue = o.toString();
0913: addOption(req, optionValue, optionValue);
0914: }
0915: }
0916: }
0917: }
0918:
0919: // add the value from the DataSource and Default value
0920: addDatasourceIfNeeded(req);
0921: addDefaultsIfNeeded(req);
0922: if (_nullable && !isMultiple()) {
0923: String txt = (_nullableOptionText != null) ? _nullableOptionText
0924: : "";
0925: addOption(req, NULL_VALUE, txt);
0926: }
0927:
0928: br.doEndTag(writer);
0929: if (!ref.isNull())
0930: write((String) ref.getRef());
0931:
0932: // Continue processing this page
0933: localRelease();
0934: return EVAL_PAGE;
0935: }
0936:
0937: /**
0938: * Release any acquired resources.
0939: */
0940: protected void localRelease() {
0941: if (_repeater)
0942: DataAccessProviderStack
0943: .removeDataAccessProvider(pageContext);
0944:
0945: super .localRelease();
0946: _state.clear();
0947:
0948: _defaultSelections = null;
0949: _formatters = null;
0950: _match = null;
0951: _saveBody = null;
0952: _nullable = false;
0953: _nullableOptionText = null;
0954: _optionList = null;
0955:
0956: _repIdx = 0;
0957: _repeater = false;
0958: _repCurItem = null;
0959: _repCurStage = RepeatingStages.BEFORE;
0960: _dynamicOptions = null;
0961: _formatterError = false;
0962: _optRb = null;
0963:
0964: _order = DEFAULT_ORDER;
0965: }
0966:
0967: private String getErrorsFromBody() {
0968: final String END_TOKEN = "</span>";
0969: assert (_saveBody != null);
0970: InternalStringBuilder body = new InternalStringBuilder(
0971: _saveBody.length());
0972: InternalStringBuilder error = new InternalStringBuilder(
0973: _saveBody.length());
0974:
0975: // pull out all of the spans These should be legally constructed, otherwise we will ignore them.
0976: int len = _saveBody.length();
0977: int pos = 0;
0978: while (pos < len) {
0979:
0980: // find the start of a span, if we dont' find one then it's over....
0981: int start = _saveBody.indexOf("<span", pos);
0982: if (start == -1)
0983: break;
0984:
0985: // if we don't find the end of the <span> then we don't have a legal span so ignore it
0986: int end = _saveBody.indexOf(END_TOKEN);
0987: if (end == -1)
0988: break;
0989:
0990: // copy the pos to start into the body
0991: int realEnd = end + END_TOKEN.length() + 1;
0992: body.append(_saveBody.substring(pos, start));
0993: error.append(_saveBody.substring(start, realEnd));
0994: pos = realEnd;
0995: }
0996:
0997: // recreate the remainder of the body, everything not left
0998: body.append(_saveBody.substring(pos, len));
0999: _saveBody = body.toString();
1000:
1001: // return the error
1002: return error.toString();
1003: }
1004:
1005: /**
1006: * This method will side affects the <code>_repCurItem</code> to insure that it
1007: * is set to the next item in the iteration set. It will return <code>true</code>
1008: * if there is a next item, and <code>false</code> when we are done with the iteration
1009: * @return returns <code>true</code> when <code>_repCurItem</code> contains the next item and
1010: * <code>false</code> when we are done.
1011: * @throws JspException
1012: */
1013: private boolean doRepeaterAfterBody() throws JspException {
1014: switch (_repCurStage.getValue()) {
1015: case RepeatingStages.INT_BEFORE:
1016: if (!moveNext())
1017: return false;
1018: return doRepeaterAfterBody();
1019: case RepeatingStages.INT_OPTION:
1020: assert (_repeaterIterator instanceof Iterator);
1021: while (_repeaterIterator.hasNext()) {
1022: _repCurItem = _repeaterIterator.next();
1023: if (_repCurItem != null) {
1024: _optionList.add(_repCurItem);
1025: return true;
1026: }
1027: }
1028: if (!moveNext())
1029: return false;
1030: return doRepeaterAfterBody();
1031:
1032: case RepeatingStages.INT_DEFAULT:
1033: case RepeatingStages.INT_DATASOURCE:
1034: case RepeatingStages.INT_NULL:
1035: assert (_repeaterIterator instanceof Iterator);
1036: while (_repeaterIterator.hasNext()) {
1037: _repCurItem = _repeaterIterator.next();
1038: if (!_optionList.contains(_repCurItem)) {
1039: _optionList.add(_repCurItem);
1040: return true;
1041: }
1042: }
1043: if (!moveNext())
1044: return false;
1045: return doRepeaterAfterBody();
1046: }
1047: return false;
1048: }
1049:
1050: /**
1051: * This method will move to the next iteration type. The order of the
1052: * iteration is defined by the <code>_order</code> array. The result
1053: * is side affecting the _repeaterIterator by initializing it. If there
1054: * is nothing further, then we will return false, otherwise we return true.
1055: * @return
1056: * @throws JspException
1057: */
1058: private boolean moveNext() throws JspException {
1059: // increment the current position, if we are beyond the end of the array return
1060: _repIdx++;
1061: if (_repIdx == _order.length)
1062: return false;
1063:
1064: // Get the next stage and clear the _repeaterIterator
1065: _repCurStage = _order[_repIdx];
1066: _repeaterIterator = null;
1067:
1068: // process each type of iteration...
1069: // Each will recursively call moveNext, if that stage doesn't support iteration
1070: switch (_repCurStage.getValue()) {
1071: case RepeatingStages.INT_BEFORE:
1072: break;
1073: case RepeatingStages.INT_OPTION:
1074: // This produces an error if the optionsDataSource is an instance of an iterator
1075: if (!(_dynamicOptions instanceof Iterator)) {
1076: String s = Bundle
1077: .getString("Tags_OptionsDSIteratorError");
1078: registerTagError(s, null);
1079: return false;
1080: }
1081:
1082: assert (_dynamicOptions instanceof Iterator);
1083: _repeaterIterator = (Iterator) _dynamicOptions;
1084: break;
1085:
1086: case RepeatingStages.INT_DEFAULT:
1087: if (_defaultSelections != null)
1088: _repeaterIterator = _defaultSelections.iterator();
1089: break;
1090: case RepeatingStages.INT_DATASOURCE:
1091: if (_match != null)
1092: _repeaterIterator = Arrays.asList(_match).iterator();
1093: break;
1094: case RepeatingStages.INT_NULL:
1095: if (_nullable)
1096: _repeaterIterator = new ArrayIterator(NULL_INSTANCE);
1097: break;
1098: }
1099:
1100: // return true when we set the iterator, otherwise move to the next stage.
1101: return (_repeaterIterator != null) ? true : moveNext();
1102: }
1103:
1104: /**
1105: * This method builds the list of selected items so that they can be marked as selected.
1106: * @param val The <code>dataSource</code>
1107: */
1108: private void buildMatch(Object val) {
1109: // create the match data
1110: if (val != null) {
1111: if (val instanceof String) {
1112: _match = new String[] { (String) val };
1113: } else if (val instanceof String[]) {
1114: String[] s = (String[]) val;
1115: int cnt = 0;
1116: for (int i = 0; i < s.length; i++) {
1117: if (s[i] != null)
1118: cnt++;
1119: }
1120: if (cnt == s.length)
1121: _match = s;
1122: else {
1123: if (cnt > 0) {
1124: _match = new String[cnt];
1125: cnt = 0;
1126: for (int i = 0; i < s.length; i++) {
1127: if (s[i] != null) {
1128: _match[cnt++] = s[i];
1129: }
1130: }
1131: }
1132: }
1133: } else {
1134: Iterator matchIterator = null;
1135: // val is never null so this would be an error
1136: matchIterator = IteratorFactory.createIterator(val);
1137: if (matchIterator == null) {
1138: matchIterator = IteratorFactory.EMPTY_ITERATOR;
1139: }
1140:
1141: ArrayList matchList = new ArrayList();
1142: while (matchIterator.hasNext()) {
1143: Object o = matchIterator.next();
1144: if (o == null)
1145: continue;
1146: matchList.add(o);
1147: }
1148:
1149: int size = matchList.size();
1150: _match = new String[size];
1151: for (int i = 0; i < size; i++) {
1152: assert (matchList.get(i) != null);
1153: assert (matchList.get(i).toString() != null);
1154: _match[i] = matchList.get(i).toString();
1155: }
1156: }
1157: if (logger.isDebugEnabled()) {
1158: logger.debug("****** Select Matches ******");
1159: if (_match != null) {
1160: for (int i = 0; i < _match.length; i++) {
1161: logger.debug(i + ": " + _match[i]);
1162: }
1163: }
1164: }
1165: } else {
1166: if (_nullable
1167: && !isMultiple()
1168: && (_defaultSelections == null || _defaultSelections
1169: .size() == 0)) {
1170: _match = new String[] { NULL_VALUE };
1171: }
1172: }
1173: }
1174:
1175: // add the default values specified in the tag if they are needed.
1176: private void addDefaultsIfNeeded(ServletRequest req)
1177: throws JspException {
1178: if (_defaultSelections != null) {
1179: Iterator iterator = _defaultSelections.iterator();
1180: while (iterator.hasNext()) {
1181: Object selection = iterator.next();
1182: if (!_optionList.contains(selection)) {
1183: addOption(req, selection.toString(), selection
1184: .toString());
1185: }
1186: }
1187: }
1188: }
1189:
1190: private boolean isMultiple() {
1191: return _state.multiple;
1192: }
1193:
1194: // add dthe datasource values if needed.
1195: private void addDatasourceIfNeeded(ServletRequest req)
1196: throws JspException {
1197: if (_match == null)
1198: return;
1199:
1200: for (int i = 0; i < _match.length; i++) {
1201: if (!_optionList.contains(_match[i])) {
1202: if (!_match[i].equals(NULL_VALUE))
1203: addOption(req, _match[i], _match[i]);
1204: }
1205: }
1206: }
1207:
1208: private void addOption(ServletRequest req, String optionValue,
1209: String optionDisplay) throws JspException {
1210: assert (optionValue != null);
1211: assert (optionDisplay != null);
1212:
1213: write("\n");
1214: _optionState.clear();
1215: _optionState.value = optionValue;
1216: _optionState.style = _state.style;
1217: _optionState.styleClass = _state.styleClass;
1218:
1219: if (isMatched(optionValue)) {
1220: _optionState.selected = true;
1221: }
1222:
1223: WriteRenderAppender writer = new WriteRenderAppender(
1224: pageContext);
1225: if (_optRb == null)
1226: _optRb = TagRenderingBase.Factory.getRendering(
1227: TagRenderingBase.OPTION_TAG, req);
1228: _optRb.doStartTag(writer, _optionState);
1229:
1230: if (optionDisplay != null) {
1231: write(formatText(optionDisplay));
1232: } else {
1233: write("<");
1234: write(optionValue);
1235: write(">");
1236: }
1237:
1238: _optRb.doEndTag(writer);
1239:
1240: addOptionToList(optionValue);
1241: }
1242:
1243: /**
1244: * Adds a FormatTag.Formatter to the Select's set of formatters
1245: * @param formatter a FormatTag.Formatter added by a child FormatTag.
1246: */
1247: public void addFormatter(FormatTag.Formatter formatter) {
1248: _formatters.add(formatter);
1249: }
1250:
1251: /**
1252: * Indicate that a formatter has reported an error so the formatter should output it's
1253: * body text.
1254: */
1255: public void formatterHasError() {
1256: _formatterError = true;
1257: }
1258:
1259: /**
1260: */
1261: public void addOptionToList(String value) {
1262: _optionList.add(value);
1263: }
1264:
1265: /**
1266: * Apply the Select's set of formatters to the given text
1267: * @param text the text to format.
1268: * @return the formatted text
1269: */
1270: public String formatText(Object text) throws JspException {
1271: int cnt = _formatters.size();
1272: for (int i = 0; i < cnt; i++) {
1273: FormatTag.Formatter currentFormatter = (FormatTag.Formatter) _formatters
1274: .get(i);
1275: try {
1276: text = currentFormatter.format(text);
1277: } catch (JspException e) {
1278: registerTagError(e.getMessage(), e);
1279: }
1280: }
1281: return text.toString();
1282: }
1283:
1284: /* ==================================================================
1285: *
1286: * This tag's publically exposed HTML, CSS, and JavaScript attributes
1287: *
1288: * ==================================================================
1289: */
1290:
1291: /**
1292: * Sets the accessKey attribute value. This should key value of the
1293: * keyboard navigation key. It is recommended not to use the following
1294: * values because there are often used by browsers <code>A, C, E, F, G,
1295: * H, V, left arrow, and right arrow</code>.
1296: * @param accessKey the accessKey value.
1297: * @jsptagref.attributedescription The keyboard navigation key for the element.
1298: * The following values are not recommended because they
1299: * are often used by browsers: <code>A, C, E, F, G,
1300: * H, V, left arrow, and right arrow</code>
1301: * @jsptagref.databindable false
1302: * @jsptagref.attributesyntaxvalue <i>string_accessKey</i>
1303: * @netui:attribute required="false" rtexprvalue="true" type="char"
1304: * description="The keyboard navigation key for the element.
1305: * The following values are not recommended because they
1306: * are often used by browsers: A, C, E, F, G,
1307: * H, V, left arrow, and right arrow"
1308: */
1309: public void setAccessKey(char accessKey) {
1310: _state.registerAttribute(AbstractHtmlState.ATTR_GENERAL,
1311: ACCESSKEY, Character.toString(accessKey));
1312: }
1313:
1314: /**
1315: * Sets the tabIndex of the rendered html tag.
1316: * @param tabindex the tab index.
1317: * @jsptagref.attributedescription The tabIndex of the rendered HTML tag. This attribute determines the position of the
1318: * tag in the sequence of page elements that the user may advance through by pressing the TAB key.
1319: * @jsptagref.databindable false
1320: * @jsptagref.attributesyntaxvalue <i>string_tabIndex</i>
1321: * @netui:attribute required="false" rtexprvalue="true" type="int"
1322: * description="The tabIndex of the rendered HTML tag. This attribute determines the position of the
1323: * tag in the sequence of page elements that the user may advance through by pressing the TAB key."
1324: */
1325: public void setTabindex(int tabindex) {
1326: _state.registerAttribute(AbstractHtmlState.ATTR_GENERAL,
1327: TABINDEX, Integer.toString(tabindex));
1328: }
1329: }
|