KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > apache > beehive > netui > tags > html > Select


1 /*
2  * Copyright 2004 The Apache Software Foundation.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  *
16  * $Header:$
17  */

18 package org.apache.beehive.netui.tags.html;
19
20 import org.apache.beehive.netui.util.internal.InternalStringBuilder;
21
22 import org.apache.beehive.netui.pageflow.ProcessPopulate;
23 import org.apache.beehive.netui.pageflow.RequestParameterHandler;
24 import org.apache.beehive.netui.script.common.DataAccessProviderStack;
25 import org.apache.beehive.netui.script.common.IDataAccessProvider;
26 import org.apache.beehive.netui.tags.ByRef;
27 import org.apache.beehive.netui.tags.naming.FormDataNameInterceptor;
28 import org.apache.beehive.netui.tags.naming.IndexedNameInterceptor;
29 import org.apache.beehive.netui.tags.naming.PrefixNameInterceptor;
30 import org.apache.beehive.netui.tags.rendering.*;
31 import org.apache.beehive.netui.util.Bundle;
32 import org.apache.beehive.netui.util.iterator.ArrayIterator;
33 import org.apache.beehive.netui.util.iterator.IteratorFactory;
34 import org.apache.beehive.netui.util.logging.Logger;
35
36 import javax.servlet.ServletRequest JavaDoc;
37 import javax.servlet.jsp.JspException JavaDoc;
38 import java.util.*;
39
40
41 /**
42  * Renders a select containing a set of SelectOptions.
43  *
44  * Select binds to an Iterator of Strings.
45  *
46  * If Select uses any Format tags, it must have those tags come before any nested
47  * SelectOption tags.
48  * @jsptagref.tagdescription Renders an HTML <select> tag containing a set of selectable options.
49  *
50  * <p>The &lt;netui:select> tag can generate a set of
51  * selectable options in two ways:
52  *
53  * <blockquote>
54  * <ol>
55  * <li>they can be dynamically generated by pointing the
56  * &lt;netui:select> tag at a String[] object or
57  * {@link java.util.HashMap java.util.HashMap}</li>
58  * <li>they can be statically generated by providing a set of children
59  * {@link SelectOption}
60  * tags</li>
61  * </ol>
62  * </blockquote>
63  *
64  * <p><b>Dynamically Generated Options</b>
65  *
66  * <p>You can dynamically generate a set of selectable options by
67  * pointing the &lt;netui:select> tag at a String[].
68  *
69  * <pre> public String[] colors = {"red", "green", "blue", "orange", "pink", "aqua", "black", "brown", "tan"};
70  *
71  * public String[] getColors()
72  * {
73  * return colors;
74  * }</pre>
75  *
76  * <p>To point the &lt;netui:select> tag at the String[] object use the
77  * <code>optionsDataSource</code> attribute.</p>
78  *
79  * <pre> &lt;netui:select dataSource="actionForm.selection"
80  * optionsDataSource="${pageFlow.colors}"/></pre>
81  *
82  * Note that you can make the display value and the submitted value differ by pointing the
83  * optionsDataSource attribute of the &lt;netui:select> tag at a HashMap object.
84  * (Any object that implements the {@link java.util.Map java.util.Map} interface will work.)
85  *
86  * <pre> public HashMap optionsMap = new HashMap();
87  *
88  * protected HashMap getOptionsMap()
89  * {
90  * return optionsMap;
91  * }
92  *
93  * protected void onCreate()
94  * {
95  * optionsMap.put("#ff3333", "red");
96  * optionsMap.put("#3333ff", "blue");
97  * optionsMap.put("#33ff33", "green");
98  * }</pre>
99  *
100  * <p>However, you cannot use a Map object if you choose to use the Select as a repeater
101  * (setting the attribute repeater="true").</p>
102  *
103  * <p>Point the &lt;netui:select> at the Map object using the <code>optionsDataSource</code> attribute.</p>
104  *
105  * <pre> &lt;netui:select dataSource="actionForm.selection"
106  * optionsDataSource="${pageFlow.optionsMap}"/></pre>
107  *
108  * The following HTML will be generated.
109  *
110  * <pre> &lt;select name="wlw-select_key:{actionForm.selection}">
111  * &lt;option value="#3333ff">blue&lt;/option>
112  * &lt;option value="#33ff33">green&lt;/option>
113  * &lt;option value="#ff3333">red&lt;/option>
114  * &lt;/select></pre>
115  *
116  * <p><b>Statically Generated Options</b></p>
117  *
118  * <p>To statically generate selecable options, place a set of &lt;netui:selectOption> tags inside
119  * the &lt;netui:select> tag.
120  *
121  * <pre> &lt;netui:select dataSource="actionForm.selection" size="5">
122  * &lt;netui:selectOption value="red" />
123  * &lt;netui:selectOption value="blue" />
124  * &lt;netui:selectOption value="green" />
125  * &lt;netui:selectOption value="yellow" />
126  * &lt;netui:selectOption value="orange" />
127  * &lt;/netui:select></pre>
128  *
129  * <p><b>Submitting Selections</b></p>
130  *
131  * <p>A &lt;netui:select> is submitted as a String or String[] object, depending on whether the
132  * <code>multiple</code> attribute is set to true. In the following example, the <code>dataSource</code>
133  * attribute points at a String[] object.</p>
134  *
135  * <pre> &lt;/netui:select dataSource="actionForm.selections"...</pre>
136  *
137  * <p>In this case, the &lt;netui:select> tag submits to a String[] field of a Form Bean.</p>
138  *
139  * <pre> public static class SubmitForm extends FormData
140  * {
141  * private String[] selections;
142  *
143  * public void setSelections(String[] selections)
144  * {
145  * this.selections = selections;
146  * }
147  *
148  * public String[] getSelections()
149  * {
150  * return this.selections;
151  * }
152  * }</pre>
153  *
154  * <p><b>Use Select as a Repeater with Multiple Repeating Types</b></p>
155  *
156  * <p>Optionally, use the &lt;netui:select> tag as a repeater to render multiple options
157  * from the <code>dataSource</code> and <code>defaultValue</code> attributes as well as
158  * the <code>optionsDataSource</code>. The &lt;netui:select> element can dynamically generate
159  * option elements for different repeating types of "option", "dataSource", "default",
160  * (optionsDataSource, dataSource, and defaultValue attributes respectively) and "null".
161  * The Select <code>repeatingOrder</code> attribute sets the order that repeating types
162  * are generated. The <code>repeatingType</code> attribute on the &lt;netui:selectOption>
163  * tag identifies each of the types to be rendered.</p>
164  *
165  * <p>Use JSTL boolean conditional tags with the &lt;netui:selectOption> elements
166  * to help manage repeaters of different data types.
167  * For example, the <code>dataSource</code> could point to a String[] while
168  * the <code>optionsDataSource</code> points to an Object[] where each object has
169  * name and value fields...</p>
170  *
171  * <pre> &lt;netui:select dataSource="actionForm.selections"
172  * optionsDataSource="${pageFlow.options}"
173  * repeatingOrder="dataSource,option"
174  * repeater="true" multiple="true">
175  * &lt;c:if test="${container.metadata.dataSourceStage}">
176  * &lt;netui:selectOption repeatingType="dataSource" value="${container.item}">
177  * &lt;netui:span value="${container.item}" />
178  * &lt;/netui:selectOption>
179  * &lt;/c:if>
180  * &lt;c:if test="${container.metadata.optionStage}">
181  * &lt;netui:selectOption repeatingType="option" value="${container.item.name}">
182  * &lt;netui:span value="${container.item.value}" />
183  * &lt;/netui:selectOption>
184  * &lt;/c:if>
185  * &lt;/netui:select>
186  * </pre>
187  * @example The following sample uses the <code>optionsDataSource</code> attribute to reference a
188  * dynamically generated dropdown list.
189  *
190  * <pre>
191  * &lt;netui:select dataSource="actionForm.selectedOption"
192  * optionsDataSource="${actionForm.itemOptions}" />
193  * </pre>
194  *
195  * <p>Assume that the <code>optionsDataSource</code> attribute refers to
196  * a <code>java.util.Map</code> object.
197  * The Map object will be rendered as a series
198  * of &lt;option> tags. HTML that is similar to the following will be
199  * rendered in the browser:</p>
200  *
201  * <pre> &lt;select name="wlw-select_key:{actionForm.itemOptions}">
202  * &lt;option value="633">Aurora Bridge&lt;/option>
203  * &lt;option value="631">FA-18 fighter jet&lt;/option>
204  * &lt;option value="635">Space Needle&lt;/option>
205  * &lt;option value="642">Thin Mints&lt;/option>
206  * ...
207  * &lt;/select></pre>
208  * @netui:tag name="select" description="Defines a multiple-choice menu or drop-down list within a netui:form element."
209  * @netui:attribute name="onSelect" hide="true" description=""
210  */

