Table viewer using JQuery plugin and JEE

DataTables is a JQuery plugin, facilitating building Ajax table editors. In this example, I show how to connect it to JEE backend, which is a simple Servlet.

Backend exposes a table, stored as a Java List within Servlet instance. Data is served back in JSON format using Jackson library. Example is deployed on Google App Engine, datatablesjee.appspot.com. Code is available on GitHub github.com/rafalrusin/datatablesjee.

First, we need to instantiate DataTables plugin within a html page. This is extremely easy, by using code below: AjaxSource parameter refers to Servlet URI, which handles requests for data. ServerSide argument is set to true, which means that backend will do sorting, filtering and pagination. This allows us to use large tables (>1000 rows) without performance problems on client side.

Now, we need to implement backend. Servlet requires a doGet method, which needs to retrieve parameters sent from client as an Ajax request. Those parameters describe search keyword, starting row and page size of a table.Then, we need to do filtering and sorting. I used a simple toString + contains methods on a single row in order to do filtering.

Sorting is done via custom comparator, which sorts by given column number. Following code does the job:

Last, we need to send JSON response back to client. Here, we use Jackson, which is a very convenient library for manipulating JSON in Java.iTotalRecords is total number of records, without filtering. iTotalDisplayRecords is number of records after applying filter. aaData is two dimensional array of strings, representing visible table data.

Summing up, I like the idea of Ajax in this form, because client side is not rendered directly by backend (no jsp, etc.). This makes it a detached view, which could be served as static content, from Apache Web Server for example, which is very performant.

You May Also Like

Simple trick to DRY your Grails controller

Grails controllers are not very DRY. It's easy to find duplicated code fragments in default generated controller. Take a look at code sample below. It is duplicated four times in show, edit, update and delete actions:

class BookController {
def show() {
def bookInstance = Book.get(params.id)
if (!bookInstance) {
flash.message = message(code: 'default.not.found.message', args: [message(code: 'book.label', default: 'Book'), params.id])
redirect(action: "list")
return
}
[bookInstance: bookInstance]
}
}

Why is it duplicated?

There is a reason for that duplication, though. If you move this snippet to a method, it can redirect to "list" action, but it can't prevent controller from further execution. After you call redirect, response status changes to 302, but after method exits, controller still runs subsequent code.

Solution

At TouK we've implemented a simple trick to resolve that situation:

  1. wrap everything with a simple withStoppingOnRender method,
  2. whenever you want to render or redirect AND stop controller execution - throw EndRenderingException.

We call it Big Return - return from a method and return from a controller at once. Here is how it works:

class BookController {
def show(Long id) {
withStoppingOnRender {
Book bookInstance = Book.get(id)
validateInstanceExists(bookInstance)
[bookInstance: bookInstance]
}
}

protected Object withStoppingOnRender(Closure closure) {
try {
return closure.call()
} catch (EndRenderingException e) {}
}

private void validateInstanceExists(Book instance) {
if (!instance) {
flash.message = message(code: 'default.not.found.message', args: [message(code: 'book.label', default: 'Book'), params.id])
redirect(action: "list")
throw new EndRenderingException()
}
}
}

class EndRenderingException extends RuntimeException {}

Example usage

For simple CRUD controllers, you can use this solution and create some BaseController class for your controllers. We use withStoppingOnRender in every controller so code doesn't look like a spaghetti, we follow DRY principle and code is self-documented. Win-win-win! Here is a more complex example:

class DealerController {
@Transactional
def update() {
withStoppingOnRender {
Dealer dealerInstance = Dealer.get(params.id)
validateInstanceExists(dealerInstance)
validateAccountInExternalService(dealerInstance)
checkIfInstanceWasConcurrentlyModified(dealerInstance, params.version)
dealerInstance.properties = params
saveUpdatedInstance(dealerInstance)
redirectToAfterUpdate(dealerInstance)
}
}
}