org.apache.myfaces.component.html.ext.AbstractHtmlDataTable Maven / Gradle / Ivy
Show all versions of tomahawk Show documentation
/*
* 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.component.html.ext;
import java.io.IOException;
import java.sql.ResultSet;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.StringTokenizer;
import javax.faces.application.Application;
import javax.faces.component.EditableValueHolder;
import javax.faces.component.NamingContainer;
import javax.faces.component.UIColumn;
import javax.faces.component.UIComponent;
import javax.faces.context.FacesContext;
import javax.faces.el.ValueBinding;
import javax.faces.model.DataModel;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.myfaces.component.NewspaperTable;
import org.apache.myfaces.component.UserRoleAware;
import org.apache.myfaces.component.UserRoleUtils;
import org.apache.myfaces.custom.column.HtmlSimpleColumn;
import org.apache.myfaces.custom.crosstable.UIColumns;
import org.apache.myfaces.custom.sortheader.HtmlCommandSortHeader;
import org.apache.myfaces.renderkit.html.ext.HtmlTableRenderer;
import org.apache.myfaces.renderkit.html.util.TableContext;
import org.apache.myfaces.shared_tomahawk.renderkit.JSFAttr;
import org.apache.myfaces.shared_tomahawk.util.ClassUtils;
/**
* The MyFacesDataTable extends the standard JSF DataTable by two
* important features:
*
*
* - Possiblity to save the state of the DataModel.
*
* - Support for clickable sort headers (see SortHeader
* component).
*
*
* Extended data_table that adds some additional features to the
* standard data_table action: see attribute descriptions for
* preserveDataModel, sortColumn, sortAscending and preserveSort.
*
* Unless otherwise specified, all attributes accept static values or EL expressions.
*
* @JSFComponent
* name = "t:dataTable"
* class = "org.apache.myfaces.component.html.ext.HtmlDataTable"
* tagClass = "org.apache.myfaces.generated.taglib.html.ext.HtmlDataTableTag"
* @since 1.1.7
* @author Thomas Spiegl (latest modification by $Author: lu4242 $)
* @author Manfred Geiler
* @version $Revision: 1124510 $ $Date: 2011-05-19 04:00:26 +0200 (Thu, 19 May 2011) $
*/
public abstract class AbstractHtmlDataTable extends HtmlDataTableHack implements UserRoleAware, NewspaperTable
{
private static final Log log = LogFactory.getLog(AbstractHtmlDataTable.class);
private static final int PROCESS_DECODES = 1;
private static final int PROCESS_VALIDATORS = 2;
private static final int PROCESS_UPDATES = 3;
private static final boolean DEFAULT_SORTASCENDING = true;
private static final boolean DEFAULT_SORTABLE = false;
private static final boolean DEFAULT_EMBEDDED = false;
private static final boolean DEFAULT_DETAILSTAMP_EXPANDED = false;
private static final Class OBJECT_ARRAY_CLASS = (new Object[0]).getClass();
private static final Integer DEFAULT_NEWSPAPER_COLUMNS = new Integer(1);
private static final String DEFAULT_NEWSPAPER_ORIENTATION = "vertical";
private static final String DEFAULT_DETAILSTAMP_LOCATION = "after";
/**
* the property names
*/
public static final String NEWSPAPER_COLUMNS_PROPERTY = "newspaperColumns";
public static final String SPACER_FACET_NAME = "spacer";
public static final String NEWSPAPER_ORIENTATION_PROPERTY = "newspaperOrientation";
public static final String DETAIL_STAMP_FACET_NAME = "detailStamp";
private _SerializableDataModel _preservedDataModel;
private String _forceIdIndexFormula = null;
private String _sortColumn = null;
private Boolean _sortAscending = null;
private String _sortProperty = null;
private String _rowStyleClass = null;
private String _rowStyle = null;
private String _varDetailToggler = null;
private int _sortColumnIndex = -1;
private boolean _isValidChildren = true;
private Map _expandedNodes = new HashMap();
//private Map _detailRowStates = new HashMap();
private TableContext _tableContext = null;
public TableContext getTableContext()
{
if (_tableContext == null)
{
_tableContext = new TableContext();
}
return _tableContext;
}
public void setDetailStamp(UIComponent facet)
{
getFacets().put(DETAIL_STAMP_FACET_NAME, facet);
}
/**
* This facet renders an additional row after or before (according
* to detailStampLocation value) the current row, usually containing
* additional information of the related row. It is toggled usually
* using varDetailToggle variable and the method toggleDetail().
*
* @JSFFacet name="detailStamp"
*/
public UIComponent getDetailStamp()
{
return (UIComponent) getFacets().get(DETAIL_STAMP_FACET_NAME);
}
public String getClientId(FacesContext context)
{
String standardClientId = super.getClientId(context);
int rowIndex = getRowIndex();
if (rowIndex == -1)
{
return standardClientId;
}
String forcedIdIndex = getForceIdIndexFormula();
if (forcedIdIndex == null || forcedIdIndex.length() == 0)
return standardClientId;
// Trick : Remove the last part starting with NamingContainer.SEPARATOR_CHAR that contains the rowIndex.
// It would be best to not resort to String manipulation,
// but we can't get super.super.getClientId() :-(
int indexLast_ = standardClientId.lastIndexOf(NamingContainer.SEPARATOR_CHAR);
if (indexLast_ == -1)
{
log.info("Could not parse super.getClientId. forcedIdIndex will contain the rowIndex.");
return standardClientId + NamingContainer.SEPARATOR_CHAR + forcedIdIndex;
}
//noinspection UnnecessaryLocalVariable
String parsedForcedClientId = standardClientId.substring(0, indexLast_ + 1) + forcedIdIndex;
return parsedForcedClientId;
}
public UIComponent findComponent(String expr)
{
if (expr.length() > 0 && Character.isDigit(expr.charAt(0)))
{
int separatorIndex = expr.indexOf(NamingContainer.SEPARATOR_CHAR);
String rowIndexStr = expr;
String remainingPart = null;
if (separatorIndex != -1)
{
rowIndexStr = expr.substring(0, separatorIndex);
remainingPart = expr.substring(separatorIndex + 1);
}
int rowIndex = Integer.valueOf(rowIndexStr).intValue();
if (remainingPart == null)
{
log.error("Wrong syntax of expression : " + expr +
" rowIndex was provided, but no component name.");
return null;
}
UIComponent comp = super.findComponent(remainingPart);
if (comp == null)
return null;
//noinspection UnnecessaryLocalVariable
UIComponentPerspective perspective = new UIComponentPerspective(this, comp, rowIndex);
return perspective;
}
else
{
return super.findComponent(expr);
}
}
public void setRowIndex(int rowIndex)
{
//FacesContext facesContext = FacesContext.getCurrentInstance();
if (rowIndex < -1)
{
throw new IllegalArgumentException("rowIndex is less than -1");
}
//UIComponent facet = getFacet(HtmlTableRenderer.DETAIL_STAMP_FACET_NAME);
/*Just for obtaining an iterator which must be passed to saveDescendantComponentStates()*/
//Set set = new HashSet();
//set.add(facet);
//if (getRowIndex() != -1 && facet != null)
//{
// _detailRowStates.put(getClientId(facesContext), saveDescendantComponentStates(set.iterator(), false));
//}
String rowIndexVar = getRowIndexVar();
String rowCountVar = getRowCountVar();
String previousRowDataVar = getPreviousRowDataVar();
if (rowIndexVar != null || rowCountVar != null || previousRowDataVar != null)
{
Map requestMap = FacesContext.getCurrentInstance().getExternalContext().getRequestMap();
if (previousRowDataVar != null && rowIndex >= 0) //we only need to provide the previousRowDataVar for a valid rowIndex
{
if (isRowAvailable())
{
//previous row is available
requestMap.put(previousRowDataVar, getRowData());
}
else
{
//no previous row available
requestMap.put(previousRowDataVar, null);
}
}
super.setRowIndex(rowIndex);
if (rowIndex >= 0)
{
//regular row index, update request scope variables
if (rowIndexVar != null)
{
requestMap.put(rowIndexVar, new Integer(rowIndex));
}
if (rowCountVar != null)
{
requestMap.put(rowCountVar, new Integer(getRowCount()));
}
}
else
{
//rowIndex == -1 means end of loop --> remove request scope variables
if (rowIndexVar != null)
{
requestMap.remove(rowIndexVar);
}
if (rowCountVar != null)
{
requestMap.remove(rowCountVar);
}
if (previousRowDataVar != null)
{
requestMap.remove(previousRowDataVar);
}
}
}
else
{
// no extended var attributes defined, no special treatment
super.setRowIndex(rowIndex);
}
//if (rowIndex != -1 && facet != null)
//{
// Object rowState = _detailRowStates.get(getClientId(facesContext));
// restoreDescendantComponentStates(set.iterator(),
// rowState, false);
//}
if (_varDetailToggler != null)
{
Map requestMap = getFacesContext().getExternalContext().getRequestMap();
requestMap.put(_varDetailToggler, this);
}
}
protected Object saveDescendantComponentStates()
{
if (!getFacets().isEmpty())
{
UIComponent detailStampFacet = getFacet(DETAIL_STAMP_FACET_NAME);
if (detailStampFacet != null)
{
return saveDescendantComponentStates(new _DetailStampFacetAndChildrenIterator(detailStampFacet, getChildren()), false);
}
}
return super.saveDescendantComponentStates();
}
protected void restoreDescendantComponentStates(Object state)
{
if (!getFacets().isEmpty())
{
UIComponent detailStampFacet = getFacet(DETAIL_STAMP_FACET_NAME);
if (detailStampFacet != null)
{
restoreDescendantComponentStates(new _DetailStampFacetAndChildrenIterator(detailStampFacet, getChildren()), state, false);
return;
}
}
super.restoreDescendantComponentStates(state);
}
public void processDecodes(FacesContext context)
{
if (!isRendered())
{
return;
}
// We must remove and then replace the facet so that
// it is not processed by default facet handling code
//
//Object facet = getFacets().remove(HtmlTableRenderer.DETAIL_STAMP_FACET_NAME);
//super.processDecodes(context);
//if ( facet != null ) getFacets().put(HtmlTableRenderer.DETAIL_STAMP_FACET_NAME, (UIComponent)facet);
setRowIndex(-1);
processFacets(context, PROCESS_DECODES);
processColumnFacets(context, PROCESS_DECODES);
processColumnChildren(context, PROCESS_DECODES);
setRowIndex(-1);
try
{
decode(context);
}
catch (RuntimeException e)
{
context.renderResponse();
throw e;
}
setRowIndex(-1);
processColumns(context, PROCESS_DECODES);
setRowIndex(-1);
processDetails(context, PROCESS_DECODES);
setRowIndex(-1);
}
private void processFacets(FacesContext context, int processAction)
{
for (Iterator it = getFacets().entrySet().iterator(); it.hasNext(); )
{
Map.Entry entry = (Map.Entry) it.next();
if (!DETAIL_STAMP_FACET_NAME.equals((String)entry.getKey()))
{
process(context, (UIComponent) entry.getValue(), processAction);
}
}
}
/**
* Invoke the specified phase on all facets of all UIColumn children of this component. Note that no methods are
* called on the UIColumn child objects themselves.
*
* @param context
* is the current faces context.
* @param processAction
* specifies a JSF phase: decode, validate or update.
*/
private void processColumnFacets(FacesContext context, int processAction)
{
for (Iterator childIter = getChildren().iterator(); childIter.hasNext();)
{
UIComponent child = (UIComponent) childIter.next();
if (child instanceof UIColumn)
{
if (!child.isRendered())
{
//Column is not visible
continue;
}
for (Iterator facetsIter = child.getFacets().values()
.iterator(); facetsIter.hasNext();)
{
UIComponent facet = (UIComponent) facetsIter.next();
process(context, facet, processAction);
}
}
}
}
/**
* Invoke the specified phase on all non-facet children of all UIColumn children of this component. Note that no
* methods are called on the UIColumn child objects themselves.
*
* @param context
* is the current faces context.
* @param processAction
* specifies a JSF phase: decode, validate or update.
*/
private void processColumnChildren(FacesContext context, int processAction)
{
int first = getFirst();
int rows = getRows();
int last;
if (rows == 0)
{
last = getRowCount();
}
else
{
last = first + rows;
}
for (int rowIndex = first; last==-1 || rowIndex < last; rowIndex++)
{
setRowIndex(rowIndex);
//scrolled past the last row
if (!isRowAvailable())
break;
for (Iterator it = getChildren().iterator(); it.hasNext();)
{
UIComponent child = (UIComponent) it.next();
if (child instanceof UIColumn)
{
if (!child.isRendered())
{
//Column is not visible
continue;
}
for (Iterator columnChildIter = child.getChildren()
.iterator(); columnChildIter.hasNext();)
{
UIComponent columnChild = (UIComponent) columnChildIter
.next();
process(context, columnChild, processAction);
}
}
}
}
}
/**
* @param context
* @param processAction
*/
private void processDetails(FacesContext context, int processAction)
{
UIComponent facet = getFacet(HtmlTableRenderer.DETAIL_STAMP_FACET_NAME);
if (facet != null)
{
int first = getFirst();
int rows = getRows();
int last;
if (rows == 0)
{
last = getRowCount();
}
else
{
last = first + rows;
}
for (int rowIndex = first; last == -1 || rowIndex < last; rowIndex++)
{
setRowIndex(rowIndex);
//scrolled past the last row
if (!isRowAvailable())
{
break;
}
if (!isCurrentDetailExpanded())
{
continue;
}
// If we are in the decode phase, the values restored into our
// facet in setRowIndex() may be incorrect. This will happen
// if some input fields are rendered in some rows, but not
// rendered in others. In this case the input field components
// will still contain the _submittedValue from the previous row
// that had that input field and _submittedValue will not be set to
// null by the process() method if there was no value submitted.
// Thus, an invalid component state for that row will be saved in
// _detailRowStates. The validation phase will not put a null into
// _sumbittedValue either, b/c the component is not rendered, so
// validation code doesn't run. This erroneous value will propagate all the way
// to the render phase, and result in all rows on the current page being
// rendered with the "stuck" _submittedValue, rather than evaluating the
// value to render for every row.
//
// We can fix this by initializing _submittedValue of all input fields in the facet
// to null before calling the process() method below during the decode phase.
//
if (PROCESS_DECODES == processAction)
{
resetAllSubmittedValues(facet);
}
process(context, facet, processAction);
// This code comes from TOMAHAWK-493, but really the problem was caused by
// TOMAHAWK-1534, by a bad comparison of rowIndex. The solution proposed is override
// the default iterator for save/restore on HtmlDataTableHack.setRowIndex(), to
// include the detailStamp. In this way, we'll be sure that the state is correctly
// saved and the component clientId is reset for all components that requires it
//if ( rowIndex == (last - 1) )
//{
// Set set = new HashSet();
// set.add(facet);
// _detailRowStates.put(
// getClientId(FacesContext.getCurrentInstance()),
// saveDescendantComponentStates(set.iterator(),false));
//}
}
}
}
private void resetAllSubmittedValues(UIComponent component)
{
if (component instanceof EditableValueHolder)
{
((EditableValueHolder) component).setSubmittedValue(null);
}
for (Iterator it = component.getFacetsAndChildren(); it.hasNext();)
{
resetAllSubmittedValues((UIComponent) it.next());
}
}
/**
* @param context
* @param processAction
*/
private void processColumns(FacesContext context, int processAction)
{
for (Iterator it = getChildren().iterator(); it.hasNext();)
{
UIComponent child = (UIComponent) it.next();
if (child instanceof UIColumns)
{
process(context, child, processAction);
}
}
}
private void process(FacesContext context, UIComponent component, int processAction)
{
switch (processAction)
{
case PROCESS_DECODES:
component.processDecodes(context);
break;
case PROCESS_VALIDATORS:
component.processValidators(context);
break;
case PROCESS_UPDATES:
component.processUpdates(context);
break;
}
}
public void processValidators(FacesContext context)
{
if (!isRendered())
{
return;
}
// We must remove and then replace the facet so that
// it is not processed by default facet handling code
//
//Object facet = getFacets().remove(HtmlTableRenderer.DETAIL_STAMP_FACET_NAME);
//super.processValidators(context);
//if ( facet != null ) getFacets().put(HtmlTableRenderer.DETAIL_STAMP_FACET_NAME,(UIComponent) facet);
setRowIndex(-1);
processFacets(context, PROCESS_VALIDATORS);
processColumnFacets(context, PROCESS_VALIDATORS);
processColumnChildren(context, PROCESS_VALIDATORS);
setRowIndex(-1);
processColumns(context, PROCESS_VALIDATORS);
setRowIndex(-1);
processDetails(context, PROCESS_VALIDATORS);
setRowIndex(-1);
if (context.getRenderResponse())
{
_isValidChildren = false;
}
}
public void processUpdates(FacesContext context)
{
if (!isRendered())
{
return;
}
// We must remove and then replace the facet so that
// it is not processed by default facet handling code
//
//Object facet = getFacets().remove(HtmlTableRenderer.DETAIL_STAMP_FACET_NAME);
//super.processUpdates(context);
//if ( facet != null ) getFacets().put(HtmlTableRenderer.DETAIL_STAMP_FACET_NAME,(UIComponent) facet);
setRowIndex(-1);
processFacets(context, PROCESS_UPDATES);
processColumnFacets(context, PROCESS_UPDATES);
processColumnChildren(context, PROCESS_UPDATES);
setRowIndex(-1);
processColumns(context, PROCESS_UPDATES);
setRowIndex(-1);
processDetails(context, PROCESS_UPDATES);
setRowIndex(-1);
if (isPreserveDataModel())
{
updateModelFromPreservedDataModel(context);
}
if (context.getRenderResponse())
{
_isValidChildren = false;
}
}
private void updateModelFromPreservedDataModel(FacesContext context)
{
ValueBinding vb = getValueBinding("value");
if (vb != null && !vb.isReadOnly(context))
{
_SerializableDataModel dm = (_SerializableDataModel) getDataModel();
Class type = (getValueType() == null) ?
vb.getType(context) :
ClassUtils.simpleClassForName(getValueType());
Class dmType = dm.getClass();
if (DataModel.class.isAssignableFrom(type))
{
vb.setValue(context, dm);
}
else if (List.class.isAssignableFrom(type) || _SerializableListDataModel.class.isAssignableFrom(dmType))
{
vb.setValue(context, dm.getWrappedData());
}
else if (OBJECT_ARRAY_CLASS.isAssignableFrom(type))
{
List lst = (List) dm.getWrappedData();
vb.setValue(context, lst.toArray(new Object[lst.size()]));
}
else if (ResultSet.class.isAssignableFrom(type))
{
throw new UnsupportedOperationException(this.getClass().getName()
+ " UnsupportedOperationException");
}
else
{
//Assume scalar data model
List lst = (List) dm.getWrappedData();
if (lst!= null && lst.size() > 0)
{
vb.setValue(context, lst.get(0));
}
else
{
vb.setValue(context, null);
}
}
}
_preservedDataModel = null;
}
public void encodeBegin(FacesContext context) throws IOException
{
if (!isRendered())
return;
if (_isValidChildren && !hasErrorMessages(context))
{
_preservedDataModel = null;
}
for (Iterator iter = getChildren().iterator(); iter.hasNext();)
{
UIComponent component = (UIComponent) iter.next();
if (component instanceof UIColumns)
{
// Merge the columns from the tomahawk dynamic component
// into this object.
((UIColumns) component).encodeTableBegin(context);
}
}
//replace facet header content component of the columns, with a new command sort header component
//if sortable=true, replace it for all, if not just for the columns marked as sortable
for (Iterator iter = getChildren().iterator(); iter.hasNext();)
{
UIComponent component = (UIComponent) iter.next();
if (component instanceof UIColumn)
{
UIColumn aColumn = (UIColumn) component;
UIComponent headerFacet = aColumn.getHeader();
boolean replaceHeaderFacets = isSortable(); //if the table is sortable, all
//header facets can be changed if needed
String columnName = null;
String propertyName = null;
boolean defaultSorted = false;
if (aColumn instanceof HtmlSimpleColumn)
{
HtmlSimpleColumn asColumn = (HtmlSimpleColumn) aColumn;
propertyName = asColumn.getSortPropertyName();
defaultSorted = asColumn.isDefaultSorted();
if (asColumn.isSortable())
replaceHeaderFacets = true;
}
//replace header facet with a sortable header component if needed
if (replaceHeaderFacets && isSortHeaderNeeded(aColumn, headerFacet))
{
propertyName = propertyName != null ? propertyName : getSortPropertyFromEL(aColumn);
if (propertyName == null)
log.warn("Couldn't determine sort property for column [" + aColumn.getId() + "].");
if (headerFacet != null)
{
HtmlCommandSortHeader sortHeader = createSortHeaderComponent(context, aColumn, headerFacet, propertyName);
columnName = sortHeader.getColumnName();
aColumn.setHeader(sortHeader);
sortHeader.setParent(aColumn);
}
}
else if (headerFacet instanceof HtmlCommandSortHeader)
{
//command sort headers are already in place, just store the column name and sort property name
HtmlCommandSortHeader sortHeader = (HtmlCommandSortHeader) headerFacet;
columnName = sortHeader.getColumnName();
propertyName = sortHeader.getPropertyName();
//if the command sort header component doesn't specify a sort property, determine it
if (propertyName == null)
{
propertyName = getSortPropertyFromEL(aColumn);
sortHeader.setPropertyName(propertyName);
}
if (propertyName == null)
log.warn("Couldn't determine sort property for column [" + aColumn.getId() + "].");
}
//make the column marked as default sorted be the current sorted column
if (defaultSorted && getSortColumn() == null)
{
setSortColumn(columnName);
setSortProperty(propertyName);
}
}
}
// Now invoke the superclass encodeBegin, which will eventually
// execute the encodeBegin for the associated renderer.
super.encodeBegin(context);
}
/**
*
*/
protected boolean isSortHeaderNeeded(UIColumn parentColumn, UIComponent headerFacet)
{
return !(headerFacet instanceof HtmlCommandSortHeader);
}
/**
*
*/
protected HtmlCommandSortHeader createSortHeaderComponent(FacesContext context, UIColumn parentColumn, UIComponent initialHeaderFacet, String propertyName)
{
Application application = context.getApplication();
HtmlCommandSortHeader sortHeader = (HtmlCommandSortHeader) application.createComponent(HtmlCommandSortHeader.COMPONENT_TYPE);
String id = context.getViewRoot().createUniqueId();
sortHeader.setId(id);
sortHeader.setColumnName(id);
sortHeader.setPropertyName(propertyName);
sortHeader.setArrow(true);
sortHeader.setImmediate(true); //needed to work when dataScroller is present
sortHeader.getChildren().add(initialHeaderFacet);
initialHeaderFacet.setParent(sortHeader);
return sortHeader;
}
/**
*
*/
protected String getSortPropertyFromEL(UIComponent component)
{
if (getVar() == null)
{
log.warn("There is no 'var' specified on the dataTable, sort properties cannot be determined automaticaly.");
return null;
}
for (Iterator iter = component.getChildren().iterator(); iter.hasNext();)
{
UIComponent aChild = (UIComponent) iter.next();
if (aChild.isRendered())
{
ValueBinding vb = aChild.getValueBinding("value");
if (vb != null)
{
String expressionString = vb.getExpressionString();
int varIndex = expressionString.indexOf(getVar() + ".");
if (varIndex != -1)
{
int varEndIndex = varIndex + getVar().length();
String tempProp = expressionString.substring(varEndIndex + 1, expressionString.length());
StringTokenizer tokenizer = new StringTokenizer(tempProp, " +[]{}-/*|?:&<>!=()%");
if (tokenizer.hasMoreTokens())
return tokenizer.nextToken();
}
}
else
{
String sortProperty = getSortPropertyFromEL(aChild);
if (sortProperty != null)
return sortProperty;
}
}
}
return null;
}
/**
* @return the index coresponding to the given column name.
*/
protected int columnNameToIndex(String columnName)
{
int index = 0;
for (Iterator iter = getChildren().iterator(); iter.hasNext();)
{
UIComponent aChild = (UIComponent) iter.next();
if (aChild instanceof UIColumn)
{
UIComponent headerFacet = ((UIColumn) aChild).getHeader();
if (headerFacet != null && headerFacet instanceof HtmlCommandSortHeader)
{
HtmlCommandSortHeader sortHeader = (HtmlCommandSortHeader) headerFacet;
if (columnName != null && columnName.equals(sortHeader.getColumnName()))
return index;
}
}
index += 1;
}
return -1;
}
/**
* @see javax.faces.component.UIData#encodeEnd(javax.faces.context.FacesContext)
*/
public void encodeEnd(FacesContext context) throws IOException
{
super.encodeEnd(context);
for (Iterator iter = getChildren().iterator(); iter.hasNext();)
{
UIComponent component = (UIComponent) iter.next();
if (component instanceof UIColumns)
{
((UIColumns) component).encodeTableEnd(context);
}
}
}
/**
* The index of the first row to be displayed, where 0 is the first row.
*
* @JSFProperty
*/
public int getFirst()
{
if (_preservedDataModel != null)
{
//Rather get the currently restored DataModel attribute
return _preservedDataModel.getFirst();
}
else
{
return super.getFirst();
}
}
public void setFirst(int first)
{
if (_preservedDataModel != null)
{
//Also change the currently restored DataModel attribute
_preservedDataModel.setFirst(first);
}
super.setFirst(first);
}
/**
* The number of rows to be displayed. Specify zero for all remaining rows in the table.
*
* @JSFProperty
*/
public int getRows()
{
if (_preservedDataModel != null)
{
//Rather get the currently restored DataModel attribute
return _preservedDataModel.getRows();
}
else
{
return super.getRows();
}
}
public void setRows(int rows)
{
if (_preservedDataModel != null)
{
//Also change the currently restored DataModel attribute
_preservedDataModel.setRows(rows);
}
super.setRows(rows);
}
public Object saveState(FacesContext context)
{
boolean preserveSort = isPreserveSort();
Object[] values = new Object[15];
values[0] = super.saveState(context);
values[1] = _preserveDataModel;
if (isPreserveDataModel())
{
_preservedDataModel = getSerializableDataModel();
values[2] = saveAttachedState(context, _preservedDataModel);
}
else
{
values[2] = null;
}
values[3] = _preserveSort;
values[4] = _forceIdIndexFormula;
values[5] = _sortColumn;
values[6] = _sortAscending;
values[7] = _sortProperty;
values[8] = _rowStyleClass;
values[9] = _rowStyle;
values[10] = preserveSort ? getSortColumn() : null;
values[11] = preserveSort ? Boolean.valueOf(isSortAscending()) : null;
values[12] = _varDetailToggler;
values[13] = _expandedNodes;
values[14] = new Integer(_sortColumnIndex);
return values;
}
/**
* @see org.apache.myfaces.component.html.ext.HtmlDataTableHack#getDataModel()
*/
protected DataModel getDataModel()
{
if (_preservedDataModel != null)
{
setDataModel(_preservedDataModel);
_preservedDataModel = null;
}
return super.getDataModel();
}
/**
* @see org.apache.myfaces.component.html.ext.HtmlDataTableHack#createDataModel()
*/
protected DataModel createDataModel()
{
DataModel dataModel = super.createDataModel();
boolean isSortable = isSortable();
if (!(dataModel instanceof SortableModel))
{
//if sortable=true make each column sortable
//if sortable=false, check to see if at least one column sortable case in which
//the current model needs to be wrapped by a sortable one.
for (Iterator iter = getChildren().iterator(); iter.hasNext();)
{
UIComponent component = (UIComponent) iter.next();
if (component instanceof HtmlSimpleColumn)
{
HtmlSimpleColumn aColumn = (HtmlSimpleColumn) component;
if (isSortable())
aColumn.setSortable(true);
if (aColumn.isSortable())
isSortable = true;
if (aColumn.isDefaultSorted() && getSortColumn() == null)
setSortProperty(aColumn.getSortPropertyName());
}
}
if (isSortable)
dataModel = new SortableModel(dataModel);
}
if (isSortable && getSortProperty() != null)
{
SortCriterion criterion = new SortCriterion(getSortProperty(), isSortAscending());
List criteria = new ArrayList();
criteria.add(criterion);
((SortableModel) dataModel).setSortCriteria(criteria);
}
return dataModel;
}
public void restoreState(FacesContext context, Object state)
{
Object[] values = (Object[]) state;
super.restoreState(context, values[0]);
_preserveDataModel = (Boolean) values[1];
if (isPreserveDataModel())
{
_preservedDataModel = (_SerializableDataModel) restoreAttachedState(context, values[2]);
}
else
{
_preservedDataModel = null;
}
_preserveSort = (Boolean) values[3];
_forceIdIndexFormula = (String) values[4];
_sortColumn = (String) values[5];
_sortAscending = (Boolean) values[6];
_sortProperty = (String) values[7];
_rowStyleClass = (String) values[8];
_rowStyle = (String) values[9];
if (isPreserveSort())
{
String sortColumn = (String) values[10];
Boolean sortAscending = (Boolean) values[11];
if (sortColumn != null && sortAscending != null)
{
ValueBinding vb = getValueBinding("sortColumn");
if (vb != null && !vb.isReadOnly(context))
{
vb.setValue(context, sortColumn);
}
vb = getValueBinding("sortAscending");
if (vb != null && !vb.isReadOnly(context))
{
vb.setValue(context, sortAscending);
}
}
}
_varDetailToggler = (String) values[12];
_expandedNodes = (Map) values[13];
_sortColumnIndex = values[14] != null ? ((Integer) values[14]).intValue() : -1;
}
public _SerializableDataModel getSerializableDataModel()
{
DataModel dm = getDataModel();
if (dm instanceof _SerializableDataModel)
{
return (_SerializableDataModel) dm;
}
return createSerializableDataModel();
}
/**
* @return _SerializableDataModel
*/
private _SerializableDataModel createSerializableDataModel()
{
Object value = getValue();
if (value == null)
{
return null;
}
else if (value instanceof DataModel)
{
return new _SerializableDataModel(getFirst(), getRows(), (DataModel) value);
}
else if (value instanceof List)
{
return new _SerializableListDataModel(getFirst(), getRows(), (List) value);
}
// accept a Collection is not supported in the Spec
else if (value instanceof Collection)
{
return new _SerializableListDataModel(getFirst(), getRows(), new ArrayList((Collection) value));
}
else if (OBJECT_ARRAY_CLASS.isAssignableFrom(value.getClass()))
{
return new _SerializableArrayDataModel(getFirst(), getRows(), (Object[]) value);
}
else if (value instanceof ResultSet)
{
return new _SerializableResultSetDataModel(getFirst(), getRows(), (ResultSet) value);
}
else if (value instanceof javax.servlet.jsp.jstl.sql.Result)
{
return new _SerializableResultDataModel(getFirst(), getRows(),
(javax.servlet.jsp.jstl.sql.Result) value);
}
else
{
return new _SerializableScalarDataModel(getFirst(), getRows(), value);
}
}
public boolean isRendered()
{
if (!UserRoleUtils.isVisibleOnUserRole(this))
return false;
return super.isRendered();
}
public void setForceIdIndexFormula(String forceIdIndexFormula)
{
_forceIdIndexFormula = forceIdIndexFormula;
ValueBinding vb = getValueBinding("forceIdIndexFormula");
if (vb != null)
{
vb.setValue(getFacesContext(), _forceIdIndexFormula);
_forceIdIndexFormula = null;
}
}
/**
* A formula that overrides the default row index in the
* construction of table's body components.
*
* Example : #{myRowVar.key} Warning, the EL should
* evaluate to a unique value for each row !
*
* @JSFProperty
*/
public String getForceIdIndexFormula()
{
if (_forceIdIndexFormula != null)
return _forceIdIndexFormula;
ValueBinding vb = getValueBinding("forceIdIndexFormula");
if (vb == null)
return null;
Object eval = vb.getValue(getFacesContext());
return eval == null ? null : eval.toString();
}
/**
* Specify what column the data should be sorted on.
*
* Note that calling this method immediately stores the value
* via any value-binding with name "sortColumn". This is done because
* this method is called by the HtmlCommandSortHeader component when
* the user has clicked on a column's sort header. In this case, the
* the model getter method mapped for name "value" needs to read this
* value in able to return the data in the desired order - but the
* HtmlCommandSortHeader component is usually "immediate" in order to
* avoid validating the enclosing form. Yes, this is rather hacky -
* but it works.
*/
public void setSortColumn(String sortColumn)
{
_sortColumn = sortColumn;
// update model is necessary here, because processUpdates is never called
// reason: HtmlCommandSortHeader.isImmediate() == true
ValueBinding vb = getValueBinding("sortColumn");
if (vb != null)
{
vb.setValue(getFacesContext(), _sortColumn);
_sortColumn = null;
}
setSortColumnIndex(columnNameToIndex(sortColumn));
}
/**
* Value reference to a model property that gives the current
* sort column name. The target String property is set to
* the "columnName" of whichever column has been chosen
* to sort by, and the method which is bound to the "value"
* attribute of this table (ie which provides the DataModel used)
* is expected to use this property to determine how to sort
* the DataModel's contents.
*
* @JSFProperty
*/
public String getSortColumn()
{
if (_sortColumn != null) return _sortColumn;
ValueBinding vb = getValueBinding("sortColumn");
return vb != null ? (String) vb.getValue(getFacesContext()) : null;
}
public void setSortAscending(boolean sortAscending)
{
_sortAscending = Boolean.valueOf(sortAscending);
// update model is necessary here, because processUpdates is never called
// reason: HtmlCommandSortHeader.isImmediate() == true
ValueBinding vb = getValueBinding("sortAscending");
if (vb != null)
{
vb.setValue(getFacesContext(), _sortAscending);
_sortAscending = null;
}
}
/**
* Value reference to a model property that gives the current
* sort direction. The target Boolean property is set to true
* when the selected sortColumn should be sorted in ascending
* order, and false otherwise. The method which is bound to
* the "value" attribute of this table (ie which provides the
* DataModel used) is expected to use this property to
* determine how to sort the DataModel's contents.
*
* @JSFProperty
* defaultValue = "true"
*/
public boolean isSortAscending()
{
if (_sortAscending != null)
return _sortAscending.booleanValue();
ValueBinding vb = getValueBinding("sortAscending");
Boolean v = vb != null ? (Boolean) vb.getValue(getFacesContext()) : null;
return v != null ? v.booleanValue() : DEFAULT_SORTASCENDING;
}
/**
* The name of a javabean property on which the table is sorted.
*
* The datamodel should contain objects that have this property;
* reflection will be used to sort the datamodel on that property
* using the default comparator for that type.
*
* This value is part of the component state. However it is not
* directly settable by users; instead it is set by other components
* such as a CommandSortHeader.
*/
public void setSortProperty(String sortProperty)
{
_sortProperty = sortProperty;
}
/**
* @JSFProperty
* literalOnly="true"
* tagExcluded="true"
*/
public String getSortProperty()
{
return _sortProperty;
}
/**
* Define if the table is sortable or not
*
* @JSFProperty
* defaultValue="false"
*/
public abstract boolean isSortable();
/**
* Avoids rendering the html table tags, thus, giving you a
* table rendering just rows. You can use this together
* with the detailStamp faces of the parent datatable
* to render child-tables using the same layout as the parent.
*
* Notice: You have to ensure both tables do have the same
* number of columns. Using the colspan attribute of the
* column tag might help alot.
*
* @JSFProperty
* defaultValue="false"
*/
public abstract boolean isEmbedded();
/**
* true|false - true if the detailStamp should be expanded by default. default: false
*
* @JSFProperty
* defaultValue="false"
*/
public abstract boolean isDetailStampExpandedDefault();
/**
* before|after - where to render the detailStamp, before the
* actual row or after it. default: after
*
* @JSFProperty
* defaultValue="after"
*/
public abstract String getDetailStampLocation();
/**
* Defines a JavaScript onmouseover event handler for each table row
*
* @JSFProperty
*/
public abstract String getRowOnMouseOver();
/**
* Defines a JavaScript onmouseout event handler for each table row
*
* @JSFProperty
*/
public abstract String getRowOnMouseOut();
/**
* Defines a JavaScript onclick event handler for each table row
*
* @JSFProperty
*/
public abstract String getRowOnClick();
/**
* Defines a JavaScript ondblclick event handler for each table row
*
* @JSFProperty
*/
public abstract String getRowOnDblClick();
/**
* Defines a JavaScript onkeydown event handler for each table row
*
* @JSFProperty
*/
public abstract String getRowOnKeyDown();
/**
* Defines a JavaScript onkeypress event handler for each table row
*
* @JSFProperty
*/
public abstract String getRowOnKeyPress();
/**
* Defines a JavaScript onkeyup event handler for each table row
*
* @JSFProperty
*/
public abstract String getRowOnKeyUp();
/**
* Corresponds to the HTML class attribute for the row tr tag.
*
* @JSFProperty
*/
public String getRowStyleClass()
{
if (_rowStyleClass != null)
return _rowStyleClass;
// TODO: temporarily support fully-qualified ext. dataTable attribute names.
ValueBinding vb = getValueBinding("org.apache.myfaces.dataTable.ROW_STYLECLASS");
if (vb != null)
log.warn("org.apache.myfaces.dataTable.ROW_STYLECLASS is deprecated. Please use rowStyleClass instead.");
else
vb = getValueBinding(JSFAttr.ROW_STYLECLASS_ATTR);
if(vb == null)
return null;
String bindingValue = (String) vb.getValue(getFacesContext());
if(bindingValue == "")
return null; // Fix for JSF 1.2 EL coercing nulls to empty string
return bindingValue;
}
public void setRowStyleClass(String rowStyleClass)
{
_rowStyleClass = rowStyleClass;
}
/**
* Corresponds to the HTML style attribute for the row tr tag.
*
* @JSFProperty
*/
public String getRowStyle()
{
if (_rowStyle != null)
return _rowStyle;
// TODO: temporarily support fully-qualified ext. dataTable attribute names.
ValueBinding vb = getValueBinding("org.apache.myfaces.dataTable.ROW_STYLE");
if (vb != null)
log.warn("org.apache.myfaces.dataTable.ROW_STYLE is deprecated. Please use rowStyle instead.");
else
vb = getValueBinding(JSFAttr.ROW_STYLE_ATTR);
if(vb == null)
return null;
String bindingValue = (String) vb.getValue(getFacesContext());
if(bindingValue == "")
return null; // Fix for JSF 1.2 EL coercing nulls to empty string
return bindingValue;
}
public void setRowStyle(String rowStyle)
{
_rowStyle = rowStyle;
}
/**
* Defines a JavaScript onmpusedown event handler for each table row
*
* @JSFProperty
*/
public abstract String getRowOnMouseDown();
/**
* Defines a JavaScript onmousemove event handler for each table row
*
* @JSFProperty
*/
public abstract String getRowOnMouseMove();
/**
* Defines a JavaScript onmouseup event handler for each table row
*
* @JSFProperty
*/
public abstract String getRowOnMouseUp();
/**
* @JSFProperty
* tagExcluded = "true"
*/
protected boolean isValidChildren()
{
return _isValidChildren;
}
protected void setIsValidChildren(boolean isValidChildren)
{
_isValidChildren = isValidChildren;
}
protected _SerializableDataModel getPreservedDataModel()
{
return _preservedDataModel;
}
protected void setPreservedDataModel(_SerializableDataModel preservedDataModel)
{
_preservedDataModel = preservedDataModel;
}
public boolean isCurrentDetailExpanded()
{
Boolean expanded = (Boolean) _expandedNodes.get(getClientId(getFacesContext()));
if (expanded != null)
{
return expanded.booleanValue();
}
return isDetailStampExpandedDefault();
}
public void setVarDetailToggler(String varDetailToggler)
{
_varDetailToggler = varDetailToggler;
}
/**
* This variable has the boolean property "currentdetailExpanded"
* which is true if the current detail row is expanded and the
* action method "toggleDetail" which expand/collapse the current
* detail row.
*
* @JSFProperty
*/
public String getVarDetailToggler()
{
if (_varDetailToggler != null)
return _varDetailToggler;
ValueBinding vb = getValueBinding("varDetailToggler");
return vb != null ? (String) vb.getValue(getFacesContext()) : null;
}
/**
* Corresponds to the HTML style attribute for grouped rows.
*
* @JSFProperty
*/
public abstract String getRowGroupStyle();
/**
* StyleClass for grouped rows.
*
* @JSFProperty
*/
public abstract String getRowGroupStyleClass();
/**
* Corresponds to the HTML style attribute for the table body tag
*
* @JSFProperty
*/
public abstract String getBodyStyle();
/**
* Corresponds to the HTML class attribute for the table body tag.
*
* @JSFProperty
*/
public abstract String getBodyStyleClass();
public AbstractHtmlDataTable()
{
setRendererType(DEFAULT_RENDERER_TYPE);
}
/**
* Change the status of the current detail row from collapsed to expanded or
* viceversa.
*/
public void toggleDetail()
{
String derivedRowKey = getClientId(getFacesContext());
// get the current expanded state of the row
boolean expanded = isDetailExpanded();
if (expanded)
{
// toggle to "collapsed"
if (isDetailStampExpandedDefault())
{
// if default is expanded we have to override with FALSE here
_expandedNodes.put(derivedRowKey, Boolean.FALSE);
}
else
{
// if default is collapsed we can fallback to this default
_expandedNodes.remove(derivedRowKey);
}
}
else
{
// toggle to "expanded"
if (isDetailStampExpandedDefault())
{
// if default is expanded we can fallback to this default
_expandedNodes.remove(derivedRowKey);
}
else
{
// if default is collapsed we have to override with TRUE
_expandedNodes.put(derivedRowKey, Boolean.TRUE);
}
}
}
/**
* Return true if the current detail row is expanded.
*
* @return true if the current detail row is expanded.
*/
public boolean isDetailExpanded()
{
Boolean expanded = (Boolean) _expandedNodes.get(getClientId(getFacesContext()));
if (expanded == null)
{
return isDetailStampExpandedDefault();
}
return expanded.booleanValue();
}
public int getSortColumnIndex()
{
if (_sortColumnIndex == -1)
_sortColumnIndex = columnNameToIndex(getSortColumn());
return _sortColumnIndex;
}
public void setSortColumnIndex(int sortColumnIndex)
{
_sortColumnIndex = sortColumnIndex;
}
/**
* The number of columns to wrap the table over. Default: 1
*
* Set the number of columns the table will be divided over.
*
* @JSFProperty
* defaultValue="1"
*/
public abstract int getNewspaperColumns();
/**
* The orientation of the newspaper columns in the newspaper
* table - "horizontal" or "vertical". Default: vertical
*
* @JSFProperty
* defaultValue = "vertical"
*/
public abstract String getNewspaperOrientation();
/**
* Gets the spacer facet, between each pair of newspaper columns.
*
* @JSFFacet
* name="spacer"
*/
public UIComponent getSpacer()
{
return (UIComponent) getFacets().get(SPACER_FACET_NAME);
}
public void setSpacer(UIComponent spacer)
{
getFacets().put(SPACER_FACET_NAME, spacer);
}
/**
* Expand all details
*/
public void expandAllDetails()
{
int rowCount = getRowCount();
_expandedNodes.clear();
if (getRowKey() != null)
{
int oldRow = getRowIndex();
try
{
for (int row = 0; row < rowCount; row++)
{
setRowIndex(row);
_expandedNodes.put(getClientId(getFacesContext()), Boolean.TRUE);
}
}
finally
{
setRowIndex(oldRow);
}
}
else
{
for (int row = 0; row < rowCount; row++)
{
_expandedNodes.put(new Integer(row).toString(), Boolean.TRUE);
}
}
}
/**
* Collapse all details
*/
public void collapseAllDetails()
{
_expandedNodes.clear();
}
/**
* @return true is any of the details is expanded
*/
public boolean isExpandedEmpty()
{
boolean expandedEmpty = true;
if (_expandedNodes != null)
{
expandedEmpty = _expandedNodes.isEmpty();
}
return expandedEmpty;
}
/**
* Clears expanded nodes set if expandedEmpty is true
*
* @param expandedEmpty
*/
public void setExpandedEmpty(boolean expandedEmpty)
{
if (expandedEmpty)
{
if (_expandedNodes != null)
{
_expandedNodes.clear();
}
}
}
//------------------ GENERATED CODE BEGIN (do not modify!) --------------------
public static final String COMPONENT_TYPE = "org.apache.myfaces.HtmlDataTable";
public static final String DEFAULT_RENDERER_TYPE = "org.apache.myfaces.Table";
private static final boolean DEFAULT_PRESERVEDATAMODEL = false;
private static final boolean DEFAULT_PRESERVESORT = true;
private static final boolean DEFAULT_RENDEREDIFEMPTY = true;
private Boolean _preserveDataModel = null;
private Boolean _preserveSort = null;
public void setPreserveDataModel(boolean preserveDataModel)
{
_preserveDataModel = Boolean.valueOf(preserveDataModel);
}
/**
* Indicates whether the state of the whole DataModel should
* be saved and restored. When set to false, the value-binding
* for the "value" attribute of this table is executed each
* time the page is rendered. When set to true, that
* value-binding is only executed when the component is first
* created, and the DataModel state is thereafter saved/restored
* automatically by the component. When column sorting is
* used for a table this property needs to be false so that
* the DataModel can be updated to reflect any changes in the
* sort criteria. Default: false
*
* @JSFProperty
*/
public boolean isPreserveDataModel()
{
if (_preserveDataModel != null)
return _preserveDataModel.booleanValue();
ValueBinding vb = getValueBinding("preserveDataModel");
Boolean v = vb != null ? (Boolean) vb.getValue(getFacesContext()) : null;
return v != null ? v.booleanValue() : DEFAULT_PRESERVEDATAMODEL;
}
public void setPreserveSort(boolean preserveSort)
{
_preserveSort = Boolean.valueOf(preserveSort);
}
/**
* Indicates whether the state of the sortColumn and sortAscending
* attribute should be saved and restored and written back to the
* model during the update model phase. Default: true
*
* @JSFProperty
* defaultValue = "true"
*/
public boolean isPreserveSort()
{
if (_preserveSort != null)
return _preserveSort.booleanValue();
ValueBinding vb = getValueBinding("preserveSort");
Boolean v = vb != null ? (Boolean) vb.getValue(getFacesContext()) : null;
return v != null ? v.booleanValue() : DEFAULT_PRESERVESORT;
}
/**
* Indicates whether this table should be rendered if the
* underlying DataModel is empty. You could as well use
* rendered="#{not empty bean.list}", but this one causes
* the getList method of your model bean beeing called up
* to five times per request, which is not optimal when
* the list is backed by a DB table. Using
* renderedIfEmpty="false" solves this problem, because
* the MyFaces extended HtmlDataTable automatically caches
* the DataModel and calles the model getter only once
* per request. Default: true
*
* @JSFProperty
* defaultValue = "true"
*/
public abstract boolean isRenderedIfEmpty();
/**
* A parameter name, under which the current rowIndex is set
* in request scope similar to the var parameter.
*
* @JSFProperty
*/
public abstract String getRowIndexVar();
/**
* A parameter name, under which the rowCount is set in
* request scope similar to the var parameter.
*
* @JSFProperty
*/
public abstract String getRowCountVar();
/**
* A parameter name, under which the previous RowData Object
* is set in request scope similar to the rowIndexVar and
* rowCountVar parameters. Mind that the value of this
* request scope attribute is null in the first row or
* when isRowAvailable returns false for the previous row.
*
* @JSFProperty
*/
public abstract String getPreviousRowDataVar();
/**
* A parameter name, under which the a boolean is set in request
* scope similar to the var parameter. TRUE for the column that
* is currently sorted, FALSE otherwise.
*
* @JSFProperty
*/
public abstract String getSortedColumnVar();
/**
* HTML: Specifies the horizontal alignment of this element.
* Deprecated in HTML 4.01.
*
* @JSFProperty
*/
public abstract String getAlign();
/**
* The id to use for
*
* @JSFProperty
*/
public abstract String getRowId();
/**
* Reserved for future use.
*
* @JSFProperty
*/
public abstract String getDatafld();
/**
* Reserved for future use.
*
* @JSFProperty
*/
public abstract String getDatasrc();
/**
* Reserved for future use.
*
* @JSFProperty
*/
public abstract String getDataformatas();
/**
* Indicate the expected type of the EL expression pointed
* by value property. It is useful when vb.getType() cannot
* found the type, like when a map value is resolved on
* the expression.
*
* @JSFProperty
*/
public abstract String getValueType();
}