211 public class Select extends HtmlOptionsDataSourceTag
212         implements IDataAccessProvider, IFormattable
213 {
214     // @todo: needs to create DRT tests for: verification of errors, verirication of data sources matching,
215
// @todo: verification of formating inside a repeater.
216
// @todo: on the null tag, we need to default the null value to NULL_VALUE
217
// @todo: need to handle null, in the options
218
// @todo: should handle no optionDataSource when repeating...
219

220     private static final Logger logger = Logger.getInstance(Select.class);
221
222     private SelectTag.State _state = new SelectTag.State();
223     private OptionTag.State _optionState = new OptionTag.State();
224     private InputHiddenTag.State _hiddenState = new InputHiddenTag.State();
225     private boolean _formatterError = false;
226
227     private static Object JavaDoc[] NULL_INSTANCE = {null};
228
229
230     /**
231      * This enum defines stages through the possible option values.
232      */

233     public static class RepeatingStages
234     {
235         private static final int INT_BEFORE = 0;
236         private static final int INT_OPTION = 1;
237         private static final int INT_DEFAULT = 2;
238         private static final int INT_DATASOURCE = 3;
239         private static final int INT_NULL = 4;
240         private static final int INT_DONE = 5;
241
242         static final RepeatingStages BEFORE = new RepeatingStages(INT_BEFORE);
243         static final RepeatingStages OPTION = new RepeatingStages(INT_OPTION);
244         static final RepeatingStages DEFAULT = new RepeatingStages(INT_DEFAULT);
245         static final RepeatingStages DATASOURCE = new RepeatingStages(INT_DATASOURCE);
246         static final RepeatingStages NULL = new RepeatingStages(INT_NULL);
247         static final RepeatingStages DONE = new RepeatingStages(INT_DONE);
248         
249         /**
250          * These are the publically exposed stages, <code>REPEATING_OPTION, REPEATING_DEFAULT,
251          * REPEATING_DATASOURCE and REPEATING_NULL</code>.
252          */

253         public static final String JavaDoc REPEATING_OPTION = "option";
254         public static final String JavaDoc REPEATING_DEFAULT = "default";
255         public static final String JavaDoc REPEATING_DATASOURCE = "dataSource";
256         public static final String JavaDoc REPEATING_NULL = "null";
257
258         public int value;
259
260         // prevent construction...
261
private RepeatingStages(int val)
262         {
263             value = val;
264         }
265         
266         int getValue()
267         {
268             return value;
269         }
270
271         /**
272          * Returns the String value that can be used to order the selection.
273          * @return The String Value.
274          */

275         public String JavaDoc toString()
276         {
277             switch (value) {
278                 case INT_OPTION:
279                     return REPEATING_OPTION;
280                 case INT_DEFAULT:
281                     return REPEATING_DEFAULT;
282                 case INT_DATASOURCE:
283                     return REPEATING_DATASOURCE;
284                 case INT_NULL:
285                     return REPEATING_NULL;
286                 default:
287                     return "Unknown Stage";
288             }
289         }
290
291         /**
292          * Given a String value defined above, return the enum value for it.
293          * @param value a String value matching one of the public Strings defined for the class.
294          * @return the matching RepeatingStages or null.
295          */

296         public static RepeatingStages parseString(String JavaDoc value)
297         {
298             if (REPEATING_OPTION.equals(value))
299                 return OPTION;
300             if (REPEATING_DEFAULT.equals(value))
301                 return DEFAULT;
302             if (REPEATING_DATASOURCE.equals(value))
303                 return DATASOURCE;
304             if (REPEATING_NULL.equals(value))
305                 return NULL;
306             return null;
307         }
308     }
309
310     /**
311      * This defines the default order of processing the options when repeating.
312      */

313     private static final RepeatingStages[] DEFAULT_ORDER = {RepeatingStages.BEFORE,
314                                                             RepeatingStages.OPTION,
315                                                             RepeatingStages.DATASOURCE,
316                                                             RepeatingStages.DEFAULT,
317                                                             RepeatingStages.NULL};
318
319     /**
320      * Default value of the options <code>value</code> attribute.
321      */

322     public static final String JavaDoc NULL_VALUE = "netui_null";
323
324     /**
325      * Constant value of the <code>repeatingType</code> attribute for options handling the <code>null</code> option.
326      */

327     //public static final String REPEATING_NULL = "Null";
328

329     private static final String JavaDoc SELECT_KEY = "select_key";
330     private static final String JavaDoc OLDVALUE_SUFFIX = "OldValue";
331
332     // IDataAccessProvider support
333
private int _repIdx = 0; // The current index for repeating over the optionsDataSource
334
private RepeatingStages _repCurStage = RepeatingStages.BEFORE; // The current stage defined by the stage constants above
335
private boolean _repeater; // Boolean flag indicating if this is a repeater or not
336
private Object JavaDoc _repCurItem; // The current item access by the IDataAccessProvider
337
private Iterator _repeaterIterator; // The iterator being used to output the options.
338
private RepeatingStages[] _order = DEFAULT_ORDER;
339
340     private Object JavaDoc _dynamicOptions; // The interator (or map) for the options data source, repeating this is current var
341

342     private String JavaDoc _saveBody;
343     private String JavaDoc _nullableOptionText;
344
345     private List _defaultSelections;
346     private ArrayList _formatters;
347     private ArrayList _optionList;
348     private String JavaDoc[] _match; // The actual values we will match against
349
private boolean _nullable;
350     private TagRenderingBase _optRb;
351
352     private static final List _internalNamingChain;
353
354     static
355     {
356         List l = new ArrayList(3);
357         l.add(new FormDataNameInterceptor());
358         l.add(new IndexedNameInterceptor());
359         l.add(new PrefixNameInterceptor(SELECT_KEY));
360         _internalNamingChain = Collections.unmodifiableList(l);
361
362         org.apache.beehive.netui.pageflow.ProcessPopulate.registerPrefixHandler(SELECT_KEY, new SelectPrefixHandler());
363     }
364
365     /**
366      */

367     public static class SelectPrefixHandler
368             implements RequestParameterHandler
369     {
370         public void process(javax.servlet.http.HttpServletRequest JavaDoc request, String JavaDoc key,
371                             String JavaDoc expr, ProcessPopulate.ExpressionUpdateNode node)
372         {
373             String JavaDoc[] returnArray = null;
374
375             if (!key.endsWith(OLDVALUE_SUFFIX)) {
376                 //This select has values and should stay that way
377
returnArray = request.getParameterValues(key);
378             }
379             else {
380                 //Check the request to see if select also exists
381
String JavaDoc newKey = key.substring(0, key.indexOf(OLDVALUE_SUFFIX));
382                 String JavaDoc[] select = request.getParameterValues(newKey);
383                 if (select != null) {
384                     returnArray = select;
385                 }
386                 else {
387                     returnArray = new String JavaDoc[0]; //null;
388
}
389             }
390
391             if (node.expression.endsWith(OLDVALUE_SUFFIX)) {
392                 node.expression = node.expression.substring(0, node.expression.indexOf(OLDVALUE_SUFFIX));
393             }
394
395             //Check for the NULL_VALUE, replace it with null
396
for (int i = 0; i < returnArray.length; i++) {
397                 if (returnArray[i].equals(NULL_VALUE)) {
398                     returnArray[i] = null;
399                 }
400             }
401
402             node.values = returnArray;
403
404             if (logger.isDebugEnabled()) {
405                 logger.debug("\n*********************************************\n" +
406                         "process with key \"" + key + "\" and expression \"" + node.expression + "\"" + "and result size: "
407                         + (returnArray != null ? "" + returnArray.length : null) + "\n" +
408                         "*********************************************\n");
409             }
410         }
411     }
412
413     public Select()
414     {
415         super();
416     }
417
418     /**
419      * Return the name of the Tag.
420      */

421     public String JavaDoc getTagName()
422     {
423         return "Select";
424     }
425
426     public String JavaDoc getDataSource()
427     {
428         return _dataSource.toString();
429     }
430
431     /**
432      * This method will return the state associated with the tag. This is used by this
433      * base class to access the individual state objects created by the tags.
434      * @return a subclass of the <code>AbstractHtmlState</code> class.
435      */

436     protected AbstractHtmlState getState()
437     {
438         return _state;
439     }
440
441     /**
442      * Return an <code>ArrayList</code> which represents a chain of <code>INameInterceptor</code>
443      * objects. This method by default returns <code>null</code> and should be overridden
444      * by objects that support naming.
445      * @return an <code>ArrayList</code> that will contain <code>INameInterceptor</code> objects.
446      */

447     protected List getNamingChain()
448     {
449         return _internalNamingChain;
450     }
451
452     /**
453      * Evaluate the defaultValues
454      */

455     protected Object JavaDoc evaluateDefaultValue()
456             throws JspException JavaDoc
457     {
458         Object JavaDoc val = _defaultValue;
459
460         List defaults = null;
461         if (val instanceof String JavaDoc) {
462             defaults = new ArrayList();
463             defaults.add(val);
464         }
465         else {
466             Iterator optionsIterator = null;
467             optionsIterator = IteratorFactory.createIterator(val);
468
469             // default value is optional so only warn
470
if (optionsIterator == null && _defaultValue != null)
471                 logger.warn(Bundle.getString("Tags_IteratorError",
472                         new Object JavaDoc[]{getTagName(), "defaultValue", _defaultValue}));
473
474             if (optionsIterator == null)
475                 optionsIterator = IteratorFactory.EMPTY_ITERATOR;
476
477             defaults = new ArrayList();
478             while (optionsIterator.hasNext()) {
479                 Object JavaDoc o = optionsIterator.next();
480                 defaults.add(o.toString());
481             }
482         }
483
484         return defaults;
485     }
486
487     /**
488      * Set whether multiple selections are allowed.
489      * @param multiple the multiple value ("true" or "false")
490      * @jsptagref.attributedescription Boolean. Whether or not multi-selection is enabled.
491      * If multiple selection is enabled, a null option will not be displayed, even if
492      * the <code>nullable</code> is set to true.
493      * @jsptagref.databindable false
494      * @jsptagref.attributesyntaxvalue <i>boolean_multipleSelectEnabled</i>
495      * @netui:attribute required="false" rtexprvalue="true" type="boolean"
496      * description="Whether or not multi-selection is enabled.
497      * If multiple selection is enabled, a null option will not be displayed, even if
498      * the nullable is set to true."
499      */

500     public void setMultiple(boolean multiple)
501     {
502         _state.multiple = multiple;
503     }
504
505     /**
506      * Set whether repeating of contained options is on.
507      * @param repeater the repeater value ("true" or "false")
508      * @jsptagref.attributedescription Set whether repeating of contained options is on.
509      * @jsptagref.databindable false
510      * @jsptagref.attributesyntaxvalue <i>boolean_repeater</i>
511      * @netui:attribute required="false" rtexprvalue="true" type="boolean"
512      * description="Set whether repeating of contained options is on."
513      */

514     public void setRepeater(boolean repeater)
515     {
516         _repeater = repeater;
517     }
518
519     /**
520      * Gets whether a repeating contained options is on.
521      * @return the repeater value
522      */

523     public boolean isRepeater()
524     {
525         return _repeater;
526     }
527
528     /**
529      * This method will set the order of the options generated in the select. It must contain a
530      * comma separated string listing the order or the stages that the repeating types are processed.
531      * These values are "option", "dataSource", "default", and "null".
532      * @param order comma separated ordering of items when there is a repeating select.
533      * @jsptagref.attributedescription Define the order of options generated for a repeating Select.
534      * It must contain a comma separated string listing the order or the stages that the repeating types
535      * are processed. These values are "option", "dataSource", "default", and "null". For example,
536      * <pre> repeatingOrder="dataSource,option"</pre>
537      *
538      * Then a &lt;netui:selectOption> element could set the repeatingType attribute to "dataSource"
539      * while another is defined for "option".
540      * @jsptagref.databindable false
541      * @jsptagref.attributesyntaxvalue <i>string_order</i>
542      * @netui:attribute required="false" rtexprvalue="true"
543      * description="Define the order of options for a repeating Select"
544      */

545     public void setRepeatingOrder(String JavaDoc order)
546             throws JspException JavaDoc
547     {
548         String JavaDoc[] options = order.split(",");
549         RepeatingStages[] stageOrder = new RepeatingStages[options.length + 1];
550         stageOrder[0] = RepeatingStages.BEFORE;
551         for (int i = 0; i < options.length; i++) {
552             String JavaDoc opt = options[i].trim();
553             stageOrder[i + 1] = RepeatingStages.parseString(opt);
554             if (stageOrder[i + 1] == null) {
555                 String JavaDoc s = Bundle.getString("Tags_SelectBadRepeatingStage", new Object JavaDoc[]{opt});
556                 registerTagError(s, null);
557             }
558         }
559         _order = stageOrder;
560     }
561
562     /**
563      * Set whether a null option is desired.
564      * @param nullable the nullable value
565      * @jsptagref.attributedescription Boolean.
566      * Whether a option with the value null should be added to the bottom of the list.
567      * If &lt;select> has the multiple <code>attribute</code> set to true, the null option won't be shown.
568      * @jsptagref.databindable false
569      * @jsptagref.attributesyntaxvalue <i>boolean_nullable</i>
570      * @netui:attribute required="false" rtexprvalue="true" type="boolean"
571      * description="Whether a option with the value null should be added to the bottom of the list.
572      * If <select> has the multiple attribute set to true, the null option won't be shown."
573      */

574     public void setNullable(boolean nullable)
575     {
576         _nullable = nullable;
577     }
578
579     /**
580      * Gets the options datasource value (an expression).
581      * @return the options datasource
582      */

583     public Object JavaDoc getOptionsDataSource()
584     {
585         return _optionsDataSource;
586     }
587
588     /**
589      * Set the text of the nullable option.
590      * If the <code>nullable<code> option is true, this is
591      * the text of that option. The default is "";
592      * @jsptagref.attributedescription Boolean.
593      * If the <code>nullable</code> attribute is set to true, then the <code>nullableOptionText</code>
594      * attribute determines the display text of the null option.
595      * The default is to use the empty string, "", as the display text.
596      * @jsptagref.databindable false
597      * @jsptagref.attributesyntaxvalue <i>boolean_nullableOptionText</i>
598      * @netui:attribute required="false" rtexprvalue="true" type="boolean"
599      * description="If the nullable attribute is set to true, then the nullableOptionText
600      * attribute determines the display text of the null option.
601      */

602     public void setNullableOptionText(String JavaDoc nullableOptionText)
603     {
604         _nullableOptionText = nullableOptionText;
605     }
606
607     /**
608      * This method will return the object representing the <code>optionsDataSource</code>. This
609      * is overridden from the base class, because there are only two types which will be
610      * retunred from the method. The <code>optionsDataSource</code> will either be a instance of a <code>Map</code>
611      * or and instanceof a <code>Iterator</code>.
612      * @return the object instance object representing the objectsDataSource. This may be null.
613      * @throws JspException on an error
614      */

615     protected Object JavaDoc evaluateOptionsDataSource()
616             throws JspException JavaDoc
617     {
618         Object JavaDoc val = _optionsDataSource;
619         if (val == null) {
620             // optionsDataSource is option so this is a warning
621
if (_optionsDataSource != null)
622                 logger.warn(Bundle.getString("Tags_IteratorError",
623                         new Object JavaDoc[]{getTagName(), "optionsDataSource", _optionsDataSource}));
624             return null;
625         }
626
627         if (val instanceof Map)
628             return val;
629
630         Iterator options = null;
631         options = IteratorFactory.createIterator(val);
632         if (options == null)
633             options = IteratorFactory.EMPTY_ITERATOR;
634
635         return options;
636     }
637
638     /**
639      * Sets how many options are displayed.
640      * @param size the size (a number)
641      * @jsptagref.attributedescription The number of visible options
642      * @jsptagref.databindable false
643      * @jsptagref.attributesyntaxvalue <i>integer_size</i>
644      * @netui:attribute required="false" rtexprvalue="true" type="int"
645      * description="The number of visible options"
646      */

647     public void setSize(int size)
648     {
649         _state.size = size;
650     }
651
652     /**
653      * Does the specified value match one of those we are looking for?
654      * @param value Value to be compared
655      */

656     public boolean isMatched(String JavaDoc value)
657     {
658         if (value == null)
659             return false;
660         if ((_match != null)) {
661             for (int i = 0; i < _match.length; i++) {
662                 if (value.equals(_match[i]))
663                     return true;
664             }
665         }
666         else {
667             if (_defaultSelections != null) {
668                 return (_defaultSelections.contains(value));
669             }
670         }
671
672         return false;
673
674     }
675
676     //********************************** IDataAccessProvider Interface ******************************
677
// setDataSource is implemented by the HtmlDataSourceTag class
678
// getDataSource is implemented by the HtmlDataSourceTag class
679

680     /**
681      * Get the current index in this iteration. This should be a
682      * zero based integer that increments after each iteration.
683      * @return the current index of iteration or 0
684      */

685     public int getCurrentIndex()
686     {
687         return _repIdx;
688     }
689
690     /**
691      * Get the current data item in this IDataAccessProvider.
692      * @return the current data item or <code>null</code>
693      */

694     public Object JavaDoc getCurrentItem()
695     {
696         return _repCurItem;
697     }
698
699     /**
700      * Get a metadata object for the current item. This interface
701      * is optional, and implementations of this interface are
702      * provided by the IDataAccessProvider interface. See these
703      * implementations for information about their support for
704      * current item metadata.
705      * @return the current metadata or <code>null</code> if no metadata can be
706      * found or metadata is not supported by a IDataAccessProvider implementation
707      */

708     public Object JavaDoc getCurrentMetadata()
709     {
710         return this;
711     }
712
713     /**
714      * Get the parent IDataAccessProvider of a IDataAccessProvider. A IDataAccessProvider
715      * implementation may be able to nest IDataAccessProviders. In this case,
716      * it can be useful to be able to also nest access to data from parent
717      * providers. Implementations of this interface are left with having
718      * to discover and export parents. The return value from this call
719      * on an implementing Object can be <code>null</code>.
720      * @return the parent IDataAccessProvider or <code>null</code> if this method
721      * is not supported or the parent can not be found.
722      */

723     public IDataAccessProvider getProviderParent()
724     {
725         return (IDataAccessProvider) findAncestorWithClass(this, IDataAccessProvider.class);
726     }
727
728     /**
729      * Return the enum value of the currently repeating stage.
730      * @return The currently repeating stage.
731      */

732     public RepeatingStages getRepeatingStage()
733     {
734         return _repCurStage;
735     }
736
737     /**
738      * Boolean indicating that we are processing the optionsDataSource.
739      * @return <code>true</code> if we are processing the optionsDataSource.
740      */

741     public boolean isOptionStage()
742     {
743         return _repCurStage == RepeatingStages.OPTION;
744     }
745
746     /**
747      * Boolean indicating that we are processing the defaultValue.
748      * @return <code>true</code> if we are processing the defaultValue.
749      */

750     public boolean isDefaultStage()
751     {
752         return _repCurStage == RepeatingStages.DEFAULT;
753     }
754
755     /**
756      * Boolean indicating that we are processing the dataSource.
757      * @return <code>true</code> if we are processing the dataSource.
758      */

759     public boolean isDataSourceStage()
760     {
761         return _repCurStage == RepeatingStages.DATASOURCE;
762     }
763
764     /**
765      * Boolean indicating that we are processing the defined null value.
766      * @return <code>true</code> if we are processing the defined null value.
767      */

768     public boolean isNullStage()
769     {
770         return _repCurStage == RepeatingStages.NULL;
771     }
772
773     /**
774      * Render the beginning of this select.
775      * @throws JspException if a JSP exception has occurred
776      */

777     public int doStartTag() throws JspException JavaDoc
778     {
779         Object JavaDoc val = evaluateDataSource();
780         _defaultSelections = (List) evaluateDefaultValue();
781
782         // if there were expression errors report them
783
if (hasErrors())
784             return SKIP_BODY;
785
786         buildMatch(val);
787         if (hasErrors())
788             return SKIP_BODY;
789
790
791         _formatters = new ArrayList();
792         _optionList = new ArrayList();
793
794         // Walk the options data source
795
_dynamicOptions = evaluateOptionsDataSource();
796         if (_repeater) {
797             _repCurStage = _order[0];
798             boolean valid = doRepeaterAfterBody();
799             if (!valid)
800                 return SKIP_BODY;
801             DataAccessProviderStack.addDataAccessProvider(this, pageContext);
802         }
803
804         // Continue processing this page
805
return EVAL_BODY_BUFFERED;
806     }
807
808     /**
809      * Save any body content of this tag, which will generally be the
810      * option(s) representing the values displayed to the user.
811      * @throws JspException if a JSP exception has occurred
812      */

813     public int doAfterBody() throws JspException JavaDoc
814     {
815         if (hasErrors()) {
816             return SKIP_BODY;
817         }
818
819         // if this is a repeater we need to repeater over the body...
820
if (_repeater) {
821             if (doRepeaterAfterBody())
822                 return EVAL_BODY_AGAIN;
823         }
824
825         if (bodyContent != null) {
826             String JavaDoc value = bodyContent.getString();
827             bodyContent.clearBody();
828             if (value == null)
829                 value = "";
830             _saveBody = value.trim();
831         }
832         return SKIP_BODY;
833     }
834
835     /**
836      * Render the end of this select.
837      * @throws JspException if a JSP exception has occurred
838      */

839     public int doEndTag() throws JspException JavaDoc
840     {
841         ServletRequest JavaDoc req = pageContext.getRequest();
842
843         String JavaDoc fmtErrors = null;
844         if (_formatterError) {
845             fmtErrors = getErrorsFromBody();
846         }
847         if (hasErrors())
848             return reportAndExit(EVAL_PAGE);
849
850         _state.disabled = isDisabled();
851
852         //Create hidden field for state tracking
853
ByRef ref = new ByRef();
854         nameHtmlControl(_state, ref);
855
856         if (hasErrors())
857             return reportAndExit(EVAL_PAGE);
858
859         // Only write out the hidden field if the select is not
860
// disabled. If it is disabled, then nothing will be posted
861
// back from this.
862
WriteRenderAppender writer = new WriteRenderAppender(pageContext);
863         if (!_state.disabled) {
864             _hiddenState.clear();
865             String JavaDoc hiddenParamName = null;
866             hiddenParamName = _state.name + OLDVALUE_SUFFIX;
867             _hiddenState.name = hiddenParamName;
868             _hiddenState.value = "true";
869
870             TagRenderingBase hiddenTag = TagRenderingBase.Factory.getRendering(TagRenderingBase.INPUT_HIDDEN_TAG, req);
871             hiddenTag.doStartTag(writer, _hiddenState);
872             hiddenTag.doEndTag(writer);
873             write("\n");
874         }
875
876         // Render any formatting errors that may have occurred.
877
if (fmtErrors != null)
878             write(fmtErrors);
879
880
881         TagRenderingBase br = TagRenderingBase.Factory.getRendering(TagRenderingBase.SELECT_TAG, req);
882         br.doStartTag(writer, _state);
883
884         // Render the content of the body, these would be the options
885
if (_saveBody != null) {
886             write(_saveBody);
887         }
888
889         // if we are repeating then the body contained the options so we can exit here
890
if (_repeater) {
891
892             if (hasErrors())
893                 return reportAndExit(EVAL_PAGE);
894
895             br.doEndTag(writer);
896             if (!ref.isNull())
897                 write((String JavaDoc) ref.getRef());
898
899             // Continue processing this page
900
localRelease();
901             return EVAL_PAGE;
902         }
903
904         // All of the code below will pass through the optionsDataSource, the dataSource and defaultValue and
905
// create a full Select.
906
if (_dynamicOptions != null) {
907             if (_dynamicOptions instanceof Map) {
908                 Map dynamicOptionsMap = (Map) _dynamicOptions;
909                 Iterator keyIterator = dynamicOptionsMap.keySet().iterator();
910                 while (keyIterator.hasNext()) {
911                     Object JavaDoc optionValue = keyIterator.next();
912                     String JavaDoc optionDisplay = null;
913                     if (dynamicOptionsMap.get(optionValue) != null) {
914                         optionDisplay = dynamicOptionsMap.get(optionValue).toString();
915                     }
916
917                     addOption(req, optionValue.toString(), optionDisplay);
918                 }
919             }
920             else if (_dynamicOptions instanceof Iterator) {
921                 Iterator dynamicOptionsIterator = (Iterator) evaluateOptionsDataSource();
922                 while (dynamicOptionsIterator.hasNext()) {
923                     Object JavaDoc o = dynamicOptionsIterator.next();
924                     if (o != null) {
925                         String JavaDoc optionValue = o.toString();
926                         addOption(req, optionValue, optionValue);
927                     }
928                 }
929             }
930         }
931
932         // add the value from the DataSource and Default value
933
addDatasourceIfNeeded(req);
934         addDefaultsIfNeeded(req);
935         if (_nullable && !isMultiple()) {
936             String JavaDoc txt = (_nullableOptionText != null) ? _nullableOptionText : "";
937             addOption(req, NULL_VALUE, txt);
938         }
939
940         br.doEndTag(writer);
941         if (!ref.isNull())
942             write((String JavaDoc) ref.getRef());
943
944         // Continue processing this page
945
localRelease();
946         return EVAL_PAGE;
947     }
948
949     /**
950      * Release any acquired resources.
951      */

952     protected void localRelease()
953     {
954         if (_repeater)
955             DataAccessProviderStack.removeDataAccessProvider(pageContext);
956
957         super.localRelease();
958         _state.clear();
959
960         _defaultSelections = null;
961         _formatters = null;
962         _match = null;
963         _saveBody = null;
964         _nullable = false;
965         _nullableOptionText = null;
966         _optionList = null;
967
968         _repIdx = 0;
969         _repeater = false;
970         _repCurItem = null;
971         _repCurStage = RepeatingStages.BEFORE;
972         _dynamicOptions = null;
973         _formatterError = false;
974         _optRb = null;
975
976         _order = DEFAULT_ORDER;
977     }
978
979     private String JavaDoc getErrorsFromBody()
980     {
981         final String JavaDoc END_TOKEN = "</span>";
982         assert(_saveBody != null);
983         InternalStringBuilder body = new InternalStringBuilder(_saveBody.length());
984         InternalStringBuilder error = new InternalStringBuilder(_saveBody.length());
985
986         // pull out all of the spans These should be legally constructed, otherwise we will ignore them.
987
int len = _saveBody.length();
988         int pos = 0;
989         while (pos < len) {
990
991             // find the start of a span, if we dont' find one then it's over....
992
int start = _saveBody.indexOf("<span", pos);
993             if (start == -1)
994                 break;
995
996             // if we don't find the end of the <span> then we don't have a legal span so ignore it
997
int end = _saveBody.indexOf(END_TOKEN);
998             if (end == -1)
999                 break;
1000
1001            // copy the pos to start into the body
1002
int realEnd = end + END_TOKEN.length() + 1;
1003            body.append(_saveBody.substring(pos, start));
1004            error.append(_saveBody.substring(start, realEnd));
1005            pos = realEnd;
1006        }
1007
1008        // recreate the remainder of the body, everything not left
1009
body.append(_saveBody.substring(pos, len));
1010        _saveBody = body.toString();
1011
1012        // return the error
1013
return error.toString();
1014    }
1015
1016    /**
1017     * This method will side affects the <code>_repCurItem</code> to insure that it
1018     * is set to the next item in the iteration set. It will return <code>true</code>
1019     * if there is a next item, and <code>false</code> when we are done with the iteration
1020     * @return returns <code>true</code> when <code>_repCurItem</code> contains the next item and
1021     * <code>false</code> when we are done.
1022     * @throws JspException
1023     */

1024    private boolean doRepeaterAfterBody()
1025            throws JspException JavaDoc
1026    {
1027        switch (_repCurStage.getValue()) {
1028            case RepeatingStages.INT_BEFORE:
1029                if (!moveNext())
1030                    return false;
1031                return doRepeaterAfterBody();
1032            case RepeatingStages.INT_OPTION:
1033                assert (_repeaterIterator instanceof Iterator);
1034                while (_repeaterIterator.hasNext()) {
1035                    _repCurItem = _repeaterIterator.next();
1036                    if (_repCurItem != null) {
1037                        _optionList.add(_repCurItem);
1038                        return true;
1039                    }
1040                }
1041                if (!moveNext())
1042                    return false;
1043                return doRepeaterAfterBody();
1044
1045            case RepeatingStages.INT_DEFAULT:
1046            case RepeatingStages.INT_DATASOURCE:
1047            case RepeatingStages.INT_NULL:
1048                assert (_repeaterIterator instanceof Iterator);
1049                while (_repeaterIterator.hasNext()) {
1050                    _repCurItem = _repeaterIterator.next();
1051                    if (!_optionList.contains(_repCurItem)) {
1052                        _optionList.add(_repCurItem);
1053                        return true;
1054                    }
1055                }
1056                if (!moveNext())
1057                    return false;
1058                return doRepeaterAfterBody();
1059        }
1060        return false;
1061    }
1062
1063    /**
1064     * This method will move to the next iteration type. The order of the
1065     * iteration is defined by the <code>_order</code> array. The result
1066     * is side affecting the _repeaterIterator by initializing it. If there
1067     * is nothing further, then we will return false, otherwise we return true.
1068     * @return
1069     * @throws JspException
1070     */

1071    private boolean moveNext()
1072            throws JspException JavaDoc
1073    {
1074        // increment the current position, if we are beyond the end of the array return
1075
_repIdx++;
1076        if (_repIdx == _order.length)
1077            return false;
1078
1079        // Get the next stage and clear the _repeaterIterator
1080
_repCurStage = _order[_repIdx];
1081        _repeaterIterator = null;
1082
1083        // process each type of iteration...
1084
// Each will recursively call moveNext, if that stage doesn't support iteration
1085
switch (_repCurStage.getValue()) {
1086            case RepeatingStages.INT_BEFORE:
1087                break;
1088            case RepeatingStages.INT_OPTION:
1089                // This produces an error if the optionsDataSource is an instance of an iterator
1090
if (!(_dynamicOptions instanceof Iterator)) {
1091                    String JavaDoc s = Bundle.getString("Tags_OptionsDSIteratorError");
1092                    registerTagError(s, null);
1093                    return false;
1094                }
1095
1096                assert(_dynamicOptions instanceof Iterator);
1097                _repeaterIterator = (Iterator) _dynamicOptions;
1098                break;
1099
1100            case RepeatingStages.INT_DEFAULT:
1101                if (_defaultSelections != null)
1102                    _repeaterIterator = _defaultSelections.iterator();
1103                break;
1104            case RepeatingStages.INT_DATASOURCE:
1105                if (_match != null)
1106                    _repeaterIterator = Arrays.asList(_match).iterator();
1107                break;
1108            case RepeatingStages.INT_NULL:
1109                if (_nullable)
1110                    _repeaterIterator = new ArrayIterator(NULL_INSTANCE);
1111                break;
1112        }
1113
1114        // return true when we set the iterator, otherwise move to the next stage.
1115
return (_repeaterIterator != null) ? true : moveNext();
1116    }
1117
1118    /**
1119     * This method builds the list of selected items so that they can be marked as selected.
1120     * @param val The <code>dataSource</code>
1121     */

1122    private void buildMatch(Object JavaDoc val)
1123    {
1124        // create the match data
1125
if (val != null) {
1126            if (val instanceof String JavaDoc) {
1127                _match = new String JavaDoc[]{(String JavaDoc) val};
1128            }
1129            else if (val instanceof String JavaDoc[]) {
1130                String JavaDoc[] s = (String JavaDoc[]) val;
1131                int cnt = 0;
1132                for (int i = 0; i < s.length; i++) {
1133                    if (s[i] != null)
1134                        cnt++;
1135                }
1136                if (cnt == s.length)
1137                    _match = s;
1138                else {
1139                    if (cnt > 0) {
1140                        _match = new String JavaDoc[cnt];
1141                        cnt = 0;
1142                        for (int i = 0; i < s.length; i++) {
1143                            if (s[i] != null) {
1144                                _match[cnt++] = s[i];
1145                            }
1146                        }
1147                    }
1148                }
1149            }
1150            else {
1151                Iterator matchIterator = null;
1152                // val is never null so this would be an error
1153
matchIterator = IteratorFactory.createIterator(val);
1154                if (matchIterator == null) {
1155                    matchIterator = IteratorFactory.EMPTY_ITERATOR;
1156                }
1157
1158                ArrayList matchList = new ArrayList();
1159                while (matchIterator.hasNext()) {
1160                    Object JavaDoc o = matchIterator.next();
1161                    if (o == null)
1162                        continue;
1163                    matchList.add(o);
1164                }
1165
1166                int size = matchList.size();
1167                _match = new String JavaDoc[size];
1168                for (int i = 0; i < size; i++) {
1169                    assert (matchList.get(i) != null);
1170                    assert (matchList.get(i).toString() != null);
1171                    _match[i] = matchList.get(i).toString();
1172                }
1173            }
1174            if (logger.isDebugEnabled()) {
1175                logger.debug("****** Select Matches ******");
1176                if (_match != null) {
1177                    for (int i = 0; i < _match.length; i++) {
1178                        logger.debug(i + ": " + _match[i]);
1179                    }
1180                }
1181            }
1182        }
1183        else {
1184            if (_nullable && !isMultiple()) {
1185                _match = new String JavaDoc[]{NULL_VALUE};
1186            }
1187        }
1188    }
1189
1190    // add the default values specified in the tag if they are needed.
1191
private void addDefaultsIfNeeded(ServletRequest JavaDoc req)
1192            throws JspException JavaDoc
1193    {
1194        if (_defaultSelections != null) {
1195            Iterator iterator = _defaultSelections.iterator();
1196            while (iterator.hasNext()) {
1197                Object JavaDoc selection = iterator.next();
1198                if (!_optionList.contains(selection)) {
1199                    addOption(req, selection.toString(), selection.toString());
1200                }
1201            }
1202        }
1203    }
1204
1205    private boolean isMultiple()
1206    {
1207        return _state.multiple;
1208    }
1209
1210    // add dthe datasource values if needed.
1211
private void addDatasourceIfNeeded(ServletRequest JavaDoc req)
1212            throws JspException JavaDoc
1213    {
1214        if (_match == null)
1215            return;
1216
1217        for (int i = 0; i < _match.length; i++) {
1218            if (!_optionList.contains(_match[i])) {
1219                if (!_match[i].equals(NULL_VALUE))
1220                    addOption(req, _match[i], _match[i]);
1221            }
1222        }
1223    }
1224
1225    private void addOption(ServletRequest JavaDoc req, String JavaDoc optionValue, String JavaDoc optionDisplay)
1226            throws JspException JavaDoc
1227    {
1228        assert(optionValue != null);
1229        assert(optionDisplay != null);
1230
1231        write("\n");
1232        _optionState.clear();
1233        _optionState.value = optionValue;
1234        _optionState.style = _state.style;
1235        _optionState.styleClass = _state.styleClass;
1236
1237        if (isMatched(optionValue)) {
1238            _optionState.selected = true;
1239        }
1240
1241        WriteRenderAppender writer = new WriteRenderAppender(pageContext);
1242        if (_optRb == null)
1243            _optRb = TagRenderingBase.Factory.getRendering(TagRenderingBase.OPTION_TAG, req);
1244        _optRb.doStartTag(writer, _optionState);
1245
1246
1247        if (optionDisplay != null) {
1248            write(formatText(optionDisplay));
1249        }
1250        else {
1251            write("&lt;");
1252            write(optionValue);
1253            write(">");
1254        }
1255
1256        _optRb.doEndTag(writer);
1257
1258        addOptionToList(optionValue);
1259    }
1260
1261    /**
1262     * Adds a FormatTag.Formatter to the Select's set of formatters
1263     * @param formatter a FormatTag.Formatter added by a child FormatTag.
1264     */

1265    public void addFormatter(FormatTag.Formatter formatter)
1266    {
1267        _formatters.add(formatter);
1268    }
1269
1270    /**
1271     * Indicate that a formatter has reported an error so the formatter should output it's
1272     * body text.
1273     */

1274    public void formatterHasError()
1275    {
1276        _formatterError = true;
1277    }
1278
1279    /**
1280     */

1281    public void addOptionToList(String JavaDoc value)
1282    {
1283        _optionList.add(value);
1284    }
1285
1286    /**
1287     * Apply the Select's set of formatters to the given text
1288     * @param text the text to format.
1289     * @return the formatted text
1290     */

1291    public String JavaDoc formatText(Object JavaDoc text)
1292            throws JspException JavaDoc
1293    {
1294        int cnt = _formatters.size();
1295        for (int i = 0; i < cnt; i++) {
1296            FormatTag.Formatter currentFormatter = (FormatTag.Formatter) _formatters.get(i);
1297            try {
1298                text = currentFormatter.format(text);
1299            }
1300            catch (JspException JavaDoc e) {
1301                registerTagError(e.getMessage(), e);
1302            }
1303        }
1304        return text.toString();
1305    }
1306
1307    /* ==================================================================
1308     *
1309     * This tag's publically exposed HTML, CSS, and JavaScript attributes
1310     *
1311     * ==================================================================
1312     */

1313
1314    /**
1315     * Sets the accessKey attribute value. This should key value of the
1316     * keyboard navigation key. It is recommended not to use the following
1317     * values because there are often used by browsers <code>A, C, E, F, G,
1318     * H, V, left arrow, and right arrow</code>.
1319     * @param accessKey the accessKey value.
1320     * @jsptagref.attributedescription The keyboard navigation key for the element.
1321     * The following values are not recommended because they
1322     * are often used by browsers: <code>A, C, E, F, G,
1323     * H, V, left arrow, and right arrow</code>
1324     * @jsptagref.databindable false
1325     * @jsptagref.attributesyntaxvalue <i>string_accessKey</i>
1326     * @netui:attribute required="false" rtexprvalue="true" type="char"
1327     * description="The keyboard navigation key for the element.
1328     * The following values are not recommended because they
1329     * are often used by browsers: A, C, E, F, G,
1330     * H, V, left arrow, and right arrow"
1331     */

1332    public void setAccessKey(char accessKey)
1333    {
1334        _state.registerAttribute(AbstractHtmlState.ATTR_GENERAL, ACCESSKEY, Character.toString(accessKey));
1335    }
1336
1337    /**
1338     * Sets the tabIndex of the rendered html tag.
1339     * @param tabindex the tab index.
1340     * @jsptagref.attributedescription The tabIndex of the rendered HTML tag. This attribute determines the position of the
1341     * tag in the sequence of page elements that the user may advance through by pressing the TAB key.
1342     * @jsptagref.databindable false
1343     * @jsptagref.attributesyntaxvalue <i>string_tabIndex</i>
1344     * @netui:attribute required="false" rtexprvalue="true" type="int"
1345     * description="The tabIndex of the rendered HTML tag. This attribute determines the position of the
1346     * tag in the sequence of page elements that the user may advance through by pressing the TAB key."
1347     */

1348    public void setTabindex(int tabindex)
1349    {
1350        _state.registerAttribute(AbstractHtmlState.ATTR_GENERAL, TABINDEX, Integer.toString(tabindex));
1351    }
1352}
1353
1354
Popular Tags