Specifically, it would be great if we didn't have to click on a task to open the show view and then click again to edit it, just to mark a task as completed. What would be really nifty is if we could mark a task as completed by clicking on a simple checkbox. And it turns out that Grails provides an Ajax tag -
<g:remotefunction>
- that's just what we need.Here's a picture of our current list view. We'll put our checkbox in the last column, since we don't really need to repeat the Event name.
We'll modify the list view in two places. First, in the
<head>
section, we'll replace this:
<tr>
<g:sortableColumn property="id" title="Id" />
<g:sortableColumn property="title" title="Title" />
<g:sortableColumn property="notes" title="Notes" />
<th>Assigned To</th>
<g:sortableColumn property="dueDate" title="Due Date" />
<th>Event</th>
</tr>
With this:
<tr>
<g:sortableColumn property="id" title="Id" />
<g:sortableColumn property="title" title="Title" />
<g:sortableColumn property="notes" title="Notes" />
<th>Assigned To</th>
<g:sortableColumn property="dueDate" title="Due Date" />
<th>Completed</th>
</tr>
All we did here is change the text of the column heading. (Trivial, yet necessary.)
Next, we will modify the table row that currently displays the Event name. Let's replace this:
<td>${fieldValue(bean:taskInstance, field:'event')}</td>
with this:
<td>
<g:checkBox name='completed' value="${taskInstance.completed}"
onclick="${remoteFunction(action:'toggleCompleted', id:taskInstance.id,
params:'\'completed=\' + this.checked')}" />
</td>
Here we are using a
<g:checkBox>
tag to show whether a Task is completed or not. We will also use it to toggle the completed value. For that, we use a onclick
event of the checkBox
. We'll talk a bit more about the remoteFunction
, but first, there's one more thing we need.In order to use any of Grails' JavaScript tags, we need to set a JavaScript library to be used. We do this with the
<g:javascript>
tag, which can be placed anywhere in the <head>
section of our page. Like so:
<g:javascript library=”prototype” />
The
remoteFunction
tag can take a controller
, action
, id
and params
. We will use all except the controller
, since we are on a page produced by the TaskController
, which is the controller we'll be using. This tag can also take an update
attribute, but we don't want to update anything on the page, so we'll skip that one too. For the action
we'll use 'toggleCompleted'
(which we will write shortly). The id
will be the id of the taskInstance
being displayed. Then comes the params
.The
params
property can be a Map
just as in other GSP tags, but it can also be a String
formatted like request parameters. That's the way we use it here: params:'\'completed=\' + this.checked'
. Since we are already inside a double quoted GString
, we have to use single quotes for the params
value, and that means escaping the single quotes that need to appear in the final value. The phrase “this.checked
” is a literal that will be passed into the resulting JavaScript function, where it will be evaluated to the checked
value of the checkbox. This value will end up in our controller as either 'true
' or 'false
'.If we were to look at the page with Firebug (or a similar tool) we would see that the value being assigned to the
onclick
event is something like this:
new Ajax.Request('/TekDays/task/toggleCompleted/1',{asynchronous:true,evalScripts:true,parameters:'completed=' + this.checked});
Here's what our page looks like now:
As I mentioned earlier, we don't need to update anything on the page when the user clicks the checkbox. The checkbox already gives the visual cue we are looking for. What we want is to set the
completed
property on the corresponding Task
instance and then save it. So, let's implement the toggleCompleted
action in our TaskController
. Open /grails-app/controllers/TaskController.groovy
and add the following action:
def toggleCompleted = {
def task = Task.get(params.id)
if (task){
task.completed = params.completed == 'true'
task.save()
}
render ''
}
The first step in this action is to get the
Task
based on the id
passed in. Then if we get that successfully, we'll set its completed
property based on the value of the completed
parameter. You might be tempted to try task.completed = params.completed
, but params.completed
is a String
(either 'true
' or 'false
') and not a Boolean
value. So we have to compare against a String
literal.Next we save the Task instance, and finally render a blank
String
to prevent any attempt to render something back to the browser.That's all there is to it. We can now mark a task complete or incomplete right from the list view - no form needed. Pretty handy.
(Now we could also clean up the page in other ways, as we did on our
TekEvent
list view in section 6.2, “Modifying the Scaffolded Views,” on page 92.)