Sunday, September 11, 2011

Using Grails with jQuery Autocomplete

Using jQuery Autocomplete feature in Grails is very easy. I will tell you how to integrate jQuery Autocomplete in a Grails application with a simple example.
For this I have used jQuery 1.6.2, jQuery UI 1.8.16 (start theme), Grails 1.3.7, MySQL 5.5 and jGrowl for notification.

Please note, I am writing this blog post based on the experience I have and I always welcome suggestions for improving myself and the ways to build solutions.

I have a list of technology Companies with Nasdaq symbols. You can get the complete list from here. I have Company and Employee domains with hasMany and belongsTo static mapping defined respectively. I have bootstraped the Company domain with the complete list.

In order to create an Employee for a Company, in the Employee Create screen by default you will get a select list of all the Companies (in this case all 686 rows).  Check the screenshot. If there are 1000s of companies then this list will be very loooong. Imagine the page size and load time! From an end user's perspective, this is not very user friendly as user may have to scroll down a long list of companies just to select the required company for which an Employee is to be created.


Moving on to the fun part.
This is where the magic of jQuery Autocomplete comes into the picture. To get a hang of it, see here. In our case we are more interested in Remote Datasource.
I will assume you know how to configure jQuery & jQuery UI into your Grails application. Note: There is jQuery Plugin available for Grails but I prefer to have more flexibility over the JS I write. 

Let's take the steps one by one.
1. I have edited the Employee create.gsp by removing the default select  i.e.

and added one hidden field and one text field.
 
  
The hidden field company.id is used to store the company id when user selects a company from the auto complete list. Please note the field name is company.id as Grails will map this automatically when saving an Employee record and store the company id in the employee table.

2. The Controller and Service class which will render the JSON object required for jQuery auto complete. Please note, the Grails team discourages the embedding of core application logic inside controllers, as it does not promote re-use and a clean separation of concerns.  
In the AutoCompleteController, the action complist which is invoked from the JS.
       def complist = {
           render autoCompleteService.complist(params) as JSON
       }
and the service method in service AutoCompleteService
def complist(params){
   def query = {
    or {
     like("nasdaqSymbol", "${params.term}%") // term is the parameter send by jQuery autocomplete
     like("companyName", "${params.term}%")
    }
  projections { // good to select only the required columns.
     property("id")
     property("nasdaqSymbol")
     property("companyName")
    }
   }
   def clist = Company.createCriteria().list(query) // execute  to the get the list of companies
   def companySelectList = [] // to add each company details
   clist.each {
    def companyMap = [:] // add to map. jQuery autocomplete expects the JSON object to be with id/label/value.
    companyMap.put("id", it[0])
    companyMap.put("label", it[2])
    companyMap.put("value", it[2])
    companyMap.put("nasSymbol", it[1]) // will use this to pre-populate the Emp Id
    companySelectList.add(companyMap) // add to the arraylist
 }
   return companySelectList
}
3. In the JS we map the textfield to jQuery autocomplete.
$(document).ready(function() {
 $("#comauto").autocomplete({
  source: function(request, response){
   $.ajax({
    url: "/sample/autoComplete/complist", // remote datasource
    data: request,
    success: function(data){
     response(data); // set the response
    },
    error: function(){ // handle server errors
     $.jGrowl("Unable to retrieve Companies", {
      theme: 'ui-state-error ui-corner-all'    
     });
    }
   });
  },
  minLength: 2, // triggered only after minimum 2 characters have been entered.
  select: function(event, ui) { // event handler when user selects a company from the list.
   $("#company\\.id").val(ui.item.id); // update the hidden field.
   $("#empId").val(ui.item.nasSymbol + "-") // populate the employee field with the nasdaq symbol.
  }
 });
});
That’s it! When a user starts typing in Company textfield, user will see jQuery working its magic and giving the list of Company names.



On selecting a Company, the Company name is set to the Company textfield and employee id field is auto populated with the Company's NASDAQ symbol. The hidden field (company.id) is set to the id of the selected Company. User can then update the Employee Id as required and create the new Employee.


