Sunday, December 12, 2010

jqGrid and Spring 3 MVC Integration Tutorial

In this tutorial we will build a Spring 3 MVC application that utilizes annotations in the business layer and jqGrid in the presentation layer. A working knowledge of Spring MVC, JQuery, and jqGrid is assumed for this tutorial. If you need guidance regarding jqGrid, feel free to visit the jqGrid Documentation and also search the www.stackoverflow.com for questions about jqGrid configuration. For Spring and JQuery, a simple Google search will provide numerous tutorials.

Note: An updated version of this tutorial is now accessible at Spring MVC 3.1, jqGrid, and Spring Data JPA Integration Guide

What is jqGrid?
jqGrid is an Ajax-enabled JavaScript control that provides solutions for representing and manipulating tabular data on the web. Since the grid is a client-side solution loading data dynamically through Ajax callbacks, it can be integrated with any server-side technology, including PHP, ASP, Java Servlets, JSP, ColdFusion, and Perl.

jqGrid uses a jQuery Java Script Library and is written as plugin for that package.

Source: http://www.trirand.com/jqgridwiki/doku.php
Here's a screenshot of what we will be doing:



Our application is a simple CRUD system for managing a list of users. The presentation layer uses a jqGrid to display the data which is built on top of the JQuery, a great JavaScript framework. The business layer is composed of simple Spring beans annotated with @Controller and @Service. Our data is provided by an in-memory dummy list of users. Switching to a real data provider, like a database, should be easy enough. Notice in this tutorial I strive
to stick with simple POJO development. This makes the application easy to comprehend as well to debug. Comments are provided within the code to facilitate in the comprehension of this material.

Let's start by defining our domain.

Our domain is quite simple. It only contains a single object. We have a User class with three fields: id, firstName, lastName
package org.krams.tutorial.domain;

/**
 * A simple POJO to represent our user domain  
 *
 */
public class User {

 private Long id;
 private String firstName;
 private String lastName;
 
 public Long getId() {
  return id;
 }
 public void setId(Long id) {
  this.id = id;
 }
 
 public String getFirstName() {
  return firstName;
 }
 public void setFirstName(String firstName) {
  this.firstName = firstName;
 }
 
 public String getLastName() {
  return lastName;
 }
 public void setLastName(String lastName) {
  this.lastName = lastName;
 }

}
Now we define our service layer.

The service handles the business logic of our application. It's provides CRUD operations for the User object, like adding, editing, deleting, and retrieving all users. Following good OOP practice we declare an interface first:

Our service implementation is a simple class annotated with the @Service to make it service, and @Transactional to make it transactional as well. Our service manipulates an in-memory array of users. Then init() method is the one responsible for initializing our dummy users.

Here's the service implementation:
/**
 * 
 */
package org.krams.tutorial.service;

import java.util.ArrayList;
import java.util.List;

import javax.annotation.Resource;

import org.apache.log4j.Logger;
import org.krams.tutorial.domain.User;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

/**
 * Handles CRUD services for users
 * 
 */
@Service("userService")
@Transactional
public class UserService implements IUserService   {
  
 private List dummyUsersList = new ArrayList();
 
 protected static Logger logger = Logger.getLogger("service");

 public UserService() {
  // Initialize our in-memory list of users
  init();
 }
 
 public List getAll() {
  logger.debug("Retrieving all users");

  return dummyUsersList;
 }
 
 public User get( String id ) {
  logger.debug("Retrieving an existing user");
  
  return dummyUsersList.get( Integer.valueOf(id) );
  
 }

 public Boolean add( User user ) {
  logger.debug("Adding a new user");
  
  try {
   // Assign a new id
   user.setId( Long.valueOf(dummyUsersList.size()) );
   
   dummyUsersList.add(user);
   return true;
  } catch (Exception e) {
   return false;
  }
 }
 
 public Boolean delete( User user ) {
  logger.debug("Deleting an existing user");
  
  try {
   // Retrieve id to delete
   Long id =  Long.valueOf( user.getId().toString() );
   
   // Loop array
   for ( User dummyUser: dummyUsersList) {
    if ( dummyUser.getId().compareTo(id) == 0 ) {
     dummyUsersList.remove(dummyUser); 
     break;
    }
   }
   
   return true;
  } catch (Exception e) {
   return false;
  }

 }
  
 public Boolean edit( User user ) {
  logger.debug("Editing an existing user");
  
  try {
   // Retrieve id to edit
   Long id =  Long.valueOf( user.getId().toString() );
   
   // Loop array
   for ( User dummyUser: dummyUsersList) {
    if ( dummyUser.getId().compareTo(id) == 0 ) {
     dummyUser.setFirstName( user.getFirstName());
     dummyUser.setLastName( user.getLastName());
     break;
    }
   }
   return true;
  } catch (Exception e) {
   return false;
  }
  
 }
 
 private void init() {
  // Populate our in-memory, dummy list of users
  // Normally, the data should come from your DAOs or your persitence layer

  logger.debug("Init in-memory users");
  
  User user = new User();
  user.setId(Long.valueOf("1"));
  user.setFirstName("John");
  user.setLastName("Smith");
  dummyUsersList.add(user);
  
  user = new User();
  user.setId(Long.valueOf("2"));
  user.setFirstName("Jane");
  user.setLastName("Adams");
  dummyUsersList.add(user);
  
  user = new User();
  user.setId(Long.valueOf("3"));
  user.setFirstName("Jeff");
  user.setLastName("Mayer");
  dummyUsersList.add(user);
 }
}
Let's move on to the controllers.

In this application we declare two controllers:
1. MediatorController
2. UserController

The purpose of the MediatorController is to handle and show the JSP page that contains the jqGrid itself. The JSP page is mapped to the URI template /main/users. To access the JSP page, you type the root path of your web application then followed by /main/users

