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.databinding.cellrepeater;
020:
021: import java.util.ArrayList;
022: import java.util.Iterator;
023: import java.util.List;
024: import java.util.Collections;
025: import javax.servlet.jsp.JspException;
026: import javax.servlet.jsp.tagext.TryCatchFinally;
027: import javax.servlet.jsp.tagext.SimpleTagSupport;
028: import javax.servlet.ServletRequest;
029:
030: import org.apache.beehive.netui.script.common.IDataAccessProvider;
031: import org.apache.beehive.netui.script.common.DataAccessProviderStack;
032: import org.apache.beehive.netui.tags.AbstractClassicTag;
033: import org.apache.beehive.netui.tags.ExpressionHandling;
034: import org.apache.beehive.netui.tags.rendering.TagRenderingBase;
035: import org.apache.beehive.netui.tags.rendering.TableTag;
036: import org.apache.beehive.netui.tags.rendering.TdTag;
037: import org.apache.beehive.netui.tags.rendering.TrTag;
038: import org.apache.beehive.netui.tags.rendering.AbstractRenderAppender;
039: import org.apache.beehive.netui.tags.rendering.StringBuilderRenderAppender;
040: import org.apache.beehive.netui.tags.rendering.ConstantRendering;
041: import org.apache.beehive.netui.util.Bundle;
042: import org.apache.beehive.netui.util.exception.LocalizedUnsupportedOperationException;
043: import org.apache.beehive.netui.util.internal.InternalStringBuilder;
044: import org.apache.beehive.netui.util.iterator.IteratorFactory;
045: import org.apache.beehive.netui.util.logging.Logger;
046:
047: /**
048: * <p/>
049: * This tag is a repeating, databound tag that renders its body each cell of a table of the specified dimensions.
050: * The tag is bound to a dataset that is specified in the <code>dataSource</code> attribute. For each item in
051: * the data set, the body of this tag is rendered, and NetUI tags in the body that are databound
052: * can use the <code>container.item</code> syntax to access the "current" data item in the iteration.
053: * Properties on this data item can be accessed using expressions.
054: * </p>
055: * <p>
056: * The tag will automatically insert the open and close table, row, and cell tags which will comprise
057: * the table that is rendered. Style attributes may be set using attributes on this tag in order to
058: * cusotmize the tag's final appearance. The dimensions of the table are specified by using at least
059: * one of the attributes columns and rows. If only one is specified, the other will be inferred by
060: * using the size of the given data set. As a result, the entire dataset will be rendered. For example,
061: * if a table should be four columns wide and the data set has twenty items, the resulting table will
062: * have five rows. If the data set is fewer items than the number of cells that should be rendered,
063: * the cells are padded with HTML table cells:
064: * </p>
065: * <pre>
066: * <td>&nbsp;</td>
067: * </pre>
068: * <p/>
069: * This will prevent rendering a malformed HTML table. If the number of cells to render is smaller than
070: * the fully specified dimensions of the table, only this number of cells will be rendered. For example,
071: * if the data set is size fifty but the <code>rows</code> and the <code>columns</code> attributes are
072: * both seven, only the first forty-nine items in the dataset will be rendered and the fiftieth
073: * will not be shown. The values of the <code>rows</code> and the <code>columns</code> can be databound with
074: * an expression; in this case, each value will be converted into an integer. An error will be reported
075: * on the page if this conversion fails.
076: * </p><p>
077: * This tag implements the {@link IDataAccessProvider} interface which provides tags access to the "current"
078: * data item. Properties on the <code>IDataAccessProvider</code> interface are available through the
079: * "container" binding context, which can be used inside of the body of the CellRepeater. Properties of
080: * the <code>IDataAccessProvider</code> interface that are available include:
081: * <table border="1" cellspacing="0" cellpadding="5" width="75%">
082: * <tr><td><b>Name</b></td><td><b>Description</b></td></tr>
083: * <tr><td>index</td><td>the current index in the iteration; this index is absolute to the dataset</td></tr>
084: * <tr><td>parent</td><td>any <code>IDataAccessProvider</code> parent of this tag</td></tr>
085: * <tr><td>item</td><td>the current data item</td></tr>
086: * </table>
087: * </p>
088: * <p/>
089: * <b>Note:</b> the metadata property of the <code>container</code> binding context is not supported
090: * on the CellRepeater.
091: * </p>
092: *
093: * @jsptagref.tagdescription
094: * <p/>
095: * This tag is a repeating, databound tag that renders its body each cell of a table of the specified dimensions.
096: * The tag is bound to a dataset that is specified in the <code>dataSource</code> attribute. For each item in
097: * the data set, the body of this tag is rendered, and NetUI tags in the body that are databound
098: * can use the <code>container.item</code> syntax to access the "current" data item in the iteration.
099: * Properties on this data item can be accessed using expressions.
100: * </p>
101: * <p>
102: * The tag will automatically insert the open and close table, row, and cell tags which will comprise
103: * the table that is rendered. Style attributes may be set using attributes on this tag in order to
104: * cusotmize the tag's final appearance. The dimensions of the table are specified by using at least
105: * one of the attributes columns and rows. If only one is specified, the other will be inferred by
106: * using the size of the given data set. As a result, the entire dataset will be rendered. For example,
107: * if a table should be four columns wide and the data set has twenty items, the resulting table will
108: * have five rows. If the data set is fewer items than the number of cells that should be rendered,
109: * the cells are padded with HTML table cells:
110: * </p>
111: * <pre>
112: * <td>&nbsp;</td>
113: * </pre>
114: * <p/>
115: * This will prevent rendering a malformed HTML table. If the number of cells to render is smaller than
116: * the fully specified dimensions of the table, only this number of cells will be rendered. For example,
117: * if the data set is size fifty but the <code>rows</code> and the <code>columns</code> attributes are
118: * both seven, only the first forty-nine items in the dataset will be rendered and the fiftieth
119: * will not be shown. The values of the <code>rows</code> and the <code>columns</code> can be databound with
120: * an expression; in this case, each value will be converted into an integer. An error will be reported
121: * on the page if this conversion fails.
122: * </p><p>
123: * This tag implements the {@link IDataAccessProvider} interface which provides tags access to the "current"
124: * data item. Properties on the <code>IDataAccessProvider</code> interface are available through the
125: * "container" binding context, which can be used inside of the body of the CellRepeater. Properties of
126: * the <code>IDataAccessProvider</code> interface that are available include:
127: * <table border="1" cellspacing="0" cellpadding="5" width="75%">
128: * <tr><td><b>Name</b></td><td><b>Description</b></td></tr>
129: * <tr><td>index</td><td>the current index in the iteration; this index is absolute to the dataset</td></tr>
130: * <tr><td>parent</td><td>any <code>IDataAccessProvider</code> parent of this tag</td></tr>
131: * <tr><td>item</td><td>the current data item</td></tr>
132: * </table>
133: * </p>
134: * <p/>
135: * <b>Note:</b> the metadata property of the <code>container</code> binding context is not supported
136: * on the CellRepeater.
137: * </p>
138: *
139: * @example
140: * In this example, the <netui-data:cellRepeater> tag creates a table with the number of columns set
141: * given as <code>${pageFlow.numColumns}</code> and as many rows as necessary to display all the items in the
142: * <code>pageFlow.itemArray</code> data set.
143: * <pre>
144: * <netui-data:cellRepeater dataSource="pageFlow.itemArray" columns="pageFlow.numColumns">
145: * Item: <netui:span value="${container.item}"/>
146: * </netui-data:cellRepeater>
147: * </pre>
148: * @netui:tag name="cellRepeater"
149: * description="A repeating, databound tag that renders its body into each cell of a table of the specified dimensions."
150: */
151: public class CellRepeater extends AbstractClassicTag implements
152: IDataAccessProvider, TryCatchFinally {
153:
154: private static final Logger LOGGER = Logger
155: .getInstance(CellRepeater.class);
156: private static final int DIMENSION_DEFAULT_VALUE = -1;
157: private static final TableTag.State STATE_TABLE = new TableTag.State();
158: private static final TrTag.State STATE_TR = new TrTag.State();
159: private static final TdTag.State STATE_TD = new TdTag.State();
160:
161: private boolean _valid = true;
162: private boolean _verticalRepeat = false;
163: private boolean _containerInPageContext = false;
164: private int _columns = DIMENSION_DEFAULT_VALUE;
165: private int _rows = DIMENSION_DEFAULT_VALUE;
166: private int _currentIndex = -1;
167: private int _currentRow = -1;
168: private int _currentColumn = -1;
169:
170: private ArrayList _dataList = null;
171: private Object _currentItem = null;
172: private String _dataSource = null;
173: private String _altCellClass = null;
174: private String _cellClass = null;
175: private ConstantRendering _htmlConstantRendering = null;
176: private TagRenderingBase _tableRenderer = null;
177: private TagRenderingBase _trRenderer = null;
178: private TagRenderingBase _tdRenderer = null;
179: private TableTag.State _tableState = null;
180: private TdTag.State _tdState = null;
181: private TrTag.State _trState = null;
182: private InternalStringBuilder _sb = null;
183: private AbstractRenderAppender _appender = null;
184:
185: /**
186: * Get the name of this tag. This is used to identify the type of this tag
187: * for reporting tag errors.
188: *
189: * @return a constant String representing the name of this tag.
190: */
191: public String getTagName() {
192: return "CellRepeater";
193: }
194:
195: /**
196: * The HTML style class that is rendered on the HTML table. For example, if the row class is "tableClass",
197: * each opening table tag is:
198: * <pre>
199: * <table class="tableClass">
200: * </pre>
201: *
202: * @param tableClass the name of a style class in a CSS
203: * @jsptagref.attributedescription
204: * The HTML style class that is rendered on the HTML table. For example, if the row class is "tableClass",
205: * each opening table tag is:
206: * <pre>
207: * <table class="tableClass">
208: * </pre>
209: * @jsptagref.attributesyntaxvalue <i>string_class</i>
210: * @netui:attribute required="false"
211: */
212: public void setTableClass(String tableClass) {
213: if ("".equals(tableClass))
214: return;
215: _tableState = new TableTag.State();
216: _tableState.styleClass = tableClass;
217: }
218:
219: /**
220: * Set the HTML style class that is rendered on each HTML table row that
221: * is opened by this tag. For example, if the row class is "rowClass",
222: * each opening table row tag is:
223: * <pre>
224: * <tr class="rowClass">
225: * </pre>
226: *
227: * @param rowClass the name of a style class in the CSS
228: * @jsptagref.attributedescription
229: * Set the HTML style class that is rendered on each HTML table row that
230: * is opened by this tag. For example, if the row class is "rowClass",
231: * each opening table row tag is:
232: * <pre>
233: * <tr class="rowClass">
234: * </pre>
235: * @jsptagref.attributesyntaxvalue <i>string_class</i>
236: * @netui:attribute required="false"
237: */
238: public void setRowClass(String rowClass) {
239: if ("".equals(rowClass))
240: return;
241: _trState = new TrTag.State();
242: _trState.styleClass = rowClass;
243: }
244:
245: /**
246: * Set the HTML style class that is rendered on each HTML table cell that
247: * is opened by this tag. For example, if the cell class is "cellClass",
248: * each opening table cell tag is:
249: * <pre>
250: * <td class="cellClass">
251: * </pre>
252: *
253: * @param cellClass the name of a style class in a CSS
254: * @jsptagref.attributedescription
255: * Set the HTML style class that is rendered on each HTML table cell that
256: * is opened by this tag. For example, if the cell class is "cellClass",
257: * each opening table cell tag is:
258: * <pre>
259: * <td class="cellClass">
260: * </pre>
261: * @jsptagref.attributesyntaxvalue <i>string_class</i>
262: * @netui:attribute required="false"
263: */
264: public void setCellClass(String cellClass) {
265: if ("".equals(cellClass))
266: return;
267: _cellClass = cellClass;
268: }
269:
270: /**
271: * Set the HTML style class that is rendered on each HTML table cell that
272: * is opened by this tag. The starting cell is alternated for each row, which
273: * results in a checkerboard colored table being displayed. For example, if the
274: * alteranting cell class is \"alternatingCellClass\", every other table cell
275: * tag is:
276: * <pre>
277: * <td cell="alternatingCellClass">
278: * </pre>
279: *
280: * @param alternatingCellClass the name of a style class in a CSS
281: * @jsptagref.attributedescription
282: * The HTML style class that is rendered on alternating table cells.
283: * The starting cell is alternated for each row, which
284: * results in a checkerboard colored table being displayed. For example, if the
285: * alteranting cell class is "alternatingCellClass", every other table cell
286: * tag is:
287: * <pre>
288: * <td cell="alternatingCellClass"></pre>
289: * @jsptagref.attributesyntaxvalue <i>string_class</i>
290: * @netui:attribute required="false"
291: */
292: public void setAlternatingCellClass(String alternatingCellClass) {
293: if ("".equals(alternatingCellClass))
294: return;
295: _altCellClass = alternatingCellClass;
296: }
297:
298: /**
299: * This tag can render the items in its dataset horizontally or vertically. If
300: * the rows are rendered horizontally, the items in the dataset are rendered
301: * across each row from top to bottom. Otherwise, they are rendered down each
302: * column from left to right. The default is to render the items horizontally.
303: *
304: * @param verticalRepeat if set to <code>true</code>, the dataset is rendered down
305: * each column; otherwise it is rendered across each row, the default.
306: * @jsptagref.attributedescription
307: * Boolean. If true the data set is rendered vertically, otherwise it is rendered horizontally. If
308: * the rows are rendered horizontally, the items in the data set are rendered
309: * across each row from top to bottom. Otherwise, they are rendered down each
310: * column from left to right. The default is to render the items horizontally.
311: * @jsptagref.attributesyntaxvalue <i>boolean_verticalRepeat</i>
312: * @netui:attribute required="false"
313: */
314: public void setVerticalRepeat(boolean verticalRepeat) {
315: _verticalRepeat = verticalRepeat;
316: }
317:
318: /**
319: * Set the number of columns that should be rendered in the table
320: * generated by the tag. If the columns attribute is specified but
321: * the rows attribute is not, the rows attribute will be inferred
322: * using the size of the dataset.
323: *
324: * @param columns an integer or an expression
325: * @jsptagref.attributedescription
326: * Integer. The number of columns that should be rendered in the HTML table.
327: * If the <code>columns</code> attribute is specified but
328: * the <code>rows</code> attribute is not, the <code>rows</code> attribute will be inferred
329: * using the size of the data set.
330: * @jsptagref.attributesyntaxvalue <i>integer_columns</i>
331: * @netui:attribute required="false" rtexprvalue="true"
332: */
333: public void setColumns(int columns) {
334: _columns = columns;
335: }
336:
337: /**
338: * Set the number of rows that should be rendered in the table
339: * generated by the tag. If the rows attribute is specified but
340: * the columns attribute is not, the columns attribute will be
341: * inferred using the size of the dataset.
342: *
343: * @param rows an integer or an expression whose value can be
344: * converted into an integer.
345: * @jsptagref.attributedescription
346: * Integer. The number of rows that should be rendered in the HTML table.
347: * If the <code>rows</code> attribute is specified but
348: * the <code>columns</code> attribute is not, the <code>columns</code> attribute will be
349: * inferred using the size of the data set.
350: * @jsptagref.attributesyntaxvalue <i>integer_rows</i>
351: * @netui:attribute required="false" rtexprvalue="true"
352: */
353: public void setRows(int rows) {
354: _rows = rows;
355: }
356:
357: /**
358: * <p>The <code>dataSource</code> attribute determines both
359: * (1) the source of populating data for the tag and
360: * (2) the object to which the tag submits data.
361: *
362: * <p>For example, assume that the Controller file (= JPF file) contains
363: * a Form Bean with the property foo. Then the following <netui:textBox> tag will
364: * (1) draw populating data from the Form Bean's foo property and (2)
365: * submit user defined data to the same property.
366: *
367: * <p> <code><netui:textBox dataSource="actionForm.foo" /></code>
368: *
369: * <p>When the tag is used to submit data, the data binding expression must
370: * refer to a Form Bean property.
371: * In cases where the tag is not used to submit data, but is used for
372: * displaying data only, the data
373: * binding expression need not refer to a Form Bean property. For example,
374: * assume that myIterativeData is a member variable on
375: * the Controller file ( = JPF file). The following <netui-data:repeater>
376: * tag draws its data from myIterativeData.
377:
378: * @param dataSource the data source
379: * @jsptagref.attributedescription
380: * <p>The <code>dataSource</code> attribute determines both
381: * (1) the source of populating data for the tag and
382: * (2) the object to which the tag submits data.
383: *
384: * <p>For example, assume that the Controller file (= JPF file) contains
385: * a Form Bean with the property foo. Then the following <netui:textBox> tag will
386: * (1) draw populating data from the Form Bean's foo property and (2)
387: * submit user defined data to the same property.
388: *
389: * <p> <code><netui:textBox dataSource="actionForm.foo" /></code>
390: *
391: * <p>When the tag is used to submit data, the data binding expression must
392: * refer to a Form Bean property.
393: * In cases where the tag is not used to submit data, but is used for
394: * displaying data only, the data
395: * binding expression need not refer to a Form Bean property. For example,
396: * assume that myIterativeData is a member variable on
397: * the Controller file ( = JPF file). The following <netui-data:repeater>
398: * tag draws its data from myIterativeData.
399: *
400: * <p> <code><netui-data:cellRepeater dataSource="pageFlow.myIterativeData"></code>
401: * @jsptagref.attributesyntaxvalue <i>expression_datasource</i>
402: * @netui:attribute required="true"
403: */
404: public void setDataSource(String dataSource) {
405: _dataSource = dataSource;
406: }
407:
408: /**
409: * Prepare to render the dataset that was specified in the dataSource attribute. The
410: * dataSource expression is evaluated and the table's dimensions are computed. If
411: * there is no data in the dataset but the rows and columns attributes were specified,
412: * an empty table of the given dimensions is rendered.
413: *
414: * @return EVAL_BODY_BUFFERED or SKIP_BODY if errors are reported, the data set
415: * is null, or there is no data in the data set
416: * @throws JspException if errors occurred that could not be reported in the page
417: */
418: public int doStartTag() throws JspException {
419: ServletRequest request = pageContext.getRequest();
420:
421: _tableRenderer = TagRenderingBase.Factory.getRendering(
422: TagRenderingBase.TABLE_TAG, request);
423: _trRenderer = TagRenderingBase.Factory.getRendering(
424: TagRenderingBase.TR_TAG, request);
425: _tdRenderer = TagRenderingBase.Factory.getRendering(
426: TagRenderingBase.TD_TAG, request);
427: _htmlConstantRendering = TagRenderingBase.Factory
428: .getConstantRendering(request);
429:
430: _sb = new InternalStringBuilder(1024);
431: _appender = new StringBuilderRenderAppender(_sb);
432:
433: Object source = evaluateDataSource();
434:
435: if (hasErrors())
436: return SKIP_BODY;
437:
438: if (source != null) {
439: Iterator iterator = IteratorFactory.createIterator(source);
440: if (iterator == null) {
441: LOGGER
442: .info("CellRepeater: The data structure from which to create an iterator is null.");
443: iterator = Collections.EMPTY_LIST.iterator();
444: }
445:
446: if (iterator != null) {
447: _dataList = new ArrayList();
448: while (iterator.hasNext()) {
449: _dataList.add(iterator.next());
450: }
451: }
452: }
453:
454: if (_rows == DIMENSION_DEFAULT_VALUE
455: || _columns == DIMENSION_DEFAULT_VALUE) {
456: /* try to guess the dimensions of the table */
457: if (_dataList != null && _dataList.size() > 0) {
458: guessDimensions(_dataList);
459:
460: if (hasErrors())
461: return SKIP_BODY;
462: }
463: /* the size of the data set isn't guessable */
464: else {
465: _valid = false;
466: return SKIP_BODY;
467: }
468: }
469:
470: /* check to make sure the rows / columns are actually valid before starting to render */
471: if (_rows <= 0) {
472: String msg = Bundle.getString(
473: "Tags_CellRepeater_invalidRowValue", new Object[] {
474: getTagName(), new Integer(_rows) });
475: registerTagError(msg, null);
476: }
477:
478: if (_columns <= 0) {
479: String msg = Bundle
480: .getString("Tags_CellRepeater_invalidColumnValue",
481: new Object[] { getTagName(),
482: new Integer(_columns) });
483: registerTagError(msg, null);
484: }
485:
486: if (hasErrors())
487: return SKIP_BODY;
488:
489: openTableTag(_appender, _tableState);
490:
491: _currentRow = 0;
492: _currentColumn = 0;
493:
494: DataAccessProviderStack
495: .addDataAccessProvider(this , pageContext);
496: _containerInPageContext = true;
497:
498: boolean haveItem = ensureItem(0, _dataList);
499: if (haveItem) {
500: openRowTag(_appender, _trState);
501: openCellTag(_appender, _currentColumn);
502: return EVAL_BODY_BUFFERED;
503: } else {
504: // special case -- with no items, render the entire table here
505: for (int i = 0; i < _rows; i++) {
506: openRowTag(_appender, _trState);
507: for (int j = 0; j < _columns; j++) {
508: openCellTag(_appender, computeStyleIndex(i, j));
509: _htmlConstantRendering.NBSP(_appender);
510: closeCellTag(_appender);
511: }
512: closeRowTag(_appender);
513: _appender.append("\n");
514: }
515: _currentRow = _rows;
516: _currentColumn = _columns;
517: return SKIP_BODY;
518: }
519: }
520:
521: /**
522: * Continue rendering the body of this tag until the dimensions of the table have been reached or
523: * the entire dataset has been rendered. The buffered body content from the previous iteration
524: * of the body is added to the content this tag will render, @see addContent(java.lang.String).
525: * Pad the table if the dimensions have not been met but the dataset is empty.
526: *
527: * @return EVAL_BODY_BUFFERED if there is more data to render in the dataset or
528: * SKIP_BODY if the end of the dataset is reached or an error occurs
529: */
530: public int doAfterBody() {
531: if (bodyContent != null) {
532: _appender.append(bodyContent.getString());
533: bodyContent.clearBody();
534: }
535:
536: /*
537: this loop exists so that the table is filled out correctly up to the specified
538: or guessed table dimensions. this is a little bit of a kludge; this logic should be done
539: in doEndTag()
540: */
541: boolean haveNext = false;
542: while (!haveNext) {
543: _currentColumn++;
544:
545: /* close the previous cell whose content was rendered the last time the tag body was executed */
546: closeCellTag(_appender);
547:
548: /* open a new table row */
549: if (_currentColumn == _columns) {
550: _currentRow++;
551: _currentColumn = 0;
552: closeRowTag(_appender);
553: _appender.append("\n");
554: }
555:
556: /* reached the end of the table as the current row is now equal to the total number of rows */
557: if (_currentRow == _rows)
558: return SKIP_BODY;
559:
560: if (_currentColumn == 0)
561: openRowTag(_appender, _trState != null ? _trState
562: : STATE_TR);
563:
564: int itemIndex = -1;
565: if (_verticalRepeat)
566: itemIndex = _currentColumn * _rows + _currentRow;
567: else
568: itemIndex = _currentRow * _columns + _currentColumn;
569:
570: haveNext = ensureItem(itemIndex, _dataList);
571:
572: openCellTag(_appender, computeStyleIndex(_currentRow,
573: _currentColumn));
574:
575: /* render empty cell and continue filling the table */
576: if (!haveNext)
577: _htmlConstantRendering.NBSP(_appender);
578: /* open a new table cell and render the body once again. note, this exits the while loop above */
579: else
580: return EVAL_BODY_AGAIN;
581: }
582:
583: /* default is to skip the tag body */
584: return SKIP_BODY;
585: }
586:
587: /**
588: * Complete rendering the tag. If no errors have occurred, the content that
589: * the tag buffered is rendered.
590: *
591: * @return EVAL_PAGE to continue evaluating the page
592: * @throws JspException if an error occurs that can not be reported on the page
593: */
594: public int doEndTag() throws JspException {
595: if (hasErrors())
596: reportErrors();
597: else if (_valid) {
598: closeTableTag(_appender);
599: write(_sb.toString());
600: }
601:
602: return EVAL_PAGE;
603: }
604:
605: public void doFinally() {
606: localRelease();
607: }
608:
609: public void doCatch(Throwable t) throws Throwable {
610: throw t;
611: }
612:
613: /**
614: * Gets the tag's data source (can be an expression).
615: * @return the data source
616: */
617: public String getDataSource() {
618: return "{" + _dataSource + "}";
619: }
620:
621: /**
622: * Get the index of the current iteration through the body of this tag. This
623: * data can be accessed using the expression <code>container.index</code>
624: * on an attribute of a databindable NetUI tag that is contained within the
625: * repeating body of this tag. This expression is only valid when the dataset
626: * is being rendered.
627: *
628: * @return the integer index of the current data item in the data set
629: * @see org.apache.beehive.netui.script.common.IDataAccessProvider
630: */
631: public int getCurrentIndex() {
632: return _currentIndex;
633: }
634:
635: /**
636: * Get the item that is currently being rendered by this repeating tag.
637: * This can be accessed using the expression <code>expression.item</code>
638: * on an attribute of a databindable netUI tag that is contained within
639: * the repeating body of this tag. The expression is only valid when the dataset
640: * is being rendered.
641: *
642: * @return the current item in the data set
643: * @see org.apache.beehive.netui.script.common.IDataAccessProvider
644: */
645: public Object getCurrentItem() {
646: return _currentItem;
647: }
648:
649: /**
650: * Get the metadata for the current item. This method is not supported by
651: * this tag.
652: *
653: * @throws UnsupportedOperationException this tag does not support this method from the IDataAccessProvider interface
654: * @see org.apache.beehive.netui.script.common.IDataAccessProvider
655: */
656: public Object getCurrentMetadata() {
657: LocalizedUnsupportedOperationException uoe = new LocalizedUnsupportedOperationException(
658: "The "
659: + getTagName()
660: + "does not export metadata for its iterated items.");
661: uoe.setLocalizedMessage(Bundle.getErrorString(
662: "Tags_DataAccessProvider_metadataUnsupported",
663: new Object[] { getTagName() }));
664: throw uoe;
665: }
666:
667: /**
668: * Get the parent IDataAccessProvider for this tag. If this tag is contained within
669: * a IDataAccessProvider, the containing IDataAccessProvider is available through the
670: * expression <code>container.container</code>. Any valid properties of the
671: * parent IDataAccessProvider can be accessed through this expression. This method
672: * will return null if there is no parent IDataAccessProvider
673: *
674: * @return a containing IDataAccessProvider if one exists, null otherwise.
675: * @see org.apache.beehive.netui.script.common.IDataAccessProvider
676: */
677: public IDataAccessProvider getProviderParent() {
678: return (IDataAccessProvider) SimpleTagSupport
679: .findAncestorWithClass(this , IDataAccessProvider.class);
680: }
681:
682: /**
683: * Return an <code>ArrayList</code> which represents a chain of <code>INameInterceptor</code>
684: * objects. This method by default returns <code>null</code> and should be overridden
685: * by objects that support naming.
686: * @return an <code>ArrayList</code> that will contain <code>INameInterceptor</code> objects.
687: */
688: protected List getNamingChain() {
689: return AbstractClassicTag.DefaultNamingChain;
690: }
691:
692: /**
693: * Reset all of the fields of this tag.
694: */
695: protected void localRelease() {
696: super .localRelease();
697:
698: if (bodyContent != null)
699: bodyContent.clearBody();
700:
701: _rows = DIMENSION_DEFAULT_VALUE;
702: _columns = DIMENSION_DEFAULT_VALUE;
703: _currentRow = -1;
704: _currentColumn = -1;
705: _currentIndex = -1;
706: _verticalRepeat = false;
707: _dataList = null;
708: _currentItem = null;
709: _valid = true;
710: _dataSource = null;
711:
712: if (_tdState != null)
713: _tdState.clear();
714: if (_trState != null)
715: _trState.clear();
716: if (_tableState != null)
717: _tableState.clear();
718:
719: _tableRenderer = null;
720: _tdRenderer = null;
721: _trRenderer = null;
722:
723: _sb = null;
724: _appender = null;
725:
726: if (_containerInPageContext) {
727: DataAccessProviderStack
728: .removeDataAccessProvider(pageContext);
729: _containerInPageContext = false;
730: }
731: }
732:
733: private final void guessDimensions(ArrayList data)
734: throws JspException {
735:
736: if (_rows == 0 || _columns == 0)
737: registerTagError(
738: Bundle
739: .getString("Tags_CellRepeater_missingRowsOrColumns"),
740: null);
741:
742: if (data == null)
743: return;
744:
745: int dataSize = data.size();
746: if (_rows == DIMENSION_DEFAULT_VALUE
747: && _columns == DIMENSION_DEFAULT_VALUE) {
748: registerTagError(Bundle
749: .getString("Tags_CellRepeater_invalidRowOrColumn"),
750: null);
751: } else if (_rows == DIMENSION_DEFAULT_VALUE) {
752: int remainder = dataSize % _columns;
753: _rows = (dataSize / _columns) + (remainder > 0 ? 1 : 0);
754: LOGGER.debug("guessed row size: " + _rows);
755: } else if (_columns == DIMENSION_DEFAULT_VALUE) {
756: int remainder = dataSize % _rows;
757: _columns = (dataSize / _rows) + (remainder > 0 ? 1 : 0);
758: LOGGER.debug("guessed column size: " + _columns);
759: }
760: }
761:
762: private void openTableTag(AbstractRenderAppender appender,
763: TableTag.State tableState) {
764: if (tableState == null)
765: tableState = STATE_TABLE;
766: _tableRenderer.doStartTag(appender, tableState);
767: }
768:
769: private void closeTableTag(AbstractRenderAppender appender) {
770: assert appender != null;
771: assert _tableRenderer != null;
772: _tableRenderer.doEndTag(appender);
773: }
774:
775: private void openRowTag(AbstractRenderAppender appender,
776: TrTag.State trState) {
777: if (trState == null)
778: trState = STATE_TR;
779: _trRenderer.doStartTag(appender, trState);
780: }
781:
782: private void closeRowTag(AbstractRenderAppender appender) {
783: assert _trRenderer != null;
784: assert appender != null;
785: _trRenderer.doEndTag(appender);
786: }
787:
788: private void openCellTag(AbstractRenderAppender appender, int index) {
789: assert appender != null;
790: assert index >= 0;
791: assert _tdRenderer != null;
792:
793: TdTag.State tdState = STATE_TD;
794: if (_cellClass != null) {
795: if (_tdState != null)
796: _tdState.clear();
797: else
798: _tdState = new TdTag.State();
799: if (index % 2 == 0)
800: _tdState.styleClass = _cellClass;
801: else
802: _tdState.styleClass = (_altCellClass != null ? _altCellClass
803: : _cellClass);
804: tdState = _tdState;
805: }
806:
807: _tdRenderer.doStartTag(appender, tdState);
808: }
809:
810: private void closeCellTag(AbstractRenderAppender appender) {
811: assert _tdRenderer != null;
812: assert appender != null;
813: _tdRenderer.doEndTag(appender);
814: }
815:
816: private int computeStyleIndex(int r, int c) {
817: return c + (r % 2);
818: }
819:
820: private boolean ensureItem(int index, ArrayList data) {
821: LOGGER.debug("item: "
822: + 0
823: + " data: "
824: + (data == null ? "null data"
825: : (index < data.size() ? "" + index
826: : "index out of bounds for size "
827: + data.size())));
828:
829: if (data != null && index < data.size()) {
830: _currentItem = data.get(index);
831: _currentIndex = index;
832: return true;
833: } else
834: return false;
835: }
836:
837: /**
838: * Return the Object that is represented by the specified data source.
839: * @return Object
840: * @throws JspException
841: */
842: private Object evaluateDataSource() throws JspException {
843: ExpressionHandling expr = new ExpressionHandling(this );
844: String dataSource = getDataSource();
845: String ds = expr.ensureValidExpression(dataSource,
846: "dataSource", "DataSourceError");
847: if (ds == null)
848: return null;
849:
850: Object o = expr.evaluateExpression(dataSource, "dataSource",
851: pageContext);
852: return o;
853: }
854: }
|