Friday, November 27, 2009

Easy in-place delete with ajax and grails

I was recently asked to implement new delete functionality on a list of items. The catch of this request was that the removal of an item would be done without reloading the page. This required me to write a boatload of code in both JavaScript and Java to successfully implement. After this somewhat painful experience, I wanted to see how elegantly this experience would be handled in grails. I was not disappointed.

I started by following the grails quick-start. After creating my book class, I chose to use the grails generate-all command to create the controller and views all at once.

With the auto-generated scaffolding done, the next thing we need to do is find this line in list.gsp:

<tr class="${(i % 2) == 0 ? 'odd' : 'even'}">

and change it to the following:

<tr id="book-${bookInstance.id}" class="${(i % 2) == 0 ? 'odd' : 'even'}">

This adds an ID to each row of the list (book-1, book-2, etc.) so that we can easily identify it from javascript. By default, grails has the prototype library available, but we are going to add support for scriptaculous which will give us some slick effects to work with. This can be done by adding the following to /grails-app/views/layouts/main.gsp:



To handle the new AJAX delete functionality, we will need to add a new delete method to our controller. The following method will redirect the request to the list() method if no book exists for the provided id, or return plain text describing the result of the operation if one is found.

def ajaxDelete = {
  def book = Book.get( params.id )
   if(bookInstance) {
   try {
    bookInstance.delete(flush:true)
    render "Book ${params.id} deleted"
   } catch(org.springframework.dao.DataIntegrityViolationException e) {
    render "Book ${params.id} could not be deleted"
   }
  } else {
   flash.message = "Book not found with id ${params.id}"
   redirect(action:list)
  }
 }

To connect our user interface with the newly created ajaxDelete method we will use the fantastic tag. Add a new column to display our delete button by adding the following code to list.gsp between the
tags that are nested in the tag generated by the grails scaffolding.


delete

This tag binds the link to the BookController.ajaxDelete() method, passing the id of the current book as a parameter. The update parameter instructs grails to inject the results from a success message into the 'message' div, and any failure into the 'error' div. I have instructed the onComplete to invoke a custom bookRemoved() method, and to pass the id of the current book as a parameter. We will use the passed id to determine which row to remove on a successful deletion. Here is the code that works in tandem with the defined earlier.

<div id="message" class="error"></div>
<div id="error" class="error"></div>

 function bookRemoved(bookId) {
  Effect.toggle('book-' + bookId, 'appear');
 }


With this last bit of code complete, you can start the server and view the new AJAX delete button in action!

2 comments:

  1. Nice post man! I am gaining motivation about the grails platform through your experiences

    ReplyDelete
  2. In the ajaxDelete code above, where is bookInstance getting its value?

    In the code, should the "def book = Book.get( params.id )" be "def bookInstance = Book.get( params.id )"?

    Thanks for this post!

    ReplyDelete