Notice our controllers are simple classes annotated with @Controller and @RequestMapping. Same with the @Service annotation, this class becomes a Spring Controller that's capable to handle URI-template-based requests.

Here's the MediatorController:
package org.krams.tutorial.controller;

import org.apache.log4j.Logger;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;

/**
 * Handles CRUD requests for users
 * 
 */
@Controller
@RequestMapping("/main")
public class MediatorController {

 protected static Logger logger = Logger.getLogger("controller");
 
 /**
     * Retrieves the JSP page that contains our JqGrid
     */
    @RequestMapping(value = "/users", method = RequestMethod.GET)
    public String getUsersPage() {
     logger.debug("Received request to show users page");
    
     // This will resolve to /WEB-INF/jsp/users.jsp page
     return "users";
 }
}
On the other hand the UserController does not display any JSP page. It's job is to handle JSON request from the jqGrid and respond with JSON as well. The UserController receives the CRUD operations which it delegates to the UserService. When the UserService is done processing, the UserController responds back to the jqGrid.

Here's the UserController:
/**
 * 
 */
package org.krams.tutorial.controller;

import java.util.List;

import javax.annotation.Resource;

import org.apache.log4j.Logger;
import org.krams.tutorial.domain.User;
import org.krams.tutorial.json.CustomGenericResponse;
import org.krams.tutorial.json.CustomUserResponse;
import org.krams.tutorial.service.IUserService;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;

/**
 * Handles CRUD requests for users
 * 
 */
@Controller
@RequestMapping("/crud")
public class UserController {
 
  protected static Logger logger = Logger.getLogger("controller");
 
   @Resource(name="userService")
   private IUserService userService;
   
   /**
    * The default method when a request to /users is made.
    * This essentially retrieves all users, which are wrapped inside a CustomUserResponse object.
    * The object is automatically converted to JSON when returning back the response.
    * The @ResponseBody is responsible for this behavior.
    */
        @RequestMapping(method = RequestMethod.GET)
        public @ResponseBody  CustomUserResponse getAll(
        ) {
         logger.debug("Received request to get all users");

         // Retrieve all users from the service
         List users = userService.getAll();
         
         // Initialize our custom user response wrapper
         CustomUserResponse response = new CustomUserResponse();
         
         // Assign the result from the service to this response
         response.setRows(users);

         // Assign the total number of records found. This is used for paging
         response.setRecords( String.valueOf(users.size()) );
         
         // Since our service is just a simple service for teaching purposes
         // We didn't really do any paging. But normally your DAOs or your persistence layer should support this
         // Assign a dummy page
         response.setPage( "1" );
         
         // Same. Assign a dummy total pages
         response.setTotal( "10" );
         
         // Return the response
         // Spring will automatically convert our CustomUserResponse as JSON object. 
         // This is triggered by the @ResponseBody annotation. 
         // It knows this because the JqGrid has set the headers to accept JSON format when it made a request
         // Spring by default uses Jackson to convert the object to JSON
         return response;
     }
        
        /**
         * Edit the current user.
         */
        @RequestMapping(value = "/edit", method = RequestMethod.POST)
        public @ResponseBody CustomGenericResponse edit(
          @RequestParam("id") String id,
          @RequestParam("firstName") String firstName,
          @RequestParam("lastName") String lastName
        ) {
         logger.debug("Received request to edit user");
        
         // Construct our user object
         // Assign the values from the parameters
         User user = new User();
         user.setId( Long.valueOf(id) );
         user.setFirstName(firstName);
         user.setLastName(lastName);
         
         // Do custom validation here or in your service
         
         // Call service to edit
         Boolean success = userService.edit(user);
         
         // Check if successful
         if ( success == true ) {
          // Success. Return a custom response
          CustomGenericResponse response = new CustomGenericResponse();
       response.setSuccess(true);
       response.setMessage("Action successful!");
          return response;
          
         } else {
          // A failure. Return a custom response as well
          CustomGenericResponse response = new CustomGenericResponse();
       response.setSuccess(false);
       response.setMessage("Action failure!");
          return response;
         }
 
     }
        
        /**
         * Add a new user
         */
        @RequestMapping(value = "/add", method = RequestMethod.POST)
        public @ResponseBody CustomGenericResponse add(
          @RequestParam("firstName") String firstName,
          @RequestParam("lastName") String lastName
        ) {
         logger.debug("Received request to add a new user");
         
         // Construct our new user object. Take note the id is not required.
         // Assign the values from the parameters
         User user = new User();
         user.setFirstName(firstName);
         user.setLastName(lastName);
         
         // Do custom validation here or in your service
         
         // Call service to add
         Boolean success = userService.add(user);
         
         // Check if successful
         if ( success == true ) {
          // Success. Return a custom response
          CustomGenericResponse response = new CustomGenericResponse();
       response.setSuccess(true);
       response.setMessage("Action successful!");
          return response;
          
         } else {
          // A failure. Return a custom response as well
          CustomGenericResponse response = new CustomGenericResponse();
       response.setSuccess(false);
       response.setMessage("Action failure!");
          return response;
         }
         
     }
        
        /**
         * Delete an existing user
         */
        @RequestMapping(value = "/delete", method = RequestMethod.POST)
        public @ResponseBody CustomGenericResponse delete(
          @RequestParam("id") String id
        ) {
         
         logger.debug("Received request to delete an existing user");
         
         // Construct our user object. We just need the id for deletion.
         // Assign the values from the parameters
         User user = new User();
         user.setId( Long.valueOf(id) );
         
         // Do custom validation here or in your service
         
         // Call service to add
         Boolean success = userService.delete(user);
         
         // Check if successful
         if ( success == true ) {
          // Success. Return a custom response
          CustomGenericResponse response = new CustomGenericResponse();
       response.setSuccess(true);
       response.setMessage("Action successful!");
          return response;
          
         } else {
          // A failure. Return a custom response as well
          CustomGenericResponse response = new CustomGenericResponse();
       response.setSuccess(false);
       response.setMessage("Action failure!");
          return response;
         }
     }
        
}
The UserController has four mappings:
/crud/add
/crud/edit
/crud/delete
/crud

