Reputation: 55
What I wanted to achieve in a recent application was to use my own pagination - so I need to know how many documents are in a (categorized) view to calculate the pages needed.
<xp:repeat id="repeat1" rows="30"
value="#{javascript:viewScope.counterArray}"
indexVar="counter" var="row" disableTheme="true"
removeRepeat="true">
<li>
<xp:link escape="true"
text="#{javascript:(counter + 1) + ' (' + row.toString() + ')'}" id="link3"
disableTheme="true">
<xp:eventHandler event="onclick"
submit="true">
<xp:this.action>
<xp:executeScript>
<xp:this.script><![CDATA[#{javascript:viewScope.put("repeatFirstIndex", row);}]]></xp:this.script>
</xp:executeScript>
</xp:this.action>
</xp:eventHandler>
</xp:link>
</li>
</xp:repeat>
Becaue of this known problem I have to do a workaround, querying the number of documents via a repeat control.
<xp:repeat id="blindRepeat" rows="0" value="#{viewStories}"></xp:repeat>
I use this sample code to get the right number of iterations for the repeat control.
<xp:this.beforePageLoad><![CDATA[#{javascript:
var counterArray = [];
var numberOfStories = getComponent("blindRepeat").getRowCount();
var numberOfPagesNecessary = Math.ceil(numberOfStories / sessionScope.get("ListPagerPerPage"));
for (var i = 0; i < (numberOfPagesNecessary - 1); i++) {
counterArray[i] = i*sessionScope.get("ListPagerPerPage");
viewScope.lastPage = i*sessionScope.get("ListPagerPerPage");
}
viewScope.counterArray = counterArray;}]]></xp:this.beforePageLoad>
But right here is where I run into a Catch 22: I can only use a repeat control, because I cannot get the richt number of documents from the view directly, like Knut Herrmann (1) does, but the repeat control is not available , so I cannot use it like Per Henrik Lausten (2) proposes.
A colleague proposed filling the Array in a Script block that is nested behind the blind Repeat Control (so it should be drawn) but before the pagination repeat control (so viewScope should hold the right numbers), but this solution seems rather flaky to me.
So I am inquiring if there is a 'best practise' for this kind of situation - I'd like to solve this properly and not in a way that depends on the timely execution of some javascript
Upvotes: 0
Views: 642
Reputation: 1260
Considered the reason you gave in the comment to your question I suggest you to implement your own custom renderer for the pager.
In the faces-config.xml
declare as follows:
<render-kit>
<renderer>
<component-family>com.ibm.xsp.Pager
</component-family>
<renderer-type>com.ibm.xsp.XPager
</renderer-type>
<renderer-class>com.irc.xsp.renderer.UIPagerRenderer
</renderer-class>
</renderer>
</render-kit>
Where com.irc.xsp.renderer.UIPagerRenderer
is the rendering class. Obviously you can name it whatever you want. Just use the same name in both places.
The renderer could look something like this:
package com.irc.xsp.renderer;
import java.io.IOException;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import javax.faces.component.UIComponent;
import javax.faces.context.FacesContext;
import javax.faces.context.ResponseWriter;
import com.ibm.commons.util.StringUtil;
import com.ibm.xsp.FacesExceptionEx;
import com.ibm.xsp.component.UIPager;
import com.ibm.xsp.component.UIPagerControl;
import com.ibm.xsp.component.xp.XspPager;
import com.ibm.xsp.component.xp.XspPagerControl;
import com.ibm.xsp.context.FacesContextEx;
import com.ibm.xsp.event.PagerEvent;
import com.ibm.xsp.extsn.ResourceHandler;
import com.ibm.xsp.renderkit.html_extended.XPagerRenderer;
import com.ibm.xsp.util.AjaxUtilEx;
import com.ibm.xsp.util.FacesUtil;
import com.ibm.xsp.util.JavaScriptUtil;
import com.ibm.xsp.util.TypedUtil;
public class UIPagerRenderer extends XPagerRenderer {
public static final String VAR_PAGE = "page"; //$NON-NLS-1$
private enum Property {
LIST_ITEM_CLASS, PAGER_LINK_CLASS, ACTIVE_CLASS("active"), DISABLED_CLASS("disabled");
String className;
Property() {
}
Property(String s) {
this.className = s;
}
String getClassName() {
return className;
}
}
@Override
public void decode(FacesContext context, UIComponent component) {
super.decode(context, component);
// check that this component cause the submit
if (decodeCausedSubmit(context, component)) {
PagerEvent pagerEvent = new PagerEvent(component);
String hiddenValue = FacesUtil.getHiddenFieldValue(context);
if (StringUtil.isNotEmpty(hiddenValue)) {
int pos = hiddenValue.lastIndexOf('_');
if (pos > -1) {
hiddenValue = hiddenValue.substring(pos + 1);
if (isFirst(hiddenValue)) {
pagerEvent.setAction(PagerEvent.ACTION_FIRST);
} else if (isLast(hiddenValue)) {
pagerEvent.setAction(PagerEvent.ACTION_LAST);
} else if (isNext(hiddenValue)) {
pagerEvent.setAction(PagerEvent.ACTION_NEXT);
} else if (isPrevious(hiddenValue)) {
pagerEvent.setAction(PagerEvent.ACTION_PREVIOUS);
} else {
try {
int value = Integer.parseInt(hiddenValue);
pagerEvent.setAction(PagerEvent.ACTION_GOTOPAGE);
pagerEvent.setPage(value);
} catch (NumberFormatException nfe) {
return; // just don't queue the event
}
}
} else {
return;
}
}
((UIPager) component).queueEvent(pagerEvent);
}
}
private boolean decodeCausedSubmit(FacesContext context, UIComponent component) {
String currentClientId = component.getClientId(context);
String hiddenValue = FacesUtil.getHiddenFieldValue(context);
if (currentClientId != null && hiddenValue != null) {
return StringUtil.indexOfIgnoreCase(hiddenValue, currentClientId) > -1;
}
return false;
}
@Override
public boolean getRendersChildren() {
return true;
}
@Override
public void encodeChildren(FacesContext context, UIComponent component) throws IOException {
if (context == null || component == null) {
throw new IOException();
}
List<?> controls = component.getChildren();
if (controls.isEmpty()) {
return;
}
XspPager pager = (XspPager) component;
UIPager.PagerState pagerState = ((UIPager) component).createPagerState();
if (pagerState == null) {
throw new FacesExceptionEx(ResourceHandler.getString("PagerRenderer.Pagerisnotassociatedwithanydataco"));
}
encodePagerContent(context, context.getResponseWriter(), pager, pagerState, controls, false);
}
protected void encodePagerContent(FacesContext context, ResponseWriter writer, XspPager pager,
UIPager.PagerState pagerState, List<?> controls, boolean rtl) throws IOException {
// Compute the pages that should be displayed
int pageCount = pagerState.getPageCount();
int startCount = getStartCount(pagerState, pageCount);
int endCount = getEndCount(pagerState, pageCount, startCount);
String pagerId = pager.getClientId(context);
String pagerRole = pager.getRole();
String pagerTitle = pager.getTitle();
String pagerOuterClass = pager.getOuterStyleClass();
String pagerAriaLabel = pager.getAriaLabel();
boolean closeOuterTag = false;
if (isAnyAssigned(pagerRole, pagerTitle, pagerOuterClass, pagerAriaLabel)) {
writer.startElement("div", null); // $NON-NLS-1$
writeNonNullAttributeOnly(writer, "role", pagerRole);
writeNonNullAttributeOnly(writer, "title", pagerTitle);
writeNonNullAttributeOnly(writer, "class", pagerOuterClass);
writeNonNullAttributeOnly(writer, "aria-label", pagerAriaLabel);
closeOuterTag = true;
}
writer.startElement("ul", null);
writeNonNullAttributeOnly(writer, "class", pager.getStyleClass());
writeNonNullAttributeOnly(writer, "id", pagerId);
Iterator<?> it = controls.iterator();
while (it.hasNext()) {
Object obj = it.next();
if (obj instanceof XspPagerControl) {
XspPagerControl control = (XspPagerControl) obj;
String type = control.getType();
if (StringUtil.isNotEmpty(type)) {
if (isFirst(type) || isNext(type) || isPrevious(type)
|| (isLast(type) && pager.isAlwaysCalculateLast())) {
encodeAction(context, writer, pager, pagerState, pagerId, control, type, startCount, endCount,
rtl);
continue;
} else if (isLast(type) && !pager.isAlwaysCalculateLast()) {
if (!pagerState.hasMoreRows()) {
encodeAction(context, writer, pager, pagerState, pagerId, control, type, startCount,
endCount, rtl);
} else {
writer.startElement("li", null);
writeNonNullAttributeOnly(writer, "class", com.irc.util.StringUtil.concat(Property.LIST_ITEM_CLASS
.getClassName(), Property.DISABLED_CLASS.getClassName()));
writer.startElement("a", null);
writeNonNullAttributeOnly(writer, "class", Property.PAGER_LINK_CLASS.getClassName());
writer.writeText(getMayBeMorePages(), null);
writer.endElement("a");
writer.endElement("li");
}
continue;
} else if (type.equalsIgnoreCase(UIPagerControl.TYPE_GROUP)) {
encodeGroup(context, writer, pager, pagerState, pagerId, control, startCount, endCount);
continue;
} else if (type.equalsIgnoreCase(UIPagerControl.TYPE_STATUS)) {
encodeStatus(context, writer, pager, pagerState, control, startCount, endCount);
continue;
} else if (isSeparator(type)) {
encodeSeparator(context, writer, control, type);
continue;
} else if (type.equalsIgnoreCase(UIPagerControl.TYPE_GOTO)) {
encodeGoto();
continue;
}
}
// "Unknown control type {0}"
String msg = com.ibm.xsp.extsn.ResourceHandler.getString("PagerRenderer.Unknowncontroltype0"); //$NON-NLS-1$
msg = StringUtil.format(msg, type);
throw new FacesExceptionEx(msg);
}
}
writer.endElement("ul");
if (closeOuterTag) {
writer.endElement("div");
}
}
protected void encodeAction(FacesContext context, ResponseWriter writer, XspPager pager,
UIPager.PagerState pagerState, String pagerId, XspPagerControl control, String type, int startCount,
int endCount, boolean rtl) throws IOException {
String controlId = pagerId + "__" + type;
String defaultText = "";
String ariaLabel = "";
boolean renderLink = true;
//TODO need to handle BIDI here for the unicode symbols
if (isFirst(type)) {
renderLink = pagerState.getCurrentPage() > startCount;
// "\u00AB" FirstSymbol
defaultText = "\u00AB"; //$NON-NLS-1$
// "First page"
ariaLabel = ResourceHandler.getString("PagerRenderer.First"); //$NON-NLS-1$
} else if (isPrevious(type)) {
renderLink = pagerState.getCurrentPage() > startCount;
// "\u2039" PreviousSymbol
defaultText = "\u2039"; //$NON-NLS-1$
// "Previous page"
ariaLabel = ResourceHandler.getString("PagerRenderer.Previous"); //$NON-NLS-1$
} else if (isNext(type)) {
renderLink = pagerState.getCurrentPage() < endCount - 1;
// "\u203A" NextSymbol
defaultText = "\u203A"; //$NON-NLS-1$;
// "Next page"
ariaLabel = ResourceHandler.getString("PagerRenderer.Next"); //$NON-NLS-1$
} else if (isLast(type)) {
renderLink = pagerState.getCurrentPage() < endCount - 1;
// "\u00BB" LastSymbol
defaultText = "\u00BB"; //$NON-NLS-1$
// "Last page"
ariaLabel = ResourceHandler.getString("PagerRenderer.Last"); //$NON-NLS-1$
}
writer.startElement("li", null);
String listItemClass = Property.LIST_ITEM_CLASS.getClassName();
if (!renderLink) {
//If current page is the first, disable first/previous pagers
//and if current page is the last, disable last/next pagers
listItemClass = com.irc.util.StringUtil.concat(listItemClass, Property.DISABLED_CLASS.getClassName());
}
writeNonNullAttributeOnly(writer, "class", listItemClass);
// Generate the image link
String val = (String) control.getValue();
if (StringUtil.isEmpty(val)) {
val = defaultText;
}
// Generate the text link
if (StringUtil.isNotEmpty(val)) {
writer.startElement("a", null); // $NON-NLS-1$
if (renderLink) {
writer.writeAttribute("aria-disabled", "false", null); // $NON-NLS-1$ $NON-NLS-2$
writer.writeAttribute("href", "#", null); // $NON-NLS-1$ $NON-NLS-2$
} else {
//add a11y attributes
writer.writeAttribute("aria-disabled", "true", null); // $NON-NLS-1$ $NON-NLS-2$
}
writeNonNullAttributeOnly(writer, "class", Property.PAGER_LINK_CLASS.getClassName());
writer.writeAttribute("id", controlId + "__lnk", null); // $NON-NLS-1$ $NON-NLS-2$
writer.writeAttribute("role", "button", null); // $NON-NLS-1$ $NON-NLS-2$
writer.writeAttribute("aria-label", ariaLabel, null); // $NON-NLS-1$
writer.writeText(val, null);
writer.endElement("a"); // $NON-NLS-1$
if (renderLink) {
setupSubmitOnClick(context, pager, pagerState, controlId, controlId + "__lnk"); // $NON-NLS-1$
}
}
writer.endElement("li"); // $NON-NLS-1$
}
protected void encodeGroup(FacesContext context, ResponseWriter writer, XspPager pager,
UIPager.PagerState pagerState, String pagerId, XspPagerControl control, int startCount, int endCount)
throws IOException {
// Save the old page value
Map<String, Object> requestMap = TypedUtil.getRequestMap(context.getExternalContext());
Object oldPage = requestMap.get(VAR_PAGE);
String controlId = pagerId + "__" + control.getType();//$NON-NLS-1$
// Encode the pages
for (int i = startCount; i < endCount; i++) {
// Push the page number
requestMap.put(VAR_PAGE, i + 1);
boolean renderLink = (i != pagerState.getCurrentPage());
writer.startElement("li", null); // $NON-NLS-1$
String listItemClass = Property.LIST_ITEM_CLASS.getClassName();
if (!renderLink) {
listItemClass = com.irc.util.StringUtil.concat(listItemClass, Property.ACTIVE_CLASS.getClassName());
}
writeNonNullAttributeOnly(writer, "class", listItemClass);
// Generate the image link
String val = (String) control.getValue();
if (StringUtil.isEmpty(val)) {
val = Integer.toString(i + 1);
}
// Generate the text link
if (StringUtil.isNotEmpty(val)) { // Generate the text link
writer.startElement("a", control); //$NON-NLS-1$
writer.writeAttribute("id", controlId + "__lnk__" + i, null); // $NON-NLS-1$ $NON-NLS-2$
// "Page {0}"
String ariaLabel = ResourceHandler.getString("PagerRenderer.Gotopage0"); //$NON-NLS-1$
ariaLabel = StringUtil.format(ariaLabel, val);
writer.writeAttribute("aria-label", ariaLabel, null); // $NON-NLS-1$
writer.writeAttribute("role", "button", null); // $NON-NLS-1$ $NON-NLS-2$
writeNonNullAttributeOnly(writer, "class", Property.PAGER_LINK_CLASS.getClassName());
if (renderLink) { //make sure the a is tab-able
writer.writeAttribute("tabindex", "0", null); // $NON-NLS-1$ $NON-NLS-2$
writer.writeAttribute("aria-pressed", "false", null); // $NON-NLS-1$ $NON-NLS-2$
} else {
writer.writeAttribute("aria-pressed", "true", null); // $NON-NLS-1$ $NON-NLS-2$
}
writer.writeText(val, null);
writer.endElement("a"); // $NON-NLS-1$
if (renderLink) {
setupSubmitOnClick(context, pager, pagerState, controlId + "__lnk__" + i, controlId + "__lnk__" + i); // $NON-NLS-1$ $NON-NLS-2$
}
}
writer.endElement("li"); // $NON-NLS-1$
}
// Encode after the pages
if (!pager.isAlwaysCalculateLast()) {
if (endCount < pagerState.getLastPage() || pagerState.hasMoreRows()) {
writer.startElement("li", null); // $NON-NLS-1$
writeNonNullAttributeOnly(writer, "class", com.irc.util.StringUtil.concat(Property.LIST_ITEM_CLASS.getClassName(),
Property.DISABLED_CLASS.getClassName()));
writer.startElement("a", control); //$NON-NLS-1$
writeNonNullAttributeOnly(writer, "class", Property.PAGER_LINK_CLASS.getClassName());
writer.writeText(getMayBeMorePages(), null);
writer.endElement("a"); // $NON-NLS-1$
writer.endElement("li"); // $NON-NLS-1$
}
}
// Restore the old page value
if (oldPage != null) {
requestMap.put(VAR_PAGE, oldPage);
} else {
requestMap.remove(VAR_PAGE);
}
}
protected void setupSubmitOnClick(FacesContext context, XspPager component, UIPager.PagerState st, String clientId,
String sourceId) {
boolean immediate = false;
UIComponent subTree = ((FacesContextEx) context).getSubTreeComponent();
boolean partialExec = component.isPartialExecute();
String execId = null;
if (partialExec) {
execId = component.getClientId(context);
immediate = true;
} else {
if (subTree != null) {
partialExec = true;
execId = subTree.getClientId(context);
immediate = true;
}
}
boolean partialRefresh = component.isPartialRefresh();
String refreshId = null;
if (partialRefresh) {
UIComponent refreshComponent = component.findSharedDataPagerParent();
if (null == refreshComponent) {
refreshComponent = (UIComponent) st.getDataIterator();
}
refreshId = AjaxUtilEx.getRefreshId(context, refreshComponent);
} else {
if (subTree != null) {
partialRefresh = true;
refreshId = subTree.getClientId(context);
}
}
// call some JavaScript in xspClient.js
final String event = "onclick"; // $NON-NLS-1$
// Note, the onClick event is also triggered if the user tabs to the
// image\link and presses enter (Not just when clicked with a
// mouse).
// When the source is clicked, put its id in the hidden field and
// submit the form.
StringBuilder buff = new StringBuilder();
if (partialRefresh) {
JavaScriptUtil.appendAttachPartialRefreshEvent(buff, clientId, sourceId, execId, event,
/* clientSideScriptName */null, immediate ? JavaScriptUtil.VALIDATION_NONE
: JavaScriptUtil.VALIDATION_FULL,
/* refreshId */refreshId,
/* onstart */getOnStart(component),
/* oncomplete */getOnComplete(component),
/* onerror */getOnError(component));
} else {
JavaScriptUtil.appendAttachEvent(buff, clientId, sourceId, execId, event,
/* clientSideScriptName */null,
/* submit */true, immediate ? JavaScriptUtil.VALIDATION_NONE : JavaScriptUtil.VALIDATION_FULL);
}
String script = buff.toString();
// Add the script block we just generated.
JavaScriptUtil.addScriptOnLoad(script);
}
protected String getOnStart(XspPager component) {
return (String) component.getAttributes().get("onStart"); // $NON-NLS-1$
}
protected String getOnComplete(XspPager component) {
return (String) component.getAttributes().get("onComplete"); // $NON-NLS-1$
}
protected String getOnError(XspPager component) {
return (String) component.getAttributes().get("onError"); // $NON-NLS-1$
}
protected void encodeStatus(FacesContext context, ResponseWriter writer, XspPager pager,
UIPager.PagerState pagerState, XspPagerControl control, int startCount, int endCount) throws IOException {
writer.startElement("li", null); // $NON-NLS-1$
writeNonNullAttributeOnly(writer, "class", com.irc.util.StringUtil.concat(Property.LIST_ITEM_CLASS.getClassName(),
Property.DISABLED_CLASS.getClassName()));
String val = (String) control.getValue();
if (StringUtil.isEmpty(val)) {
val = "{0}"; // $NON-NLS-1$
}
if (pagerState.getLastPage() > 0) {
writer.startElement("a", null); // $NON-NLS-1$
writeNonNullAttributeOnly(writer, "class", Property.PAGER_LINK_CLASS.getClassName());
writer.writeAttribute("role", "button", null); // $NON-NLS-2$ $NON-NLS-1$
val = StringUtil.format(val, pagerState.getCurrentPage() + 1, pagerState.getLastPage(), startCount,
endCount);
writer.writeText(val, null);
writer.endElement("a"); // $NON-NLS-1$
}
writer.endElement("li"); // $NON-NLS-1$
}
protected void encodeSeparator(FacesContext context, ResponseWriter writer, XspPagerControl control, String type)
throws IOException {
String val = (String) control.getValue();
writer.startElement("li", null); // $NON-NLS-1$
if (StringUtil.isEmpty(val)) {
String defaultSeparator = "|"; // $NON-NLS-1$
if (type.equalsIgnoreCase(UIPagerControl.TYPE_SEPARATORPAGE)) {
// "Page"
defaultSeparator = com.ibm.xsp.extsn.ResourceHandler.getString("PagerRenderer.Page"); //$NON-NLS-1$
}
val = defaultSeparator;
}
writeNonNullAttributeOnly(writer, "class", com.irc.util.StringUtil.concat(Property.LIST_ITEM_CLASS.getClassName(),
Property.DISABLED_CLASS.getClassName()));
// Generate the text link
if (StringUtil.isNotEmpty(val)) {
writer.startElement("a", null); // $NON-NLS-1$
writeNonNullAttributeOnly(writer, "class", Property.PAGER_LINK_CLASS.getClassName());
writer.writeText(val, null);
writer.endElement("a"); // $NON-NLS-1$
}
writer.endElement("li"); // $NON-NLS-1$
}
protected void encodeGoto() {
// Do not exists in core XPages yet..
}
protected int getStartCount(UIPager.PagerState st, int pageCount) {
int start = (st.getFirst() / st.getRows()) - pageCount / 2;
start = Math.min(Math.max(0, st.getLastPage() - pageCount), Math.max(0, start));
return start;
}
protected int getEndCount(UIPager.PagerState st, int pageCount, int start) {
int sizeOfPageRange = Math.min(start + pageCount, st.getLastPage()) - start;
int end = start + sizeOfPageRange;
return end;
}
protected boolean isFirst(String type) {
return (type.equalsIgnoreCase(UIPagerControl.TYPE_FIRST)
|| type.equalsIgnoreCase(UIPagerControl.TYPE_FIRSTARROW) || type
.equalsIgnoreCase(UIPagerControl.TYPE_FIRSTIMAGE));
}
protected boolean isNext(String type) {
return (type.equalsIgnoreCase(UIPagerControl.TYPE_NEXT) || type.equalsIgnoreCase(UIPagerControl.TYPE_NEXTARROW) || type
.equalsIgnoreCase(UIPagerControl.TYPE_NEXTIMAGE));
}
protected boolean isLast(String type) {
return (type.equalsIgnoreCase(UIPagerControl.TYPE_LAST) || type.equalsIgnoreCase(UIPagerControl.TYPE_LASTARROW) || type
.equalsIgnoreCase(UIPagerControl.TYPE_LASTIMAGE));
}
protected boolean isPrevious(String type) {
return (type.equalsIgnoreCase(UIPagerControl.TYPE_PREVIOUS)
|| type.equalsIgnoreCase(UIPagerControl.TYPE_PREVIOUSARROW) || type
.equalsIgnoreCase(UIPagerControl.TYPE_PREVIOUSIMAGE));
}
protected boolean isSeparator(String type) {
return (type.equalsIgnoreCase(UIPagerControl.TYPE_SEPARATOR) || type
.equalsIgnoreCase(UIPagerControl.TYPE_SEPARATORPAGE));
}
protected String getMayBeMorePages() {
return "..."; // $NON-NLS-1$
}
private void writeNonNullAttributeOnly(ResponseWriter writer, String attributeName, String attributeValue)
throws IOException {
if (attributeValue != null && !attributeValue.isEmpty()) {
writer.writeAttribute(attributeName, attributeValue, null);
}
}
private boolean isAnyAssigned(String... objs) {
for (String s : objs) {
if (s != null && !s.isEmpty())
return true;
}
return false;
}
}
That is a class I've been using in my last project. Basically it's re-implemeting everything that the pager does in order to have control over the look and feel of the component - it's called renderer after all. You could try to tweak it here and there to see how the various things work out on the page. It's a learning process.
Upvotes: 3