Using jQuery DataTables In Grails

DataTables (a table plug-in for jQuery) helps you add tables with dynamic content and behavior to your web applications. You get features like column sorting, content paging, filtering, etc. In this post I’ll show you an example of how to use DataTables to present data.

DataTables Screenshot

DataTables Screenshot

DataTables can obtain its data in many ways; from the DOM, a Javascript array, etc. For large data sets it’s probably best to use ajax to get just the data you want to display from the back end, rather than loading the entire data set into the browser. That’s what we’ll do here.

The source code for the view creates a table defining the columns, but no rows. Javascript runs on document load that creates the DataTable, sets some attributes, and defines the URL where the table will obtain its data.

<html>
 <head>
 <meta name="layout" content="main" />
 <title>Launching Success Products</title>
 <link rel='stylesheet' href='http://www.datatables.net//release-datatables/media/css/demo_table.css' />
 <g:javascript library="DataTables-1.7.5/media/js/jquery.dataTables" />
 <g:javascript>
    function renderPriceWithCents(obj) {
       var num = obj.aData[obj.iDataColumn]
       return '$' + num.toFixed(2);
    }
    $(document).ready(function() {
       $('#example').dataTable({
          sScrollY: '70%',
          bProcessing: true,
          bServerSide: true,
          sAjaxSource: '${request.contextPath + '/product/dataTablesSource'}' ,
          bJQueryUI: true,
          sPaginationType: "full_numbers",
          aLengthMenu: [[100, 500, 1000, 5000, -1], [100, 500, 1000, 5000, "All"]],
          iDisplayLength: 500,
          aoColumnDefs: [{
             fnRender: renderPriceWithCents,
             aTargets: [2]
          }]
       });
    });
 </g:javascript>

 </head>

 <body>
 <div id="demo">
    <table cellpadding="0" cellspacing="0" border="0" id="example">
    <thead>
       <tr>
          <th>Item Number</th>
          <th>Title</th>
          <th>Price</th>
          <th>Link</th>
          <th>Manufacturer</th>
       </tr>
    </thead>
    <tbody></tbody>
    <tfoot>
       <tr>
          <th>Item Number</th>
          <th>Title</th>
          <th>Price</th>
          <th>Link</th>
          <th>Manufacturer</th>
       </tr>
    </tfoot>
    </table>
 </div>
 </body>
</html>

The controller action to generate the JSON needed by the table is…

/**
 * Generates JSON needed for a DataTables table.
 */
 def dataTablesSource = {

    def propertiesToRender = ['itemNumber', 'title', 'price', 'link', 'manufacturer.name']

    def filters = []
    propertiesToRender.each { prop ->
       filters << "p.${prop} like :filter"
    }
    def filter = filters.join(" OR ")

    def dataToRender = [:]
    dataToRender.sEcho = params.sEcho
    dataToRender.aaData=[]                // Array of products.

    dataToRender.iTotalRecords = Product.countByEnabled(true)
    dataToRender.iTotalDisplayRecords = dataToRender.iTotalRecords

    def query = new StringBuilder("from Product as p where p.enabled=true")
    if ( params.sSearch ) {
       query.append(" and (${filter})")
    }
    def sortDir = params.sSortDir_0?.equalsIgnoreCase('asc') ? 'asc' : 'desc'
    def sortProperty = propertiesToRender[params.iSortCol_0 as int]
    query.append(" order by p.${sortProperty} ${sortDir}")

    def products = []
    if ( params.sSearch ) {
       // Revise the number of total display records after applying the filter
       def countQuery = new StringBuilder("select count(*) from Product as p where p.enabled=true")
       if ( params.sSearch ) {
          countQuery.append(" and (${filter})")
       }
       def result = Product.executeQuery(countQuery.toString(),
                                         [filter: "%${params.sSearch}%"])
       if ( result ) {
          dataToRender.iTotalDisplayRecords = result[0]
       }
       products = Product.findAll(query.toString(),
          [filter: "%${params.sSearch}%"],
          [max: params.iDisplayLength as int, offset: params.iDisplayStart as int])
    } else {
       products = Product.findAll(query.toString(),
       [max: params.iDisplayLength as int, offset: params.iDisplayStart as int])
    }

    products?.each { prod ->
       dataToRender.aaData << [prod.itemNumber,
                               prod.title,
                               prod.price,
                               prod.link,
                               prod.manufacturer?.name ?: 'Unknown']
    }

    render dataToRender as JSON
}
About these ads

About Eric Turner

I started developing software professionally back in 2001, and love it! Before that I did system administration and networking for a newspaper. Currently I earn my living developing web applications using Grails, Javascript, etc... for a small software consulting company, and am interested in the issues and technologies related to that. I'm more of a pragmatist than a purist, happy with a 90% solution that quickly gets code into the hands of people that need it (undelivered, unused code has little value).
This entry was posted in Groovy on Grails, jQuery and tagged , , , . Bookmark the permalink.

