/**
* License Agreement.
*
* Rich Faces - Natural Ajax for Java Server Faces (JSF)
*
* Copyright (C) 2007 Exadel, Inc.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License version 2.1 as published by the Free Software Foundation.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
package org.richfaces.component;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import javax.el.ELContext;
import javax.el.ExpressionFactory;
import javax.el.ValueExpression;
import javax.faces.component.UIColumn;
import javax.faces.component.UIComponent;
import javax.faces.component.UIViewRoot;
import javax.faces.context.FacesContext;
import javax.faces.convert.Converter;
import javax.faces.event.AbortProcessingException;
import javax.faces.event.FacesEvent;
import javax.faces.event.PhaseId;
import org.ajax4jsf.component.AjaxComponent;
import org.ajax4jsf.component.SequenceDataAdaptor;
import org.ajax4jsf.context.AjaxContext;
import org.ajax4jsf.event.AjaxEvent;
import org.ajax4jsf.model.DataComponentState;
import org.ajax4jsf.model.DataVisitor;
import org.ajax4jsf.model.ExtendedDataModel;
import org.ajax4jsf.model.Range;
import org.ajax4jsf.util.ELUtils;
import org.apache.commons.collections.iterators.IteratorChain;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.richfaces.convert.rowkey.ScrollableDataTableRowKeyConverter;
import org.richfaces.event.AttributeHolder;
import org.richfaces.event.ScrollableGridViewEvent;
import org.richfaces.event.sort.MultiColumnSortListener;
import org.richfaces.event.sort.SingleColumnSortListener;
import org.richfaces.event.sort.SortEvent;
import org.richfaces.event.sort.SortListener;
import org.richfaces.model.DataModelCache;
import org.richfaces.model.Modifiable;
import org.richfaces.model.ModifiableModel;
import org.richfaces.model.Ordering;
import org.richfaces.model.ScrollableTableDataModel;
import org.richfaces.model.ScrollableTableDataRange;
import org.richfaces.model.SelectionMode;
import org.richfaces.model.SortField;
import org.richfaces.model.SortField2;
import org.richfaces.model.SortOrder;
import org.richfaces.renderkit.html.ScrollableDataTableUtils;
/**
* @author Anton Belevich
*
*/
public abstract class UIScrollableDataTable extends SequenceDataAdaptor implements AjaxComponent, Sortable, Selectable, ScriptExportable{
public static final String COMPONENT_TYPE = "org.richfaces.component.ScrollableDataTable";
public static final String SORT_SINGLE = "single";
public static final String SORT_MULTI = "multi";
private final static Log log = LogFactory.getLog(UIScrollableDataTable.class);
/**
* Flag set on each phase to determine what range of data to walk
* true means - locally saved ranges (for data pending update)
* false means - use range that comes from component state
*/
private boolean useSavedRanges = true;
/**
* hold list of ranges previously accessed until updates are fully done for them
*/
private List<Range> ranges;
private Collection<String> responseData = new ArrayList<String>();
private int reqRowsCount = -1;
private String scrollPos;
private SortListener sortListener;
private Converter defaultRowKeyConverter = new ScrollableDataTableRowKeyConverter();
public abstract SortOrder getSortOrder();
public abstract void setSortOrder(SortOrder sortOrder) ;
public Collection<String> getResponseData() {
return responseData;
}
public void setResponseData(Collection<String> responseData) {
this.responseData = responseData;
}
protected DataComponentState createComponentState() {
return new DataComponentState(){
public Range getRange() {
int curentRow = getFirst();
int rows;
if(reqRowsCount == -1 ){
rows = getRows();
} else {
rows = reqRowsCount;
}
int rowsCount = getExtendedDataModel().getRowCount();
if(rows > 0){
rows += curentRow;
if(rows >=0){
rows = Math.min(rows, rowsCount);
}
} else if(rowsCount >=0 ){
rows = rowsCount;
} else {
rows = -1;
}
return new ScrollableTableDataRange(curentRow,rows, getSortOrder());
}
};
}
public void processDecodes(FacesContext faces) {
useSavedRanges = false;
if (log.isTraceEnabled()) {
log.trace("UIScrollableDataTable.processDecodes(faces)");
}
checkRange();
super.processDecodes(faces);
}
public void processValidators(FacesContext faces) {
useSavedRanges = true;
if (log.isTraceEnabled()) {
log.trace("UIScrollableDataTable.processValidators(faces)");
}
super.processValidators(faces);
}
public void processUpdates(FacesContext faces) {
useSavedRanges = true;
if (log.isTraceEnabled()) {
log.trace("UIScrollableDataTable.processUpdates(faces)");
}
super.processUpdates(faces);
ranges = null;
}
public void encodeBegin(FacesContext context) throws IOException {
if (log.isTraceEnabled()) {
log.trace("UIScrollableDataTable.encodeBegin(context)");
}
useSavedRanges = false;
checkRange();
super.encodeBegin(context);
}
protected ExtendedDataModel createDataModel() {
ExtendedDataModel model = super.createDataModel();
if (model instanceof ScrollableTableDataModel) {
if (isCacheable()) {
if (log.isDebugEnabled()) {
log.debug("Initializing cache of type "
+ DataModelCache.class);
}
model = new DataModelCache((ScrollableTableDataModel<?>) model);
}
} else {
List<SortField2> sortFields = getSortFields();
if (sortFields != null && !sortFields.isEmpty()) {
Modifiable modifiable = null;
if (model instanceof Modifiable) {
modifiable = (Modifiable) model;
} else {
ModifiableModel modifiableModel = new ModifiableModel(
model, getVar());
model = modifiableModel;
modifiable = modifiableModel;
}
modifiable.modify(null, sortFields);
}
}
/*
* TODO: Verify - do we really need it
model.setSortOrder(getSortOrder());
*/
return model;
}
private List<SortField2> getSortFields() {
FacesContext context = FacesContext.getCurrentInstance();
ELContext eLContext= context.getELContext();
ExpressionFactory expressionFactory = context.getApplication().getExpressionFactory();
String var = getVar();
SortOrder sortOrder = getSortOrder();
List<SortField2> sortFields2 = null;
if (sortOrder != null) {
SortField[] sortFields = sortOrder.getFields();
sortFields2 = new LinkedList<SortField2>();
for (SortField sortField : sortFields) {
ValueExpression valueExpression = null;
String name = sortField.getName();
if (ELUtils.isValueReference(name)) {
valueExpression = expressionFactory.createValueExpression(
eLContext, name, Object.class);
} else if (!name.startsWith(UIViewRoot.UNIQUE_ID_PREFIX)) {
valueExpression = expressionFactory.createValueExpression(
eLContext, "#{" + var + "." + name + "}",
Object.class);
}
Ordering ordering = Ordering.UNSORTED;
Boolean ascending = sortField.getAscending();
if (ascending != null) {
if (ascending) {
ordering = Ordering.ASCENDING;
} else {
ordering = Ordering.DESCENDING;
}
}
if (valueExpression != null
&& !Ordering.UNSORTED.equals(ordering)) {
sortFields2.add(new SortField2(valueExpression, ordering));
}
}
}
return sortFields2;
}
public Object saveState(FacesContext context) {
Object values[] = new Object[4];
values[0] = super.saveState(context);
values[1] = ranges;
values[2] = scrollPos;
values[3] = saveAttachedState(context, sortListener);
return (Object)values;
}
@SuppressWarnings("unchecked")
public void restoreState(FacesContext context, Object state) {
Object values[] = (Object[])state;
super.restoreState(context, values[0]);
ranges = ((List<Range>)values[1]);
scrollPos = (String)values[2];
sortListener = (SortListener) restoreAttachedState(context, values[3]);
}
@SuppressWarnings("unchecked")
protected Iterator<UIComponent> dataChildren() {
IteratorChain chain = new IteratorChain();
//RF-1248 Adding facets to both dataChildren and fixed children
//To make both supports and header/footer work
chain.addIterator(getFacets().values().iterator());
for (Iterator<UIComponent> i = getChildren().iterator(); i.hasNext(); ) {
UIComponent kid = (UIComponent)i.next();
if (kid instanceof Column || kid instanceof UIColumn) {
chain.addIterator(kid.getChildren().iterator());
}
}
return chain;
}
@SuppressWarnings("unchecked")
protected Iterator<UIComponent> fixedChildren() {
IteratorChain chain = new IteratorChain(getFacets().values().iterator());
//RF-1248 Adding facets to both dataChildren and fixed children
//To make both supports and header/footer work
for (Iterator<UIComponent> i = getChildren().iterator(); i.hasNext(); ) {
UIComponent kid = (UIComponent)i.next();
if (kid instanceof Column || kid instanceof UIColumn) {
chain.addIterator(kid.getFacets().values().iterator());
}
}
return chain;
}
public void broadcast(FacesEvent event) throws AbortProcessingException {
super.broadcast(event);
if (event instanceof AttributeHolder) {
((AttributeHolder) event).applyAttributes(this);
}
if(event instanceof AjaxEvent){
AjaxContext.getCurrentInstance().addComponentToAjaxRender(this);
}else if(event instanceof SortEvent){
processSortingChange(event);
// new AjaxEvent(this).queue();
}else if(event instanceof ScrollableGridViewEvent){
// new AjaxEvent(this).queue();
processScrolling(event);
}
}
protected boolean broadcastLocal(FacesEvent event) {
return super.broadcastLocal(event) || event instanceof SortEvent || event instanceof ScrollableGridViewEvent;
}
public void queueEvent(FacesEvent event) {
if(event instanceof AjaxEvent){
event.setPhaseId(PhaseId.APPLY_REQUEST_VALUES);
}else if(event instanceof SortEvent){
new AjaxEvent(this).queue();
event.setPhaseId(PhaseId.APPLY_REQUEST_VALUES);
}else if(event instanceof ScrollableGridViewEvent){
event.setPhaseId(PhaseId.APPLY_REQUEST_VALUES);
new AjaxEvent(this).queue();
}
super.queueEvent(event);
}
public void processScrolling(FacesEvent event){
ScrollableGridViewEvent e = (ScrollableGridViewEvent)event;
setFirst(e.getFirst());
reqRowsCount = e.getRows();
getFacesContext().renderResponse();
}
public void processSortingChange(FacesEvent event){
SortEvent e = (SortEvent)event;
getSortListener().processSort(e);
setFirst(e.getFirst());
reqRowsCount = e.getRows();
resetDataModel();
getFacesContext().renderResponse();
}
public void walk(FacesContext faces, DataVisitor visitor, Object argument) throws IOException {
Range visitedRange = getComponentState().getRange();
if(ranges == null){
ranges = new ArrayList<Range>();
}
if(!ranges.contains(visitedRange)){
ranges.add(visitedRange);
}
if(useSavedRanges){
for (Iterator<Range> iter = ranges.iterator(); iter.hasNext();) {
ScrollableTableDataRange range = (ScrollableTableDataRange) iter.next();
if (log.isDebugEnabled()) {
log.debug("Range is: " + range.getFirst() + " - " + range.getLast() + " sortOrder: " + range.getSortOrder() );
}
getExtendedDataModel().walk(faces, visitor,range, argument);
}
}else{
super.walk(faces, visitor, argument);
}
}
public void encodeEnd(FacesContext context) throws IOException {
super.encodeEnd(context);
}
public boolean isCacheable() {
return true;
}
public String getScrollPos() {
return scrollPos;
}
public void setScrollPos(String scrollPos) {
this.scrollPos = scrollPos;
}
public SortListener getSortListener() {
if (sortListener != null) {
return sortListener;
}
if (SORT_MULTI.equals(getSortMode())) {
return MultiColumnSortListener.INSTANCE;
} else {
return SingleColumnSortListener.INSTANCE;
}
}
public void setSortListener(SortListener sortListener) {
this.sortListener = sortListener;
}
public abstract String getSortMode();
public abstract void setSortMode(String mode);
public abstract SelectionMode getSelectionMode();
public abstract void setSelectionMode(SelectionMode mode);
public boolean isSelectionEnabled() {
return getSelectionMode().isSelectionEnabled();
}
public abstract Object getActiveRowKey();
public abstract void setActiveRowKey(Object activeRowKey);
/* (non-Javadoc)
* @see org.ajax4jsf.component.UIDataAdaptor#setRowIndex(int)
*/
public void setRowIndex(int index) {
if (index < 0) {
setRowKey(null);
}
//super.setRowIndex(index);
}
public void resetReqRowsCount() {
this.reqRowsCount = -1;
}
private void checkRange() {
int rows;
if(reqRowsCount == -1 ){
rows = getRows();
} else {
rows = reqRowsCount;
}
if (getRowCount() < getFirst() + rows) {
setFirst(0);
setScrollPos("0,0," + rows + "," + ScrollableDataTableUtils.getClientRowIndex(this));
}
}
//RF-2771
public boolean isLimitToList() {
// TODO Auto-generated method stub
return false;
}
public void setLimitToList(boolean submitForm) {
// TODO Auto-generated method stub
}
public boolean isAjaxSingle() {
// TODO Auto-generated method stub
return false;
}
public void setAjaxSingle(boolean single) {
// TODO Auto-generated method stub
}
@Override
public Converter getRowKeyConverter() {
Converter converter = super.getRowKeyConverter();
if (null == converter) {
return defaultRowKeyConverter;
}
return converter;
}
@Override
public void setRowKeyConverter(Converter rowKeyConverter) {
super.setRowKeyConverter(rowKeyConverter);
}
}