Creating a Facebook-like panel with slim scrollbars and infinite scrolling in PrimeFaces

Friday February 08, 2013 ()

Some of the Facebook panels with slim scrollbars and infinite scrolling are those non full-page panels used to display notifications and messages. These are activated by button-icons in the upper left corner of your timeline. In this blog, we will demonstrate how to create similar panels using slimScroll jQuery plugin in PrimeFaces.

Please note that the first section of this blog (slimScroll panel without the infinite scrolling) also works for standard JSF projects.

The slimScroll Panel

The first step in building our slimScroll panel is to load slimScroll plugin.  Download the plugin itself from the link mentioned above and add them to the head section of the page similar to what is shown below.

<!-- jQuery itself for non PrimeFaces only, jQuery is bundled in PrimeFaces -->
<script type="text/javascript" 
     src="https://ajax.googleapis.com/ajax/libs/jquery/1.8.2/jquery.min.js">
</script>
<script type="text/javascript" 
     src="https://ajax.googleapis.com/ajax/libs/jqueryui/1.8.23/jquery-ui.js">
</script>
<!-- End jQuery itself -->

<-- slimScroll plugin -->
<script type="text/javascript" src="/kauswagan/js/jquery.slimscroll.min.js">
</script>

Attach slimScroll to an element.  In our example below, we attach slimScroll to an element with class most_slim.  Our panel has width of 300px and height of 400px.  The slim scrollBar appears only when we hover above it and fades out if we stay idle over the scrollable area.  This behaviour is similar to Facebook's.  Please refer to slimScroll documentation for more options.

<script>          
    $(function(){
        $('.most_slim').slimScroll({
            height: '400px',
            disableFadeOut: false,
            width:'300px',
            railVisible: true,
            alwaysVisible: false
        });
    });            
</script>

The markup structure below defines our most_slim panel which wraps a <h:dataTable /> our scrollable content component.  We also used <h:panelGroup /> with block layout so that JSF will generate a <div /> instead of <span />.  Notice that our most_slim panel is in turn wrapped with another <h:panelGroup />.  This is the parent of our most_slim, it is required.

<h:panelGroup style="border:1px solid #bbdddd;">
    <h:panelGroup  styleClass="most_slim" layout="block" >
        <h:dataTable  style="width:290px;" cellpadding="5" 
                      rowClasses="row_2a,row_2b"  
                      value="#{facebookBean.records}"
                      var="facebook">
            <h:column>
                <h:outputLink  value="#{newsObject.seoId}" >
                    <h:outputText escape="false" 
                           value="#{facebook.title}" />
                </h:outputLink>

            </h:column>

            <h:column>
                <h:outputText value="#{facebook.views}" escape="false" /> 
            </h:column> 
        </h:dataTable>
    </h:panelGroup>
</h:panelGroup>

Our <h:dataTable /> has a width of 290px and our most_slim has 300px.  This is to give room for the scrollBar and rail, so that it does not draw itself over the content.

For our <h:dataTable /> above to be populated with data, your backing bean (FaceBookBean in this example) should have a method List getRecords (), this is set as value #{facebookBean.records} in <h:dataTable />.  Please see further down this page for details of this bean.

At this point we already have a working panel with slim scrollBars.  This panel we just created works for both PrimeFaces and also for standard JSF projects.

Infinite scrolling demonstrated after this point, works only for PrimeFaces.

slimscroll Panel with infinite scroll

Below is the same snippet we used for the scroll panel we earlier created with slimScroll, only we added line 11 and below.  Note that in the snippet below, some user prefer to chain the two calls below, appending bind to the first call (see a chaining example near the bottom of this article).

<script>          
    $(function(){
        $('.most_slim').slimScroll({
            height: '400px',
            disableFadeOut: false,
            width:'300px',
            railVisible: true,
            alwaysVisible: false
        });

        $('.most_slim').slimScroll().bind('slimscroll', function(event, pos){
            if (pos == 'bottom') {
                more_records();
            }
        });            
    }); 
</script>

slimScroll publishes an event when either the top or bottom of the scrollable panel is reached.  In line 11 above we bind to this event to update our panel to implement our infinite scroll.  To accomplish this infinite scrolling, we add PrimeFaces' <p:remoteCommand /> (line 28 below).