20 Responses to Using jQuery DataTables In Grails

  1. Eric Turner says:

    There must be a more efficient way to calculate iTotalDisplayRecords when a search filter has been provided (see line 29 of the controller action) than running the query just to get the size() of the resulting list! I’d love to see better ways to write this controller action.

  2. Lim Chee Kin says:

    Hi Eric,

    Thanks for writing the post. For line 29, you may write custom “select count(*)” HQL and execute it with executeQuery dynamic method http://grails.org/doc/latest/ref/Domain%20Classes/executeQuery.html.

    Best regards,
    Chee Kin

  3. MattA says:

    I’m just starting with Grails and JQuery, so I’m still trying to understand some of the basics. Could you show your domain class for Product?

    • Eric Turner says:

      It’s pretty simple; just a typical domain class with some properties, constraints, an association to another class, etc.

      package com.launchingsuccess
      
      import org.apache.commons.lang.builder.*
      
      class Product {
      
      	int productId
      	String itemNumber
      	String title
      	String description
      	String price
      	String link
      	String condition
      	Boolean enabled = false
      	String updateError
      	
      	Date dateCreated
      	Date lastUpdated
      	
          static constraints = {
      		productId(unique: true)
      		itemNumber(unique: true, nullable: true)
      		title(nullable: true)
      		description(nullable: true, size:0..4000)
      		price(nullable: true)
      		link(url: true, nullable: true)
      		condition(nullable: true)
      		enabled(nullable: false)
      		manufacturer(nullable: true)
      		updateError(nullable: true, size:0..4000)
          }
      	
      	static mapping = {
      		condition column: 'cond'
      	}
      	
      	static belongsTo = [manufacturer: Manufacturer]
      	
      	boolean equals(o) {
      		if ( ! o instanceof Product ) {
      			return false
      		}
      		def eb = new EqualsBuilder().append(productId, o.productId)
      		eb.append(itemNumber, o.itemNumber)
      		return eb.isEquals()
      	}
      	
      	int hashCode() {
      		def hcb = new HashCodeBuilder(17,37).append(productId)
      		hcb.append(itemNumber)
      		return hcb.toHashCode()
      		
      	}
      }
      
  4. Eric Turner says:

    Just an update; one thing I don’t like about my implementation is that sorting by price doesn’t behave as you would expect. This is because my Product.price is just a String (which is fine for what I’m doing because I’m simply creating a Google Merchant Center feed file from catalog data) so DataTables sorts it in ascii-betical order.

    I modified my Product domain class so that price is a BigDecimal, then added a render function to my DataTables definition to format the floating-point number. Here’s the revised DataTables creation:

    			function renderPriceWithCents(obj) {
    				var num = obj.aData[obj.iDataColumn]
    				return '$' + num.toFixed(2);
    			}
    			
    			$(document).ready(function() {
    				$('#example').dataTable({
    					sScrollY: '75%',
    					bProcessing: true,
    					bServerSide: true,
    					sAjaxSource: '/LaunchingSuccessProductFeed/product/dataTablesSource',
    					bJQueryUI: true,
    					sPaginationType: "full_numbers",
    					aLengthMenu: [[100, 500, 1000, 5000, -1], [100, 500, 1000, 5000, "All"]],
    					iDisplayLength: 500,
    					aoColumnDefs: [{
    							fnRender: renderPriceWithCents,
    							aTargets: [2]
    						}]
    				});
    			} );
    
  5. Eric Turner says:

    Another update. I modified the count query to be more efficient, and updated the code in the blog post so that it’s the latest with all the improvements so far.

  6. Lim Chee Kin says:

    Hi Eric,

    Thanks to update the code. Fyi, I had released a JQuery DataTables Grails plugin at http://grails.org/plugin/jquery-datatables few days ago.

    Did you have any idea how to use the HQL like keyword for non-string property?

    Best regards,
    Chee Kin

    • Eric Turner says:

      I’ll take a look at the plugin!

      As far as I know, the LIKE keyword in SQL (and HQL) only works with strings. You’ll need to create a string representation of your non-string properties.

  7. Lim Chee Kin says:

    Hi Eric,

    I don’t think creating additional string properties is the way to go. Searchable plugin at http://grails.org/plugin/searchable might be the solution as it support search by domain properties.

    Regards.

  8. Lim Chee Kin says:

    What do you think? Wish to hear from you again.

  9. Aaron says:

    Great article, now how about showing how to load the contents of a row in the datatable in a jQuery Dialog window when the user clicks on a row?

  10. Pingback: More jQuery DataTables: Showing Row Details | leapingmind

  11. Jay says:

    Thanks a lot… :) Gonna tweet your link too… :)

    • Eric Turner says:

      Glad you liked it! I’ve thought about writing up a blog post about how to do something similar using GSP adaptive tag libraries and the remote pagination plug-in, but haven’t quite gotten around to it yet.

  12. Awesome blog! Is your theme custom made or did you download it from somewhere?
    A design like yours with a few simple adjustements would really make my
    blog stand out. Please let me know where you got your theme.
    Kudos

  13. Pingback: Articulo: Using jQuery DataTables In Grails | Las Neuras de Davioth

  14. I’m not sure why but this weblog is loading extremely slow for me. Is anyone else having this problem or is it a problem on my end? I’ll check back
    later on and see if the problem still exists.

  15. An outstanding share! I have just forwarded this onto a co-worker who had been doing a
    little homework on this. And he actually ordered me dinner due
    to the fact that I discovered it for him… lol. So allow me to reword this.
    … Thanks for the meal!! But yeah, thanks for spending time to talk about this subject here on your website.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Connecting to %s