To make an add request, a jqGrid should call the /crud/add URI template. To make an edit request, call the /crud/edit URI template. And so forth.

When you call a get request from the Spring Controller, here's the exact JSON data that's being transported:


You can easily retrieve this data by calling directly the JSON controller via a third-party tool. I use RESTClient by WizTools to verify the response. Here's a screenshot of the tool:


You can download the tool at RESTClient

The UserController has two types of response:
1. CustomGenericResponse
2. CustomUserResponse

These responses are custom POJO classes that I created. The idea of these classes is to contain JSON object within a Java class. It's easier to manipulate a Java object than a JSON object inside Java. The conversion from JSON to Java is done automatically by Spring! It uses Jackson to do the conversion.

Here's the CustomGenericResponse:

The CustomUserResponse is a wrapper containing an array of users, along with some extra data used by a jqGrid, like paging, total records, and alike. The reasoning for the structure of this class is dictated by the standard parameters that a jqGrid expects. Check the following jqGrid doc jqGrid JSON format

Here's CustomUserResponse:


Let's move on to the JSP page.

The JSP page contains the jqGrid. I added custom buttons on the grid itself. The jqGrid declaration below has some extra features that we will not be using for this tutorial. I put them here anyway just in case I write another tutorial utilizing these extra features.

Here's a screenshot of these custom buttons:

On the body of the JSP, we declare the following:

users.jsp
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
<%@ taglib uri="http://www.springframework.org/tags/form" prefix="form" %>

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">

<head>
 <link rel="stylesheet" type="text/css" media="screen" href="/spring-jqgrid-integration/resources/css/jquery/ui-lightness/jquery-ui-1.8.6.custom.css" />
 <link rel="stylesheet" type="text/css" media="screen" href="/spring-jqgrid-integration/resources/css/jqgrid/ui.jqgrid.css" />

 <script type="text/javascript" src="/spring-jqgrid-integration/resources/js/jquery/jquery-1.4.4.min.js"></script>
 <script type="text/javascript">
     var jq = jQuery.noConflict();
 </script>
 <script type="text/javascript" src="/spring-jqgrid-integration/resources/js/jquery/jquery-ui-1.8.6.custom.min.js"></script> 
 <script type="text/javascript" src="/spring-jqgrid-integration/resources/js/jqgrid/grid.locale-en.js" ></script>
 <script type="text/javascript" src="/spring-jqgrid-integration/resources/js/jqgrid/jquery.jqGrid.min.js"></script>
 
 <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
 <title>JqGrid - Spring 3 MVC Integration Tutorial</title>
 
</head>

<body >

<script type="text/javascript">
 jq(function() {
  jq("#grid").jqGrid({
      url:'/spring-jqgrid-integration/krams/crud',
   datatype: 'json',
   mtype: 'GET',
      colNames:['Id', 'First Name', 'Last Name'],
      colModel:[
       {name:'id',index:'id', width:55,editable:false,editoptions:{readonly:true,size:10},hidden:true},
       {name:'firstName',index:'lastName', width:100,editable:true, editrules:{required:true}, editoptions:{size:10}},
       {name:'lastName',index:'firstName', width:100,editable:true, editrules:{required:true}, editoptions:{size:10}}
      ],
      postData: { 
   },
   rowNum:20,
      rowList:[20,40,60],
      height: 200,
      autowidth: true,
   rownumbers: true,
      pager: '#pager',
      sortname: 'id',
      viewrecords: true,
      sortorder: "asc",
      caption:"Users",
      emptyrecords: "Empty records",
      loadonce: false,
      loadComplete: function() {
   },
      jsonReader : {
          root: "rows",
          page: "page",
          total: "total",
          records: "records",
          repeatitems: false,
          cell: "cell",
          id: "id"
      }
  });
  jq("#grid").jqGrid('navGrid','#pager',
    {edit:false,add:false,del:false,search:true},
    { },
          { },
          { }, 
    { 
        sopt:['eq', 'ne', 'lt', 'gt', 'cn', 'bw', 'ew'],
           closeOnEscape: true, 
            multipleSearch: true, 
             closeAfterSearch: true }
  );


  
  jq("#grid").navButtonAdd('#pager',
    {  caption:"Add", 
     buttonicon:"ui-icon-plus", 
     onClickButton: addRow,
     position: "last", 
     title:"", 
     cursor: "pointer"
    } 
  );
  
  jq("#grid").navButtonAdd('#pager',
    {  caption:"Edit", 
     buttonicon:"ui-icon-pencil", 
     onClickButton: editRow,
     position: "last", 
     title:"", 
     cursor: "pointer"
    } 
  );
  
  jq("#grid").navButtonAdd('#pager',
   {  caption:"Delete", 
    buttonicon:"ui-icon-trash", 
    onClickButton: deleteRow,
    position: "last", 
    title:"", 
    cursor: "pointer"
   } 
  );

  jq("#btnFilter").click(function(){
   jq("#grid").jqGrid('searchGrid',
     {multipleSearch: false, 
      sopt:['eq']}
   );
  });

  // Toolbar Search
  jq("#grid").jqGrid('filterToolbar',{stringResult: true,searchOnEnter : true, defaultSearch:"cn"});

 });
</script>
  

<script type="text/javascript">

