elstar IT

Fullstack | Java | Tech Speaker | Tech Coach | Frank van der Linden

  • About me
  • Blog license
  • My Open source projects

XPages tip: My take on a bootstrap pager

17-12-2015 4 responses flinden68 community development

On Stackoverflow I answered a question about the strange behaviour of builtin bootstrap pager of the Extension Library. Bryan Schmiedeler asked me to share my own renderer.

How it started

Before Boostrap4XPages was pulled into the Extension Library I already has a requirement of a Bootstrap styled pager in a XPages application. I gave Bootstrap4XPages a try, but noticed some strange side effects in my application. So I switched off Bootstrap4XPages in the application and switched back to plain vanilla Bootstrap, but I took a quik look at the source code on Github and took the BoostrapPagerRenderer code and added to my own application. I changed to code to my needs.

Java class

The base of the new Bootstrap pager look and feel starts with the Java class

package nl.elstarit.renderers;

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 BootstrapPagerRenderer extends Renderer {
    
    public static final String VAR_PAGE = "page";
    
    @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 {

        int pageCount = st.getPageCount();
        
        int start = getStart(st, pageCount);
        int end = getEnd(st, pageCount, start);
        
        String pagerId = pager.getClientId(context);
        
        boolean RTL = false;
        
        // bootstrap 2
        boolean b2 = false;
        
        // bootstrap 3
        boolean b3 = true;
        
        w.startElement("div", null);
        if (b2) {
            w.writeAttribute("class", ExtLibUtil.concatStyleClasses(
                    "pagination", pager.getStyleClass()), null);
        }
        if (b3) {
            String pgClass = pager.getStyleClass();
            if (StringUtil.isNotEmpty(pgClass)) {
                w.writeAttribute("class", pgClass, null);
            }
        }
        if (StringUtil.isNotEmpty(pagerId)) {
            w.writeAttribute("id", pagerId, null);
        }
        w.startElement("ul", null);
        if (b3) {
            w.writeAttribute("class", "pagination", null);
        }
        
        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);
                            w.writeAttribute("class", "disabled", null);
                            w.startElement("a", null);
                            w.writeText(getMayBeMorePages(), null);
                            w.endElement("li");
                            w.endElement("a");
                        }
                        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;
                    }
                }
                String msg = StringUtil
                        .format("Unknown control type {0}", type);
                throw new FacesExceptionEx(msg);
            }
        }
        
        w.endElement("ul");
        w.endElement("div");
    }
    
    private 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 = "";
        boolean renderLink = true;
        
        if (isFirst(type)) {
            renderLink = st.getCurrentPage() > start;
            defaultText = "\u00AB"; // First
        } else if (isPrevious(type)) {
            renderLink = true; 
            defaultText = "\u2039"; // Previous
        } else if (isNext(type)) {
            renderLink = true; 
            defaultText = "\u203A"; // Next
        } else if (isLast(type)) {
            renderLink = st.getCurrentPage() < end - 1;
            defaultText = "\u00BB"; // Last
        }
        
        writer.startElement("li", null);
        if (!renderLink) {
            writer.writeAttribute("class", "active", null);
        }
        
        // 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);
            writer.writeAttribute("id", controlId + "__lnk", null);
            writer.writeAttribute("href", "#", null);

            if ("next".equals(val)) {
                writer.startElement("i", null);
                writer.writeAttribute("class", "fa fa-chevron-right", null);
                writer.endElement("i");
            } else if ("previous".equals(val)) {
                writer.startElement("i", null);
                writer.writeAttribute("class", "fa fa-chevron-left", null);
                writer.endElement("i");
            } else {
                writer.writeText(val, null);
            }
            writer.endElement("a");
            if (renderLink) {
                setupSubmitOnClick(context, pager, st, controlId, controlId
                        + "__lnk");
            }
        }
        
        writer.endElement("li");
    }
    
    private 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);
            if (!renderLink) {
                writer.writeAttribute("class", "active", null);
            }
            
            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);
                writer.writeAttribute("id", controlId + "__lnk__" + i, null);
                writer.writeText(val, null);
                
                writer.endElement("a");
                if (renderLink) {
                    setupSubmitOnClick(context, pager, st, controlId
                            + "__lnk__" + i, controlId + "__lnk__" + i);
                }
            }
            
            writer.endElement("li");
        }
        
        // Encode after the pages
        if (!pager.isAlwaysCalculateLast()) {
            if (end < st.getLastPage() || st.hasMoreRows()) {
                writer.startElement("li", null);
                writer.writeAttribute("class", "disabled", null);
                writer.startElement("a", control);
                writer.writeText(getMayBeMorePages(), null);
                writer.endElement("a");
                writer.endElement("li");
            }
        }
        
        // Restore the old page value
        if (oldPage != null) {
            requestMap.put(VAR_PAGE, oldPage);
        } else {
            requestMap.remove(VAR_PAGE);
        }
        
    }
    
    private 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);
            }
        }

        final String event = "onclick";
        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$
    }
    
    private void encodeStatus(FacesContext context, UIPager.PagerState st,
            ResponseWriter writer, XspPager pager, XspPagerControl control,
            int start, int end) throws IOException {
        writer.startElement("li", null);
        writer.writeAttribute("class", "disabled", null);
        
        String val = (String) control.getValue();
        if (StringUtil.isEmpty(val)) {
            val = "{0}";
        }
        if (StringUtil.isNotEmpty(val) && st.getLastPage() > 0) {
            writer.startElement("a", null);
            val = StringUtil.format(val, st.getCurrentPage() + 1, st
                    .getLastPage(), start, end);
            writer.writeText(val, null);
            writer.endElement("a");
        }
        
        writer.endElement("li");
    }
    
    private void encodeSeparator(FacesContext context, ResponseWriter writer,
            XspPagerControl control, String type) throws IOException {
        String val = (String) control.getValue();
        
        writer.startElement("li", null);
        
        if (StringUtil.isEmpty(val)) {
            String defaultSeparator = "|";
            if (type.equalsIgnoreCase(UIPagerControl.TYPE_SEPARATORPAGE)) {
                defaultSeparator = "Page";
            }
            val = defaultSeparator;
        }
        
        // Generate the text link
        if (StringUtil.isNotEmpty(val)) {
            writer.startElement("a", null);
            writer.writeText(val, null);
            writer.endElement("a");
        }
        
        writer.endElement("li");
    }
    
    private void encodeGoto() {
        // Do not exists in core XPages yet..
    }
    
    private 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;
    }
    
    private int getEnd(UIPager.PagerState st, int pageCount, int start) {
        int sizeOfPageRange = Math.min(start + pageCount, st.getLastPage())
                - start;
        int end = start + sizeOfPageRange;
        return end;
    }
    
    private boolean isFirst(String type) {
        return (type.equalsIgnoreCase(UIPagerControl.TYPE_FIRST)
                || type.equalsIgnoreCase(UIPagerControl.TYPE_FIRSTARROW) || type
                .equalsIgnoreCase(UIPagerControl.TYPE_FIRSTIMAGE));
    }
    
    private boolean isNext(String type) {
        return (type.equalsIgnoreCase(UIPagerControl.TYPE_NEXT)
                || type.equalsIgnoreCase(UIPagerControl.TYPE_NEXTARROW) || type
                .equalsIgnoreCase(UIPagerControl.TYPE_NEXTIMAGE));
    }
    
    private boolean isLast(String type) {
        return (type.equalsIgnoreCase(UIPagerControl.TYPE_LAST)
                || type.equalsIgnoreCase(UIPagerControl.TYPE_LASTARROW) || type
                .equalsIgnoreCase(UIPagerControl.TYPE_LASTIMAGE));
    }
    
    private boolean isPrevious(String type) {
        return (type.equalsIgnoreCase(UIPagerControl.TYPE_PREVIOUS)
                || type.equalsIgnoreCase(UIPagerControl.TYPE_PREVIOUSARROW) || type
                .equalsIgnoreCase(UIPagerControl.TYPE_PREVIOUSIMAGE));
    }
    
    private boolean isSeparator(String type) {
        return (type.equalsIgnoreCase(UIPagerControl.TYPE_SEPARATOR) || type
                .equalsIgnoreCase(UIPagerControl.TYPE_SEPARATORPAGE));
    }
    
    private String getMayBeMorePages() {
        return "..."; // $NLS-PagerRenderer.MayBeMorePages-1$
    }
}

