How to use and append data to Masonry in responsive jQueryMobile with JSF 2 and ajax

Monday March 02, 2015 ()

   Using Masonry in jQueryMobile with JSF 2 and Ajax
This is a demonstration on using Masonry, a grid layout JavaScript library, in a responsive JSF 2 page with jQueryMobile framework. This also shows how to do partial update, appending new grid items to Masonry by fetching additional data from the server via JSF <.f:ajax /> requests. The following assumes that you have included in a page the necessary jQuery, jQueryMobile, Masonry, and ImagesLoaded JavaScript and CSS files. This is based on MyFaces 2.2.3 JSF implementation, version 1.43 of jQueryMobile (JQM), and Masonry version 3.2.

As a demo please check out our Gallery page, it employs the same code as described here.

The JSF/JQM Page

To begin, here is our JSF page with JQM and Masonry layout fetching content from a server. You should already be familiar with <ui:repeat />, if not please refer to your JSF documentation on how they work and how to use them. The "masonry-box" is our Masonry item selector and "masonry-block" is our container. See JS snippet farther down.

<div id="masonry-block">
    <ui:repeat value="#{galleryBean.gallery}" var="gal">
        <div class="masonry-box">
             <div class="masonry-item ui-body ui-body-a ui-content ui-shadow">
                <div>                                                     					
                    <!-- Contents -->                                
                </div>
            </div>
         </div>                        
    </ui:repeat>
</div>

The CSS

This page layout targets 3 broad screen sizes. Narrow down ranges/sizes and/or more media queries if targetting specific screen sizes.

@media all and (max-width:35.49em) {
   #masonry-block .masonry-box {width: 100.0%;}
   .masonry-item {margin:0 15px 20px 2px;}
   // rest of the styles corresponding to this screen size.
}

@media all and (min-width: 35.5em ) and (max-width:63.95em) {
   #masonry-block .masonry-box {width: 50%;}
    .masonry-item {margin:0 15px 20px 2px;}
   // rest of the styles
}

@media all and (min-width: 64.0em )  {
   #masonry-block .masonry-box {width: 33.3%;}
   .masonry-item {margin:0 20px 20px 2px;} 
   // rest of the styles
}

The JavaScript

The "pagecreate" event of JQM below, is similar to the ready event of jQuery. It is here where we create our Masonry object. Here we choose not to layout Masonry items after initialization (isInitLayout: false) to enable us to do extra task before we actually layout our grid. Note that in some cases, plugins like, sliders, carousels, etc, may not work with "pagecreate". If you have those in your page, you may have to bind to other events like "pagecontainershow" for example. Please check the docs for more JQM events availble for use in a given situation.

