/*
* � Copyright IBM Corp. 2014, 2015
*
* 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.
*/
package com.ibm.xsp.theme.bootstrap.renderkit.html.extlib;
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 javax.faces.render.Renderer;
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.extlib.util.ExtLibUtil;
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 PagerRenderer extends Renderer {
public static final String VAR_PAGE = "page"; //$NON-NLS-1$
protected static final int PROP_PAGERCLASS = 1;
protected static final int PROP_LISTITEMCLASS = 2;
protected static final int PROP_PAGERLINKCLASS = 3;
protected static final int PROP_ACTIVECLASS = 4;
protected static final int PROP_DISABLEDCLASS = 5;
protected static final int PROP_PAGERARIAROLE = 10;
protected Object getProperty(int prop) {
switch(prop) {
case PROP_PAGERCLASS: return "pagination"; // $NON-NLS-1$
case PROP_LISTITEMCLASS: return ""; // $NON-NLS-1$
case PROP_PAGERLINKCLASS: return ""; // $NON-NLS-1$
case PROP_ACTIVECLASS: return "active"; // $NON-NLS-1$
case PROP_DISABLEDCLASS: return "disabled"; // $NON-NLS-1$
case PROP_PAGERARIAROLE: return "navigation"; // $NON-NLS-1$
}
return null;
}
@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();
}
XspPager pager = (XspPager) component;
UIPager.PagerState st = ((UIPager) component).createPagerState();
if (st == null) {
return;
}
ResponseWriter writer = context.getResponseWriter();
encodePagerContent(context, writer, st, pager);
}
protected void encodePagerContent(FacesContext context, ResponseWriter w, UIPager.PagerState st, XspPager pager) throws IOException {
// Compute the page that should be displayed
int pageCount = st.getPageCount();
int start = getStart(st, pageCount);
int end = getEnd(st, pageCount, start);
String pagerId = pager.getClientId(context);
boolean RTL = false;
w.startElement("div", null); // $NON-NLS-1$
String pagerRole = (String)getProperty(PROP_PAGERARIAROLE);
if(StringUtil.isNotEmpty(pagerRole)) {
w.writeAttribute("role", pagerRole, null); // $NON-NLS-1$
}
// "Pager" is default aria label
String pagerAriaLabel = pager.getAriaLabel();
String ariaLabel = StringUtil.isNotEmpty(pagerAriaLabel) ? pagerAriaLabel : com.ibm.xsp.extlib.controls.ResourceHandler.getString("PagerRenderer.Pager"); //$NON-NLS-1
if(StringUtil.isNotEmpty(ariaLabel)) {
w.writeAttribute("aria-label", ariaLabel, null); // $NON-NLS-1$
}
w.startElement("ul", null); // $NON-NLS-1$
String styleClass = pager.getStyleClass();
String pgClass = ExtLibUtil.concatStyleClasses((String)getProperty(PROP_PAGERCLASS), styleClass);
if (StringUtil.isNotEmpty(pgClass)) {
w.writeAttribute("class", pgClass, null); // $NON-NLS-1$
}
if (StringUtil.isNotEmpty(pagerId)) {
w.writeAttribute("id", pagerId, null); // $NON-NLS-1$
}
List<?> listControls = pager.getChildren();
if (listControls.isEmpty()) {
return;
}
Iterator<?> it = listControls.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, pager, st, w, control, type, start, end, RTL);
continue;
} else if (isLast(type) && !pager.isAlwaysCalculateLast()) {
if (!st.hasMoreRows()) {
encodeAction(context, pager, st, w, control, type, start, end, RTL);
} else {
w.startElement("li", null); // $NON-NLS-1$
String disabledClass = ExtLibUtil.concatStyleClasses((String)getProperty(PROP_LISTITEMCLASS), (String)getProperty(PROP_DISABLEDCLASS));
if(StringUtil.isNotEmpty(disabledClass)) {
w.writeAttribute("class", disabledClass, null); // $NON-NLS-1$
}
w.startElement("a", null);
String pagerLinkClass = (String)getProperty(PROP_PAGERLINKCLASS);
if(StringUtil.isNotEmpty(pagerLinkClass)) {
w.writeAttribute("class", pagerLinkClass, null); // $NON-NLS-1$
}
w.writeText(getMayBeMorePages(), null);
w.endElement("li"); // $NON-NLS-1$
w.endElement("a"); // $NON-NLS-1$
}
continue;
} else if (type.equalsIgnoreCase(UIPagerControl.TYPE_GROUP)) {
encodeGroup(context, pager, st, w, control, start, end);
continue;
} else if (type.equalsIgnoreCase(UIPagerControl.TYPE_STATUS)) {
encodeStatus(context, st, w, pager, control, start, end);
continue;
} else if (isSeparator(type)) {
encodeSeparator(context, w, 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);
}
}
w.endElement("ul"); // $NON-NLS-1$
w.endElement("div"); // $NON-NLS-1$
}
protected void encodeAction(FacesContext context, XspPager pager, UIPager.PagerState st, ResponseWriter writer, XspPagerControl control, String type, int start, int end,
boolean RTL) throws IOException {
String clientId = pager.getClientId(context);
String controlId = clientId + "__" + type;
String defaultText = "";
String ariaLabel = "";
boolean renderLink = true;
//TODO need to handle BIDI here for the unicode symbols
if (isFirst(type)) {
renderLink = st.getCurrentPage() > start;
// "\u00AB" FirstSymbol
defaultText = "\u00AB"; //$NON-NLS-1$
// "First page"
ariaLabel = com.ibm.xsp.extlib.controls.ResourceHandler.getString("PagerRenderer.Firstpage"); //$NON-NLS-1$
} else if (isPrevious(type)) {
renderLink = st.getCurrentPage() > start;
// "\u2039" PreviousSymbol
defaultText = "\u2039"; //$NON-NLS-1$
// "Previous page"
ariaLabel = com.ibm.xsp.extlib.controls.ResourceHandler.getString("PagerRenderer.Previouspage"); //$NON-NLS-1$
} else if (isNext(type)) {
renderLink = st.getCurrentPage() < end - 1;
// "\u203A" NextSymbol
defaultText = "\u203A"; //$NON-NLS-1$;
// "Next page"
ariaLabel = com.ibm.xsp.extlib.controls.ResourceHandler.getString("PagerRenderer.Nextpage"); //$NON-NLS-1$
} else if (isLast(type)) {
renderLink = st.getCurrentPage() < end - 1;
// "\u00BB" LastSymbol
defaultText = "\u00BB"; //$NON-NLS-1$
// "Last page"
ariaLabel = com.ibm.xsp.extlib.controls.ResourceHandler.getString("PagerRenderer.Lastpage"); //$NON-NLS-1$
}
writer.startElement("li", null); // $NON-NLS-1$
String listItemClass = (String)getProperty(PROP_LISTITEMCLASS);
if(!renderLink) {
//If current page is the first, disable first/previous pagers
//and if current page is the last, disable last/next pagers
listItemClass = ExtLibUtil.concatStyleClasses(listItemClass, (String)getProperty(PROP_DISABLEDCLASS));
}
if(StringUtil.isNotEmpty(listItemClass)) {
writer.writeAttribute("class", listItemClass, null); // $NON-NLS-1$
}
// 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) {
//add a11y attributes
writer.writeAttribute("aria-disabled", "true", null); // $NON-NLS-1$ $NON-NLS-2$
}else{
writer.writeAttribute("aria-disabled", "false", null); // $NON-NLS-1$ $NON-NLS-2$
writer.writeAttribute("href", "#", null); // $NON-NLS-1$ $NON-NLS-2$
}
String pagerLinkClass = (String)getProperty(PROP_PAGERLINKCLASS);
if(StringUtil.isNotEmpty(pagerLinkClass)) {
writer.writeAttribute("class", pagerLinkClass, null); // $NON-NLS-1$
}
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, st, controlId, controlId + "__lnk"); // $NON-NLS-1$
}
}
writer.endElement("li"); // $NON-NLS-1$
}
protected void encodeGroup(FacesContext context, XspPager pager, UIPager.PagerState st, ResponseWriter writer, XspPagerControl control, int start, int end) throws IOException {
// Save the old page value
Map<String, Object> requestMap = TypedUtil.getRequestMap(context.getExternalContext());
Object oldPage = requestMap.get(VAR_PAGE);
String clientId = pager.getClientId(context);
String controlId = clientId + "__" + control.getType();//$NON-NLS-1$
// Encode the pages
for (int i = start; i < end; i++) {
// Push the page number
requestMap.put(VAR_PAGE, i + 1);
boolean renderLink = (i != st.getCurrentPage());
writer.startElement("li", null); // $NON-NLS-1$
String listItemClass = (String)getProperty(PROP_LISTITEMCLASS);
if (!renderLink) {
listItemClass = ExtLibUtil.concatStyleClasses(listItemClass, (String)getProperty(PROP_ACTIVECLASS));
}
if(StringUtil.isNotEmpty(listItemClass)) {
writer.writeAttribute("class", listItemClass, null); // $NON-NLS-1$
}
String val = (String) control.getValue();
if (StringUtil.isEmpty(val)) {
val = Integer.toString(i + 1);
}
// Generate the text link
if (StringUtil.isNotEmpty(val)) {
writer.startElement("a", control); //$NON-NLS-1$
writer.writeAttribute("id", controlId + "__lnk__" + i, null); // $NON-NLS-1$ $NON-NLS-2$
// "Page {0}"
String ariaLabel = com.ibm.xsp.extlib.controls.ResourceHandler.getString("PagerRenderer.Page0"); //$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$
String pagerLinkClass = (String)getProperty(PROP_PAGERLINKCLASS);
if(StringUtil.isNotEmpty(pagerLinkClass)) {
writer.writeAttribute("class", pagerLinkClass, null); // $NON-NLS-1$
}
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, st, 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 (end < st.getLastPage() || st.hasMoreRows()) {
writer.startElement("li", null); // $NON-NLS-1$
String listItemClass = ExtLibUtil.concatStyleClasses((String)getProperty(PROP_LISTITEMCLASS), (String)getProperty(PROP_DISABLEDCLASS));
if(StringUtil.isNotEmpty(listItemClass)) {
writer.writeAttribute("class", listItemClass, null); // $NON-NLS-1$
}
writer.startElement("a", control); //$NON-NLS-1$
String pagerLinkClass = (String)getProperty(PROP_PAGERLINKCLASS);
if(StringUtil.isNotEmpty(pagerLinkClass)) {
writer.writeAttribute("class", pagerLinkClass, null); // $NON-NLS-1$
}
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, UIPager.PagerState st, ResponseWriter writer, XspPager pager, XspPagerControl control, int start, int end) throws IOException {
writer.startElement("li", null); // $NON-NLS-1$
String listItemClass = ExtLibUtil.concatStyleClasses((String)getProperty(PROP_LISTITEMCLASS), (String)getProperty(PROP_DISABLEDCLASS));
if(StringUtil.isNotEmpty(listItemClass)) {
writer.writeAttribute("class", listItemClass, null); // $NON-NLS-1$
}
String val = (String) control.getValue();
if (StringUtil.isEmpty(val)) {
val = "{0}"; // $NON-NLS-1$
}
if (StringUtil.isNotEmpty(val) && st.getLastPage() > 0) {
writer.startElement("a", null); // $NON-NLS-1$
String pagerLinkClass = (String)getProperty(PROP_PAGERLINKCLASS);
if(StringUtil.isNotEmpty(pagerLinkClass)) {
writer.writeAttribute("class", pagerLinkClass, null); // $NON-NLS-1$
}
writer.writeAttribute("role", "button", null); // $NON-NLS-2$ $NON-NLS-1$
val = StringUtil.format(val, st.getCurrentPage() + 1, st.getLastPage(), start, end);
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;
}
String listItemClass = ExtLibUtil.concatStyleClasses((String)getProperty(PROP_LISTITEMCLASS), (String)getProperty(PROP_DISABLEDCLASS));
if(StringUtil.isNotEmpty(listItemClass)) {
writer.writeAttribute("class", listItemClass, null); // $NON-NLS-1$
}
// Generate the text link
if (StringUtil.isNotEmpty(val)) {
writer.startElement("a", null); // $NON-NLS-1$
String pagerLinkClass = (String)getProperty(PROP_PAGERLINKCLASS);
if(StringUtil.isNotEmpty(pagerLinkClass)) {
writer.writeAttribute("class", pagerLinkClass, null); // $NON-NLS-1$
}
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 getStart(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 getEnd(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$
}
}