001: /*
002: * Licensed to the Apache Software Foundation (ASF) under one or more
003: * contributor license agreements. See the NOTICE file distributed with
004: * this work for additional information regarding copyright ownership.
005: * The ASF licenses this file to You under the Apache License, Version 2.0
006: * (the "License"); you may not use this file except in compliance with
007: * the License. You may obtain a copy of the License at
008: *
009: * http://www.apache.org/licenses/LICENSE-2.0
010: *
011: * Unless required by applicable law or agreed to in writing, software
012: * distributed under the License is distributed on an "AS IS" BASIS,
013: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014: * See the License for the specific language governing permissions and
015: * limitations under the License.
016: *
017: * $Header:$
018: */
019: package org.apache.beehive.netui.tags.html;
020:
021: import org.apache.beehive.netui.util.internal.InternalStringBuilder;
022:
023: import org.apache.beehive.netui.pageflow.ProcessPopulate;
024: import org.apache.beehive.netui.script.common.DataAccessProviderStack;
025: import org.apache.beehive.netui.tags.naming.FormDataNameInterceptor;
026: import org.apache.beehive.netui.tags.naming.IndexedNameInterceptor;
027: import org.apache.beehive.netui.tags.naming.PrefixNameInterceptor;
028: import org.apache.beehive.netui.tags.rendering.*;
029: import org.apache.beehive.netui.util.Bundle;
030: import org.apache.beehive.netui.util.iterator.IteratorFactory;
031: import org.apache.beehive.netui.util.logging.Logger;
032: import org.apache.beehive.netui.util.tags.GroupOption;
033:
034: import javax.servlet.ServletRequest;
035: import javax.servlet.http.HttpServletRequest;
036: import javax.servlet.jsp.JspException;
037: import java.util.*;
038:
039: /**
040: * Groups a collection of CheckBoxOptions, and handles databinding of their values.
041: *
042: * CheckBoxGroup binds to an Iterator of Strings.
043: *
044: * If CheckBoxGroup uses any Format tags, it must have those tags come before any nested
045: * CheckBoxOption tags.
046: * @jsptagref.tagdescription Renders a collection of checkbox options as <input type="checkbox">
047: * and handles the data binding.
048: *
049: * <p><b>Submitting Data</b></p>
050: * <p>The <netui:checkBoxGroup> submits data in the form of a String[] object.
051: * For example, if the <netui:checkBoxGroup> submits data to a Form Bean field...
052: *
053: * <pre> <netui:checkBoxGroup
054: * dataSource="actionForm.userSelections"
055: * optionsDataSource="${pageFlow.availableSelections}" /></pre>
056: *
057: * ...then the Form Bean field must be a String[] object...
058: *
059: * <pre> public static class SubmitForm extends FormData
060: * {
061: * private String[] userSelections;
062: *
063: * public void setUserSelections(String[] userSelections)
064: * {
065: * this.userSelections = userSelections;
066: * }
067: *
068: * public String[] getUserSelections()
069: * {
070: * return this.userSelections;
071: * }
072: * }</pre>
073: *
074: * <p><b>Dynamically Defined Checkboxes</b></p>
075: * You can dynamically define a set of checkboxes by pointing the <code>optionsDataSource</code> attribute
076: * at a String[] object. When the <netui:checkBoxGroup> is rendered in the browser, a
077: * corresponding set of
078: * checkboxes will be genereated from the String[] object.
079: *
080: * <p>For example, if you define a String[] object and get method in the Controller file...
081: *
082: * <pre> public String[] availableSelections = {"option1", "option2", "option3"};
083: *
084: * public String[] getavailableSelections()
085: * {
086: * return this.availableSelections;
087: * }</pre>
088: *
089: * ...and reference this String[] from the <code>optionsDataSource</code> attribute...
090: *
091: * <pre> <netui:checkBoxGroup
092: * dataSource="actionForm.userSelections"
093: * optionsDataSource="${pageFlow.availableSelections}" /></pre>
094: *
095: * ...then the appropriate checkboxes will be rendered in the browser.
096: *
097: * <pre> <input type="checkbox" name="wlw-checkbox_group_key:{actionForm.userSelections}" value="option1">option1</input>
098: * <input type="checkbox" name="wlw-checkbox_group_key:{actionForm.userSelections}" value="option2">option2</input>
099: * <input type="checkbox" name="wlw-checkbox_group_key:{actionForm.userSelections}" value="option3">option3</input></pre>
100: *
101: * For checkboxes to be rendered, either the <code>optionsDataSource</code> attribute must be provided
102: * (and point to a String[] object) or the <netui:checkBoxGroup> must have children
103: * <netuiCheckBoxOption> tags.
104: *
105: * <p><b>Setting Default Options</b></p>
106: * <p>The <code>defaultValue</code> attribute can be used to determine which checkboxs are checked
107: * when they are first rendered in the browser. The <code>defaultValue</code> attribute
108: * should point to a String, if only one checkbox should appear checked, or to a String[] object,
109: * if multiple checkboxes should appear checked.
110: * @example In this first sample, the <netui:checkBoxGroup>
111: * submits data to the Form Bean field <code>preferredColors</code>.
112: *
113: * <pre> <netui:checkBoxGroup
114: * dataSource="actionForm.preferredColors"
115: * optionsDataSource="${pageFlow.colors}" /></pre>
116: *
117: * The <code>optionsDataSource</code> attribute points to a get method for a String[] on the Controller file:
118: *
119: * <pre> String[] colors = new String[] {"Red", "Blue", "Green", "Yellow", "White", "Black"};
120: *
121: * public String[] getColors()
122: * {
123: * return colors;
124: * }</pre>
125: *
126: * This automatically renders the appropriate set of checkbox options within the <checkBoxGroup>:
127: *
128: * <pre> <input type="checkbox" name="wlw-checkbox_group_key:{actionForm.preferredColors}" value="Red">Red</input>
129: * <input type="checkbox" name="wlw-checkbox_group_key:{actionForm.preferredColors}" value="Blue">Blue</input>
130: * <input type="checkbox" name="wlw-checkbox_group_key:{actionForm.preferredColors}" value="Green">Green</input>
131: * <input type="checkbox" name="wlw-checkbox_group_key:{actionForm.preferredColors}" value="Yellow">Yellow</input>
132: * <input type="checkbox" name="wlw-checkbox_group_key:{actionForm.preferredColors}" value="White">White</input>
133: * <input type="checkbox" name="wlw-checkbox_group_key:{actionForm.preferredColors}" value="Black">Black</input></pre>
134: *
135: * The <code>defaultValue</code> attribute may point to a String or a String[].
136: *
137: * <pre> <netui:checkBoxGroup
138: * dataSource="actionForm.preferredColors"
139: * optionsDataSource="${pageFlow.colors}"
140: * defaultValue="${pageFlow.defaultColor}" /></pre>
141: *
142: * And in the Controller:
143: * <pre> String defaultColor = new String ("Blue");
144: * ...</pre>
145: * or
146: * <pre> String[] defaultColor = new String[] {"Red", "Blue"};
147: * ...</pre>
148: *
149: * In either case, the appropriate
150: * checkbox options will appear checked in the browser.
151: *
152: * <pre> <input type="checkbox" name="wlw-checkbox_group_key:{actionForm.preferredColors}" value="Red" checked="true">Red</input>
153: * <input type="checkbox" name="wlw-checkbox_group_key:{actionForm.preferredColors}" value="Blue" checked="true">Blue</input>
154: * <input type="checkbox" name="wlw-checkbox_group_key:{actionForm.preferredColors}" value="Green">Green</input>
155: * <input type="checkbox" name="wlw-checkbox_group_key:{actionForm.preferredColors}" value="Yellow">Yellow</input>
156: * <input type="checkbox" name="wlw-checkbox_group_key:{actionForm.preferredColors}" value="White">White</input>
157: * <input type="checkbox" name="wlw-checkbox_group_key:{actionForm.preferredColors}" value="Black">Black</input></pre>
158: * @netui:tag name="checkBoxGroup" description="Groups a collection of CheckBoxOptions, and handles databinding of their values."
159: */
160: public class CheckBoxGroup extends HtmlGroupBaseTag {
161: // @todo: seems like we should write out the hidden field even if the option is disabled.
162: private static final Logger logger = Logger
163: .getInstance(CheckBoxGroup.class);
164:
165: /**
166: * This is the name of the prefixHandler for the checkbox grup.
167: */
168: public static final String CHECKBOXGROUP_KEY = "checkbox_group_key";
169:
170: private static final String OLDVALUE_SUFFIX = "OldValue";
171:
172: private InputHiddenTag.State _state = new InputHiddenTag.State();
173: private InputHiddenTag.State _hiddenState = new InputHiddenTag.State();
174:
175: private List _defaultSelections;
176: private boolean _defaultSingleton = false;
177: private boolean _defaultSingleValue = false;
178:
179: private String[] _match; // The actual values we will match against, calculated in doStartTag().
180: private Object _dynamicAttrs; // the Object
181: private InternalStringBuilder _saveBody;
182:
183: private static final List _internalNamingChain;
184: private WriteRenderAppender _writer;
185:
186: static {
187: List l = new ArrayList(3);
188: l.add(new FormDataNameInterceptor());
189: l.add(new IndexedNameInterceptor());
190: l.add(new PrefixNameInterceptor(CHECKBOXGROUP_KEY));
191: _internalNamingChain = Collections.unmodifiableList(l);
192: }
193:
194: static {
195: org.apache.beehive.netui.pageflow.ProcessPopulate
196: .registerPrefixHandler(CHECKBOXGROUP_KEY,
197: new CheckboxGroupPrefixHandler());
198: }
199:
200: /**
201: * The handler for naming and indexing the CheckBoxGroup.
202: */
203: public static class CheckboxGroupPrefixHandler implements
204: org.apache.beehive.netui.pageflow.RequestParameterHandler {
205: /**
206: * Determines the current state of the CheckBoxGroup based on the Request.
207: */
208: public void process(HttpServletRequest request, String key,
209: String expr, ProcessPopulate.ExpressionUpdateNode node) {
210: String[] returnArray = null;
211:
212: if (!key.endsWith(OLDVALUE_SUFFIX)) {
213: //This select has values and should stay that way
214: returnArray = request.getParameterValues(key);
215: } else {
216: //Check the request to see if select also exists
217: String newKey = key.substring(0, key
218: .indexOf(OLDVALUE_SUFFIX));
219: String[] select = request.getParameterValues(newKey);
220: if (select != null) {
221: returnArray = select;
222: } else {
223: returnArray = new String[0]; //null;
224: }
225: }
226:
227: if (node.expression.endsWith(OLDVALUE_SUFFIX)) {
228: node.expression = node.expression.substring(0,
229: node.expression.indexOf(OLDVALUE_SUFFIX));
230: }
231: node.values = returnArray;
232:
233: if (logger.isDebugEnabled()) {
234: logger
235: .debug("\n*********************************************\n"
236: + "process with key \""
237: + key
238: + "\" and expression \""
239: + node.expression
240: + "\""
241: + "and result size: "
242: + (returnArray != null ? ""
243: + returnArray.length : null)
244: + "\n"
245: + "*********************************************\n");
246: }
247: }
248: }
249:
250: public CheckBoxGroup() {
251: super ();
252: }
253:
254: /**
255: * Return the name of the Tag.
256: */
257: public String getTagName() {
258: return "CheckBoxGroup";
259: }
260:
261: /**
262: * Return an <code>ArrayList</code> which represents a chain of <code>INameInterceptor</code>
263: * objects. This method by default returns <code>null</code> and should be overridden
264: * by objects that support naming.
265: * @return an <code>ArrayList</code> that will contain <code>INameInterceptor</code> objects.
266: */
267: protected List getNamingChain() {
268: return _internalNamingChain;
269: }
270:
271: /**
272: * Overrided method to return a list of the possible default values. The method always return either
273: * a <code>List</code> or null.
274: * @return a <code>List</code> that represents the default value.
275: */
276: private Object evaluateDefaultValue() {
277: Object val = _defaultValue;
278:
279: List defaults = null;
280: if (val instanceof String) {
281: if ("checked".equals(val)) {
282: _defaultSingleton = true;
283: _defaultSingleValue = true;
284: return null;
285: } else if ("unchecked".equals(val)) {
286: _defaultSingleton = true;
287: _defaultSingleValue = false;
288: return null;
289: }
290:
291: defaults = new ArrayList();
292: defaults.add(val);
293: return defaults;
294: }
295:
296: Iterator optionsIterator = null;
297: optionsIterator = IteratorFactory.createIterator(val);
298:
299: // log an error, default value is optional so only warn
300: if (optionsIterator == null && _defaultValue != null) {
301: logger.warn(Bundle.getString("Tags_IteratorError",
302: new Object[] { getTagName(), "defaultValue",
303: _defaultValue }));
304: }
305: if (optionsIterator == null)
306: optionsIterator = IteratorFactory.EMPTY_ITERATOR;
307:
308: defaults = new ArrayList();
309: while (optionsIterator.hasNext()) {
310: defaults.add(optionsIterator.next());
311: }
312:
313: return defaults;
314: }
315:
316: /**
317: * Checks whether the given value matches one of the CheckBoxGroup's selected
318: * CheckBoxOptions.
319: * @param value Value to be compared
320: */
321: public boolean isMatched(String value, Boolean defaultValue) {
322: if (value == null)
323: return false;
324:
325: if (_match != null) {
326: for (int i = 0; i < _match.length; i++) {
327: if (value.equals(_match[i]))
328: return true;
329: }
330: } else {
331: // a provided default value will override the group
332: if (defaultValue != null)
333: return defaultValue.booleanValue();
334:
335: // if we have a singleton definition then use that
336: if (_defaultSingleton)
337: return _defaultSingleValue;
338:
339: // check to see if we have a default arraylist with the value in it
340: if (_defaultSelections != null)
341: return _defaultSelections.contains(value);
342: }
343:
344: return false;
345: }
346:
347: /**
348: * Determine the set of matches for the CheckBoxGroup
349: * @throws JspException if a JSP exception has occurred
350: */
351: public int doStartTag() throws JspException {
352: ServletRequest req = pageContext.getRequest();
353: if (_cr == null)
354: _cr = TagRenderingBase.Factory.getConstantRendering(req);
355:
356: // get the evaluated dataSource and default values
357: Object val = evaluateDataSource();
358: _defaultSelections = (List) evaluateDefaultValue();
359: if (hasErrors()) {
360: return SKIP_BODY;
361: }
362:
363: // process the default values and create the matching array...
364: if (val != null) {
365: buildMatch(val);
366: if (hasErrors()) {
367: return SKIP_BODY;
368: }
369: }
370:
371: // if the checkbox group is disabled do not write out the
372: // hidden field.
373: _writer = new WriteRenderAppender(pageContext);
374: if (!_repeater && !_disabled) {
375:
376: //Create hidden field for state tracking
377: _state.clear();
378: String hiddenParamName = null;
379: hiddenParamName = getQualifiedDataSourceName()
380: + OLDVALUE_SUFFIX;
381: _state.name = hiddenParamName;
382: _state.value = "true";
383:
384: TagRenderingBase hiddenTag = TagRenderingBase.Factory
385: .getRendering(TagRenderingBase.INPUT_HIDDEN_TAG,
386: req);
387: hiddenTag.doStartTag(_writer, _state);
388: hiddenTag.doEndTag(_writer);
389: }
390:
391: if (isVertical())
392: _cr.TABLE(_writer);
393:
394: // if this is a repeater then we shouid prime the pump...
395: _dynamicAttrs = evaluateOptionsDataSource();
396: assert (_dynamicAttrs != null);
397: assert (_dynamicAttrs instanceof Map || _dynamicAttrs instanceof Iterator);
398:
399: if (_repeater) {
400: if (_dynamicAttrs instanceof Map) {
401: _dynamicAttrs = ((Map) _dynamicAttrs).entrySet()
402: .iterator();
403: }
404: if (!(_dynamicAttrs instanceof Iterator)) {
405: String s = Bundle
406: .getString("Tags_OptionsDSIteratorError");
407: registerTagError(s, null);
408: return SKIP_BODY;
409: }
410: while (((Iterator) _dynamicAttrs).hasNext()) {
411: _repCurItem = ((Iterator) _dynamicAttrs).next();
412: if (_repCurItem != null)
413: break;
414: }
415: if (isVertical())
416: _cr.TR_TD(_writer);
417:
418: DataAccessProviderStack.addDataAccessProvider(this ,
419: pageContext);
420: }
421: _saveBody = new InternalStringBuilder(128);
422:
423: // Continue processing this page
424: return EVAL_BODY_BUFFERED;
425:
426: }
427:
428: /**
429: * Save any body content of this tag, which will generally be the
430: * option(s) representing the values displayed to the user.
431: * @throws JspException if a JSP exception has occurred
432: */
433: public int doAfterBody() throws JspException {
434: StringBuilderRenderAppender writer = new StringBuilderRenderAppender(
435: _saveBody);
436: if (bodyContent != null) {
437: String value = bodyContent.getString();
438: bodyContent.clearBody();
439: if (value == null)
440: value = "";
441: _saveBody.append(value);
442: }
443:
444: if (_repeater) {
445: ServletRequest req = pageContext.getRequest();
446: if (_cr == null)
447: _cr = TagRenderingBase.Factory
448: .getConstantRendering(req);
449:
450: if (isVertical())
451: _cr.end_TD_TR(writer);
452:
453: while (((Iterator) _dynamicAttrs).hasNext()) {
454: _repCurItem = ((Iterator) _dynamicAttrs).next();
455: if (_repCurItem != null) {
456: _repIdx++;
457: if (isVertical())
458: _cr.TR_TD(writer);
459:
460: return EVAL_BODY_AGAIN;
461: }
462: }
463: }
464:
465: return SKIP_BODY;
466: }
467:
468: /**
469: * Render the set of CheckBoxOptions.
470: * @throws JspException if a JSP exception has occurred
471: */
472: public int doEndTag() throws JspException {
473: if (hasErrors())
474: return reportAndExit(EVAL_PAGE);
475:
476: ServletRequest req = pageContext.getRequest();
477: if (_cr == null)
478: _cr = TagRenderingBase.Factory.getConstantRendering(req);
479:
480: String idScript = null;
481: String altText = null;
482: char accessKey = 0x00;
483:
484: // Render a tag representing the end of our current form
485: if (_saveBody != null)
486: write(_saveBody.toString());
487:
488: // if this is a repeater then we have created the content in the body so we write that
489: if (_repeater) {
490: // Render a tag representing the end of our current form
491: if (isVertical())
492: _cr.end_TABLE(_writer);
493:
494: if (idScript != null)
495: write(idScript);
496:
497: localRelease();
498: return EVAL_PAGE;
499: }
500:
501: // non repeater working against the options data source
502: assert (_dynamicAttrs != null);
503: if (_dynamicAttrs instanceof Map) {
504: Map dynamicCheckboxesMap = (Map) _dynamicAttrs;
505: Iterator keyIterator = dynamicCheckboxesMap.keySet()
506: .iterator();
507: int idx = 0;
508: while (keyIterator.hasNext()) {
509: Object optionValue = keyIterator.next();
510: String optionDisplay = "";
511: if (dynamicCheckboxesMap.get(optionValue) != null)
512: optionDisplay = dynamicCheckboxesMap.get(
513: optionValue).toString();
514: if (optionValue != null) {
515: addOption(_writer, INPUT_CHECKBOX, optionValue
516: .toString(), optionDisplay, idx++, altText,
517: accessKey, _disabled);
518: }
519:
520: if (hasErrors()) {
521: reportErrors();
522: if (isVertical()) {
523: _cr.end_TABLE(_writer);
524: }
525: localRelease();
526: return EVAL_PAGE;
527: }
528: write("\n");
529: }
530: } else {
531: assert (_dynamicAttrs instanceof Iterator);
532: Iterator it = (Iterator) _dynamicAttrs;
533: int idx = 0;
534: while (it.hasNext()) {
535: Object o = it.next();
536: if (o == null)
537: continue;
538:
539: if (o instanceof GroupOption) {
540: GroupOption go = (GroupOption) o;
541: addOption(_writer, INPUT_CHECKBOX, go.getValue(),
542: go.getName(), idx++, go.getAlt(), go
543: .getAccessKey(), _disabled);
544: } else {
545: String checkboxValue = o.toString();
546: addOption(_writer, INPUT_CHECKBOX, checkboxValue,
547: checkboxValue, idx++, altText, accessKey,
548: _disabled);
549: }
550:
551: if (hasErrors()) {
552: reportErrors();
553: if (isVertical()) {
554: _cr.end_TABLE(_writer);
555: }
556: localRelease();
557: return EVAL_PAGE;
558: }
559: write("\n");
560: }
561: }
562:
563: if (isVertical())
564: _cr.end_TABLE(_writer);
565:
566: if (idScript != null)
567: write(idScript);
568:
569: localRelease();
570: return EVAL_PAGE;
571: }
572:
573: public void createHiddenField(AbstractRenderAppender results)
574: throws JspException {
575: if (_repIdx == 0 && !_disabled) {
576:
577: ServletRequest req = pageContext.getRequest();
578:
579: //Create hidden field for state tracking
580: String hiddenParamName = null;
581: hiddenParamName = getQualifiedDataSourceName()
582: + OLDVALUE_SUFFIX;
583: _hiddenState.name = hiddenParamName;
584: _hiddenState.value = "true";
585:
586: TagRenderingBase hiddenTag = TagRenderingBase.Factory
587: .getRendering(TagRenderingBase.INPUT_HIDDEN_TAG,
588: req);
589: hiddenTag.doStartTag(results, _hiddenState);
590: hiddenTag.doEndTag(results);
591: }
592:
593: }
594:
595: /**
596: * Release any acquired resources.
597: */
598: protected void localRelease() {
599: // cleanup the context variables used for binding during repeater
600: if (_repeater)
601: DataAccessProviderStack
602: .removeDataAccessProvider(pageContext);
603:
604: super .localRelease();
605:
606: _defaultSelections = null;
607: _match = null;
608: _dynamicAttrs = null;
609: _saveBody = null;
610: _defaultSingleton = false;
611: _defaultSingleValue = false;
612: _writer = null;
613:
614: _state.clear();
615: _hiddenState.clear();
616: }
617:
618: // This method will build the match list, should this be a hashmap?
619: private void buildMatch(Object val) {
620: if (val instanceof String[]) {
621: _match = (String[]) val;
622: } else {
623: Iterator matchIterator = null;
624: // this should return null, but we should handle it it does
625: matchIterator = IteratorFactory.createIterator(val);
626: if (matchIterator == null)
627: matchIterator = IteratorFactory.EMPTY_ITERATOR;
628:
629: List matchList = new ArrayList();
630: while (matchIterator.hasNext()) {
631: Object o = matchIterator.next();
632: if (o != null)
633: matchList.add(o.toString());
634: }
635: int size = matchList.size();
636:
637: _match = new String[size];
638: for (int i = 0; i < size; i++) {
639: _match[i] = matchList.get(i).toString();
640: }
641: }
642:
643: if (logger.isDebugEnabled()) {
644: logger.debug("****** CheckboxGroup Matches ******");
645: if (_match != null) {
646: for (int i = 0; i < _match.length; i++) {
647: logger.debug(i + ": " + _match[i]);
648: }
649: }
650: }
651: }
652: }
|