function addRow() {

 // Get the currently selected row
    jq("#grid").jqGrid('editGridRow','new',
      {  url: "/spring-jqgrid-integration/krams/crud/add", 
     editData: {
       },
       recreateForm: true,
       beforeShowForm: function(form) {
       },
    closeAfterAdd: true,
    reloadAfterSubmit:false,
    afterSubmit : function(response, postdata) 
    { 
           var result = eval('(' + response.responseText + ')');
     var errors = "";
     
           if (result.success == false) {
      for (var i = 0; i < result.message.length; i++) {
       errors +=  result.message[i] + "<br/>";
      }
           }  else {
            jq("#dialog").text('Entry has been added successfully');
      jq("#dialog").dialog( 
        { title: 'Success',
         modal: true,
         buttons: {"Ok": function()  {
          jq(this).dialog("close");} 
         }
        });
                 }
        // only used for adding new records
        var new_id = null;
        
     return [result.success, errors, new_id];
    }
      });

}

function editRow() {
 // Get the currently selected row
 var row = jq("#grid").jqGrid('getGridParam','selrow');
 
 if( row != null ) 
  jq("#grid").jqGrid('editGridRow',row,
   { url: "/spring-jqgrid-integration/krams/crud/edit", 
    editData: {
          },
          recreateForm: true,
          beforeShowForm: function(form) {
          },
    closeAfterEdit: true,
    reloadAfterSubmit:false,
    afterSubmit : function(response, postdata) 
    { 
              var result = eval('(' + response.responseText + ')');
     var errors = "";
     
              if (result.success == false) {
      for (var i = 0; i < result.message.length; i++) {
       errors +=  result.message[i] + "<br/>";
      }
              }  else {
               jq("#dialog").text('Entry has been edited successfully');
      jq("#dialog").dialog( 
        { title: 'Success',
         modal: true,
         buttons: {"Ok": function()  {
          jq(this).dialog("close");} 
         }
        });
                 }
           
     return [result.success, errors, null];
    }
   });
 else jq( "#dialogSelectRow" ).dialog();
}

function deleteRow() {
 // Get the currently selected row
    var row = jq("#grid").jqGrid('getGridParam','selrow');

    // A pop-up dialog will appear to confirm the selected action
 if( row != null ) 
  jq("#grid").jqGrid( 'delGridRow', row,
           { url: '/spring-jqgrid-integration/krams/crud/delete', 
      recreateForm: true,
               beforeShowForm: function(form) {
                 //change title
                 jq(".delmsg").replaceWith('<span style="white-space: pre;">' +
                   'Delete selected record?' + '</span>');
                 
        //hide arrows
                 jq('#pData').hide();  
                 jq('#nData').hide();  
               },
              reloadAfterSubmit:false,
              closeAfterDelete: true,
              afterSubmit : function(response, postdata) 
      { 
                   var result = eval('(' + response.responseText + ')');
       var errors = "";
       
                   if (result.success == false) {
        for (var i = 0; i < result.message.length; i++) {
         errors +=  result.message[i] + "<br/>";
        }
                   }  else {
                    jq("#dialog").text('Entry has been deleted successfully');
        jq("#dialog").dialog( 
          { title: 'Success',
           modal: true,
           buttons: {"Ok": function()  {
            jq(this).dialog("close");} 
           }
          });
                   }
                   // only used for adding new records
                   var new_id = null;
                   
       return [result.success, errors, new_id];
      }
           });
  else jq( "#dialogSelectRow" ).dialog();
}

</script>  
  
<p>JqGrid - Spring 3 MVC Integration Tutorial</p>
<div id="jqgrid">
 <table id="grid"></table>
 <div id="pager"></div>
</div>

<div id="dialog" title="Feature not supported" style="display:none">
 <p>That feature is not supported.</p>
</div>

<div id="dialogSelectRow" title="Warning" style="display:none">
 <p>Please select row</p>
</div>

</body>

</html>

For the Javascript functions, addRow, editRow, deleteRow, each declares a custom URL. For example, the deleteRow declares the following URL:


This URL corresponds the controller delete URI template: /crud/delete.

Another important setting you must look at is the jsonReader we declared inside the jqGrid:
jsonReader : {
root: "rows",
page: "page",
total: "total",
records: "records",
repeatitems: false,
cell: "cell",
id: "id"
}
It turns out all you need to declare among those properties is the repeatitems: false. You can remove the root, page, total, records, cell, and id. The application will still run. I believe jqGrid has an internal naming convention that it expects. By default if your JSON data has the following format:

Then it's following convention. Try changing the rows to something like arbirtrary, i.e mydog. Your grid will not show any data.

If that's the case, make sure you set the root property in your jsonReader like the following:
jsonReader : {
root: "mydog",
repeatitems: false
}
Another important setting you must be wary of is the colModel names.

