Discovering Grails Web Flows
I recently discovered Grails Web Flows. This is basically a Groovy DSL on top of Spring Web Flows.
I did a quick dirty and imperfect example which is a number guessing game. I took inspiration from another Grails Web Flows tutorial.
In this post, I will limit myself to some very quick description of web flows, and I urge you to read the documentations (from Grails and Spring). The idea behind web flows is to provide a framework for web conversations that span multiple HTTP requests, such as wizards, payment processes and such. A "web flow" is modeled as a state machine, where:
- states represent either an action (i.e., some code) or a view, and
- transitions are triggered based on the outcome from the state, which is a simple string.
As such, web flows free the developer from much repetitive logic when you need to control states of a web conversation.
In this example, the number to guess is 25 (we could have made it non-static, but who cares?). The user has 5 attempts. The corresponding flow is:

Grails provides web flows out of the box. All you have to do is to create a special controller closure property which ends by "Flow":
class GuessController { def toGuess = "25" def index = { redirect(action: "guess") } def guessFlow = { ... } }
We now have to define the "guess" flow. As often in Groovy and Grails, it boils down to some intuitive builder structure:
class GuessController { def toGuess = "25" def index = { redirect(action: "guess") } def guessFlow = { start { action { [guessLeft: 5] } on("success").to("ask") } ask { on("evaluate").to("evaluate") } done() fail() evaluate { action { if (params.number == toGuess) { return yes() } flow.guessLeft = flow.guessLeft - 1 if (flow.guessLeft == 0) { return loose() } else { return no() } } on("yes").to("done") on("no").to("ask") on("loose").to("fail") } } }
startis an action state, which is called first, and that puts aguessLeftvariable in the flow context.- The
askstate displays a web page, then delegates the logic to theevaluateaction state. - The
evaluateaction state either pushes the flow to the final states, or loops back to theaskstate if there are guesses left. doneandfailare final states that just show some web pages.
Finally there are 3 views that you need to create. Since the web flow is named "guess", they must be in a special "guess" subfolder of the matching controller views. Also, they must be named by the state names.
ask.gsp
<%@ page contentType="text/html;charset=UTF-8" %>
<html>
<head><title>Simple GSP page</title></head>
<body>
<g:javascript library="prototype"/>
<g:javascript>
document.observe("dom:loaded", function() {
$("field").focus();
});
</g:javascript>
<h1>Guess</h1>
<p>Guesses left: <strong>${guessLeft}</strong>.</p>
<p>
<g:form action="guess">
<g:textField id="field" name="number" value="0" />
<g:submitButton name="evaluate" value="Try this number!" />
</g:form>
</p>
</body>
</html>Note that the guessLeft flow context variable is available directly in the GSP. Also, note that the form action must match the web flow name and that the submit button name is the outcome of the view state.
done.gsp
<%@ page contentType="text/html;charset=UTF-8" %> <html> <head><title>Simple GSP page</title></head> <body>You won!</body> </html>
fail.gsp
<%@ page contentType="text/html;charset=UTF-8" %> <html> <head><title>Simple GSP page</title></head> <body>You failed...</body> </html>