<script>
    $(document).on( "pagecreate", "#main", function(event ) {  

       var $container = $("#masonry-block");  
                                                      
       $container.imagesLoaded(function () {                                                           
           $container.masonry({
               isInitLayout: false,                     
               itemSelector: ".masonry-box",                     
               transitionDuration:"0.8s"
           });

           // Do something before actual layout.
                    
           $container.data("masonry").layout();                                                
                                                                   
   });                                                          
</script>

Appending new Masonry items with JSF <f:ajax /> requests

The code snippet that follows uses a button to prompt users to fetch new data. It is through this button that Ajax request is accomplished and new data is appended to our Masonry block. Our page from Listing 1 becomes like below:

<div id="masonry-block">
    <ui:repeat value="#{galleryBean.gallery}" var="gal">
        <div class="masonry-box">
             <div class="masonry-item ui-body ui-body-a ui-content ui-shadow">
                <div>                                                     					
                     <!-- Contents -->                                
                </div>
            </div>
         </div>                        
    </ui:repeat>
</div>

<h:inputHidden id="nextPages" value="#{galleryBean.newData}" /> 

<div  id="nextpagebwrapper">
     <h:form prependId="false" >
           <button id="nextpageb"                   
               data-icon="carat-r"
               data-iconpos="right"
               class="ui-nodisc-icon"
               data-theme="b" type="button">
            <span>Show more records</span>
            <f:ajax render="nextPages" 
               event="click"
               listener="#{monthHistory.handleEvent}"
               onerror="function(data) { myajaxerror(data)}"
               onevent="function(data) { layoutAgain(data)}"/>                                
           </button>
    </h:form>
</div>

<h:inputHidden /> above holds the data returned from the server as <f:ajax /> succeeds. This is the source of data of our new items for our Masonry. This data is formatted from the server to correspond to our Masonry item selector, identical in format to the <div /> blocks contained in the <ui:repeat /> in Listing 1. This is emptied by our JavaScript code before a new <f:ajax /> request is made.

We used <button /> but <h:commandButton /> may be used if preferred just style them as desired. Use JSF passthrough and/or JQM ui classes for visual consistency with other JQM components in the page.

The Javascript

In addition to creating our Masonry, we now have functions that are called by <f:ajax /> in Listing 4. Our timoeut of 1 second is just to make sure data is updated completely before accessing them.

<script>
    $(document).on( "pagecreate", "#main", function(event ) {  

       var $container = $("#masonry-block");    
                                                    
       $container.imagesLoaded(function () {                                                           
           $container.masonry({
               isInitLayout: false,                     
               itemSelector: ".masonry-box",                     
               transitionDuration:"0.8s"
           });

           // Do something before actual layout.
                    
           $container.data("masonry").layout();                                                
                                                                   
   });                                                          


   function layoutAgain (data) {
     if (data.status === "begin") {                        
         ajaxRequestStart();
     }
     else if (data.status === "success") {                   
         ajaxRequestSuccess();
     }   
   }

   function ajaxRequestStart () {
       $.mobile.loading("show"); 
       $("#nextPages").val("");                
   }
            
   function ajaxRequestSuccess() {
       setTimeout(function () {
       var html = $("#nextPages").val(); 
       if (html.length === 0) {
          $("#nextpageb").hide();         
        }
        else {
           html = $(html);                     
           $container.append(html).imagesLoaded(function(){                                                   
               $container.masonry( "appended", html, true );                           
           }); 
       }

       $.mobile.loading("hide"); 
                                                         
      }, 1000);                       
    }

   // open and close a basic JQM popup to inform user of the error                                               
   function myajaxerror(data) {
      $( "#ajaxerrorpopup" ).popup( "open" );  
      setTimeout(function () {
         $("#ajaxerrorpopup").popup ("close");
      },3500);                 
   }

</script>

It is in line 36 above where we access data of <h:inputHidden id="nextPages" /> for the consumption by Masonry "appended" method. See highlighted block above.

The JSF Backing bean

Our backing bean assumes we are fecthing data from MySQL data source. Please refer to inline comments for description.

@ManagedBean
@ViewScoped
public class GalleryBean {
    public GalleryBean() {
    }

    int dataStart, dataCount;
    StringBuilder htmlData, oldHtmlData;
    List<Gallery> gallery;
    String newData;

    @PostConstruct
    public void init() {
       dataStart=dataCount=10;
       fetchInitialData ();
    }

    private void fetchInitialData () {

        // When quering sql data use limit clause using 0 start and dataCount and the count
        // example select * from datasource where .... limit 0, dataCount 
        // and then update startData;
        
        // Also populate gallery object with Gallery objects      
  
        startData += dataCount;
    }

    private void loadMore () {
        // When quering sql data use limit clause using startData and dataCount
        // example select * from datasource where .... limit dataStart, dataCount
        // 
        // Build htmlData using html structure that would end up
        // like the initial data in <ui:repeat />

        // increment dataStart,  update flag that would tell us there is
        // no more data to fetch

        startData += dataCount;

        if (!htmlData.equals (oldHtmlData) {
            oldHtmlData = htmlData;
            newData = htmlData.toString ();
        }
        else {
            // No more data to fetch based on a given SQL query
            // condition.   
            newData ="";
        }
    }

    List<Gallery>getGellery () {
         // this populates <ui:repeat /> using ArrayList of Gallery object 
         // in Listing 1 which become Listing 4

         return gallery;
    }

    // This is called by our <f:ajax />
    public void handleEvent(AjaxBehaviorEvent event) {
        loadMore ();
    }

    // This populates our <h:inputHidden id="nextPages" /> which is
    // updated when ajax requests succeeds.  This is the data we append to our
    // Masonry instance.
    public String getNewData () {
        return newData;
    }

   
    public static class Gallery {
          // Members correspond to items in our initial <ui:repeat /> layout. 
          // Getters and setters too.
    }
}

That's it, good luck.


2,297

Comments (How to use and append data to Masonry in responsive jQueryMobile with JSF 2 and ajax)