Friday, December 28, 2012

Jquery JTable with Java and Spring

I just finished a new project working with Jtable to create some crud tables. I was pretty happy with the tool but made numerous extension to the framework which I'll look at today, I'll also provide some java spring mvc examples which I was unable to find online as all the documentation was for asp.net.

Why don't I start with a list of the features I've added:

  • Client side sorting with tablesorter
  • New hidden-edit and hidden-create types
  • Custom up/down arrows for sorting
  • Date Format fix for a date that is just a number ie: doesn't have format Date(1320259705710)
  • Removed cancel buttons so you have to close the dialog with the X in the top corner
  • Use an add record button that is outside the table (default is in the footer row)
  • Default null date to empty string rather than today
  • Loading dialog is optional
  • Option to show error message in table row rather than in popup
  • Added expand contract buttons for the child table
  • Added date time formatting (default is only date)
  • Allow hidden header row (handy for child tables)
  • Added grouping of data by a column (data must be sorted by the group)

Lets start by looking at some of the java code in the spring controller, and then move on to the jtable extensions:

This is the search method:
 
@RequestMapping(value = "/custSearch.do", method = RequestMethod.POST)
@ResponseBody
public JsonJtableResponse search(@ModelAttribute CustSearch searchFilters) {
 //check all search filters are not empty
 if (searchFilters == null || searchFilters.isEmpty()) {
  return new JsonJtableResponse().error("At least one filter must be specified");
 }

 List customerList = customerService.geCustByFilter(searchFilters);

 return new JsonJtableResponse().ok(customerList);
}
Next we have the insert new record:
 
@RequestMapping(value = "/insertCust.do", method = RequestMethod.POST)
@ResponseBody
public JsonJtableResponse insert(@ModelAttribute Customer customer, BindingResult result) {
 if (result.hasErrors()) {
  return new JsonJtableResponse().error(result.getAllErrors());
 }
 try {
  Customer newCust = customerService.insertCust(customer);
  return new JsonJtableResponse().ok(newCust);
 } catch (Exception e) {
  return new JsonJtableResponse().error(e.getMessage());
 }
}
Then the update record:
@RequestMapping(value = "/updateCust.do", method = RequestMethod.POST)
@ResponseBody
public JsonJtableResponse update(@ModelAttribute Customer customer, BindingResult result) {
 if (result.hasErrors()) {
  return new JsonJtableResponse().error(result.getAllErrors());
 }
 try {
  customerService.updateCust(customer);
  return new JsonJtableResponse().ok();
 } catch (Exception e) {
  return new JsonJtableResponse().error(e.getMessage());
 }
}
Finally the delete:
@RequestMapping(value = "/deleteCust.do", method = RequestMethod.POST)
@ResponseBody
public JsonJtableResponse delete(@RequestParam Integer custId) {
 try {
  customerService.deleteCust(custId);
  return new JsonJtableResponse().ok();
 } catch (Exception e) {
  return new JsonJtableResponse().error(e.getMessage());
 }
}
You've probably noticed that I've wrapped the jtable api with the JsonJtableResponse object. You can download this handy helper class here.

 As for the jsp, all you really need is a div for the jtable:
<!-- Jtable Dependencies -->
<link rel="stylesheet" type="text/css" href="/css/jquery/jtable.css"/>
<link rel="stylesheet" type="text/css" href="/css/jquery/validationEngine.jquery.css"/>
<script type="text/javascript" src="/scripts/jquery.jtable.js"></script>
<script type="text/javascript" src="/scripts/jquery.validationEngine.js"></script>
<script type="text/javascript" src="/scripts/jquery.validationEngine-en.js"></script>
<script type="text/javascript" src="/scripts/jquery.tablesorter.min.js"></script>

<script type="text/javascript" src="/scripts/manageCust.js"></script>

<%-- Built with jtable --%>
<div id="custSearchResultsDiv" class="searchResultsDiv">
 <div style="overflow: hidden; margin-top: 5px; margin-bottom: 5px;">
  <div class="jtable-rowCount"></div>
 </div>
 <div class="buttonDiv">
  <input id="custInsertButton" type="button" class="but searchButton jtable-add-record" value="Insert New Customer"/>
 </div>
</div>
The real work is done in the manageCust.js this is where the table structure is defined and the controller urls get configured:
$(document).ready(function() {
 
 $("#custSearchButton").click(function() {
  initResultsTable();
 });
 
 //setup the jtable that will display the results
    $('#custSearchResultsDiv').jtable({
        paging: false,
        sorting: false, //this is an ajax sort
        clientSort: true, //this needs jquery.tablesorter.min.js
        columnResizable: false,
        columnSelectable: false,
        selecting: false,
        multiselect: false,
        selectingCheckboxes: false,
        
        actions: {
            listAction: baseUrl + '/admin/searchCust.do',
            createAction: baseUrl + '/admin/insertCust.do',
            updateAction: baseUrl + '/admin/updateCust.do',
            deleteAction: baseUrl + '/admin/deleteCust.do'
        },
        fields: {
         custId: {
                key: true,
                create: false,
                edit: false,
                list: false
            },
            name: {
                title: 'Full Name',
                width: '30%',
                inputClass: 'validate[required]',
                type: 'hidden-edit' 
            },
            birthYear: {
                title: 'Birth Year',
                width: '15%'
            },
            employer: {
                title: 'Employer',
                width: '25%'
            },
            infoAsOfDate: {
                title: 'As Of Date',
                type: 'date',
                width: '15%',
            },
            disabled: {
                title: 'Status',
                type: 'checkbox',
                values: { 'false': 'Active', 'true': 'Disabled' },
                defaultValue: 'false',
                width: '15%',
                listClass: 'center'
            }
        },
        //Initialize validation logic when a form is created
        formCreated: function (event, data) {
            data.form.validationEngine();
        },
        //Validate form when it is being submitted
        formSubmitting: function (event, data) {
            return data.form.validationEngine('validate');
        },
        //Dispose validation logic when form is closed
        formClosed: function (event, data) {
            data.form.validationEngine('hide');
            data.form.validationEngine('detach');
        }
    });
    
});

function initResultsTable() {
 
 //perform the search and passin the filters - filters will be bound to the CustomerSearch bean
    $('#custSearchResultsDiv').jtable('load', {
     birthYear: $("#birthYear").val(),
     employer: $("#employer").val()
    }, 
    function() { //load complete
     //alert('table data loaded');
    });
    
}

Above you can see some of the extension I've made; type: 'hidden-edit' allows the Customer Name to be entered when you insert a new customer, but the name is not able to be updated when editing.

I've also added clientSort: true, this allows the data to be sorted on the client side using table sorter which must be in the jsp.

As I'm running out of time I'll just attach my enhanced version of jtable as well as the css I've been using:
Original Jtable (handy to do a compare and extract the new features you need)
My Enhanced Jtable
My new Jtable css

Feel free to use and edit these as needed. I will go into more detail of my enhancements in a future post.