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.StringBuilderRenderAppender;
029: import org.apache.beehive.netui.tags.rendering.TagRenderingBase;
030: import org.apache.beehive.netui.tags.rendering.WriteRenderAppender;
031: import org.apache.beehive.netui.util.Bundle;
032: import org.apache.beehive.netui.util.logging.Logger;
033: import org.apache.beehive.netui.util.tags.GroupOption;
034:
035: import javax.servlet.ServletRequest;
036: import javax.servlet.jsp.JspException;
037: import java.util.*;
038:
039: /**
040: * Groups a collection of RadioButtonOptions, and handles databinding of their values.
041: *
042: * If RadioButtonGroup uses any Format tags, it must have those tags come before above any nested
043: * RadioButtonOption tags.
044: * @jsptagref.tagdescription Renders a collection of radiobutton options
045: * as <input type="radio"> and handles the data binding of their values.
046: *
047: * <p>The <netui:radioButtonGroup> tag can generate a set of
048: * radiobutton options in two ways:
049: *
050: * <blockquote>
051: * <ol>
052: * <li>they can be dynamically generated by pointing the
053: * <netui:radioButtonGroup> tag at a {@link java.util.HashMap java.util.HashMap}
054: * or String[] object</li>
055: * <li>they can be statically generated by providing a set of children
056: * {@link RadioButtonOption}
057: * tags</li>
058: * </ol>
059: * </blockquote>
060: *
061: * <p><b>Dynamically Generated Radiobutton Options</b>
062: *
063: * <p>You can dynamically generate a set of radionbutton options by
064: * pointing the <netui:radioButtonGroup> tag at a HashMap
065: * (or any object that implements the {@link java.util.Map java.util.Map} interface).
066: *
067: * <p>For example, if you define a HashMap object and get method in the Controller file...
068: *
069: * <pre> public HashMap hashMap = new HashMap();
070: *
071: * protected HashMap getHashMap()
072: * {
073: * return hashMap;
074: * }
075: *
076: * protected void onCreate()
077: * {
078: * hashMap.put("value1", "Display Text 1");
079: * hashMap.put("value2", "Display Text 2");
080: * hashMap.put("value3", "Display Text 3");
081: * }</pre>
082: *
083: * ...point the <netui:radioButtonGroup>
084: * at the Map object using the <code>optionsDataSource</code> attribute.
085: *
086: * <pre> <netui:radioButtonGroup
087: * dataSource="actionForm.selection"
088: * optionsDataSource="${pageFlow.hashMap}"></pre>
089: *
090: * <p>In the generated radiobutton options, the display text and the
091: * submitted value can be made to differ. The HashMap keys will
092: * form the submitted values, while the HashMap entries will form
093: * the display texts.
094: *
095: * <pre> <input type="radio" name="wlw-radio_button_group_key:{actionForm.selection}" value="value1">Display Text 1</input>
096: * <input type="radio" name="wlw-radio_button_group_key:{actionForm.selection}" value="value2">Display Text 2</input>
097: * <input type="radio" name="wlw-radio_button_group_key:{actionForm.selection}" value="value3">Display Text 3</input></pre>
098: *
099: * <p>Note that you can point the <netui:radioButtonGroup> tag at a
100: * String[] object. A set of radiobutton options will be generated,
101: * but there will be no difference between the
102: * display texts and the submitted values.
103: *
104: * <p><b>Statically Generated Radiobutton Options</b></p>
105: *
106: * <p>To statically generate radiobutton options, place a set of <netui:radioButtonOption> tags inside
107: * the <netui:radioButtonGroup> tag.
108: *
109: * <pre> <netui:radioButtonGroup dataSource="actionForm.selection">
110: * <netui:radioButtonOption value="value1">Display Text 1</netui:radioButtonOption><br>
111: * <netui:radioButtonOption value="value2">Display Text 2</netui:radioButtonOption><br>
112: * <netui:radioButtonOption value="value3">Display Text 3</netui:radioButtonOption><br>
113: * </netui:radioButtonGroup></pre>
114: *
115: * <p><b>Submitting Data</b></p>
116: *
117: * <p>A <netui:radioButtonGroup> is submitted as a String value. Use the <code>dataSource</code> attribute
118: * to submit to a String object.
119: *
120: * <pre> <netui:radioButtonGroup dataSource="actionForm.selection"></pre>
121: *
122: * <p>In this case, the <netui:radioButtonGroup> submits to a String field of a Form Bean.
123: *
124: * <pre> public static class ProcessDataForm extends FormData
125: * {
126: * private String selection;
127: *
128: * public void setSelection(String selection)
129: * {
130: * this.selection = selection;
131: * }
132: *
133: * public String getSelection()
134: * {
135: * return this.selection;
136: * }
137: * }</pre>
138: * @example In this sample, the <netui:radioButtonGroup>
139: * submits data to the Form Bean field <code>preferredColors</code>.
140: *
141: * <pre> <netui:radioButtonGroup
142: * dataSource="actionForm.preferredColors"
143: * optionsDataSource="${pageFlow.colors}" /></pre>
144: *
145: * The <code>optionsDataSource</code> attribute points to a get method for a String[] on the Controller file:
146: *
147: * <pre> String[] colors = new String[] {"Red", "Blue", "Green", "Yellow", "White", "Black"};
148: *
149: * public String[] getColors()
150: * {
151: * return colors;
152: * }</pre>
153: *
154: * This automatically renders the appropriate set of radionbutton options:
155: *
156: * <pre> <input type="radio" name="wlw-radio_button_group_key:{actionForm.preferredColors}" value="Red">Red</input>
157: * <input type="radio" name="wlw-radio_button_group_key:{actionForm.preferredColors}" value="Blue">Blue</input>
158: * <input type="radio" name="wlw-radio_button_group_key:{actionForm.preferredColors}" value="Green">Green</input>
159: * <input type="radio" name="wlw-radio_button_group_key:{actionForm.preferredColors}" value="Yellow">Yellow</input>
160: * <input type="radio" name="wlw-radio_button_group_key:{actionForm.preferredColors}" value="White">White</input>
161: * <input type="radio" name="wlw-radio_button_group_key:{actionForm.preferredColors}" value="Black">Black</input></pre>
162: * @netui:tag name="radioButtonGroup" description="Defines a group of netui:radioButtonOption elements."
163: */
164: public class RadioButtonGroup extends HtmlGroupBaseTag {
165: // @todo: selection may not work with options in repeater.
166: private static final Logger logger = Logger
167: .getInstance(RadioButtonGroup.class);
168:
169: public static final String RADIOBUTTONGROUP_KEY = "radio_button_group_key";
170:
171: private String _match; // The actual values we will match against, calculated in doStartTag().
172: private String _defaultRadio; //
173: private Object _dynamicAttrs; // The optionsDataSource object
174: private InternalStringBuilder _saveBody; // The body text
175: private WriteRenderAppender _writer;
176:
177: private static final List _internalNamingChain;
178:
179: static {
180: List l = new ArrayList(3);
181: l.add(new FormDataNameInterceptor());
182: l.add(new IndexedNameInterceptor());
183: l.add(new PrefixNameInterceptor(RADIOBUTTONGROUP_KEY));
184: _internalNamingChain = Collections.unmodifiableList(l);
185: }
186:
187: static {
188: org.apache.beehive.netui.pageflow.ProcessPopulate
189: .registerPrefixHandler(RADIOBUTTONGROUP_KEY,
190: new RadioButtonGroupPrefixHandler());
191: }
192:
193: /**
194: * The handler for naming and indexing the RadioButtonGroup.
195: */
196: public static class RadioButtonGroupPrefixHandler implements
197: org.apache.beehive.netui.pageflow.RequestParameterHandler {
198: public void process(
199: javax.servlet.http.HttpServletRequest request,
200: String key, String expr,
201: ProcessPopulate.ExpressionUpdateNode node) {
202: if (logger.isDebugEnabled()) {
203: logger
204: .debug("*********************************************\n"
205: + "process with key \""
206: + key
207: + "\" and expression \""
208: + node.expression
209: + "\""
210: + "*********************************************\n");
211: }
212: }
213: }
214:
215: public RadioButtonGroup() {
216: super ();
217: }
218:
219: /**
220: * Return the name of the Tag.
221: */
222: public String getTagName() {
223: return "RadioButtonGroup";
224: }
225:
226: /**
227: * Return an <code>ArrayList</code> which represents a chain of <code>INameInterceptor</code>
228: * objects. This method by default returns <code>null</code> and should be overridden
229: * by objects that support naming.
230: * @return an <code>ArrayList</code> that will contain <code>INameInterceptor</code> objects.
231: */
232: protected List getNamingChain() {
233: return _internalNamingChain;
234: }
235:
236: /**
237: * Override the default value to return a string or the empty string if the default value results in a
238: * <code>null</code> value.
239: * @return the value returned from <code>super.evaluteDefaultValue</code> or the empty string.
240: */
241: private String evaluateDefaultValue() {
242: Object val = _defaultValue;
243:
244: if (val != null)
245: return val.toString();
246: return "";
247: }
248:
249: /**
250: * Does the specified value match one of those we are looking for?
251: * @param value Value to be compared
252: */
253: public boolean isMatched(String value, Boolean defaultValue) {
254: // @todo: there isn't a defaultValue for radio button, what should we do here?
255: if (value == null)
256: return false;
257: if (_match != null)
258: return value.equals(_match);
259: if (_defaultRadio != null)
260: return value.equals(_defaultRadio);
261:
262: return false;
263: }
264:
265: /**
266: * Determine the match for the RadioButtonGroup
267: * @throws JspException if a JSP exception has occurred
268: */
269: public int doStartTag() throws JspException {
270: // evaluate the datasource and disabled state.
271: Object val = evaluateDataSource();
272: if (val != null)
273: _match = val.toString();
274:
275: // Store this tag itself as a page attribute
276: pageContext.setAttribute(RADIOBUTTONGROUP_KEY, this );
277: _defaultRadio = evaluateDefaultValue();
278:
279: // see if there are errors in the evaluation
280: if (hasErrors())
281: return SKIP_BODY;
282:
283: ServletRequest req = pageContext.getRequest();
284: if (_cr == null)
285: _cr = TagRenderingBase.Factory.getConstantRendering(req);
286:
287: _writer = new WriteRenderAppender(pageContext);
288: if (isVertical()) {
289: _cr.TABLE(_writer);
290: }
291:
292: // if this is a repeater then we shouid prime the pump...
293: _dynamicAttrs = evaluateOptionsDataSource();
294: assert (_dynamicAttrs != null);
295: assert (_dynamicAttrs instanceof Map || _dynamicAttrs instanceof Iterator);
296:
297: if (_repeater) {
298: if (_dynamicAttrs instanceof Map) {
299: _dynamicAttrs = ((Map) _dynamicAttrs).entrySet()
300: .iterator();
301:
302: }
303: if (!(_dynamicAttrs instanceof Iterator)) {
304: String s = Bundle
305: .getString("Tags_OptionsDSIteratorError");
306: registerTagError(s, null);
307: return SKIP_BODY;
308: }
309: while (((Iterator) _dynamicAttrs).hasNext()) {
310: _repCurItem = ((Iterator) _dynamicAttrs).next();
311: if (_repCurItem != null)
312: break;
313: }
314: if (isVertical())
315: _cr.TR_TD(_writer);
316:
317: DataAccessProviderStack.addDataAccessProvider(this ,
318: pageContext);
319: }
320: //write(results.toString());
321: // This is basically this is if enough for 5 options
322: _saveBody = new InternalStringBuilder(640);
323: return EVAL_BODY_INCLUDE;
324: }
325:
326: /**
327: * Save any body content of this tag, which will generally be the
328: * option(s) representing the values displayed to the user.
329: * @throws JspException if a JSP exception has occurred
330: */
331: public int doAfterBody() throws JspException {
332: StringBuilderRenderAppender writer = new StringBuilderRenderAppender(
333: _saveBody);
334: if (bodyContent != null) {
335: String value = bodyContent.getString();
336: bodyContent.clearBody();
337: if (value == null)
338: value = "";
339: _saveBody.append(value);
340: }
341:
342: if (_repeater) {
343: ServletRequest req = pageContext.getRequest();
344: if (_cr == null)
345: _cr = TagRenderingBase.Factory
346: .getConstantRendering(req);
347: if (isVertical())
348: _cr.end_TD_TR(writer);
349:
350: while (((Iterator) _dynamicAttrs).hasNext()) {
351: _repCurItem = ((Iterator) _dynamicAttrs).next();
352: if (_repCurItem != null) {
353: _repIdx++;
354: if (isVertical())
355: _cr.TR_TD(writer);
356: return EVAL_BODY_AGAIN;
357: }
358: }
359: }
360:
361: return SKIP_BODY;
362: }
363:
364: /**
365: * Render the set of RadioButtonOptions.
366: * @throws JspException if a JSP exception has occurred
367: */
368: public int doEndTag() throws JspException {
369: if (hasErrors())
370: return reportAndExit(EVAL_PAGE);
371:
372: String idScript = null;
373: String altText = null;
374: char accessKey = 0x00;
375:
376: // Remove the page scope attributes we created
377: pageContext.removeAttribute(RADIOBUTTONGROUP_KEY);
378: ServletRequest req = pageContext.getRequest();
379: if (_cr == null)
380: _cr = TagRenderingBase.Factory.getConstantRendering(req);
381:
382: //InternalStringBuilder results = new InternalStringBuilder(128);
383: if (_saveBody != null)
384: write(_saveBody.toString());
385:
386: // if this is a repeater we output the content during the body processing
387: if (_repeater) {
388: // Render a tag representing the end of our current form
389: if (isVertical())
390: _cr.end_TABLE(_writer);
391:
392: if (idScript != null)
393: write(idScript);
394:
395: //write(results.toString());
396: localRelease();
397: return EVAL_PAGE;
398: }
399:
400: // Render a tag representing the end of our current form
401: if (_dynamicAttrs instanceof Map) {
402: Map dynamicRadiosMap = (Map) _dynamicAttrs;
403: Iterator keyIterator = dynamicRadiosMap.keySet().iterator();
404: int idx = 0;
405: while (keyIterator.hasNext()) {
406: Object optionValue = keyIterator.next();
407: String optionDisplay = null;
408: if (dynamicRadiosMap.get(optionValue) != null) {
409: optionDisplay = dynamicRadiosMap.get(optionValue)
410: .toString();
411: } else {
412: optionDisplay = "";
413: }
414:
415: if (optionValue != null) {
416: addOption(_writer, INPUT_RADIO, optionValue
417: .toString(), optionDisplay, idx++, altText,
418: accessKey, _disabled);
419: }
420:
421: if (hasErrors()) {
422: reportErrors();
423: localRelease();
424: return EVAL_PAGE;
425: }
426: write("\n");
427:
428: }
429: } else {
430: assert (_dynamicAttrs instanceof Iterator);
431:
432: Iterator it = (Iterator) _dynamicAttrs;
433: int idx = 0;
434: while (it.hasNext()) {
435: Object o = it.next();
436: if (o == null)
437: continue;
438:
439: if (o instanceof GroupOption) {
440: GroupOption go = (GroupOption) o;
441: addOption(_writer, INPUT_RADIO, go.getValue(), go
442: .getName(), idx++, go.getAlt(), go
443: .getAccessKey(), _disabled);
444: } else {
445: String radioValue = o.toString();
446: addOption(_writer, INPUT_RADIO, radioValue,
447: radioValue, idx++, altText, accessKey,
448: _disabled);
449: }
450: if (hasErrors()) {
451: reportErrors();
452: localRelease();
453: return EVAL_PAGE;
454: }
455: write("\n");
456: }
457: }
458:
459: if (isVertical()) {
460: _cr.end_TABLE(_writer);
461: }
462:
463: if (idScript != null)
464: write(idScript);
465:
466: //write(results.toString());
467: localRelease();
468: return EVAL_PAGE;
469: }
470:
471: /**
472: * Release any acquired resources.
473: */
474: protected void localRelease() {
475: // remove the context allowing binding to container.item during binding
476: if (_repeater)
477: DataAccessProviderStack
478: .removeDataAccessProvider(pageContext);
479:
480: super.localRelease();
481:
482: _match = null;
483: _defaultRadio = null;
484: }
485: }
|