org.apache.myfaces.component.html.ext.HtmlDataTableHack Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of tomahawk Show documentation
Show all versions of tomahawk Show documentation
JSF components and utilities that can be used with any JSF implementation.
This library is compatible with both JSF1.1 and JSF1.2; however for JSF1.2 users there
is an alternative build of Tomahawk available that takes advantage of JSF1.2 features to
offer some additional benefits.
/*
* 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 javax.faces.application.FacesMessage;
import javax.faces.component.EditableValueHolder;
import javax.faces.component.NamingContainer;
import javax.faces.component.UIComponent;
import javax.faces.context.FacesContext;
import javax.faces.el.ValueBinding;
import javax.faces.model.ArrayDataModel;
import javax.faces.model.DataModel;
import javax.faces.model.ListDataModel;
import javax.faces.model.ResultDataModel;
import javax.faces.model.ResultSetDataModel;
import javax.faces.model.ScalarDataModel;
import javax.servlet.jsp.jstl.sql.Result;
import org.apache.myfaces.component.html.util.HtmlComponentUtils;
import org.apache.myfaces.custom.ExtendedComponentBase;
/**
* Reimplement all UIData functionality to be able to have (protected) access
* the internal DataModel.
*
* @JSFComponent
* configExcluded = "true"
*
* @author Manfred Geiler (latest modification by $Author: lu4242 $)
* @version $Revision: 1082309 $ $Date: 2011-03-16 16:38:06 -0500 (Wed, 16 Mar 2011) $
*/
public abstract class HtmlDataTableHack extends
javax.faces.component.html.HtmlDataTable implements
ExtendedComponentBase
{
private Map _dataModelMap = new HashMap();
// will be set to false if the data should not be refreshed at the beginning of the encode phase
private boolean _isValidChilds = true;
// holds for each row the states of the child components of this UIData
private Map _rowStates = new HashMap();
// contains the initial row state which is used to initialize each row
private Object _initialDescendantComponentState = null;
// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
// Every field and method from here is identical to UIData !!!!!!!!!
// EXCEPTION: we can create a DataModel from a Collection
private static final Class OBJECT_ARRAY_CLASS = (new Object[0]).getClass();
private static final boolean DEFAULT_PRESERVEROWSTATES = false;
private static final String UNIQUE_ROW_ID_PREFIX = "r_id_";
private int _rowIndex = -1;
private Boolean _preserveRowStates;
public boolean isRowAvailable()
{
return getDataModel().isRowAvailable();
}
public int getRowCount()
{
return getDataModel().getRowCount();
}
public Object getRowData()
{
return getDataModel().getRowData();
}
public int getRowIndex()
{
return _rowIndex;
}
/**
* Hack since RI does not call getRowIndex()
*/
public String getClientId(FacesContext context)
{
String clientId = HtmlComponentUtils.getClientId(this, getRenderer(context), context);
if (clientId == null)
{
clientId = super.getClientId(context);
}
int rowIndex = getRowIndex();
if (rowIndex == -1)
{
return clientId;
}
// the following code tries to avoid rowindex to be twice in the client id
int index = clientId.lastIndexOf(NamingContainer.SEPARATOR_CHAR);
if(index != -1)
{
String rowIndexString = clientId.substring(index + 1);
try
{
if(Integer.parseInt(rowIndexString) == rowIndex)
{
return clientId.substring(0, index+1) + getDerivedSubClientId();
}
}
catch(NumberFormatException e)
{
return clientId + NamingContainer.SEPARATOR_CHAR + getDerivedSubClientId();
}
}
return clientId + NamingContainer.SEPARATOR_CHAR + getDerivedSubClientId();
}
/**
* @see javax.faces.component.UIData#processUpdates(javax.faces.context.FacesContext)
*/
public void processUpdates(FacesContext context)
{
super.processUpdates(context);
// check if a update model error forces the render response for our data
if (context.getRenderResponse())
{
_isValidChilds = false;
}
}
/**
* This method is used when a custom processUpdates and processValidators
* is defined, to check if a update model error forces the render
* response for our data, because _isValidChilds is a private field
* and is not available on child components that inherits this
* component class like t:dataList. (TOMAHAWK-1225)
*/
protected void checkUpdateModelError(FacesContext context)
{
if (context.getRenderResponse())
{
_isValidChilds = false;
}
}
/**
* @see javax.faces.component.UIData#processValidators(javax.faces.context.FacesContext)
*/
public void processValidators(FacesContext context)
{
super.processValidators(context);
// check if a validation error forces the render response for our data
if (context.getRenderResponse())
{
_isValidChilds = false;
}
}
/**
* @see javax.faces.component.UIData#encodeBegin(javax.faces.context.FacesContext)
*/
public void encodeBegin(FacesContext context) throws IOException
{
_initialDescendantComponentState = null;
if (_isValidChilds && !hasErrorMessages(context))
{
//Refresh DataModel for rendering:
_dataModelMap.clear();
if (!isPreserveRowStates())
{
_rowStates.clear();
}
}
super.encodeBegin(context);
}
public void setPreserveRowStates(boolean preserveRowStates)
{
_preserveRowStates = Boolean.valueOf(preserveRowStates);
}
/**
* Indicates whether the state for each row should not be
* discarded before the datatable is rendered again.
*
* Setting this to true might be hepful if an input
* component inside the datatable has no valuebinding and
* the value entered in there should be displayed again.
*
* This will only work reliable if the datamodel of the
* datatable did not change either by sorting, removing or
* adding rows. Default: false
*
* @JSFProperty
* defaultValue="false"
*/
public boolean isPreserveRowStates()
{
if (_preserveRowStates != null)
return _preserveRowStates.booleanValue();
ValueBinding vb = getValueBinding("preserveRowStates");
Boolean v = vb != null ? (Boolean) vb.getValue(getFacesContext()) : null;
return v != null ? v.booleanValue() : DEFAULT_PRESERVEROWSTATES;
}
protected boolean hasErrorMessages(FacesContext context)
{
for(Iterator iter = context.getMessages(); iter.hasNext();)
{
FacesMessage message = (FacesMessage) iter.next();
if(FacesMessage.SEVERITY_ERROR.compareTo(message.getSeverity()) <= 0)
{
return true;
}
}
return false;
}
/**
* @see javax.faces.component.UIComponentBase#encodeEnd(javax.faces.context.FacesContext)
*/
public void encodeEnd(FacesContext context) throws IOException
{
setRowIndex(-1);
super.encodeEnd(context);
}
public void setRowIndex(int rowIndex)
{
if (rowIndex < -1)
{
throw new IllegalArgumentException("rowIndex is less than -1");
}
if (_rowIndex == rowIndex)
{
return;
}
FacesContext facesContext = getFacesContext();
if (_rowIndex == -1)
{
if (_initialDescendantComponentState == null)
{
_initialDescendantComponentState = saveDescendantComponentStates(getChildren()
.iterator(), false);
}
}
else
{
_rowStates.put(getClientId(facesContext),
saveDescendantComponentStates(getChildren()
.iterator(), false));
}
_rowIndex = rowIndex;
DataModel dataModel = getDataModel();
dataModel.setRowIndex(rowIndex);
String var = getVar();
if (rowIndex == -1)
{
if (var != null)
{
facesContext.getExternalContext().getRequestMap().remove(var);
}
}
else
{
if (var != null)
{
if (isRowAvailable())
{
Object rowData = dataModel.getRowData();
facesContext.getExternalContext().getRequestMap().put(var,
rowData);
}
else
{
facesContext.getExternalContext().getRequestMap().remove(
var);
}
}
}
if (_rowIndex == -1)
{
restoreDescendantComponentStates(getChildren().iterator(),
_initialDescendantComponentState, false);
}
else
{
Object rowState = _rowStates.get(getClientId(facesContext));
if (rowState == null)
{
restoreDescendantComponentStates(getChildren().iterator(),
_initialDescendantComponentState, false);
}
else
{
restoreDescendantComponentStates(getChildren().iterator(),
rowState, false);
}
}
}
protected void restoreDescendantComponentStates(Object state)
{
restoreDescendantComponentStates(getChildren().iterator(), state, false);
}
protected void restoreDescendantComponentStates(Iterator childIterator,
Object state, boolean restoreChildFacets)
{
Iterator descendantStateIterator = null;
while (childIterator.hasNext())
{
if (descendantStateIterator == null && state != null)
{
descendantStateIterator = ((Collection) state).iterator();
}
UIComponent component = (UIComponent) childIterator.next();
// reset the client id (see spec 3.1.6)
component.setId(component.getId());
if(!component.isTransient())
{
Object childState = null;
Object descendantState = null;
if (descendantStateIterator != null
&& descendantStateIterator.hasNext())
{
Object[] object = (Object[]) descendantStateIterator.next();
childState = object[0];
descendantState = object[1];
}
if (childState != null && component instanceof EditableValueHolder)
{
((EditableValueHolderState) childState)
.restoreState((EditableValueHolder) component);
}
Iterator childsIterator;
if (restoreChildFacets)
{
childsIterator = component.getFacetsAndChildren();
}
else
{
childsIterator = component.getChildren().iterator();
}
restoreDescendantComponentStates(childsIterator, descendantState,
true);
}
}
}
protected Object saveDescendantComponentStates()
{
return saveDescendantComponentStates(getChildren().iterator(), false);
}
protected Object saveDescendantComponentStates(Iterator childIterator,
boolean saveChildFacets)
{
Collection childStates = null;
while (childIterator.hasNext())
{
if (childStates == null)
{
childStates = new ArrayList();
}
UIComponent child = (UIComponent) childIterator.next();
if(!child.isTransient())
{
Iterator childsIterator;
if (saveChildFacets)
{
childsIterator = child.getFacetsAndChildren();
}
else
{
childsIterator = child.getChildren().iterator();
}
Object descendantState = saveDescendantComponentStates(
childsIterator, true);
Object state = null;
if (child instanceof EditableValueHolder)
{
state = new EditableValueHolderState(
(EditableValueHolder) child);
}
childStates.add(new Object[] { state, descendantState });
}
}
return childStates;
}
public void setValueBinding(String name, ValueBinding binding)
{
if (name == null)
{
throw new NullPointerException("name");
}
else if (name.equals("value"))
{
_dataModelMap.clear();
}
else if (name.equals("var") || name.equals("rowIndex"))
{
throw new IllegalArgumentException(
"You can never set the 'rowIndex' or the 'var' attribute as a value-binding. Set the property directly instead. Name " + name);
}
super.setValueBinding(name, binding);
}
/**
* @see javax.faces.component.UIData#setValue(java.lang.Object)
*/
public void setValue(Object value)
{
super.setValue(value);
_dataModelMap.clear();
_rowStates.clear();
_isValidChilds = true;
}
protected DataModel getDataModel()
{
DataModel dataModel = null;
String clientID = "";
UIComponent parent = getParent();
if (parent != null)
{
clientID = parent.getClientId(getFacesContext());
}
dataModel = (DataModel) _dataModelMap.get(clientID);
if (dataModel == null)
{
dataModel = createDataModel();
_dataModelMap.put(clientID, dataModel);
}
return dataModel;
}
protected void setDataModel(DataModel datamodel)
{
UIComponent parent = getParent();
String clientID = "";
if(parent != null)
{
clientID = parent.getClientId(getFacesContext());
}
_dataModelMap.put(clientID, datamodel);
}
/**
* Creates a new DataModel around the current value.
*/
protected DataModel createDataModel()
{
Object value = getValue();
if (value == null)
{
return EMPTY_DATA_MODEL;
}
else if (value instanceof DataModel)
{
return (DataModel) value;
}
else if (value instanceof List)
{
return new ListDataModel((List) value);
}
// accept a Collection is not supported in the Spec
else if (value instanceof Collection)
{
return new ListDataModel(new ArrayList((Collection) value));
}
else if (OBJECT_ARRAY_CLASS.isAssignableFrom(value.getClass()))
{
return new ArrayDataModel((Object[]) value);
}
else if (value instanceof ResultSet)
{
return new ResultSetDataModel((ResultSet) value);
}
else if (value instanceof Result)
{
return new ResultDataModel((Result) value);
}
else
{
return new ScalarDataModel(value);
}
}
public Object saveState(FacesContext context)
{
Object[] values = new Object[5];
values[0] = super.saveState(context);
values[1] = _preserveRowStates;
values[2] = _forceId;
values[3] = _forceIdIndex;
values[4] = _derivedRowKeyPrefix;
return values;
}
public void restoreState(FacesContext context, Object state)
{
Object[] values = (Object[])state;
super.restoreState(context, values[0]);
_preserveRowStates = (Boolean) values[1];
_forceId = (Boolean) values[2];
_forceIdIndex = (Boolean) values[3];
_derivedRowKeyPrefix = (String) values[4];
}
private static final DataModel EMPTY_DATA_MODEL = new _SerializableDataModel()
{
public boolean isRowAvailable()
{
return false;
}
public int getRowCount()
{
return 0;
}
public Object getRowData()
{
throw new IllegalArgumentException();
}
public int getRowIndex()
{
return -1;
}
public void setRowIndex(int i)
{
if (i < -1)
throw new IndexOutOfBoundsException("Index < 0 : " + i);
}
public Object getWrappedData()
{
return null;
}
public void setWrappedData(Object obj)
{
if (obj == null)
return; //Clearing is allowed
throw new UnsupportedOperationException(this.getClass().getName()
+ " UnsupportedOperationException");
}
};
private class EditableValueHolderState
{
private final Object _value;
private final boolean _localValueSet;
private final boolean _valid;
private final Object _submittedValue;
public EditableValueHolderState(EditableValueHolder evh)
{
_value = evh.getLocalValue();
_localValueSet = evh.isLocalValueSet();
_valid = evh.isValid();
_submittedValue = evh.getSubmittedValue();
}
public void restoreState(EditableValueHolder evh)
{
evh.setValue(_value);
evh.setLocalValueSet(_localValueSet);
evh.setValid(_valid);
evh.setSubmittedValue(_submittedValue);
}
}
// Property: forceId
private Boolean _forceId = Boolean.valueOf(false);
/**
* If true, this component will force the use of the specified id when rendering.
*
* @JSFProperty
* literalOnly = "true"
* defaultValue = "false"
*
* @return
*/
public boolean isForceId()
{
return _forceId.booleanValue();
}
public void setForceId(boolean forceId)
{
this._forceId = Boolean.valueOf(forceId);
}
// Property: forceIdIndex
private Boolean _forceIdIndex = Boolean.valueOf(true);
/**
* If false, this component will not append a '[n]' suffix
* (where 'n' is the row index) to components that are
* contained within a "list." This value will be true by
* default and the value will be ignored if the value of
* forceId is false (or not specified.)
*
* @JSFProperty
* literalOnly = "true"
* defaultValue = "true"
*
* @return
*/
public boolean isForceIdIndex()
{
return _forceIdIndex.booleanValue();
}
public void setForceIdIndex(boolean forceIdIndex)
{
this._forceIdIndex = Boolean.valueOf(forceIdIndex);
}
private static boolean booleanFromObject(Object obj, boolean defaultValue)
{
if(obj instanceof Boolean)
{
return ((Boolean) obj).booleanValue();
}
else if(obj instanceof String)
{
return Boolean.valueOf(((String) obj)).booleanValue();
}
return defaultValue;
}
/**
* Remove all preserved row state for the dataTable
*/
public void clearRowStates()
{
_rowStates.clear();
}
/**
* Remove preserved row state for deleted row and adjust row state to reflect deleted row.
* @param deletedIndex index of row to delete
*/
public void deleteRowStateForRow(int deletedIndex)
{
// save row index
int savedRowIndex = getRowIndex();
FacesContext facesContext = getFacesContext();
setRowIndex(deletedIndex);
String currentRowStateKey = getClientId(facesContext);
Object rowKey = getRowKey();
if (rowKey != null)
{
setRowIndex(deletedIndex);
_rowStates.remove(currentRowStateKey);
setRowIndex(savedRowIndex);
}
else
{
// copy next rowstate to current row for each row from deleted row onward.
int rowCount = getRowCount();
for (int index = deletedIndex + 1; index < rowCount; ++index)
{
setRowIndex(index);
String nextRowStateKey = getClientId(facesContext);
Object nextRowState = _rowStates.get(nextRowStateKey);
if (nextRowState == null)
{
_rowStates.remove(currentRowStateKey);
}
else
{
_rowStates.put(currentRowStateKey, nextRowState);
}
currentRowStateKey = nextRowStateKey;
}
// Remove last row
_rowStates.remove(currentRowStateKey);
// restore saved row index
setRowIndex(savedRowIndex);
}
}
//Since it should be unique, no need to store it as a local var
//private Object _rowKey;
/**
* Used to assign a value expression that identify in a unique way a row. This value
* will be used later instead of rowIndex as a key to be appended to the container
* client id using getDerivedSubClientId() method.
*
* @JSFProperty
* @return
*/
public Object getRowKey()
{
//if (_rowKey != null)
//{
// return _rowKey;
//}
ValueBinding vb = getValueBinding("rowKey");
if (vb != null)
{
Object value = vb.getValue(getFacesContext());
if (value == null)
{
return null;
}
else
{
return (Object) value;
}
}
return null;
}
public void setRowKey(Object rowKey)
{
//_rowKey = rowKey;
}
private String _derivedRowKeyPrefix;
/**
* This attribute is used to append an unique prefix when rowKey is not used, to prevent
* a key match a existing component id (note two different components can't have the
* same unique id).
*
* @JSFProperty defaultValue="r_id_"
* @return
*/
public String getDerivedRowKeyPrefix()
{
if (_derivedRowKeyPrefix != null)
{
return _derivedRowKeyPrefix;
}
ValueBinding vb = getValueBinding("derivedRowKeyPrefix");
if (vb != null)
{
Object value = vb.getValue(getFacesContext());
if (value == null)
{
return UNIQUE_ROW_ID_PREFIX;
}
else
{
return (String) value.toString();
}
}
return UNIQUE_ROW_ID_PREFIX;
}
public void setDerivedRowKeyPrefix(String derivedRowKeyPrefix)
{
this._derivedRowKeyPrefix = derivedRowKeyPrefix;
}
/**
* Return the fragment to be used on the container client id to
* identify a row. As a side effect, it will be used to indicate
* a row component state and a datamodel in nested datatable case.
*
*
* The returned value must comply with the following rules:
*
*
* - Can be followed by: letters (A-Za-z), digits (0-9), hyphens ("-"),
* underscores ("_"), colons (":"), and periods (".")
* - Values are case-sensitive
*
*
* @return
*/
protected String getDerivedSubClientId()
{
Object key = getRowKey();
if (key == null)
{
return _SubIdConverter.encode(Integer.toString(getRowIndex()));
}
else
{
return getDerivedRowKeyPrefix() + _SubIdConverter.encode(key.toString());
}
}
}