Screen shot of a new employee created.


Hope you enjoyed reading this as much I have enjoyed writing it. :)

25 comments:

  1. Hi! You tutorial is just what I need. Let me tell you I'm a Grails beginner so I don't know how to set up jQuery & jQuery UI into my Grails application. Please could you help me with this? Write me if you can to orion_mjcl@yahoo.com. Thanks in advance.

    ReplyDelete
  2. Thanks Jay so much. I was stuck implementing this, mistake being I just returned my results "as JSON" and did not form them how the autocomplete needs it as you have shown. cheers!

    ReplyDelete
  3. You are welcome Steve. I was also doing the same mistake as you and took me some time to figure it out!

    ReplyDelete
  4. Hi, hope you can help me... thanks for the example... i'm trying to use it on my app but when i run the app it works excellent... but when i deploy it on a war it always returns an error in jGrowl. Do you know what can it be? Sorry for my english

    ReplyDelete
  5. Hi Robert, whats the error you are getting with jGrowl? Have you tried using Firebug and see whats wrong?

    ReplyDelete
  6. it's says that can't get the list of instances... but when i try to use the action directly with params it returns the json list without a problem... i think it's a view problem :S and it only happens on war mode... when i try dev and prod run-app it works perfect

    ReplyDelete
  7. This is what i get on my tomcat Log: [08/Mar/2012:09:36:12 -0700] "GET /cita/pacienteList?term=ui HTTP/1.1" 404 -

    ReplyDelete
  8. You are getting a 404 error. Is the resource available? Looks like it does not. I guess you something wrong in the URL.

    ReplyDelete
  9. This post was a big help. Thanks, Jay.

    ReplyDelete
  10. Happy to know it was helpful.

    ReplyDelete
  11. Very good thanxs

    ReplyDelete
  12. That works very well in create controller, but fails in edit controller, because it gets the instance from hibernate and populates the id. How can I make it works from both "create" and "edit" gsp?? I have to change the code you posted a little bit, isn't?
    Thanks a lot!

    ReplyDelete
  13. Thanks. This helped me out. A minor correction: <g:hiddenfield should be <g:hiddenField and <g:textfield should be <g:textField

    ReplyDelete
  14. hi
    I am trying to do drag and drop with ajax ,jquery in grails, but can not post the id of the li
    to update the mysql table.
    can you suggest how to catch the ajax call and to post the id of the li

    ReplyDelete
    Replies
    1. This should be helpful. http://linssen.me/entry/extending-the-jquery-sortable-with-ajax-mysql/

      Delete
  15. This mostly works, but will not reset the hiddenfield if you backspace out a few characters.

    Apparently, this section needs some love:

    select: function(event, ui) { // event handler when user selects an employee from the list.
    $("#employee\\.id").val(ui.item.id); // update the hidden field.
    }


    Still, good stuff. Thanks. Mike.

    ReplyDelete
  16. thank you so much, I passed more time to find this, thank you :)

    ReplyDelete
  17. Thanks a lot, I am yet to try it but it looks good. I don't get one thing, what is the url that says 'remote datasource' in comments in the JS script? Sorry for the dumb question I am new to Ajax and javascript

    ReplyDelete
    Replies
    1. Its the URL to which request is sent. In this case the controller.

      Delete
    2. Hi Jay Chandran ! This is Kalpana , first of allnice to see the Blog, i am also very new to Grails, i am trying to adopt the same autocomplete in my project. I installed Jquery and Jquery-ui plugin . After that i included in my gsp page. I couldnt get the controller part, i can get "def owners = Owner.findAllByCompleteNameLike("%${params.query}%")" the list of owners and i dont know how to proceed further. Can you please guide me, my id is Kalpana.vasan@gmail.com

      Delete
    3. I guess you have not followed the steps completely. Please retrace your steps again and it will work fine.

      Delete
  18. Thanks very much. It saved me a lot of time.

    ReplyDelete