/**
* License Agreement.
*
* JBoss RichFaces - Ajax4jsf Component Library
*
* 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.util.Map;
import javax.el.ELException;
import javax.el.MethodExpression;
import javax.el.ValueExpression;
import javax.faces.FacesException;
import javax.faces.application.FacesMessage;
import javax.faces.component.ActionSource;
import javax.faces.component.UIComponent;
import javax.faces.component.UIData;
import javax.faces.component.UIInput;
import javax.faces.context.FacesContext;
import javax.faces.event.AbortProcessingException;
import javax.faces.event.FacesEvent;
import org.ajax4jsf.Messages;
import org.ajax4jsf.component.AjaxActionComponent;
import org.ajax4jsf.event.AjaxEvent;
import org.ajax4jsf.renderkit.AjaxRendererUtils;
import org.ajax4jsf.renderkit.RendererUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.richfaces.component.util.MessageUtil;
import org.richfaces.event.DataScrollerEvent;
import org.richfaces.event.DataScrollerListener;
import org.richfaces.event.DataScrollerSource;
/** JSF component class */
//xxxx nick -> alex - extend UIComponentBase and
//create event listener & event classes to define PageSwitchEvent
//public abstract class UIDatascroller extends UIComponentBase implements DataScrollerSource{
public abstract class UIDatascroller extends AjaxActionComponent
implements DataScrollerSource, ActionSource {
private static final Log log = LogFactory.getLog(UIDatascroller.class);
private Integer page;
public static final String COMPONENT_TYPE = "org.richfaces.Datascroller";
public static final String COMPONENT_FAMILY = "org.richfaces.Datascroller";
public static final String FIRST_FACET_NAME = "first";
public static final String LAST_FACET_NAME = "last";
public static final String NEXT_FACET_NAME = "next";
public static final String PREVIOUS_FACET_NAME = "previous";
public static final String FAST_FORWARD_FACET_NAME = "fastforward";
public static final String FAST_REWIND_FACET_NAME = "fastrewind";
public static final String FIRST_DISABLED_FACET_NAME = "first_disabled";
public static final String LAST_DISABLED_FACET_NAME = "last_disabled";
public static final String NEXT_DISABLED_FACET_NAME = "next_disabled";
public static final String PREVIOUS_DISABLED_FACET_NAME
= "previous_disabled";
public static final String FAST_FORWARD_DISABLED_FACET_NAME
= "fastforward_disabled";
public static final String FAST_REWIND_DISABLED_FACET_NAME
= "fastrewind_disabled";
public void addScrollerListener(DataScrollerListener listener) {
addFacesListener(listener);
}
public DataScrollerListener[] getScrollerListeners() {
return (DataScrollerListener[]) getFacesListeners(
DataScrollerListener.class);
}
public void removeScrollerListener(DataScrollerListener listener) {
removeFacesListener(listener);
}
public void broadcast(FacesEvent event) throws AbortProcessingException {
super.broadcast(event);
if (event instanceof DataScrollerEvent) {
DataScrollerEvent dataScrollerEvent = (DataScrollerEvent) event;
updateModel(dataScrollerEvent.getPage());
FacesContext context = getFacesContext();
MethodExpression scrollerListener = getScrollerListener();
if (scrollerListener != null) {
scrollerListener.invoke(context.getELContext(), new Object[]{event});
}
} else if (event instanceof AjaxEvent) {
FacesContext context = getFacesContext();
AjaxRendererUtils.addRegionByName(context, this, this.getId());
AjaxRendererUtils.addRegionByName(context, this, this.getFor());
setupReRender(context);
}
}
public abstract MethodExpression getScrollerListener();
public abstract void setScrollerListener(MethodExpression scrollerListener);
public abstract void setFor(String f);
public abstract String getFor();
public abstract int getFastStep();
public abstract void setFastStep(int FastStep);
public abstract boolean isRenderIfSinglePage();
public abstract void setRenderIfSinglePage(boolean renderIfSinglePage);
public abstract int getMaxPages();
public abstract void setMaxPages(int maxPages);
public abstract String getSelectedStyleClass();
public abstract void setSelectedStyleClass(String selectedStyleClass);
public abstract String getSelectedStyle();
public abstract void setSelectedStyle(String selectedStyle);
public abstract String getEventsQueue();
public abstract void setEventsQueue(String eventsQueue);
public abstract boolean isAjaxSingle();
public abstract void setAjaxSingle(boolean ajaxSingle);
public abstract int getRequestDelay();
public abstract void setRequestDelay(int requestDelay);
public abstract String getTableStyleClass();
public abstract void setTableStyleClass(String tableStyleClass);
public abstract String getStyleClass();
public abstract String getStyle();
public abstract void setStyleClass(String styleClass);
public abstract void setStyle(String styleClass);
public abstract String getAlign();
public abstract void setAlign(String align);
public abstract String getBoundaryControls();
public abstract void setBoundaryControls(String boundaryControls);
public abstract String getFastControls();
public abstract void setFastControls(String fastControls);
public abstract String getStepControls();
public abstract void setStepControls(String stepControls);
public abstract String getInactiveStyleClass();
public abstract String getInactiveStyle();
public abstract void setInactiveStyleClass(String inactiveStyleClass);
public abstract void setInactiveStyle(String inactiveStyle);
/**
* Finds the dataTable which id is mapped to the "for" property
*
* @return the dataTable component
*/
public UIData getDataTable() {
String forAttribute = getFor();
UIComponent forComp;
if (forAttribute == null) {
forComp = this;
while ((forComp = forComp.getParent()) != null) {
if (forComp instanceof UIData) {
setFor(forComp.getId());
return (UIData) forComp;
}
}
throw new FacesException(
"could not find dataTable for datascroller " + this.getId());
} else {
forComp = RendererUtils.getInstance().findComponentFor(this, forAttribute);
}
if (forComp == null) {
throw new IllegalArgumentException("could not find dataTable with id '"
+ forAttribute + "'");
} else if (!(forComp instanceof UIData)) {
throw new IllegalArgumentException(
"component with id '" + forAttribute
+ "' must be of type " + UIData.class.getName()
+ ", not type "
+ forComp.getClass().getName());
}
return (UIData) forComp;
}
private int getFastStepOrDefault() {
int step = getFastStep();
if (step <= 0) {
return 1;
} else {
return step;
}
}
/**
* @param data
* @return
* @see #getPage()
* @deprecated
*/
public int getPageIndex(UIData data) {
return getPageIndex();
}
/**
* @return
* @see #getPage()
* @deprecated
*/
public int getPageIndex() {
return getPageIndex();
}
public int getPageForFacet(String facetName) {
if (facetName == null) {
throw new NullPointerException();
}
int newPage = 1;
int pageCount = getPageCount();
if (FIRST_FACET_NAME.equals(facetName)) {
newPage = 1;
} else if (PREVIOUS_FACET_NAME.equals(facetName)) {
newPage = getPage() - 1;
} else if (NEXT_FACET_NAME.equals(facetName)) {
newPage = getPage() + 1;
} else if (LAST_FACET_NAME.equals(facetName)) {
newPage = pageCount > 0 ? pageCount : 1;
} else if (FAST_FORWARD_FACET_NAME.equals(facetName)) {
newPage = getPage() + getFastStepOrDefault();
} else if (FAST_REWIND_FACET_NAME.equals(facetName)) {
newPage = getPage() - getFastStepOrDefault();
} else {
try {
newPage = Integer.parseInt(facetName.toString());
} catch (NumberFormatException e) {
throw new FacesException(e.getLocalizedMessage(), e);
}
}
if (newPage >= 1 && newPage <= pageCount) {
return newPage;
} else {
return 0;
}
}
/**
* Sets the page number according to the parameter recived from the
* commandLink
*
* @param facetName - can be "first:, "last", "next", "previous",
* "fastforward", "fastrewind"
*
* @see #setPage(int)
* @see #getPageForFacet(String)
*
* @deprecated as of 3.2 replaced with <code>setPage(int)</page>
*/
public void setPage(String facetName) {
int newPage = getPageForFacet(facetName);
if (newPage != 0) {
setPage(newPage);
}
}
public int getPageCount(UIData data) {
int rowCount = getRowCount(data);
int rows = getRows(data);
int pageCount;
if (rows > 0) {
pageCount = rows <= 0 ? 1 : rowCount / rows;
if (rowCount % rows > 0) {
pageCount++;
}
if (pageCount == 0) {
pageCount = 1;
}
} else {
rows = 1;
pageCount = 1;
}
return pageCount;
}
/** @return the page count of the uidata */
public int getPageCount() {
return getPageCount(getDataTable());
}
public int getRowCount(UIData data) {
int rowCount = data.getRowCount();
if (rowCount >= 0) {
return rowCount;
}
return BinarySearch.search(data);
}
/** @return int */
public int getRowCount() {
//xxx nick -> alex - scrollable models can return -1 here
//let's implement "dychotomic" discovery
// setPage(1)... if isPageAvailable() setPage(2) then 4, 8, etc.
// setPage() { setRowIndex(pageIdx * rows); }
// isPageAvailable() { return isRowAvailable() }
//return getUIData().getRowCount();
return getRowCount(getDataTable());
}
public int getRows(UIData data) {
int row = 0;
row = data.getRows();
if (row == 0) {
row = getRowCount(data);
}
return row;
}
/**
* @param data
* @return
* @see #getPage()
* @deprecated
*/
public int getFirstRow(UIData data) {
return data.getFirst();
}
/**
* @param rows
* @see #setPage(int)
* @deprecated
*/
public void setFirstRow(int rows) {
getDataTable().setFirst(rows);
}
// facet getter methods
public UIComponent getFirst() {
return getFacet(FIRST_FACET_NAME);
}
public UIComponent getLast() {
return getFacet(LAST_FACET_NAME);
}
public UIComponent getNext() {
return getFacet(NEXT_FACET_NAME);
}
public UIComponent getFastForward() {
return getFacet(FAST_FORWARD_FACET_NAME);
}
public UIComponent getFastRewind() {
return getFacet(FAST_REWIND_FACET_NAME);
}
public UIComponent getPrevious() {
return getFacet(PREVIOUS_FACET_NAME);
}
private static boolean isRendered(UIComponent component) {
UIComponent c = component;
while (c != null) {
if (!c.isRendered()) {
return false;
}
c = c.getParent();
}
return true;
}
public void setupFirstRowValue() {
UIData dataTable = getDataTable();
if (isRendered()) {
Integer pageCount = getPageCount(dataTable);
int page = getPage();
int newPage = -1;
if (page < 1) {
newPage = 1;
} else if (page > pageCount) {
newPage = (pageCount != 0 ? pageCount : 1);
}
if (newPage != -1) {
FacesContext context = getFacesContext();
Object label = MessageUtil.getLabel(context, this);
String formattedMessage = Messages.getMessage(Messages.DATASCROLLER_PAGE_MISSING,
new Object[] {label, page, pageCount, newPage});
log.warn(formattedMessage);
page = newPage;
dataTable.getAttributes().put(SCROLLER_STATE_ATTRIBUTE, page);
}
if (isRendered(dataTable)) {
dataTable.setFirst((page - 1) * getRows(dataTable));
}
}
}
public void setPage(int newPage) {
this.page = newPage;
}
public int getPage() {
Map<String, Object> attributes = getDataTable().getAttributes();
Integer state = (Integer) attributes.get(SCROLLER_STATE_ATTRIBUTE);
if (state != null) {
return state;
}
if (this.page != null) {
return page;
}
ValueExpression ve = getValueExpression("page");
if (ve != null) {
try {
Integer pageObject = (Integer) ve.getValue(getFacesContext().getELContext());
if (pageObject != null) {
return pageObject;
} else {
return 1;
}
} catch (ELException e) {
throw new FacesException(e);
}
} else {
return 1;
}
}
private void updateModel(int newPage) {
UIData dataTable = getDataTable();
if (isRendered(dataTable)) {
dataTable.setFirst((newPage - 1) * getRows(dataTable));
}
Map<String, Object> attributes = dataTable.getAttributes();
attributes.put(SCROLLER_STATE_ATTRIBUTE, newPage);
FacesContext context = getFacesContext();
ValueExpression ve = getValueExpression("page");
if (ve != null) {
try {
ve.setValue(context.getELContext(), newPage);
attributes.remove(SCROLLER_STATE_ATTRIBUTE);
} catch (ELException e) {
String messageStr = e.getMessage();
Throwable result = e.getCause();
while (null != result &&
result.getClass().isAssignableFrom(ELException.class)) {
messageStr = result.getMessage();
result = result.getCause();
}
FacesMessage message;
if (null == messageStr) {
message =
MessageUtil.getMessage(context, UIInput.UPDATE_MESSAGE_ID,
new Object[] { MessageUtil.getLabel(
context, this) });
} else {
message = new FacesMessage(FacesMessage.SEVERITY_ERROR,
messageStr,
messageStr);
}
context.getExternalContext().log(message.getSummary(), result);
context.addMessage(getClientId(context), message);
context.renderResponse();
} catch (IllegalArgumentException e) {
FacesMessage message =
MessageUtil.getMessage(context, UIInput.UPDATE_MESSAGE_ID,
new Object[] { MessageUtil.getLabel(
context, this) });
context.getExternalContext().log(message.getSummary(), e);
context.addMessage(getClientId(context), message);
context.renderResponse();
} catch (Exception e) {
FacesMessage message =
MessageUtil.getMessage(context, UIInput.UPDATE_MESSAGE_ID,
new Object[] { MessageUtil.getLabel(
context, this) });
context.getExternalContext().log(message.getSummary(), e);
context.addMessage(getClientId(context), message);
context.renderResponse();
}
}
}
public String getFamily() {
return COMPONENT_FAMILY;
}
static class BinarySearch {
public static int search(UIData data) {
int rowIndex = data.getRowIndex();
try {
int n = 1;
int k = 2;
for (; ;) {
data.setRowIndex(k - 1);
if (data.isRowAvailable()) {
n = k;
k = k * 2;
} else {
break;
}
}
while (n < k) {
int kk = Math.round((n + k) / 2) + 1;
data.setRowIndex(kk - 1);
if (data.isRowAvailable()) {
n = kk;
} else {
k = kk - 1;
}
}
data.setRowIndex(k - 1);
if (data.isRowAvailable()) {
return k;
} else {
return 0;
}
} finally {
data.setRowIndex(rowIndex);
}
}
}
public Object saveState(FacesContext context) {
return new Object[] {
super.saveState(context),
page
};
}
public void restoreState(FacesContext context, Object object) {
Object[] state = (Object[]) object;
super.restoreState(context, state[0]);
page = (Integer) state[1];
}
public static final String SCROLLER_STATE_ATTRIBUTE = COMPONENT_TYPE + ":page";
public boolean isLocalPageSet() {
return page != null;
}
public void resetLocalPage() {
page = null;
}
}