/*
* 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.
*/
package org.apache.myfaces.trinidadinternal.renderkit.core.pda;
import java.io.IOException;
import java.util.List;
import javax.faces.component.UIComponent;
import javax.faces.context.FacesContext;
import javax.faces.context.ResponseWriter;
import org.apache.myfaces.trinidad.bean.FacesBean;
import org.apache.myfaces.trinidad.component.CollectionComponent;
import org.apache.myfaces.trinidad.component.UIXColumn;
import org.apache.myfaces.trinidad.component.UIXTable;
import org.apache.myfaces.trinidad.component.core.data.CoreColumn;
import org.apache.myfaces.trinidad.component.core.data.CoreTable;
import org.apache.myfaces.trinidad.context.RenderingContext;
import org.apache.myfaces.trinidadinternal.renderkit.core.xhtml.OutputUtils;
import org.apache.myfaces.trinidadinternal.renderkit.core.xhtml.SkinSelectors;
import org.apache.myfaces.trinidadinternal.renderkit.core.xhtml.TableRenderer;
import org.apache.myfaces.trinidadinternal.renderkit.core.xhtml.XhtmlConstants;
import org.apache.myfaces.trinidadinternal.renderkit.core.xhtml.table.BandingData;
import org.apache.myfaces.trinidadinternal.renderkit.core.xhtml.table.ColumnData;
import org.apache.myfaces.trinidadinternal.renderkit.core.xhtml.table.RenderStage;
import org.apache.myfaces.trinidadinternal.renderkit.core.xhtml.table.RowData;
import org.apache.myfaces.trinidadinternal.renderkit.core.xhtml.table.TableRenderingContext;
import org.apache.myfaces.trinidadinternal.renderkit.core.xhtml.table.TableUtils;
import org.apache.myfaces.trinidad.util.IntegerUtils;
public class PdaTableRenderer extends TableRenderer
{
/**
*/
public PdaTableRenderer()
{
super(CoreTable.TYPE);
}
/**
* renders attributes on the outermost table element.
* this includes width, cellpadding, cellspacing, border.
*/
@Override
protected void renderTableAttributes(
FacesContext context,
RenderingContext arc,
UIComponent component,
FacesBean bean,
Object cellPadding,
Object border
) throws IOException
{
Object width = getWidth(bean);
// On mobile devices, table width is set to full browser width unless
// the width is specified.
if (width == null || width == "")
{
width = "100%";
}
OutputUtils.renderLayoutTableAttributes(context,
arc,
cellPadding,
"0", // cell spacing
border,
width); // table width
}
@Override
protected final void renderControlBar(
FacesContext context,
RenderingContext arc,
TableRenderingContext tContext,
UIComponent component)
throws IOException
{
UIComponent action = getFacet(component, CoreTable.ACTIONS_FACET);
boolean tableNotEmpty = !tContext.getRowData().isEmptyTable();
boolean hasNav = tContext.hasNavigation() && tableNotEmpty;
if (hasNav || (action != null))
{
ResponseWriter writer = context.getResponseWriter();
// start control bar row
writer.startElement("tr", null);
writer.startElement("td", null);
// start control bar
writer.startElement("div", null);
renderStyleClass(context, arc,
SkinSelectors.AF_TABLE_CONTROL_BAR_TOP_STYLE);
if (action != null)
{
writer.startElement(XhtmlConstants.TABLE_DATA_ELEMENT, null);
encodeChild(context, action);
writer.endElement(XhtmlConstants.TABLE_DATA_ELEMENT);
}
if ( hasNav)
{
writer.startElement("div", null);
/*
if (arc.isRightToLeft())
writer.writeAttribute("align", "left", null);
else
writer.writeAttribute("align", "right", null);
*/
// =-=AEW Is "valign" even a real attr for divs???
writer.writeAttribute("valign", "middle", null);
delegateRenderer(context, arc, component,
getFacesBean(component), getSharedNavBarRenderer());
writer.endElement("div");
}
writer.endElement("div");
// end control bar row
writer.endElement("td");
writer.endElement("tr");
}
}
@Override
protected void renderSubControlBar(
FacesContext context,
RenderingContext arc,
TableRenderingContext tContext,
UIComponent component,
boolean isUpper) throws IOException
{
// No-op.
}
private void _renderEmptyCell(
FacesContext context,
RenderingContext arc,
TableRenderingContext tContext,
boolean isSelect,
Object emptyText) throws IOException
{
ResponseWriter writer = context.getResponseWriter();
writer.startElement("td", null);
String cellClass = _getCellFormat(tContext, isSelect);
renderStyleClass(context, arc, cellClass);
if (emptyText == null)
emptyText = XhtmlConstants.NBSP_STRING;
writer.writeText(emptyText, null);
// end the cell
writer.endElement("td");
}
// render the actual table content, with headers
@Override
protected void renderTableContent(
FacesContext context,
final RenderingContext arc,
final TableRenderingContext tContext,
final UIComponent component) throws IOException
{
ResponseWriter writer = context.getResponseWriter();
FacesBean bean = getFacesBean(component);
//
// no nested tables, so end the previous table and start a
// new one
//
writer.endElement("table");
//
// start the content table with the same attributes as the title table
//
writer.startElement("table", component);
renderTableAttributes(context, arc, component, bean,
"2", "1");
//
// write out the grid color as the border color for the grid lines
// and border of the table
//
renderStyleClass(context, arc, SkinSelectors.AF_TABLE_CONTENT_STYLE);
//
// 1. Gather all the data we need to render
//
final RowData rowData = tContext.getRowData();
boolean isEmptyTable = rowData.isEmptyTable();
final UIComponent detail = tContext.getDetail();
//
// 2. Render the top / column header
//
_renderTableHeader(context, arc, tContext, component);
// render the column header
if (tContext.hasColumnHeaders())
_renderColumnHeader(context, arc, tContext, component);
//
// 3. Render each row
//
ColumnData colData = tContext.getColumnData();
final RenderStage renderStage = tContext.getRenderStage();
renderStage.setStage(RenderStage.DATA_STAGE);
// use the special response writer in our data section that
// defaults data cells with no data to <br>
//tContext.setDataResponseWriterUsed(true);
int physicalCol = 0;
if (isEmptyTable)
{
writer.startElement("tr", null);
if (tContext.hasSelection())
{
colData.setColumnIndex(physicalCol++, ColumnData.SPECIAL_COLUMN_INDEX);
_renderEmptyCell(context, arc, tContext, true, null);
}
// render detail control (hide/show for the row)
if (detail != null)
{
colData.setColumnIndex(physicalCol++, ColumnData.SPECIAL_COLUMN_INDEX);
_renderEmptyCell(context, arc, tContext, true, null);
}
int objectNameColumnIndex = colData.getObjectNameColumnIndex();
if (objectNameColumnIndex < physicalCol)
objectNameColumnIndex = physicalCol;
for (int columns = colData.getColumnCount(); physicalCol < columns;)
{
colData.setColumnIndex(physicalCol, ColumnData.SPECIAL_COLUMN_INDEX);
final Object emptyText;
if (objectNameColumnIndex == physicalCol)
{
emptyText = getEmptyText(bean);
}
else
{
emptyText = null;
}
_renderEmptyCell(context, arc, tContext, false, //isSelect
emptyText);
physicalCol++;
}
writer.endElement("tr");
}
else //not an empty table
{
TableUtils.RowLoop loop = new TableUtils.RowLoop()
{
@Override
protected void processRowImpl(FacesContext fc, CollectionComponent tableBase)
throws IOException
{
ResponseWriter rw = fc.getResponseWriter();
// compute all the rowSpans for the current row:
rowData.setCurrentRowSpan(-1); //reset
renderStage.setStage(RenderStage.START_ROW_STAGE);
renderSingleRow(fc, arc, tContext, component);
renderStage.setStage(RenderStage.DATA_STAGE);
// render each of the individual rows in the rowSpan:
for(int i=0, sz=rowData.getCurrentRowSpan(); i<sz; i++)
{
// start the row
rw.startElement("tr", null);
renderSingleRow(fc, arc, tContext, component);
rowData.incCurrentSubRow();
// end the row
rw.endElement("tr");
}
// if necessary, render a detail row
if ((detail != null) &&
((UIXTable) tableBase).getDisclosedRowKeys().isContained())
{
renderStage.setStage(RenderStage.DETAIL_ROW_STAGE);
rw.startElement("tr", null);
rw.startElement("td", null);
rw.writeAttribute("colspan",
IntegerUtils.getString(tContext.getActualColumnCount()),
null);
// out.writeAttribute(CLASS_ATTRIBUTE, TABLE_DETAIL_STYLE);
encodeChild(fc, detail);
rw.endElement("td");
rw.endElement("tr");
// restore the data stage
renderStage.setStage(RenderStage.DATA_STAGE);
}
}
};
loop.run(context, tContext.getCollectionComponent());
}
// render the column footer
_renderColumnFooter(context,arc,tContext,component);
// we're done with the defaulting data response writer
//context.setDataResponseWriterUsed(false);
}
@SuppressWarnings("unchecked")
private void _renderColumnFooter(
FacesContext context,
RenderingContext arc,
TableRenderingContext tContext,
UIComponent component) throws IOException
{
tContext.getRenderStage().setStage(RenderStage.COLUMN_FOOTER_STAGE);
final ColumnData colData = tContext.getColumnData();
UIComponent footer = getFacet(component, CoreTable.FOOTER_FACET);
if (footer != null)
{
ResponseWriter writer = context.getResponseWriter();
writer.startElement(XhtmlConstants.TABLE_ROW_ELEMENT, null);
/* boolean useScroll = (getHeight(getFacesBean(component)) != null) && isIE(arc);
if (useScroll)
{
writer.writeAttribute("style", "position:relative;"+
"bottom:expression("+
"this.offsetParent.scrollHeight-this.offsetParent.scrollTop-"+
"this.offsetParent.clientHeight+1);" +
"left:-1px", null);
}
*/
writer.startElement(XhtmlConstants.TABLE_HEADER_ELEMENT, null);
// total rows may need an ID. see bug 3211593:
/* Need new scheme for generateUniqueId()?
String rowID = XhtmlLafUtils.generateUniqueID(tContext);
writer.writeAttribute(XhtmlLafConstants.ID_ATTRIBUTE, rowID, null);
tContext.getRowData().setCurrentRowHeaderID(rowID);
*/
final int firstFooterPhysicalIndex = colData.getPhysicalIndexOfFirstFooter();
final int colSpan = (firstFooterPhysicalIndex > 0)? firstFooterPhysicalIndex: tContext.getActualColumnCount();
writer.writeAttribute(XhtmlConstants.COLSPAN_ATTRIBUTE, IntegerUtils.getString(colSpan), null);
renderStyleClass(context, arc, SkinSelectors.AF_TABLE_COLUMN_FOOTER_STYLE);
encodeChild(context, footer);
writer.endElement(XhtmlConstants.TABLE_HEADER_ELEMENT);
if (firstFooterPhysicalIndex > 0)
{
colData.setColumnIndex(tContext.getSpecialColumnCount(),
0/*logicalColumnIndex*/);
for(UIComponent child : (List<UIComponent>)component.getChildren())
{
if (child.isRendered())
{
encodeChild(context, child);
}
}
}
writer.endElement(XhtmlConstants.TABLE_ROW_ELEMENT);
}
}
@Override
protected final void renderSingleRow(
FacesContext context,
RenderingContext arc,
TableRenderingContext tContext,
UIComponent component) throws IOException
{
int stage = tContext.getRenderStage().getStage();
switch(stage)
{
case RenderStage.COLUMN_HEADER_STAGE:
return;
case RenderStage.INITIAL_STAGE:
case RenderStage.DATA_STAGE:
case RenderStage.END_STAGE:
case RenderStage.START_ROW_STAGE:
break;
default:
throw new AssertionError("Bad renderStage:"+stage);
}
ColumnData colData = tContext.getColumnData();
int[] hidden = tContext.getHiddenColumns();
int columns = tContext.getColumnCount();
// render the special columns, such as selection and details:
int physicalColumn = renderSpecialColumns(context,
arc,
tContext,
component,
0);
for (int currCol = 0; currCol < columns; currCol++)
{
if (hidden[currCol] == TableRenderingContext.NORMAL_COLUMN)
{
UIComponent child =
(UIComponent) component.getChildren().get(currCol);
if (!(child instanceof UIXColumn))
continue;
UIXColumn column = (UIXColumn) child;
boolean isRowHeader = Boolean.TRUE.equals(
column.getAttributes().get(CoreColumn.ROW_HEADER_KEY.getName()));
if (!isRowHeader)
{
colData.setColumnIndex(physicalColumn,currCol);
encodeChild(context, column);
// ColumnBeans automatically increment the physical and logical
// column indices (these may be increase by more than one, if
// there are columnGroups). So we must not increment the column
// indices here
physicalColumn = colData.getPhysicalColumnIndex();
}
}
}
}
// render the complete column header
private void _renderColumnHeader(
FacesContext context,
RenderingContext arc,
TableRenderingContext tContext,
UIComponent component) throws IOException
{
tContext.getRenderStage().setStage(RenderStage.COLUMN_HEADER_STAGE);
ResponseWriter writer = context.getResponseWriter();
ColumnData colData = tContext.getColumnData();
colData.setRowIndex(0);
writer.startElement("tr", null);
int physicalCol = renderSpecialColumns(context, arc, tContext, component, 0);
int[] hidden = tContext.getHiddenColumns();
int colCount = component.getChildCount();
for (int j = 0; j < colCount; j++)
{
if (hidden[j] != TableRenderingContext.NORMAL_COLUMN)
continue;
UIComponent child =
(UIComponent) component.getChildren().get(j);
if (!(child instanceof UIXColumn))
continue;
UIXColumn column = (UIXColumn) child;
boolean isRowHeader = Boolean.TRUE.equals(
column.getAttributes().get(CoreColumn.ROW_HEADER_KEY.getName()));
if (!isRowHeader)
{
colData.setColumnIndex(physicalCol, j);
encodeChild(context, column);
// ColumnBeans automatically increment the physical and logical
// column indices (these may be increase by more than one, if
// there are columnGroups). So we must not increment the column
// indices here
physicalCol = colData.getPhysicalColumnIndex();
}
}
colData.setRowIndex(-1);
writer.endElement("tr");
}
// get the style class for the current cell
private String _getCellFormat(
TableRenderingContext tContext,
boolean isSelect
) throws IOException
{
ColumnData colData = tContext.getColumnData();
RowData rowData = tContext.getRowData();
int row = rowData.getRangeIndex();
int physicalColumn = colData.getPhysicalColumnIndex();
int logicalColumn = colData.getLogicalColumnIndex();
BandingData bandingData = tContext.getBanding();
boolean band = bandingData.getBand(tContext, row,
physicalColumn,
logicalColumn);
//
// determine the cell class
//
String cellClass;
if (band)
{
if (isSelect)
cellClass = SkinSelectors.TABLE_BAND_SELECT_CELL_STYLE;
else
{
cellClass = ColumnData.selectFormat(tContext,
SkinSelectors.AF_COLUMN_CELL_TEXT_BAND_STYLE,
SkinSelectors.AF_COLUMN_CELL_NUMBER_BAND_STYLE,
SkinSelectors.AF_COLUMN_CELL_ICON_BAND_STYLE);
}
}
else
{
if (isSelect)
cellClass = SkinSelectors.TABLE_SELECT_CELL_STYLE;
else
{
cellClass = ColumnData.selectFormat(tContext,
SkinSelectors.AF_COLUMN_CELL_TEXT_STYLE,
SkinSelectors.AF_COLUMN_CELL_NUMBER_STYLE,
SkinSelectors.AF_COLUMN_CELL_ICON_FORMAT_STYLE);
}
}
return cellClass;
}
/**
* @todo Reconsider our choice of style for this element!
*/
private void _renderTableHeader(
FacesContext context,
RenderingContext arc,
TableRenderingContext tContext,
UIComponent component)
throws IOException
{
// implement header facet on table: see bug 3788610
ResponseWriter writer = context.getResponseWriter();
UIComponent header = getFacet(component, CoreTable.HEADER_FACET);
if (header != null)
{
writer.startElement("thead", null);
writer.startElement(XhtmlConstants.TABLE_ROW_ELEMENT, null);
writer.startElement(XhtmlConstants.TABLE_DATA_ELEMENT, null);
writer.writeAttribute(XhtmlConstants.COLSPAN_ATTRIBUTE,
tContext.getActualColumnCount(), null);
renderStyleClass(context, arc, SkinSelectors.AF_COLUMN_SORTABLE_HEADER_ICON_STYLE_CLASS);
encodeChild(context, header);
writer.endElement(XhtmlConstants.TABLE_DATA_ELEMENT);
writer.endElement(XhtmlConstants.TABLE_ROW_ELEMENT);
writer.endElement("thead");
}
}
}