
I just noticed that SABnzbd+ fetched the first two Episodes of 24 Season 8 this morning… Just as Heroes is coming to an end, and Lost‘s final season is about to start as well…
![]()
The Grails framework is a very powerful MVC web framework based on technology that has proven itself (Spring, Hibernate, Java, etc). For a while now Grails has supported Spring webflows which is a great way to develop wizard like user interfaces and underlying logic. While Grails webflows work very well for full page refreshes, they do not really work in an Ajax manner to allow rendering of fractions of pages. This would be useful for a number of reasons:
- separation of templates and template elements
- limit page rendering to the things that change instead of the complete page
- reduce bandwidth and only download require JavaScript and CSS once when the complete page is loaded.
- bookmarking the wizard would only bookmark the initial wizard page
- etc
In order for a webflow to work with Ajax the webflow should be able to render and refresh only portions of a front-end. While Grails does provide a submitToRemote tag to perform an Ajax submit, the tag itself does not work with webflows. That is because a Grails webflow links form buttons to flow actions. So, if you have a submit button named ‘delete’, the delete action will be executed in the particular flow execution element. This is done by adding ‘_eventId_delete=1′ to the POST data. However, the Grails tag submitToRemote does not do that, so the webflow is not executed properly.
So, to solve this particular issue we have to create a new tag library to extend / modify the default submitToRemote tag behaviour. In the example below I have created a new tag library called myTagLib (grails create-tag-lib com.example.myTagLib) in which I extend the default tag library. It currently contains one method (‘tag’) called ajaxButton which wraps around the submitToRemote tag to add the webflow variable (_eventId_buttonName) to the Ajax call (note that I use jQuery as Ajax provider):
package com.example
import org.codehaus.groovy.grails.plugins.web.taglib.JavascriptTagLib
class MyTagLib extends JavascriptTagLib {
// define the tag namespace (e.g.: <my:action ... />
static namespace = "my"
/**
* ajaxButton tag, this is a modified version of the default
* grails submitToRemote tag to work with grails webflows.
* Usage is identical to submitToRemote with the only exception
* that a 'name' form element attribute is required. E.g.
* <my:ajaxButton name="myAction" value="myButton ... />
*
* @see http://www.grails.org/WebFlow
* @see http://www.grails.org/Tag+-+submitToRemote
* @param Map attributes
* @param Closure body
*/
def ajaxButton = {attrs, body ->
// get the jQuery version
def jQueryVersion = grailsApplication.getMetadata()['plugins.jquery']
// fetch the element name from the attributes
def elementName = attrs['name'].replaceAll(/ /, "_")
// generate a normal submitToRemote button
def button = submitToRemote(attrs, body)
/**
* as of now (grails 1.2.0 and jQuery 1.3.2.4) the grails webflow does
* not properly work with AJAX as the submitToRemote button does not
* handle and submit the form properly. In order to support webflows
* this method modifies two parts of a 'normal' submitToRemote button:
*
* 1) replace 'this' with 'this.form' as the 'this' selector in a button
* action refers to the button and / or the action upon that button.
* However, it should point to the form the button is part of as the
* the button should submit the form data.
* 2) prepend the button name to the serialized data. The default behaviour
* of submitToRemote is to remove the element name altogether, while
* the grails webflow expects a parameter _eventId_BUTTONNAME to execute
* the appropriate webflow action. Hence, we are going to prepend the
* serialized formdata with an _eventId_BUTTONNAME parameter.
*/
if (jQueryVersion =~ /^1.([1|2|3]).(.*)/) {
// fix for older jQuery plugin versions
button = button.replaceFirst(/data\:jQuery\(this\)\.serialize\(\)/, "data:\'_eventId_${elementName}=1&\'+jQuery(this.form).serialize()")
} else {
// as of jQuery plugin version 1.4.0.1 submitToRemote has been modified and the
// this.form part has been fixed. Consequently, our wrapper has changed as well...
button = button.replaceFirst(/data\:jQuery/, "data:\'_eventId_${elementName}=1&\'+jQuery")
}
// render button
out << button
}
}
Now that we have this tag library in place we can add a webflow compatible submitToRemote button by adding the following tag:
<my:ajaxButton name="next" value="next »" url="[controller:'wizard',action:'pages']" update="[success:'wizardPage',failure:'wizardError']" class="prevnext" />
As the new ajaxButton tag is a wrapper of the submitToRemote tag it supports all arguments the submitToRemote tag supports, but in contrast it does not ignore the name argument. In fact, the name argument is required as it is responsible for executing webflow actions. The only thing which remains is to make sure the webflow only renders portions, and not complete pages.
Observe the following WizardController code:
package com.example
class WizardController {
def index = {
redirect(action:'pages')
}
def pagesFlow = {
// render the main wizard page
mainPage {
render(view:"/wizard/index")
on("next") {
println "next page!"
}.to "pageTwo"
}
// render page one
pageOne {
render(view:"_one")
on("next") {
println "next page!"
}.to "pageTwo"
}
// render page two
pageTwo {
render(view:"_two")
on("next") {
println "next page!"
}.to "pageThree"
on("previous") {
println "previous page!"
}.to "pageOne"
}
}
}
As seen in the webflow we have a two paged wizard. The mainpage renders the initial wizard page (index.gsp), while the pageOne and pageTwo actions only render portions of a page (respectively: pages/_one.gsp and pages/_two.gsp). We could split this up into the following four templates. Note that you can seperate templates even more, but for this example I will stick to these four:
index.gsp
<html> <head> <meta name="layout" content="main" /> </head> <body> <g:render template="common/wizard"/> </body> </html>
common/_wizard.gsp
<div id="wizard" class="wizard">
<h1>Proof of concept AJAXified Webflow Wizard</h1>
<g:form action="pages" name="_wizard" >
<div id="wizardPage">
<g:render template="pages/one"/>
</div>
<div id="wizardError" class="error"/>
</g:form>
</div>
pages/_one.gsp
...my form content for page 1...<br /> <my:ajaxButton name="next" value="next »" url="[controller:'wizard',action:'pages']" update="[success:'wizardPage',failure:'wizardError']" class="prevnext" />
pages/_two.gsp
...my form content for page 2...<br /> <my:ajaxButton name="previous" value="« prev" url="[controller:'wizard',action:'pages']" update="[success:'wizardPage',failure:'wizardError']" class="prevnext" />
As you can see, index.gsp will render default layout (add your CSS here) and renders common/_wizard.gsp. Wizard.gsp will, on it’s turn, render the first web flow template (pages/_one.gsp) which includes a form and the ajaxButton we created in the tag library above. This button will now use and Ajax call to submit to our webflow and execute the on(“next”) action in the pageOne part of our webflow, which will make the logic jump to the pageTwo part. Upon entry pageTwo will render pages/_two.gsp and return the rendered content to the Ajax call.
The ajaxButton contains update=”[success:'wizardPage'...]” which makes the Ajax call refresh the wizardPage div with the result of the Ajax call. Hence, the wizardPage div will now contain the rendered content of pages/_two.gsp with it’s own form.
Hence, we have Ajaxified our Grails webflow