821 lines
34 KiB
Java
821 lines
34 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.cellrepeater;
|
|
|
|
import java.util.ArrayList;
|
|
import java.util.Iterator;
|
|
import java.util.List;
|
|
import java.util.Collections;
|
|
import javax.servlet.jsp.JspException;
|
|
import javax.servlet.jsp.tagext.TryCatchFinally;
|
|
import javax.servlet.jsp.tagext.SimpleTagSupport;
|
|
import javax.servlet.ServletRequest;
|
|
|
|
import org.apache.beehive.netui.script.common.IDataAccessProvider;
|
|
import org.apache.beehive.netui.script.common.DataAccessProviderStack;
|
|
import org.apache.beehive.netui.tags.AbstractClassicTag;
|
|
import org.apache.beehive.netui.tags.ExpressionHandling;
|
|
import org.apache.beehive.netui.tags.rendering.TagRenderingBase;
|
|
import org.apache.beehive.netui.tags.rendering.TableTag;
|
|
import org.apache.beehive.netui.tags.rendering.TdTag;
|
|
import org.apache.beehive.netui.tags.rendering.TrTag;
|
|
import org.apache.beehive.netui.tags.rendering.AbstractRenderAppender;
|
|
import org.apache.beehive.netui.tags.rendering.StringBuilderRenderAppender;
|
|
import org.apache.beehive.netui.tags.rendering.ConstantRendering;
|
|
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;
|
|
|
|
/**
|
|
* <p/>
|
|
* This tag is a repeating, databound tag that renders its body each cell of a table of the specified dimensions.
|
|
* The tag is bound to a dataset that is specified in the <code>dataSource</code> attribute. For each item in
|
|
* the data set, the body of this tag is rendered, and NetUI tags in the body that are databound
|
|
* can use the <code>container.item</code> syntax to access the "current" data item in the iteration.
|
|
* Properties on this data item can be accessed using expressions.
|
|
* </p>
|
|
* <p>
|
|
* The tag will automatically insert the open and close table, row, and cell tags which will comprise
|
|
* the table that is rendered. Style attributes may be set using attributes on this tag in order to
|
|
* cusotmize the tag's final appearance. The dimensions of the table are specified by using at least
|
|
* one of the attributes columns and rows. If only one is specified, the other will be inferred by
|
|
* using the size of the given data set. As a result, the entire dataset will be rendered. For example,
|
|
* if a table should be four columns wide and the data set has twenty items, the resulting table will
|
|
* have five rows. If the data set is fewer items than the number of cells that should be rendered,
|
|
* the cells are padded with HTML table cells:
|
|
* </p>
|
|
* <pre>
|
|
* <td>&nbsp;</td>
|
|
* </pre>
|
|
* <p/>
|
|
* This will prevent rendering a malformed HTML table. If the number of cells to render is smaller than
|
|
* the fully specified dimensions of the table, only this number of cells will be rendered. For example,
|
|
* if the data set is size fifty but the <code>rows</code> and the <code>columns</code> attributes are
|
|
* both seven, only the first forty-nine items in the dataset will be rendered and the fiftieth
|
|
* will not be shown. The values of the <code>rows</code> and the <code>columns</code> can be databound with
|
|
* an expression; in this case, each value will be converted into an integer. An error will be reported
|
|
* on the page if this conversion fails.
|
|
* </p><p>
|
|
* This tag implements the {@link IDataAccessProvider} interface which provides tags access to the "current"
|
|
* data item. Properties on the <code>IDataAccessProvider</code> interface are available through the
|
|
* "container" binding context, which can be used inside of the body of the CellRepeater. Properties of
|
|
* the <code>IDataAccessProvider</code> interface that are available include:
|
|
* <table border="1" cellspacing="0" cellpadding="5" width="75%">
|
|
* <tr><td><b>Name</b></td><td><b>Description</b></td></tr>
|
|
* <tr><td>index</td><td>the current index in the iteration; this index is absolute to the dataset</td></tr>
|
|
* <tr><td>parent</td><td>any <code>IDataAccessProvider</code> parent of this tag</td></tr>
|
|
* <tr><td>item</td><td>the current data item</td></tr>
|
|
* </table>
|
|
* </p>
|
|
* <p/>
|
|
* <b>Note:</b> the metadata property of the <code>container</code> binding context is not supported
|
|
* on the CellRepeater.
|
|
* </p>
|
|
*
|
|
* @jsptagref.tagdescription
|
|
* <p/>
|
|
* This tag is a repeating, databound tag that renders its body each cell of a table of the specified dimensions.
|
|
* The tag is bound to a dataset that is specified in the <code>dataSource</code> attribute. For each item in
|
|
* the data set, the body of this tag is rendered, and NetUI tags in the body that are databound
|
|
* can use the <code>container.item</code> syntax to access the "current" data item in the iteration.
|
|
* Properties on this data item can be accessed using expressions.
|
|
* </p>
|
|
* <p>
|
|
* The tag will automatically insert the open and close table, row, and cell tags which will comprise
|
|
* the table that is rendered. Style attributes may be set using attributes on this tag in order to
|
|
* cusotmize the tag's final appearance. The dimensions of the table are specified by using at least
|
|
* one of the attributes columns and rows. If only one is specified, the other will be inferred by
|
|
* using the size of the given data set. As a result, the entire dataset will be rendered. For example,
|
|
* if a table should be four columns wide and the data set has twenty items, the resulting table will
|
|
* have five rows. If the data set is fewer items than the number of cells that should be rendered,
|
|
* the cells are padded with HTML table cells:
|
|
* </p>
|
|
* <pre>
|
|
* <td>&nbsp;</td>
|
|
* </pre>
|
|
* <p/>
|
|
* This will prevent rendering a malformed HTML table. If the number of cells to render is smaller than
|
|
* the fully specified dimensions of the table, only this number of cells will be rendered. For example,
|
|
* if the data set is size fifty but the <code>rows</code> and the <code>columns</code> attributes are
|
|
* both seven, only the first forty-nine items in the dataset will be rendered and the fiftieth
|
|
* will not be shown. The values of the <code>rows</code> and the <code>columns</code> can be databound with
|
|
* an expression; in this case, each value will be converted into an integer. An error will be reported
|
|
* on the page if this conversion fails.
|
|
* </p><p>
|
|
* This tag implements the {@link IDataAccessProvider} interface which provides tags access to the "current"
|
|
* data item. Properties on the <code>IDataAccessProvider</code> interface are available through the
|
|
* "container" binding context, which can be used inside of the body of the CellRepeater. Properties of
|
|
* the <code>IDataAccessProvider</code> interface that are available include:
|
|
* <table border="1" cellspacing="0" cellpadding="5" width="75%">
|
|
* <tr><td><b>Name</b></td><td><b>Description</b></td></tr>
|
|
* <tr><td>index</td><td>the current index in the iteration; this index is absolute to the dataset</td></tr>
|
|
* <tr><td>parent</td><td>any <code>IDataAccessProvider</code> parent of this tag</td></tr>
|
|
* <tr><td>item</td><td>the current data item</td></tr>
|
|
* </table>
|
|
* </p>
|
|
* <p/>
|
|
* <b>Note:</b> the metadata property of the <code>container</code> binding context is not supported
|
|
* on the CellRepeater.
|
|
* </p>
|
|
*
|
|
* @example
|
|
* In this example, the <netui-data:cellRepeater> tag creates a table with the number of columns set
|
|
* given as <code>${pageFlow.numColumns}</code> and as many rows as necessary to display all the items in the
|
|
* <code>pageFlow.itemArray</code> data set.
|
|
* <pre>
|
|
* <netui-data:cellRepeater dataSource="pageFlow.itemArray" columns="pageFlow.numColumns">
|
|
* Item: <netui:span value="${container.item}"/>
|
|
* </netui-data:cellRepeater>
|
|
* </pre>
|
|
* @netui:tag name="cellRepeater"
|
|
* description="A repeating, databound tag that renders its body into each cell of a table of the specified dimensions."
|
|
*/
|
|
public class CellRepeater
|
|
extends AbstractClassicTag
|
|
implements IDataAccessProvider, TryCatchFinally {
|
|
|
|
private static final Logger LOGGER = Logger.getInstance(CellRepeater.class);
|
|
private static final int DIMENSION_DEFAULT_VALUE = -1;
|
|
private static final TableTag.State STATE_TABLE = new TableTag.State();
|
|
private static final TrTag.State STATE_TR = new TrTag.State();
|
|
private static final TdTag.State STATE_TD = new TdTag.State();
|
|
|
|
private boolean _valid = true;
|
|
private boolean _verticalRepeat = false;
|
|
private boolean _containerInPageContext = false;
|
|
private int _columns = DIMENSION_DEFAULT_VALUE;
|
|
private int _rows = DIMENSION_DEFAULT_VALUE;
|
|
private int _currentIndex = -1;
|
|
private int _currentRow = -1;
|
|
private int _currentColumn = -1;
|
|
|
|
private ArrayList _dataList = null;
|
|
private Object _currentItem = null;
|
|
private String _dataSource = null;
|
|
private String _altCellClass = null;
|
|
private String _cellClass = null;
|
|
private ConstantRendering _htmlConstantRendering = null;
|
|
private TagRenderingBase _tableRenderer = null;
|
|
private TagRenderingBase _trRenderer = null;
|
|
private TagRenderingBase _tdRenderer = null;
|
|
private TableTag.State _tableState = null;
|
|
private TdTag.State _tdState = null;
|
|
private TrTag.State _trState = null;
|
|
private InternalStringBuilder _sb = null;
|
|
private AbstractRenderAppender _appender = 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 "CellRepeater";
|
|
}
|
|
|
|
/**
|
|
* The HTML style class that is rendered on the HTML table. For example, if the row class is "tableClass",
|
|
* each opening table tag is:
|
|
* <pre>
|
|
* <table class="tableClass">
|
|
* </pre>
|
|
*
|
|
* @param tableClass the name of a style class in a CSS
|
|
* @jsptagref.attributedescription
|
|
* The HTML style class that is rendered on the HTML table. For example, if the row class is "tableClass",
|
|
* each opening table tag is:
|
|
* <pre>
|
|
* <table class="tableClass">
|
|
* </pre>
|
|
* @jsptagref.attributesyntaxvalue <i>string_class</i>
|
|
* @netui:attribute required="false"
|
|
*/
|
|
public void setTableClass(String tableClass) {
|
|
if("".equals(tableClass))
|
|
return;
|
|
_tableState = new TableTag.State();
|
|
_tableState.styleClass = tableClass;
|
|
}
|
|
|
|
/**
|
|
* Set the HTML style class that is rendered on each HTML table row that
|
|
* is opened by this tag. For example, if the row class is "rowClass",
|
|
* each opening table row tag is:
|
|
* <pre>
|
|
* <tr class="rowClass">
|
|
* </pre>
|
|
*
|
|
* @param rowClass the name of a style class in the CSS
|
|
* @jsptagref.attributedescription
|
|
* Set the HTML style class that is rendered on each HTML table row that
|
|
* is opened by this tag. For example, if the row class is "rowClass",
|
|
* each opening table row tag is:
|
|
* <pre>
|
|
* <tr class="rowClass">
|
|
* </pre>
|
|
* @jsptagref.attributesyntaxvalue <i>string_class</i>
|
|
* @netui:attribute required="false"
|
|
*/
|
|
public void setRowClass(String rowClass) {
|
|
if("".equals(rowClass))
|
|
return;
|
|
_trState = new TrTag.State();
|
|
_trState.styleClass = rowClass;
|
|
}
|
|
|
|
/**
|
|
* Set the HTML style class that is rendered on each HTML table cell that
|
|
* is opened by this tag. For example, if the cell class is "cellClass",
|
|
* each opening table cell tag is:
|
|
* <pre>
|
|
* <td class="cellClass">
|
|
* </pre>
|
|
*
|
|
* @param cellClass the name of a style class in a CSS
|
|
* @jsptagref.attributedescription
|
|
* Set the HTML style class that is rendered on each HTML table cell that
|
|
* is opened by this tag. For example, if the cell class is "cellClass",
|
|
* each opening table cell tag is:
|
|
* <pre>
|
|
* <td class="cellClass">
|
|
* </pre>
|
|
* @jsptagref.attributesyntaxvalue <i>string_class</i>
|
|
* @netui:attribute required="false"
|
|
*/
|
|
public void setCellClass(String cellClass) {
|
|
if("".equals(cellClass))
|
|
return;
|
|
_cellClass = cellClass;
|
|
}
|
|
|
|
/**
|
|
* Set the HTML style class that is rendered on each HTML table cell that
|
|
* is opened by this tag. The starting cell is alternated for each row, which
|
|
* results in a checkerboard colored table being displayed. For example, if the
|
|
* alteranting cell class is \"alternatingCellClass\", every other table cell
|
|
* tag is:
|
|
* <pre>
|
|
* <td cell="alternatingCellClass">
|
|
* </pre>
|
|
*
|
|
* @param alternatingCellClass the name of a style class in a CSS
|
|
* @jsptagref.attributedescription
|
|
* The HTML style class that is rendered on alternating table cells.
|
|
* The starting cell is alternated for each row, which
|
|
* results in a checkerboard colored table being displayed. For example, if the
|
|
* alteranting cell class is "alternatingCellClass", every other table cell
|
|
* tag is:
|
|
* <pre>
|
|
* <td cell="alternatingCellClass"></pre>
|
|
* @jsptagref.attributesyntaxvalue <i>string_class</i>
|
|
* @netui:attribute required="false"
|
|
*/
|
|
public void setAlternatingCellClass(String alternatingCellClass) {
|
|
if("".equals(alternatingCellClass))
|
|
return;
|
|
_altCellClass = alternatingCellClass;
|
|
}
|
|
|
|
/**
|
|
* This tag can render the items in its dataset horizontally or vertically. If
|
|
* the rows are rendered horizontally, the items in the dataset are rendered
|
|
* across each row from top to bottom. Otherwise, they are rendered down each
|
|
* column from left to right. The default is to render the items horizontally.
|
|
*
|
|
* @param verticalRepeat if set to <code>true</code>, the dataset is rendered down
|
|
* each column; otherwise it is rendered across each row, the default.
|
|
* @jsptagref.attributedescription
|
|
* Boolean. If true the data set is rendered vertically, otherwise it is rendered horizontally. If
|
|
* the rows are rendered horizontally, the items in the data set are rendered
|
|
* across each row from top to bottom. Otherwise, they are rendered down each
|
|
* column from left to right. The default is to render the items horizontally.
|
|
* @jsptagref.attributesyntaxvalue <i>boolean_verticalRepeat</i>
|
|
* @netui:attribute required="false"
|
|
*/
|
|
public void setVerticalRepeat(boolean verticalRepeat) {
|
|
_verticalRepeat = verticalRepeat;
|
|
}
|
|
|
|
/**
|
|
* Set the number of columns that should be rendered in the table
|
|
* generated by the tag. If the columns attribute is specified but
|
|
* the rows attribute is not, the rows attribute will be inferred
|
|
* using the size of the dataset.
|
|
*
|
|
* @param columns an integer or an expression
|
|
* @jsptagref.attributedescription
|
|
* Integer. The number of columns that should be rendered in the HTML table.
|
|
* If the <code>columns</code> attribute is specified but
|
|
* the <code>rows</code> attribute is not, the <code>rows</code> attribute will be inferred
|
|
* using the size of the data set.
|
|
* @jsptagref.attributesyntaxvalue <i>integer_columns</i>
|
|
* @netui:attribute required="false" rtexprvalue="true"
|
|
*/
|
|
public void setColumns(int columns) {
|
|
_columns = columns;
|
|
}
|
|
|
|
/**
|
|
* Set the number of rows that should be rendered in the table
|
|
* generated by the tag. If the rows attribute is specified but
|
|
* the columns attribute is not, the columns attribute will be
|
|
* inferred using the size of the dataset.
|
|
*
|
|
* @param rows an integer or an expression whose value can be
|
|
* converted into an integer.
|
|
* @jsptagref.attributedescription
|
|
* Integer. The number of rows that should be rendered in the HTML table.
|
|
* If the <code>rows</code> attribute is specified but
|
|
* the <code>columns</code> attribute is not, the <code>columns</code> attribute will be
|
|
* inferred using the size of the data set.
|
|
* @jsptagref.attributesyntaxvalue <i>integer_rows</i>
|
|
* @netui:attribute required="false" rtexprvalue="true"
|
|
*/
|
|
public void setRows(int rows) {
|
|
_rows = rows;
|
|
}
|
|
|
|
/**
|
|
* <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>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.
|
|
|
|
* @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>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:cellRepeater dataSource="pageFlow.myIterativeData"></code>
|
|
* @jsptagref.attributesyntaxvalue <i>expression_datasource</i>
|
|
* @netui:attribute required="true" rtexprvalue="true"
|
|
*/
|
|
public void setDataSource(String dataSource) {
|
|
_dataSource = dataSource;
|
|
}
|
|
|
|
/**
|
|
* Prepare to render the dataset that was specified in the dataSource attribute. The
|
|
* dataSource expression is evaluated and the table's dimensions are computed. If
|
|
* there is no data in the dataset but the rows and columns attributes were specified,
|
|
* an empty table of the given dimensions is rendered.
|
|
*
|
|
* @return EVAL_BODY_BUFFERED or SKIP_BODY if errors are reported, the data set
|
|
* is null, or there is no data in the data set
|
|
* @throws JspException if errors occurred that could not be reported in the page
|
|
*/
|
|
public int doStartTag()
|
|
throws JspException {
|
|
ServletRequest request = pageContext.getRequest();
|
|
|
|
_tableRenderer = TagRenderingBase.Factory.getRendering(TagRenderingBase.TABLE_TAG, request);
|
|
_trRenderer = TagRenderingBase.Factory.getRendering(TagRenderingBase.TR_TAG, request);
|
|
_tdRenderer = TagRenderingBase.Factory.getRendering(TagRenderingBase.TD_TAG, request);
|
|
_htmlConstantRendering = TagRenderingBase.Factory.getConstantRendering(request);
|
|
|
|
_sb = new InternalStringBuilder(1024);
|
|
_appender = new StringBuilderRenderAppender(_sb);
|
|
|
|
Object source = evaluateDataSource();
|
|
|
|
if(hasErrors())
|
|
return SKIP_BODY;
|
|
|
|
if(source != null) {
|
|
Iterator iterator = IteratorFactory.createIterator(source);
|
|
if(iterator == null) {
|
|
LOGGER.info("CellRepeater: The data structure from which to create an iterator is null.");
|
|
iterator = Collections.EMPTY_LIST.iterator();
|
|
}
|
|
|
|
if(iterator != null) {
|
|
_dataList = new ArrayList();
|
|
while(iterator.hasNext()) {
|
|
_dataList.add(iterator.next());
|
|
}
|
|
}
|
|
}
|
|
|
|
if(_rows == DIMENSION_DEFAULT_VALUE || _columns == DIMENSION_DEFAULT_VALUE) {
|
|
/* try to guess the dimensions of the table */
|
|
if(_dataList != null && _dataList.size() > 0) {
|
|
guessDimensions(_dataList);
|
|
|
|
if(hasErrors())
|
|
return SKIP_BODY;
|
|
}
|
|
/* the size of the data set isn't guessable */
|
|
else {
|
|
_valid = false;
|
|
return SKIP_BODY;
|
|
}
|
|
}
|
|
|
|
/* check to make sure the rows / columns are actually valid before starting to render */
|
|
if(_rows <= 0) {
|
|
String msg = Bundle.getString("Tags_CellRepeater_invalidRowValue", new Object[]{getTagName(), new Integer(_rows)});
|
|
registerTagError(msg, null);
|
|
}
|
|
|
|
if(_columns <= 0) {
|
|
String msg = Bundle.getString("Tags_CellRepeater_invalidColumnValue", new Object[]{getTagName(), new Integer(_columns)});
|
|
registerTagError(msg, null);
|
|
}
|
|
|
|
if(hasErrors())
|
|
return SKIP_BODY;
|
|
|
|
openTableTag(_appender, _tableState);
|
|
|
|
_currentRow = 0;
|
|
_currentColumn = 0;
|
|
|
|
DataAccessProviderStack.addDataAccessProvider(this, pageContext);
|
|
_containerInPageContext = true;
|
|
|
|
boolean haveItem = ensureItem(0, _dataList);
|
|
if(haveItem) {
|
|
openRowTag(_appender, _trState);
|
|
openCellTag(_appender, _currentColumn);
|
|
return EVAL_BODY_BUFFERED;
|
|
}
|
|
else {
|
|
// special case -- with no items, render the entire table here
|
|
for(int i = 0; i < _rows; i++) {
|
|
openRowTag(_appender, _trState);
|
|
for(int j = 0; j < _columns; j++) {
|
|
openCellTag(_appender, computeStyleIndex(i, j));
|
|
_htmlConstantRendering.NBSP(_appender);
|
|
closeCellTag(_appender);
|
|
}
|
|
closeRowTag(_appender);
|
|
_appender.append("\n");
|
|
}
|
|
_currentRow = _rows;
|
|
_currentColumn = _columns;
|
|
return SKIP_BODY;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Continue rendering the body of this tag until the dimensions of the table have been reached or
|
|
* the entire dataset has been rendered. The buffered body content from the previous iteration
|
|
* of the body is added to the content this tag will render, @see addContent(java.lang.String).
|
|
* Pad the table if the dimensions have not been met but the dataset is empty.
|
|
*
|
|
* @return EVAL_BODY_BUFFERED if there is more data to render in the dataset or
|
|
* SKIP_BODY if the end of the dataset is reached or an error occurs
|
|
*/
|
|
public int doAfterBody() {
|
|
if(bodyContent != null) {
|
|
_appender.append(bodyContent.getString());
|
|
bodyContent.clearBody();
|
|
}
|
|
|
|
/*
|
|
this loop exists so that the table is filled out correctly up to the specified
|
|
or guessed table dimensions. this is a little bit of a kludge; this logic should be done
|
|
in doEndTag()
|
|
*/
|
|
boolean haveNext = false;
|
|
while(!haveNext) {
|
|
_currentColumn++;
|
|
|
|
/* close the previous cell whose content was rendered the last time the tag body was executed */
|
|
closeCellTag(_appender);
|
|
|
|
/* open a new table row */
|
|
if(_currentColumn == _columns) {
|
|
_currentRow++;
|
|
_currentColumn = 0;
|
|
closeRowTag(_appender);
|
|
_appender.append("\n");
|
|
}
|
|
|
|
/* reached the end of the table as the current row is now equal to the total number of rows */
|
|
if(_currentRow == _rows)
|
|
return SKIP_BODY;
|
|
|
|
if(_currentColumn == 0)
|
|
openRowTag(_appender, _trState != null ? _trState : STATE_TR);
|
|
|
|
int itemIndex = -1;
|
|
if(_verticalRepeat)
|
|
itemIndex = _currentColumn * _rows + _currentRow;
|
|
else itemIndex = _currentRow * _columns + _currentColumn;
|
|
|
|
haveNext = ensureItem(itemIndex, _dataList);
|
|
|
|
openCellTag(_appender, computeStyleIndex(_currentRow, _currentColumn)) ;
|
|
|
|
/* render empty cell and continue filling the table */
|
|
if(!haveNext)
|
|
_htmlConstantRendering.NBSP(_appender);
|
|
/* open a new table cell and render the body once again. note, this exits the while loop above */
|
|
else return EVAL_BODY_AGAIN;
|
|
}
|
|
|
|
/* default is to skip the tag body */
|
|
return SKIP_BODY;
|
|
}
|
|
|
|
/**
|
|
* Complete rendering the tag. If no errors have occurred, the content that
|
|
* the tag buffered is rendered.
|
|
*
|
|
* @return EVAL_PAGE to continue evaluating the page
|
|
* @throws JspException if an error occurs that can not be reported on the page
|
|
*/
|
|
public int doEndTag()
|
|
throws JspException {
|
|
if(hasErrors())
|
|
reportErrors();
|
|
else if(_valid) {
|
|
closeTableTag(_appender);
|
|
write(_sb.toString());
|
|
}
|
|
|
|
return EVAL_PAGE;
|
|
}
|
|
|
|
public void doFinally() {
|
|
localRelease();
|
|
}
|
|
|
|
public void doCatch(Throwable t)
|
|
throws Throwable {
|
|
throw t;
|
|
}
|
|
|
|
/**
|
|
* Gets the tag's data source (can be an expression).
|
|
* @return the data source
|
|
*/
|
|
public String getDataSource() {
|
|
return "{" + _dataSource + "}";
|
|
}
|
|
|
|
/**
|
|
* 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() {
|
|
return (IDataAccessProvider)SimpleTagSupport.findAncestorWithClass(this, IDataAccessProvider.class);
|
|
}
|
|
|
|
/**
|
|
* 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;
|
|
}
|
|
|
|
/**
|
|
* Reset all of the fields of this tag.
|
|
*/
|
|
protected void localRelease() {
|
|
super.localRelease();
|
|
|
|
if(bodyContent != null)
|
|
bodyContent.clearBody();
|
|
|
|
_rows = DIMENSION_DEFAULT_VALUE;
|
|
_columns = DIMENSION_DEFAULT_VALUE;
|
|
_currentRow = -1;
|
|
_currentColumn = -1;
|
|
_currentIndex = -1;
|
|
_verticalRepeat = false;
|
|
_dataList = null;
|
|
_currentItem = null;
|
|
_valid = true;
|
|
_dataSource = null;
|
|
|
|
if(_tdState != null)
|
|
_tdState.clear();
|
|
if(_trState != null)
|
|
_trState.clear();
|
|
if(_tableState != null)
|
|
_tableState.clear();
|
|
|
|
_tableRenderer = null;
|
|
_tdRenderer = null;
|
|
_trRenderer = null;
|
|
|
|
_sb = null;
|
|
_appender = null;
|
|
|
|
if(_containerInPageContext) {
|
|
DataAccessProviderStack.removeDataAccessProvider(pageContext);
|
|
_containerInPageContext = false;
|
|
}
|
|
}
|
|
|
|
private final void guessDimensions(ArrayList data)
|
|
throws JspException {
|
|
|
|
if(_rows == 0 || _columns == 0)
|
|
registerTagError(Bundle.getString("Tags_CellRepeater_missingRowsOrColumns"), null);
|
|
|
|
if(data == null)
|
|
return;
|
|
|
|
int dataSize = data.size();
|
|
if(_rows == DIMENSION_DEFAULT_VALUE && _columns == DIMENSION_DEFAULT_VALUE) {
|
|
registerTagError(Bundle.getString("Tags_CellRepeater_invalidRowOrColumn"), null);
|
|
}
|
|
else if(_rows == DIMENSION_DEFAULT_VALUE) {
|
|
int remainder = dataSize % _columns;
|
|
_rows = (dataSize / _columns) + (remainder > 0 ? 1 : 0);
|
|
LOGGER.debug("guessed row size: " + _rows);
|
|
}
|
|
else if(_columns == DIMENSION_DEFAULT_VALUE) {
|
|
int remainder = dataSize % _rows;
|
|
_columns = (dataSize / _rows) + (remainder > 0 ? 1 : 0);
|
|
LOGGER.debug("guessed column size: " + _columns);
|
|
}
|
|
}
|
|
|
|
private void openTableTag(AbstractRenderAppender appender, TableTag.State tableState) {
|
|
if(tableState == null)
|
|
tableState = STATE_TABLE;
|
|
_tableRenderer.doStartTag(appender, tableState);
|
|
}
|
|
|
|
private void closeTableTag(AbstractRenderAppender appender) {
|
|
assert appender != null;
|
|
assert _tableRenderer != null;
|
|
_tableRenderer.doEndTag(appender);
|
|
}
|
|
|
|
private void openRowTag(AbstractRenderAppender appender, TrTag.State trState) {
|
|
if(trState == null)
|
|
trState = STATE_TR;
|
|
_trRenderer.doStartTag(appender, trState);
|
|
}
|
|
|
|
private void closeRowTag(AbstractRenderAppender appender) {
|
|
assert _trRenderer != null;
|
|
assert appender != null;
|
|
_trRenderer.doEndTag(appender);
|
|
}
|
|
|
|
private void openCellTag(AbstractRenderAppender appender, int index) {
|
|
assert appender != null;
|
|
assert index >= 0;
|
|
assert _tdRenderer != null;
|
|
|
|
TdTag.State tdState = STATE_TD;
|
|
if(_cellClass != null) {
|
|
if(_tdState != null)
|
|
_tdState.clear();
|
|
else _tdState = new TdTag.State();
|
|
if(index % 2 == 0)
|
|
_tdState.styleClass = _cellClass;
|
|
else _tdState.styleClass = (_altCellClass != null ? _altCellClass : _cellClass);
|
|
tdState = _tdState;
|
|
}
|
|
|
|
_tdRenderer.doStartTag(appender, tdState);
|
|
}
|
|
|
|
private void closeCellTag(AbstractRenderAppender appender) {
|
|
assert _tdRenderer != null;
|
|
assert appender != null;
|
|
_tdRenderer.doEndTag(appender);
|
|
}
|
|
|
|
private int computeStyleIndex(int r, int c) {
|
|
return c + (r % 2);
|
|
}
|
|
|
|
private boolean ensureItem(int index, ArrayList data) {
|
|
LOGGER.debug("item: " + 0 + " data: " + (data == null ? "null data" :
|
|
(index < data.size() ? "" + index : "index out of bounds for size " + data.size())));
|
|
|
|
if(data != null && index < data.size()) {
|
|
_currentItem = data.get(index);
|
|
_currentIndex = index;
|
|
return true;
|
|
}
|
|
else return false;
|
|
}
|
|
|
|
/**
|
|
* 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;
|
|
|
|
Object o = expr.evaluateExpression(dataSource, "dataSource", pageContext);
|
|
return o;
|
|
}
|
|
} |