org.icefaces.ace.component.datatable.DataTable Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of icefaces-ace Show documentation
Show all versions of icefaces-ace Show documentation
${icefaces.product.name} ACE Component Library
/*
* Original Code Copyright Prime Technology.
* Subsequent Code Modifications Copyright 2011-2014 ICEsoft Technologies Canada Corp. (c)
*
* Licensed 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.
*
* NOTE THIS CODE HAS BEEN MODIFIED FROM ORIGINAL FORM
*
* Subsequent Code Modifications have been made and contributed by ICEsoft Technologies Canada Corp. (c).
*
* Code Modification 1: Integrated with ICEfaces Advanced Component Environment.
* Contributors: ICEsoft Technologies Canada Corp. (c)
*/
package org.icefaces.ace.component.datatable;
import org.icefaces.ace.component.ajax.AjaxBehavior;
import org.icefaces.ace.component.column.Column;
import org.icefaces.ace.component.column.ColumnType;
import org.icefaces.ace.component.column.IProxiableColumn;
import org.icefaces.ace.component.columngroup.ColumnGroup;
import org.icefaces.ace.component.expansiontoggler.ExpansionToggler;
import org.icefaces.ace.component.panelexpansion.PanelExpansion;
import org.icefaces.ace.component.row.Row;
import org.icefaces.ace.component.rowexpansion.RowExpansion;
import org.icefaces.ace.component.tableconfigpanel.TableConfigPanel;
import org.icefaces.ace.event.DataTableCellClickEvent;
import org.icefaces.ace.event.SelectEvent;
import org.icefaces.ace.event.TableFilterEvent;
import org.icefaces.ace.event.UnselectEvent;
import org.icefaces.ace.model.MultipleExpressionComparator;
import org.icefaces.ace.model.filter.ContainsFilterConstraint;
import org.icefaces.ace.model.table.*;
import org.icefaces.ace.util.ComponentUtils;
import org.icefaces.ace.util.Constants;
import org.icefaces.ace.util.collections.AllPredicate;
import org.icefaces.ace.util.collections.AnyPredicate;
import org.icefaces.ace.util.collections.Predicate;
import org.icefaces.ace.util.collections.PropertyConstraintPredicate;
import org.icefaces.ace.util.collections.RangeConstraintPredicate;
import org.icefaces.util.JavaScriptRunner;
import org.icefaces.util.EnvUtils;
import javax.el.ELContext;
import javax.el.ELResolver;
import javax.el.MethodExpression;
import javax.el.ValueExpression;
import javax.faces.FacesException;
import javax.faces.application.Application;
import javax.faces.application.NavigationHandler;
import javax.faces.component.*;
import javax.faces.component.behavior.ClientBehavior;
import javax.faces.component.visit.VisitCallback;
import javax.faces.component.visit.VisitContext;
import javax.faces.component.visit.VisitHint;
import javax.faces.component.visit.VisitResult;
import javax.faces.context.FacesContext;
import javax.faces.event.*;
import javax.faces.model.*;
import javax.faces.render.Renderer;
import javax.faces.view.Location;
import java.io.Serializable;
import java.sql.ResultSet;
import java.util.*;
import java.util.logging.Logger;
public class DataTable extends DataTableBase implements Serializable {
private static Logger log = Logger.getLogger(DataTable.class.getName());
private static Class SQL_RESULT = null;
// Cached results
private TableConfigPanel panel;
private RowStateMap stateMap;
// Cache model instance as long as setRowIndex(-1) is not called, this is a Mojarra derived behaviour
private DataModel model = null;
// Cache filteredData longer than model as model regen may be attempted during iterative case
// and getFilteredData will fail.
private List filteredData;
private String lastContainerClientId;
transient protected SortState savedSortState;
transient protected FilterState savedFilterState;
transient protected PageState savedPageState;
// used to associate a client id with a state, in order to avoid sharing/overriding states in the case of nested tables
transient protected Map savedSelectionChanges = new HashMap();
transient protected boolean decoded = false;
transient protected String editSubmit = null;
public String getEditSubmitParameter() { return editSubmit; }
static {
try {
SQL_RESULT = Class.forName("javax.servlet.jsp.jstl.sql.Result");
} catch (Throwable t) {
//ignore if sql.result not available
}
}
/*#######################################################################*/
/*###################### Overridden API #################################*/
/*#######################################################################*/
/* Removed (ICE-10297).
@Override
public Integer getScrollHeight() {
Integer height = super.getHeight();
Map clientValues = (Map) getStateHelper().get("scrollHeight_rowValues");
// If height is not null and scrollHeight only has a default value to return.
if (height != null && (clientValues == null || !clientValues.containsKey(getClientId())))
return height;
// Else return the value of scrollHeight
return super.getScrollHeight();
}
*/
@Override
public RowStateMap getStateMap() {
if (stateMap != null) return stateMap;
stateMap = super.getStateMap();
if (stateMap == null) {
stateMap = new RowStateMap();
super.setStateMap(stateMap);
}
return stateMap;
}
// Allow renderer to void state map between iterations to avoid
// sharing stateMap due to caching
// (caching is necessary to avoid attempting to load a stateMap when the clientId contains a row index)
protected void clearCachedStateMap() {
stateMap = null;
}
@Override
public Object getValue() {
Object superValue = super.getValue();
int superValueHash;
if (superValue != null) superValueHash = superValue.hashCode();
else return null;
// If model is altered or new reapply filters / sorting
if (getValueHashCode() == null || superValueHash != getValueHashCode()) {
setValueHashCode(superValueHash);
if (!(superValue instanceof LazyDataModel)) {
applySorting();
if (getFilteredData() != null || filteredData != null) {
applyFilters();
}
}
}
// If we have filtered data return that instead of the standard collection
// Lazy case should recalculate filters in the persistence layer with every load
List filteredValue = getFilteredData();
if (!(superValue instanceof LazyDataModel) && filteredValue != null)
return filteredValue;
else return superValue;
}
@Override
protected DataModel getDataModel() {
if (this.model != null) {
return (model);
}
Object current = getValue();
// If existing tree check for changes or return cached model
if (current == null) {
setDataModel(new ListDataModel(Collections.EMPTY_LIST));
} else if (current instanceof DataModel) {
setDataModel((DataModel) current);
} else if (current instanceof List) {
List list = (List)current;
if (list.size() > 0 && list.get(0) instanceof Map.Entry)
setDataModel(new TreeDataModel(list));
else
setDataModel(new ListDataModel(list));
} else if (Object[].class.isAssignableFrom(current.getClass())) {
setDataModel(new ArrayDataModel((Object[]) current));
} else if (current instanceof ResultSet) {
setDataModel(new ResultSetDataModel((ResultSet) current));
} else if ((null != SQL_RESULT) && SQL_RESULT.isInstance(current)) {
DataModel dataModel = new ResultDataModel();
dataModel.setWrappedData(current);
setDataModel(dataModel);
} else {
setDataModel(new ScalarDataModel(current));
}
return model;
}
@Override
protected void setDataModel(DataModel dataModel) {
this.model = dataModel;
}
// Private class to wrap an event with a row index
class WrapperEvent extends FacesEvent {
public WrapperEvent(UIComponent component, FacesEvent event, int rowIndex) {
super(component);
this.event = event;
this.rowIndex = rowIndex;
}
private FacesEvent event = null;
private int rowIndex = -1;
public FacesEvent getFacesEvent() {
return (this.event);
}
public int getRowIndex() {
return (this.rowIndex);
}
public PhaseId getPhaseId() {
return (this.event.getPhaseId());
}
public void setPhaseId(PhaseId phaseId) {
this.event.setPhaseId(phaseId);
}
public boolean isAppropriateListener(FacesListener listener) {
return (false);
}
public void processListener(FacesListener listener) {
throw new IllegalStateException();
}
}
class TreeWrapperEvent extends WrapperEvent {
Integer[] rootIndexes;
public TreeWrapperEvent(UIComponent component, FacesEvent event, int rowIndex, Integer[] rootIndex) {
super(component, event, rowIndex);
rootIndexes = rootIndex;
}
public Integer[] getRootIndexes() {
return rootIndexes;
}
public void setRootIndexes(Integer[] rootIndexes) {
this.rootIndexes = rootIndexes;
}
}
/*
Merges implementations of Mojarra UIData & UIComponentBase
*/
@Override
public void queueEvent(FacesEvent event) {
if (event == null) {
throw new NullPointerException();
}
int rowIndex = getRowIndex();
if (event instanceof AjaxBehaviorEvent) {
FacesContext context = FacesContext.getCurrentInstance();
Map params = context.getExternalContext()
.getRequestParameterMap();
String eventName = params.get(Constants.PARTIAL_BEHAVIOR_EVENT_PARAM);
if (eventName.equals("cellClick") || eventName.equals("cellDblClick")) {
int columnIndex = Integer.parseInt(params.get(getClientId(context) + "_colIndex"));
rowIndex = Integer.parseInt(params.get(getClientId(context) + "_rowIndex"));
Column column = getColumns().get(columnIndex);
event = new DataTableCellClickEvent((AjaxBehaviorEvent)event, column);
}
}
UIComponent parent = getParent();
if (parent == null) {
throw new IllegalStateException();
} else {
if (hasTreeDataModel())
parent.queueEvent(new TreeWrapperEvent(this, event, rowIndex, ((TreeDataModel)model).getRootIndexArray()));
else
parent.queueEvent(new WrapperEvent(this, event, rowIndex));
}
}
@Override
public void broadcast(FacesEvent event) throws AbortProcessingException {
broadcast(event, false);
}
public void broadcast(FacesEvent event, boolean skipRegen) throws AbortProcessingException {
// The data model that is used in myFaces may have been generated
// from incorrect getValue() results (I assume) causing it to
// mistakenly contain 0 rows or the data of a previous ui:repeat
// iteration.
if (!skipRegen) setDataModel(null);
boolean treeEvent = event instanceof TreeWrapperEvent;
if (!(event instanceof WrapperEvent || treeEvent)) {
super.broadcast(event);
return;
}
FacesContext context = FacesContext.getCurrentInstance();
// Set up the correct context and fire our wrapped event
TreeDataModel treeModel = null;
TreeWrapperEvent tevent = null;
String oldRoot = null;
if (treeEvent) {
treeModel = (TreeDataModel) getDataModel();
oldRoot = treeModel.getRootIndex();
}
int oldRowIndex = getRowIndex();
// Clear row index pre datamodel refresh to be sure we're getting accurate row data
WrapperEvent revent = (WrapperEvent) event;
if (treeEvent) tevent = (TreeWrapperEvent) event;
if (isNestedWithinUIData()) {
setDataModel(null);
}
// Refresh data model before setRowIndex does for an incorrect clientId / filteredData state.
setRowIndex(-1);
getDataModel();
getStateMap();
if (treeEvent) {
treeModel = (TreeDataModel) getDataModel();
if (tevent.getRootIndexes() != null && tevent.getRootIndexes().length > 0)
treeModel.setRootIndex(join(Arrays.asList(tevent.getRootIndexes()), "."));
}
setRowIndex(revent.getRowIndex());
FacesEvent rowEvent = revent.getFacesEvent();
UIComponent source = rowEvent.getComponent();
UIComponent compositeParent = null;
try {
if (!UIComponent.isCompositeComponent(source)) {
compositeParent = UIComponent.getCompositeComponentParent(source);
}
if (compositeParent != null) {
compositeParent.pushComponentToEL(context, null);
}
source.pushComponentToEL(context, null);
// Skip regen to prevent losing current row context if broadcasting to ourselves
// Alternatively we could remove the DataModel regen added to broadcast for MyFaces as it may no longer be requires
if (source.equals(this)) broadcast(rowEvent, true);
else source.broadcast(rowEvent);
} finally {
source.popComponentFromEL(context);
if (compositeParent != null) {
compositeParent.popComponentFromEL(context);
}
}
if (treeEvent) {
if (!oldRoot.equals(""))
treeModel.setRootIndex(oldRoot);
}
setRowIndex(oldRowIndex);
String outcome = null;
MethodExpression me = null;
FacesEvent fevent = revent.getFacesEvent();
if (fevent instanceof SelectEvent) me = getRowSelectListener();
else if (fevent instanceof UnselectEvent) me = getRowUnselectListener();
else if (fevent instanceof TableFilterEvent) me = getFilterListener();
if (!context.isValidationFailed() || !(fevent instanceof TableFilterEvent)) {
if (me != null) outcome = (String) me.invoke(context.getELContext(), new Object[] {fevent});
}
if (outcome != null) {
NavigationHandler navHandler = context.getApplication().getNavigationHandler();
navHandler.handleNavigation(context, null, outcome);
context.renderResponse();
}
}
private String join(Collection strCollection, String delimiter) {
String joined = "";
int noOfItems = 0;
for (Integer item : strCollection) {
joined += item;
if (++noOfItems < strCollection.size())
joined += delimiter;
}
return joined;
}
@Override
public void processUpdates(FacesContext context) {
if (context == null) {
throw new NullPointerException();
}
if (!isRendered()) {
return;
}
pushComponentToEL(context, this);
//preUpdate(context);
// Required to prevent child input component processing on filter and pagination initiated submits.
if (isAlwaysExecuteContents() || !isTableFeatureRequest(context))
iterate(context, PhaseId.UPDATE_MODEL_VALUES);
if (savedPageState != null)
savedPageState.apply(this);
if (savedSelectionChanges.get(getClientId(context)) != null)
savedSelectionChanges.get(getClientId(context)).apply(this);
if (isApplyingFilters() && !isLazy()) {
if (savedFilterState != null)
savedFilterState.apply(this);
setFilteredData(processFilters(context));
}
if (isApplyingSorts()) {
if (savedSortState != null)
savedSortState.apply(this);
processSorting();
}
popComponentFromEL(context);
}
@Override
public void processDecodes(FacesContext context) {
decoded = true;
if (context == null) {
throw new NullPointerException();
}
if (!isRendered()) {
return;
}
pushComponentToEL(context, this);
// save editSubmit parameter for cell deitors
editSubmit = context.getExternalContext().getRequestParameterMap().get(getClientId(context)+"_editSubmit");
//super.preDecode() - private and difficult to port
if (isAlwaysExecuteContents() || !isTableFeatureRequest(context))
iterate(context, PhaseId.APPLY_REQUEST_VALUES);
decode(context);
if (isApplyingFilters()) {
Map params = context.getExternalContext().getRequestParameterMap();
queueEvent(
new TableFilterEvent(this,
getFilterMap().get(params.get(getClientId(context) + "_filteredColumn")))
);
}
popComponentFromEL(context);
}
@Override
public void processValidators(FacesContext context) {
if (context == null) {
throw new NullPointerException();
}
if (!isRendered()) {
return;
}
pushComponentToEL(context, this);
Application app = context.getApplication();
app.publishEvent(context, PreValidateEvent.class, this);
//preValidate(context);
if (isAlwaysExecuteContents() || !isTableFeatureRequest(context))
iterate(context, PhaseId.PROCESS_VALIDATIONS);
app.publishEvent(context, PostValidateEvent.class, this);
popComponentFromEL(context);
}
@Override
public void setRowIndex(int index) {
Map requestMap = FacesContext.getCurrentInstance().getExternalContext().getRequestMap();
if (getRowIndex() != index) {
super.setRowIndex(index);
}
if (index > -1 && isRowAvailable()) {
requestMap.put(getRowStateVar(), getStateMap().get(getRowData()));
}
}
/*#######################################################################*/
/*###################### Public API #####################################*/
/*#######################################################################*/
public boolean isSortingEnabled() {
for (Column c : getColumns(true))
if (c.getValueExpression("sortBy") != null)
return true;
return false;
}
public boolean isFilteringEnabled() {
for (Column c : getColumns(true))
if (c.getValueExpression("filterBy") != null)
return true;
return false;
}
/**
* A public proxy to the getDataModel() method, intended for use in situations
* where a sub-component needs access to a custom DataModel object.
*
* @return java.faces.model.DataModel instance currently used by this table
*/
public DataModel getModel() {
return getDataModel();
}
/**
* A public proxy to the getDataModel() method, intended for use in situations
* where a sub-component needs to null a cached DataModel to force regeneration.
*/
public void setModel(DataModel m) {
try { setDataModel(null); }
catch (UnsupportedOperationException uoe) {
//MyFaces doesn't support this method and throws an UnsupportedOperationException
}
}
/**
* Determine if this DataTable is using a custom ICEFaces TreeDataModel.
* @return true, if a TreeDataModel instance is the result of getDataModel()
*/
public Boolean hasTreeDataModel() {
return (model instanceof TreeDataModel);
}
/**
* If a PanelExpansion component is a child of this table, return it.
* This is intended for table sub-components to vary their behavior varied
* on the presence of PanelExpansion and/or RowExpansion.
* @return PanelExpansion child of the table, or null
*/
public PanelExpansion getPanelExpansion() {
for (UIComponent kid : getChildren())
if (kid instanceof PanelExpansion) return (PanelExpansion) kid;
return null;
}
/**
* If a RowExpansion component is a child of this table, return it.
* This is intended for table sub-components to vary their behavior varied
* on the presence of PanelExpansion and/or RowExpansion.
* @return RowExpansion child of the table, or null
*/
public RowExpansion getRowExpansion() {
for (UIComponent kid : getChildren())
if (kid instanceof RowExpansion) return (RowExpansion) kid;
return null;
}
/**
* Generates the list of DataTable Column children, reordered according to the
* column ordering property, or the header ColumnGroup columns in page order.
* @param headColumns Enable to return the header columns in page order
* @return List of ACE Column Components.
*/
public List getColumns(boolean headColumns) {
UIComponent columnGroup = getColumnGroup("header");
if (headColumns && columnGroup != null) {
ArrayList columns = new ArrayList();
for (UIComponent child : columnGroup.getChildren()) {
if (child instanceof Row) {
for (UIComponent gchild : child.getChildren()) {
if (gchild instanceof Column) {
columns.add((Column)gchild);
}
}
}
}
return columns;
} else {
ArrayList columns = new ArrayList();
List columnOrdering = generateColumnOrdering();
ArrayList unordered = new ArrayList();
Stack childStack = new Stack();
childStack.add(this);
while (!childStack.empty()) {
for (UIComponent child : ((UIComponent)childStack.pop()).getChildren()) {
if (!(child instanceof ColumnGroup) && !(child instanceof Column) && !(child instanceof DataTable) && !(child instanceof Row)) {
if (child.getChildren().size() > 0) childStack.add(child);
} else if (child instanceof Column) unordered.add((Column) child);
}
}
// Allow the ordering to grow beyond the current set of columns,
// to allow persistence of order during column swaps.
while (columnOrdering.size() < unordered.size()) columnOrdering.add(columnOrdering.size());
for (Integer i : columnOrdering)
if (i < unordered.size()) columns.add(unordered.get(i));
return columns;
}
}
/**
* Generates the list of DataTable Column children, reordered according to the
* column ordering property. Note this list doesn't return Column components used
* in a ColumnGroup to define the header.
* @return List of ACE Column Components.
*/
public List getColumns() {
return getColumns(false);
}
List getProxiedBodyColumns() {
if (!isColumnsInheritProperties() ||
(null == getColumnGroup("header"))) {
return new ArrayList(getColumns());
}
return getColumnModel().getProxiedBodyColumns();
}
public ColumnModel getColumnModel() {
ColumnGroupModel headerModel = null;
ColumnGroupModel bodyModel;
ColumnGroup columnGroup = getColumnGroup("header");
if (columnGroup != null) {
log.fine("HEADER columnGroup");
// Assume for any columns beyond columnOrdering's size, columnOrdering[i]==i
List headerColumnOrdering = getHeaderColumnOrdering();
log.fine("headerColumnOrdering: " + headerColumnOrdering);
headerModel = new ColumnGroupModel(columnGroup);
ColumnGroupModel.ConstructionState state = headerModel.construct(headerColumnOrdering);
for (UIComponent child : columnGroup.getChildren()) {
if (child instanceof Row) {
state.addRow((Row)child);
for (UIComponent gchild : child.getChildren()) {
if (gchild instanceof Column) {
state.addUnsortedColumnForRow((Column)gchild);
}
}
state.endRow();
}
}
state.end();
}
log.fine("BODY columns");
// Assume for any columns beyond columnOrdering's size, columnOrdering[i]==i
List columnOrdering = getColumnOrdering();
log.fine("columnOrdering: " + columnOrdering);
bodyModel = new ColumnGroupModel();
ColumnGroupModel.ConstructionState state = bodyModel.construct(columnOrdering);
state.addRow(null);
Stack childStack = new Stack();
childStack.add(this);
while (!childStack.empty()) {
for (UIComponent child : ((UIComponent)childStack.pop()).getChildren()) {
if (!(child instanceof ColumnGroup) && !(child instanceof Column) && !(child instanceof DataTable) && !(child instanceof Row)) {
if (child.getChildren().size() > 0) {
childStack.add(child);
}
} else if (child instanceof Column) {
state.addUnsortedColumnForRow((Column)child);
}
}
}
state.endRow();
state.end();
ColumnModel columnModel = new ColumnModel(headerModel, bodyModel);
columnModel.verifyCorresponding(getClientId());
return columnModel;
}
/**
* Generates a list of DataTable Column children intended to render the header, either from the header segement, or
* from the normal grouping of columns.
* @return List of ACE Column Components.
*/
public List getHeaderColumns() {
return null;
}
/**
* Associates this table with a particular TableConfigPanel component. That
* table will render the launch controls, and be configured by the specified
* panel.
* @param panel TableConfigPanel component that will configure this table.
*/
public void setTableConfigPanel(TableConfigPanel panel) {
this.panel = panel;
FacesContext c = FacesContext.getCurrentInstance();
setTableConfigPanel(panel.getClientId(c)) ;
}
/**
* Sets the value property of this dataTable to null.
*/
public void resetValue() {
setValue(null);
}
/**
* Sets the position of pagination in the table to the first page.
*/
public void resetPagination() {
setFirst(0);
setPage(1);
}
/**
* Sets the properties of this table to null, clears all filters and resets pagination.
*/
public void reset() {
resetValue();
resetFilters();
resetPagination();
}
/**
* Blanks the sortPriority and set to false the sortAscending property of each Column
* component.
*/
public void resetSorting() {
for (Column c : getColumns()) {
c.setSortPriority(null);
c.setSortAscending(null);
}
for (Column c : getColumns(true)) {
c.setSortPriority(null);
c.setSortAscending(null);
}
}
/**
* Blanks the filterValue property of each Column component and removes the
* presently filtered set of data.
*/
public void resetFilters() {
for (Column c : getColumns()) {
c.setFilterValue("");
}
for (Column c : getColumns(true)) {
c.setFilterValue("");
}
setFilterValue("");
setFilteredData(null);
}
@Override
public List getFilteredData() {
List d = super.getFilteredData();
if (d != null) return d;
return filteredData;
}
@Override
public void setFilteredData(List filteredData) {
super.setFilteredData(filteredData);
this.filteredData = filteredData;
}
/**
* Processes any changes to sortPriority or sortAscending properties of Columns
* to the data model; resorting the table according to the new settings.
*/
public void applySorting() {
setApplyingSorts(true);
}
/**
* Processes any changes to the filterInput property of the Columns to the data model;
* refiltering the data model to meet the new criteria.
*/
public void applyFilters() {
setApplyingFilters(true);
}
public Boolean isApplyingFilters() {
if (isConstantRefilter()) return true;
Object cached = getCachedGlobalFilter();
String gFilterVal = getFilterValue();
boolean globalFilterChanged = (cached == null)
? gFilterVal != null && !gFilterVal.equals("")
: !cached.equals(gFilterVal);
setCachedGlobalFilter(gFilterVal);
return super.isApplyingFilters() || globalFilterChanged;
}
public enum SearchType {
CONTAINS, ENDS_WITH, STARTS_WITH, EXACT
}
/**
* Find the index of a row object in the current DataModel.
* @param query The string to be searched for in the row object fields.
* @param fields The fields of the row object to search the String representations of.
* @param startRow The index to begin searching, inclusive.
* @param searchType A enumeration representing where to search for a match.
* @param caseSensitive A boolean representing the case sensitive.
* @return Index of the row found or -1
*/
public int findRow(String query, String[] fields, int startRow, SearchType searchType, boolean caseSensitive) {
int savedRowIndex = getRowIndex();
FacesContext context = FacesContext.getCurrentInstance();
ELContext elContext = context.getELContext();
ELResolver resolver = elContext.getELResolver();
Application app = context.getApplication();
String rowVar = getVar();
if (!caseSensitive) query = query.toLowerCase();
setRowIndex(startRow);
try {
// Contains
if (searchType.equals(SearchType.CONTAINS))
while (isRowAvailable()) {
for (int i = 0; i < fields.length; i++) {
String rowFieldString = resolver.getValue(elContext, getRowData(), fields[i]).toString();
if (!caseSensitive) rowFieldString = rowFieldString.toLowerCase();
if (rowFieldString.contains(query))
return getRowIndex();
}
setRowIndex(getRowIndex()+1);
}
// Ends with
else if (searchType.equals(SearchType.ENDS_WITH))
while (isRowAvailable()) {
for (int i = 0; i < fields.length; i++) {
String rowFieldString = resolver.getValue(elContext, getRowData(), fields[i]).toString();
if (!caseSensitive) rowFieldString = rowFieldString.toLowerCase();
if (rowFieldString.endsWith(query))
return getRowIndex();
}
setRowIndex(getRowIndex()+1);
}
// Starts with
else if (searchType.equals(SearchType.STARTS_WITH))
while (isRowAvailable()) {
for (int i = 0; i < fields.length; i++) {
String rowFieldString = resolver.getValue(elContext, getRowData(), fields[i]).toString();
if (!caseSensitive) rowFieldString = rowFieldString.toLowerCase();
if (rowFieldString.startsWith(query))
return getRowIndex();
}
setRowIndex(getRowIndex()+1);
}
// Exact
else if (searchType.equals(SearchType.EXACT))
while (isRowAvailable()) {
for (int i = 0; i < fields.length; i++) {
String rowFieldString = resolver.getValue(elContext, getRowData(), fields[i]).toString();
if (!caseSensitive) rowFieldString = rowFieldString.toLowerCase();
if (rowFieldString.equals(query))
return getRowIndex();
}
setRowIndex(getRowIndex()+1);
}
// Falls through if not found, or searchType is invalid.
return -1;
} finally {
setRowIndex(savedRowIndex);
}
}
/**
* Find the index of a row object in the current DataModel.
* @param query The string to be searched for in the row object fields.
* @param fields The fields of the row object to search the String representations of.
* @param startRow The index to begin searching, inclusive.
* @param searchType A enumeration representing where to search for a match.
* @return Index of the row found or -1
*/
public int findRow(String query, String[] fields, int startRow, SearchType searchType) {
return findRow(query, fields, startRow, searchType, true);
}
/**
* Find the index of a row object in the current DataModel.
* @param query The string to be searched for in the row object fields.
* @param fields The fields of the row object to search the String representations of.
* @param startRow The index to begin searching, inclusive.
* @return Index of the row found or -1
*/
public int findRow(String query, String[] fields, int startRow) {
return findRow(query, fields, startRow, SearchType.CONTAINS, true);
}
public enum SearchEffect {
HIGHLIGHT, PULSATE
}
private void doNavigate(int row) {
if (row >= getRowCount())
throw new IndexOutOfBoundsException();
int rowsPerPage = getRows();
if (rowsPerPage > 0) {
int page = row / rowsPerPage;
setPage((row / rowsPerPage) + 1);
}
}
/**
* Navigate the client to a row in the table indicating the target row with css tween to a given class and back.
* @param row Index of the row to be navigated to.
* @param effect Name of css class to add to the target and than remove.
* @param durationMillis Duration of wax and wane of css animation.
*/
public void navigateToRow(int row, String effect, Integer durationMillis) {
doNavigate(row);
FacesContext context = FacesContext.getCurrentInstance();
String id = getClientId(context) + "_row_" + row;
if (effect != null)
JavaScriptRunner.runScript(context,
"ice.ace.jq(ice.ace.escapeClientId('" + id + "'))." +
"toggleClass('" + effect + "', " + durationMillis / 2+ ")." +
"delay(" + durationMillis / 2 + ")." +
"toggleClass('" + effect + "', " + durationMillis / 2 + ")." +
"focus();");
}
/**
* Navigate the client to a row in the table, indicate the target row with the indicated effect, either pulsate or highlight.
* @param row Index of the row to be navigated to.
* @param effect SearchEffect enum indicating pulsate or highlight.
*/
public void navigateToRow(int row, SearchEffect effect) {
doNavigate(row);
FacesContext context = FacesContext.getCurrentInstance();
String id = getClientId(context) + "_row_" + row;
if (effect != null)
if (effect.equals(SearchEffect.HIGHLIGHT))
JavaScriptRunner.runScript(context,
"ice.ace.jq(ice.ace.escapeClientId('" + id + "')).effect('highlight').focus();");
else if (effect.equals(SearchEffect.PULSATE))
JavaScriptRunner.runScript(context,
"ice.ace.jq(ice.ace.escapeClientId('" + id + "')).effect('pulsate').focus();");
}
/**
* Navigate the client to a row in the table, indicate the target row with a default highlight effect.
* @param row Index of the row to be navigated to.
*/
public void navigateToRow(int row) {
navigateToRow(row, SearchEffect.HIGHLIGHT);
}
/*#######################################################################*/
/*###################### Protected API ##################################*/
/*#######################################################################*/
protected boolean isPaginationRequest(FacesContext x) { return isIdPrefixedParamSet("_paging", x); }
protected boolean isTableConfigurationRequest(FacesContext x) { return isIdPrefixedParamSet("_tableconf", x); }
protected boolean isTrashConfigurationRequest(FacesContext x) { return isIdPrefixedParamSet("_tabletrash", x); }
protected boolean isColumnReorderRequest(FacesContext x) { return isIdPrefixedParamSet("_columnReorder", x); }
protected boolean isSortRequest(FacesContext x) { return isIdPrefixedParamSet("_sorting", x); }
protected boolean isFilterRequest(FacesContext x) { return isIdPrefixedParamSet("_filtering", x); }
protected boolean isInstantSelectionRequest(FacesContext x) { return isIdPrefixedParamSet("_instantSelectedRowIndexes", x); }
protected boolean isInstantUnselectionRequest(FacesContext x) { return isIdPrefixedParamSet("_instantUnselectedRowIndexes", x); }
protected boolean isScrollingRequest(FacesContext x) { return isIdPrefixedParamSet("_scrolling", x); }
protected boolean isTableFeatureRequest(FacesContext x) { return isColumnReorderRequest(x) || isScrollingRequest(x) || isInstantUnselectionRequest(x) || isInstantSelectionRequest(x) || isPaginationRequest(x) || isFilterRequest(x) || isSortRequest(x) || isTableConfigurationRequest(x); }
protected boolean isRowExpansionRequest(FacesContext x) {
Iterator names = x.getExternalContext().getRequestParameterNames();
String clientId = this.getClientId(x);
while (names.hasNext()) {
String name = names.next();
if (name.startsWith(clientId)) {
if (name.endsWith("_rowExpansion")) {
Map params = x.getExternalContext().getRequestParameterMap();
String value = params.get(name);
if (value != null && clientId.equals(value)) {
return true;
}
}
}
}
return false;
}
protected Boolean isInDuplicateSegment() {
return isInDuplicateSegment;
}
protected void setInDuplicateSegment(Boolean inFakeHeader) {
Stack compsToIdReinit = new Stack() {{
for (UIComponent c : getColumns(true)) push(c);
}};
while (!compsToIdReinit.empty()) {
UIComponent c = compsToIdReinit.pop();
c.setId(c.getId());
Iterator fnc = c.getFacetsAndChildren();
while (fnc.hasNext()) compsToIdReinit.push(fnc.next());
}
isInDuplicateSegment = inFakeHeader;
}
protected Boolean isInConditionalRow() {
return isInConditionalRow;
}
protected void setInConditionalRow(Boolean inConditionalRow, final List rowColumns) {
Stack compsToIdReinit = new Stack() {{
addAll(rowColumns);
}};
while (!compsToIdReinit.empty()) {
UIComponent c = compsToIdReinit.pop();
c.setId(c.getId());
Iterator fnc = c.getFacetsAndChildren();
while (fnc.hasNext()) compsToIdReinit.push(fnc.next());
}
if (inConditionalRow) conditionalRowIndex++;
isInConditionalRow = inConditionalRow;
}
protected Map getFilterMap() {
Map filterMap = new HashMap();
ColumnGroup group = getColumnGroup("header");
FacesContext context = FacesContext.getCurrentInstance();
if (group != null) {
for (UIComponent child : group.getChildren())
if (child.isRendered()) for (UIComponent grandchild : child.getChildren())
if (grandchild.isRendered() && grandchild.getValueExpression("filterBy") != null) {
if (((Column)grandchild).getColumnType() == ColumnType.DATE)
filterMap.put(grandchild.getClientId(context) + "_filter_input", (Column)grandchild);
else
filterMap.put(grandchild.getClientId(context) + "_filter", (Column)grandchild);
}
} else
for (Column column : getColumns())
if (column.getValueExpression("filterBy") != null) {
if (column.getColumnType() == ColumnType.DATE)
filterMap.put(column.getClientId(context) + "_filter_input", column);
else
filterMap.put(column.getClientId(context) + "_filter", column);
}
return filterMap;
}
protected ColumnGroup getColumnGroup(String target) {
for (UIComponent child : this.getChildren())
if (child instanceof ColumnGroup) {
ColumnGroup colGroup = (ColumnGroup) child;
if (target.equals(colGroup.getType())) return colGroup;
}
return null;
}
protected SortCriteria[] getSortCriteria() {
ArrayList sortableColumns = new ArrayList();
ArrayList groupedColumns = new ArrayList();
int highestGroupedPriority = 0;
for (Column c : getColumns(true)) {
Integer priority = c.getSortPriority();
if (c.getValueExpression("groupBy") != null && c.isSortWhenGrouping()) {
if (priority != null && priority > highestGroupedPriority)
highestGroupedPriority = priority;
groupedColumns.add(c);
}
else if (priority != null && priority > 0 && c.getValueExpression("groupBy") == null) {
sortableColumns.add(c);
}
}
// Any grouped columns without priorities have arbitrary priorities following the highest grouped
for (Column c : groupedColumns)
if (c.getSortPriority() == null)
c.setSortPriority(++highestGroupedPriority);
// Adjust sortable column priority to ensure group columns are placed together
Collections.sort(sortableColumns, new PriorityComparator());
Collections.sort(groupedColumns, new PriorityComparator());
// Give all grouped columns, now in order, the highest priorities
int groupedColumnSize = groupedColumns.size();
for (int i = 0; i < groupedColumnSize; i++)
groupedColumns.get(i).setSortPriority(i+1);
// Give all sortable columns, now in order, the priorities following the grouped columns
for (int i = 0; i < sortableColumns.size(); i++)
sortableColumns.get(i).setSortPriority(i+groupedColumnSize+1);
// Prepend grouped columns to sortable columns
sortableColumns.addAll(0,groupedColumns);
SortCriteria[] criterias = new SortCriteria[sortableColumns.size()];
int i = 0;
for (Column c : sortableColumns) {
Comparator