more_records in line 13 above, is the name of our <p:remoteCommand />.  This gets executed as soon as we reach the bottom of the scrollable area.  The body of the JavaScript function more_records is empty, it has no body, PrimeFaces generates its body for us, at runtime.

The actionListener (line 32 below) also gets executed as more_records executes, updating our slim_panel which is our <h:dataTable />.

<h:panelGroup style="border:1px solid #bbdddd;">
    <h:form prependId="false">
        <h:panelGroup  styleClass="most_slim" layout="block" > 
            <h:dataTable id="slim_panel"  style="width:290px;" cellpadding="5" 
                         rowClasses="row_2a,row_2b"  
                         value="#{facebookBean.records}"
                         var="facebook">
                <h:column>
                    <h:outputLink  value="#{newsObject.seoId}" >
                        <h:outputText escape="false" 
                                      value="#{facebook.title}" />
                    </h:outputLink>
                </h:column>

                <h:column>
                    <h:outputText value="#{facebook.views}" escape="false" 
                          style="font-size: 11px;font-weight: normal;" />
                </h:column> 
            </h:dataTable>
        </h:panelGroup>

        <h:panelGrid columns="2" style="width:100%;padding:7px;margin-top: 3px;">
            <h:outputText value="Recent posts" />
            <h:graphicImage style="display:none;width:15px;" 
                 id="preloader" value="/images/prog.gif" />
        </h:panelGrid>

        <p:remoteCommand global="false" name="more_records"   
                         onstart="$('#preloader').show();"
                         oncomplete="$('#preloader').hide();"
                         update="slim_panel"
                         actionListener="#{facebookBean.loadMore}" />

    </h:form>
</h:panelGroup>

LIne 22 to 26 above is just the holder of the preloader image.  The preloader visiblity is controlled by onstart and oncomplete events of <p:remoteCommand />.

Below is the backing bean of the page snippet above.  We included this to highlight line 19 below, our loadMore function called by <p:remoteCommand />.  Notice that each time the function is called, which corresponds to each time our slimScroll hits the bottom of the scrollable area, endLimit is incremented and returns more data to our <h:dataTable />.  Also for this to work, the bean must be either SessionScoped or ViewScoped so that endLimit keeps its value.

@ManagedBean
@ViewScoped
public class FacebookBean implements java.io.Serializable {

    FacebookBean () {}
    
    private List<PostRecord> records;
    
    public List<PostRecord> getRecords() {
        if (records == null) {
            records = theRecords(0, endLimit);
        }
        return records;
    }

    // endLimit is the end limit of an SQL query of limit clause 
    private int endLimit = 20;
    
    public void loadMore() {        
        endLimit += 10; // Change this value to suit your needs.       
        records = theRecords(0, endLimit);
    }

    List<PostRecord>theRecords(int start, int end) {
        List<PostRecord>list = new ArrayList<>();

        // Database Query, 
        // Select statement should have a limit clause
        // using the start and end parameters of
        // this method.

        while (loop through database records) {
            PostRecord rec = new PostRecord ();
            //
            // more code
            //
            list.add (rec);
        }
        // Some code were removed for clarity.
        
        return list;
    }

    // Some lines were removed for clarity.

    public static class PostRecord implements java.io.Serializable {
        String title;
        String views;

        // getters and setters here
    }
}

Notes on infinite scroll

As more data is loaded into our scrollable panel, the scrollBar will be shorter and tends to stay near or at the bottom of the panel.  At that point infinite scroll may stop working.  For scrolling to continue working, move away from the bottom either by user action or programmatically.  See line 11 below.

<script>          
    $(function(){
        $('.most_slim').slimScroll({
            height: '400px',
            disableFadeOut: false,
            width:'300px',
            railVisible: true,
            alwaysVisible: false
        }).bind('slimscroll', function(event, pos){
            if (pos == 'bottom') {
                $('.most_slim').slimScroll({ scrollBy: '-60px' });
                more_records();
            }
        });            
    }); 
</script>

What we have above is the same snippet we have earlier, only we added line 11 to scroll away from the bottom.  In this example we used 60px of scroll back. Change this value to suit your needs.  This snippet also illustrates chaining.

That's it Good Luck.


7,223

Comments (Creating a Facebook-like panel with slim scrollbars and infinite scrolling in PrimeFaces)