blob: 1cb73567a7a883d4016b26619d3b7c570472f825 [file] [log] [blame]
/*
* 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.renderkit.html.base;
import org.apache.myfaces.renderkit.html.util.HtmlRendererUtils;
import org.apache.myfaces.renderkit.html.util.ClientBehaviorRendererUtils;
import org.apache.myfaces.renderkit.html.util.CommonPropertyUtils;
import org.apache.myfaces.renderkit.html.util.CommonEventUtils;
import java.io.IOException;
import java.util.List;
import java.util.Map;
import java.util.logging.Logger;
import javax.faces.component.UIColumn;
import javax.faces.component.UIComponent;
import javax.faces.component.UIData;
import javax.faces.component.behavior.ClientBehavior;
import javax.faces.component.behavior.ClientBehaviorHolder;
import javax.faces.component.html.HtmlColumn;
import javax.faces.component.html.HtmlDataTable;
import javax.faces.context.FacesContext;
import javax.faces.context.ResponseWriter;
import org.apache.myfaces.renderkit.html.util.JSFAttr;
import org.apache.myfaces.renderkit.RendererUtils;
import org.apache.myfaces.renderkit.html.util.ResourceUtils;
import org.apache.myfaces.util.lang.ArrayUtils;
import org.apache.myfaces.util.lang.StringUtils;
import org.apache.myfaces.renderkit.html.util.HTML;
/**
* Common methods for renderers for components that subclass the standard
* JSF HtmlDataTable component.
*/
public class HtmlTableRendererBase extends HtmlRenderer
{
protected static final String HEADER_FACET_NAME = "header";
protected static final String FOOTER_FACET_NAME = "footer";
protected static final String CAPTION_FACET_NAME = "caption";
private static final Logger log = Logger.getLogger(HtmlTableRendererBase.class.getName());
private static final Integer[] ZERO_INT_ARRAY = new Integer[]{0};
/**
* @param component dataTable
* @return number of layout columns
*/
protected int getNewspaperColumns(UIComponent component)
{
return 1;
}
/**
* @param component dataTable
* @return component to display between layout columns
*/
protected UIComponent getNewspaperTableSpacer(UIComponent component)
{
return null;
}
/**
* @param component dataTable
* @return whether dataTable has component to display between layout columns
*/
protected boolean hasNewspaperTableSpacer(UIComponent component)
{
return false;
}
/**
* @param component dataTable
* @return whether dataTable has newspaper columns layed out horizontally
*/
protected boolean isNewspaperHorizontalOrientation(UIComponent component)
{
return false;
}
@Override
public boolean getRendersChildren()
{
return true;
}
/**
* Render the necessary bits that come before any actual <i>rows</i> in the table.
*
* @see javax.faces.render.Renderer#encodeBegin(FacesContext, UIComponent)
*/
@Override
public void encodeBegin(FacesContext facesContext, UIComponent uiComponent) throws IOException
{
RendererUtils.checkParamValidity(facesContext, uiComponent, UIData.class);
if (uiComponent instanceof ClientBehaviorHolder)
{
Map<String, List<ClientBehavior>> behaviors = ((ClientBehaviorHolder) uiComponent).getClientBehaviors();
if (!behaviors.isEmpty())
{
ResourceUtils.renderDefaultJsfJsInlineIfNecessary(facesContext, facesContext.getResponseWriter());
}
}
beforeTable(facesContext, (UIData) uiComponent);
startTable(facesContext, uiComponent);
}
/**
* actually render the start of the table
*/
protected void startTable(FacesContext facesContext, UIComponent uiComponent) throws IOException
{
ResponseWriter writer = facesContext.getResponseWriter();
writer.startElement(HTML.TABLE_ELEM, uiComponent);
Map<String, List<ClientBehavior>> behaviors = null;
if (uiComponent instanceof ClientBehaviorHolder)
{
behaviors = ((ClientBehaviorHolder) uiComponent).getClientBehaviors();
if (!behaviors.isEmpty())
{
HtmlRendererUtils.writeIdAndName(writer, uiComponent, facesContext);
}
else
{
HtmlRendererUtils.writeIdIfNecessary(writer, uiComponent, facesContext);
}
if (behaviors.isEmpty() && isCommonPropertiesOptimizationEnabled(facesContext))
{
CommonPropertyUtils.renderEventProperties(writer,
CommonPropertyUtils.getCommonPropertiesMarked(uiComponent), uiComponent);
}
else
{
if (isCommonEventsOptimizationEnabled(facesContext))
{
CommonEventUtils.renderBehaviorizedEventHandlers(facesContext, writer,
CommonPropertyUtils.getCommonPropertiesMarked(uiComponent),
CommonEventUtils.getCommonEventsMarked(uiComponent), uiComponent, behaviors);
}
else
{
HtmlRendererUtils.renderBehaviorizedEventHandlers(facesContext, writer, uiComponent, behaviors);
}
}
if (isCommonPropertiesOptimizationEnabled(facesContext))
{
HtmlRendererUtils.renderHTMLAttributes(writer, uiComponent, HTML.TABLE_ATTRIBUTES);
CommonPropertyUtils.renderCommonPassthroughPropertiesWithoutEvents(writer,
CommonPropertyUtils.getCommonPropertiesMarked(uiComponent), uiComponent);
}
else
{
HtmlRendererUtils.renderHTMLAttributes(writer, uiComponent,
HTML.TABLE_PASSTHROUGH_ATTRIBUTES_WITHOUT_EVENTS);
}
}
else
{
HtmlRendererUtils.writeIdIfNecessary(writer, uiComponent, facesContext);
if (isCommonPropertiesOptimizationEnabled(facesContext))
{
HtmlRendererUtils.renderHTMLAttributes(writer, uiComponent, HTML.TABLE_ATTRIBUTES);
CommonPropertyUtils.renderCommonPassthroughProperties(writer,
CommonPropertyUtils.getCommonPropertiesMarked(uiComponent), uiComponent);
}
else
{
HtmlRendererUtils.renderHTMLAttributes(writer, uiComponent,
HTML.TABLE_PASSTHROUGH_ATTRIBUTES);
}
}
}
/**
* Render the TBODY section of the html table. See also method encodeInnerHtml.
*
* @see javax.faces.render.Renderer#encodeChildren(FacesContext, UIComponent)
*/
@Override
public void encodeChildren(FacesContext facesContext, UIComponent component) throws IOException
{
RendererUtils.checkParamValidity(facesContext, component, UIData.class);
beforeBody(facesContext, (UIData) component);
encodeInnerHtml(facesContext, component);
afterBody(facesContext, (UIData) component);
}
/**
* Renders the caption facet.
* @param facesContext the <code>FacesContext</code>.
* @param writer the <code>ResponseWriter</code>.
* @param component the parent <code>UIComponent</code> containing the facets.
* @throws IOException if an exception occurs.
*/
protected void renderCaptionFacet(FacesContext facesContext, ResponseWriter writer, UIComponent component)
throws IOException
{
HtmlRendererUtils.renderTableCaption(facesContext, writer, component);
}
/**
* Renders the colgroups facet.
* @param facesContext the <code>FacesContext</code>.
* @param writer the <code>ResponseWriter</code>.
* @param component the parent <code>UIComponent</code> containing the facets.
* @throws IOException if an exception occurs.
* @since 2.0
*/
protected void renderColgroupsFacet(FacesContext facesContext, ResponseWriter writer, UIComponent component)
throws IOException
{
UIComponent colgroupsFacet = component.getFacet("colgroups");
if (colgroupsFacet == null)
{
// no facet to be rendered
return;
}
// render the facet
//RendererUtils.renderChild(facesContext, colgroupsFacet);
colgroupsFacet.encodeAll(facesContext);
}
/**
* Gets styles for the specified component.
*/
protected static Styles getStyles(UIData uiData)
{
String rowClasses;
String columnClasses;
if(uiData instanceof HtmlDataTable)
{
rowClasses = ((HtmlDataTable)uiData).getRowClasses();
columnClasses = ((HtmlDataTable)uiData).getColumnClasses();
}
else
{
rowClasses = (String)uiData.getAttributes().get(JSFAttr.ROW_CLASSES_ATTR);
columnClasses = (String)uiData.getAttributes().get(JSFAttr.COLUMN_CLASSES_ATTR);
}
return new Styles(rowClasses, columnClasses);
}
/**
* Class manages the styles from String lists.
*/
protected static class Styles
{
private String[] _columnStyle;
private String[] _rowStyle;
Styles(String rowStyles, String columnStyles)
{
_rowStyle = (rowStyles == null)
? ArrayUtils.EMPTY_STRING_ARRAY
: StringUtils.trim(
StringUtils.splitShortString(rowStyles, ','));
_columnStyle = (columnStyles == null)
? ArrayUtils.EMPTY_STRING_ARRAY
: StringUtils.trim(
StringUtils.splitShortString(columnStyles, ','));
}
public String getRowStyle(int idx)
{
if(!hasRowStyle())
{
return null;
}
return _rowStyle[idx % _rowStyle.length];
}
public String getColumnStyle(int idx)
{
if(!hasColumnStyle())
{
return null;
}
//return _columnStyle[idx % _columnStyle.length];
if (idx < _columnStyle.length)
{
return _columnStyle[idx];
}
return null;
}
public boolean hasRowStyle()
{
return _rowStyle.length > 0;
}
public boolean hasColumnStyle()
{
return _columnStyle.length > 0;
}
}
private Integer[] getBodyRows(FacesContext facesContext, UIComponent component)
{
Integer[] bodyrows = null;
String bodyrowsAttr = (String) component.getAttributes().get(JSFAttr.BODYROWS_ATTR);
if (bodyrowsAttr != null && !bodyrowsAttr.isEmpty())
{
String[] bodyrowsString = StringUtils.trim(StringUtils.splitShortString(bodyrowsAttr, ','));
// parsing with no exception handling, because of JSF-spec:
// "If present, this must be a comma separated list of integers."
bodyrows = new Integer[bodyrowsString.length];
for(int i = 0; i < bodyrowsString.length; i++)
{
bodyrows[i] = Integer.valueOf(bodyrowsString[i]);
}
}
else
{
bodyrows = ZERO_INT_ARRAY;
}
return bodyrows;
}
/**
* Renders everything inside the TBODY tag by iterating over the row objects
* between offsets first and first+rows and applying the UIColumn components
* to those objects.
* <p>
* This method is separated from the encodeChildren so that it can be overridden by
* subclasses. One class that uses this functionality is autoUpdateDataTable.
*/
public void encodeInnerHtml(FacesContext facesContext, UIComponent component)throws IOException
{
UIData uiData = (UIData) component;
ResponseWriter writer = facesContext.getResponseWriter();
int rowCount = uiData.getRowCount();
int newspaperColumns = getNewspaperColumns(component);
if (rowCount == -1 && newspaperColumns == 1)
{
encodeInnerHtmlUnknownRowCount(facesContext, component);
return;
}
if (rowCount == 0)
{
//nothing to render, to get valid xhtml we render an empty dummy row
writer.startElement(HTML.TBODY_ELEM, null); // uiData);
writer.writeAttribute(HTML.ID_ATTR, component.getClientId(facesContext) + ":tbody_element", null);
writer.startElement(HTML.TR_ELEM, null); // uiData);
writer.startElement(HTML.TD_ELEM, null); // uiData);
writer.endElement(HTML.TD_ELEM);
writer.endElement(HTML.TR_ELEM);
writer.endElement(HTML.TBODY_ELEM);
return;
}
// begin the table
// get the CSS styles
Styles styles = getStyles(uiData);
int first = uiData.getFirst();
int rows = uiData.getRows();
int last;
if (rows <= 0)
{
last = rowCount;
}
else
{
last = first + rows;
if (last > rowCount)
{
last = rowCount;
}
}
int newspaperRows;
if((last - first) % newspaperColumns == 0)
{
newspaperRows = (last - first) / newspaperColumns;
}
else
{
newspaperRows = ((last - first) / newspaperColumns) + 1;
}
boolean newspaperHorizontalOrientation = isNewspaperHorizontalOrientation(component);
// get the row indizes for which a new TBODY element should be created
Integer[] bodyrows = getBodyRows(facesContext, component);
int bodyrowsCount = 0;
// walk through the newspaper rows
for(int nr = 0; nr < newspaperRows; nr++)
{
boolean rowStartRendered = false;
// walk through the newspaper columns
for(int nc = 0; nc < newspaperColumns; nc++)
{
// the current row in the 'real' table
int currentRow;
if (newspaperHorizontalOrientation)
{
currentRow = nr * newspaperColumns + nc + first;
}
else
{
currentRow = nc * newspaperRows + nr + first;
}
// if this row is not to be rendered
if(currentRow >= last)
{
continue;
}
// bail if any row does not exist
uiData.setRowIndex(currentRow);
if(!uiData.isRowAvailable())
{
log.severe("Row is not available. Rowindex = " + currentRow);
break;
}
if (nc == 0)
{
// first column in table, start new row
beforeRow(facesContext, uiData);
// is the current row listed in the bodyrows attribute
if(ArrayUtils.contains(bodyrows, currentRow))
{
// close any preopened TBODY element first
if(bodyrowsCount != 0)
{
writer.endElement(HTML.TBODY_ELEM);
}
writer.startElement(HTML.TBODY_ELEM, null); // uiData);
// Do not attach bodyrowsCount to the first TBODY element, because of backward compatibility
writer.writeAttribute(HTML.ID_ATTR, component.getClientId(facesContext) + ":tbody_element" +
(bodyrowsCount == 0 ? "" : bodyrowsCount), null);
bodyrowsCount++;
}
renderRowStart(facesContext, writer, uiData, styles, nr);
rowStartRendered = true;
}
List children = null;
int columnStyleIndex = 0;
for (int j = 0, size = getChildCount(component); j < size; j++)
{
if (children == null)
{
children = getChildren(component);
}
UIComponent child = (UIComponent) children.get(j);
if (child.isRendered())
{
boolean columnRendering = child instanceof UIColumn;
if (columnRendering)
{
beforeColumn(facesContext, uiData, columnStyleIndex);
}
encodeColumnChild(facesContext, writer, uiData, child,
styles, nc * uiData.getChildCount() + columnStyleIndex);
if (columnRendering)
{
afterColumn(facesContext, uiData, columnStyleIndex);
}
columnStyleIndex = columnStyleIndex +
getColumnCountForComponent(facesContext, uiData, child);
}
}
if (hasNewspaperTableSpacer(uiData))
{
// draw the spacer facet
if(nc < newspaperColumns - 1)
{
renderSpacerCell(facesContext, writer, uiData);
}
}
}
if (rowStartRendered)
{
renderRowEnd(facesContext, writer, uiData);
afterRow(facesContext, uiData);
}
}
if(bodyrowsCount != 0)
{
// close the last TBODY element
writer.endElement(HTML.TBODY_ELEM);
}
}
private void encodeInnerHtmlUnknownRowCount(FacesContext facesContext, UIComponent component)throws IOException
{
UIData uiData = (UIData) component;
ResponseWriter writer = facesContext.getResponseWriter();
Styles styles = getStyles(uiData);
Integer[] bodyrows = getBodyRows(facesContext, component);
int bodyrowsCount = 0;
int first = uiData.getFirst();
int rows = uiData.getRows();
int currentRow = first;
boolean isRowRendered = false;
while(true)
{
uiData.setRowIndex(currentRow);
if (!uiData.isRowAvailable())
{
break;
}
isRowRendered = true;
// first column in table, start new row
beforeRow(facesContext, uiData);
// is the current row listed in the bodyrows attribute
if(ArrayUtils.contains(bodyrows, currentRow))
{
// close any preopened TBODY element first
if(bodyrowsCount != 0)
{
writer.endElement(HTML.TBODY_ELEM);
}
writer.startElement(HTML.TBODY_ELEM, null); // uiData);
// Do not attach bodyrowsCount to the first TBODY element, because of backward compatibility
writer.writeAttribute(HTML.ID_ATTR, component.getClientId(facesContext) + ":tbody_element" +
(bodyrowsCount == 0 ? "" : bodyrowsCount), null);
bodyrowsCount++;
}
renderRowStart(facesContext, writer, uiData, styles, currentRow);
List<UIComponent> children = null;
int columnStyleIndex = 0;
for (int j = 0, size = getChildCount(component); j < size; j++)
{
if (children == null)
{
children = getChildren(component);
}
UIComponent child = (UIComponent) children.get(j);
if (child.isRendered())
{
boolean columnRendering = child instanceof UIColumn;
if (columnRendering)
{
beforeColumn(facesContext, uiData, columnStyleIndex);
}
encodeColumnChild(facesContext, writer, uiData, child,
styles, columnStyleIndex);
if (columnRendering)
{
afterColumn(facesContext, uiData, columnStyleIndex);
}
columnStyleIndex = columnStyleIndex +
getColumnCountForComponent(facesContext, uiData, child);
}
}
renderRowEnd(facesContext, writer, uiData);
afterRow(facesContext, uiData);
currentRow++;
if (rows > 0 && currentRow-first > rows )
{
break;
}
}
if (!isRowRendered)
{
//nothing to render, to get valid xhtml we render an empty dummy row
writer.startElement(HTML.TBODY_ELEM, null); // uiData);
writer.writeAttribute(HTML.ID_ATTR, component.getClientId(facesContext) + ":tbody_element", null);
writer.startElement(HTML.TR_ELEM, null); // uiData);
writer.startElement(HTML.TD_ELEM, null); // uiData);
writer.endElement(HTML.TD_ELEM);
writer.endElement(HTML.TR_ELEM);
writer.endElement(HTML.TBODY_ELEM);
return;
}
if(bodyrowsCount != 0)
{
// close the last TBODY element
writer.endElement(HTML.TBODY_ELEM);
}
}
protected void encodeColumnChild(FacesContext facesContext, ResponseWriter writer,
UIData uiData, UIComponent component, Styles styles, int columnStyleIndex) throws IOException
{
if (component instanceof UIColumn)
{
renderColumnBody(facesContext, writer, uiData, component, styles, columnStyleIndex);
}
}
/**
* Renders the body of a given <code>UIColumn</code> (everything but
* the header and footer facets). This emits a TD cell, whose contents
* are the result of calling encodeBegin, encodeChildren and
* encodeEnd methods on the component (or its associated renderer).
*
* @param facesContext the <code>FacesContext</code>.
* @param writer the <code>ResponseWriter</code>.
* @param uiData the <code>UIData</code> being rendered.
* @param component the <code>UIComponent</code> to render.
* @throws IOException if an exception occurs.
*/
protected void renderColumnBody(
FacesContext facesContext,
ResponseWriter writer,
UIData uiData,
UIComponent component,
Styles styles, int columnStyleIndex) throws IOException
{
// Get the rowHeader attribute from the attribute map, because of MYFACES-1790
Object rowHeaderAttr = component.getAttributes().get(JSFAttr.ROW_HEADER_ATTR);
boolean rowHeader = rowHeaderAttr != null && ((Boolean) rowHeaderAttr);
if(rowHeader)
{
writer.startElement(HTML.TH_ELEM, null); // uiData);
writer.writeAttribute(HTML.SCOPE_ATTR, HTML.SCOPE_ROW_VALUE, null);
}
else
{
writer.startElement(HTML.TD_ELEM, null); // uiData);
}
String styleClass = null;
if (component instanceof HtmlColumn)
{
styleClass = ((HtmlColumn) component).getStyleClass();
}
if (styles.hasColumnStyle())
{
if (styleClass == null)
{
styleClass = styles.getColumnStyle(columnStyleIndex);
}
else
{
styleClass = styleClass+ ' ' +styles.getColumnStyle(columnStyleIndex);
}
}
if (styleClass != null)
{
writer.writeAttribute(HTML.CLASS_ATTR, styleClass, null);
}
//RendererUtils.renderChild(facesContext, component);
component.encodeAll(facesContext);
if(rowHeader)
{
writer.endElement(HTML.TH_ELEM);
}
else
{
writer.endElement(HTML.TD_ELEM);
}
}
/**
* Renders the start of a new row of body content.
* @param facesContext the <code>FacesContext</code>.
* @param writer the <code>ResponseWriter</code>.
* @param uiData the <code>UIData</code> being rendered.
* @throws IOException if an exceptoin occurs.
*/
protected void renderRowStart(
FacesContext facesContext,
ResponseWriter writer,
UIData uiData,
Styles styles, int rowStyleIndex) throws IOException
{
writer.startElement(HTML.TR_ELEM, null); // uiData);
renderRowStyle(facesContext, writer, uiData, styles, rowStyleIndex);
Object rowId = uiData.getAttributes().get(JSFAttr.ROW_ID);
if (rowId != null)
{
writer.writeAttribute(HTML.ID_ATTR, rowId.toString(), null);
}
}
protected void renderRowStyle(FacesContext facesContext, ResponseWriter writer,
UIData uiData, Styles styles, int rowStyleIndex) throws IOException
{
String rowClass = null;
if (uiData instanceof HtmlDataTable)
{
rowClass = ((HtmlDataTable) uiData).getRowClass();
}
if (styles.hasRowStyle())
{
if (rowClass == null)
{
rowClass = styles.getRowStyle(rowStyleIndex);
}
else
{
rowClass = rowClass + ' ' + styles.getRowStyle(rowStyleIndex);
}
}
if (rowClass != null)
{
writer.writeAttribute(HTML.CLASS_ATTR, rowClass, null);
}
}
/**
* Renders the end of a row of body content.
* @param facesContext the <code>FacesContext</code>.
* @param writer the <code>ResponseWriter</code>.
* @param uiData the <code>UIData</code> being rendered.
* @throws IOException if an exceptoin occurs.
*/
protected void renderRowEnd(
FacesContext facesContext,
ResponseWriter writer,
UIData uiData) throws IOException
{
writer.endElement(HTML.TR_ELEM);
}
/**
* Perform any operations necessary immediately before the TABLE start tag
* is output.
*
* @param facesContext the <code>FacesContext</code>.
* @param uiData the <code>UIData</code> being rendered.
*/
protected void beforeTable(FacesContext facesContext, UIData uiData) throws IOException
{
}
/**
* Perform any operations necessary after TABLE start tag is output
* but before the TBODY start tag.
* <p>
* This method generates the THEAD/TFOOT sections of a table if there
* are any header or footer facets defined on the table or on any child
* UIColumn component.
*
* @param facesContext the <code>FacesContext</code>.
* @param uiData the <code>UIData</code> being rendered.
*/
protected void beforeBody(FacesContext facesContext, UIData uiData) throws IOException
{
ResponseWriter writer = facesContext.getResponseWriter();
renderCaptionFacet(facesContext, writer, uiData);
renderColgroupsFacet(facesContext, writer, uiData);
renderFacet(facesContext, writer, uiData, true);
renderFacet(facesContext, writer, uiData, false);
}
/**
* Perform any operations necessary immediately before each TR start tag
* is output.
*
* @param facesContext the <code>FacesContext</code>.
* @param uiData the <code>UIData</code> being rendered.
*/
protected void beforeRow(FacesContext facesContext, UIData uiData) throws IOException
{
}
/**
* Perform any operations necessary immediately after each TR end tag
* is output.
*
* @param facesContext the <code>FacesContext</code>.
* @param uiData the <code>UIData</code> being rendered.
*/
protected void afterRow(FacesContext facesContext, UIData uiData) throws IOException
{
}
/**
* Perform any operations necessary immediately before each column child is rendered
*
* @param facesContext the <code>FacesContext</code>.
* @param uiData the <code>UIData</code> being rendered.
* @param columnIndex the index of the currenly rendered column
*/
protected void beforeColumn(FacesContext facesContext, UIData uiData, int columnIndex) throws IOException
{
}
/**
* Perform any operations necessary immediately after each column child is rendered
*
* @param facesContext the <code>FacesContext</code>.
* @param uiData the <code>UIData</code> being rendered.
* @param columnIndex the index of the currenly rendered column
*/
protected void afterColumn(FacesContext facesContext, UIData uiData, int columnIndex) throws IOException
{
}
/**
* Indicates the number of columns the component represents. By default each UIColumn instance
* is 1 column
* @param facesContext
* @param uiData
* @param child
* @return
*/
protected int getColumnCountForComponent(FacesContext facesContext, UIData uiData, UIComponent child)
{
if (child instanceof UIColumn)
{
return 1;
}
return 0;
}
/**
*Perform any operations necessary immediately before each column child's header or footer is rendered
*
* @param facesContext the <code>FacesContext</code>.
* @param uiData the <code>UIData</code> being rendered.
* @param header true if the header of the column child is rendered
* @param columnIndex the index of the currenly rendered column
*/
protected void beforeColumnHeaderOrFooter(FacesContext facesContext, UIData uiData, boolean header,
int columnIndex) throws IOException
{
}
/**
* Perform any operations necessary immediately after each column child's header of footer is rendered
*
* @param facesContext the <code>FacesContext</code>.
* @param uiData the <code>UIData</code> being rendered.
* @param header true if the header of the column child is rendered
* @param columnIndex the index of the currenly rendered column
*/
protected void afterColumnHeaderOrFooter(FacesContext facesContext, UIData uiData, boolean header,
int columnIndex) throws IOException
{
}
/**
* Perform any operations necessary in the TBODY start tag.
*
* @param facesContext the <code>FacesContext</code>.
* @param uiData the <code>UIData</code> being rendered.
*/
protected void inBodyStart(FacesContext facesContext, UIData uiData) throws IOException
{
}
/**
* Perform any operations necessary immediately after the TBODY end tag
* is output.
*
* @param facesContext the <code>FacesContext</code>.
* @param uiData the <code>UIData</code> being rendered.
*/
protected void afterBody(FacesContext facesContext, UIData uiData) throws IOException
{
}
/**
* Perform any operations necessary immediately after the TABLE end tag
* is output.
*
* @param facesContext the <code>FacesContext</code>.
* @param uiData the <code>UIData</code> being rendered.
*/
protected void afterTable(FacesContext facesContext, UIData uiData) throws IOException
{
}
/**
* @see javax.faces.render.Renderer#encodeEnd(FacesContext, UIComponent)
*/
public void encodeEnd(FacesContext facesContext, UIComponent uiComponent) throws IOException
{
RendererUtils.checkParamValidity(facesContext, uiComponent, UIData.class);
endTable(facesContext, uiComponent);
afterTable(facesContext, (UIData) uiComponent);
}
/**
* actually render the end of the table
*/
protected void endTable(FacesContext facesContext, UIComponent uiComponent) throws IOException
{
ResponseWriter writer = facesContext.getResponseWriter();
writer.endElement(HTML.TABLE_ELEM);
}
/**
* Renders either the header or the footer facets for the UIData component
* and all the child UIColumn components, as a THEAD or TFOOT element
* containing TR (row) elements.
* <p>
* If there is a header or footer attached to the UIData then that is
* rendered as a TR element whose COLSPAN is the sum of all rendered
* columns in the table. This allows that header/footer to take up the
* entire width of the table.
* <p>
* If any child column has a header or footer then a TR is rendered
* with a TH cell for each column child.
*
* @param facesContext the <code>FacesContext</code>.
* @param writer the <code>ResponseWriter</code>.
* @param component the UIData component
* @param header whether this is the header facet (if not, then the footer facet).
* @throws IOException if an exception occurs.
*/
protected void renderFacet(FacesContext facesContext, ResponseWriter writer,
UIComponent component, boolean header)
throws IOException
{
int colspan = 0;
boolean hasColumnFacet = false;
int childCount = component.getChildCount();
for (int i = 0; i < childCount; i++)
{
UIComponent uiComponent = component.getChildren().get(i);
if(uiComponent.isRendered())
{
// a UIColumn has a span of 1, anything else has a span of 0
colspan += determineChildColSpan(uiComponent);
// hasColumnFacet is true if *any* child column has a facet of
// the specified type.
if (!hasColumnFacet)
{
hasColumnFacet = hasFacet(header, uiComponent);
}
}
}
UIComponent facet = null;
if (component.getFacetCount() > 0)
{
facet = header ? (UIComponent) component.getFacets().get(HEADER_FACET_NAME)
: (UIComponent) component.getFacets().get(FOOTER_FACET_NAME);
}
if (facet != null || hasColumnFacet)
{
// Header or Footer present on either the UIData or a column, so we
// definitely need to render the THEAD or TFOOT section.
String elemName = determineHeaderFooterTag(facesContext, component, header);
if (elemName != null)
{
writer.startElement(elemName, null); // component);
}
if (header)
{
String headerStyleClass = getHeaderClass(component);
if (facet != null)
{
renderTableHeaderRow(facesContext, writer, component, facet, headerStyleClass, colspan);
}
if (hasColumnFacet)
{
renderColumnHeaderRow(facesContext, writer, component, headerStyleClass);
}
}
else
{
String footerStyleClass = getFooterClass(component);
if (hasColumnFacet)
{
renderColumnFooterRow(facesContext, writer, component, footerStyleClass);
}
if (facet != null)
{
renderTableFooterRow(facesContext, writer, component, facet, footerStyleClass, colspan);
}
}
if (elemName != null)
{
writer.endElement(elemName);
}
}
}
protected String determineHeaderFooterTag(FacesContext facesContext, UIComponent component, boolean header)
{
return header ? HTML.THEAD_ELEM : HTML.TFOOT_ELEM;
}
/**
* @param header
* @param uiComponent
* @return boolean
*/
protected boolean hasFacet(boolean header, UIComponent uiComponent)
{
if (uiComponent instanceof UIColumn)
{
UIColumn uiColumn = (UIColumn) uiComponent;
return header ? uiColumn.getHeader() != null : uiColumn.getFooter() != null;
}
return false;
}
/**
* Calculate the number of columns the specified child component will span
* when rendered.
* <p>
* Normally, this is a fairly simple calculation: a UIColumn component
* is rendered as one column, every other child type is not rendered
* (ie spans zero columns). However custom subclasses of this renderer may
* override this method to handle cases where a single component renders
* as multiple columns.
*/
protected int determineChildColSpan(UIComponent uiComponent)
{
if (uiComponent instanceof UIColumn)
{
return 1;
}
return 0;
}
/**
* Renders the header row of the table being rendered.
* @param facesContext the <code>FacesContext</code>.
* @param writer the <code>ResponseWriter</code>.
* @param component the <code>UIComponent</code> for whom a table is being rendered.
* @param headerFacet the facet for the header.
* @param headerStyleClass the styleClass of the header.
* @param colspan the number of columns the header should span. Typically, this is
* the number of columns in the table.
* @throws IOException if an exception occurs.
*/
protected void renderTableHeaderRow(FacesContext facesContext, ResponseWriter writer, UIComponent component,
UIComponent headerFacet, String headerStyleClass, int colspan) throws IOException
{
renderTableHeaderOrFooterRow(facesContext, writer, component, headerFacet, headerStyleClass,
determineHeaderCellTag(facesContext, component),
colspan, true);
}
/**
* Renders the footer row of the table being rendered.
* @param facesContext the <code>FacesContext</code>.
* @param writer the <code>ResponseWriter</code>.
* @param component the <code>UIComponent</code> for whom a table is being rendered.
* @param footerFacet the facet for the footer.
* @param footerStyleClass the styleClass of the footer.
* @param colspan the number of columns the header should span. Typically, this is
* the number of columns in the table.
* @throws IOException if an exception occurs.
*/
protected void renderTableFooterRow(FacesContext facesContext, ResponseWriter writer, UIComponent component,
UIComponent footerFacet, String footerStyleClass, int colspan) throws IOException
{
renderTableHeaderOrFooterRow(facesContext, writer, component, footerFacet, footerStyleClass, HTML.TD_ELEM,
colspan, false);
}
/**
* Renders the header row for the columns, which is a separate row from the header row for the
* <code>UIData</code> header facet.
*
* @param facesContext the <code>FacesContext</code>.
* @param writer the <code>ResponseWriter</code>.
* @param component the UIData component for whom a table is being rendered.
* @param headerStyleClass the styleClass of the header
* @throws IOException if an exception occurs.
*/
protected void renderColumnHeaderRow(FacesContext facesContext, ResponseWriter writer, UIComponent component,
String headerStyleClass) throws IOException
{
renderColumnHeaderOrFooterRow(facesContext, writer, component, headerStyleClass, true);
}
/**
* Renders the footer row for the columns, which is a separate row from the footer row for the
* <code>UIData</code> footer facet.
* @param facesContext the <code>FacesContext</code>.
* @param writer the <code>ResponseWriter</code>.
* @param component the <code>UIComponent</code> for whom a table is being rendered.
* @param footerStyleClass the styleClass of the footerStyleClass
* @throws IOException if an exception occurs.
*/
protected void renderColumnFooterRow(FacesContext facesContext, ResponseWriter writer, UIComponent component,
String footerStyleClass) throws IOException
{
renderColumnHeaderOrFooterRow(facesContext, writer, component, footerStyleClass, false);
}
protected void renderTableHeaderOrFooterRow(FacesContext facesContext, ResponseWriter writer,
UIComponent component,
UIComponent facet, String styleClass, String colElementName, int colspan, boolean isHeader)
throws IOException
{
writer.startElement(HTML.TR_ELEM, null); // component);
writer.startElement(colElementName, null); // component);
if (colElementName.equals(determineHeaderCellTag(facesContext, component)) && isHeader)
{
writer.writeAttribute(HTML.SCOPE_ATTR, HTML.SCOPE_COLGROUP_VALUE, null);
}
// span all the table's columns
int newsPaperColumns = getNewspaperColumns(component);
int totalColumns = colspan * newsPaperColumns;
if(hasNewspaperTableSpacer(component))
{
totalColumns = totalColumns + newsPaperColumns - 1;
}
// Only render colspan if is > 0
if (totalColumns > 0)
{
writer.writeAttribute(HTML.COLSPAN_ATTR, Integer.valueOf(totalColumns), null);
}
if (styleClass != null)
{
writer.writeAttribute(HTML.CLASS_ATTR, styleClass, null);
}
if (facet != null)
{
//RendererUtils.renderChild(facesContext, facet);
facet.encodeAll(facesContext);
}
writer.endElement(colElementName);
writer.endElement(HTML.TR_ELEM);
}
/**
* @param component the UIData component for whom a table is being rendered.
*/
private void renderColumnHeaderOrFooterRow(FacesContext facesContext, ResponseWriter writer,
UIComponent component, String styleClass, boolean header) throws IOException
{
writer.startElement(HTML.TR_ELEM, null); // component);
int columnIndex = 0;
int newspaperColumns = getNewspaperColumns(component);
for(int nc = 0; nc < newspaperColumns; nc++)
{
for (int i = 0, childCount = component.getChildCount(); i < childCount; i++)
{
UIComponent uiComponent = component.getChildren().get(i);
if (uiComponent.isRendered())
{
if (component instanceof UIData && uiComponent instanceof UIColumn)
{
beforeColumnHeaderOrFooter(facesContext, (UIData) component, header, columnIndex);
}
renderColumnChildHeaderOrFooterRow(facesContext, writer, uiComponent, styleClass, header);
if (component instanceof UIData && uiComponent instanceof UIColumn)
{
afterColumnHeaderOrFooter(facesContext, (UIData) component, header, columnIndex);
}
}
columnIndex += 1;
}
if (hasNewspaperTableSpacer(component))
{
// draw the spacer facet
if(nc < newspaperColumns - 1)
{
renderSpacerCell(facesContext, writer, component);
}
}
}
writer.endElement(HTML.TR_ELEM);
}
/**
* Renders a spacer between adjacent newspaper columns.
*/
protected void renderSpacerCell(FacesContext facesContext, ResponseWriter writer, UIComponent component)
throws IOException
{
UIComponent spacer = getNewspaperTableSpacer(component);
if(spacer == null)
{
return;
}
writer.startElement(HTML.TD_ELEM, null); // component);
//RendererUtils.renderChild(facesContext, spacer);
spacer.encodeAll(facesContext);
writer.endElement(HTML.TD_ELEM);
}
protected void renderColumnChildHeaderOrFooterRow(FacesContext facesContext,
ResponseWriter writer, UIComponent uiComponent, String styleClass, boolean isHeader) throws IOException
{
if (uiComponent instanceof UIColumn)
{
// allow column to override style class, new in JSF 1.2
if (uiComponent instanceof HtmlColumn)
{
HtmlColumn column = (HtmlColumn)uiComponent;
if (isHeader && column.getHeaderClass()!=null)
{
styleClass = column.getHeaderClass();
}
else if (!isHeader && column.getFooterClass()!=null)
{
styleClass = column.getFooterClass();
}
}
else
{
//This code corrects MYFACES-1790, because HtmlColumnTag
//has as component type javax.faces.Column, so as side
//effect it not create HtmlColumn, it create UIColumn
//classes.
UIColumn column = (UIColumn) uiComponent;
if (isHeader)
{
String headerClass = (String) column.getAttributes().get("headerClass");
if (headerClass != null)
{
styleClass = (String) headerClass;
}
}
else
{
String footerClass = (String) column.getAttributes().get("footerClass");
if (footerClass != null)
{
styleClass = (String) footerClass;
}
}
}
if (isHeader)
{
renderColumnHeaderCell(facesContext, writer, uiComponent,
((UIColumn) uiComponent).getHeader(), styleClass, 0);
}
else
{
renderColumnFooterCell(facesContext, writer, uiComponent,
((UIColumn) uiComponent).getFooter(), styleClass, 0);
}
}
}
/**
* Renders the header facet for the given <code>UIColumn</code>.
* @param facesContext the <code>FacesContext</code>.
* @param writer the <code>ResponseWriter</code>.
* @param uiColumn the <code>UIColumn</code>.
* @param headerStyleClass the styleClass of the header facet.
* @param colspan the colspan for the tableData element in which the header facet
* will be wrapped.
* @throws IOException
*/
protected void renderColumnHeaderCell(FacesContext facesContext, ResponseWriter writer, UIColumn uiColumn,
String headerStyleClass, int colspan) throws IOException
{
renderColumnHeaderCell(facesContext, writer, uiColumn, uiColumn.getHeader(), headerStyleClass, colspan);
}
/**
* Renders a TH cell within a TR within a THEAD section. If the specified
* UIColumn object does have a header facet, then that facet is rendered
* within the cell, otherwise the cell is left blank (though any specified
* style class is still applied to empty cells).
*
* @param facesContext the <code>FacesContext</code>.
* @param writer the <code>ResponseWriter</code>.
* @param uiComponent the <code>UIComponent</code> to render the facet for.
* @param facet the <code>UIComponent</code> to render as facet.
* @param headerStyleClass the styleClass of the header facet.
* @param colspan the colspan for the tableData element in which the header facet
* will be wrapped.
* @throws IOException
*/
protected void renderColumnHeaderCell(FacesContext facesContext, ResponseWriter writer, UIComponent uiComponent,
UIComponent facet, String headerStyleClass, int colspan) throws IOException
{
writer.startElement(determineHeaderCellTag(facesContext, uiComponent.getParent()), null); // uiComponent);
if (colspan > 1)
{
writer.writeAttribute(HTML.COLSPAN_ATTR, Integer.valueOf(colspan), null);
}
if (headerStyleClass != null)
{
writer.writeAttribute(HTML.CLASS_ATTR, headerStyleClass, null);
}
writer.writeAttribute(HTML.SCOPE_ATTR, "col", null);
if (facet != null)
{
//RendererUtils.renderChild(facesContext, facet);
facet.encodeAll(facesContext);
}
writer.endElement(determineHeaderCellTag(facesContext, uiComponent.getParent()));
}
protected String determineHeaderCellTag(FacesContext facesContext, UIComponent uiComponent)
{
return HTML.TH_ELEM;
}
/**
* Renders the footer facet for the given <code>UIColumn</code>.
* @param facesContext the <code>FacesContext</code>.
* @param writer the <code>ResponseWriter</code>.
* @param uiColumn the <code>UIComponent</code>.
* @param footerStyleClass the styleClass of the footer facet.
* @param colspan the colspan for the tableData element in which the footer facet
* will be wrapped.
* @throws IOException
*/
protected void renderColumnFooterCell(FacesContext facesContext, ResponseWriter writer, UIColumn uiColumn,
String footerStyleClass, int colspan) throws IOException
{
renderColumnFooterCell(facesContext, writer, uiColumn, uiColumn.getFooter(), footerStyleClass, colspan);
}
/**
* Renders the footer facet for the given <code>UIColumn</code>.
* @param facesContext the <code>FacesContext</code>.
* @param writer the <code>ResponseWriter</code>.
* @param uiComponent the <code>UIComponent</code> to render the facet for.
* @param facet the <code>UIComponent</code> to render as facet.
* @param footerStyleClass the styleClass of the footer facet.
* @param colspan the colspan for the tableData element in which the footer facet
* will be wrapped.
* @throws IOException
*/
protected void renderColumnFooterCell(FacesContext facesContext, ResponseWriter writer, UIComponent uiComponent,
UIComponent facet, String footerStyleClass, int colspan) throws IOException
{
writer.startElement(HTML.TD_ELEM, null); // uiComponent);
if (colspan > 1)
{
writer.writeAttribute(HTML.COLSPAN_ATTR, Integer.valueOf(colspan), null);
}
if (footerStyleClass != null)
{
writer.writeAttribute(HTML.CLASS_ATTR, footerStyleClass, null);
}
if (facet != null)
{
//RendererUtils.renderChild(facesContext, facet);
facet.encodeAll(facesContext);
}
writer.endElement(HTML.TD_ELEM);
}
/**
* Gets the headerClass attribute of the given <code>UIComponent</code>.
* @param component the <code>UIComponent</code>.
* @return the headerClass attribute of the given <code>UIComponent</code>.
*/
protected static String getHeaderClass(UIComponent component)
{
if (component instanceof HtmlDataTable)
{
return ((HtmlDataTable) component).getHeaderClass();
}
else
{
return (String) component.getAttributes().get(
JSFAttr.HEADER_CLASS_ATTR);
}
}
/**
* Gets the footerClass attribute of the given <code>UIComponent</code>.
* @param component the <code>UIComponent</code>.
* @return the footerClass attribute of the given <code>UIComponent</code>.
*/
protected static String getFooterClass(UIComponent component)
{
if (component instanceof HtmlDataTable)
{
return ((HtmlDataTable) component).getFooterClass();
}
else
{
return (String) component.getAttributes().get(JSFAttr.FOOTER_CLASS_ATTR);
}
}
@Override
public void decode(FacesContext context, UIComponent component)
{
super.decode(context, component);
ClientBehaviorRendererUtils.decodeClientBehaviors(context, component);
}
}