759 lines
30 KiB
Java
759 lines
30 KiB
Java
/*
|
|
* 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.databinding.repeater;
|
|
|
|
import javax.servlet.jsp.JspException;
|
|
import javax.servlet.jsp.tagext.*;
|
|
import java.util.Iterator;
|
|
import java.util.List;
|
|
import java.util.Collections;
|
|
|
|
import org.apache.beehive.netui.script.common.DataAccessProviderStack;
|
|
import org.apache.beehive.netui.script.common.IDataAccessProvider;
|
|
import org.apache.beehive.netui.tags.AbstractClassicTag;
|
|
import org.apache.beehive.netui.tags.ExpressionHandling;
|
|
import org.apache.beehive.netui.tags.databinding.repeater.pad.PadContext;
|
|
import org.apache.beehive.netui.util.Bundle;
|
|
import org.apache.beehive.netui.util.exception.LocalizedUnsupportedOperationException;
|
|
import org.apache.beehive.netui.util.internal.InternalStringBuilder;
|
|
import org.apache.beehive.netui.util.iterator.IteratorFactory;
|
|
import org.apache.beehive.netui.util.logging.Logger;
|
|
|
|
/**
|
|
* The <netui-data:repeater> tag is a markup-generic tag that repeats over a data set.
|
|
* The repeater tag set is used to render data from a data set into a page. The repeater
|
|
* itself does not render any markup. Instead, the markup from its contained tags is
|
|
* rendered to create the content generated by this tag. The tags in the repeater tag
|
|
* set are as follows:
|
|
* <table border="1" cellspacing="0" cellpadding="5" width="75%">
|
|
* <tr><td><b>Tag</b></td><td><b>Description</b></td></tr>
|
|
* <tr><td>{@link RepeaterHeader}</td><td>Renders once in the {@link #HEADER} state.</td></tr>
|
|
* <tr><td>{@link RepeaterItem}</td><td>Renders once in the {@link #ITEM} state.</td></tr>
|
|
* <tr><td>{@link RepeaterFooter}</td><td>Renders once in the {@link #FOOTER} state.</td></tr>
|
|
* <tr><td>{@link org.apache.beehive.netui.tags.databinding.repeater.pad.Pad}</td><td>Used to convert irregular data sets into regular data sets through padding or truncating the output.</td></tr>
|
|
* </table>
|
|
* <p>The repeater can render in two modes; the first mode is a simple mode where the body of
|
|
* the repeater is rendered once for each item in the data set. In this case, none of the
|
|
* other tags above are present in the repeater body. For example, the following will
|
|
* render an unordered HTML list of items that are list items which contain the lastName, firstName
|
|
* of the current "customer" in the data set.</p>
|
|
* <pre>
|
|
* <ul>
|
|
* <netui-data:repeater dataSource="pageInput.customers">
|
|
* <li><netui:span value="${container.item.lastName}, ${container.item.firstName}"/></li>
|
|
* </netui-data:repeater>
|
|
* </ul>
|
|
* </pre>
|
|
* <p>The second mode is a more structured mode
|
|
* of rendering where the tags above are used to delineate iteration boundaries on the body of
|
|
* a <netui-data:repeater> tag. In this case, if one of the above tags is present,
|
|
* any content directly in the body of the repeater is not rendered; rather, the content
|
|
* inside the structured tags of the repeater is rendered.</p>
|
|
* <p>For example, the following will render the same output as the example
|
|
* shown above, but it uses the structured tags for rendering the
|
|
* <code>pageFlow.customers</code> expression:</p>
|
|
* <pre>
|
|
* <netui-data:repeater dataSource="pageInput.customers">
|
|
* <netui-data:repeaterHeader>
|
|
* <ul>
|
|
* </netui-data:repeaterHeader>
|
|
* <netui-data:repeaterItem>
|
|
* <li><netui:span value="${container.item.lastName}, ${container.item.firstName}"/></li>
|
|
* </netui-data:repeaterItem>
|
|
* <netui-data:repeaterFooter>
|
|
* </ul>
|
|
* </netui-data:repeaterFooter>
|
|
* </netui-data:repeater>
|
|
* </pre>
|
|
*
|
|
* @jsptagref.tagdescription <p>Iterates over a data set to render it as HTML.
|
|
* The HTML is specified either directly within the the body of
|
|
* <netui-data:repeater> tag or within an associated set of "helper" tags.
|
|
* The "helper" tags are listed below.
|
|
* <p/>
|
|
* <blockquote>
|
|
* <table border="1" cellspacing="0" cellpadding="5" width="90%">
|
|
* <tr><td><b>Tag</b></td><td><b>Description</b></td></tr>
|
|
* <tr><td>{@link RepeaterHeader}</td><td>Renders once at the start of the iteration.</td></tr>
|
|
* <tr><td>{@link RepeaterItem}</td><td>Renders once for each iteration.</td></tr>
|
|
* <tr><td>{@link RepeaterFooter}</td><td>Renders once at the end of the iteration.</td></tr>
|
|
* <tr><td>{@link org.apache.beehive.netui.tags.databinding.repeater.pad.Pad}</td><td>Used to convert
|
|
* irregular data sets into regular data sets through padding
|
|
* or truncating the output.</td></tr>
|
|
* </table>
|
|
* </blockquote>
|
|
* <p/>
|
|
* <p>The <netui-data:repeater> tag can render in two modes; the first mode is a simple mode where the body of
|
|
* the <netui-data:repeater> tag is rendered once for each item in the data set. In this case, none of the
|
|
* other tags above are present in the repeater body. For example, the following will
|
|
* render the items in the "customers" data set as an unordered HTML list.</p>
|
|
* <p/>
|
|
* <pre> <ul>
|
|
* <netui-data:repeater dataSource="pageInput.customers">
|
|
* <li><netui:span value="${container.item.lastName}, ${container.item.firstName}"/></li>
|
|
* </netui-data:repeater>
|
|
* </ul></pre>
|
|
* <p/>
|
|
* <p>The second mode is a more structured mode
|
|
* of rendering where the "helper" tags
|
|
* are used to define the rendering of the data set. In this case, if one of the helper tags
|
|
* is present,
|
|
* any HTML markup directly in the body of the <netui-data:repeater> tag is not rendered; rather, the HTML markup
|
|
* inside the helper tags is rendered.</p>
|
|
* <p>For example, the following will render the same output as the example
|
|
* shown above, but it uses the "helper" tags for rendering the
|
|
* HTML markup:</p>
|
|
* <p/>
|
|
* <pre>
|
|
* <netui-data:repeater dataSource="pageInput.customers">
|
|
* <netui-data:repeaterHeader>
|
|
* <ul>
|
|
* </netui-data:repeaterHeader>
|
|
* <netui-data:repeaterItem>
|
|
* <li><netui:span value="${container.item.lastName}, ${container.item.firstName}"/></li>
|
|
* </netui-data:repeaterItem>
|
|
* <netui-data:repeaterFooter>
|
|
* </ul>
|
|
* </netui-data:repeaterFooter>
|
|
* </netui-data:repeater></pre>
|
|
* @example The following sample renders the data set as an HTML table. The table has two columns, "index" and "name",
|
|
* and each iteration over the data set is rendered as a row of the table.
|
|
* <p/>
|
|
* <pre> <netui-data:repeater dataSource="pageInput.myDataSet">
|
|
* <netui-data:repeaterHeader>
|
|
* <table border="1">
|
|
* <tr>
|
|
* <td><b>index</b></td>
|
|
* <td><b>name</b></td>
|
|
* </tr>
|
|
* </netui-data:repeaterHeader>
|
|
* <netui-data:repeaterItem>
|
|
* <tr>
|
|
* <td>
|
|
* <netui:span value="${container.index}" />
|
|
* </td>
|
|
* <td>
|
|
* <netui: value="${container.item}" />
|
|
* </td>
|
|
* </tr>
|
|
* </netui-data:repeaterItem>
|
|
* <netui-data:repeaterFooter>
|
|
* </table>
|
|
* </netui-data:repeaterFooter>
|
|
* </netui-data:repeater></pre>
|
|
* @netui:tag name="repeater" description="A markup-generic tag that repeats over a data set, and renders the data onto the page."
|
|
*/
|
|
public class Repeater
|
|
extends AbstractClassicTag
|
|
implements IDataAccessProvider, TryCatchFinally {
|
|
|
|
private static final Logger LOGGER = Logger.getInstance(Repeater.class);
|
|
|
|
/**
|
|
* A Repeater rendering state that signals the beginning of repeater rendering.
|
|
*/
|
|
public static final int INIT = 0;
|
|
|
|
/**
|
|
* A Repeater rendering state that signals the rendering of the HEADER.
|
|
* The body renders in the HEADER state once.
|
|
*/
|
|
public static final int HEADER = 1;
|
|
|
|
/**
|
|
* A Repeater rendering state that signals the rendering of the ITEM.
|
|
* The body renders in the ITEM state once for each item in the
|
|
* data set.
|
|
*/
|
|
public static final int ITEM = 2;
|
|
|
|
/**
|
|
* A Repeater rendering state that signals the rendering of the FOOTER.
|
|
* The body renders in the FOOTER state once.
|
|
*/
|
|
public static final int FOOTER = 3;
|
|
|
|
/**
|
|
* A Repeater rendering state that signals the end of repeater rendering.
|
|
*/
|
|
public static final int END = 4;
|
|
|
|
private boolean _ignoreNulls = false;
|
|
private boolean _haveKids = false;
|
|
private boolean _containerInPageContext = false;
|
|
private int _currentIndex = -1;
|
|
private int _renderedItems = 0;
|
|
private int _renderState = INIT;
|
|
private Object _defaultText = null;
|
|
private Object _currentItem = null;
|
|
private String _dataSource = null;
|
|
private Iterator _iterator = null;
|
|
private PadContext _padContext = null;
|
|
private InternalStringBuilder _contentBuffer = null;
|
|
|
|
/**
|
|
* Get the name of this tag. This is used to identify the type of this tag
|
|
* for reporting tag errors.
|
|
*
|
|
* @return a constant String representing the name of this tag.
|
|
*/
|
|
public String getTagName() {
|
|
return "Repeater";
|
|
}
|
|
|
|
/**
|
|
* Set a boolean that describes whether the repeater should ignore null
|
|
* items encountered while iterating over a data set.
|
|
*
|
|
* @param ignoreNulls whether or not to ignore nulls
|
|
* @jsptagref.attributedescription
|
|
* Boolean. If set to true, any null iteration items in the data set will be ignored.
|
|
* @jsptagref.attributesyntaxvalue <i>boolean_ignoreNulls</i>
|
|
* @netui:attribute required="false"
|
|
*/
|
|
public void setIgnoreNulls(boolean ignoreNulls) {
|
|
_ignoreNulls = ignoreNulls;
|
|
}
|
|
|
|
/**
|
|
* @param padContext
|
|
*/
|
|
public void setPadContext(PadContext padContext) {
|
|
if(_padContext == null)
|
|
_padContext = padContext;
|
|
|
|
LOGGER.debug("Repeater has a padContext with text: " + _padContext);
|
|
|
|
return;
|
|
}
|
|
|
|
/**
|
|
* Set the text that will be rendered if the dataSource expression
|
|
* references a null object and the defaultText attribute is non-null.
|
|
*
|
|
* @param defaultText the default text
|
|
* @jsptagref.attributedescription
|
|
* The text to render if the <code>dataSource</code> attribute references a null data set.
|
|
* @jsptagref.attributesyntaxvalue <i>string_defaultText</i>
|
|
* @netui:attribute required="false" rtexprvalue="true"
|
|
*/
|
|
public void setDefaultText(Object defaultText) {
|
|
_defaultText = defaultText;
|
|
}
|
|
|
|
/**
|
|
* Get the index of the current iteration through the body of this tag. This
|
|
* data can be accessed using the expression <code>container.index</code>
|
|
* on an attribute of a databindable NetUI tag that is contained within the
|
|
* repeating body of this tag. This expression is only valid when the dataset
|
|
* is being rendered.
|
|
*
|
|
* @return the integer index of the current data item in the data set
|
|
* @see org.apache.beehive.netui.script.common.IDataAccessProvider
|
|
*/
|
|
public int getCurrentIndex() {
|
|
return _currentIndex;
|
|
}
|
|
|
|
/**
|
|
* Get the item that is currently being rendered by this repeating tag.
|
|
* This can be accessed using the expression <code>expression.item</code>
|
|
* on an attribute of a databindable NetUI tag that is contained within
|
|
* the repeating body of this tag. The expression is only valid when the dataset
|
|
* is being rendered.
|
|
*
|
|
* @return the current item in the data set
|
|
* @see org.apache.beehive.netui.script.common.IDataAccessProvider
|
|
*/
|
|
public Object getCurrentItem() {
|
|
return _currentItem;
|
|
}
|
|
|
|
/**
|
|
* Get the metadata for the current item. This method is not supported by
|
|
* this tag.
|
|
*
|
|
* @throws UnsupportedOperationException this tag does not support this method from the IDataAccessProvider interface
|
|
* @see org.apache.beehive.netui.script.common.IDataAccessProvider
|
|
*/
|
|
public Object getCurrentMetadata() {
|
|
LocalizedUnsupportedOperationException uoe =
|
|
new LocalizedUnsupportedOperationException("The " + getTagName() + "does not export metadata for its iterated items.");
|
|
uoe.setLocalizedMessage(Bundle.getErrorString("Tags_DataAccessProvider_metadataUnsupported", new Object[]{getTagName()}));
|
|
throw uoe;
|
|
}
|
|
|
|
/**
|
|
* Get the parent IDataAccessProvider for this tag. If this tag is contained within
|
|
* a IDataAccessProvider, the containing IDataAccessProvider is available through the
|
|
* expression <code>container.container</code>. Any valid properties of the
|
|
* parent IDataAccessProvider can be accessed through this expression. This method
|
|
* will return null if there is no parent IDataAccessProvider
|
|
*
|
|
* @return a containing IDataAccessProvider if one exists, null otherwise.
|
|
* @see org.apache.beehive.netui.script.common.IDataAccessProvider
|
|
*/
|
|
public IDataAccessProvider getProviderParent() {
|
|
IDataAccessProvider dap = (IDataAccessProvider)SimpleTagSupport.findAncestorWithClass(this, IDataAccessProvider.class);
|
|
return dap;
|
|
}
|
|
|
|
/**
|
|
* Get the current render state for the repeater. This tag is used by child tags
|
|
* to access the current location in the repeater's rendering lifecycle.
|
|
*
|
|
* @return an integer that represents the current state of the grid; this is one
|
|
* of {@link #INIT}, {@link #HEADER}, {@link #ITEM},{@link #FOOTER}, or {@link #END}.
|
|
*/
|
|
public int getRenderState() {
|
|
return _renderState;
|
|
}
|
|
|
|
/**
|
|
* Add content to the content that is being buffered by this tag. All content
|
|
* written by the body of this tag is added to this buffer. The buffer is rendered
|
|
* at the end of the tag's lifecycle if no fatal errors have occurred during this
|
|
* tag's lifecycle.
|
|
*
|
|
* @param content content that this tag should render.
|
|
*/
|
|
public void addContent(String content) {
|
|
if(_contentBuffer == null) {
|
|
int size = (content != null ? (5 * content.length()) : 1024);
|
|
_contentBuffer = new InternalStringBuilder(size);
|
|
}
|
|
|
|
_contentBuffer.append(content);
|
|
}
|
|
|
|
/**
|
|
* Method used by tags in the repeater tag set to register the presence of a contained
|
|
* tag. When registered, the repeater will change the way in which it renders to
|
|
* either use structured or non-structured rendering.
|
|
* @param repeaterComponent {@link RepeaterComponent} to register with the Repeater parent
|
|
*/
|
|
public void registerChildTag(RepeaterComponent repeaterComponent) {
|
|
_haveKids = true;
|
|
}
|
|
|
|
/**
|
|
* Start rendering the repeater.
|
|
* @return {@link #SKIP_BODY} if an error occurs; {@link #EVAL_BODY_BUFFERED} otherwise
|
|
* @throws JspException if an error occurs that can not be reported in the page
|
|
*/
|
|
public int doStartTag()
|
|
throws JspException {
|
|
Object source = evaluateDataSource();
|
|
|
|
// report any errors that may have occured
|
|
if(hasErrors())
|
|
return SKIP_BODY;
|
|
|
|
_renderState = INIT;
|
|
|
|
boolean empty = false;
|
|
if(source != null) {
|
|
_iterator = IteratorFactory.createIterator(source);
|
|
if(_iterator == null) {
|
|
LOGGER.warn(Bundle.getString("Tags_Repeater_nullIterator"));
|
|
_iterator = Collections.EMPTY_LIST.iterator();
|
|
}
|
|
|
|
if(_iterator.hasNext()) {
|
|
_currentIndex = 0;
|
|
_currentItem = _iterator.next();
|
|
|
|
if(_ignoreNulls && _currentItem == null) {
|
|
/*
|
|
doStartTag doesn't know if the repeater is structured or unstructured
|
|
thus, if ignoreNulls is true, it's going to make an attempt to go
|
|
through the body with a non-null item. if there are no non-null
|
|
items in the data structure, the doAfterBody method will handle
|
|
this correctly, but a data structure with a null first item
|
|
will render the same as a data structure with a null second item
|
|
*/
|
|
advanceToNonNullItem();
|
|
|
|
/*
|
|
this null check needs to re-run here because the advanceToNonNullItem method
|
|
side-effects the _currentItem.
|
|
*/
|
|
if(_currentItem == null)
|
|
empty = true;
|
|
}
|
|
}
|
|
/* there is no data set of there are zero items in the iterator */
|
|
else empty = true;
|
|
}
|
|
/* the dataSource evaluated to null */
|
|
else {
|
|
_iterator = Collections.EMPTY_LIST.iterator();
|
|
empty = true;
|
|
}
|
|
|
|
if(empty) {
|
|
/* if the defaultText attribute is non-null, it will be evaluated as an expression and rendered to the page */
|
|
if(_defaultText != null)
|
|
addContent(_defaultText.toString());
|
|
return SKIP_BODY;
|
|
}
|
|
else {
|
|
DataAccessProviderStack.addDataAccessProvider(this, pageContext);
|
|
_containerInPageContext = true;
|
|
return EVAL_BODY_BUFFERED;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* <p>
|
|
* Continue rendering the repeater changing the render state or advancing to a new data item
|
|
* as needed.
|
|
* </p>
|
|
* @return {@link #SKIP_BODY} if an error occurs or the data set has been rendered; {@link #EVAL_BODY_AGAIN} otherwise
|
|
*/
|
|
public int doAfterBody() {
|
|
|
|
if(hasErrors())
|
|
return SKIP_BODY;
|
|
|
|
LOGGER.debug("structured repeater: " + _haveKids + " render state: " + renderStateToString(_renderState));
|
|
|
|
/*
|
|
structured rendering of the repeater; body content is ignored and real
|
|
content is rendered through cooperating nested tags
|
|
*/
|
|
if(_haveKids)
|
|
return renderStructured();
|
|
/*
|
|
unstructured rendering of the repeater; this means that there isn't
|
|
a repeater(Header|Item|Footer) inside the body of the repeater.
|
|
*/
|
|
else {
|
|
if(bodyContent != null) {
|
|
addContent(bodyContent.getString());
|
|
bodyContent.clearBody();
|
|
}
|
|
|
|
if(_iterator.hasNext()) {
|
|
_currentIndex++;
|
|
_currentItem = _iterator.next();
|
|
|
|
if(_ignoreNulls && _currentItem == null) {
|
|
advanceToNonNullItem();
|
|
|
|
/* ignoring null items and no more non-null items, so skip to doEndTag() */
|
|
if(_currentItem == null)
|
|
return SKIP_BODY;
|
|
}
|
|
|
|
/* found another item; re-render the repeater's body */
|
|
return EVAL_BODY_AGAIN;
|
|
}
|
|
/* no more items; skip to doEndTag() */
|
|
else return SKIP_BODY;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Complete rendering the repeater.
|
|
* @return {@link #EVAL_PAGE}
|
|
* @throws JspException if an error occurs that can not be reported in the page
|
|
*/
|
|
public int doEndTag()
|
|
throws JspException {
|
|
|
|
if(hasErrors())
|
|
reportErrors();
|
|
else if(_contentBuffer != null)
|
|
write(_contentBuffer.toString());
|
|
|
|
return EVAL_PAGE;
|
|
}
|
|
|
|
public void doFinally() {
|
|
localRelease();
|
|
}
|
|
|
|
public void doCatch(Throwable t)
|
|
throws Throwable {
|
|
throw t;
|
|
}
|
|
|
|
/**
|
|
* Reset all of the fields of this tag.
|
|
*/
|
|
protected void localRelease() {
|
|
super.localRelease();
|
|
_currentItem = null;
|
|
_currentIndex = -1;
|
|
_iterator = null;
|
|
_defaultText = null;
|
|
_renderState = INIT;
|
|
_haveKids = false;
|
|
_contentBuffer = null;
|
|
_padContext = null;
|
|
_ignoreNulls = false;
|
|
_renderedItems = 0;
|
|
if(_containerInPageContext) {
|
|
DataAccessProviderStack.removeDataAccessProvider(pageContext);
|
|
_containerInPageContext = false;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Render a the repeater using the full repeater lifecycle. This
|
|
* method is executed after each pass through the body if there
|
|
* are tags from the repeater tag set in the body of this repeater.
|
|
* This method ensures that the repeater tag runs as a full state
|
|
* machine for these tags.
|
|
*
|
|
* @return EVAL_BODY_AGAIN unless the lifecycle has completed; then return SKIP_BODY
|
|
*/
|
|
/* todo: perf -- optimize the number of trips through the body by ignoring the header / footer when necessary */
|
|
private int renderStructured() {
|
|
if(LOGGER.isDebugEnabled() && _padContext != null)
|
|
LOGGER.debug("\ncurrentIndex: " + _currentIndex + "\n" +
|
|
"checkMaxRepeat: " + _padContext.checkMaxRepeat(_currentIndex) + "\n" +
|
|
"checkMinRepeat: " + _padContext.checkMinRepeat(_currentIndex) + "\n");
|
|
|
|
if(_renderState == INIT) {
|
|
_renderState = HEADER;
|
|
return EVAL_BODY_AGAIN;
|
|
}
|
|
|
|
if(_renderState == HEADER) {
|
|
assert _renderedItems == 0;
|
|
|
|
/* this would only happen if Pad.maxRepeat == 0 */
|
|
if(_padContext != null && _padContext.checkMaxRepeat(_renderedItems)) {
|
|
_renderState = FOOTER;
|
|
return EVAL_BODY_AGAIN;
|
|
}
|
|
|
|
if(_currentItem == null && _ignoreNulls) {
|
|
advanceToNonNullItem();
|
|
|
|
/* no non-null item was found; render the footer */
|
|
if(_currentItem == null) {
|
|
doPadding();
|
|
// render the header
|
|
_renderState = FOOTER;
|
|
}
|
|
/* non-null item found; it's not the 0th item; render it */
|
|
else _renderState = ITEM;
|
|
}
|
|
/* 0th item is non-null; render it */
|
|
else _renderState = ITEM;
|
|
|
|
return EVAL_BODY_AGAIN;
|
|
}
|
|
|
|
if(_renderState == ITEM) {
|
|
_renderedItems++;
|
|
|
|
/* check that the maximum number of items to render has *not* been reached */
|
|
if(_iterator.hasNext() && (_padContext == null || (_padContext != null && !_padContext.checkMaxRepeat(_renderedItems)))) {
|
|
_currentIndex++;
|
|
_currentItem = _iterator.next();
|
|
|
|
if(_ignoreNulls && _currentItem == null) {
|
|
advanceToNonNullItem();
|
|
|
|
/* last item */
|
|
if(_currentItem == null) {
|
|
doPadding();
|
|
|
|
/* render the header */
|
|
_renderState = FOOTER;
|
|
return EVAL_BODY_AGAIN;
|
|
}
|
|
}
|
|
|
|
/* if _ignoreNulls is false, the _currentItem may be null here */
|
|
return EVAL_BODY_AGAIN;
|
|
}
|
|
/*
|
|
have finished rendering items for some reason:
|
|
1) there isn't a next item
|
|
2) reached the maximum number of items to render
|
|
So:
|
|
1) pad if necessary
|
|
2) render the footer
|
|
*/
|
|
else {
|
|
doPadding();
|
|
|
|
_renderState = FOOTER;
|
|
return EVAL_BODY_AGAIN;
|
|
}
|
|
}
|
|
|
|
if(_renderState == FOOTER) {
|
|
_renderState = END;
|
|
return SKIP_BODY;
|
|
}
|
|
|
|
return SKIP_BODY;
|
|
}
|
|
|
|
/**
|
|
* <p/>
|
|
* Internal utility method.
|
|
* </p>
|
|
* </p>
|
|
* This is called in places where the repeater needs to move to the next
|
|
* non-null item in the data set to render. This occurs when the
|
|
* current data item is null and the <code>ignoreNulls</code>
|
|
* flag has been set to skip rendering null items in the data set.
|
|
* </p>
|
|
* <p/>
|
|
* This method side-effects to advance the iterator to the next
|
|
* non-null item or the end if there are zero remaining non-null
|
|
* items.
|
|
* </p>
|
|
* <p/>
|
|
* At the end, the <code>currentItem</code> may be null, and the
|
|
* <code>currentIndex</code> will reference either the integer
|
|
* location in the data structure of the non-null data item, or
|
|
* it will reference the end of the data structure.
|
|
* </p>
|
|
*/
|
|
private final void advanceToNonNullItem() {
|
|
assert _iterator != null;
|
|
assert _currentItem == null;
|
|
|
|
while(_iterator.hasNext() && _currentItem == null) {
|
|
_currentItem = _iterator.next();
|
|
_currentIndex++;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* When using the repeater's pad tag, it is possible to require a minimum number of
|
|
* items render in the repeater. This method pads out the number of items until it
|
|
* reaches the {@link org.apache.beehive.netui.tags.databinding.repeater.pad.PadContext}'s
|
|
* <code>minRepeat</code> property.
|
|
*/
|
|
private final void doPadding() {
|
|
if(_padContext != null && !_padContext.checkMinRepeat(_renderedItems)) {
|
|
/*
|
|
since padding is now running, un-set the current item so that the last
|
|
item isn't accessible during any later data binding
|
|
*/
|
|
_currentItem = null;
|
|
|
|
for(int i = _renderedItems; !_padContext.checkMinRepeat(i); i++) {
|
|
_currentIndex++;
|
|
addContent(_padContext.getPadText());
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* An internal method that turns the current render state into a string for debugging.
|
|
*/
|
|
private static final String renderStateToString(int state) {
|
|
switch(state) {
|
|
case INIT:
|
|
return "INIT";
|
|
case HEADER:
|
|
return "HEADER";
|
|
case ITEM:
|
|
return "ITEM";
|
|
case FOOTER:
|
|
return "FOOTER";
|
|
case END:
|
|
return "END";
|
|
default:
|
|
return "INVALID STATE";
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Return an <code>ArrayList</code> which represents a chain of <code>INameInterceptor</code>
|
|
* objects. This method by default returns <code>null</code> and should be overridden
|
|
* by objects that support naming.
|
|
* @return an <code>ArrayList</code> that will contain <code>INameInterceptor</code> objects.
|
|
*/
|
|
protected List getNamingChain() {
|
|
return AbstractClassicTag.DefaultNamingChain;
|
|
}
|
|
|
|
/**
|
|
* Sets the tag's data source (can be an expression).
|
|
* @param dataSource - the data source
|
|
* @jsptagref.attributedescription <p>The <code>dataSource</code> attribute determines both
|
|
* (1) the source of populating data for the tag and
|
|
* (2) the object to which the tag submits data.
|
|
*
|
|
* <p>For example, assume that the Controller file (= JPF file) contains
|
|
* a Form Bean with the property foo. Then the following <netui:textBox> tag will
|
|
* (1) draw populating data from the Form Bean's foo property and (2)
|
|
* submit user defined data to the same property.
|
|
*
|
|
* <p> <code><netui:textBox dataSource="actionForm.foo" /></code>
|
|
*
|
|
* <p>The <code>dataSource</code> attribute takes either a data binding expression or
|
|
* the name of a Form Bean property. In the
|
|
* above example, <code><netui:textBox dataSource="foo" /></code>
|
|
* would have the exactly same behavior.
|
|
*
|
|
* <p>When the tag is used to submit data, the data binding expression must
|
|
* refer to a Form Bean property.
|
|
* In cases where the tag is not used to submit data, but is used for
|
|
* displaying data only, the data
|
|
* binding expression need not refer to a Form Bean property. For example,
|
|
* assume that myIterativeData is a member variable on
|
|
* the Controller file ( = JPF file). The following <netui-data:repeater>
|
|
* tag draws its data from myIterativeData.
|
|
*
|
|
* <p> <code><netui-data:repeater dataSource="pageFlow.myIterativeData"></code>
|
|
* @jsptagref.attributesyntaxvalue <i>expression_datasource</i>
|
|
* @netui:attribute required="true" rtexprvalue="true"
|
|
*/
|
|
public void setDataSource(String dataSource) {
|
|
_dataSource = dataSource;
|
|
}
|
|
|
|
/**
|
|
* Gets the tag's data source (can be an expression).
|
|
* @return the data source
|
|
*/
|
|
public String getDataSource() {
|
|
return "{" + _dataSource + "}";
|
|
}
|
|
|
|
/**
|
|
* Return the Object that is represented by the specified data source.
|
|
* @return Object
|
|
* @throws JspException
|
|
*/
|
|
private Object evaluateDataSource()
|
|
throws JspException {
|
|
ExpressionHandling expr = new ExpressionHandling(this);
|
|
String dataSource = getDataSource();
|
|
String ds = expr.ensureValidExpression(dataSource, "dataSource", "DataSourceError");
|
|
if (ds == null)
|
|
return null;
|
|
|
|
/* have a valid expression */
|
|
return expr.evaluateExpression(dataSource, "dataSource", pageContext);
|
|
}
|
|
}
|