/*
* 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.desktop;
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.bean.PropertyKey;
import org.apache.myfaces.trinidad.component.CollectionComponent;
import org.apache.myfaces.trinidad.component.UIXCollection;
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.trinidadinternal.io.RepeatIdResponseWriter;
import org.apache.myfaces.trinidad.context.RenderingContext;
import org.apache.myfaces.trinidad.render.CoreRenderer;
import org.apache.myfaces.trinidadinternal.renderkit.core.xhtml.OutputUtils;
import org.apache.myfaces.trinidadinternal.renderkit.core.xhtml.ShowDetailRenderer;
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.XhtmlUtils;
import org.apache.myfaces.trinidadinternal.renderkit.core.xhtml.table.CellUtils;
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.TableSelectManyRenderer;
import org.apache.myfaces.trinidadinternal.renderkit.core.xhtml.table.TableUtils;
import org.apache.myfaces.trinidadinternal.renderkit.core.xhtml.table.TreeUtils;
import org.apache.myfaces.trinidad.util.IntegerUtils;
public class DesktopTableRenderer extends TableRenderer
{
/**
* @todo Figure out if "height" is really being used; it's
* not exposed on our tag, but it might be a "hidden" feature
* =-= awijeyek =-= height is used for server-side scrollable tables for ECM,
* but we don't support it beyond what is needed by ECM.
*/
protected DesktopTableRenderer(FacesBean.Type type)
{
super(type);
}
@Override
protected void findTypeConstants(FacesBean.Type type)
{
super.findTypeConstants(type);
_summaryKey = type.findKey("summary");
_heightKey = type.findKey("height");
// Since height isn't really exposed, we won't really
// have a key for it...
if (_heightKey == null)
_heightKey = PropertyKey.createPropertyKey("height");
_allDetailsEnabledKey = type.findKey("allDetailsEnabled");
_allDisclosed = new AllDetail(type, true);
_allUndisclosed = new AllDetail(type, false);
_autoSubmitKey = type.findKey("autoSubmit");
}
public DesktopTableRenderer()
{
this(CoreTable.TYPE);
}
@Override
protected final void renderSingleRow(
FacesContext context,
RenderingContext arc,
TableRenderingContext tContext,
UIComponent component) throws IOException
{
// This renders a whole bunch of <TH>..</TH> elements or <TD>..</TD>
// elements depending on the RenderStage
RenderStage renderStage = tContext.getRenderStage();
int stage = renderStage.getStage();
if (stage == RenderStage.COLUMN_HEADER_STAGE)
{
renderColumnHeader(context, arc, tContext, component);
return;
}
// render the special columns, such as selection and details:
int physicalColumn = renderSpecialColumns(context, arc,
tContext, component, 0);
_renderRegularColumns(context, tContext, component, physicalColumn);
}
/**
* @todo Support autoSubmit!
*/
protected void renderSelectionLinks(
FacesContext context,
RenderingContext arc,
TableRenderingContext tContext,
UIComponent component) throws IOException
{
// Never render for empty tables
if (tContext.getRowData().isEmptyTable())
return;
// =-=AEW For some odd reason, we want all the above rendering even if we don't
// have select all or detail disclosure, just not this cell.
if (hasControlBarLinks(context, arc, tContext, component))
{
ResponseWriter writer = context.getResponseWriter();
writer.startElement(XhtmlConstants.TABLE_ROW_ELEMENT, null);
writer.startElement(XhtmlConstants.TABLE_DATA_ELEMENT, null);
writer.startElement(XhtmlConstants.TABLE_ELEMENT, null);
OutputUtils.renderLayoutTableAttributes(context, arc, "0", "100%");
renderStyleClass(context, arc, SkinSelectors.AF_TABLE_SUB_CONTROL_BAR_STYLE);
writer.startElement(XhtmlConstants.TABLE_ROW_ELEMENT, null);
writer.startElement(XhtmlConstants.TABLE_DATA_ELEMENT, null);
writer.writeAttribute("nowrap", Boolean.TRUE, null);
writer.writeAttribute("valign", XhtmlConstants.MIDDLE_ATTRIBUTE_VALUE, null);
renderControlBarLinks(context, arc, tContext, component, false);
writer.endElement(XhtmlConstants.TABLE_DATA_ELEMENT);
writer.endElement(XhtmlConstants.TABLE_ROW_ELEMENT);
writer.endElement(XhtmlConstants.TABLE_ELEMENT);
writer.endElement(XhtmlConstants.TABLE_DATA_ELEMENT);
writer.endElement(XhtmlConstants.TABLE_ROW_ELEMENT);
}
}
/**
* Should we render the select-all/none links?
*/
protected boolean hasControlBarLinks(
FacesContext context,
RenderingContext arc,
TableRenderingContext tContext,
UIComponent component) throws IOException
{
return tContext.hasSelectAll() ||
((tContext.getDetail() != null) &&
getAllDetailsEnabled(getFacesBean(component)));
}
/**
*
* @param context
* @param arc
* @param component
* @param useDivider whether to render a divider after all the links
* @throws IOException
*/
protected void renderControlBarLinks(
FacesContext context,
RenderingContext arc,
TableRenderingContext trc,
UIComponent component,
boolean useDivider) throws IOException
{
FacesBean bean = getFacesBean(component);
boolean hasAllDetails = ((trc.getDetail() != null) &&
getAllDetailsEnabled(bean));
boolean needsDivider = false;
if (trc.hasSelectAll())
{
String jsVarName = trc.getJSVarName();
renderControlBarLink(context, arc,
TreeUtils.callJSSelectAll(jsVarName, true),
_SELECT_ALL_TEXT_KEY, null, true);
renderControlBarLink(context, arc,
TreeUtils.callJSSelectAll(jsVarName, false),
_SELECT_NONE_TEXT_KEY, null, hasAllDetails);
needsDivider = true;
TableSelectManyRenderer.renderScripts(context, arc, trc, isAutoSubmit(bean));
}
ResponseWriter writer = context.getResponseWriter();
if (hasAllDetails)
{
delegateRenderer(context, arc, component, bean, _allUndisclosed);
writer.writeText(LINKS_DIVIDER_TEXT, null);
delegateRenderer(context, arc, component, bean, _allDisclosed);
needsDivider = true;
}
if (useDivider && needsDivider)
{
writer.writeText(LINKS_DIVIDER_TEXT, null);
}
}
protected final void renderControlBarLink(
FacesContext context,
RenderingContext arc,
String onclick,
String translationKey,
String id,
boolean hasDivider) throws IOException
{
ResponseWriter writer = context.getResponseWriter();
writer.startElement("a", null);
writer.writeAttribute(XhtmlConstants.ID_ATTRIBUTE, id, null);
renderStyleClass(context, arc, SkinSelectors.NAV_BAR_ALINK_STYLE_CLASS);
writer.writeAttribute("onclick", onclick, null);
writer.writeURIAttribute("href", "#", null);
writer.writeText(arc.getTranslatedString(translationKey), null);
writer.endElement("a");
if (hasDivider)
writer.writeText(LINKS_DIVIDER_TEXT, null);
}
@Override
protected void renderSubControlBar(
FacesContext context,
RenderingContext arc,
TableRenderingContext tContext,
UIComponent component,
boolean isUpper) throws IOException
{
if (!isUpper)
return;
RenderStage rs = tContext.getRenderStage();
rs.setStage(RenderStage.SUB_CONTROL_BAR_STAGE);
renderSelectionLinks(context, arc, tContext, component);
}
@Override
protected void renderTableContent(
FacesContext context,
RenderingContext arc,
TableRenderingContext tContext,
UIComponent component) throws IOException
{
if (getFacet(component, CoreTable.FOOTER_FACET) != null)
tContext.setExplicitHeaderIDMode(true);
ResponseWriter writer = context.getResponseWriter();
UIComponent table = tContext.getTable();
// DataObject savedData = tContext.getCurrentDataObject();
RenderStage renderStage = tContext.getRenderStage();
Object assertKey = null;
assert ((assertKey = ((UIXCollection)table).getRowKey()) != null)||true;
//
// 2. Render the top / column header
//
// =-= ACW: if a table is very wide, then we render in a special mode
// where the control bars and the table content are in different HTML
// tables. see bug 2530006:
boolean wideMode = "100%".equals(tContext.getTableWidth());
if (wideMode)
{
// if we are in wideMode, we must close the outer table:
writer.endElement(XhtmlConstants.TABLE_ELEMENT);
}
else
{
// the content table is a row in the overall table
writer.startElement(XhtmlConstants.TABLE_ROW_ELEMENT, null);
writer.startElement(XhtmlConstants.TABLE_DATA_ELEMENT, null);
}
String height = getHeight(getFacesBean(component));
final boolean useScrollIE;
final String scrollID;
if ((height != null) && isIE(arc))
{
useScrollIE = true;
String tableId = tContext.getTableId();
scrollID = tableId+"_scroll";
writer.startElement("script", null);
renderScriptDeferAttribute(context, arc);
renderScriptTypeAttribute(context, arc);
_writeIEscrollScript(context, arc, tableId, scrollID);
writer.endElement("script");
writer.startElement("div", null);
// IE in standards compliant mode needs the "width:100%" for the vertical
// scroll bars to appear:
writer.writeAttribute("style", "overflow:auto;overflow-x:hidden;width:100%;height:"+height, null);
// bug 4585888:
writer.writeAttribute("onscroll", "return _uixIEmaskFrame.tickle('"+scrollID+"');", null);
writer.startElement("div", null);
// make room for the vertical scroll bar: //bug 4364828:
writer.writeAttribute("style", "padding-right:16px", null);
}
else
{
useScrollIE = false;
scrollID = null;
}
writer.startElement(XhtmlConstants.TABLE_ELEMENT, null);
renderStyleClass(context, arc, SkinSelectors.AF_TABLE_CONTENT_STYLE);
if ((height != null)&& isGecko(arc))
{
writer.writeAttribute("style", "border-width:0px", null);
}
FacesBean bean = getFacesBean(table);
String summary = getSummary(bean);
Object cellPadding = getTablePadding(table);
OutputUtils.renderLayoutTableAttributes(
context, arc, cellPadding,
"0", // cell spacing
"0", //border
"100%", //table width
summary);
_renderTableHeader(context, arc, tContext, table);
// render the column header
if (tContext.hasColumnHeaders())
{
renderStage.setStage(RenderStage.COLUMN_HEADER_STAGE);
writer.startElement(XhtmlConstants.TABLE_ROW_ELEMENT, null);
if (useScrollIE)
{
// we create a bogus <tr> as a placeholder to occupy space
// while we use absolute positioning to hold the real <tr> in
// place. this is to solve bug 4624925:
writer.endElement(XhtmlConstants.TABLE_ROW_ELEMENT);
writer.startElement(XhtmlConstants.TABLE_ROW_ELEMENT, null);
writer.writeAttribute("style", // for ECM. optimized for simple look-n-feel.
"position:relative;top:-2px;left:0px;z-index:2", null);
//bug4364828, bug 4585888
writer.writeAttribute("id", scrollID, null);
}
renderColumnHeader(context, arc, tContext, component);
writer.endElement(XhtmlConstants.TABLE_ROW_ELEMENT);
}
assert _assertCurrencyKeyPreserved(assertKey, table);
//
// 3. Render all the rows
//
renderStage.setStage(RenderStage.DATA_STAGE);
renderTableRows(context, arc, tContext, component, bean);
assert _assertCurrencyKeyPreserved(assertKey, table);
// the content table is a row in the overall table
writer.endElement(XhtmlConstants.TABLE_ELEMENT);
if (useScrollIE)
{
writer.endElement("div");
writer.endElement("div");
}
if (wideMode)
{
// when we are in wide mode, we break the outer table into two and
// insert the content table between the two. Now we have to start the
// second half of the outer table. This second half will be used by a
// repeating control bar:
writer.startElement(XhtmlConstants.TABLE_ELEMENT, null);
// ideally, the attributes on this table should be the same as on the
// root table, rendered by super.renderAttributes(..). However, that
// method writes out an ID_ATTR, which we do not want to do here:
OutputUtils.renderLayoutTableAttributes(context, arc,
"0", // cell spacing
"0", // border
"100%"); // table width
}
else
{
writer.endElement(XhtmlConstants.TABLE_DATA_ELEMENT);
writer.endElement(XhtmlConstants.TABLE_ROW_ELEMENT);
}
// restore the saved data object
// tContext.setCurrentDataObject(savedData);
}
private void _writeIEscrollScript(
FacesContext context,
RenderingContext arc,
String tableId,
String scrollID) throws IOException
{
ResponseWriter writer = context.getResponseWriter();
boolean previouslyNotRendered =
(arc.getProperties().put(_IE_SCROLL_KEY, Boolean.TRUE) == null);
if (previouslyNotRendered)
{
writer.write(
"function _uixIEmaskFrame(){};" +
"_uixIEmaskFrame.addElement = function(elementId,tableId)"+
"{"
+ "if (_uixIEmaskFrame.elements == null)"
+ "{"
+ "_uixIEmaskFrame.elements = new Array();"
+ "}"
+ "_uixIEmaskFrame.elements.push(elementId);"
+ "_uixIEmaskFrame.elements.push(tableId);"+
"};"+
"_uixIEmaskFrame.createFrames = function()"+
"{"
+ "if (_uixIEmaskFrame.frames == null)"
+ "{"
+ "_uixIEmaskFrame.frames = new Object();"
+ "}"
+ "var elements = _uixIEmaskFrame.elements;"
+ "for(var i=0; i<elements.length; i+=2)"
+ "{"
+ "var elementId = elements[i];"
+ "var tableId = elements[i+1];"
+ "var element = document.getElementById(elementId);"
+ "var maskFrame = element.ownerDocument.createElement('iframe');"
+ "maskFrame.frameBorder = 'none';"
+ "maskFrame.scrolling = 'no';"
+ "maskFrame.title = '';" // is this necessary for accessibility?
+ "var maskFrameStyle = maskFrame.style;"
+ "maskFrameStyle.borderStyle = 'none';"
+ "maskFrameStyle.top = element.offsetTop;"
+ "maskFrameStyle.posLeft = element.offsetLeft;"
+ "maskFrameStyle.width = element.offsetWidth;"
+ "maskFrameStyle.height = element.offsetHeight + 'px';"
// we use a maskFrame to go behind our header <TR>, so that input controls
// do not appear on top of the header, when the table is scrolled:
+ "maskFrameStyle.position = 'absolute';"
+ "maskFrameStyle.zIndex = '1';"
// we must not append the mask frame to the body, because then it will
// not go away if we change this table using PPR:
//+ "element.ownerDocument.body.appendChild(maskFrame);"
// instead append the mask frame to the div that contains the table:
+ "var tableDiv = document.getElementById(tableId);"
+ "tableDiv.appendChild(maskFrame);"
+ "_uixIEmaskFrame.frames[elementId] = maskFrame;"
// the following should return the bogus <tr> that we added to the table:
+ "var subtr = element.parentNode.childNodes[0];"
+ "var subtrStyle = subtr.style;"
// since we are going to change the positioning of the real <tr> to be
// absolute, it will be removed from the flow of the document. Therefore,
// the bogus <TR> must be adjusted to fill the space; otherwise, the
// table date will move up to where the headers should be:
+ "subtrStyle.width = element.offsetWidth + 16;"
+ "subtrStyle.height = element.offsetHeight;"
+ "var elementStyle = element.style;"
// we must use absolute positioning for the header <TR>, otherwise
// it jumps around if we scroll the table and issue a PPR request.
+ "elementStyle.position = 'absolute';"
+ "elementStyle.top = maskFrame.offsetTop;"
+ "elementStyle.posLeft = maskFrame.offsetLeft;"
+ "}"
+ "_uixIEmaskFrame.elements = null;"+
"};"+
"_uixIEmaskFrame.tickle = function(elementId)"+
"{"
+ "var maskFrame = _uixIEmaskFrame.frames[elementId];"
+ "var maskFrameStyle = maskFrame.style;"
+ "maskFrameStyle.visibility = 'hidden';"
+ "maskFrameStyle.visibility = 'visible';"
+ "return false;"+
"};"
);
}
writer.write("_uixIEmaskFrame.addElement('"+scrollID+"','"+tableId+"');");
writer.write(
// bug 4635425:
// this script should always run to support multiple scroll tables
// with PPR:
"if (document.readyState == 'complete')" +
"{"
+ "_uixIEmaskFrame.createFrames();" +
"}"
);
if (previouslyNotRendered)
{
writer.write(
// bug 4635425:
// attach the onload handler only if it has not been attached before.
// there are 3 cases:
// 1. this script is run on initial page load
// 2. this script is run on PPR
// 3. this script is run both by initial page load and subsequent PPR
"if (_uixIEmaskFrame.attached == null)" +
"{"
+ "_uixIEmaskFrame.attached = true;"
+ "window.attachEvent('onload', _uixIEmaskFrame.createFrames);" +
"}"
);
}
}
// render the control bar
@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))
{
boolean isUpper = (tContext.getRenderStage().getStage() ==
RenderStage.UPPER_CONTROL_BAR_STAGE);
ResponseWriter oldRW = null;
try
{
// Install a RepeatIdResponseWriter for the lower bar
// to ensure that IDs are unique
if (!isUpper)
oldRW = RepeatIdResponseWriter.install(context);
ResponseWriter writer = context.getResponseWriter();
// start control bar row
writer.startElement(XhtmlConstants.TABLE_ROW_ELEMENT, null);
writer.startElement(XhtmlConstants.TABLE_DATA_ELEMENT, null);
// start control bar
String style = SkinSelectors.AF_TABLE_CONTROL_BAR_TOP_STYLE;
if (!isUpper)
style = SkinSelectors.AF_TABLE_CONTROL_BAR_BOTTOM_STYLE;
writer.startElement(XhtmlConstants.TABLE_ELEMENT, null);
OutputUtils.renderLayoutTableAttributes(context, arc, "0", "0", "0", "100%");
renderStyleClass(context, arc, style);
writer.startElement(XhtmlConstants.TABLE_ROW_ELEMENT, null);
if (action != null)
{
writer.startElement(XhtmlConstants.TABLE_DATA_ELEMENT, null);
encodeChild(context, action);
writer.endElement(XhtmlConstants.TABLE_DATA_ELEMENT);
}
writer.startElement(XhtmlConstants.TABLE_DATA_ELEMENT, null);
writer.writeAttribute(XhtmlConstants.WIDTH_ATTRIBUTE,
XhtmlConstants.ONE_HUNDRED_PERCENT_ATTRIBUTE_VALUE, null);
writer.endElement(XhtmlConstants.TABLE_DATA_ELEMENT);
if (hasNav)
{
writer.startElement(XhtmlConstants.TABLE_DATA_ELEMENT, null);
if (arc.isRightToLeft())
writer.writeAttribute(XhtmlConstants.ALIGN_ATTRIBUTE,
XhtmlConstants.LEFT_ATTRIBUTE_VALUE, null);
else
writer.writeAttribute(XhtmlConstants.ALIGN_ATTRIBUTE,
XhtmlConstants.RIGHT_ATTRIBUTE_VALUE, null);
writer.writeAttribute(XhtmlConstants.VALIGN_ATTRIBUTE,
XhtmlConstants.MIDDLE_ATTRIBUTE_VALUE, null);
renderRangePagingControl(context, arc, tContext, component);
writer.endElement(XhtmlConstants.TABLE_DATA_ELEMENT);
}
// end control bar table
writer.endElement(XhtmlConstants.TABLE_ROW_ELEMENT);
writer.endElement(XhtmlConstants.TABLE_ELEMENT);
// end control bar row
writer.endElement(XhtmlConstants.TABLE_DATA_ELEMENT);
writer.endElement(XhtmlConstants.TABLE_ROW_ELEMENT);
}
finally
{
if (!isUpper)
{
assert oldRW != null;
RepeatIdResponseWriter.remove(context, oldRW);
}
}
}
}
/**
* Render the next, previous links and the choicebar
*/
protected void renderRangePagingControl(
FacesContext context,
RenderingContext arc,
TableRenderingContext tContext,
UIComponent component)
throws IOException
{
delegateRenderer(context, arc, component,
getFacesBean(component), getSharedNavBarRenderer());
}
private boolean _assertCurrencyKeyPreserved(Object oldKey, UIComponent table)
{
UIXCollection base = (UIXCollection) table;
Object newKey = base.getRowKey();
return (oldKey != null)? oldKey.equals(newKey): (newKey == null);
}
// needed for BIBeans. Contact: Max Starets
protected Object getTablePadding(UIComponent component)
{
return "1";
}
protected void renderTableRows(
FacesContext context,
RenderingContext arc,
TableRenderingContext trc,
UIComponent component,
FacesBean bean) throws IOException
{
if (trc.getRowData().isEmptyTable())
_renderEmptyTable(context, arc, trc);
else
_renderTableRows(context, arc, trc, component);
// render the footer
renderFooter(context, arc, trc, component);
}
/**
* 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
{
super.renderTableAttributes(context, arc, component, bean,
cellPadding, border);
}
/**
* @todo Implement cellClass correctly!
* @todo Implement "headers" attribute correctly!
*/
protected void renderCellFormatAttributes(
FacesContext context,
RenderingContext arc,
TableRenderingContext tContext) throws IOException
{
// renders "style", "class", "nowrap", "headers".
// renders "width" when there are no column headers.
//TODO: must get individual column's style:
String cellClass = SkinSelectors.AF_COLUMN_CELL_TEXT_STYLE;/*ColumnRenderer.getDataStyleClass(...)*/
String borderStyleClass = CellUtils.getDataBorderStyle(arc, tContext);
renderStyleClasses(context, arc, new String[]{cellClass, borderStyleClass});
final ResponseWriter writer = context.getResponseWriter();
int row = tContext.getRowData().getRangeIndex();
int physicalColumn = tContext.getColumnData().getPhysicalColumnIndex();
boolean noSelect = (!tContext.hasSelection());
// Bug 1807935: if there's no column headers (and no
// selection) then we haven't yet rendered the width
// attribute. Render it on the first row of cells.
if ((row == 0) &&
noSelect &&
!tContext.hasColumnHeaders())
{
Object width = tContext.getColumnWidth(physicalColumn);
writer.writeAttribute(XhtmlConstants.WIDTH_ATTRIBUTE, width, null);
}
// render "headers" attribute if necessary
/*ColumnRenderer.renderHeadersAttr(context);*/
// support "nowrap"
if (tContext.getColumnData().getNoWrap(physicalColumn))
writer.writeAttribute(XhtmlConstants.NOWRAP_ATTRIBUTE, Boolean.TRUE, null);
}
/**
* @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");
}
}
private void _renderEmptyTable(
FacesContext context,
RenderingContext arc,
TableRenderingContext tContext)
throws IOException
{
int specialCols = tContext.hasSelection() ? 1 : 0;
if (tContext.getDetail() != null)
specialCols++;
renderEmptyTableRow(context, arc, tContext, specialCols);
}
/**
* Renders a row for an empty table. This includes the rowHeader and any
* special columns, and all the regular columns. The emptyText is
* rendered in the first column following the special columns.
* @param specialColumnCount The number of special columns in this table.
*/
protected final void renderEmptyTableRow(
FacesContext context,
RenderingContext arc,
TableRenderingContext tContext,
int specialColumnCount) throws IOException
{
renderEmptyTableRow(context, arc, tContext, specialColumnCount, null);
}
protected final void renderEmptyTableRow(
FacesContext context,
RenderingContext arc,
TableRenderingContext tContext,
int specialColumnCount,
CoreRenderer emptyTextRenderer) throws IOException
{
// renders <TR> followed by a whole bunch of <TD>..</TD>, followed by
// </TR>
ResponseWriter writer = context.getResponseWriter();
writer.startElement(XhtmlConstants.TABLE_ROW_ELEMENT, null);
final ColumnData colData = tContext.getColumnData();
int physicalColumn = 0;
int objectNameColumnIndex = colData.getObjectNameColumnIndex();
for (int i = 0, sz = Math.max(specialColumnCount, objectNameColumnIndex); i < sz; i++)
{
_renderEmptyCell(context, arc, tContext, physicalColumn++, null);
}
if (emptyTextRenderer == null)
{
_renderEmptyCell(context, arc, tContext, physicalColumn++,
getEmptyText(getFacesBean(tContext.getTable())));
}
else
{
UIComponent table = tContext.getTable();
delegateRenderer(context, arc, table, getFacesBean(table), emptyTextRenderer);
}
for (int totalCols = tContext.getActualColumnCount(); physicalColumn < totalCols; )
{
_renderEmptyCell(context, arc, tContext, physicalColumn++, null);
}
// clear the current header id
colData.setCurrentHeaderID(null);
writer.endElement(XhtmlConstants.TABLE_ROW_ELEMENT);
}
private void _renderEmptyCell(
FacesContext context,
RenderingContext arc,
TableRenderingContext tContext,
int physicalColumn,
String text) throws IOException
{
ColumnData colData = tContext.getColumnData();
ResponseWriter writer = context.getResponseWriter();
// root columns only, so headerID is singleton
// rather than space-separated list
String colID = colData.getHeaderID(physicalColumn);
colData.setCurrentHeaderID(colID);
colData.setColumnIndex(physicalColumn, ColumnData.SPECIAL_COLUMN_INDEX);
writer.startElement(XhtmlConstants.TABLE_DATA_ELEMENT, null);
renderCellFormatAttributes(context, arc, tContext);
if (text != null)
writer.writeText(text, null);
writer.endElement(XhtmlConstants.TABLE_DATA_ELEMENT);
}
private void _renderTableRows(
FacesContext context,
final RenderingContext arc,
final TableRenderingContext tContext,
UIComponent component
)
throws IOException
{
// renders a whole bunch of <TR>...</TR> elements, one for each row in the
// table, and additional ones for any disclosed-details rows
//
// 1. Gather all the data we need to render
//
final RowData rowData = tContext.getRowData();
final UIComponent detail = tContext.getDetail();
final RenderStage renderStage = tContext.getRenderStage();
TableUtils.RowLoop loop = new TableUtils.RowLoop() {
@Override
protected void processRowImpl(FacesContext fc, CollectionComponent tableBase)
throws IOException
{
ResponseWriter writer = fc.getResponseWriter();
// compute all the rowSpans for the current row:
rowData.setCurrentRowSpan(-1);
//reset
renderStage.setStage(RenderStage.START_ROW_STAGE);
renderSingleRow(fc, arc, tContext, (UIComponent) tableBase);
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
writer.startElement(XhtmlConstants.TABLE_ROW_ELEMENT, null);
renderSingleRow(fc, arc, tContext, (UIComponent) tableBase);
rowData.incCurrentSubRow();
// end the row
writer.endElement(XhtmlConstants.TABLE_ROW_ELEMENT);
}
// if necessary, render a detail row
if ((detail != null)&&
((UIXTable)tableBase).getDisclosedRowKeys().isContained())
{
// indicate that we are now rendering inside of a details section
renderStage.setStage(RenderStage.DETAIL_ROW_STAGE);
ColumnData colData = tContext.getColumnData();
// while rendering the named children in the detail row,
// do not use the special response writer that
// defaults data cells with no data to <br>.
// This fixes bug 2367693.
writer.startElement(XhtmlConstants.TABLE_ROW_ELEMENT, null);
writer.startElement(XhtmlConstants.TABLE_DATA_ELEMENT, null);
writer.writeAttribute("headers",
colData.getHeaderID(tContext.getDetailColumnIndex()),
null);
writer.writeAttribute(XhtmlConstants.COLSPAN_ATTRIBUTE,
IntegerUtils.getString(tContext.getActualColumnCount()), null);
String styleClass = SkinSelectors.AF_TABLE_DETAIL_STYLE;
String borderStyleClass = CellUtils.getBorderClass(
true, true, true, true);
renderStyleClasses(fc, arc,
new String[]{styleClass, borderStyleClass});
encodeChild(fc, detail);
writer.endElement(XhtmlConstants.TABLE_DATA_ELEMENT);
writer.endElement(XhtmlConstants.TABLE_ROW_ELEMENT);
// restore the data stage
renderStage.setStage(RenderStage.DATA_STAGE);
}
// end detail
}
};
ResponseWriter writer = context.getResponseWriter();
String height = getHeight(getFacesBean(component));
boolean useScroll;
if ((height != null) && isGecko(arc))
{
useScroll = true;
writer.startElement("tbody", null);
writer.writeAttribute("style", "overflow:auto;max-height:"+height, null);
}
else
useScroll = false;
loop.run(context, tContext.getCollectionComponent());
if (useScroll)
{
writer.endElement("tbody");
}
}
/**
* render the complete column header, including the special columns (like
* select,details,...) and the regular table columns
*/
protected final void renderColumnHeader(
FacesContext context,
RenderingContext arc,
TableRenderingContext tContext,
UIComponent component) throws IOException
{
// This renders a whole bunch of <TH>...</TH> elements
final ColumnData colData = tContext.getColumnData();
// we need to keep track of which row we are on; this makes it easier
// to do rowSpanning in columnGroups:
colData.setRowIndex(0);
int physicalCol = renderSpecialColumns(context, arc, tContext, component, 0);
renderRegularHeaders(context, arc, tContext, component, physicalCol);
// we are done, so reset the current row:
colData.setRowIndex(-1);
}
/**
* renders the regular table column headers.
*/
protected final void renderRegularHeaders(
FacesContext context,
RenderingContext arc,
TableRenderingContext tContext,
UIComponent component,
int physicalCol) throws IOException
{
// this renders a whole bunch of <TH>...</TH> elements.
// if there are columnGroups present, it will render some
// </TR><TR><TH>...</TH> sequences.
final ColumnData colData = tContext.getColumnData();
_renderRegularColumns(context, tContext, component, physicalCol);
int rowSpan = colData.getHeaderRowSpan();
if (rowSpan > 1)
{
ResponseWriter writer = context.getResponseWriter();
for (int i = 1; i < rowSpan; i++)
{
colData.setRowIndex(i);
writer.endElement(XhtmlConstants.TABLE_ROW_ELEMENT);
writer.startElement(XhtmlConstants.TABLE_ROW_ELEMENT, null);
_renderRegularColumns(context, tContext, component, physicalCol);
}
}
}
@SuppressWarnings("unchecked")
private void _renderRegularColumns(
FacesContext context,
TableRenderingContext tContext,
UIComponent component,
int physicalCol) throws IOException
{
// this renders a whole bunch of <TH>...</TH> elements.
// part of #1313720, base column header count on
// table child count
List<UIComponent> children = component.getChildren();
int colCount = children.size();
int[] hidden = tContext.getHiddenColumns();
ColumnData colData = tContext.getColumnData();
for (int i = 0; i < colCount; i++)
{
if (hidden[i] != TableRenderingContext.NORMAL_COLUMN)
continue;
UIComponent child = children.get(i);
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, i);
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();
}
}
}
/**
* @todo Re-fix bug 3211593 (see below)
*/
@SuppressWarnings("unchecked")
protected final void renderFooter(
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);
boolean hasColumnFooters = colData.getPhysicalIndexOfFirstFooter() >= 0;
// If there's a table footer, or column footers, we've got work to do
if ((footer != null) || hasColumnFooters)
{
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);
}
// 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();
// By default, we try to render the table footer in the same row
// as the column footers; this is to save on screen real-estate,
// and do something with that space in the table if the first N
// columns have no footer content.
// When the first column does have a footer, we'll need to push
// the table footer down to an extra row
// If there isn't a column footer in the first row, render a TH
// with a sufficient colspan - and put the table footer in there
// if it exists.
// (Note this does need to be != 0, not > 0. Negative numbers
// mean there's no column footers, in which case we'll handle
// outputting the table footer right here)
if (firstFooterPhysicalIndex != 0)
{
writer.startElement(XhtmlConstants.TABLE_HEADER_ELEMENT, null);
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);
if (footer != null)
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);
// OK, we need to put the table footer at the end in its own row,
// because the first column is already taken
if ((firstFooterPhysicalIndex == 0) && (footer != null))
{
writer.startElement(XhtmlConstants.TABLE_ROW_ELEMENT, null);
writer.startElement(XhtmlConstants.TABLE_HEADER_ELEMENT, null);
// Make it span the whole table
writer.writeAttribute(XhtmlConstants.COLSPAN_ATTRIBUTE, tContext.getActualColumnCount(), null);
renderStyleClass(context, arc, SkinSelectors.AF_TABLE_COLUMN_FOOTER_STYLE);
encodeChild(context, footer);
writer.endElement(XhtmlConstants.TABLE_HEADER_ELEMENT);
writer.endElement(XhtmlConstants.TABLE_ROW_ELEMENT);
}
}
}
protected String getSummary(FacesBean bean)
{
return toString(bean.getProperty(_summaryKey));
}
protected String getHeight(FacesBean bean)
{
return toString(bean.getProperty(_heightKey));
}
/**
* Tells whether or not the autoSubmit attribute is set on the bean
*
* @param bean the bean
*/
protected boolean isAutoSubmit(FacesBean bean)
{
if (_autoSubmitKey == null)
return false;
return Boolean.TRUE.equals(bean.getProperty(_autoSubmitKey));
}
protected boolean getAllDetailsEnabled(FacesBean bean)
{
Object o = bean.getProperty(_allDetailsEnabledKey);
if (o == null)
o = _allDetailsEnabledKey.getDefault();
return Boolean.TRUE.equals(o);
}
static private class AllDetail extends ShowDetailRenderer
{
public AllDetail(FacesBean.Type type, boolean disclosed)
{
super(type);
_disclosed = disclosed;
}
@Override
protected void renderAllAttributes(
FacesContext context,
RenderingContext arc,
FacesBean bean)
{
}
@Override
protected boolean isTableAllDisclosure()
{
return true;
}
@Override
protected boolean renderAsInline()
{
return true;
}
@Override
protected String getValueParameter(UIComponent component)
{
return "all";
}
@Override
protected boolean getDisclosed(FacesBean bean)
{
return _disclosed;
}
@Override
protected String getDisclosedText(FacesBean bean)
{
RenderingContext arc = RenderingContext.getCurrentInstance();
return arc.getTranslatedString(_HIDE_ALL_DETAILS_TEXT_KEY);
}
@Override
protected String getUndisclosedText(FacesBean bean)
{
RenderingContext arc = RenderingContext.getCurrentInstance();
return arc.getTranslatedString(_SHOW_ALL_DETAILS_TEXT_KEY);
}
@Override
protected String getLinkId(String rootId, boolean disclosed)
{
String suffix = (disclosed ? "ha" : "sa");
return XhtmlUtils.getCompositeId(rootId, suffix);
}
@Override
protected String getClientId(FacesContext context, UIComponent component)
{
TableRenderingContext tContext = TableRenderingContext.getCurrentInstance();
return tContext.getTableId();
}
private boolean _disclosed;
}
//
// Private variables
//
private CoreRenderer _allDisclosed;
private CoreRenderer _allUndisclosed;
// translation keys
private static final String _SHOW_ALL_DETAILS_TEXT_KEY = "af_table.SHOW_ALL_DETAILS";
private static final String _HIDE_ALL_DETAILS_TEXT_KEY = "af_table.HIDE_ALL_DETAILS";
private static final String _SELECT_ALL_TEXT_KEY = "af_tableSelectMany.SELECT_ALL";
private static final String _SELECT_NONE_TEXT_KEY = "af_tableSelectMany.SELECT_NONE";
public static final String LINKS_DIVIDER_TEXT = "\u00a0|\u00a0";
private static final Object _IE_SCROLL_KEY = new Object();
private PropertyKey _autoSubmitKey;
private PropertyKey _summaryKey;
private PropertyKey _heightKey;
private PropertyKey _allDetailsEnabledKey;
}