Sunday, April 27, 2008

Grails and Simple AJAX

For me there is no doubt that Grails is currently the most exciting existing web framework. But these days when talking about web development first thing that comes into mind is AJAX. And of course there is number of AJAX plugins for the grails (GWT, Flex, YahooUI...). But what when you need only simple AJAX functionality. Do you really have to include some of these big frameworks. Well actually no. Grails has built in simple AJAX support. Following tags can be particularly useful for simple AJAX functionality incorporated into Grails web application.

<g:formRemote  
<g:remoteField
<g:remoteFunction
<g:remoteLink
<g:submitToRemote


Among other common attributes of mentioned tags following are really useful:

  • controller - name of the controller to use

  • action - name of the action within controller

  • update - element to update or map containing elements to update in the case of success or failure

  • params - parameters to send with the call

Attribute that really enables AJAX behavior is value of update attribute. Content of the page placed between

<div id="value_of_the_update_attribute">
</div>

will be replaced with the result of the action specified by the action attribute.

For other attributes and more details you can have a look at the grails documentation.

Let explore remoteFunction tag as this one is interesting. This tag can be attached to the onX events of other Grails or HTML tags. To create simple grails application with the controller in the command line type:

grails create-app simple-ajax

Now switch to simple-ajax directory and type:

grails create-controller example

Now we can create view for the ExampleController so the ground for our example will be prepared. In the simple-ajax/grails-app/views/example directory create example.gsp file with the following code:

<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
<meta name="layout" content="main" />
<title>Edit Project</title>
<g:javascript library="prototype" />
</head>
<body>
<div class="body">
</div>
</body>
</html>

So far important line to notice that imports one of the by default supported Ajax frameworks is

<g:javascript library="prototype" />

To prepare page extend the example.gsp with the following code.

<select onchange="${remoteFunction(controller:'example', action:'continentSelected',update:'continent', params:'\'continent=\' + this.value' )}">
<option>select continent</option>
<option>America</option>
<option>Asia</option>
<option>Europa</option>
<option>Africa</option>
<option>Australia</option>
</select>
<div/>
<div id="continent">
Selected continent:
</div>

To get more info how to work with g:select tag have a look at this post.

Idea is that when continent is selected we will display selected continent :) This will ensure remoteFunction tag assigned to the onChange event of the select tag. Still we need to update controller to support this behavior. So add the following code into ExampleController

def index = {
render(view:'example')
}

def continentSelected = {
render params['continent']
}

Controller is simple enough. index action will display our example.gsp page when navigating to ExampleController while the continentSelected is action called from the remoteFunction tag. All it does it returns selected continent that is stored within params of the request. So when the user selects continent from the select box, ajax call to the continentSelected is issued and the content in the div id="continent" is replaced with the result from the continentSelected action.

So give it a try. Run the application by typing grails run-app in the command line and in your browser navigate to http://localhost:8080/simple-ajax

And we got simple Ajax. If this is not enough to see possibilities emerging change the controller code as follows:

def index = {
render(view:'example')
}

def continentSelected = {
def lst
def continent = params['continent']
if ( continent == 'America') {
lst = ['USA', 'Argentina']
} else if (continent == 'Asia') {
lst = ['China', 'India']
} else if (continent == 'Europa') {
lst = ['Slovakia', 'Serbia']
} else if (continent == 'Africa') {
lst = ['Egypt', 'Tunis']
} else if (continent == 'Australia') {
render ""
return
} else {
render ""
return
}
render g.select(from:lst)
}

What we have reused here is possibility to use grails tags as method calls. Now have a look how to work with grails templates and tags and you will see that actually default AJAX support offered by Grails needs not to be so simple if you don't need fancy front end. Maybe more about it in some future posts.

19 comments:

Anonymous said...

Great stuff. Thanks

Beaumont Muni said...

Thanks ... that was way cool. Helped me get my first exposure to Ajax. Something I would not really care for as a server-side developer.

Anonymous said...

On the remoteFunction, it looks like the 'params' part is cut off. I'm trying to use this tag but my params submission value is null. What is the syntax on passing one or more params to the controller?

jan said...

Cut of string for params is:

params:'\'continent=\' + this.value' )}">

Passing params to tags can be very often tricky and I also get often confused about which syntax to use.

Ursula said...

Thanks for the syntax on the params. I'm getting really frustrated with the Grails documentation. So many of the examples don't work!

Grails may be really cool but I'm finding it's not very useful in real life. The company I'm contracting at decided to use Grails for two smaller projects. They are significantly behind.

The lack of (good) documentation and the fact that Grails does so much behind the scenes just seems to mean that they've only considered the obvious, success case. We've had to do a lot of workarounds to get (simple) functionality to work properly.

I wouldn't recommend using Grails in a production environment, at least not yet. It's still too young a framework.

jan said...

@ursula
Thanks for the opinion Ursula but I must completely disagree with you in both cases.

I believe that documentation is far better for Grails than for the most web frameworks and I usually do find answers within documentation.

