Monday, January 31, 2011

Grails AJAX Examples

Early on, newcomers to Grails encounter a lack of working samples accompanying the framework.  Good places for examples and documentation include the Grails documentation and bloggers.

Ajax is one of the features with built-in support by Grails.   Once people get their "head around" the Grails basics, they generally want to move onto more interesting features like Ajax.  This usually requires scouring the web, looking for examples and/or reading the documentation fairly closely.   I went through this process and figured that others might benefit from a set of short working ajax examples in Grails.  I have created a small application that demonstrates some of the Ajax features and provide the application for download to anyone interested. (see bottom for download information)

The example application uses each of the following:
  • FormRemote
  • RemoteLink
  • SubmitToRemote
  • RemoteFunction
  • RemoteField
 General Steps
  • Pick your javascript library - prototype used in my example. Also shown is some Ajax event registration.


     function showSpinner(visible) {
         $('spinner').style.display = visible ? "inline" : "none";
     }
     Ajax.Responders.register({
     onLoading: function() {
           showSpinner(true);
     },
     onComplete: function() {
     if(!Ajax.activeRequestCount) showSpinner(false);
     }
   });

  • Update your controller with any code needed - below is my entire BookController
  • package ajax
    
    class BookController {
    
         def scaffold = true
      
      def showEditAjax = {
       render (view:'editAjax', model:[bookInstance:Book.get(params.id)])
      }
      
      def showListSelect = {
       render (view:'listSelect', model:[bookInstanceList:Book.list()])
      }
      
      def showTitleSearch = {
       render (view:'titleSearchAjax')
      }
      
      def showRemoteLink = {
       render (view:'remoteLink', model:[bookInstanceList:Book.list()])
      }
      
      def showDetails = {
       render(template:'bookDetails', model:[bookInstance:Book.get(params.id)])
      }
      
      def showSearch = {
       render (view:'searchAjax')
      }
      
      def String wrapSearchParm(value) {
       '%'+value+'%'
      }
      
      def searchTitle = {
       def list = Book.findAllByTitleIlike(wrapSearchParm(params.searchvalue))
       render(template:'searchResults', model:[searchresults:list])
      }
      
      def search = {
       def list
       if (params.publisher && params.title)
        list = Book.findAllByTitleIlikeAndPublisherIlike(wrapSearchParm(params.title), wrapSearchParm(params.publisher))
       else if (params.publisher)
        list = Book.findAllByPublisherIlike(wrapSearchParm(params.publisher))
          else if (params.title)
           list = Book.findAllByTitleIlike(wrapSearchParm(params.title))
       
       render(template:'searchResults', model:[searchresults:list])
      }
      
      def listByPublisher = {
       def list
       if (params.filter.equals("All"))
        list = Book.list()
        else
          list = Book.findAllByPublisher(params.filter)
       
       render(template:'searchResults', model:[searchresults:list])
      }
      
     def update = {
      def bookInstance = Book.get( params.id )
      if(bookInstance) {
       bookInstance.properties = params
       if(!bookInstance.hasErrors() && bookInstance.save()) {
        render "
    

    Book ${params.title} updated with Ajax using FormRemote

    " } else { render(view:'edit',model:[bookInstance:bookInstance]) } } else { flash.message = "Book not found with id ${params.id}" redirect(action:edit,id:params.id) } } }
  • Create/update view(s) - See screen shots below and/or download the application
    Most of the examples are fairly straight forward and should not need a lot of explanation.  They may not be great 'real world' examples but the intention is to provide a working example for someone to start from.

    FormRemote
    The formRemote taglib creates a form tag that uses a remote uri to execute an ajax call.  In this case, I have slightly modified the default generated edit.gsp to change the form tag to a formRemote tag and added a div to show the edit results.   The screen shot below shows the result of first clicking the Form Remote navigation menu button, changing the number of pages in the 'Grails in Action' book and pressing the Update button at the bottom of the screen.




    RemoteLink
    The RemoteLink tag creates a link that calls a remote function when clicked.  In the example application,  I display a list of book titles and the title is a link.  Clicking on the title shows the book details at the bottom of the screen.
    
        ${fieldValue(bean: bookInstance, field: "title")}    
    


    SubmitToRemote
    The submitToRemote tag creates a button that submits the surrounding form as a remote Ajax call serializing the fields into parameters.  In the example application, I created search fields for Title and Publisher.  The search button was created using the SubmitToRemote taglib to submit the search parameters using Ajax.

    Title:
    Publisher:
    RemoteFunction
    The example application uses the RemoteFunction taglib to create a remote javascript function that is assigned to the onChange DOM event.  When the user selects or changes the value in the Publisher dropdown list, the remote function is called and the bottom half of the screen displays books published by the selected Publisher.



    RemoteField 
    The example application uses the RemoteField taglib to create a dynamic search for Book titles, similar to Google Instant.  Search results are returned the screen as you type values in the Title Search field.   The controller code in this case,  wraps the search parameter with the wild card character (%) to search the database and returns all matches to be displayed.




    Problems Encountered
    • I ran into problems trying to get the spinner to show correctly until I found this entry on StackOverflow that showed how to register the Ajax events.  When running locally on the same box, this happens very fast so you really need to keep and eye on the upper right corner of the screen if you want to see the spinner.
    • I also ran into problems with the RemoteFunction due to syntax problems.  Being used to the taglibs,  I was setting the update parameter using update="mydiv" when the syntax should have been update:"mydiv".  This did not flag as an error.  The controller was called, it just never updated the target div!
    Downloads
    It was fun putting this together.   Hope it helps some of you!