RenderType

In my custom theme I have specified a RenderType

<!-- Theme pager -->
	<control>
		<name>Pager</name>
	    <property>
	      <name>rendererType</name>
	      <value>nl.elstarit.renderers.type.BootstrapPagerRenderer</value>
	    </property>
	  </control>

Final step
To make it work everything comes together in the faces-config.xml

<!-- Bootstrap render pager -->
  <render-kit>
    <renderer>
      <component-family>com.ibm.xsp.Pager</component-family>
      <renderer-type>nl.elstarit.renderers.type.BootstrapPagerRenderer</renderer-type>
      <renderer-class>nl.elstarit.renderers.BootstrapPagerRenderer</renderer-class>
    </renderer>
  </render-kit>

Result

The final result is a list, which can be put every where on the XPages. And can align by css to the right or left.

BootstrapPager

Tags: development, java, xpages

4 thoughts on “XPages tip: My take on a bootstrap pager”

  1. Patrick Kwinten says:
    17-12-2015 at 11:13

    great! I was just about to ask you if you could tell more about it/share some code. thanks!

    Reply
    1. Frank van der Linden says:
      17-12-2015 at 11:35

      May be good to know, that I first added bootstrap manually to my app, but after that Bootstrap4XPages is part of the Extension Library I removed Bootstrap from my app and used the builtin version

      Reply
  2. Patrick Kwinten says:
    01-03-2016 at 11:26

    Hi Frank (and readers)

    There is also a xsnippet for rendering a pager via CSS but I notice in the snippet the … notation for next set of pages is not styled properly (read: not styled at all) so I can definitely recommend your approach.

    Reply
  3. Pingback: Styling the Pager control for Bootstrap UI – Kwintessential Notes

