/* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * * $Header:$ */ package org.apache.beehive.netui.tags.html; import org.apache.beehive.netui.util.internal.InternalStringBuilder; import org.apache.beehive.netui.pageflow.ProcessPopulate; import org.apache.beehive.netui.script.common.DataAccessProviderStack; import org.apache.beehive.netui.tags.naming.FormDataNameInterceptor; import org.apache.beehive.netui.tags.naming.IndexedNameInterceptor; import org.apache.beehive.netui.tags.naming.PrefixNameInterceptor; import org.apache.beehive.netui.tags.rendering.*; import org.apache.beehive.netui.util.Bundle; import org.apache.beehive.netui.util.iterator.IteratorFactory; import org.apache.beehive.netui.util.logging.Logger; import org.apache.beehive.netui.util.tags.GroupOption; import javax.servlet.ServletRequest; import javax.servlet.http.HttpServletRequest; import javax.servlet.jsp.JspException; import java.util.*; /** * Groups a collection of CheckBoxOptions, and handles databinding of their values. * * CheckBoxGroup binds to an Iterator of Strings. * * If CheckBoxGroup uses any Format tags, it must have those tags come before any nested * CheckBoxOption tags. * @jsptagref.tagdescription Renders a collection of checkbox options as <input type="checkbox"> * and handles the data binding. * *
Submitting Data
*The <netui:checkBoxGroup> submits data in the form of a String[] object. * For example, if the <netui:checkBoxGroup> submits data to a Form Bean field... * *
<netui:checkBoxGroup * dataSource="actionForm.userSelections" * optionsDataSource="${pageFlow.availableSelections}" />* * ...then the Form Bean field must be a String[] object... * *
public static class SubmitForm extends FormData * { * private String[] userSelections; * * public void setUserSelections(String[] userSelections) * { * this.userSelections = userSelections; * } * * public String[] getUserSelections() * { * return this.userSelections; * } * }* *
Dynamically Defined Checkboxes
* You can dynamically define a set of checkboxes by pointing theoptionsDataSource
attribute
* at a String[] object. When the <netui:checkBoxGroup> is rendered in the browser, a
* corresponding set of
* checkboxes will be genereated from the String[] object.
*
* For example, if you define a String[] object and get method in the Controller file... * *
public String[] availableSelections = {"option1", "option2", "option3"}; * * public String[] getavailableSelections() * { * return this.availableSelections; * }* * ...and reference this String[] from the
optionsDataSource
attribute...
*
* <netui:checkBoxGroup * dataSource="actionForm.userSelections" * optionsDataSource="${pageFlow.availableSelections}" />* * ...then the appropriate checkboxes will be rendered in the browser. * *
<input type="checkbox" name="wlw-checkbox_group_key:{actionForm.userSelections}" value="option1">option1</input> * <input type="checkbox" name="wlw-checkbox_group_key:{actionForm.userSelections}" value="option2">option2</input> * <input type="checkbox" name="wlw-checkbox_group_key:{actionForm.userSelections}" value="option3">option3</input>* * For checkboxes to be rendered, either the
optionsDataSource
attribute must be provided
* (and point to a String[] object) or the <netui:checkBoxGroup> must have children
* <netuiCheckBoxOption> tags.
*
* Setting Default Options
*The defaultValue
attribute can be used to determine which checkboxs are checked
* when they are first rendered in the browser. The defaultValue
attribute
* should point to a String, if only one checkbox should appear checked, or to a String[] object,
* if multiple checkboxes should appear checked.
* @example In this first sample, the <netui:checkBoxGroup>
* submits data to the Form Bean field preferredColors
.
*
*
<netui:checkBoxGroup * dataSource="actionForm.preferredColors" * optionsDataSource="${pageFlow.colors}" />* * The
optionsDataSource
attribute points to a get method for a String[] on the Controller file:
*
* String[] colors = new String[] {"Red", "Blue", "Green", "Yellow", "White", "Black"}; * * public String[] getColors() * { * return colors; * }* * This automatically renders the appropriate set of checkbox options within the <checkBoxGroup>: * *
<input type="checkbox" name="wlw-checkbox_group_key:{actionForm.preferredColors}" value="Red">Red</input> * <input type="checkbox" name="wlw-checkbox_group_key:{actionForm.preferredColors}" value="Blue">Blue</input> * <input type="checkbox" name="wlw-checkbox_group_key:{actionForm.preferredColors}" value="Green">Green</input> * <input type="checkbox" name="wlw-checkbox_group_key:{actionForm.preferredColors}" value="Yellow">Yellow</input> * <input type="checkbox" name="wlw-checkbox_group_key:{actionForm.preferredColors}" value="White">White</input> * <input type="checkbox" name="wlw-checkbox_group_key:{actionForm.preferredColors}" value="Black">Black</input>* * The
defaultValue
attribute may point to a String or a String[].
*
* <netui:checkBoxGroup * dataSource="actionForm.preferredColors" * optionsDataSource="${pageFlow.colors}" * defaultValue="${pageFlow.defaultColor}" />* * And in the Controller: *
String defaultColor = new String ("Blue"); * ...* or *
String[] defaultColor = new String[] {"Red", "Blue"}; * ...* * In either case, the appropriate * checkbox options will appear checked in the browser. * *
<input type="checkbox" name="wlw-checkbox_group_key:{actionForm.preferredColors}" value="Red" checked="true">Red</input> * <input type="checkbox" name="wlw-checkbox_group_key:{actionForm.preferredColors}" value="Blue" checked="true">Blue</input> * <input type="checkbox" name="wlw-checkbox_group_key:{actionForm.preferredColors}" value="Green">Green</input> * <input type="checkbox" name="wlw-checkbox_group_key:{actionForm.preferredColors}" value="Yellow">Yellow</input> * <input type="checkbox" name="wlw-checkbox_group_key:{actionForm.preferredColors}" value="White">White</input> * <input type="checkbox" name="wlw-checkbox_group_key:{actionForm.preferredColors}" value="Black">Black</input>* @netui:tag name="checkBoxGroup" description="Groups a collection of CheckBoxOptions, and handles databinding of their values." */ public class CheckBoxGroup extends HtmlGroupBaseTag { // @todo: seems like we should write out the hidden field even if the option is disabled. private static final Logger logger = Logger.getInstance(CheckBoxGroup.class); /** * This is the name of the prefixHandler for the checkbox grup. */ public static final String CHECKBOXGROUP_KEY = "checkbox_group_key"; private static final String OLDVALUE_SUFFIX = "OldValue"; private InputHiddenTag.State _state = new InputHiddenTag.State(); private InputHiddenTag.State _hiddenState = new InputHiddenTag.State(); private List _defaultSelections; private boolean _defaultSingleton = false; private boolean _defaultSingleValue = false; private String[] _match; // The actual values we will match against, calculated in doStartTag(). private Object _dynamicAttrs; // the Object private InternalStringBuilder _saveBody; private static final List _internalNamingChain; private WriteRenderAppender _writer; static { List l = new ArrayList(3); l.add(new FormDataNameInterceptor()); l.add(new IndexedNameInterceptor()); l.add(new PrefixNameInterceptor(CHECKBOXGROUP_KEY)); _internalNamingChain = Collections.unmodifiableList(l); } static { org.apache.beehive.netui.pageflow.ProcessPopulate.registerPrefixHandler(CHECKBOXGROUP_KEY, new CheckboxGroupPrefixHandler()); } /** * The handler for naming and indexing the CheckBoxGroup. */ public static class CheckboxGroupPrefixHandler implements org.apache.beehive.netui.pageflow.RequestParameterHandler { /** * Determines the current state of the CheckBoxGroup based on the Request. */ public void process(HttpServletRequest request, String key, String expr, ProcessPopulate.ExpressionUpdateNode node) { String[] returnArray = null; if (!key.endsWith(OLDVALUE_SUFFIX)) { //This select has values and should stay that way returnArray = request.getParameterValues(key); } else { //Check the request to see if select also exists String newKey = key.substring(0, key.indexOf(OLDVALUE_SUFFIX)); String[] select = request.getParameterValues(newKey); if (select != null) { returnArray = select; } else { returnArray = new String[0]; //null; } } if (node.expression.endsWith(OLDVALUE_SUFFIX)) { node.expression = node.expression.substring(0, node.expression.indexOf(OLDVALUE_SUFFIX)); } node.values = returnArray; if (logger.isDebugEnabled()) { logger.debug("\n*********************************************\n" + "process with key \"" + key + "\" and expression \"" + node.expression + "\"" + "and result size: " + (returnArray != null ? "" + returnArray.length : null) + "\n" + "*********************************************\n"); } } } public CheckBoxGroup() { super(); } /** * Return the name of the Tag. */ public String getTagName() { return "CheckBoxGroup"; } /** * Return an
ArrayList
which represents a chain of INameInterceptor
* objects. This method by default returns null
and should be overridden
* by objects that support naming.
* @return an ArrayList
that will contain INameInterceptor
objects.
*/
protected List getNamingChain()
{
return _internalNamingChain;
}
/**
* Overrided method to return a list of the possible default values. The method always return either
* a List
or null.
* @return a List
that represents the default value.
*/
private Object evaluateDefaultValue()
{
Object val = _defaultValue;
List defaults = null;
if (val instanceof String) {
if ("checked".equals(val)) {
_defaultSingleton = true;
_defaultSingleValue = true;
return null;
}
else if ("unchecked".equals(val)) {
_defaultSingleton = true;
_defaultSingleValue = false;
return null;
}
defaults = new ArrayList();
defaults.add(val);
return defaults;
}
Iterator optionsIterator = null;
optionsIterator = IteratorFactory.createIterator(val);
// log an error, default value is optional so only warn
if (optionsIterator == null && _defaultValue != null) {
logger.warn(Bundle.getString("Tags_IteratorError",
new Object[]{getTagName(), "defaultValue", _defaultValue}));
}
if (optionsIterator == null)
optionsIterator = IteratorFactory.EMPTY_ITERATOR;
defaults = new ArrayList();
while (optionsIterator.hasNext()) {
defaults.add(optionsIterator.next());
}
return defaults;
}
/**
* Checks whether the given value matches one of the CheckBoxGroup's selected
* CheckBoxOptions.
* @param value Value to be compared
*/
public boolean isMatched(String value, Boolean defaultValue)
{
if (value == null)
return false;
if (_match != null) {
for (int i = 0; i < _match.length; i++) {
if (value.equals(_match[i]))
return true;
}
}
else {
// a provided default value will override the group
if (defaultValue != null)
return defaultValue.booleanValue();
// if we have a singleton definition then use that
if (_defaultSingleton)
return _defaultSingleValue;
// check to see if we have a default arraylist with the value in it
if (_defaultSelections != null)
return _defaultSelections.contains(value);
}
return false;
}
/**
* Determine the set of matches for the CheckBoxGroup
* @throws JspException if a JSP exception has occurred
*/
public int doStartTag() throws JspException
{
ServletRequest req = pageContext.getRequest();
if (_cr == null)
_cr = TagRenderingBase.Factory.getConstantRendering(req);
// get the evaluated dataSource and default values
Object val = evaluateDataSource();
_defaultSelections = (List) evaluateDefaultValue();
if (hasErrors()) {
return SKIP_BODY;
}
// process the default values and create the matching array...
if (val != null) {
buildMatch(val);
if (hasErrors()) {
return SKIP_BODY;
}
}
// if the checkbox group is disabled do not write out the
// hidden field.
_writer = new WriteRenderAppender(pageContext);
if (!_repeater && !_disabled) {
//Create hidden field for state tracking
_state.clear();
String hiddenParamName = null;
hiddenParamName = getQualifiedDataSourceName() + OLDVALUE_SUFFIX;
_state.name = hiddenParamName;
_state.value = "true";
TagRenderingBase hiddenTag = TagRenderingBase.Factory.getRendering(TagRenderingBase.INPUT_HIDDEN_TAG, req);
hiddenTag.doStartTag(_writer, _state);
hiddenTag.doEndTag(_writer);
}
if (isVertical())
_cr.TABLE(_writer);
// if this is a repeater then we shouid prime the pump...
_dynamicAttrs = evaluateOptionsDataSource();
assert (_dynamicAttrs != null);
assert (_dynamicAttrs instanceof Map ||
_dynamicAttrs instanceof Iterator);
if (_repeater) {
if (_dynamicAttrs instanceof Map) {
_dynamicAttrs = ((Map) _dynamicAttrs).entrySet().iterator();
}
if (!(_dynamicAttrs instanceof Iterator)) {
String s = Bundle.getString("Tags_OptionsDSIteratorError");
registerTagError(s, null);
return SKIP_BODY;
}
while (((Iterator) _dynamicAttrs).hasNext()) {
_repCurItem = ((Iterator) _dynamicAttrs).next();
if (_repCurItem != null)
break;
}
if (isVertical())
_cr.TR_TD(_writer);
DataAccessProviderStack.addDataAccessProvider(this, pageContext);
}
_saveBody = new InternalStringBuilder(128);
// Continue processing this page
return EVAL_BODY_BUFFERED;
}
/**
* Save any body content of this tag, which will generally be the
* option(s) representing the values displayed to the user.
* @throws JspException if a JSP exception has occurred
*/
public int doAfterBody() throws JspException
{
StringBuilderRenderAppender writer = new StringBuilderRenderAppender(_saveBody);
if (bodyContent != null) {
String value = bodyContent.getString();
bodyContent.clearBody();
if (value == null)
value = "";
_saveBody.append(value);
}
if (_repeater) {
ServletRequest req = pageContext.getRequest();
if (_cr == null)
_cr = TagRenderingBase.Factory.getConstantRendering(req);
if (isVertical())
_cr.end_TD_TR(writer);
while (((Iterator) _dynamicAttrs).hasNext()) {
_repCurItem = ((Iterator) _dynamicAttrs).next();
if (_repCurItem != null) {
_repIdx++;
if (isVertical())
_cr.TR_TD(writer);
return EVAL_BODY_AGAIN;
}
}
}
return SKIP_BODY;
}
/**
* Render the set of CheckBoxOptions.
* @throws JspException if a JSP exception has occurred
*/
public int doEndTag()
throws JspException
{
if (hasErrors())
return reportAndExit(EVAL_PAGE);
ServletRequest req = pageContext.getRequest();
if (_cr == null)
_cr = TagRenderingBase.Factory.getConstantRendering(req);
String idScript = null;
String altText = null;
char accessKey = 0x00;
// Render a tag representing the end of our current form
if (_saveBody != null)
write(_saveBody.toString());
// if this is a repeater then we have created the content in the body so we write that
if (_repeater) {
// Render a tag representing the end of our current form
if (isVertical())
_cr.end_TABLE(_writer);
if (idScript != null)
write(idScript);
localRelease();
return EVAL_PAGE;
}
// non repeater working against the options data source
assert(_dynamicAttrs != null);
if (_dynamicAttrs instanceof Map) {
Map dynamicCheckboxesMap = (Map) _dynamicAttrs;
Iterator keyIterator = dynamicCheckboxesMap.keySet().iterator();
int idx = 0;
while (keyIterator.hasNext()) {
Object optionValue = keyIterator.next();
String optionDisplay = "";
if (dynamicCheckboxesMap.get(optionValue) != null)
optionDisplay = dynamicCheckboxesMap.get(optionValue).toString();
if (optionValue != null) {
addOption(_writer, INPUT_CHECKBOX, optionValue.toString(), optionDisplay, idx++, altText, accessKey, _disabled);
}
if (hasErrors()) {
reportErrors();
if (isVertical()) {
_cr.end_TABLE(_writer);
}
localRelease();
return EVAL_PAGE;
}
write("\n");
}
}
else {
assert(_dynamicAttrs instanceof Iterator);
Iterator it = (Iterator) _dynamicAttrs;
int idx = 0;
while (it.hasNext()) {
Object o = it.next();
if (o == null)
continue;
if (o instanceof GroupOption) {
GroupOption go = (GroupOption) o;
addOption(_writer, INPUT_CHECKBOX, go.getValue(), go.getName(), idx++, go.getAlt(), go.getAccessKey(), _disabled);
}
else {
String checkboxValue = o.toString();
addOption(_writer, INPUT_CHECKBOX, checkboxValue, checkboxValue, idx++, altText, accessKey, _disabled);
}
if (hasErrors()) {
reportErrors();
if (isVertical()) {
_cr.end_TABLE(_writer);
}
localRelease();
return EVAL_PAGE;
}
write("\n");
}
}
if (isVertical())
_cr.end_TABLE(_writer);
if (idScript != null)
write(idScript);
localRelease();
return EVAL_PAGE;
}
public void createHiddenField(AbstractRenderAppender results)
throws JspException
{
if (_repIdx == 0 && !_disabled) {
ServletRequest req = pageContext.getRequest();
//Create hidden field for state tracking
String hiddenParamName = null;
hiddenParamName = getQualifiedDataSourceName() + OLDVALUE_SUFFIX;
_hiddenState.name = hiddenParamName;
_hiddenState.value = "true";
TagRenderingBase hiddenTag = TagRenderingBase.Factory.getRendering(TagRenderingBase.INPUT_HIDDEN_TAG, req);
hiddenTag.doStartTag(results, _hiddenState);
hiddenTag.doEndTag(results);
}
}
/**
* Release any acquired resources.
*/
protected void localRelease()
{
// cleanup the context variables used for binding during repeater
if (_repeater)
DataAccessProviderStack.removeDataAccessProvider(pageContext);
super.localRelease();
_defaultSelections = null;
_match = null;
_dynamicAttrs = null;
_saveBody = null;
_defaultSingleton = false;
_defaultSingleValue = false;
_writer = null;
_state.clear();
_hiddenState.clear();
}
// This method will build the match list, should this be a hashmap?
private void buildMatch(Object val)
{
if (val instanceof String[]) {
_match = (String[]) val;
}
else {
Iterator matchIterator = null;
// this should return null, but we should handle it it does
matchIterator = IteratorFactory.createIterator(val);
if (matchIterator == null)
matchIterator = IteratorFactory.EMPTY_ITERATOR;
List matchList = new ArrayList();
while (matchIterator.hasNext()) {
Object o = matchIterator.next();
if (o != null)
matchList.add(o.toString());
}
int size = matchList.size();
_match = new String[size];
for (int i = 0; i < size; i++) {
_match[i] = matchList.get(i).toString();
}
}
if (logger.isDebugEnabled()) {
logger.debug("****** CheckboxGroup Matches ******");
if (_match != null) {
for (int i = 0; i < _match.length; i++) {
logger.debug(i + ": " + _match[i]);
}
}
}
}
}