Update Table Data in Grails using Ajax Calls

March 27, 2009 - 5 minute read -
ajax groovy grails

Using Ajax for simple forms can offer users a very clean, simple and fast way to input data. I came across a situation recently where I was looking into replacing a document based workflow with an application. The documents themselves contained a series of different kinds of transactions that could have occurred. There were not a set number of any of the types and the user needed a simple way to enter many rows of data.

Grails offers some very easy to use simple Ajax controls. The formRemote tag has an update parameter that can be used to specify a DOM element to update after the form is submitted. This is Ajax in it's simplest form. Submit some data and replace the contents of a DOM element with a partial-page update. This works well if you want to update a div for example. But what if you want to add a row to a table? In the standard update method, you would have to re-render the entire table contents. In many cases that will be fine, but I figured I could do better than that and just render the new row and add it to the table.

Defining Domain Classes

For this example we'll start with some simple classes. An Account can have many Checks posted against it. There are no set number, so we define a hasMany relationship between the Account and the Checks.

class Account {
    static transients = ['checksTotal']
    static hasMany = [checks:Check]</p>
<p>    Date date = new Date()</p>
<p>    Double getChecksTotal() {
        if (! checks)
            return 0
        return checks.inject(0) { current, check-> current + check.amount }
    }</p>
<p>    static mapping = {
    	  checks joinTable: false
    }
}</p>
<p>class Check {
    String name
    Double amount
    String checkNumber
    String reason
}

Creating a Controller

Next we need to create a Controller to mediate between our Domain and our Views. The Controller is setup to generate the default scaffold. We'll end up overriding the show view to add a form on the page to easily post Checks to that Account. The action is the postCheck action. This is our Ajax action that will post the Check to the Account.

class AccountController {</p>
<p>    def scaffold = true</p>
<p>    def postCheck = {
        def account = Account.get(params.id)
        if(account) {
            def check = new Check(params)
            account.addToChecks(check)
            if(! account.hasErrors() && account.save()) {
                render template:'check', bean:check, var:'check'
            }
        }
    }
}

The postCheck method calls the render method to render a template and return it as the Ajax response. This template contains the markup for a single Check row.

You can see the check template here:

</p>
<tr>
<td>${check?.name}</td></p>
<td>${check?.checkNumber}</td></p>
<td><g:formatNumber number="${check?.amount}" format="\$###,##0" /></td></p>
<td>${check?.reason}</td>
</tr>

Creating the Ajax Form

Now we need to add the form to our account/show.gsp so that we can call the AccountController.postCheck action. The example form is below. The thing to notice is that we're not using the update parameter of the formRemote, but rather the onSuccess parameter. The update method tell the Ajax callback what element to replace but we can't do that with a table. onSuccess on the other hand allows us to handle the returned values in our own way. Here' we're going to call the appendTableRow Javascript function to handle the response.

In this case I definitely don't want to replace the entire node because the first row of my table is actually the form itself. This gives it a very nice look where the data is entered under the appropriate header and then immediately shows up as the following row when the user hits enter.

</p>
<h3>Checks</h3>
<g:formRemote name="postCheck" url="[action: 'postCheck', params: [id:account?.id]]"
        onSuccess="appendTableRow('checks', e)"></p>
<table id="checks">
<thead>
<tr>
<th>Name</th></p>
<th>Check #</th></p>
<th>Amount</th></p>
<th>Reason</th>
        </tr>
        </thead></p>
<tbody>
<tr>
<td><g:textField id="name" name="name"/></td></p>
<td><g:textField id="checkNumber" name="checkNumber"/></td></p>
<td><g:textField id="amount" name="amount"/></td></p>
<td><g:textField id="reason" name="reason"/></td>
        </tr>
        <g:render template="check" collection="${account?.checks}" var="check"/>
        </tbody>
    </table>
</g:formRemote>

Luckily the appendTableRow function is made pretty easy by using the prototype APIs. The event returned to us is an HTML snippet, so in this case we just want to insert that snippet directly into the DOM which is accomplished with the insert prototype function.

function appendTableRow(tableId, e) {
    $(tableId).down('tbody').down('tr').insert({after: e.responseText});
}

In the end you have a data table that allows for easy, fast updates with almost no hassle.