Leave a Reply Cancel reply

Your email address will not be published. Required fields are marked *

  • « XPages quicktip: use the Calendar build in Months
  • Mentoring, modernisation or liberation »

Contact me

My name is Frank van der Linden and I am an independent software developer based in the Netherlands. The last 2 years I was awarded as IBM Champion. Also I am on the board of OpenNTF. My specialisations are Java, Web development and Domino.


If you want to hire me, please fill in the Contact form


IBM Champion web badge
Apache Logo

All the code on this blog are under the Apache License 2.0. For more details, see Apache License 2.0

Most recent posts

  • NL Portal, a open source project
  • 10th anniversary as a freelancer and more…
  • Engage 2020: Hello are you listening, There is stream for everything
  • Spring Cloud Function on Azure run locally
  • Deploy Spring Cloud Function to IBM Cloud

Latest reactions

  • Spring Cloud Function on Azure run locally - elstar IT on Deploy Spring Cloud Function to IBM Cloud
  • flinden68 on Quick XPages tip: add Fullcalendar plugin to your application
  • Rajesh samal on Quick tip: Swagger support for Spring Webflux
  • dsieyx on Quick XPages tip: add Fullcalendar plugin to your application
  • John on Named as IBM Champion 2019

Archive

  • November 2024
  • April 2024
  • March 2020
  • February 2020
  • January 2020
  • October 2019
  • September 2019
  • June 2019
  • May 2019
  • April 2019
  • March 2019
  • January 2019
  • December 2018
  • October 2018
  • September 2018
  • May 2018
  • April 2018
  • March 2018
  • February 2018
  • January 2018
  • December 2017
  • November 2017
  • October 2017
  • July 2017
  • June 2017
  • May 2017
  • April 2017
  • March 2017
  • February 2017
  • January 2017
  • December 2016
  • November 2016
  • October 2016
  • September 2016
  • August 2016
  • June 2016
  • May 2016
  • April 2016
  • March 2016
  • February 2016
  • December 2015
  • November 2015
  • October 2015
  • September 2015
  • August 2015
  • July 2015
  • June 2015
  • May 2015
  • April 2015
  • March 2015
  • February 2015
  • December 2014
  • October 2014
  • September 2014
  • August 2014
  • July 2014
  • June 2014
  • May 2014
  • April 2014
  • March 2014
  • February 2014

Category

  • bluemix
  • business
  • cloudant
  • community
  • development
  • hrassistant
  • openntf
  • running
  • salesforce
  • Springboot
  • Tesla
  • trailrunning
  • Uncategorized
  • watson
  • OpenNTF
  • Collaboration Today
  • XSnippets
  • Stackoverflow
  • IBM Collaboration Solutions
  • Social Business Toolkit
  • About me
  • Dutch curriculum vitae
  • English curriculum vitae
  • Google+
  • LinkedIn profile
  • Twitter
  • Slideshare
  • Blog license
  • My open source projects