1 package com.randomnoun.common.webapp.taglib;
2
3 /* (c) 2013 randomnoun. All Rights Reserved. This work is licensed under a
4 * BSD Simplified License. (http://www.randomnoun.com/bsd-simplified.html)
5 */
6
7 import java.io.*;
8 import java.text.*;
9 import java.util.*;
10
11 import jakarta.servlet.jsp.*;
12
13 import org.apache.log4j.Logger;
14
15 import com.randomnoun.common.Struct;
16 import com.randomnoun.common.ErrorList;
17 import com.randomnoun.common.Text;
18
19 /**
20 * Render a dynamic HTML SELECT tag using information from a structured list.
21 *
22 * <p>This class is intended to be used in a JSP 2.0 container, i.e. EL expressions
23 * will have already been evaluated by the container before they reach this class.
24 *
25 * <p>Attributes defined for this tag (in common.tld) are:
26 * <ul>
27 * <li> name - the NAME element of the generated SELECT tag.
28 * <li> value - the current value of the SELECT tag.
29 * <li> data - a reference to a structured list containing both display and value columns,
30 * or a list of Strings. An empty list can be forced by setting an empty data attribute.
31 * <li> valueColumn - the name of the column in the list to use to populate OPTION values
32 * <li> displayColumn - the name of the column in the list to use to populate OPTION display text
33 * (will default to valueColumn if left blank)
34 * <li> firstOption - specifies a display value for the first option for a list; this
35 * option corresponds to a null value. Used to generate options like
36 * '(please select...)' at the top of a select box.
37 * <li> bundle - the resource bundle used to internationalise display column text
38 * <li> bundleFormat - a MessageFormat used to convert a value into a bundle key
39 * </ul>
40 *
41 * Only one of 'value' and 'valueFromRequest' can be set.
42 *
43 * <p>Additional attributes that are passed directly through to HTML:
44 * See {@link StandardHtmlTag} for a list of additional attributes which
45 * will be directly generated as HTML.
46 *
47 *
48 *
49 */
50 public class SelectTag
51 extends StandardHtmlTag
52 {
53
54
55
56 /** Logger for this class */
57 private final static Logger logger = Logger.getLogger(SelectTag.class);
58
59 /** The name of the SELECT tag. */
60 private String name;
61
62 /** The current value of the SELECT tag. */
63 private String value;
64
65 /** The value of the 'data' attribute */
66 private String dataString;
67
68 /** A reference to a structured list (evaluated from dataString). */
69 private List data;
70
71 /** The value of the 'bundle' attribute */
72 private String bundleString;
73
74 /** The value of the 'bundlePattern' attribute */
75 private String bundleFormat;
76
77 /** A reference to a resource bundle (evaluated from bundleString). */
78 private ResourceBundle bundle;
79
80 /** The name of the column in the list to use to populate OPTION values */
81 private String valueColumn;
82
83 /** The name of the column in the list to use to populate OPTION display text */
84 private String displayColumn;
85
86 /** Some display text for a first OPTION of the SELECT tag */
87 private String firstOption;
88
89 /** If non-null, and the displayValue or value to be displayed is a Date, will format the
90 * value with a SimpleDateFormatter object with this pattern
91 */
92 private String formatDate;
93
94 /* ***********************************************************************
95 * Tag Library attribute methods
96 */
97
98 /** Sets the name of the SELECT tag.
99 * @param value the name of the SELECT tag.
100 */
101 public void setName(String name)
102 {
103 this.name = name;
104 }
105
106 /** Retrieves the name of the SELECT tag.
107 * @return the name of the SELECT tag.
108 */
109 public String getName()
110 {
111 return name;
112 }
113
114 /** Sets the current value of the SELECT tag.
115 * @param value the current value of the SELECT tag.
116 */
117 public void setValue(String value)
118 {
119 this.value = value;
120 }
121
122 /** Retrieves the current value of the SELECT tag.
123 * @return the current value of the SELECT tag.
124 */
125 public String getValue()
126 {
127 return value;
128 }
129
130 /** Sets the structured list used to populate the SELECT tag.
131 * @param value the structured list used to populate the SELECT tag.
132 */
133 public void setData(Object data)
134 {
135 this.data = (List) data;
136 }
137
138 /** Retrieves the structured list used to populate the SELECT tag.
139 * @return the structured list used to populate the SELECT tag.
140 */
141 public Object getData()
142 {
143 return dataString;
144 }
145
146 /** Sets the resource bundle used to internationalise display column text
147 * @param value the resource bundle used to internationalise display column text
148 */
149 public void setBundle(String bundleString)
150 {
151 this.bundleString = bundleString;
152 }
153
154 /** Retrieves the resource bundle used to internationalise display column text
155 * @return the resource bundle used to internationalise display column text
156 */
157 public String getBundle()
158 {
159 return bundleString;
160 }
161
162 /** Sets how the resource bundle will be used to retrieve i18n text
163 * @param value how the resource bundle will be used to retrieve i18n text
164 */
165 public void setBundleFormat(String bundleFormat)
166 {
167 this.bundleFormat = bundleFormat;
168 }
169
170 /** Retrieves how the resource bundle will be used to retrieve i18n text
171 * @return how the resource bundle will be used to retrieve i18n text
172 */
173 public String bundlePattern()
174 {
175 return bundleFormat;
176 }
177
178 /** Sets the column name in the 'data' structured list used to represent
179 * OPTION values in the generated SELECT HTML.
180 * @param value the column name in the 'data' structured list used to represent
181 * OPTION values in the generated SELECT HTML.
182 */
183 public void setValueColumn(String valueColumn)
184 {
185 this.valueColumn = valueColumn;
186 }
187
188 /** Retrieves the column name in the 'data' structured list used to represent
189 * OPTION values in the generated SELECT HTML.
190 * @return the column name in the 'data' structured list used to represent
191 * OPTION values in the generated SELECT HTML.
192 */
193 public String getValueColumn()
194 {
195 return valueColumn;
196 }
197
198 /** Sets the column name in the 'data' structured list used to represent
199 * OPTION values in the generated SELECT HTML.
200 * @param value the column name in the 'data' structured list used to represent
201 * OPTION values in the generated SELECT HTML.
202 */
203 public void setDisplayColumn(String displayColumn)
204 {
205 this.displayColumn = displayColumn;
206 }
207
208 /** Retrieves the column name in the 'data' structured list used to represent
209 * OPTION display text in the generated SELECT HTML.
210 * @return the column name in the 'data' structured list used to represent
211 * OPTION display text in the generated SELECT HTML.
212 */
213 public String getDisplayColumn()
214 {
215 return displayColumn;
216 }
217
218 /** Sets the display text for a first OPTION of the SELECT tag.
219 * @param value the display text for a first OPTION of the SELECT tag.
220 */
221 public void setFirstOption(String firstOption)
222 {
223 this.firstOption = firstOption;
224 }
225
226 /** Retrieves the display text for a first OPTION of the SELECT tag.
227 * @return the display text for a first OPTION of the SELECT tag.
228 */
229 public String getFirstOption()
230 {
231 return firstOption;
232 }
233
234 /** Sets the date pattern to use to format Date objects
235 * @param value the date pattern to use to format Date objects
236 */
237 public void setFormatDate(String formatDate)
238 {
239 this.formatDate = formatDate;
240 }
241
242 /** Retrieves the date pattern to use to format Date objects
243 * @return the date pattern to use to format Date objects
244 */
245 public String getFormatDate()
246 {
247 return formatDate;
248 }
249
250 /** Sets the name of the multiple attribute.
251 * @param value the name of the multiple attribute.
252 */
253 public void setMultiple(String multiple)
254 {
255 this.attributes.put("multiple", multiple);
256 }
257
258 /** Retrieves the name of the multiple attribute.
259 * @return the name of the multiple attribute.
260 */
261 public String getMultiple()
262 {
263 return (String) attributes.get("multiple");
264 }
265
266
267
268
269
270 /* ***********************************************************************
271 * End of tag library attribute methods
272 */
273
274 /** Start custom taglibrary processing. Sets internal private variables based on
275 * HttpSession values and defaults, and emits the generating HTML. The body
276 * of this tag will not be evaluated, as declared in the the .tld file.
277 *
278 * @todo escape HTML attributes and values
279 *
280 * @return This method always returns TagSupport.SKIP_BODY, as required by the
281 * JSP Taglib specification for empty tags
282 */
283 public int doStartTag()
284 throws JspException
285 {
286 try {
287 SimpleDateFormat sdf = null;
288 evaluateAttributes();
289 if (formatDate!=null) {
290 sdf = new SimpleDateFormat(formatDate);
291 }
292 JspWriter out = pageContext.getOut();
293
294 // use value column for display if not explicitly set
295 if (displayColumn == null) {
296 displayColumn = valueColumn;
297 }
298
299 // remove quotes from name
300 if (name.startsWith("\"")) {
301 name = name.substring(1);
302 }
303 if (name.endsWith("\"")) {
304 name = name.substring(0, name.length() - 1);
305 }
306
307 // determine styling based on whether this field is in error
308 ErrorList errors = null;
309 try {
310 errors = (ErrorList)pageContext.getAttribute("errors",
311 PageContext.REQUEST_SCOPE);
312 } catch (ClassCastException cce) {
313 // just ignore these.
314 }
315
316 String fieldStyle;
317 boolean hasError = false;
318 fieldStyle = "inputField";
319 if (errors != null && errors.hasErrorOn(name)) {
320 fieldStyle = "inputFieldError";
321 hasError = true;
322 }
323 if (attributes.containsKey("class")) {
324 attributes.put("class", fieldStyle + " " + attributes.get("class"));
325 } else {
326 attributes.put("class", fieldStyle);
327 }
328
329 // generate the HTML
330 if (hasError) {
331 out.print("<span class=\"inputFieldError\">");
332 }
333 out.print("<select name=\"" + name + "\" id=\"" + name + "\" " + this.getAttributeString() + ">");
334 if (firstOption != null) {
335 out.println("<option value=\"\">" + Text.escapeHtml(firstOption) +
336 "</option>\n");
337 }
338
339 // generate select options
340 if (data != null) {
341 for (Iterator i = data.iterator(); i.hasNext();) {
342 Object obj = i.next();
343 Object displayObj = null;
344 Object valueObj = null;
345 String valueText = "";
346 String displayText = null;
347
348 // could generalise the first two cases here
349 if (obj instanceof Map) {
350 Map row = (Map) obj;
351 if (row.get(displayColumn) != null) {
352 displayObj = row.get(displayColumn);
353 if (displayObj instanceof Date && sdf!=null) {
354 displayText = sdf.format((Date) displayObj);
355 } else {
356 displayText = displayObj.toString();
357 }
358 }
359
360 valueObj = row.get(valueColumn);
361 if (valueObj == null) {
362 throw new IllegalStateException(
363 "Null value encountered in SelectTag data '" + dataString + "'");
364 }
365 if (valueObj instanceof Date && sdf!=null) {
366 valueText = sdf.format((Date) valueObj);
367 } else {
368 valueText = valueObj.toString();
369 }
370 if (displayText == null) {
371 displayText = valueText;
372 }
373 } else if (displayColumn!=null && valueColumn!=null) {
374 valueObj = Struct.getValue(obj, valueColumn);
375 displayObj = (Struct.getValue(obj, displayColumn));
376 if (valueObj == null) {
377 throw new IllegalStateException(
378 "Null value encountered in SelectTag data '" + dataString + "'");
379 }
380 if (valueObj instanceof Date && sdf!=null) {
381 valueText = sdf.format((Date) valueObj);
382 } else {
383 valueText = valueObj.toString();
384 }
385 if (displayObj instanceof Date && sdf!=null) {
386 displayText = sdf.format((Date) displayObj);
387 } else {
388 displayText = displayObj.toString();
389 }
390 } else {
391 if (obj instanceof Date && sdf!=null) {
392 valueText = sdf.format((Date) obj);
393 } else {
394 valueText = obj.toString();
395 }
396 displayText = valueText;
397 }
398
399 if (bundle != null) {
400 String bundleKey = displayText;
401 if (bundleFormat != null) {
402 bundleKey = MessageFormat.format(bundleFormat,
403 new Object[] { displayText });
404 }
405
406 try {
407 displayText = bundle.getString(bundleKey);
408 } catch (MissingResourceException mre) {
409 // just use the bundle key in this case
410 // displayText = "???" + bundleKey + "???";
411 displayText = bundleKey;
412 }
413 }
414
415 out.print("<option value=\"" + Text.escapeHtml(valueText) + "\"" +
416 (valueText.equals(value) ? " selected" : "") + ">" +
417 Text.escapeHtml(displayText) + "</option>\n");
418 }
419 }
420
421 out.print("</select>\n");
422
423 if (hasError) {
424 out.print("</span>");
425 }
426 } catch (IOException ex) {
427 // ignore these errors, since they can occur when the user hits 'stop' in their browser
428 } catch (Throwable t) {
429 // WAS does not log exceptions that occur within tag libraries; log and rethrow
430 t.printStackTrace();
431 throw (JspException) new JspException("Exception occurred in SelectTag").initCause(t);
432 }
433
434 // selectTag element contents are not evaluated
435 return SKIP_BODY;
436 }
437
438 /** End of custom tag library processing. This method performs no work.
439 *
440 * @return This tag always returns TagSupport.SKIP_BODY .
441 **/
442 public int doEndTag()
443 {
444 // reset attributes
445 id = null;
446 name = null;
447 value = null;
448 dataString = null;
449 data = null;
450 valueColumn = null;
451 displayColumn = null;
452 bundle = null;
453 bundleFormat = null;
454 formatDate = null;
455 clearAttributes();
456
457 return SKIP_BODY;
458 }
459
460 /** Return a localised list for use in a mm:select tag. This function returns a list of
461 * Maps, each element of which is a single option in the select tag. The map contains
462 * two attributes: 'text' and 'value', containing the internationalised and
463 * non-internationalised versions of each option. Note that some select boxes are
464 * not internationalised (e.g. 'selectTop').
465 *
466 * @param bundle The bundle to retrieve localisation information form.
467 * @param prefix A prefix used to return information from the bundle
468 * @param options A comma-separated list of option values
469 *
470 * @return A List as documented above
471 */
472 public static List getSelectOptions(ResourceBundle bundle, String prefix, String options) {
473 List optionsList;
474 try {
475 optionsList = Text.parseCsv(options);
476 } catch (ParseException pe) {
477 throw new IllegalArgumentException("Invalid options list '" + options + "'");
478 }
479 List list = new ArrayList();
480 for (Iterator i = optionsList.iterator(); i.hasNext(); ) {
481 String option = (String) i.next();
482 Map map = new HashMap();
483 map.put("value", option);
484 if (bundle!=null) {
485 map.put("text", bundle.getString(prefix + option)); // localisation
486 } else {
487 map.put("text", option); // no localisation
488 }
489 list.add(map);
490 }
491 return list;
492 }
493
494
495 }