Our jqGrid expects the following colModel names:
colModel:[
{name:'id', ...}
{name:'firstName', ...},
{name:'lastName, ...}
],
These names must exactly match the properties you're passing in your JSON data. It's case sensitive! FIRSTNAME and firstName are not the same names!

Thanks to Oleg for pointing these nuances. He's the guy I consider expert when it comes to jqGrid. He's an active guy in www.stackoverflow.com. Ask a jqGrid question there and he'll more than likely answer it.

Of course, you need to add the JQuery and jqGrid library on the head section of your JSP. Otherwise, you won't see any grids. Make sure the path is correct.

I need to point one critical JQuery configuration, however. If you notice on standard JQuery examples, most use the $, but in this tutorial you don't see that sign! Instead you see the jq. The jq and $ are equivalent. To force JQuery to use a different identifier, declare the following on the head section of your JSP:
Let's return to Spring.

Typical with any Spring application, we must declare a few required beans in the xml configuration.

applicationContext.xml


spring-servlet.xml


web.xml

  spring
  org.springframework.web.servlet.DispatcherServlet
  1
 
 
  spring
  /krams/*
 

 
  org.springframework.web.context.ContextLoaderListener
 
Take note of the URL pattern. When accessing any pages in our MVC application, the host name must be appended with
/krams

Run the Application

To run the application, please use the following URL:
http://localhost:8080/spring-jqgrid-integration/krams/main/users

Some reminders.
1. Download the Jackson library and put it in your classpath.
2. Download the JQuery framework and put it in your resources folder
3. Download the jqGrid plugin .
4. You may need to customize the URLs declared in the JSP

The best way to learn further is to try the actual application.

Download the project
You can access the project site at Google's Project Hosting at http://code.google.com/p/jqgrid-spring3mvc-integration-tutorial/

You can run the project directly using an embedded server via Maven.
For Tomcat: mvn tomcat:run
For Jetty: mvn jetty:run

You can download the project as a Maven build. Look for the spring-jqgrid-integration.zip in the Download sections.
StumpleUpon DiggIt! Del.icio.us Blinklist Yahoo Furl Technorati Simpy Spurl Reddit Google I'm reading: jqGrid and Spring 3 MVC Integration Tutorial ~ Twitter FaceBook

Subscribe by reader Subscribe by email Share

77 comments:

  1. Thanks so much posting such a detailed tutorial! It is really useful. Just a note that the code pasted for Custom User Response class is CustomGenericResponse. Can you please post the CustomUserResonse class instead?

    ReplyDelete
  2. @JP, thanks for pointing that out. I've updated the tutorial with the correct CustomUserResponse. Just to point out, if you look at the CustomUserResponse rows fields you can easily change the type using Generics.

    I'm glad you liked the tutorial. This is my way of thanking the open-source community for helping me as well in just about everything.

    ReplyDelete
  3. I've just uploaded a Maven version of the project. Enjoy

    ReplyDelete
  4. you can process data to insert hibernate by extending this tutorial.

    ReplyDelete
  5. @Anonymous, yes, you can use Hibernate to access your data.

    ReplyDelete
  6. i am getting the oddest error......that googling didnt resolve:

    server side:

    [DEBUG] [http-8080-exec-9 01:29:29] (AbstractHandlerExceptionResolver.java:resolveException:132) Resolving exception from handler [org.krams.tutorial.controller.UserController@9c176c]: org.springframework.web.HttpMediaTypeNotAcceptableException: Could not find acceptable representation
    [DEBUG] [http-8080-exec-9 01:29:29] (AbstractHandlerExceptionResolver.java:resolveException:132) Resolving exception from handler [org.krams.tutorial.controller.UserController@9c176c]: org.springframework.web.HttpMediaTypeNotAcceptableException: Could not find acceptable representation
    [DEBUG] [http-8080-exec-9 01:29:29] (AbstractHandlerExceptionResolver.java:resolveException:132) Resolving exception from handler [org.krams.tutorial.controller.UserController@9c176c]: org.springframework.web.HttpMediaTypeNotAcceptableException: Could not find acceptable representation
    [DEBUG] [http-8080-exec-9 01:29:29] (DispatcherServlet.java:doDispatch:824) Null ModelAndView returned to DispatcherServlet with name 'spring': assuming HandlerAdapter completed request handling
    [DEBUG] [http-8080-exec-9 01:29:29] (FrameworkServlet.java:processRequest:674) Successfully completed request


    client side (with the help of wiztools): The resource identified by this request is only capable of generating responses with characteristics not acceptable according to the request "accept" headers


    headers being set


    accept:json

    ReplyDelete
  7. ok, figured it out:

    had to add:













    and the jackson-all.jar

    ReplyDelete
  8. hmm....bean stuff didnt add....

    i added a message adapter like this this thread:

    http://forum.springsource.org/showthread.php?t=89618

    ReplyDelete
  9. @Eric, you don't really need to add the adapter as long as you have the tag. That tag declares the same AnnotationMethodHandlerAdapter. I assume you're using the latest Spring 3.0.5 jars?

    ReplyDelete
  10. The tag I was referring to mvc:annotation-driven

    ReplyDelete
  11. This comment has been removed by the author.

    ReplyDelete
  12. This comment has been removed by the author.

    ReplyDelete
  13. This comment has been removed by the author.

    ReplyDelete
  14. Thank you very much krams..this help me alot..i got some deployment issues when i used your maven version. but i was able to plug this grid to a working spring mvc 3 sample..anyhow my grid won't sort when i click on the col name. and also it wnt get filtered when i type some letters in the toolbar search box ( jst above the column header ) can you please give me some advice?

    ReplyDelete
  15. @Sam, yes that's right. The grid won't sort and filter. That's because I purposely didn't enable that feature in the tutorial to stay within the scope of the tutorial. Maybe it's time for me to write a FULL jqGrid tutorial. To make the filtering work, you will need to retrieve the values as request parameters and you'll have to convert them to your database's language.

    ReplyDelete
  16. appreciate your effort krams..I went through JQgrid wiki and saw the parameter called 'soratable'. i set it to true but still it wnt sort.am new to this JQgrid thing and spring 3.after saw your cutting edge tutorials i loved to play with those..full jqgrid tutorial will be really helpful krams..Thanks again for your great effort and time..

    ReplyDelete
  17. This is a nice article..
    Its very easy to understand ..
    And this article is using to learn something about it..

    c#, dot.net, php tutorial

    Thanks a lot..!

    ReplyDelete
  18. doesn't work for me.
    first to make it work on tomcat i had to put all sort of jars on the classpath and when trying to run the project a 404 error page is shown.

    ReplyDelete
  19. doesn't work, when i'm trying to run users.jps, it returns the 404 page. (link: http://hostname/spring-jqgrid-integration/krams/users).

    The User Controller work when i lunch http://hostname/spring-jqgrid-integration/krams/crud it returns the user list as json.

    Thanks

    ReplyDelete
  20. Hi krams, i have also tried to run the mvn project, but when i happen to access,spring-jqgrid-integration/krams/users it gives me 404 error. it has not yet worked for me. where could the problem be?

    thanks

    gbro

    ReplyDelete
  21. I just tried with Maven and Tomcat by running "mvn tomcat:run", and it works. The URL I used is http://localhost:8080/spring-jqgrid-integration/krams/main/users

    ReplyDelete
  22. Thanks for the feedback krams. This tutorial can be very useful for me...could you please add configuration tips step by step ?
    don't I need to change the jdbc properties file ?
    build a particular database...
    thanks a lot

    ReplyDelete
  23. If you need an actual database, all you'll need is change the Service layer so-that instead of doing an in-memory manipulation of data, you delegate to your database. Remember jqGrid is only for the presentation layer. If you need help on configuring, MVC and JDBC check my tutorial: Spring 3 MVC - JDBC Integration Tutorial or Spring MVC 3, Hibernate Annotations, MySQL Integration Tutorial (See the Tutorials section)

    ReplyDelete
  24. Also, have you looked at my tutorial: Spring 3: Dynamic MVC using jqGrid and MongoDB (See the Tutorials section)? This uses a NoSQL database. But the principle still applies for JDBC. I know I owe my readers a full jqGrid, JDBC tutorial :)

    ReplyDelete
  25. Hello krams, great tutorial finally, but I have problems deploying my .war, I'm trying to deploy this to jboss server, I get this error while starting it "org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'org.springframework.web.servlet.mvc.annotation.DefaultAnnotationHandlerMapping#0': Initialization of bean failed; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'userController': Injection of resource dependencies failed; nested exception is org.springframework.beans.factory.NoSuchBeanDefinitionException: No bean named 'userService' is defined: not found in JNDI environment" , maybe you had this experience before?

    ReplyDelete
  26. @c0mrade, you would do better if you ask this in the Spring forums (in the Web thread). I know there are some issues with JBoss, but I just can't remember the solution

    ReplyDelete
  27. [DEBUG] [http-8080-1 11:12:45] (FrameworkServlet.java:processRequest:671) Could not complete request
    javax.servlet.ServletException: Circular view path [users]: would dispatch back to the current handler URL [/spring-jqgrid-integration/krams/main/users] again. Check your ViewResolver setup! (Hint: This may be the result of an unspecified view, due to default view name generation.)

    Running tomcat 6.032/J2SE 6.023. As others have noted, the json url's are fine.

    Thanks for the tute, btw. Good stuff.

    ReplyDelete
  28. Seems like a viewResolver issue. InternalResourceViewResolver is forwarding/redirecting to "/spring-jqgrid-integration/krams/main/users" instead of "/WEB-INF/jsp/users.jsp".

    If I hardcode "forward:/WEB-INF/jsp/users.jsp" it works fine.

    I see this (not sure if related):

    [DEBUG] [main 01:15:17] (AbstractAutowireCapableBeanFactory.java:createBean:458) Finished creating instance of bean 'org.springframework.web.servlet.view.InternalResourceViewResolver'
    [DEBUG] [main 01:15:17] (DispatcherServlet.java:initViewResolvers:584) No ViewResolvers found in servlet 'spring': using default

    Still investigating.

    ReplyDelete
  29. More logs (in context):

    [DEBUG] [http-8080-1 01:15:25] (AbstractAutowireCapableBeanFactory.java:invokeInitMethods:1461) Invoking afterPropertiesSet() on bean with name 'users'
    [DEBUG] [http-8080-1 01:15:25] (DispatcherServlet.java:render:1045) Rendering view [org.springframework.web.servlet.view.JstlView: name 'users'; URL [users]] in DispatcherServlet with name 'spring'
    [DEBUG] [http-8080-1 01:15:25] (FrameworkServlet.java:processRequest:671) Could not complete request
    javax.servlet.ServletException: Circular view path [users]: would dispatch back to the current handler URL [/spring-jqgrid-integration/krams/main/users] again. Check your ViewResolver setup! (Hint: This may be the result of an unspecified view, due to default view name generation.)

    ReplyDelete
  30. added viewNames property to the viewResolver and set the list value as "users"

    works fine now.

    ReplyDelete
  31. This comment has been removed by the author.

    ReplyDelete
  32. Hi all, what do i need to implement a dyanamic dropdpwn list. i want to pull data from the database and allow a user to select a category that is in my db.

    I am also tryn to figure out how to show the value of an id. i.e. i pull data that has a referenced field so to show the user the value i use itemClass.classname and there i have the class name displayed other than the id. Saving becames a problem coz i get an error 400. I would love to save the id again not the name.

    ReplyDelete
  33. OK. successfully implemented the drop down using dataUrl and buildselect.
    Anyone with an example for a dynamic drop down in which i select a country and it loads the second drop down with cities then cities fires a dropdown with its states. Will be so greatful for your help.

    ReplyDelete
  34. Thanks buddy!!!! I was looking exactly for this to start my internal project perfect maven rocks no issue while executing it...

    Thanks once again.... keep up the good work....

    ReplyDelete
  35. hi,


    I am trying this code in our spring roo based project.
    this code is note working ..

    Please advise me as per as soon....

    ReplyDelete
  36. hi, may i know how to convert @ResponseBody Spring 3 to Spring 2 ?

    ReplyDelete
  37. Hi Krams,

    Thanks for this post.

    I got the code working for me. I have a quick question here. Why a "blank row" appearing on the top of the grid. Is this a default nature of jqgrid. How can we stop showing the blank row in the page.

    Thanks.

    ReplyDelete
  38. @Prasad, are you referring to the blank row where you can enter text? If that's the one, that's the toolbar search feature of jQgrid. You can disable it. But if you want to use it, you need to provide an implementation on your Controller to handle the search filters

    ReplyDelete
  39. What should I include in pom and manifest to use jackson 1.9 in osgi? in springsource repo i fiund only 1.4.3

    ReplyDelete
  40. really good one.M going to try this out

    ReplyDelete
  41. hey kram,

    i am a newbie to jqgrid and finding difficulty to integrate with a JDBC instead of in memory DB,

    Can u help??

    problem
    #1 - how to provide pagination info to spring??
    #2 - i am using mybatis for mapping will there be any changes??

    Thanks in advance..!
    Awaiting reply.

    ReplyDelete
  42. Hi,
    how can i remove the first empty rows can you Please help me

    ReplyDelete
  43. how can we remove the first row

    ReplyDelete
  44. Great tutorial, thanks.

    Having bit of a weird problem. Got it working with JDBC(MySQL DB). All fine except...

    The Add/Edit/Delete buttons and the first empty row not showing up on the grid.

    Other than DB changes , haven't changed any of the front-end - any idea what could be wrong??

    Cheers

    ReplyDelete
  45. Scratch that - must have inadvertently done something to mess up the js. Grid all working now.

    Thanks for the tut!!

    ReplyDelete
  46. Somebody work with jqGrid and TILES ? i don't know how can i do it :(

    ReplyDelete
  47. Hi,

    Great tutorial, thanks.

    I've got a probleme. The grid isn't load. I haven't any error message with IE9 and Chrome. With FireFox this error is written

    NetworkError: 406 Inacceptable - http://localhost:8080/englishAnnotation/do/crud?_search=false&nd=1327010598446&rows=20&page=1&sidx=id&sord=asc

    the url is good. In the eclipse console, I see logger trace for :

    23:03:17 [DEBUG] (MediatorController.java:getUsersPage:17) Recieved request to show users page
    23:03:18 [DEBUG] (Usercontroller.java:getAll:26) Recieve request to get all users
    23:03:18 [DEBUG] (UserService.java:getAll:21) Retrieve all Users

    Do you have any idea ?

    Thanks

    ReplyDelete
    Replies
    1. I found the problem. It was because I miss to put jackson-mapper-asl-1.9.3.jar, jackson-core-asl-1.9.3.jar in the classpath

      Delete
  48. hi, i get my data as json on the page but my grid is not being displayed what could be wrong ?? please HELP !!!

    ReplyDelete
  49. @Anonymous, I suggest you use Chrome and enable the Developer Tools; then check the Console output for errors. Also, check your web server's logs, i.e Tomcat. It's either you got the grid setup wrong or your Controller mapping is wrong, but since you can get the data, I would put my two cents on the grid setup (your HTML/JSP page)

    ReplyDelete
  50. Hi Krams, Thanks for this nice work here.
    I tried this on eclipse(3.7 indigo with jdk1.7) by importing as an existing maven project then I tried run tomcat 7 .
    But tomcat is stoping here :INFO: Initializing Spring root WebApplicationContext.

    It looks like the web.xml is not there but I checked it is there.

    Any suggestion please.

    Best Regards.

    ReplyDelete
  51. @Anonymous, can you try instead importing this more updated tutorial: http://krams915.blogspot.com/2012/01/spring-mvc-31-jqgrid-and-spring-data_1887.html

    ReplyDelete
  52. Hi Krams,

    I tried to run the above code in this site, but the below error is occured in local browser, so kindly help me

    HTTP Status 404 -

    type Status report

    message

    description The requested resource () is not available.
    Apache Tomcat/5.5.31

    I am not using , Manually deploying the code in Tomcat.

    ReplyDelete
  53. I am not using maven,Manually deploying the code in Tomcat.

    ReplyDelete
  54. @Kraj, you have to check the logs in your Tomcat log directory and extract the exact error message. Preferrably, you provide the complete stacktrace. You can copy and paste the error at http://pastebin.com/. Then post the link here so we can further investigate.

    ReplyDelete
  55. Learning jqGrid/Spring...March 6, 2012 at 5:16 AM

    Thanks for this tutorial. I'm curious, though, what rules govern the passing of data from the cellEdit functions in jqGrid to the Spring container?

    More specifically, is the "rule" to basically define a @RequestParam for each value on your colModel? The jqGrid documents state:

    "What is posted to the server?

    When the data is posted to the server we construct object {} that contain:
    the name:value pair where the name is the name of the input element represented in the cell
    additionally we add a pair id:rowid where the rowid is the id of the row
    if the returned data from beforeSubmitCell event is not empty we extend this data with the posted data."

    To me, this suggests more than just individual string objects are coming back from jqGrid. Your example above defines @RequestParam for each column model and apparently that works.

    In my code, I'm getting an Error 400 when I sent the jqGrid data to my Controller. Clearly I'm doing something wrong...

    ReplyDelete
  56. @Learning, if you have the extra time, I suggest you read a more updated version of this guide at http://krams915.blogspot.com/2012/01/spring-mvc-31-jqgrid-and-spring-data_1887.html

    jqGrid has its own rules for passing data, but you can modify the data that is passed. You will have to read the JavaScript source of jqGrid to know the internal details. But there are hooks that allow you to pass extra parameters.

    For Spring, the basic rule is you just match the field names from jqGrid to your Spring DTO. The best way to check which fields are passed is to use your browser's developer tools, ie Firebug.

    ReplyDelete
  57. Hi Krams, thank you for this tutorial, I am learning Spring MVC, I like your tutorial, I imported this project to eclipse, run it, it's working, but only add link is working, But all the others (edit, delete, find) are not working.
    Thanks, your help is appreciated.

    ReplyDelete
  58. Hi Krams, How to implement simple pagination with your example. I have read your other blog where you've explained JQpagination with help of JpaRepository page. But my application we are using complex sql and using JDBCTemplate for executing SQLs. So, i am not sure how to implement simple pagination with this. Any information on this will be helpful for me.

    Thanks,
    Karthick

    ReplyDelete
  59. For the life of me, I couldn't pass this error,
    looks like a bug in Spring data, I am sure you have passed it, can you share,
    I am using jdk 1.7, and maven 3.03, on 64 bit machine.
    I understand the error, but if it works for every one I must have missed something.

    error: name clash: save(Iterable(? extends T#1)) in JpaRepository and (S)save(Iterable(S)) in CrudRepository have the same erasure, yet neither overrides the other

    ReplyDelete
    Replies
    1. David,

      In the POM use:

      1.1.1.RELEASE instead of:

      1.1.0.RC1

      The released version fixes the type erasure bug.

      Alberto Acevedo

      Delete
  60. Thank you for such a great article, you saved my day.

    ReplyDelete
    Replies
    1. You should also check the more updated article "Spring MVC 3.1, jqGrid, and Spring Data JPA Integration Guide" http://krams915.blogspot.com/2012/01/spring-mvc-31-jqgrid-and-spring-data_1887.html

      Delete
  61. It works, but it appears to be a bug in Javascript. Once a number of items are created, then one is deleted and another one is created, it stops working correctly.

    Can this be fixed? I'd like to use this somple version without JPA.

    Thanks

    ReplyDelete
  62. Mark Serrano, thanks! mabuhay ka! ^_^

    ReplyDelete
  63. Hi Krams,

    Navigation part is not working for me. What I've to do to activate tht to push record on next page if it's > 10 or 20..

    ReplyDelete
  64. it is not displayed to the gris , it is only showing row of data . what did i miss. i need herl

    ReplyDelete
  65. Hello Krams

    What is license type for your tutorials?
    I am going to develop example in Spring framework for our jQuery Widget and your jqGrid tutorial is very close to what I need. So I want to refit it for our needs.

    Our core product is commercial, but our demos are released under jQuery license:

    Sergey Nikolayev

    ReplyDelete
    Replies
    1. They don't have any official license. Use it anyway you want it. You can mention my name or not (it wouldn't matter). You can use it for commercial or personal use. No hidden costs :-)

      Delete
  66. only the Json String values are displayed ({"records":"2","total":"10","rows" ....) what did i miss?

    ReplyDelete
    Replies
    1. Did you use the sample project or are you trying to implement the feature for a new project?

      Delete
  67. Hey I am new to jqgrid and i have a problem that i'm not able to get data from the dummylist, i'm hitting the url as "http://localhost:7001/integration-spring-jqgrid/krams/main/users". In Fire Bug it is showing NullPointerException, here's the stack trace

    java.lang.NullPointerException
    at org.krams.tutorial.controller.UserController.getAll(UserController.java:46)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
    at java.lang.reflect.Method.invoke(Method.java:592)
    at org.springframework.web.bind.annotation.support.HandlerMethodInvoker.invokeHandlerMethod(HandlerMethodInvoker.java:176)
    at org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter.invokeHandlerMethod(AnnotationMethodHandlerAdapter.java:426)
    at org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter.handle(AnnotationMethodHandlerAdapter.java:414)
    at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:790)
    at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:719)
    at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:644)
    at org.springframework.web.servlet.FrameworkServlet.doGet(FrameworkServlet.java:550)
    at javax.servlet.http.HttpServlet.service(HttpServlet.java:743)
    at javax.servlet.http.HttpServlet.service(HttpServlet.java:857)
    at weblogic.servlet.internal.StubSecurityHelper$ServletServiceAction.run(StubSecurityHelper.java:227)
    at weblogic.servlet.internal.StubSecurityHelper.invokeServlet(StubSecurityHelper.java:125)
    at weblogic.servlet.internal.ServletStubImpl.execute(ServletStubImpl.java:283)
    at weblogic.servlet.internal.ServletStubImpl.execute(ServletStubImpl.java:176)
    at weblogic.servlet.internal.WebAppServletContext$ServletInvocationAction.run(WebAppServletContext.java:3272)
    at weblogic.security.acl.internal.AuthenticatedSubject.doAs(AuthenticatedSubject.java:321)
    at weblogic.security.service.SecurityManager.runAs(SecurityManager.java:121)
    at weblogic.servlet.internal.WebAppServletContext.securedExecute(WebAppServletContext.java:2019)
    at weblogic.servlet.internal.WebAppServletContext.execute(WebAppServletContext.java:1925)
    at weblogic.servlet.internal.ServletRequestImpl.run(ServletRequestImpl.java:1394)
    at weblogic.work.ExecuteThread.execute(ExecuteThread.java:209)
    at weblogic.work.ExecuteThread.run(ExecuteThread.java:181)

    Everything is at it is as given in your tutorial. Please help

    ReplyDelete
  68. Hi,
    I have 2 independent jqgrids on a jsp page, boths have a checkbox to only choose one row.
    the code of each one is on a independent div but when i click into a row on the 2º grid it selects the same one havin on the first grid having the same index, but when i check directly in the checkbox this doesn't happen, any suggestion for resolving this. thx to yo for responding.

    ReplyDelete
  69. HI Krams, In the above example how to send json object using jquery in Users.jsp to spring mvc controller while ADD, UPDATE and DELETE operations.

    Could you please send this example

    ReplyDelete
  70. Krams you give so many examples why dont you use pagination in your own site for this comments part?

    ReplyDelete
  71. Hi Krams, thank you for this tutorial, I am learning Spring MVC, I like your tutorial, I imported this project to eclipse, run it, it's working, but only add link is working, But all the others (edit, delete, find) are not working.

    Please any help is appreciated.

    ReplyDelete