/*
* $Id: ScrollerComponent.java,v 1.3 2004/11/14 07:33:13 tcfujii Exp $
*/
/*
* Copyright 2004-2005 Sun Microsystems, Inc. All rights reserved.
* Use is subject to license terms.
*/
package components.components;
import components.renderkit.Util;
import javax.faces.component.UICommand;
import javax.faces.component.UIComponent;
import javax.faces.component.UIData;
import javax.faces.component.UIForm;
import javax.faces.context.FacesContext;
import javax.faces.context.ResponseWriter;
import javax.faces.el.MethodBinding;
import javax.faces.event.ActionEvent;
import java.io.IOException;
import java.util.Map;
/**
* This component produces a search engine style scroller that facilitates
* easy navigation over results that span across several pages. It
* demonstrates how a component can do decoding and encoding
* without delegating it to a renderer.
*/
public class ScrollerComponent extends UICommand {
public static final String NORTH = "NORTH";
public static final String SOUTH = "SOUTH";
public static final String EAST = "EAST";
public static final String WEST = "WEST";
public static final String BOTH = "BOTH";
public static final int ACTION_NEXT = -1;
public static final int ACTION_PREVIOUS = -2;
public static final int ACTION_NUMBER = -3;
public static final String FORM_NUMBER_ATTR = "com.sun.faces.FormNumber";
/**
* The component attribute that tells where to put the user supplied
* markup in relation to the "jump to the Nth page of results"
* widget.
*/
public static final String FACET_MARKUP_ORIENTATION_ATTR =
"navFacetOrientation";
public ScrollerComponent() {
super();
this.setRendererType(null);
}
public void decode(FacesContext context) {
String curPage = null;
String action = null;
int actionInt = 0;
int currentPage = 1;
int currentRow = 1;
String clientId = getClientId(context);
Map requestParameterMap = (Map) context.getExternalContext().
getRequestParameterMap();
action = (String) requestParameterMap.get(clientId + "_action");
if (action == null || action.length() == 0) {
// nothing to decode
return;
}
MethodBinding mb = Util.createConstantMethodBinding(action);
this.getAttributes().put("action", mb);
curPage = (String) requestParameterMap.get(clientId + "_curPage");
currentPage = Integer.valueOf(curPage).intValue();
// Assert that action's length is 1.
switch (actionInt = Integer.valueOf(action).intValue()) {
case ACTION_NEXT:
currentPage++;
break;
case ACTION_PREVIOUS:
currentPage--;
// Assert 1 < currentPage
break;
default:
currentPage = actionInt;
break;
}
// from the currentPage, calculate the current row to scroll to.
currentRow = (currentPage - 1) * getRowsPerPage(context);
this.getAttributes().put("currentPage", new Integer(currentPage));
this.getAttributes().put("currentRow", new Integer(currentRow));
this.queueEvent(new ActionEvent(this));
}
public void encodeBegin(FacesContext context) throws IOException {
return;
}
public void encodeEnd(FacesContext context) throws IOException {
int currentPage = 1;
ResponseWriter writer = context.getResponseWriter();
String clientId = getClientId(context);
Integer curPage = (Integer) getAttributes().get("currentPage");
if (curPage != null) {
currentPage = curPage.intValue();
}
int totalPages = getTotalPages(context);
writer.write("<table border=\"0\" cellpadding=\"0\" align=\"center\">");
writer.write("<tr align=\"center\" valign=\"top\">");
writer.write(
"<td><font size=\"-1\">Result Page: </font></td>");
// write the Previous link if necessary
writer.write("<td>");
writeNavWidgetMarkup(context, clientId, ACTION_PREVIOUS,
(1 < currentPage));
// last arg is true iff we're not the first page
writer.write("</td>");
// render the page navigation links
int i = 0;
int first = 1;
int last = totalPages;
if (10 < currentPage) {
first = currentPage - 10;
}
if ((currentPage + 9) < totalPages) {
last = currentPage + 9;
}
for (i = first; i <= last; i++) {
writer.write("<td>");
writeNavWidgetMarkup(context, clientId, i, (i != currentPage));
writer.write("</td>");
}
// write the Next link if necessary
writer.write("<td>");
writeNavWidgetMarkup(context, clientId, ACTION_NEXT,
(currentPage < totalPages));
writer.write("</td>");
writer.write("</tr>");
writer.write(getHiddenFields(clientId));
writer.write("</table>");
}
public boolean getRendersChildren() {
return true;
}
/**
* <p>Return the component family for this component.</p>
*/
public String getFamily() {
return ("Scroller");
}
//
// Helper methods
//
/**
* Write the markup to render a navigation widget. Override this to
* replace the default navigation widget of link with something
* else.
*/
protected void writeNavWidgetMarkup(FacesContext context,
String clientId,
int navActionType,
boolean enabled) throws IOException {
ResponseWriter writer = context.getResponseWriter();
String facetOrientation = NORTH;
String facetName = null;
String linkText = null;
String localLinkText = null;
UIComponent facet = null;
boolean isCurrentPage = false;
boolean isPageNumber = false;
// Assign values for local variables based on the navActionType
switch (navActionType) {
case ACTION_NEXT:
facetName = "next";
linkText = "Next";
break;
case ACTION_PREVIOUS:
facetName = "previous";
linkText = "Previous";
break;
default:
facetName = "number";
linkText = "" + navActionType;
isPageNumber = true;
// heuristic: if navActionType is number, and we are not
// enabled, this must be the current page.
if (!enabled) {
facetName = "current";
isCurrentPage = true;
}
break;
}
// leverage any navigation facets we have
writer.write("\n ");
if (enabled) {
writer.write("<a " + getAnchorAttrs(context, clientId,
navActionType) + ">");
}
facet = getFacet(facetName);
// render the facet pertaining to this widget type in the NORTH
// and WEST cases.
if (facet != null) {
// If we're rendering a "go to the Nth page" link
if (isPageNumber) {
// See if the user specified an orientation
String facetO = (String) getAttributes().get(
FACET_MARKUP_ORIENTATION_ATTR);
if (facet != null) {
facetOrientation = facetO;
// verify that the orientation is valid
if (!(facetOrientation.equalsIgnoreCase(NORTH) ||
facetOrientation.equalsIgnoreCase(SOUTH) ||
facetOrientation.equalsIgnoreCase(EAST) ||
facetOrientation.equalsIgnoreCase(WEST))) {
facetOrientation = NORTH;
}
}
}
// output the facet as specified in facetOrientation
if (facetOrientation.equalsIgnoreCase(NORTH) ||
facetOrientation.equalsIgnoreCase(EAST)) {
facet.encodeBegin(context);
if (facet.getRendersChildren()) {
facet.encodeChildren(context);
}
facet.encodeEnd(context);
}
// The difference between NORTH and EAST is that NORTH
// requires a <br>.
if (facetOrientation.equalsIgnoreCase(NORTH)) {
writer.startElement("br", null); // PENDING(craigmcc)
writer.endElement("br");
}
}
// if we have a facet, only output the link text if
// navActionType is number
if (null != facet) {
if (navActionType != ACTION_NEXT &&
navActionType != ACTION_PREVIOUS) {
writer.write(linkText);
}
} else {
writer.write(linkText);
}
// output the facet in the EAST and SOUTH cases
if (null != facet) {
if (facetOrientation.equalsIgnoreCase(SOUTH)) {
writer.startElement("br", null); // PENDING(craigmcc)
writer.endElement("br");
}
// The difference between SOUTH and WEST is that SOUTH
// requires a <br>.
if (facetOrientation.equalsIgnoreCase(SOUTH) ||
facetOrientation.equalsIgnoreCase(WEST)) {
facet.encodeBegin(context);
if (facet.getRendersChildren()) {
facet.encodeChildren(context);
}
facet.encodeEnd(context);
}
}
if (enabled) {
writer.write("</a>");
}
}
/**
* <p>Build and return the string consisting of the attibutes for a
* result set navigation link anchor.</p>
*
* @param context the FacesContext
* @param clientId the clientId of the enclosing UIComponent
* @param action the value for the rhs of the =
*
* @return a String suitable for setting as the value of a navigation
* href.
*/
private String getAnchorAttrs(FacesContext context, String clientId,
int action) {
int currentPage = 1;
int formNumber = getFormNumber(context);
Integer curPage = (Integer) getAttributes().get("currentPage");
if (curPage != null) {
currentPage = curPage.intValue();
}
String result =
"href=\"#\" " +
"onmousedown=\"" +
"document.forms[" + formNumber + "]['" + clientId +
"_action'].value='" +
action +
"'; " +
"document.forms[" + formNumber + "]['" + clientId +
"_curPage'].value='" +
currentPage +
"'; " +
"document.forms[" + formNumber + "].submit()\"";
return result;
}
private String getHiddenFields(String clientId) {
String result =
"<input type=\"hidden\" name=\"" + clientId + "_action\"/>\n" +
"<input type=\"hidden\" name=\"" + clientId + "_curPage\"/>";
return result;
}
// PENDING: avoid doing this each time called. Perhaps
// store in our own attr?
protected UIForm getForm(FacesContext context) {
UIComponent parent = this.getParent();
while (parent != null) {
if (parent instanceof UIForm) {
break;
}
parent = parent.getParent();
}
return (UIForm) parent;
}
protected int getFormNumber(FacesContext context) {
Map requestMap = context.getExternalContext().getRequestMap();
int numForms = 0;
Integer formsInt = null;
// find out the current number of forms in the page.
if (null != (formsInt = (Integer)
requestMap.get(FORM_NUMBER_ATTR))) {
numForms = formsInt.intValue();
// since the form index in the document starts from 0.
numForms--;
}
return numForms;
}
/**
* Returns the total number of pages in the result set based on
* <code>rows</code> and <code>rowCount</code> of <code>UIData</code>
* component that this scroller is associated with.
* For the purposes of this demo, we are assuming the <code>UIData</code> to
* be child of <code>UIForm</code> component and not nested inside a custom
* NamingContainer.
*/
protected int getTotalPages(FacesContext context) {
String forValue = (String) getAttributes().get("for");
UIData uiData = (UIData) getForm(context).findComponent(forValue);
if (uiData == null) {
return 0;
}
int rowsPerPage = uiData.getRows();
int totalRows = 0;
int result = 0;
totalRows = uiData.getRowCount();
result = totalRows / rowsPerPage;
if (0 != (totalRows % rowsPerPage)) {
result++;
}
return result;
}
/**
* Returns the number of rows to display by looking up the
* <code>UIData</code> component that this scroller is associated with.
* For the purposes of this demo, we are assuming the <code>UIData</code> to
* be child of <code>UIForm</code> component and not nested inside a custom
* NamingContainer.
*/
protected int getRowsPerPage(FacesContext context) {
String forValue = (String) getAttributes().get("for");
UIData uiData = (UIData) getForm(context).findComponent(forValue);
if (uiData == null) {
return 0;
}
return uiData.getRows();
}
}