Regarding production readiness, I consider it production ready and would recommend it for the most commercial projects. I did not get any real problem while working with grails tutorials. I have more than a year of experience on commercial projects with the JSF and I believe that those projects would be at least 2-3 times faster with Grails.

petN said...

How to add more parameters ?
I tried like this
params:'\'par1=\'+ sricpt1()+','\'par2=\'+sricpt2()' )
but failed...

Dennis said...

Thanks for the nice example. I searched around but could not find a way to pass back a List with based on multiple selections.

I tried/errored until the following worked, but there must be an easier way. Any suggestions?

The controller:
class ExampleController {

def index = {
render(view:'example')
}

def americanCountries = ['USA', 'Argentina']
def asianCountries = ['China', 'India']
def europeanCountries = ['Slovakia', 'Serbia']
def africanCountries = ['Egypt', 'Tunisia']
def australianCountries = ['Australia']

def continentSelected = {
def lst = []
def continents = params['continent']

if ( continents.contains('America')) {
lst += americanCountries
}
if (continents.contains('Asia')) {
lst += asianCountries
}
if (continents.contains('Europa')) {
lst += europeanCountries
}
if (continents.contains('Africa')) {
lst += africanCountries
}
if (continents.contains('Australia')) {
lst += australianCountries
}

if (lst?.size() == 0) {
render ""
return
}
render g.select(from:lst)
}
}

The view:(sans tag brackets)

html
head
meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/
meta name="layout" content="main" /
title Multiple Select RemoteFunction Example /title
g:javascript library="prototype" /
/head
body
div class="body" select
id="continents"
size="3"
multiple="yes"
onchange="getSelected('continents');
${remoteFunction(controller:'example', action:'continentSelected',update:'countries_div', params:'\'continent=\' + selectedArray' )}"
option America /option
option Asia /option
option Europa /option
option Africa /option
option Australia /option
/select
div/
Countries in selected continents:
div id="countries_div"
Select one or more continents.
/div
/div
script
var selectedArray = '';
function getSelected(arg){
var selObj = document.getElementById(arg);
selectedArray = ''
for (i=0; i < selObj.options.length; i++) {
if (selObj.options[i].selected) {
if (i > 0)
selectedArray += ","
selectedArray += '\'' + selObj.options[i].value + '\''
}
}
return selectedArray;
}
/script
/body
/html

jan said...

@Dennis
I am not sure if this is what you thought about but my solution would look something like this:

def countries =
['America'['USA','Argentina'],
'Asia':['China', 'India']...]

def lst = []

def continents = arams['continent']

continents.each {continent ->
lst += countries[continent]
}

if (lst?.size() == 0) {
render ""
return
} else {
render g.select(from:lst)
}

cloudboy00 said...

I lot of people seem to be having trouble with multiple params. Here's how to do it:

params:'\'tasknameenum=\'document.getElementById(\'tasknameid\').value\'&enabled=\'+document.getElementById(\'taskchkid\').value'

Here is an example of using two params in a remoteFunction call or any similar remote call in Grails, which can be extended to use more than two if needed by separating subsequent params with &

Dennis said...

Hi Jan,
Thanks for the reply. My issue was getting multiple selections passed back to the controller.

1. If params:'\'continent=\' + this.value' is used in the remoteFunction only the first selected value is passed to the controller. That is reason for having function getSelected(arg).
2. This code
continents.each {continent ->
lst += countries[continent]
}

in the controller the closure argument, continent, gets parsed as a character array ('A','s','i','a') instead of a string (Asia).
This was the case regardless of how I wrote the javascript. It must have something to do with how the result gets passed from javascript into the params map.

I had to resort to this code to get strings:
def cl = []
if (continents.contains(",")) {
def a = []
a = continents.split(',')
a.each {
cl << it
}
}
else {
cl << continents
}
cl.each {continent ->
lst += countries[continent]
}

Anonymous said...

params:'\'tasknameenum=\'document.getElementById(\'tasknameid\').value\'&enabled=\'+document.getElementById(\'taskchkid\').value'

using the multiple params doesn't work in grails 1.1

I'am geting this error "missing } after property list"

Federico said...

Thank you for this post! It really made me start using grails after months I was thinking about it :)

"SandY" said...

I'am geting this error "missing } after property list.
can u suggest any solution?

jan said...

@SandY
Sorry but not sure.
Try googling and maybe with simplest possible example and then slowly extend it.

Dhruv said...

Hi,

I had checked your post bellow is your function.
if I want to use dynamic value in update how can i do that. I have that dynamic value but how can i pass that value to update.

${remoteFunction(controller:'example', action:'continentSelected',update:'continent', params:'\'continent=\' + this.value'

jan said...

Sorry but I have not time to try it right now. Try to search for it and experiment. No better advice at this moment.

Anonymous said...

awesome dude .. thanks

Anonymous said...

for grails 2.0.x you need to add the following code to the \div or it won't refresh

Selected continent: